Compare commits

...

No commits in common. "1.1" and "master" have entirely different histories.
1.1 ... master

369 changed files with 33146 additions and 3183 deletions

164
.editorconfig Normal file
View File

@ -0,0 +1,164 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# C# files
[*.cs]
indent_size = 4
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_within_query_expression_clauses = true
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# only use var when it's obvious what the variable type is
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# Code style defaults
dotnet_sort_system_directives_first = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Space preferences
csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
[*.{asm,inc}]
indent_size = 8
# Visual Studio Solution Files
[*.sln]
indent_style = tab
# Visual Studio XML Project Files
[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# XML Configuration Files
[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
indent_size = 2
[CMakeLists.txt]
indent_size = 2
# Makefiles
[Makefile]
indent_style = tab
# Batch Files
[*.{cmd,bat}]
indent_size = 2
end_of_line = crlf
# Bash Files
[*.sh]
end_of_line = lf
# Web Files
[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,pcss,svg,vue}]
indent_size = 2
# Markdown Files
[*.md]
trim_trailing_whitespace = false
# JSON Files
[*.{json,json5,webmanifest}]
indent_size = 2

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: "https://fmodel.app/donate"

45
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Bug Report
description: File a bug report
title: "Bug Title"
labels: [bug]
assignees:
- iAmAsval
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Keep in mind that screenshots and log files help us a lot so don't forget to provide one or both of those (drag and drop files in a text area).
Your bug report will be closed without explanation if you don't follow the following rules:
- Bad bug explanation will result in bad support and probably on a negative tone
- This template shouldn't be used to ask how to use FModel or a certain feature FModel provides
- Bug reports must always use the latest FModel with the latest available version of the game you use
- If you can't load files, it's probably because of your AES key, no need to file a report
- We absolutely do not support modding
- type: input
id: game
attributes:
label: Game
placeholder: ex. Fortnite, Valorant, ...
validations:
required: true
- type: textarea
id: error
attributes:
label: Error
description: Tell us what FModel says about the error, from the console and / or the log file
placeholder: ex. [ERR] Could not export 'EditorClientAssetRegistry.bin'
render: shell
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: How do you trigger this bug? Please walk us through it step by step.
placeholder: |
1.
2.
3.
...
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Discord Server
url: https://fmodel.app/discord
about: Please ask and answer questions here.

22
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Feature Request
description: Submit a new feature request
title: "Feature Title"
labels: [suggestion]
assignees:
- iAmAsval
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request! Before going any further, make sure what you're about to submit doesn't already exist.
Your feature request will be closed without explanation if you don't follow the following rules:
- This template shouldn't be used to ask how to use FModel or a certain feature FModel provides
- We absolutely do not support modding
- type: textarea
id: description
attributes:
label: Description
description: Tell us what you want FModel to be able to do
placeholder: Please describe with details and how it could be done if possible...
validations:
required: true

48
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: FModel Builder
on:
workflow_dispatch:
inputs:
appVersion:
description: 'FModel Version And Release Tag'
required: true
default: '4.0.X.X'
jobs:
build:
runs-on: windows-latest
steps:
- name: GIT Checkout
uses: actions/checkout@v2
with:
submodules: 'true'
- name: Fetch Submodules Recursively
run: git submodule update --init --recursive
- name: .NET 8 Setup
uses: actions/setup-dotnet@v2
with:
dotnet-version: '8.0.x'
- name: .NET Restore
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish FModel -c Release --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 -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
- name: ZIP File
uses: papeloto/action-zip@v1
with:
files: ./FModel/bin/Publish/FModel.exe
dest: FModel.zip # will end up in working directory not the Publish folder
- name: GIT Release
uses: marvinpinto/action-automatic-releases@latest
with:
title: "FModel v${{ github.event.inputs.appVersion }}"
automatic_release_tag: ${{ github.event.inputs.appVersion }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false
files: FModel.zip

65
.github/workflows/qa.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: FModel QA Builder
on:
push:
branches: [ dev ]
jobs:
build:
runs-on: windows-latest
steps:
- name: GIT Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: .NET 8 Setup
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: .NET Restore
run: dotnet restore FModel
- 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
- name: ZIP File
uses: thedoctor0/zip-release@0.7.6
with:
type: zip
filename: ${{ github.sha }}.zip # will end up in working directory not the Publish folder
path: ./FModel/bin/Publish/FModel.exe
- name: Edit QA Artifact
uses: ncipollo/release-action@v1.14.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: 'FModel QA Testing'
body: 'Dev builds'
tag: 'qa'
artifacts: ${{ github.sha }}.zip
prerelease: true
allowUpdates: true
- name: Get Version
id: package_version
uses: kzrnm/get-net-sdk-project-versions-action@v2
with:
proj-path: ./FModel/FModel.csproj
- name: FModel Auth
id: fmodel_auth
uses: fjogeleit/http-request-action@v1.15.5
with:
url: "https://api.fmodel.app/v1/oauth/token"
data: '{"username": "${{ secrets.API_USERNAME }}", "password": "${{ secrets.API_PASSWORD }}"}'
- name: FModel Deploy Build
uses: fjogeleit/http-request-action@v1.15.5
with:
url: "https://api.fmodel.app/v1/infos/${{ secrets.QA_ID }}"
method: "PATCH"
bearerToken: ${{ fromJson(steps.fmodel_auth.outputs.response).accessToken }}
data: '{"version": "${{ steps.package_version.outputs.version }}-dev+${{ github.sha }}", "downloadUrl": "https://github.com/4sval/FModel/releases/download/qa/${{ github.sha }}.zip"}'

53
.gitignore vendored
View File

@ -4,6 +4,7 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files # User-specific files
*.rsuser
*.suo *.suo
*.user *.user
*.userosscache *.userosscache
@ -12,6 +13,9 @@
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
# Mono auto generated files
mono_crash.*
# Build results # Build results
[Dd]ebug/ [Dd]ebug/
[Dd]ebugPublic/ [Dd]ebugPublic/
@ -19,13 +23,17 @@
[Rr]eleases/ [Rr]eleases/
x64/ x64/
x86/ x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/ bld/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
[Ll]og/ [Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory # Visual Studio 2015/2017 cache/options directory
.vs/ .vs/
.idea/
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/ #wwwroot/
@ -36,9 +44,10 @@ Generated\ Files/
[Tt]est[Rr]esult*/ [Tt]est[Rr]esult*/
[Bb]uild[Ll]og.* [Bb]uild[Ll]og.*
# NUNIT # NUnit
*.VisualState.xml *.VisualState.xml
TestResult.xml TestResult.xml
nunit-*.xml
# Build Results of an ATL Project # Build Results of an ATL Project
[Dd]ebugPS/ [Dd]ebugPS/
@ -52,7 +61,6 @@ BenchmarkDotNet.Artifacts/
project.lock.json project.lock.json
project.fragment.lock.json project.fragment.lock.json
artifacts/ artifacts/
**/Properties/launchSettings.json
# StyleCop # StyleCop
StyleCopReport.xml StyleCopReport.xml
@ -60,7 +68,7 @@ StyleCopReport.xml
# Files built by Visual Studio # Files built by Visual Studio
*_i.c *_i.c
*_p.c *_p.c
*_i.h *_h.h
*.ilk *.ilk
*.meta *.meta
*.obj *.obj
@ -77,6 +85,7 @@ StyleCopReport.xml
*.tlh *.tlh
*.tmp *.tmp
*.tmp_proj *.tmp_proj
*_wpftmp.csproj
*.log *.log
*.vspscc *.vspscc
*.vssscc *.vssscc
@ -119,9 +128,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
*.DotSettings.user *.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in # TeamCity is a build add-in
_TeamCity* _TeamCity*
@ -179,6 +185,8 @@ PublishScripts/
# NuGet Packages # NuGet Packages
*.nupkg *.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore # The packages folder can be ignored because of Package Restore
**/[Pp]ackages/* **/[Pp]ackages/*
# except build/, which is used as an MSBuild target. # except build/, which is used as an MSBuild target.
@ -203,12 +211,14 @@ BundleArtifacts/
Package.StoreAssociation.xml Package.StoreAssociation.xml
_pkginfo.txt _pkginfo.txt
*.appx *.appx
*.appxbundle
*.appxupload
# Visual Studio cache files # Visual Studio cache files
# files ending in .cache can be ignored # files ending in .cache can be ignored
*.[Cc]ache *.[Cc]ache
# but keep track of directories ending in .cache # but keep track of directories ending in .cache
!*.[Cc]ache/ !?*.[Cc]ache/
# Others # Others
ClientBin/ ClientBin/
@ -221,7 +231,7 @@ ClientBin/
*.publishsettings *.publishsettings
orleans.codegen.cs orleans.codegen.cs
# Including strong name files can present a security risk # Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424) # (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk #*.snk
@ -252,6 +262,9 @@ ServiceFabricBackup/
*.bim.layout *.bim.layout
*.bim_*.settings *.bim_*.settings
*.rptproj.rsuser *.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes # Microsoft Fakes
FakesAssemblies/ FakesAssemblies/
@ -287,12 +300,8 @@ paket-files/
# FAKE - F# Make # FAKE - F# Make
.fake/ .fake/
# JetBrains Rider # CodeRush personal settings
.idea/ .cr/personal
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS) # Python Tools for Visual Studio (PTVS)
__pycache__/ __pycache__/
@ -317,7 +326,7 @@ __pycache__/
# OpenCover UI analysis results # OpenCover UI analysis results
OpenCover/ OpenCover/
# Azure Stream Analytics local run output # Azure Stream Analytics local run output
ASALocalRun/ ASALocalRun/
# MSBuild Binary and Structured Log # MSBuild Binary and Structured Log
@ -326,5 +335,17 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file # NVidia Nsight GPU debugger configuration file
*.nvuser *.nvuser
# MFractors (Xamarin productivity tool) working folder # MFractors (Xamarin productivity tool) working folder
.mfractor/ .mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "CUE4Parse"]
path = CUE4Parse
url = https://github.com/FabianFG/CUE4Parse

1
CUE4Parse Submodule

@ -0,0 +1 @@
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f

View File

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FModel", "FModel\FModel.csproj", "{71A31C47-30BC-4CB5-AD89-81E5008F4BEB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{71A31C47-30BC-4CB5-AD89-81E5008F4BEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71A31C47-30BC-4CB5-AD89-81E5008F4BEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71A31C47-30BC-4CB5-AD89-81E5008F4BEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71A31C47-30BC-4CB5-AD89-81E5008F4BEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CBD904D1-57AE-4FCF-B773-F675B4C3C0A6}
EndGlobalSection
EndGlobal

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

19
FModel/App.xaml Normal file
View File

@ -0,0 +1,19 @@
<Application x:Class="FModel.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
StartupUri="MainWindow.xaml" ShutdownMode="OnMainWindowClose"
Exit="AppExit" DispatcherUnhandledException="OnUnhandledException">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>
<Color x:Key="{x:Static adonisUi:Colors.AlertColor}">#D49220</Color>
<Color x:Key="{x:Static adonisUi:Colors.ErrorColor}">#C22B2B</Color>
</ResourceDictionary>
</Application.Resources>
</Application>

178
FModel/App.xaml.cs Normal file
View File

@ -0,0 +1,178 @@
using AdonisUI.Controls;
using Microsoft.Win32;
using Serilog;
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Threading;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using Newtonsoft.Json;
using Serilog.Sinks.SystemConsole.Themes;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
static extern string BrandingFormatString(string format);
protected override void OnStartup(StartupEventArgs e)
{
#if DEBUG
AttachConsole(-1);
#endif
base.OnStartup(e);
try
{
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
var currentDir = Directory.GetCurrentDirectory();
var dirInfo = new DirectoryInfo(currentDir);
if (dirInfo.Attributes.HasFlag(FileAttributes.Archive))
throw new Exception("FModel cannot be run from an archive file. Please extract it and try again.");
if (dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
throw new Exception("FModel cannot be run from a read-only directory. Please move it to a writable location.");
UserSettings.Default.OutputDirectory = Path.Combine(currentDir, "Output");
}
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
{
createMe = true;
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
{
createMe = true;
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
{
createMe = true;
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
{
createMe = true;
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
{
createMe = true;
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
#if DEBUG
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#else
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#endif
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
}
private void AppExit(object sender, ExitEventArgs e)
{
Log.Information("");
Log.CloseAndFlush();
UserSettings.Save();
Environment.Exit(0);
}
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error("{Exception}", e.Exception);
var messageBox = new MessageBoxModel
{
Text = $"An unhandled exception occurred: {e.Exception.Message}",
Caption = "Fatal Error",
Icon = MessageBoxImage.Error,
Buttons = new[]
{
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
},
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Delete();
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
}
private string GetOperatingSystemProductName()
{
var productName = string.Empty;
try
{
productName = BrandingFormatString("%WINDOWS_LONG%");
}
catch
{
// ignored
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
}
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
{
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
if (rk != null)
return rk.GetValue(name, null) as string;
return string.Empty;
}
}

10
FModel/AssemblyInfo.cs Normal file
View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -1,33 +0,0 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace FModel
{
class Config
{
private static string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments).ToString() + "\\FModel";
private const string configFile = "config.json";
public static ConfigFile conf;
static Config()
{
if (!Directory.Exists(docPath))
Directory.CreateDirectory(docPath);
if (!File.Exists(docPath + "/" + configFile))
{
string json = JsonConvert.SerializeObject(conf, Formatting.Indented);
File.WriteAllText(docPath + "/" + configFile, json);
}
else
{
string json = File.ReadAllText(docPath + "/" + configFile);
conf = JsonConvert.DeserializeObject<ConfigFile>(json);
}
}
}
public struct ConfigFile
{
public string pathToFortnitePAKs;
}
}

57
FModel/Constants.cs Normal file
View File

@ -0,0 +1,57 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Reflection;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Extensions;
namespace FModel;
public static class Constants
{
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion.SubstringAfter('+');
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
public static readonly FGuid ZERO_GUID = new(0U);
public const float SCALE_DOWN_RATIO = 0.01F;
public const int SAMPLES_COUNT = 4;
public const string WHITE = "#DAE5F2";
public const string GRAY = "#BBBBBB";
public const string RED = "#E06C75";
public const string GREEN = "#98C379";
public const string YELLOW = "#E5C07B";
public const string BLUE = "#528BCC";
public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a";
public const string GH_REPO = "https://api.github.com/repos/4sval/FModel";
public const string GH_COMMITS_HISTORY = GH_REPO + "/commits";
public const string GH_RELEASES = GH_REPO + "/releases";
public const string DONATE_LINK = "https://fmodel.app/donate";
public const string DISCORD_LINK = "https://fmodel.app/discord";
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public const string _NO_PRESET_TRIGGER = "Hand Made";
public static int PALETTE_LENGTH => COLOR_PALETTE.Length;
public static readonly Vector3[] COLOR_PALETTE =
{
new (0.231f, 0.231f, 0.231f), // Dark gray
new (0.376f, 0.490f, 0.545f), // Teal
new (0.957f, 0.263f, 0.212f), // Red
new (0.196f, 0.804f, 0.196f), // Green
new (0.957f, 0.647f, 0.212f), // Orange
new (0.612f, 0.153f, 0.690f), // Purple
new (0.129f, 0.588f, 0.953f), // Blue
new (1.000f, 0.920f, 0.424f), // Yellow
new (0.824f, 0.412f, 0.118f), // Brown
new (0.612f, 0.800f, 0.922f) // Light blue
};
}

View File

@ -0,0 +1,42 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.BB;
public class BaseBreakersIcon : BaseIcon
{
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient");
Background = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") };
Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData", "UnlockPortraitGuideImage"))
Preview = Utils.GetBitmap(iconTextureAssetData);
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
Description = description.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return new[] { ret };
}
}

View File

@ -0,0 +1,139 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseBundle : UCreator
{
private IList<BaseQuest> _quests;
private const int _headerHeight = 100;
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
}
public override void ParseForInfo()
{
_quests = new List<BaseQuest>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
{
foreach (var quest in quests)
{
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
BaseQuest q;
var path = questDefinition.AssetPathName.Text;
do
{
if (!Utils.TryLoadObject(path, out UObject uObject)) break;
q = new BaseQuest(uObject, Style);
q.ParseForInfo();
_quests.Add(q);
path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName;
} while (!string.IsNullOrEmpty(q.NextQuestName));
}
}
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
{
foreach (var completionReward in completionRewards)
{
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out int quantity, "Quantity") ||
!reward.TryGetValue(out string templateId, "TemplateId") ||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
if (!itemDefinition.AssetPathName.IsNone &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
{
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
}
else if (!string.IsNullOrWhiteSpace(templateId))
{
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
}
}
}
}
Height += 256 * _quests.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawQuests(c);
return new[] { ret };
}
private readonly SKPaint _headerPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
private void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
var background = _quests.Count > 0 ? _quests[0].Background : Background;
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { background[0].WithAlpha(50), background[1].WithAlpha(50) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
{
_headerPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawQuests(SKCanvas c)
{
var y = _headerHeight;
foreach (var quest in _quests)
{
quest.DrawQuest(c, y);
y += quest.Height;
}
}
}

View File

@ -0,0 +1,278 @@
using System.Linq;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseCommunity : BaseIcon
{
private readonly CommunityDesign _design;
private string _rarityName;
private string _source;
private string _season;
private bool _lowerDrawn;
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
{
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series"))
{
_rarityName = series.Name;
}
else if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList") &&
dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
{
_rarityName = dl.NonConstStruct?.Get<FPackageIndex>("Series").Name;
}
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer") &&
componentContainer.TryGetValue(out FPackageIndex[] components, "Components") &&
components.FirstOrDefault(c => c.Name.Contains("Series")) is { } seriesDef &&
seriesDef.TryLoad(out var seriesDefObj) && seriesDefObj is not null &&
seriesDefObj.TryGetValue(out series, "Series"))
{
_rarityName = series.Name;
}
else
{
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
}
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name.ToUpper();
DisplayName = DisplayName.ToUpper();
Description = Description.ToUpper();
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (_design == null)
{
base.Draw(c);
}
else
{
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
DrawToBottom(c, font, _season);
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
DrawToBottom(c, font, _source);
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
}
return new[] { ret };
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (_design == null) return;
if (_design.DrawSource)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
}
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] { "Cosmetics.UserFacingFlags." } : new[] { "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender." };
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
}
private string GetCosmeticSet(string setName, bool bShort)
{
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
}
private string GetCosmeticSeason(string seasonNumber, bool bShort)
{
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
(int chapterIdx, int seasonIdx) = GetInternalSID(int.Parse(s));
return $"C{chapterIdx} S{seasonIdx}";
}
private new void DrawBackground(SKCanvas c)
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
}
else
{
base.DrawBackground(c);
}
}
private new void DrawTextBackground(SKCanvas c)
{
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
_lowerDrawn = true;
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
}
else
{
base.DrawTextBackground(c);
}
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
{
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
}
else
{
base.DrawDisplayName(c);
}
}
private new void DrawDescription(SKCanvas c)
{
if (string.IsNullOrEmpty(Description)) return;
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
{
DescriptionPaint.TextSize = font.FontSize;
DescriptionPaint.TextScaleX = font.FontScale;
DescriptionPaint.Color = font.FontColor;
DescriptionPaint.TextSkewX = font.SkewValue;
DescriptionPaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
{
DescriptionPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
if (font.MaxLineCount < 2)
{
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
}
else
{
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
}
}
else
{
base.DrawDescription(c);
}
}
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
{
if (string.IsNullOrEmpty(text)) return;
if (!_lowerDrawn)
{
_lowerDrawn = true;
DrawTextBackground(c);
}
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
}
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
{
if (UserFacingFlags == null || UserFacingFlags.Count < 1) return;
if (customOnly)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
}
else
{
// add size to api
// draw
}
}
}

View File

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseIcon : UCreator
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
{
GetSeries(dataList);
Preview = Utils.GetBitmap(dataList);
}
// preview
if (Preview is null)
{
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
Preview = Utils.GetBitmap(otherPreview);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
}
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
{
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
}
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name;
}
protected void Draw(SKCanvas c)
{
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
DrawUserFacingFlags(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
if (Description != ShortDescription)
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c);
break;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return new[] { ret };
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
private void GetSeries(FInstancedStruct[] s)
{
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series"));
}
private void GetSeries(FStructFallback s)
{
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return;
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef ||
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null ||
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
GetSeries(series);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = texture2D.Decode();
return;
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
SeriesBackground = Utils.GetBitmap(backgroundTexture);
}
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
colors.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
if (uObject.Name.Equals("PlatformSeries") &&
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
return string.Format(format, name);
}
protected (int, int) GetInternalSID(int number)
{
static int GetSeasonsInChapter(int chapter) => chapter switch
{
1 => 10,
2 => 8,
3 => 4,
4 => 5,
_ => 10
};
var chapterIdx = 0;
var seasonIdx = 0;
while (number > 0)
{
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
if (number > seasonsInChapter)
number -= seasonsInChapter;
else
{
seasonIdx = number;
number = 0;
}
}
return (chapterIdx, seasonIdx);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var initial = int.Parse(s);
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text);
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
protected void GetUserFacingFlags(IList<string> userFacingFlags)
{
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
return;
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
return;
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}

View File

@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Engine.Curves;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseIconStats : BaseIcon
{
private readonly IList<IconStat> _statistics;
private const int _headerHeight = 128;
private bool _screenLayer;
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
_statistics = new List<IconStat>();
_screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
}
public override void ParseForInfo()
{
base.ParseForInfo();
DisplayName = DisplayName.ToUpperInvariant();
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
{
_screenLayer = false;
}
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
{
foreach (var location in poiLocations)
{
var locationName = "Unknown";
foreach (var poi in challengeMapPoiData)
{
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;
locationName = text.Text;
break;
}
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
}
}
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
{
if (maxStackSize.TryGetValue(out float v, "Value") && v > 0)
{
_statistics.Add(new IconStat("Max Stack", v, 15));
}
else if (TryGetCurveTableStat(maxStackSize, out var s))
{
_statistics.Add(new IconStat("Max Stack", s, 15));
}
}
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
{
_statistics.Add(new IconStat("XP Amount", x));
}
if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
{
if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"))
{
var multiplier = bpc != 0f ? bpc : 1;
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
}
if (weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge") && mdpc >= 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 200));
}
else if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
}
}
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
}
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
}
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
}
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
}
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
}
}
if (!string.IsNullOrEmpty(Description))
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
Height += 50 * _statistics.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawStatistics(c);
return new[] { ret };
}
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
{
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
curve.TryGetValue(out FName rowName, "RowName") &&
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
curveTable.TryFindCurve(rowName, out var rowValue) &&
rowValue is FSimpleCurve s && s.Keys.Length > 0)
{
statValue = s.Keys[0].Value;
return true;
}
statValue = 0F;
return false;
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630"), TextSize = 16,
Typeface = Utils.Typefaces.Description
};
private void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { Background[0].WithAlpha(180), Background[1].WithAlpha(220) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { SKColor.Parse("#262630"), SKColor.Parse("#1f1f26") }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
using var rect = new SKPath { FillType = SKPathFillType.EvenOdd };
rect.MoveTo(0, 0);
rect.LineTo(_headerHeight + _headerHeight / 3, 0);
rect.LineTo(_headerHeight, _headerHeight);
rect.LineTo(0, _headerHeight);
rect.Close();
c.DrawPath(rect, _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawPath(rect, _informationPaint);
_informationPaint.Shader = null;
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
_informationPaint.TextSize = 50;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.Bundle;
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
{
_informationPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_informationPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2f + _informationPaint.TextSize / 3, _informationPaint);
}
private void DrawStatistics(SKCanvas c)
{
var outY = _headerHeight + 25f;
if (!string.IsNullOrEmpty(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
outY += 25;
}
foreach (var stat in _statistics)
{
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
outY += 50;
}
}
}
public class IconStat
{
private readonly string _statName;
private readonly object _value;
private readonly float _maxValue;
public IconStat(string statName, object value, float maxValue = 0)
{
_statName = statName.ToUpperInvariant();
_value = value;
_maxValue = maxValue;
}
private readonly SKPaint _statPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
Color = SKColors.White
};
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
{
while (_statPaint.MeasureText(_statName) > height * 2 - 40)
{
_statPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_statPaint.Typeface);
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
_statPaint.TextAlign = SKTextAlign.Right;
_statPaint.Typeface = Utils.Typefaces.BundleNumber;
_statPaint.Color = sliderColor;
var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
_statPaint.Color = SKColors.White;
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
if (floatValue < 0)
floatValue = 0;
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
}
}

View File

@ -0,0 +1,99 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseItemAccessToken : UCreator
{
private readonly SKBitmap _locked, _unlocked;
private string _unlockedDescription, _exportName;
private BaseIcon _icon;
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
{
_unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24);
_locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24);
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
{
_exportName = uObject.Name;
_icon = new BaseIcon(uObject, EIconStyle.Default);
_icon.ParseForReward(false);
}
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName") && displayName.Text != "TBD")
DisplayName = displayName.Text;
else
DisplayName = _icon?.DisplayName;
Description = Object.TryGetValue(out FText description, "Description", "ItemDescription") ? description.Text : _icon?.Description;
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
}
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:
Preview = _icon.Preview;
DrawPreview(c);
break;
case EIconStyle.NoText:
Preview = _icon.Preview;
_icon.DrawBackground(c);
DrawPreview(c);
break;
default:
_icon.DrawBackground(c);
DrawInformation(c);
DrawToBottom(c, SKTextAlign.Right, _exportName);
break;
}
return new[] { ret };
}
private void DrawInformation(SKCanvas c)
{
var size = 45;
var left = Width / 2;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
{
DisplayNamePaint.TextSize = size -= 2;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
c.DrawShapedText(shaper, DisplayName, left - shapedText.Width / 2, _icon.Margin * 8 + size, DisplayNamePaint);
float topBase = _icon.Margin + size * 2;
if (!string.IsNullOrEmpty(_unlockedDescription))
{
c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint);
Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left,
new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
}
if (!string.IsNullOrEmpty(Description))
{
c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint);
Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left,
new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
}
var h = Width - _icon.Margin - topBase;
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
}
}

View File

@ -0,0 +1,49 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseJuno : BaseIcon
{
private BaseIcon _character;
public BaseJuno(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath baseCid, "BaseAthenaCharacterItemDefinition") &&
Utils.TryLoadObject(baseCid.AssetPathName.Text, out UObject cid))
{
_character = new BaseIcon(cid, Style);
_character.ParseForInfo();
if (Object.TryGetValue(out FSoftObjectPath assembledMeshSchema, "AssembledMeshSchema", "LowDetailsAssembledMeshSchema") &&
Utils.TryLoadObject(assembledMeshSchema.AssetPathName.Text, out UObject meshSchema) &&
meshSchema.TryGetValue(out FInstancedStruct[] additionalData, "AdditionalData"))
{
foreach (var data in additionalData)
{
if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") ?? false)
{
_character.Preview = Utils.GetBitmap(largePreview);
break;
}
}
}
}
if (Object.TryGetValue(out FSoftObjectPath baseEid, "BaseAthenaDanceItemDefinition") &&
Utils.TryLoadObject(baseEid.AssetPathName.Text, out UObject eid))
{
_character = new BaseIcon(eid, Style);
_character.ParseForInfo();
}
}
public override SKBitmap[] Draw() => _character.Draw();
}

View File

@ -0,0 +1,92 @@
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseMaterialInstance : BaseIcon
{
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
Border = new[] { SKColor.Parse("9092AB") };
}
public override void ParseForInfo()
{
if (Object is not UMaterialInstanceConstant material) return;
texture_finding:
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture) || Preview != null) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "SeriesTexture":
GetSeries(texture);
break;
case "TextureA":
case "TextureB":
case "OfferImage":
case "CarTexture":
Preview = Utils.GetBitmap(texture);
break;
}
}
while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here
material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
else return;
if (material == null) return;
}
if (Preview == null)
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
goto texture_finding;
}
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null) continue;
switch (vectorParameter.ParameterInfo.Name.Text)
{
case "Background_Color_A":
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
Border[0] = Background[0];
break;
case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base
Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
break;
}
}
}
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 };
}
}

View File

@ -0,0 +1,83 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseMtxOffer : UCreator
{
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
Border = new[] { SKColor.Parse("9092AB") };
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath image, "SoftDetailsImage", "SoftTileImage"))
{
Preview = Utils.GetBitmap(image);
}
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
gradient.TryGetValue(out FLinearColor start, "Start") &&
gradient.TryGetValue(out FLinearColor stop, "Stop"))
{
Background = new[] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
}
if (Object.TryGetValue(out FLinearColor background, "Background"))
Border = new[] { SKColor.Parse(background.Hex) };
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText shortDescription, "ShortDescription"))
Description = shortDescription.Text;
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
{
foreach (var detail in details)
{
if (detail.TryGetValue(out FText detailName, "Name"))
{
Description += $"\n- {detailName.Text.TrimEnd()}";
}
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
{
Description += $" ({detailValue.Text})";
}
}
}
Description = Utils.RemoveHtmlTags(Description);
}
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;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
break;
}
return new[] { ret };
}
}

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseOfferDisplayData : UCreator
{
private readonly List<BaseMaterialInstance> _offerImages;
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
{
_offerImages = new List<BaseMaterialInstance>();
}
public override void ParseForInfo()
{
if (!Object.TryGetValue(out FStructFallback[] contextualPresentations, "ContextualPresentations"))
return;
for (var i = 0; i < contextualPresentations.Length; i++)
{
if (!contextualPresentations[i].TryGetValue(out FSoftObjectPath material, "Material") ||
!material.TryLoad(out UMaterialInterface presentation)) continue;
var offerImage = new BaseMaterialInstance(presentation, Style);
offerImage.ParseForInfo();
_offerImages.Add(offerImage);
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap[_offerImages.Count];
for (var i = 0; i < ret.Length; i++)
{
ret[i] = _offerImages[i]?.Draw()[0];
}
return ret;
}
}

View File

@ -0,0 +1,77 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BasePlaylist : UCreator
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
{
Margin = 0;
Width = 1024;
Height = 512;
Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default");
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "UIDescription", "Description"))
Description = description.Text;
if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon"))
_missionIcon = Utils.GetBitmap(missionIcon).Resize(25);
if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text))
return;
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
if (!playlist.IsSuccess || playlist.Data.Images is not { HasShowcase: true } ||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
return;
Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512);
Width = Preview.Width;
Height = Preview.Height;
}
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;
case EIconStyle.NoText:
DrawPreview(c);
DrawMissionIcon(c);
break;
default:
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawMissionIcon(c);
break;
}
return new[] { ret };
}
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
}
}

View File

@ -0,0 +1,278 @@
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseQuest : BaseIcon
{
private int _count;
private Reward _reward;
private readonly bool _screenLayer;
private readonly string[] _unauthorizedReward = { "Token", "ChallengeBundle", "GiftBox" };
public string NextQuestName { get; private set; }
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
{
Margin = 0;
Width = 1024;
Height = 256;
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
if (uObject != null)
{
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
}
}
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
{
var description = completionCount < 0 ?
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
}
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
{
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
}
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
{
_reward = new Reward(quantity, reward);
}
public override void ParseForInfo()
{
ParseForReward(false);
if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData"))
{
if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle"))
DisplayName = eventTitle.Text;
if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription"))
Description = eventDescription.Text;
if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage"))
Preview = Utils.GetBitmap(alertIcon);
}
else
{
if (!string.IsNullOrEmpty(ShortDescription))
Description = ShortDescription;
if (string.IsNullOrEmpty(DisplayName) && !string.IsNullOrEmpty(Description))
DisplayName = Description;
if (DisplayName == Description)
Description = string.Empty;
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
(Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out iconObject)))
{
Preview = iconObject switch
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
_count = objectiveCompletionCount;
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
{
// actual description doesn't exist
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
Description = description.Text;
// ObjectiveCompletionCount doesn't exist
if (_count == 0)
{
if (objectives[0].TryGetValue(out int count, "Count") && count > 1)
_count = count;
else
_count = objectives.Length;
}
}
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
{
NextQuestName = primaryAssetName.Text;
}
else if (!_unauthorizedReward.Contains(name.Text))
{
_reward = new Reward(quantity, $"{name}:{primaryAssetName}");
}
}
}
if (_reward == null)
{
FName rowName = null;
if (Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
rowName = new FName("Default");
else if (Object.TryGetValue(out FStructFallback[] rewardTableRows, "IndividualRewardTableRows") &&
rewardTableRows.Length > 0 && rewardTableRows[0].TryGetValue(out rowName, "RowName") &&
rewardTableRows[0].TryGetValue(out rewardsTable, "DataTable")) {}
if (rewardsTable != null && rowName != null && rewardsTable.TryGetDataTableRow(rowName.Text, StringComparison.InvariantCulture, out var row))
{
if (row.TryGetValue(out FName templateId, "TemplateId") &&
row.TryGetValue(out int quantity, "Quantity"))
{
_reward = new Reward(quantity, templateId);
}
}
}
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
{
foreach (var hiddenReward in hiddenRewards)
{
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
_reward = new Reward(quantity, templateId);
break;
}
}
_reward ??= new Reward();
}
public void DrawQuest(SKCanvas c, int y)
{
DrawBackground(c, y);
DrawPreview(c, y);
DrawTexts(c, y);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return new[] { ret };
}
private string ReformatString(string s, string completionCount, bool isAll)
{
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
if (index > -1)
{
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
s = s.Replace(p, string.Empty);
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
}
var upper = s.SubstringAfter(">").SubstringBefore("</>");
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630")
};
private void DrawBackground(SKCanvas c, int y)
{
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
new[] { Background[0].WithAlpha(50), Background[1].WithAlpha(50) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
}
private void DrawPreview(SKCanvas c, int y)
{
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
}
private void DrawTexts(SKCanvas c, int y)
{
_informationPaint.Shader = null;
if (!string.IsNullOrWhiteSpace(DisplayName))
{
_informationPaint.TextSize = 40;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.Bundle;
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
{
_informationPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_informationPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
}
var outY = y + 75f;
if (!string.IsNullOrWhiteSpace(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
}
_informationPaint.Color = Border[0].WithAlpha(100);
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
if (_count > 0)
{
_informationPaint.TextSize = 25;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
_informationPaint.Color = Border[0];
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
}
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
}
}

View File

@ -0,0 +1,158 @@
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class Page
{
public int LevelsNeededForUnlock;
public int RewardsNeededForUnlock;
public Reward[] RewardEntryList;
}
public class BaseSeason : UCreator
{
private Reward _firstWinReward;
private Page[] _bookXpSchedule;
private const int _headerHeight = 150;
// keep the list because rewards are ordered by least to most important
// we only care about the most but we also have filters so we can't just take the last reward
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
public override void ParseForInfo()
{
_bookXpSchedule = Array.Empty<Page>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_firstWinReward = new Reward(uObject);
break;
}
}
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
{
foreach (var data in additionalSeasonData)
{
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
var i = 0;
_bookXpSchedule = new Page[pageList.Length];
foreach (var page in pageList)
{
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
continue;
var p = new Page
{
LevelsNeededForUnlock = levelsNeededForUnlock,
RewardsNeededForUnlock = rewardsNeededForUnlock,
RewardEntryList = new Reward[rewardEntryList.Length]
};
for (var j = 0; j < p.RewardEntryList.Length; j++)
{
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
p.RewardEntryList[j] = new Reward(uObject);
}
_bookXpSchedule[i++] = p;
}
break;
}
}
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
DrawBookSchedule(c);
return new[] { ret };
}
private const int _DEFAULT_AREA_SIZE = 80;
private readonly SKPaint _headerPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
public void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
{
_headerPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawBookSchedule(SKCanvas c)
{
var x = 20;
var y = _headerHeight + 50;
foreach (var page in _bookXpSchedule)
{
foreach (var reward in page.RewardEntryList)
{
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
x += _DEFAULT_AREA_SIZE + 20;
}
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
}
}

View File

@ -0,0 +1,26 @@
using CUE4Parse.UE4.Assets.Exports;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseSeries : BaseIcon
{
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
GetSeries(Object);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
return new []{ret};
}
}

View File

@ -0,0 +1,228 @@
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions;
using FModel.Framework;
using FModel.Settings;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseTandem : BaseIcon
{
private string _generalDescription, _additionalDescription;
public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style)
{
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy");
Margin = 0;
Width = 690;
Height = 1080;
}
public override void ParseForInfo()
{
base.ParseForInfo();
string sidePanel = string.Empty, entryList = string.Empty;
if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
sidePanel = sidePanelIcon.AssetPathName.Text;
if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon"))
entryList = entryListIcon.AssetPathName.Text;
// Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs
if (sidePanel.Contains("Clown") && entryList.Contains("Clown"))
Preview = null;
else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana"))
Preview = Utils.GetBitmap(entryList);
else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown"))
Preview = Utils.GetBitmap(sidePanel);
else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList))
Preview = Utils.GetBitmap(entryList);
if (Object.TryGetValue(out FText genDesc, "GeneralDescription"))
_generalDescription = genDesc.Text;
if (Object.TryGetValue(out FText addDesc, "AdditionalDescription"))
_additionalDescription = addDesc.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawHaze(c);
// Korean is slightly smaller than other languages, so the font size is increased slightly
DrawName(c);
DrawGeneralDescription(c);
DrawAdditionalDescription(c);
return new[] { ret };
}
private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") };
private new void DrawBackground(SKCanvas c)
{
c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) });
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
_panelPaint.Color = SKColor.Parse("#002A8C");
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height),
new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect2.MoveTo(10, 0);
rect2.LineTo(30, 0);
rect2.LineTo(63, Height);
rect2.LineTo(56, Height);
rect2.LineTo(10, 0);
rect2.Close();
c.DrawPath(rect2, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height),
new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp);
c.DrawPath(rect2, _panelPaint);
}
private new void DrawPreview(SKCanvas c)
{
var previewToUse = Preview ?? DefaultPreview;
if (Preview == null)
{
previewToUse = DefaultPreview;
ImagePaint.BlendMode = SKBlendMode.DstOut;
ImagePaint.Color = SKColor.Parse("#00175F");
}
var x = -125;
switch (previewToUse.Width)
{
case 512 when previewToUse.Height == 1024:
previewToUse = previewToUse.ResizeWithRatio(500, 1000);
x = 100;
break;
case 512 when previewToUse.Height == 512:
case 128 when previewToUse.Height == 128:
previewToUse = previewToUse.Resize(512);
x = 125;
break;
default:
previewToUse = previewToUse.Resize(1000, 1000);
break;
}
c.DrawBitmap(previewToUse, x, 30, ImagePaint);
}
private void DrawHaze(SKCanvas c)
{
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height),
new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
}
private void DrawName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 56,
_ => 42
};
DisplayNamePaint.TextScaleX = (float) 1.1;
DisplayNamePaint.Color = SKColors.White;
DisplayNamePaint.TextSkewX = (float) -.25;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemDisplayName;
if (typeface == Utils.Typefaces.Default)
{
DisplayNamePaint.TextSize = 30;
}
DisplayNamePaint.Typeface = typeface;
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint);
}
private void DrawGeneralDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_generalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 20,
_ => 17
};
DescriptionPaint.Color = SKColor.Parse("#00FFFB");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemGenDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 21;
}
DescriptionPaint.Typeface = typeface;
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint);
}
private void DrawAdditionalDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_additionalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 22,
_ => 18
};
DescriptionPaint.Color = SKColor.Parse("#89D8FF");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemAddDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 20;
}
DescriptionPaint.Typeface = typeface;
Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left,
new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _);
}
}

View File

@ -0,0 +1,191 @@
using System.Collections.Generic;
using System.Globalization;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseUserControl : UCreator
{
private List<Options> _optionValues = new();
private readonly SKPaint _displayNamePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 45,
Color = SKColors.White, TextAlign = SKTextAlign.Left
};
private readonly SKPaint _descriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 25,
Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left
};
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 512;
Height = 128;
Margin = 32;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName", "OptionText"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription", "OptionToolTip"))
{
Description = optionDescription.Text;
if (string.IsNullOrWhiteSpace(Description)) return;
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
Height += (int) _descriptionPaint.TextSize;
}
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues", "Options"))
{
_optionValues = new List<Options>();
foreach (var option in optionValues)
{
if (option.TryGetValue(out FText displayName, "DisplayName", "DisplayText"))
{
var opt = new Options { Option = displayName.Text.ToUpperInvariant() };
if (option.TryGetValue(out FLinearColor color, "Value"))
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
_optionValues.Add(opt);
}
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
{
_optionValues.Add(new Options { Option = primaryAssetName.Text });
}
}
}
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
_optionValues.Add(new Options { Option = optionOnText.Text });
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
_optionValues.Add(new Options { Option = optionOffText.Text });
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
Object.TryGetValue(out int iMax, "Max"))
{
var increment = iMin;
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
increment = incrementValue;
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
for (var i = iMin; i <= iMax; i += increment)
{
_optionValues.Add(new Options { Option = string.Format(format, i) });
}
}
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
Object.TryGetValue(out float fMax, "Max"))
{
var increment = fMin;
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
increment = incrementValue;
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
for (var i = fMin; i <= fMax; i += increment)
{
_optionValues.Add(new Options { Option = string.Format(format, i) });
}
}
Height += Margin;
Height += 35 * _optionValues.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawInformation(c);
return new[] { ret };
}
private new void DrawBackground(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height),
new SKPoint(Width, Height / 4),
new[] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") },
SKShaderTileMode.Clamp)
});
}
private void DrawInformation(SKCanvas c)
{
// display name
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
_displayNamePaint.TextSize -= 2;
}
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
#if DEBUG
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint { Color = SKColors.Blue, IsStroke = true });
#endif
// description
float y = Margin;
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
// options
foreach (var option in _optionValues)
{
option.Draw(c, Margin, Width, ref top);
}
}
}
public class Options
{
private const int _SPACE = 5;
private const int _HEIGHT = 30;
private readonly SKPaint _optionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
public string Option;
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
public void Draw(SKCanvas c, int margin, int width, ref float top)
{
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color });
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
top += _HEIGHT + _SPACE;
}
}

View File

@ -0,0 +1,151 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class Reward
{
private string _rewardQuantity;
private BaseIcon _theReward;
public bool HasReward() => _theReward != null;
public Reward()
{
_rewardQuantity = "x0";
}
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
{
var parts = assetName.Split(':');
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
_theReward = new BaseIcon(p, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
else GetReward(parts[1]);
}
else GetReward(assetName);
}
public Reward(UObject uObject)
{
_theReward = new BaseIcon(uObject, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
private readonly SKPaint _rewardPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High
};
public void DrawQuest(SKCanvas c, SKRect rect)
{
_rewardPaint.TextSize = 50;
if (HasReward())
{
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
_rewardPaint.Color = _theReward.Border[0];
_rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle;
_rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150));
while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width)
{
_rewardPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
}
else
{
_rewardPaint.Color = SKColors.White;
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
}
}
public void DrawSeasonWin(SKCanvas c, int size)
{
if (!HasReward()) return;
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize(size), new SKPoint(0, 0), _rewardPaint);
}
public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
{
if (!HasReward()) return;
// area + icon
_rewardPaint.Color = SKColor.Parse("#0F5CAF");
c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint);
c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint);
// rarity color
_rewardPaint.Color = _theReward.Background[0];
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
pathBottom.MoveTo(x, y + areaSize);
pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f);
pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f);
pathBottom.LineTo(x + areaSize, y + areaSize);
pathBottom.Close();
c.DrawPath(pathBottom, _rewardPaint);
}
private void GetReward(string trigger)
{
switch (trigger.ToLower())
{
// case "athenabattlestar":
// _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("FFDB67");
// _theReward.Background[0] = SKColor.Parse("8F4A20");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
// break;
// case "athenaseasonalxp":
// _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("E6FDB1");
// _theReward.Background[0] = SKColor.Parse("51830F");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L");
// break;
// case "mtxgiveaway":
// _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("DCE6FF");
// _theReward.Background[0] = SKColor.Parse("64A0AF");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
// break;
default:
{
var path = Utils.GetFullPath($"FortniteGame/(?:Content/Athena|Content/Items|Plugins/GameFeatures)/.*?/{trigger}.uasset"); // path has no objectname and its needed so we push the trigger again as the objectname
if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d))
{
_theReward = new BaseIcon(d, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = $"{_theReward.DisplayName} ({_rewardQuantity})";
}
break;
}
}
}
}

View File

@ -0,0 +1,287 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using SkiaSharp;
namespace FModel.Creator.Bases.MV;
public class BaseFighter : UCreator
{
private float _xOffset = 1f;
private float _yOffset = 1f;
private float _zoom = 1f;
private readonly SKBitmap _pattern;
private readonly SKBitmap _perk;
private readonly SKBitmap _emote;
private readonly SKBitmap _skin;
private (SKBitmap, List<string>) _fighterType;
private readonly List<SKBitmap> _recommendedPerks;
private readonly List<SKBitmap> _availableTaunts;
private readonly List<SKBitmap> _skins;
public BaseFighter(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
DisplayNamePaint.TextSize = 100;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
DisplayNamePaint.Typeface = Utils.Typefaces.TandemDisplayName;
DescriptionPaint.TextSize = 25;
DescriptionPaint.Typeface = Utils.Typefaces.TandemGenDescription;
DefaultPreview = Utils.GetBitmap("/Game/Panda_Main/UI/PreMatch/Images/DiamondPortraits/0010_Random.0010_Random");
_pattern = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/UI_Textures/halftone_jagged.halftone_jagged");
_perk = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_perks.ui_icons_perks");
_emote = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_emote.ui_icons_emote");
_skin = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_skins.ui_icons_skins");
_fighterType.Item2 = new List<string>();
_recommendedPerks = new List<SKBitmap>();
_availableTaunts = new List<SKBitmap>();
_skins = new List<SKBitmap>();
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FLinearColor backgroundColor, "BackgroundColor"))
Background = new[] { SKColor.Parse(backgroundColor.Hex) };
if (Object.TryGetValue(out FSoftObjectPath portraitMaterial, "CollectionsPortraitMaterial") &&
portraitMaterial.TryLoad(out UMaterialInstanceConstant portrait))
{
_xOffset = Math.Abs(portrait.ScalarParameterValues.FirstOrDefault(x => x.ParameterInfo.Name.Text == "XOffset")?.ParameterValue ?? 1f);
_yOffset = Math.Abs(portrait.ScalarParameterValues.FirstOrDefault(x => x.ParameterInfo.Name.Text == "YOffset")?.ParameterValue / 10 ?? 1f);
_zoom = Math.Clamp(portrait.ScalarParameterValues.FirstOrDefault(x => x.ParameterInfo.Name.Text == "Zoom")?.ParameterValue ?? 1f, 0, 1);
Preview = Utils.GetBitmap(portrait);
}
else if (Object.TryGetValue(out FSoftObjectPath portraitTexture, "NewCharacterSelectPortraitTexture", "HUDPortraitTexture"))
Preview = Utils.GetBitmap(portraitTexture);
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
GetFighterClassInfo(Object.GetOrDefault("Class", EFighterClass.Support));
_fighterType.Item2.Add(Utils.GetLocalizedResource(Object.GetOrDefault("Type", EFighterType.Horizontal)));
if (Object.TryGetValue(out FText property, "Property"))
_fighterType.Item2.Add(property.Text);
if (Object.TryGetValue(out UScriptSet recommendedPerks, "RecommendedPerkDatas")) // PORCO DIO WB USE ARRAYS!!!!!!
{
foreach (var recommendedPerk in recommendedPerks.Properties)
{
if (recommendedPerk.GenericValue is not FPackageIndex packageIndex ||
!Utils.TryGetPackageIndexExport(packageIndex, out UObject export) ||
!export.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail"))
continue;
_recommendedPerks.Add(Utils.GetBitmap(rewardThumbnail));
}
}
if (Object.TryGetValue(out FSoftObjectPath[] availableTaunts, "AvailableTauntData"))
{
foreach (var taunt in availableTaunts)
{
if (!Utils.TryLoadObject(taunt.AssetPathName.Text, out UObject export) ||
!export.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail"))
continue;
_availableTaunts.Add(Utils.GetBitmap(rewardThumbnail));
}
}
if (Object.TryGetValue(out FSoftObjectPath[] skins, "Skins"))
{
foreach (var skin in skins)
{
if (!Utils.TryLoadObject(skin.AssetPathName.Text, out UObject export) ||
!export.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail"))
continue;
_skins.Add(Utils.GetBitmap(rewardThumbnail));
}
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawDisplayName(c);
DrawFighterInfo(c);
DrawRecommendedPerks(c);
DrawAvailableTaunts(c);
DrawSkins(c);
return new[] { ret };
}
private void GetFighterClassInfo(EFighterClass clas)
{
if (!Utils.TryLoadObject("/Game/Panda_Main/UI/In-Game/Data/UICharacterClassInfo_Datatable.UICharacterClassInfo_Datatable", out UDataTable dataTable))
return;
var row = dataTable.RowMap.ElementAt((int) clas).Value;
if (!row.TryGetValue(out FText displayName, "DisplayName_5_9DB5DDFF490E1F4AD72329866F96B81D") ||
!row.TryGetValue(out FPackageIndex icon, "Icon_8_711534AD4F240D4B001AA6A471EA1895"))
return;
_fighterType.Item1 = Utils.GetBitmap(icon);
_fighterType.Item2.Add(displayName.Text);
}
private new void DrawBackground(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Background[0]
});
if (!string.IsNullOrWhiteSpace(DisplayName))
{
c.DrawText(DisplayName, -50, 125, new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 200,
TextScaleX = .95f, TextSkewX = -0.25f, Color = SKColors.Black.WithAlpha(25)
});
}
c.DrawBitmap(_pattern, new SKRect(0, Height / 2, Width, Height), new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.SoftLight
});
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
path.MoveTo(0, Height);
path.LineTo(0, Height - 20);
path.LineTo(Width, Height - 60);
path.LineTo(Width, Height);
path.Close();
c.DrawPath(path, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#141629")
});
}
private new void DrawPreview(SKCanvas c)
{
var img = (Preview ?? DefaultPreview).ResizeWithRatio(_zoom);
var x_offset = img.Width * _xOffset;
var y_offset = img.Height * -_yOffset;
c.DrawBitmap(img, new SKRect(Width + x_offset - img.Width, y_offset, Width + x_offset, img.Height + y_offset), ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
c.DrawText(DisplayName.ToUpper(), 50, 100, DisplayNamePaint);
}
private void DrawFighterInfo(SKCanvas c)
{
if (_fighterType.Item1 != null)
c.DrawBitmap(_fighterType.Item1, new SKRect(50, 112.5f, 98, 160.5f), ImagePaint);
c.DrawText(string.Join(" | ", _fighterType.Item2), 98, 145, DescriptionPaint);
}
private void DrawRecommendedPerks(SKCanvas c)
{
const int x = 50;
const int y = 200;
const int size = 64;
ImagePaint.ImageFilter = null;
ImagePaint.BlendMode = SKBlendMode.SoftLight;
c.DrawBitmap(_perk, new SKRect(x, y, x + size / 2, y + size / 2), ImagePaint);
if (_recommendedPerks.Count < 1) return;
ImagePaint.BlendMode = SKBlendMode.SrcOver;
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 2.5f, 2.5f, SKColors.Black);
c.DrawBitmap(_recommendedPerks[1], new SKRect(161, y, 225, y + size), ImagePaint);
c.DrawBitmap(_recommendedPerks[2], new SKRect(193, y + size / 2, 257, y + size * 1.5f), ImagePaint);
c.DrawBitmap(_recommendedPerks[3], new SKRect(161, y + size, 225, y + size * 2), ImagePaint);
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColors.Black.WithAlpha(150));
c.DrawBitmap(_recommendedPerks[0], new SKRect(x, y, x + size * 2, y + size * 2), ImagePaint);
}
private void DrawAvailableTaunts(SKCanvas c)
{
var x = 300;
const int y = 232;
const int size = 64;
ImagePaint.ImageFilter = null;
ImagePaint.BlendMode = SKBlendMode.SoftLight;
c.DrawBitmap(_emote, new SKRect(x, y - size / 2, x + size / 2, y), ImagePaint);
if (_availableTaunts.Count < 1) return;
ImagePaint.BlendMode = SKBlendMode.SrcOver;
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 1.5f, 1.5f, SKColors.Black);
foreach (var taunt in _availableTaunts)
{
c.DrawBitmap(taunt, new SKRect(x, y, x + size, y + size), ImagePaint);
x += size;
}
}
private void DrawSkins(SKCanvas c)
{
var x = 50;
const int y = 333;
const int size = 128;
ImagePaint.ImageFilter = null;
ImagePaint.BlendMode = SKBlendMode.SoftLight;
c.DrawBitmap(_skin, new SKRect(x, y, x + size / 4, y + size / 4), ImagePaint);
if (_skins.Count < 1) return;
ImagePaint.BlendMode = SKBlendMode.SrcOver;
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 1.5f, 1.5f, SKColors.Black);
foreach (var skin in _skins)
{
c.DrawBitmap(skin, new SKRect(x, y, x + size, y + size), ImagePaint);
x += size;
}
}
}
public enum EFighterClass : byte
{
Mage = 4,
Tank = 3,
Fighter = 2,
Bruiser = 2,
Assassin = 1,
Support = 0 // Default
}
public enum EFighterType : byte
{
[Description("B980C82D40FF37FD359C74A339CE1B3A")]
Hybrid = 2,
[Description("2C55443D47164019BE73A5ABDC670F36")]
Vertical = 1,
[Description("97A60DD54AA23D4B93D5B891F729BF5C")]
Horizontal = 0 // Default
}

View File

@ -0,0 +1,344 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.MV;
public class BasePandaIcon : UCreator
{
private float _y_offset;
private ERewardRarity _rarity;
private string _type;
protected readonly List<(SKBitmap, string)> Pictos;
public BasePandaIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Margin = 30;
DisplayNamePaint.TextSize = 50;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
DisplayNamePaint.Color = SKColor.Parse("#191C33");
DescriptionPaint.TextSize = 25;
DefaultPreview = Utils.GetBitmap("/Game/Panda_Main/UI/PreMatch/Images/DiamondPortraits/0010_Random.0010_Random");
_y_offset = Height / 2 + DescriptionPaint.TextSize;
Pictos = new List<(SKBitmap, string)>();
}
public override void ParseForInfo()
{
var category = Object.GetOrDefault("Category", EPerkCategory.Offense);
_rarity = Object.GetOrDefault("Rarity", ERewardRarity.None);
_type = Object.ExportType;
var t = _type switch // ERewardType like
{
"StatTrackingBundleData" => EItemType.Badge,
"AnnouncerPackData" => EItemType.Announcer,
"CharacterGiftData" => EItemType.ExperiencePoints,
"ProfileIconData" => EItemType.ProfileIcon,
"RingOutVfxData" => EItemType.Ringout,
"BannerData" => EItemType.Banner,
"EmoteData" => EItemType.Sticker,
"QuestData" => EItemType.Mission,
"TauntData" => EItemType.Emote,
"SkinData" => EItemType.Variant,
"PerkData" when category == EPerkCategory.CharacterSpecific => EItemType.SignaturePerk,
"PerkData" => EItemType.Perk,
_ => EItemType.Unknown
};
if (t == EItemType.SignaturePerk) _rarity = ERewardRarity.Legendary;
Background = GetRarityBackground(_rarity);
if (Object.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail", "DisplayTextureRef", "Texture"))
Preview = Utils.GetBitmap(rewardThumbnail);
else if (Object.TryGetValue(out FPackageIndex icon, "Icon"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "QuestName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "QuestDescription"))
Description = Utils.RemoveHtmlTags(description.Text);
var unlockLocation = Object.GetOrDefault("UnlockLocation", EUnlockLocation.None);
if (t == EItemType.Unknown && unlockLocation == EUnlockLocation.CharacterMastery) t = EItemType.MasteryLevel;
Pictos.Add((Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_unlocked.ui_icons_unlocked"), Utils.GetLocalizedResource(unlockLocation)));
if (Object.TryGetValue(out string slug, "Slug"))
{
t = _type switch
{
"HydraSyncedDataAsset" when slug == "gold" => EItemType.Gold,
"HydraSyncedDataAsset" when slug == "gleamium" => EItemType.Gleamium,
"HydraSyncedDataAsset" when slug == "match_toasts" => EItemType.Toast,
_ => t
};
Pictos.Add((Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_link.ui_icons_link"), slug));
}
if (Object.TryGetValue(out int xpValue, "XPValue"))
DisplayName += $" (+{xpValue})";
if (Utils.TryLoadObject("/Game/Panda_Main/UI/Prototype/Foundation/Types/DT_EconomyGlossary.DT_EconomyGlossary", out UDataTable dataTable))
{
if (t != EItemType.Unknown &&
dataTable.RowMap.ElementAt((int) t).Value.TryGetValue(out FText name, "Name_14_7F75AD6047CBDEA7B252B1BD76EF84B9"))
_type = name.Text;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawDisplayName(c);
DrawDescription(c);
DrawPictos(c);
return new[] { ret };
}
private SKColor[] GetRarityBackground(ERewardRarity rarity)
{
return rarity switch // the colors here are the base color and brighter color that the game uses for rarities from the "Rarity to Color" blueprint function
{
ERewardRarity.Common => new[]
{
SKColor.Parse(new FLinearColor(0.068478f, 0.651406f, 0.016807f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.081422f, 1.000000f, 0.000000f, 1.000000f).Hex)
},
ERewardRarity.Rare => new[]
{
SKColor.Parse(new FLinearColor(0.035911f, 0.394246f, 0.900000f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.033333f, 0.434207f, 1.000000f, 1.000000f).Hex)
},
ERewardRarity.Epic => new[]
{
SKColor.Parse(new FLinearColor(0.530391f, 0.060502f, 0.900000f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.579907f, 0.045833f, 1.000000f, 1.000000f).Hex)
},
ERewardRarity.Legendary => new[]
{
SKColor.Parse(new FLinearColor(1.000000f, 0.223228f, 0.002428f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(1.000000f, 0.479320f, 0.030713f, 1.000000f).Hex)
},
_ => new[]
{
SKColor.Parse(new FLinearColor(0.194618f, 0.651406f, 0.630757f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.273627f, 0.955208f, 0.914839f, 1.000000f).Hex)
}
};
}
private new void DrawBackground(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#F3FCF0")
});
var has_tr = _rarity != ERewardRarity.None;
var tr = Utils.GetLocalizedResource(_rarity);
var tr_paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextAlign = SKTextAlign.Right,
TextSize = 35,
Color = SKColors.White,
Typeface = Utils.Typefaces.DisplayName
};
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
path.MoveTo(0, Height);
path.LineTo(14, Height);
path.LineTo(20, 20);
if (has_tr)
{
const int margin = 15;
var width = tr_paint.MeasureText(tr);
path.LineTo(Width - width - margin * 2, 15);
path.LineTo(Width - width - margin * 2.5f, 60);
path.LineTo(Width, 55);
}
else
{
path.LineTo(Width, 14);
}
path.LineTo(Width, 0);
path.LineTo(0, 0);
path.LineTo(0, Height);
path.Close();
c.DrawPath(path, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Background, SKShaderTileMode.Clamp)
});
if (has_tr)
{
var x = Width - 20f;
foreach (var a in tr.Select(character => character.ToString()).Reverse())
{
c.DrawText(a, x, 40, tr_paint);
x -= tr_paint.MeasureText(a) - 2;
}
}
}
private new void DrawPreview(SKCanvas c)
{
const int size = 384;
var y = Height - size - Margin * 2;
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, size + Margin, y + size), ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName))
return;
var x = 450f;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - x / 1.25)
{
DisplayNamePaint.TextSize -= 1;
}
var y = Height / 2 - DisplayNamePaint.TextSize / 4;
foreach (var a in DisplayName.Select(character => character.ToString()))
{
c.DrawText(a, x, y, DisplayNamePaint);
x += DisplayNamePaint.MeasureText(a) - 4;
}
}
private new void DrawDescription(SKCanvas c)
{
const int x = 450;
DescriptionPaint.Color = Background[0];
c.DrawText(_type.ToUpper(), x, 170, DescriptionPaint);
if (string.IsNullOrWhiteSpace(Description)) return;
DescriptionPaint.Color = SKColor.Parse("#191C33");
Utils.DrawMultilineText(c, Description, Width - x, Margin, SKTextAlign.Left,
new SKRect(x, _y_offset, Width - Margin, Height - Margin), DescriptionPaint, out _y_offset);
}
private void DrawPictos(SKCanvas c)
{
if (Pictos.Count < 1) return;
const float x = 450f;
const int size = 24;
var color = SKColor.Parse("#495B6E");
var paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextSize = 27,
Color = color,
Typeface = Utils.Typefaces.Default
};
ImagePaint.ColorFilter = SKColorFilter.CreateBlendMode(color, SKBlendMode.SrcIn);
foreach (var picto in Pictos)
{
c.DrawBitmap(picto.Item1, new SKRect(x, _y_offset + 10, x + size, _y_offset + 10 + size), ImagePaint);
c.DrawText(picto.Item2, x + size + 10, _y_offset + size + 6, paint);
_y_offset += size + 5;
}
}
}
public enum ERewardRarity : byte
{
[Description("0D4B15CE4FB6F2BC5E5F5FAA9E8B376C")]
None = 0, // Default
[Description("0FCDEF47485E2C3D0D477988C481D8E3")]
Common = 1,
[Description("18241CA7441AE16AAFB6EFAB499FF981")]
Rare = 2,
[Description("D999D9CB4754D1078BF9A1B34A231005")]
Epic = 3,
[Description("705AE967407D6EF8870E988A08C6900E")]
Legendary = 4
}
public enum EUnlockLocation : byte
{
[Description("0D4B15CE4FB6F2BC5E5F5FAA9E8B376C")]
None = 0, // Default
[Description("0AFBCE5F41D930D6E9B5138C8EBCFE87")]
Shop = 1,
[Description("062F178B4EE74502C9AD9D878F3D7CEA")]
AccountLevel = 2,
[Description("1AE7A5DF477B2B5F4B3CCC8DCD732884")]
CharacterMastery = 3,
[Description("0B37731C49DC9AE1EAC566950C1A329D")]
Battlepass = 4,
[Description("16F160084187479E5D471786190AE5B7")]
CharacterAffinity = 5,
[Description("E5C1E35C406C585E83B5D18A817FA0B4")]
GuildBoss = 6,
[Description("4A89F5DD432113750EF52D8B58977DCE")]
Tutorial = 7
}
public enum EPerkCategory : byte
{
Offense = 0, // Default
Defense = 1,
Utility = 2,
CharacterSpecific = 3
}
public enum EItemType
{
Unknown = -1,
Announcer,
Badge,
Banner,
BattlePassPoints,
Emote,
ExperiencePoints,
Gleamium,
Gold,
MasteryLevel,
Mission,
Perk,
PlayerLevel,
ProfileIcon,
Rested,
Ringout,
SignaturePerk,
Sticker,
Toast,
Variant
}

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.MV;
public class BasePerkGroup : UCreator
{
private readonly List<BasePandaIcon> _perks;
public BasePerkGroup(UObject uObject, EIconStyle style) : base(uObject, style)
{
_perks = new List<BasePandaIcon>();
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out UScriptSet perks, "Perks")) // PORCO DIO WB USE ARRAYS!!!!!!
{
foreach (var perk in perks.Properties)
{
if (perk.GenericValue is not FPackageIndex packageIndex ||
!Utils.TryGetPackageIndexExport(packageIndex, out UObject export))
continue;
var icon = new BasePandaIcon(export, Style);
icon.ParseForInfo();
_perks.Add(icon);
}
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap[_perks.Count];
for (var i = 0; i < ret.Length; i++)
{
ret[i] = _perks[i].Draw()[0];
}
return ret;
}
}

View File

@ -0,0 +1,54 @@
using System.ComponentModel;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
namespace FModel.Creator.Bases.MV;
public class BaseQuest : BasePandaIcon
{
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FStructFallback[] questCompletionRewards, "QuestCompletionRewards") &&
questCompletionRewards.Length > 0 && questCompletionRewards[0] is { } actualReward)
{
var rewardType = actualReward.GetOrDefault("RewardType", EQuestRewardType.Inventory);
var count = actualReward.GetOrDefault("Count", 0);
Pictos.Add((Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_plus.ui_icons_plus"), count.ToString()));
base.ParseForInfo();
if (actualReward.TryGetValue(out FPackageIndex assetReward, "AssetReward") &&
Utils.TryGetPackageIndexExport(assetReward, out UObject export))
{
var item = new BasePandaIcon(export, Style);
item.ParseForInfo();
Preview = item.Preview;
}
else if (rewardType != EQuestRewardType.Inventory)
{
Preview = Utils.GetBitmap(rewardType.GetDescription());
}
}
else
{
base.ParseForInfo();
}
}
}
public enum EQuestRewardType : byte
{
Inventory = 0, // Default
[Description("/Game/Panda_Main/UI/Assets/Icons/UI_CharacterTicket.UI_CharacterTicket")]
AccountXP = 1,
[Description("/Game/Panda_Main/UI/Assets/Icons/UI_BattlepassToken.UI_BattlepassToken")]
BattlepassXP = 2
}

View File

@ -0,0 +1,47 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseDivision : UCreator
{
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
{
Preview = Utils.GetBitmap(icon);
}
if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") &&
Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") &&
Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") &&
Object.TryGetValue(out FLinearColor cardColor, "UICardColor"))
{
Background = new[] { SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex) };
Border = new[] { SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex) };
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
return new[] { ret };
}
}

View File

@ -0,0 +1,49 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseGameModeInfo : UCreator
{
private SKBitmap _icon;
public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 738;
Height = 1024;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait"))
Preview = Utils.GetBitmap(portrait);
if (Object.TryGetValue(out UTexture2D icon, "Icon"))
_icon = Utils.GetBitmap(icon).Resize(25);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawIcon(c);
return new[] { ret };
}
private void DrawIcon(SKCanvas c)
{
if (_icon == null) return;
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
}
}

View File

@ -0,0 +1,54 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseLeague : UCreator
{
private int _promotionXp, _xpLostPerMatch;
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
{
_promotionXp = 0;
_xpLostPerMatch = 0;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out int promotionXp, "PromotionXP"))
_promotionXp = promotionXp;
if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch"))
_xpLostPerMatch = xpLostPerMatch;
if (Object.TryGetValue(out FPackageIndex division, "Division") &&
Utils.TryGetPackageIndexExport(division, out UObject div))
{
var d = new BaseDivision(div, Style);
d.ParseForInfo();
Preview = d.Preview;
Background = d.Background;
Border = d.Border;
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
return new[] { ret };
}
}

View File

@ -0,0 +1,97 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseSpellIcon : BaseIcon
{
private SKBitmap _seriesBackground2;
private readonly SKPaint _overlayPaint = new()
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") };
Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512;
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FName rarity, "Rarity"))
GetRarity(rarity);
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(preview);
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
_seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackgrounds(c);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return new[] { ret };
}
private void DrawBackgrounds(SKCanvas c)
{
if (SeriesBackground != null)
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
if (_seriesBackground2 != null)
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
var x = Margin * (int) 2.5;
const int radi = 15;
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(radi, radi), radi * 2 / 5 * 4,
Background, SKShaderTileMode.Clamp)
});
}
private void GetRarity(FName n)
{
if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return;
if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row))
{
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
{
Background = new[] { SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex) };
Border = new[] { SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex) };
}
}
}
}

View File

@ -0,0 +1,228 @@
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases.FN;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases;
public abstract class UCreator
{
protected UObject Object { get; }
protected EIconStyle Style { get; }
public SKBitmap DefaultPreview { get; set; }
public SKBitmap Preview { get; set; }
public SKColor[] Background { get; protected set; }
public SKColor[] Border { get; protected set; }
public string DisplayName { get; protected set; }
public string Description { get; protected set; }
public int Margin { get; protected set; }
public int Width { get; protected set; }
public int Height { get; protected set; }
public abstract void ParseForInfo();
public abstract SKBitmap[] Draw();
protected UCreator(UObject uObject, EIconStyle style)
{
DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
Background = new[] { SKColor.Parse("5BFD00"), SKColor.Parse("003700") };
Border = new[] { SKColor.Parse("1E8500"), SKColor.Parse("5BFD00") };
DisplayName = string.Empty;
Description = string.Empty;
Width = 512;
Height = 512;
Margin = 2;
Object = uObject;
Style = style;
}
private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15;
protected readonly SKPaint DisplayNamePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE,
Color = SKColors.White, TextAlign = SKTextAlign.Center
};
protected readonly SKPaint DescriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Description, TextSize = 13,
Color = SKColors.White
};
protected readonly SKPaint ImagePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High
};
private readonly SKPaint _textBackgroundPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75)
};
private readonly SKPaint _shortDescriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColors.White
};
public void DrawBackground(SKCanvas c)
{
// reverse doesn't affect basic rarities
if (Background[0] == Background[1]) Background[0] = Border[0];
Background[0].ToHsl(out _, out _, out var l1);
Background[1].ToHsl(out _, out _, out var l2);
var reverse = l1 > l2;
// border
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
});
if (this is BaseIcon { SeriesBackground: { } } baseIcon)
c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin,
baseIcon.Height - baseIcon.Margin), ImagePaint);
else
{
switch (Style)
{
case EIconStyle.Flat:
{
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4),
new[] { Background[reverse ? 0 : 1].WithAlpha(150), Border[0] }, SKShaderTileMode.Clamp)
});
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
pathTop.MoveTo(Margin, Margin);
pathTop.LineTo(Margin + Width / 17 * 10, Margin);
pathTop.LineTo(Margin, Margin + Height / 17);
pathTop.Close();
c.DrawPath(pathTop, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = Background[1].WithAlpha(75)
});
break;
}
default:
{
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4,
new[] { Background[reverse ? 0 : 1], Background[reverse ? 1 : 0] },
SKShaderTileMode.Clamp)
});
break;
}
}
}
}
protected void DrawPreview(SKCanvas c)
=> c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint);
protected void DrawTextBackground(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
switch (Style)
{
case EIconStyle.Flat:
{
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(Margin, Height - Margin);
pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f);
pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f);
pathBottom.LineTo(Width - Margin, Height - Margin);
pathBottom.Close();
c.DrawPath(pathBottom, _textBackgroundPaint);
break;
}
default:
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
break;
}
}
protected void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
var x = Width / 2f;
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch (Style)
{
case EIconStyle.Flat:
{
DisplayNamePaint.TextAlign = SKTextAlign.Right;
x = Width - Margin * 2;
break;
}
}
#if DEBUG
var halfWidth = shapedText.Width / 2f;
c.DrawLine(x - halfWidth, 0, x - halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
c.DrawLine(x + halfWidth, 0, x + halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint { Color = SKColors.Blue, IsStroke = true });
#endif
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
}
protected void DrawDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(Description)) return;
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
var side = SKTextAlign.Center;
switch (Style)
{
case EIconStyle.Flat:
side = SKTextAlign.Right;
break;
}
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
}
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
{
if (string.IsNullOrEmpty(text)) return;
_shortDescriptionPaint.TextAlign = side;
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
switch (side)
{
case SKTextAlign.Left:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
case SKTextAlign.Right:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
}
}
}

View File

@ -0,0 +1,256 @@
using System;
using System.Runtime.CompilerServices;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases;
using FModel.Creator.Bases.BB;
using FModel.Creator.Bases.FN;
using FModel.Creator.Bases.MV;
using FModel.Creator.Bases.SB;
namespace FModel.Creator;
public class CreatorPackage : IDisposable
{
private UObject _object;
private EIconStyle _style;
public CreatorPackage(UObject uObject, EIconStyle style)
{
_object = uObject;
_style = style;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator()
{
TryConstructCreator(out var creator);
return creator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator(out UCreator creator)
{
switch (_object.ExportType)
{
// Fortnite
case "FortCreativeWeaponMeleeItemDefinition":
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "JunoKnowledgeBundle":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "SparksMicItemDefinition":
case "FortAwardItemDefinition":
case "FortStackItemDefinition":
case "FortWorldItemDefinition":
case "SparksAuraItemDefinition":
case "SparksDrumItemDefinition":
case "SparksBassItemDefinition":
case "FortGadgetItemDefinition":
case "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortMissionItemDefinition":
case "FortAccountItemDefinition":
case "SparksGuitarItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortBuildingItemDefinition":
case "FortWeaponModItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortAlterableItemDefinition":
case "SparksKeyboardItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortConsumableItemDefinition":
case "StWFortAccoladeItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortPrerollDataItemDefinition":
case "JunoRecipeBundleItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "FortPlayerAugmentItemDefinition":
case "FortSmartBuildingItemDefinition":
case "FortGiftBoxUnlockItemDefinition":
case "FortWeaponModItemDefinitionOptic":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "JunoWeaponCreatureItemDefinition":
case "FortEventDependentItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortWeaponModItemDefinitionMagazine":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "JunoBuildInstructionsItemDefinition":
case "FortCharacterCosmeticItemDefinition":
case "JunoBuildingSetAccountItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortWeaponMeleeOffhandItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortVehicleCosmeticsVariantTokenType":
case "JunoBuildingPropAccountItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortCreativeWeaponRangedItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Body":
case "FortVehicleCosmeticsItemDefinition_Skin":
case "FortVehicleCosmeticsItemDefinition_Wheel":
case "FortCreativeRealEstatePlotItemDefinition":
case "FortDeployableBaseCloudSaveItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Booster":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
_ => new BaseIcon(_object, _style)
};
return true;
case "JunoAthenaCharacterItemOverrideDefinition":
case "JunoAthenaDanceItemOverrideDefinition":
creator = new BaseJuno(_object, _style);
return true;
case "FortTandemCharacterData":
creator = new BaseTandem(_object, _style);
return true;
case "FortTrapItemDefinition":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortWeaponRangedItemDefinition":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
creator = new BaseIconStats(_object, _style);
return true;
case "FortItemSeriesDefinition":
creator = new BaseSeries(_object, _style);
return true;
case "MaterialInstanceConstant"
when _object.Owner != null &&
(_object.Owner.Name.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_object, _style);
return true;
case "FortMtxOfferData":
creator = new BaseMtxOffer(_object, _style);
return true;
case "FortPlaylistAthena":
creator = new BasePlaylist(_object, _style);
return true;
case "FortFeatItemDefinition":
case "FortQuestItemDefinition":
case "FortQuestItemDefinition_Athena":
case "FortQuestItemDefinition_Campaign":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new Bases.FN.BaseQuest(_object, _style);
return true;
case "FortCompendiumItemDefinition":
case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object, _style);
return true;
// case "AthenaSeasonItemDefinition":
// creator = new BaseSeason(_object, _style);
// return true;
case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object, _style);
return true;
case "FortCreativeOption":
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// PandaGame
case "CharacterData":
creator = new BaseFighter(_object, _style);
return true;
case "PerkGroup":
creator = new BasePerkGroup(_object, _style);
return true;
case "StatTrackingBundleData":
case "HydraSyncedDataAsset":
case "AnnouncerPackData":
case "CharacterGiftData":
case "ProfileIconData":
case "RingOutVfxData":
case "BannerData":
case "EmoteData":
case "TauntData":
case "SkinData":
case "PerkData":
creator = new BasePandaIcon(_object, _style);
return true;
case "QuestData":
creator = new Bases.MV.BaseQuest(_object, _style);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_object.ExportType} | {_style}";
public void Dispose()
{
_object = null;
}
}

199
FModel/Creator/Typefaces.cs Normal file
View File

@ -0,0 +1,199 @@
using System;
using System.IO;
using System.Windows;
using CUE4Parse.UE4.Versions;
using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator;
public class Typefaces
{
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
private const string _EXT = ".ufont";
// FortniteGame
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; // japanese fortnite
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
// PandaGame
private const string _PANDAGAME_BASE_PATH = "/Game/Panda_Main/UI/Fonts/";
private const string _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC = "Norms/TT_Norms_Std_Condensed_ExtraBold_Italic";
private const string _NORMS_PRO_EXTRABOLD_ITALIC = "Norms/TT_Norms_Pro_ExtraBold_Italic";
private const string _NORMS_STD_CONDENSED_MEDIUM = "Norms/TT_Norms_Std_Condensed_Medium";
private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black";
private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy";
private readonly CUE4ParseViewModel _viewModel;
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
public readonly SKTypeface DisplayName;
public readonly SKTypeface Description;
public readonly SKTypeface Bottom; // must be null for non-latin base languages
public readonly SKTypeface Bundle;
public readonly SKTypeface BundleNumber;
public readonly SKTypeface TandemDisplayName;
public readonly SKTypeface TandemGenDescription;
public readonly SKTypeface TandemAddDescription;
public Typefaces(CUE4ParseViewModel viewModel)
{
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
{
case "FORTNITEGAME":
{
DisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
} + _EXT);
Description = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _NOTO_SANS_REGULAR
} + _EXT);
Bottom = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => string.Empty,
ELanguage.Japanese => string.Empty,
ELanguage.Arabic => string.Empty,
ELanguage.TraditionalChinese => string.Empty,
ELanguage.Chinese => string.Empty,
_ => _BURBANK_SMALL_BOLD
} + _EXT, true);
BundleNumber = OnTheFly(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT);
Bundle = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
} + _EXT, true) ?? BundleNumber;
TandemDisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_BIG_REGULAR_BLACK
} + _EXT);
TandemGenDescription = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BLACK
} + _EXT);
TandemAddDescription = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BOLD
} + _EXT);
break;
}
case "MULTIVERSUS":
{
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_PRO_EXTRABOLD_ITALIC
} + _EXT);
Description = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
TandemDisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC
} + _EXT);
TandemGenDescription = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
break;
}
default:
{
DisplayName = Default;
Description = Default;
break;
}
}
}
public SKTypeface OnTheFly(string path, bool fallback = false)
{
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return fallback ? null : Default;
var m = new MemoryStream(data) { Position = 0 };
return SKTypeface.FromStream(m);
}
}

420
FModel/Creator/Utils.cs Normal file
View File

@ -0,0 +1,420 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Objects;
using FModel.Framework;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator;
public static class Utils
{
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
{
var match = _htmlRegex.Match(s);
while (match.Success)
{
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
return s;
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
{
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
{
preview = GetBitmap(sidePanelIcon);
return preview != null;
}
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
{
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
}
preview = GetBitmap(material);
return preview != null;
}
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
switch (export)
{
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
default:
{
if (export.TryGetValue(out FInstancedStruct[] dataList, "DataList")) return GetBitmap(dataList);
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
{
packageIndex = smallPreview;
continue;
}
return null;
}
}
}
}
public static SKBitmap GetBitmap(FInstancedStruct[] structs)
{
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "LargeIcon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isl)
{
return GetBitmap(isl.NonConstStruct.Get<FSoftObjectPath>("LargeIcon"));
}
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "Icon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isi)
{
return GetBitmap(isi.NonConstStruct.Get<FSoftObjectPath>("Icon"));
}
return null;
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "MainTex":
case "Texture":
case "TextureA":
case "TextureB":
case "OfferImage":
case "KeyArtTexture":
case "NPC-Portrait":
{
return GetBitmap(texture);
}
}
}
return null;
}
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 });
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
public static SKBitmap GetBitmap(UTexture2D texture) => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
{
var ratioX = width / me.Width;
var ratioY = height / me.Height;
return ResizeWithRatio(me, ratioX < ratioY ? ratioX : ratioY);
}
public static SKBitmap ResizeWithRatio(this SKBitmap me, double ratio)
{
return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio));
}
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
public static SKBitmap Resize(this SKBitmap me, int width, int height)
{
var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
using var pixmap = bmp.PeekPixels();
me.ScalePixels(pixmap, SKFilterQuality.Medium);
return bmp;
}
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UObject
{
return packageIndex.TryLoad(out export);
}
// fullpath must be either without any extension or with the export objectname
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
{
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
}
public static IEnumerable<UObject> LoadExports(string packagePath)
{
return _applicationView.CUE4Parse.Provider.LoadAllObjects(packagePath);
}
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
{
var max = maxFont;
var min = 0f;
var last = -1f;
float value;
while (true)
{
value = min + ((max - min) / 2);
using (SKFont ft = new SKFont(typeface, value))
using (SKPaint paint = new SKPaint(ft))
{
if (paint.MeasureText(text) > sectorSize)
{
last = value;
max = value;
}
else
{
min = value;
if (Math.Abs(last - value) <= degreeOfCertainty)
return last;
last = value;
}
}
}
}
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
{
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue);
}
public static string GetLocalizedResource<T>(T @enum) where T : Enum
{
var resource = _applicationView.CUE4Parse.Provider.GetLocalizedString("", @enum.GetDescription(), @enum.ToString());
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(resource.ToLower());
}
public static string GetFullPath(string partialPath)
{
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
{
if (regex.IsMatch(path))
{
return path;
}
}
return string.Empty;
}
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
{
var lineHeight = paint.TextSize * 1.2f;
var lines = SplitLines(text, paint, area.Width - margin);
#if DEBUG
c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint { Color = SKColors.Red, IsStroke = true });
#endif
if (lines == null) return;
if (lines.Count <= maxCount) maxCount = lines.Count;
var height = maxCount * lineHeight;
var y = area.MidY - height / 2;
var shaper = new CustomSKShaper(paint.Typeface);
for (var i = 0; i < maxCount; i++)
{
var line = lines[i];
if (line == null) continue;
var lineText = line.Trim();
var shapedText = shaper.Shape(lineText, paint);
y += lineHeight;
var x = side switch
{
SKTextAlign.Center => area.MidX - shapedText.Width / 2,
SKTextAlign.Right => size - margin - shapedText.Width,
SKTextAlign.Left => margin,
_ => throw new NotImplementedException()
};
c.DrawShapedText(shaper, lineText, x, y, paint);
}
}
public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos)
{
yPos = area.Top;
var lineHeight = paint.TextSize * 1.2f;
var lines = SplitLines(text, paint, area.Width);
if (lines == null) return;
foreach (var line in lines)
{
var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line);
if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages)
{
paint.TextSize = fontSize;
lineHeight = paint.TextSize * 1.2f;
}
if (line == null) continue;
var lineText = line.Trim();
var shaper = new CustomSKShaper(paint.Typeface);
var shapedText = shaper.Shape(lineText, paint);
var x = side switch
{
SKTextAlign.Center => area.MidX - shapedText.Width / 2,
SKTextAlign.Right => size - margin - shapedText.Width,
SKTextAlign.Left => area.Left,
_ => throw new NotImplementedException()
};
c.DrawShapedText(shaper, lineText, x, yPos, paint);
yPos += lineHeight;
}
#if DEBUG
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint { Color = SKColors.Red, IsStroke = true });
#endif
}
#region Chinese, Korean and Japanese text split
// https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs
static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)";
static string keywords = @"(\&nbsp;|[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[---]+)";
static string periods = @"([\.\,。、!\!\?]+)$";
static string bracketsBegin = @"([〈《「『「((\[【〔〚〖〘❮❬❪❨(<{❲❰{❴])";
static string bracketsEnd = @"([〉》」』」)\]】〕〗〙〛}>\)❩❫❭❯❱❳❵}])";
public static string[] SplitCKJText(string str)
{
var line1 = Regex.Split(str, keywords).ToList();
var line2 = line1.SelectMany((o, _) => Regex.Split(o, joshi)).ToList();
var line3 = line2.SelectMany((o, _) => Regex.Split(o, bracketsBegin)).ToList();
var line4 = line3.SelectMany((o, _) => Regex.Split(o, bracketsEnd)).ToList();
var words = line4.Where(o => !string.IsNullOrEmpty(o)).ToList();
var prevType = string.Empty;
var prevWord = string.Empty;
List<string> result = new List<string>();
words.ForEach(word =>
{
var token = Regex.IsMatch(word, periods) || Regex.IsMatch(word, joshi);
if (Regex.IsMatch(word, bracketsBegin))
{
prevType = "braketBegin";
prevWord = word;
return;
}
if (Regex.IsMatch(word, bracketsEnd))
{
result[result.Count - 1] += word;
prevType = "braketEnd";
prevWord = word;
return;
}
if (prevType == "braketBegin")
{
word = prevWord + word;
prevWord = string.Empty;
prevType = string.Empty;
}
// すでに文字が入っている上で助詞が続く場合は結合する
if (result.Count > 0 && token && prevType == string.Empty)
{
result[result.Count - 1] += word;
prevType = "keyword";
prevWord = word;
return;
}
// 単語のあとの文字がひらがななら結合する
if (result.Count > 1 && token || (prevType == "keyword" && Regex.IsMatch(word, @"[ぁ-んゝ]+")))
{
result[result.Count - 1] += word;
prevType = string.Empty;
prevWord = word;
return;
}
result.Add(word);
prevType = "keyword";
prevWord = word;
});
return result.ToArray();
}
#endregion
public static List<string> SplitLines(string text, SKPaint paint, float maxWidth)
{
if (string.IsNullOrEmpty(text)) return null;
var spaceWidth = paint.MeasureText(" ");
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
var ret = new List<string>(lines.Length);
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
float width = 0;
var isCJK = false;
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese)
{
words = SplitCKJText(line);
isCJK = true;
}
var lineResult = new StringBuilder();
foreach (var word in words)
{
var wordWidth = paint.MeasureText(word);
var wordWithSpaceWidth = wordWidth + spaceWidth;
var wordWithSpace = isCJK ? word : word + " ";
if (width + wordWidth > maxWidth)
{
ret.Add(lineResult.ToString());
lineResult = new StringBuilder(wordWithSpace);
width = wordWithSpaceWidth;
}
else
{
lineResult.Append(wordWithSpace);
width += wordWithSpaceWidth;
}
}
ret.Add(lineResult.ToString());
}
return ret;
}
}

116
FModel/Enums.cs Normal file
View File

@ -0,0 +1,116 @@
using System;
using System.ComponentModel;
namespace FModel;
public enum EBuildKind
{
Debug,
Release,
Unknown
}
public enum EErrorKind
{
Ignore,
Restart,
ResetSettings
}
public enum SettingsOut
{
ReloadLocres,
ReloadMappings
}
public enum EStatusKind
{
Ready, // ready
Loading, // doing stuff
Stopping, // trying to stop
Stopped, // stopped
Failed, // crashed
Completed // worked
}
public enum EAesReload
{
[Description("Always")]
Always,
[Description("Never")]
Never,
[Description("Once Per Day")]
OncePerDay
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum ELoadingMode
{
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
// public enum EUpdateMode
// {
// [Description("Stable")]
// Stable,
// [Description("Beta")]
// Beta,
// [Description("QA Testing")]
// Qa
// }
public enum ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
public enum EEndpointType
{
Aes,
Mapping
}
[Flags]
public enum EBulkType
{
None = 0,
Auto = 1 << 0,
Properties = 1 << 1,
Textures = 1 << 2,
Meshes = 1 << 3,
Skeletons = 1 << 4,
Animations = 1 << 5
}

View File

@ -0,0 +1,54 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace FModel.Extensions;
public static class AvalonExtensions
{
private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd");
private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd");
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
private static readonly IHighlightingDefinition _verseHighlighter = LoadHighlighter("Verse.xshd");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
{
var executingAssembly = Assembly.GetExecutingAssembly();
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
using var reader = new XmlTextReader(stream);
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
{
case "ini":
case "csv":
return _iniHighlighter;
case "xml":
case "tps":
return _xmlHighlighter;
case "h":
case "cpp":
return _cppHighlighter;
case "changelog":
return _changelogHighlighter;
case "verse":
return _verseHighlighter;
case "bat":
case "txt":
case "pem":
case "po":
return null;
default:
return _jsonHighlighter;
}
}
}

View File

@ -0,0 +1,198 @@
using SkiaSharp;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
namespace FModel.Extensions;
public static class ClipboardExtensions
{
public static void SetImage(byte[] pngBytes, string fileName = null)
{
Clipboard.Clear();
var data = new DataObject();
using var pngMs = new MemoryStream(pngBytes);
using var image = Image.FromStream(pngMs);
// As standard bitmap, without transparency support
data.SetData(DataFormats.Bitmap, image, true);
// As PNG. Gimp will prefer this over the other two
data.SetData("PNG", pngMs, false);
// As DIB. This is (wrongly) accepted as ARGB by many applications
using var dibMemStream = new MemoryStream(ConvertToDib(image));
data.SetData(DataFormats.Dib, dibMemStream, false);
// Optional fileName
if (!string.IsNullOrEmpty(fileName))
{
var htmlFragment = GenerateHTMLFragment($"<img src=\"{fileName}\"/>");
data.SetData(DataFormats.Html, htmlFragment);
}
// The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation
Clipboard.SetDataObject(data, true);
}
public static byte[] ConvertToDib(Image image)
{
byte[] bm32bData;
var width = image.Width;
var height = image.Height;
// Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
using (var bm32b = new Bitmap(width, height, PixelFormat.Format32bppPArgb))
{
using (var gr = Graphics.FromImage(bm32b))
{
gr.DrawImage(image, new Rectangle(0, 0, width, height));
}
// Bitmap format has its lines reversed.
bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
bm32bData = GetRawBytes(bm32b);
}
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length);
return fullImage;
}
private static byte[] ConvertToDib(byte[] pngBytes = null)
{
byte[] bm32bData;
int width, height;
using (var skBmp = SKBitmap.Decode(pngBytes))
{
width = skBmp.Width;
height = skBmp.Height;
using var rotated = new SKBitmap(new SKImageInfo(width, height, skBmp.ColorType));
using var canvas = new SKCanvas(rotated);
canvas.Scale(1, -1, 0, height / 2.0f);
canvas.DrawBitmap(skBmp, SKPoint.Empty);
canvas.Flush();
bm32bData = rotated.Bytes;
}
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length);
return fullImage;
}
public static unsafe byte[] GetRawBytes(Bitmap bmp)
{
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
var bytes = (uint) (Math.Abs(bmpData.Stride) * bmp.Height);
var buffer = new byte[bytes];
fixed (byte* pBuffer = buffer)
{
Unsafe.CopyBlockUnaligned(pBuffer, bmpData.Scan0.ToPointer(), bytes);
}
bmp.UnlockBits(bmpData);
return buffer;
}
private static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value)
{
var lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
{
throw new ArgumentOutOfRangeException(nameof(startIndex), "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
}
for (var index = 0; index < bytes; index++)
{
var offs = startIndex + (littleEndian ? index : lastByte - index);
data[offs] = (byte) (value >> 8 * index & 0xFF);
}
}
private static string GenerateHTMLFragment(string html)
{
var sb = new StringBuilder();
const string header = "Version:0.9\r\nStartHTML:<<<<<<<<<1\r\nEndHTML:<<<<<<<<<2\r\nStartFragment:<<<<<<<<<3\r\nEndFragment:<<<<<<<<<4\r\n";
const string startHTML = "<html>\r\n<body>\r\n";
const string startFragment = "<!--StartFragment-->";
const string endFragment = "<!--EndFragment-->";
const string endHTML = "\r\n</body>\r\n</html>";
sb.Append(header);
var startHTMLLength = header.Length;
var startFragmentLength = startHTMLLength + startHTML.Length + startFragment.Length;
var endFragmentLength = startFragmentLength + Encoding.UTF8.GetByteCount(html);
var endHTMLLength = endFragmentLength + endFragment.Length + endHTML.Length;
sb.Replace("<<<<<<<<<1", startHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<2", endHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<3", startFragmentLength.ToString("D10"));
sb.Replace("<<<<<<<<<4", endFragmentLength.ToString("D10"));
sb.Append(startHTML);
sb.Append(startFragment);
sb.Append(html);
sb.Append(endFragment);
sb.Append(endHTML);
return sb.ToString();
}
}

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public static class CollectionExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, T value)
{
var i = collection.IndexOf(value) + 1;
return i >= collection.Count ? collection.First() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, int index)
{
var i = index + 1;
return i >= collection.Count ? collection.First() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this IList<T> collection, T value)
{
var i = collection.IndexOf(value) - 1;
return i < 0 ? collection.Last() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this IList<T> collection, int index)
{
var i = index - 1;
return i < 0 ? collection.Last() : collection[i];
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public static class EnumExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetDescription(this Enum value)
{
var fi = value.GetType().GetField(value.ToString());
if (fi == null) return $"{value} ({value:D})";
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0) return attributes[0].Description;
var suffix = $"{value:D}";
var current = Convert.ToInt32(suffix);
var target = current & ~0xF;
if (current != target)
{
var values = Enum.GetValues(value.GetType());
var index = Array.IndexOf(values, value);
suffix = values.GetValue(index - (current - target))?.ToString();
}
return $"{value} ({suffix})";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ToEnum<T>(this string value, T defaultValue) where T : struct => !Enum.TryParse(value, true, out T ret) ? defaultValue : ret;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasAnyFlags<T>(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetIndex(this Enum value)
{
var values = Enum.GetValues(value.GetType());
return Array.IndexOf(values, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this Enum value)
{
var values = Enum.GetValues(value.GetType());
var i = Array.IndexOf(values, value) + 1;
return i == values.Length ? (T) values.GetValue(0) : (T) values.GetValue(i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this Enum value)
{
var values = Enum.GetValues(value.GetType());
var i = Array.IndexOf(values, value) - 1;
return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i);
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public enum Endianness
{
LittleEndian,
BigEndian
}
public static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
{
var b1 = s.ReadByte();
var b2 = s.ReadByte();
var b3 = s.ReadByte();
var b4 = s.ReadByte();
return endian switch
{
Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1),
Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4),
_ => throw new Exception("unknown endianness")
};
}
}

View File

@ -0,0 +1,185 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Document;
namespace FModel.Extensions;
public static class StringExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
{
if (size == 0) return "0 B";
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
var order = 0;
while (size >= 1024 && order < sizes.Length - 1)
{
order++;
size /= 1024;
}
return $"{size:# ###.##} {sizes[order]}".TrimStart();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, char delimiter)
{
var index = s.IndexOf(delimiter);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.IndexOf(delimiter, comparisonType);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfter(this string s, char delimiter)
{
var index = s.IndexOf(delimiter);
return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfter(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.IndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s[..index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeWithLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s[..(index + 1)];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBeforeWithLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s[..(index + 1)];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfterLast(this string s, char delimiter)
{
var index = s.LastIndexOf(delimiter);
return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringAfterLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetNameLineNumber(this string s, string lineToFind)
{
if (int.TryParse(lineToFind, out var index))
return s.GetLineNumber(index);
lineToFind = $" \"Name\": \"{lineToFind}\",";
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetParentExportType(this TextDocument doc, int startOffset)
{
var line = doc.GetLineByOffset(startOffset);
var lineNumber = line.LineNumber - 1;
while (doc.GetText(line.Offset, line.Length) is { } content)
{
if (content.StartsWith(" \"Type\": \"", StringComparison.OrdinalIgnoreCase))
return content.Split("\"")[3];
lineNumber--;
if (lineNumber < 1) break;
line = doc.GetLineByNumber(lineNumber);
}
return string.Empty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetKismetLineNumber(this string s, string input)
{
var match = Regex.Match(input, @"^(.+)\[(\d+)\]$");
var name = match.Groups[1].Value;
int index = int.Parse(match.Groups[2].Value);
var lineToFind = $" \"Name\": \"{name}\",";
var offset = $"\"StatementIndex\": {index}";
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
{
var objectLine = lineNum;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Contains(offset, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
return objectLine;
}
}
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLineNumber(this string s, int index)
{
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(" {"))
index--;
if (index == -1)
return lineNum + 1;
}
return 1;
}
}

View File

@ -1,158 +1,275 @@
<?xml version="1.0" encoding="utf-8"?> <Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <OutputType>WinExe</OutputType>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <TargetFramework>net8.0-windows</TargetFramework>
<ProjectGuid>{71A31C47-30BC-4CB5-AD89-81E5008F4BEB}</ProjectGuid> <UseWPF>true</UseWPF>
<OutputType>Exe</OutputType> <ApplicationIcon>FModel.ico</ApplicationIcon>
<RootNamespace>FModel</RootNamespace> <Version>4.4.4.0</Version>
<AssemblyName>FModel</AssemblyName> <AssemblyVersion>4.4.4.0</AssemblyVersion>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileVersion>4.4.4.0</FileVersion>
<FileAlignment>512</FileAlignment> <IsPackable>false</IsPackable>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <IsPublishable>true</IsPublishable>
<Deterministic>true</Deterministic> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<NuGetPackageImportStamp> <PublishSingleFile Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">true</PublishSingleFile>
</NuGetPackageImportStamp> <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<IsWebBootstrapper>false</IsWebBootstrapper> <StartupObject>FModel.App</StartupObject>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>true</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>false</MapFileExtensions>
<WebPage>publish.htm</WebPage>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols> <PlatformTarget>x64</PlatformTarget>
<DebugType>full</DebugType> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>false</Optimize> <NoWarn>1701;1702;NU1701</NoWarn>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>pdbonly</DebugType> <PlatformTarget>x64</PlatformTarget>
<Optimize>true</Optimize> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputPath>bin\Release\</OutputPath> <NoWarn>NU1701</NoWarn>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<ApplicationIcon>FNTools_Logo.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>3ED5A42E3C4689AF46F18366F8BF30D245116948</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>FModel_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>false</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<SignManifests>false</SignManifests>
</PropertyGroup>
<PropertyGroup>
<TargetZone>LocalIntranet</TargetZone>
</PropertyGroup>
<PropertyGroup />
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <None Remove="Resources\android.png" />
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <None Remove="Resources\apple.png" />
</Reference> <None Remove="Resources\battlebreakers.png" />
<Reference Include="System" /> <None Remove="Resources\blueprint.png" />
<Reference Include="System.Core" /> <None Remove="Resources\borderlands.png" />
<Reference Include="System.Xml.Linq" /> <None Remove="Resources\empty_folder.png" />
<Reference Include="System.Data.DataSetExtensions" /> <None Remove="Resources\engine.png" />
<Reference Include="Microsoft.CSharp" /> <None Remove="Resources\fallenorder.png" />
<Reference Include="System.Data" /> <None Remove="Resources\FModel.ico" />
<Reference Include="System.Deployment" /> <None Remove="Resources\folder.png" />
<Reference Include="System.Drawing" /> <None Remove="Resources\label.png" />
<Reference Include="System.Net.Http" /> <None Remove="Resources\fortnite.png" />
<Reference Include="System.Windows.Forms" /> <None Remove="Resources\fortnitebr.png" />
<Reference Include="System.Xml" /> <None Remove="Resources\gear.png" />
<None Remove="Resources\localization.png" />
<None Remove="Resources\materialicon.png" />
<None Remove="Resources\square.png" />
<None Remove="Resources\square_off.png" />
<None Remove="Resources\cube.png" />
<None Remove="Resources\cube_off.png" />
<None Remove="Resources\light.png" />
<None Remove="Resources\light_off.png" />
<None Remove="Resources\pc.png" />
<None Remove="Resources\puzzle.png" />
<None Remove="Resources\roguecompany.png" />
<None Remove="Resources\sound.png" />
<None Remove="Resources\creative.png" />
<None Remove="Resources\spellbreak.png" />
<None Remove="Resources\texture.png" />
<None Remove="Resources\thecycle.png" />
<None Remove="Resources\ui.png" />
<None Remove="Resources\valorant.png" />
<None Remove="Resources\weapon.png" />
<None Remove="Resources\windows.png" />
<None Remove="Resources\cinematics.png" />
<None Remove="Resources\archive.png" />
<None Remove="Resources\archive_enabled.png" />
<None Remove="Resources\archive_disabled.png" />
<None Remove="Resources\unknown_asset.png" />
<None Remove="Resources\asset.png" />
<None Remove="Resources\asset_ini.png" />
<None Remove="Resources\asset_psd.png" />
<None Remove="Resources\asset_png.png" />
<None Remove="Resources\athena.png" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Verse.xshd" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\Changelog.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\checker.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\Default.png" />
<None Remove="Resources\NoBackground.png" />
<None Remove="Resources\NoText.png" />
<None Remove="Resources\Flat.png" />
<None Remove="Resources\Cataba.png" />
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
<None Remove="Resources\add_directory.png" />
<None Remove="Resources\delete.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\outline.frag" />
<None Remove="Resources\outline.vert" />
<None Remove="Resources\picking.frag" />
<None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Config.cs" /> <EmbeddedResource Include="Resources\Json.xshd" />
<Compile Include="PAKWindow.cs"> <EmbeddedResource Include="Resources\Ini.xshd" />
<SubType>Form</SubType> <EmbeddedResource Include="Resources\Verse.xshd" />
</Compile> <EmbeddedResource Include="Resources\Xml.xshd" />
<Compile Include="PAKWindow.Designer.cs"> <EmbeddedResource Include="Resources\Cpp.xshd" />
<DependentUpon>PAKWindow.cs</DependentUpon> <EmbeddedResource Include="Resources\Changelog.xshd" />
</Compile> <EmbeddedResource Include="Resources\default.frag" />
<Compile Include="Parser\ItemsIDParser.cs" /> <EmbeddedResource Include="Resources\default.vert" />
<Compile Include="Program.cs" /> <EmbeddedResource Include="Resources\grid.frag" />
<Compile Include="Properties\AssemblyInfo.cs" /> <EmbeddedResource Include="Resources\grid.vert" />
<EmbeddedResource Include="PAKWindow.resx"> <EmbeddedResource Include="Resources\skybox.frag" />
<DependentUpon>PAKWindow.cs</DependentUpon> <EmbeddedResource Include="Resources\skybox.vert" />
</EmbeddedResource> <EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Resources\framebuffer.vert" />
<Generator>ResXFileCodeGenerator</Generator> <EmbeddedResource Include="Resources\outline.frag" />
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <EmbeddedResource Include="Resources\outline.vert" />
<SubType>Designer</SubType> <EmbeddedResource Include="Resources\picking.frag" />
</EmbeddedResource> <EmbeddedResource Include="Resources\picking.vert" />
<Compile Include="Properties\Resources.Designer.cs"> <EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdonisUI" Version="1.17.1" />
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="EpicManifestParser" Version="2.3.3" />
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\android.png" />
<Resource Include="Resources\apple.png" />
<Resource Include="Resources\battlebreakers.png" />
<Resource Include="Resources\blueprint.png" />
<Resource Include="Resources\borderlands.png" />
<Resource Include="Resources\fallenorder.png" />
<Resource Include="Resources\FModel.ico" />
<Resource Include="Resources\folder.png" />
<Resource Include="Resources\label.png" />
<Resource Include="Resources\fortnite.png" />
<Resource Include="Resources\fortnitebr.png" />
<Resource Include="Resources\empty_folder.png" />
<Resource Include="Resources\engine.png" />
<Resource Include="Resources\gear.png" />
<Resource Include="Resources\localization.png" />
<Resource Include="Resources\materialicon.png" />
<Resource Include="Resources\square.png" />
<Resource Include="Resources\square_off.png" />
<Resource Include="Resources\cube.png" />
<Resource Include="Resources\cube_off.png" />
<Resource Include="Resources\light.png" />
<Resource Include="Resources\light_off.png" />
<Resource Include="Resources\pc.png" />
<Resource Include="Resources\puzzle.png" />
<Resource Include="Resources\roguecompany.png" />
<Resource Include="Resources\spellbreak.png" />
<Resource Include="Resources\sound.png" />
<Resource Include="Resources\creative.png" />
<Resource Include="Resources\texture.png" />
<Resource Include="Resources\thecycle.png" />
<Resource Include="Resources\valorant.png" />
<Resource Include="Resources\ui.png" />
<Resource Include="Resources\weapon.png" />
<Resource Include="Resources\windows.png" />
<Resource Include="Resources\cinematics.png" />
<Resource Include="Resources\archive.png" />
<Resource Include="Resources\archive_enabled.png" />
<Resource Include="Resources\archive_disabled.png" />
<Resource Include="Resources\unknown_asset.png" />
<Resource Include="Resources\asset.png" />
<Resource Include="Resources\asset_ini.png" />
<Resource Include="Resources\asset_psd.png" />
<Resource Include="Resources\asset_png.png" />
<Resource Include="Resources\athena.png" />
<Resource Include="Resources\unix.png" />
<Resource Include="Resources\linux.png" />
<Resource Include="Resources\stateofdecay2.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\checker.png" />
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
<Resource Include="Resources\T-Icon-Pets-64.png" />
<Resource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\Default.png" />
<Resource Include="Resources\NoBackground.png" />
<Resource Include="Resources\NoText.png" />
<Resource Include="Resources\Flat.png" />
<Resource Include="Resources\Cataba.png" />
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
<Resource Include="Resources\add_directory.png" />
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\go_to_directory.png" />
<Resource Include="Resources\npcleftside.png" />
<Resource Include="Resources\nx.png" />
<Resource Include="Resources\ny.png" />
<Resource Include="Resources\nz.png" />
<Resource Include="Resources\px.png" />
<Resource Include="Resources\py.png" />
<Resource Include="Resources\pz.png" />
<Resource Include="Resources\pointlight.png" />
<Resource Include="Resources\spotlight.png" />
<Resource Include="Resources\link_on.png" />
<Resource Include="Resources\link_off.png" />
<Resource Include="Resources\link_has.png" />
<Resource Include="Resources\tl_play.png" />
<Resource Include="Resources\tl_pause.png" />
<Resource Include="Resources\tl_rewind.png" />
<Resource Include="Resources\tl_forward.png" />
<Resource Include="Resources\tl_previous.png" />
<Resource Include="Resources\tl_next.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="FNTools_Logo.ico" /> </Project>
<None Include="Resources\BurbankBigCondensed-Bold.otf" />
<None Include="Resources\T512.png" />
<None Include="Resources\M512.png" />
<None Include="Resources\I512.png" />
<None Include="Resources\unknown512.png" />
<None Include="Resources\U512.png" />
<None Include="Resources\R512.png" />
<None Include="Resources\L512.png" />
<None Include="Resources\E512.png" />
<None Include="Resources\C512.png" />
<None Include="Resources\BG512.png" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.6.1">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.6.1 %28x86 et x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

BIN
FModel/FModel.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

37
FModel/FModel.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\CUE4Parse\CUE4Parse.csproj", "{C4620341-BBB7-4384-AC7D-5082D3E0386E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.Build.0 = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53DB7A15-4E15-4575-9402-0110BDF2794E}
EndGlobalSection
EndGlobal

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;
namespace FModel.Framework;
public class AsyncQueue<T> : IAsyncEnumerable<T>
{
private readonly SemaphoreSlim _semaphore = new(1);
private readonly BufferBlock<T> _buffer = new();
public int Count => _buffer.Count;
public void Enqueue(T item) => _buffer.Post(item);
public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default)
{
await _semaphore.WaitAsync(token);
try
{
while (Count > 0)
{
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
}
}
finally
{
_semaphore.Release();
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Windows.Input;
namespace FModel.Framework;
public abstract class Command : ICommand
{
public abstract void Execute(object parameter);
public abstract bool CanExecute(object parameter);
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public event EventHandler CanExecuteChanged;
}

View File

@ -0,0 +1,90 @@
using System;
using HarfBuzzSharp;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using Buffer = HarfBuzzSharp.Buffer;
namespace FModel.Framework;
public class CustomSKShaper : SKShaper
{
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
{
using var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob();
using var face = new Face(blob, index);
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (paint == null)
throw new ArgumentNullException(nameof(paint));
// do the shaping
_font.Shape(buffer);
// get the shaping results
var len = buffer.Length;
var info = buffer.GlyphInfos;
var pos = buffer.GlyphPositions;
// get the sizes
var textSizeY = paint.TextSize / _FONT_SIZE_SCALE;
var textSizeX = textSizeY * paint.TextScaleX;
var points = new SKPoint[len];
var clusters = new uint[len];
var codepoints = new uint[len];
for (var i = 0; i < len; i++)
{
// move the cursor
xOffset += pos[i].XAdvance * textSizeX;
yOffset += pos[i].YAdvance * textSizeY;
codepoints[i] = info[i].Codepoint;
clusters[i] = info[i].Cluster;
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
}
return new Result(codepoints, clusters, points, points[^1].X);
}
public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint);
public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint)
{
if (string.IsNullOrEmpty(text))
return new Result();
using var buffer = new Buffer();
switch (paint.TextEncoding)
{
case SKTextEncoding.Utf8:
buffer.AddUtf8(text);
break;
case SKTextEncoding.Utf16:
buffer.AddUtf16(text);
break;
case SKTextEncoding.Utf32:
buffer.AddUtf32(text);
break;
default:
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
}
buffer.GuessSegmentProperties();
return Shape(buffer, xOffset, yOffset, paint);
}
}

View File

@ -0,0 +1,19 @@
using System;
using RestSharp;
namespace FModel.Framework;
public class FRestRequest : RestRequest
{
private const int TimeoutSeconds = 5;
public FRestRequest(string url, Method method = Method.Get) : base(url, method)
{
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
}
public FRestRequest(Uri uri, Method method = Method.Get) : base(uri, method)
{
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
}
}

View File

@ -0,0 +1,45 @@
namespace FModel.Framework;
public class FStatus : ViewModel
{
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _kind;
public EStatusKind Kind
{
get => _kind;
private set
{
SetProperty(ref _kind, value);
IsReady = Kind != EStatusKind.Loading && Kind != EStatusKind.Stopping;
}
}
private string _label;
public string Label
{
get => _label;
private set => SetProperty(ref _label, value);
}
public FStatus()
{
SetStatus(EStatusKind.Loading);
}
public void SetStatus(EStatusKind kind, string label = "")
{
Kind = kind;
UpdateStatusLabel(label);
}
public void UpdateStatusLabel(string label, string prefix = null)
{
Label = Kind == EStatusKind.Loading ? $"{prefix ?? Kind.ToString()} {label}".Trim() : Kind.ToString();
}
}

View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace FModel.Framework;
public class FullyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection()
{
}
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action is NotifyCollectionChangedAction.Remove or
NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action is NotifyCollectionChangedAction.Add or
NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (var item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var typedSender = (T) sender;
var i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{
}
}

View File

@ -0,0 +1,49 @@
using System.Text;
using System.Windows.Input;
namespace FModel.Framework;
public class Hotkey : ViewModel
{
private Key _key;
public Key Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private ModifierKeys _modifiers;
public ModifierKeys Modifiers
{
get => _modifiers;
set => SetProperty(ref _modifiers, value);
}
public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None)
{
Key = key;
Modifiers = modifiers;
}
public bool IsTriggered(Key e)
{
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
}
public override string ToString()
{
var str = new StringBuilder();
if (Modifiers.HasFlag(ModifierKeys.Control))
str.Append("Ctrl + ");
if (Modifiers.HasFlag(ModifierKeys.Shift))
str.Append("Shift + ");
if (Modifiers.HasFlag(ModifierKeys.Alt))
str.Append("Alt + ");
if (Modifiers.HasFlag(ModifierKeys.Windows))
str.Append("Win + ");
str.Append(Key);
return str.ToString();
}
}

View File

@ -0,0 +1,608 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using FModel.Settings;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using ErrorCode = OpenTK.Graphics.OpenGL4.ErrorCode;
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
namespace FModel.Framework;
public class ImGuiController : IDisposable
{
private bool _frameBegun;
private int _vertexArray;
private int _vertexBuffer;
private int _vertexBufferSize;
private int _indexBuffer;
private int _indexBufferSize;
//private Texture _fontTexture;
private int _fontTexture;
private int _shader;
private int _shaderFontTextureLocation;
private int _shaderProjectionMatrixLocation;
private int _windowWidth;
private int _windowHeight;
public ImFontPtr FontNormal;
public ImFontPtr FontBold;
public ImFontPtr FontSemiBold;
private readonly Vector2 _scaleFactor = Vector2.One;
public readonly float DpiScale = GetDpiScale();
private static bool KHRDebugAvailable = false;
public ImGuiController(int width, int height)
{
_windowWidth = width;
_windowHeight = height;
int major = GL.GetInteger(GetPName.MajorVersion);
int minor = GL.GetInteger(GetPName.MinorVersion);
KHRDebugAvailable = (major == 4 && minor >= 3) || IsExtensionSupported("KHR_debug");
IntPtr context = ImGui.CreateContext();
ImGui.SetCurrentContext(context);
var io = ImGui.GetIO();
unsafe
{
var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
}
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
CreateDeviceResources();
SetPerFrameImGuiData(1f / 60f);
ImGui.NewFrame();
_frameBegun = true;
}
public void Bold() => PushFont(FontBold);
public void SemiBold() => PushFont(FontSemiBold);
public void PopFont()
{
ImGui.PopFont();
PushFont(FontNormal);
}
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
public void WindowResized(int width, int height)
{
_windowWidth = width;
_windowHeight = height;
}
public void DestroyDeviceObjects()
{
Dispose();
}
public void CreateDeviceResources()
{
_vertexBufferSize = 10000;
_indexBufferSize = 2000;
int prevVAO = GL.GetInteger(GetPName.VertexArrayBinding);
int prevArrayBuffer = GL.GetInteger(GetPName.ArrayBufferBinding);
_vertexArray = GL.GenVertexArray();
GL.BindVertexArray(_vertexArray);
LabelObject(ObjectLabelIdentifier.VertexArray, _vertexArray, "ImGui");
_vertexBuffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
LabelObject(ObjectLabelIdentifier.Buffer, _vertexBuffer, "VBO: ImGui");
GL.BufferData(BufferTarget.ArrayBuffer, _vertexBufferSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
_indexBuffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer);
LabelObject(ObjectLabelIdentifier.Buffer, _indexBuffer, "EBO: ImGui");
GL.BufferData(BufferTarget.ElementArrayBuffer, _indexBufferSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
RecreateFontDeviceTexture();
string VertexSource = @"#version 460 core
uniform mat4 projection_matrix;
layout(location = 0) in vec2 in_position;
layout(location = 1) in vec2 in_texCoord;
layout(location = 2) in vec4 in_color;
out vec4 color;
out vec2 texCoord;
void main()
{
gl_Position = projection_matrix * vec4(in_position, 0, 1);
color = in_color;
texCoord = in_texCoord;
}";
string FragmentSource = @"#version 460 core
uniform sampler2D in_fontTexture;
in vec4 color;
in vec2 texCoord;
out vec4 outputColor;
void main()
{
outputColor = color * texture(in_fontTexture, texCoord);
}";
_shader = CreateProgram("ImGui", VertexSource, FragmentSource);
_shaderProjectionMatrixLocation = GL.GetUniformLocation(_shader, "projection_matrix");
_shaderFontTextureLocation = GL.GetUniformLocation(_shader, "in_fontTexture");
int stride = Unsafe.SizeOf<ImDrawVert>();
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, 0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 8);
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, stride, 16);
GL.EnableVertexAttribArray(0);
GL.EnableVertexAttribArray(1);
GL.EnableVertexAttribArray(2);
GL.BindVertexArray(prevVAO);
GL.BindBuffer(BufferTarget.ArrayBuffer, prevArrayBuffer);
CheckGLError("End of ImGui setup");
}
/// <summary>
/// Recreates the device texture used to render text.
/// </summary>
public void RecreateFontDeviceTexture()
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height, out int bytesPerPixel);
int mips = (int)Math.Floor(Math.Log(Math.Max(width, height), 2));
int prevActiveTexture = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int prevTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
_fontTexture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, _fontTexture);
GL.TexStorage2D(TextureTarget2d.Texture2D, mips, SizedInternalFormat.Rgba8, width, height);
LabelObject(ObjectLabelIdentifier.Texture, _fontTexture, "ImGui Text Atlas");
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, width, height, PixelFormat.Bgra, PixelType.UnsignedByte, pixels);
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, mips - 1);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
// Restore state
GL.BindTexture(TextureTarget.Texture2D, prevTexture2D);
GL.ActiveTexture((TextureUnit)prevActiveTexture);
io.Fonts.SetTexID((IntPtr)_fontTexture);
io.Fonts.ClearTexData();
}
/// <summary>
/// Renders the ImGui draw list data.
/// </summary>
public void Render()
{
if (_frameBegun)
{
_frameBegun = false;
ImGui.Render();
RenderImDrawData(ImGui.GetDrawData());
}
CheckGLError("End of frame");
}
/// <summary>
/// Updates ImGui input and IO configuration state.
/// </summary>
public void Update(GameWindow wnd, float deltaSeconds)
{
if (_frameBegun)
{
ImGui.Render();
}
SetPerFrameImGuiData(deltaSeconds);
UpdateImGuiInput(wnd);
_frameBegun = true;
ImGui.NewFrame();
}
/// <summary>
/// Sets per-frame data based on the associated window.
/// This is called by Update(float).
/// </summary>
private void SetPerFrameImGuiData(float deltaSeconds)
{
ImGuiIOPtr io = ImGui.GetIO();
// if (io.WantSaveIniSettings) ImGui.SaveIniSettingsToDisk(_iniPath);
io.DisplaySize = new Vector2(
_windowWidth / _scaleFactor.X,
_windowHeight / _scaleFactor.Y);
io.DisplayFramebufferScale = _scaleFactor;
io.DeltaTime = deltaSeconds; // DeltaTime is in seconds.
}
readonly List<char> PressedChars = new List<char>();
private void UpdateImGuiInput(GameWindow wnd)
{
ImGuiIOPtr io = ImGui.GetIO();
var mState = wnd.MouseState;
var kState = wnd.KeyboardState;
io.AddMousePosEvent(mState.X, mState.Y);
io.AddMouseButtonEvent(0, mState[MouseButton.Left]);
io.AddMouseButtonEvent(1, mState[MouseButton.Right]);
io.AddMouseButtonEvent(2, mState[MouseButton.Middle]);
io.AddMouseButtonEvent(3, mState[MouseButton.Button1]);
io.AddMouseButtonEvent(4, mState[MouseButton.Button2]);
io.AddMouseWheelEvent(mState.ScrollDelta.X, mState.ScrollDelta.Y);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
{
if (key == Keys.Unknown) continue;
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
}
foreach (var c in PressedChars)
{
io.AddInputCharacter(c);
}
PressedChars.Clear();
io.KeyShift = kState.IsKeyDown(Keys.LeftShift) || kState.IsKeyDown(Keys.RightShift);
io.KeyCtrl = kState.IsKeyDown(Keys.LeftControl) || kState.IsKeyDown(Keys.RightControl);
io.KeyAlt = kState.IsKeyDown(Keys.LeftAlt) || kState.IsKeyDown(Keys.RightAlt);
io.KeySuper = kState.IsKeyDown(Keys.LeftSuper) || kState.IsKeyDown(Keys.RightSuper);
}
public void PressChar(char keyChar)
{
PressedChars.Add(keyChar);
}
private void RenderImDrawData(ImDrawDataPtr draw_data)
{
if (draw_data.CmdListsCount == 0)
{
return;
}
// Get intial state.
int prevVAO = GL.GetInteger(GetPName.VertexArrayBinding);
int prevArrayBuffer = GL.GetInteger(GetPName.ArrayBufferBinding);
int prevProgram = GL.GetInteger(GetPName.CurrentProgram);
bool prevBlendEnabled = GL.GetBoolean(GetPName.Blend);
bool prevScissorTestEnabled = GL.GetBoolean(GetPName.ScissorTest);
int prevBlendEquationRgb = GL.GetInteger(GetPName.BlendEquationRgb);
int prevBlendEquationAlpha = GL.GetInteger(GetPName.BlendEquationAlpha);
int prevBlendFuncSrcRgb = GL.GetInteger(GetPName.BlendSrcRgb);
int prevBlendFuncSrcAlpha = GL.GetInteger(GetPName.BlendSrcAlpha);
int prevBlendFuncDstRgb = GL.GetInteger(GetPName.BlendDstRgb);
int prevBlendFuncDstAlpha = GL.GetInteger(GetPName.BlendDstAlpha);
bool prevCullFaceEnabled = GL.GetBoolean(GetPName.CullFace);
bool prevDepthTestEnabled = GL.GetBoolean(GetPName.DepthTest);
int prevActiveTexture = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int prevTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
Span<int> prevScissorBox = stackalloc int[4];
unsafe
{
fixed (int* iptr = &prevScissorBox[0])
{
GL.GetInteger(GetPName.ScissorBox, iptr);
}
}
// Bind the element buffer (thru the VAO) so that we can resize it.
GL.BindVertexArray(_vertexArray);
// Bind the vertex buffer so that we can resize it.
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
for (int i = 0; i < draw_data.CmdListsCount; i++)
{
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>();
if (vertexSize > _vertexBufferSize)
{
int newSize = (int)Math.Max(_vertexBufferSize * 1.5f, vertexSize);
GL.BufferData(BufferTarget.ArrayBuffer, newSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
_vertexBufferSize = newSize;
}
int indexSize = cmd_list.IdxBuffer.Size * sizeof(ushort);
if (indexSize > _indexBufferSize)
{
int newSize = (int)Math.Max(_indexBufferSize * 1.5f, indexSize);
GL.BufferData(BufferTarget.ElementArrayBuffer, newSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
_indexBufferSize = newSize;
}
}
// Setup orthographic projection matrix into our constant buffer
ImGuiIOPtr io = ImGui.GetIO();
var mvp = OpenTK.Mathematics.Matrix4.CreateOrthographicOffCenter(
0.0f,
io.DisplaySize.X,
io.DisplaySize.Y,
0.0f,
-1.0f,
1.0f);
GL.UseProgram(_shader);
GL.UniformMatrix4(_shaderProjectionMatrixLocation, false, ref mvp);
GL.Uniform1(_shaderFontTextureLocation, 0);
CheckGLError("Projection");
GL.BindVertexArray(_vertexArray);
CheckGLError("VAO");
draw_data.ScaleClipRects(io.DisplayFramebufferScale);
GL.Enable(EnableCap.Blend);
GL.Enable(EnableCap.ScissorTest);
GL.BlendEquation(BlendEquationMode.FuncAdd);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Disable(EnableCap.CullFace);
GL.Disable(EnableCap.DepthTest);
// Render command lists
for (int n = 0; n < draw_data.CmdListsCount; n++)
{
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
CheckGLError($"Data Vert {n}");
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, cmd_list.IdxBuffer.Size * sizeof(ushort), cmd_list.IdxBuffer.Data);
CheckGLError($"Data Idx {n}");
for (int cmd_i = 0; cmd_i < cmd_list.CmdBuffer.Size; cmd_i++)
{
ImDrawCmdPtr pcmd = cmd_list.CmdBuffer[cmd_i];
if (pcmd.UserCallback != IntPtr.Zero)
{
throw new NotImplementedException();
}
else
{
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, (int)pcmd.TextureId);
CheckGLError("Texture");
// We do _windowHeight - (int)clip.W instead of (int)clip.Y because gl has flipped Y when it comes to these coordinates
var clip = pcmd.ClipRect;
GL.Scissor((int)clip.X, _windowHeight - (int)clip.W, (int)(clip.Z - clip.X), (int)(clip.W - clip.Y));
CheckGLError("Scissor");
if ((io.BackendFlags & ImGuiBackendFlags.RendererHasVtxOffset) != 0)
{
GL.DrawElementsBaseVertex(PrimitiveType.Triangles, (int)pcmd.ElemCount, DrawElementsType.UnsignedShort, (IntPtr)(pcmd.IdxOffset * sizeof(ushort)), unchecked((int)pcmd.VtxOffset));
}
else
{
GL.DrawElements(BeginMode.Triangles, (int)pcmd.ElemCount, DrawElementsType.UnsignedShort, (int)pcmd.IdxOffset * sizeof(ushort));
}
CheckGLError("Draw");
}
}
}
GL.Disable(EnableCap.Blend);
GL.Disable(EnableCap.ScissorTest);
// Reset state
GL.BindTexture(TextureTarget.Texture2D, prevTexture2D);
GL.ActiveTexture((TextureUnit)prevActiveTexture);
GL.UseProgram(prevProgram);
GL.BindVertexArray(prevVAO);
GL.Scissor(prevScissorBox[0], prevScissorBox[1], prevScissorBox[2], prevScissorBox[3]);
GL.BindBuffer(BufferTarget.ArrayBuffer, prevArrayBuffer);
GL.BlendEquationSeparate((BlendEquationMode)prevBlendEquationRgb, (BlendEquationMode)prevBlendEquationAlpha);
GL.BlendFuncSeparate(
(BlendingFactorSrc)prevBlendFuncSrcRgb,
(BlendingFactorDest)prevBlendFuncDstRgb,
(BlendingFactorSrc)prevBlendFuncSrcAlpha,
(BlendingFactorDest)prevBlendFuncDstAlpha);
if (prevBlendEnabled) GL.Enable(EnableCap.Blend); else GL.Disable(EnableCap.Blend);
if (prevDepthTestEnabled) GL.Enable(EnableCap.DepthTest); else GL.Disable(EnableCap.DepthTest);
if (prevCullFaceEnabled) GL.Enable(EnableCap.CullFace); else GL.Disable(EnableCap.CullFace);
if (prevScissorTestEnabled) GL.Enable(EnableCap.ScissorTest); else GL.Disable(EnableCap.ScissorTest);
}
/// <summary>
/// Frees all graphics resources used by the renderer.
/// </summary>
public void Dispose()
{
GL.DeleteVertexArray(_vertexArray);
GL.DeleteBuffer(_vertexBuffer);
GL.DeleteBuffer(_indexBuffer);
GL.DeleteTexture(_fontTexture);
GL.DeleteProgram(_shader);
}
public static void LabelObject(ObjectLabelIdentifier objLabelIdent, int glObject, string name)
{
if (KHRDebugAvailable)
GL.ObjectLabel(objLabelIdent, glObject, name.Length, name);
}
static bool IsExtensionSupported(string name)
{
int n = GL.GetInteger(GetPName.NumExtensions);
for (int i = 0; i < n; i++)
{
string extension = GL.GetString(StringNameIndexed.Extensions, i);
if (extension == name) return true;
}
return false;
}
public static int CreateProgram(string name, string vertexSource, string fragmentSoruce)
{
int program = GL.CreateProgram();
LabelObject(ObjectLabelIdentifier.Program, program, $"Program: {name}");
int vertex = CompileShader(name, ShaderType.VertexShader, vertexSource);
int fragment = CompileShader(name, ShaderType.FragmentShader, fragmentSoruce);
GL.AttachShader(program, vertex);
GL.AttachShader(program, fragment);
GL.LinkProgram(program);
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out int success);
if (success == 0)
{
string info = GL.GetProgramInfoLog(program);
Debug.WriteLine($"GL.LinkProgram had info log [{name}]:\n{info}");
}
GL.DetachShader(program, vertex);
GL.DetachShader(program, fragment);
GL.DeleteShader(vertex);
GL.DeleteShader(fragment);
return program;
}
private static int CompileShader(string name, ShaderType type, string source)
{
int shader = GL.CreateShader(type);
LabelObject(ObjectLabelIdentifier.Shader, shader, $"Shader: {name}");
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out int success);
if (success == 0)
{
string info = GL.GetShaderInfoLog(shader);
Debug.WriteLine($"GL.CompileShader for shader '{name}' [{type}] had info log:\n{info}");
}
return shader;
}
public static void CheckGLError(string title)
{
ErrorCode error;
int i = 1;
while ((error = GL.GetError()) != ErrorCode.NoError)
{
Debug.Print($"{title} ({i++}): {error}");
}
}
public static float GetDpiScale()
{
return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight));
}
public static ImGuiKey TranslateKey(Keys key)
{
if (key is >= Keys.D0 and <= Keys.D9)
return key - Keys.D0 + ImGuiKey._0;
if (key is >= Keys.A and <= Keys.Z)
return key - Keys.A + ImGuiKey.A;
if (key is >= Keys.KeyPad0 and <= Keys.KeyPad9)
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
if (key is >= Keys.F1 and <= Keys.F24)
return key - Keys.F1 + ImGuiKey.F24;
return key switch
{
Keys.Tab => ImGuiKey.Tab,
Keys.Left => ImGuiKey.LeftArrow,
Keys.Right => ImGuiKey.RightArrow,
Keys.Up => ImGuiKey.UpArrow,
Keys.Down => ImGuiKey.DownArrow,
Keys.PageUp => ImGuiKey.PageUp,
Keys.PageDown => ImGuiKey.PageDown,
Keys.Home => ImGuiKey.Home,
Keys.End => ImGuiKey.End,
Keys.Insert => ImGuiKey.Insert,
Keys.Delete => ImGuiKey.Delete,
Keys.Backspace => ImGuiKey.Backspace,
Keys.Space => ImGuiKey.Space,
Keys.Enter => ImGuiKey.Enter,
Keys.Escape => ImGuiKey.Escape,
Keys.Apostrophe => ImGuiKey.Apostrophe,
Keys.Comma => ImGuiKey.Comma,
Keys.Minus => ImGuiKey.Minus,
Keys.Period => ImGuiKey.Period,
Keys.Slash => ImGuiKey.Slash,
Keys.Semicolon => ImGuiKey.Semicolon,
Keys.Equal => ImGuiKey.Equal,
Keys.LeftBracket => ImGuiKey.LeftBracket,
Keys.Backslash => ImGuiKey.Backslash,
Keys.RightBracket => ImGuiKey.RightBracket,
Keys.GraveAccent => ImGuiKey.GraveAccent,
Keys.CapsLock => ImGuiKey.CapsLock,
Keys.ScrollLock => ImGuiKey.ScrollLock,
Keys.NumLock => ImGuiKey.NumLock,
Keys.PrintScreen => ImGuiKey.PrintScreen,
Keys.Pause => ImGuiKey.Pause,
Keys.KeyPadDecimal => ImGuiKey.KeypadDecimal,
Keys.KeyPadDivide => ImGuiKey.KeypadDivide,
Keys.KeyPadMultiply => ImGuiKey.KeypadMultiply,
Keys.KeyPadSubtract => ImGuiKey.KeypadSubtract,
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
Keys.LeftShift => ImGuiKey.LeftShift,
Keys.LeftControl => ImGuiKey.LeftCtrl,
Keys.LeftAlt => ImGuiKey.LeftAlt,
Keys.LeftSuper => ImGuiKey.LeftSuper,
Keys.RightShift => ImGuiKey.RightShift,
Keys.RightControl => ImGuiKey.RightCtrl,
Keys.RightAlt => ImGuiKey.RightAlt,
Keys.RightSuper => ImGuiKey.RightSuper,
Keys.Menu => ImGuiKey.Menu,
_ => ImGuiKey.None
};
}
}

View File

@ -0,0 +1,30 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;
using RestSharp.Serializers;
namespace FModel.Framework;
public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer
{
public static readonly JsonSerializerSettings SerializerSettings = new()
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public string Serialize(Parameter parameter) => JsonConvert.SerializeObject(parameter.Value);
public string Serialize(object obj) => JsonConvert.SerializeObject(obj);
public T Deserialize<T>(RestResponse response) => JsonConvert.DeserializeObject<T>(response.Content!, SerializerSettings);
public ISerializer Serializer => this;
public IDeserializer Deserializer => this;
public ContentType ContentType { get; set; } = ContentType.Json;
public string[] AcceptedContentTypes => ContentType.JsonAccept;
public SupportsContentType SupportsContentType => contentType => contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
public DataFormat DataFormat => DataFormat.Json;
}

View File

@ -0,0 +1,46 @@
using System.Collections.Generic;
namespace FModel.Framework;
public class NavigationList<T> : List<T>
{
private int _currentIndex;
public int CurrentIndex
{
get
{
if (_currentIndex > Count - 1)
{
_currentIndex = Count - 1;
}
if (_currentIndex < 0)
{
_currentIndex = 0;
}
return _currentIndex;
}
set => _currentIndex = value;
}
public T MoveNext
{
get
{
_currentIndex++;
return this[CurrentIndex];
}
}
public T MovePrevious
{
get
{
_currentIndex--;
return this[CurrentIndex];
}
}
public T Current => this[CurrentIndex];
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace FModel.Framework;
public sealed class RangeObservableCollection<T> : ObservableCollection<T>
{
private bool _suppressNotification;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
_suppressNotification = true;
foreach (var item in list)
Add(item);
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void SetSuppressionState(bool state)
{
_suppressNotification = state;
}
public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace FModel.Framework;
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
{
private readonly Dictionary<string, IList<string>> _validationErrors = new();
public string this[string propertyName]
{
get
{
if (string.IsNullOrEmpty(propertyName))
return Error;
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
}
}
[JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors());
[JsonIgnore] public bool HasErrors => _validationErrors.Any();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return _validationErrors.SelectMany(kvp => kvp.Value);
return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<object>();
}
private IEnumerable<string> GetAllErrors()
{
return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e));
}
public void AddValidationError(string propertyName, string errorMessage)
{
if (!_validationErrors.ContainsKey(propertyName))
_validationErrors.Add(propertyName, new List<string>());
_validationErrors[propertyName].Add(errorMessage);
}
public void ClearValidationErrors(string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning disable 67
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
#pragma warning disable 67
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
}

View File

@ -0,0 +1,49 @@
using System;
namespace FModel.Framework;
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
{
private WeakReference _parent;
public TContextViewModel ContextViewModel
{
get
{
if (_parent is { IsAlive: true })
return (TContextViewModel) _parent.Target;
return null;
}
private set
{
if (ContextViewModel == value)
return;
_parent = value != null ? new WeakReference(value) : null;
}
}
protected ViewModelCommand(TContextViewModel contextViewModel)
{
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
}
public sealed override void Execute(object parameter)
{
Execute(ContextViewModel, parameter);
}
public abstract void Execute(TContextViewModel contextViewModel, object parameter);
public sealed override bool CanExecute(object parameter)
{
return CanExecute(ContextViewModel, parameter);
}
public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter)
{
return true;
}
}

114
FModel/Helper.cs Normal file
View File

@ -0,0 +1,114 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
namespace FModel;
public static class Helper
{
public static string FixKey(string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
var keySpan = key.AsSpan().Trim();
if (keySpan.Length > sizeof(char) * (2 /* 0x */ + 32 /* FAES = 256 bit */)) // maybe strictly check for length?
return string.Empty; // bullshit key
Span<char> resultSpan = stackalloc char[keySpan.Length + 2 /* pad for 0x */];
keySpan.ToUpperInvariant(resultSpan[2..]);
if (resultSpan[2..].StartsWith("0X"))
resultSpan = resultSpan[2..];
else
resultSpan[0] = '0';
resultSpan[1] = 'x';
return new string(resultSpan);
}
public static void OpenWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
else
{
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search View") w.WindowState = WindowState.Normal;
w.Focus();
}
}
public static T GetWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
var ret = (T) GetOpenedWindow<T>(windowName);
ret.Focus();
ret.Activate();
return ret;
}
public static void CloseWindow<T>(string windowName) where T : Window
{
if (!IsWindowOpen<T>(windowName)) return;
GetOpenedWindow<T>(windowName).Close();
}
private static bool IsWindowOpen<T>(string name = "") where T : Window
{
return string.IsNullOrEmpty(name)
? Application.Current.Windows.OfType<T>().Any()
: Application.Current.Windows.OfType<T>().Any(w => w.Title.Equals(name));
}
private static Window GetOpenedWindow<T>(string name) where T : Window
{
return Application.Current.Windows.OfType<T>().FirstOrDefault(w => w.Title.Equals(name));
}
public static bool IsNaN(double value)
{
var ulongValue = Unsafe.As<double, ulong>(ref value);
var exp = ulongValue & 0xfff0000000000000;
var man = ulongValue & 0x000fffffffffffff;
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
}
public static bool AreVirtuallyEqual(double d1, double d2)
{
if (double.IsPositiveInfinity(d1))
return double.IsPositiveInfinity(d2);
if (double.IsNegativeInfinity(d1))
return double.IsNegativeInfinity(d2);
if (IsNaN(d1))
return IsNaN(d2);
var n = d1 - d2;
var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15;
return -d < n && d > n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{
const float ratio = MathF.PI / 180f;
return ratio * degrees;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadiansToDegrees(float radians)
{
const float ratio = 180f / MathF.PI;
return radians * ratio;
}
}

815
FModel/MainWindow.xaml Normal file
View File

@ -0,0 +1,815 @@
<adonisControls:AdonisWindow x:Class="FModel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FModel"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:services="clr-namespace:FModel.Services"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
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.85'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.TitleExtra, RelativeSource={RelativeSource Self}, Converter={x:Static converters:IsNullToBoolReversedConverter.Instance}}" Value="True">
<Setter Property="Title">
<Setter.Value>
<MultiBinding StringFormat="{}{0} - {1} {2}">
<Binding Path="DataContext.InitialWindowTitle" RelativeSource="{RelativeSource Self}" />
<Binding Path="DataContext.GameDisplayName" RelativeSource="{RelativeSource Self}" />
<Binding Path="DataContext.TitleExtra" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Views/Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="{adonisUi:Space 1}" />
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="Directory">
<MenuItem Header="Selector" Command="{Binding MenuCommand}" CommandParameter="Directory_Selector">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoryIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource KeyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackupIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Packages">
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Auto Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" />
</MenuItem>
<MenuItem Header="Views">
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource ImageMergerIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Settings" Command="{Binding MenuCommand}" CommandParameter="Settings" />
<MenuItem Header="Help" >
<MenuItem Header="Donate" Command="{Binding MenuCommand}" CommandParameter="Help_Donate">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GiftIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Bugs Report" Command="{Binding MenuCommand}" CommandParameter="Help_BugsReport">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource BugIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Discord Server" Command="{Binding MenuCommand}" CommandParameter="Help_Discord">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource DiscordIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="About FModel" Command="{Binding MenuCommand}" CommandParameter="Help_About">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<Grid x:Name="RootGrid" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="400" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" adonisExtensions:LayerExtension.Layer="2"
Padding="{adonisUi:Space 0}" Background="Transparent">
<TabControl x:Name="LeftTabControl" SelectionChanged="OnTabItemChange">
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Archives">
<DockPanel>
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Loading Mode" VerticalAlignment="Center" />
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding LoadingModes.Modes}" IsEnabled="{Binding Status.IsReady}"
SelectedItem="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Content="Load"
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding Status.IsReady}"
CommandParameter="{Binding SelectedItems, ElementName=DirectoryFilesListBox}" />
</Grid>
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Separator Grid.Row="0" Style="{StaticResource CustomSeparator}" Tag="GAME ARCHIVES" />
<ListBox Grid.Row="1" x:Name="DirectoryFilesListBox" Style="{StaticResource DirectoryFilesListBox}" MouseDoubleClick="OnMouseDoubleClick" />
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=DirectoryFilesListBox, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FileCount, ElementName=DirectoryFilesListBox, FallbackValue='0', StringFormat={}{0} Files}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="File Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=DirectoryFilesListBox, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.Guid, ElementName=DirectoryFilesListBox, FallbackValue='00000000000000000000000000000000'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Global Unique Identifier" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</Grid>
</DockPanel>
</TabItem>
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Folders">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Image Source="/FModel;component/Resources/label.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3.5 0" />
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" TextTrimming="CharacterEllipsis">
<TextBlock.Text>
<MultiBinding StringFormat="{}'{0}' has {1} folders and {2} packages">
<Binding Path="SelectedItem.Header" ElementName="AssetsFolderName" FallbackValue="None" />
<Binding Path="SelectedItem.FoldersView.Count" ElementName="AssetsFolderName" FallbackValue="0" />
<Binding Path="SelectedItem.AssetsList.Assets.Count" ElementName="AssetsFolderName" FallbackValue="0" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Bring Selected Folder To View" Padding="4"
Command="{Binding MenuCommand}" CommandParameter="{Binding SelectedItem, ElementName=AssetsFolderName}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource LocateMeIcon}" />
</Canvas>
</Viewbox>
</Button>
<!-- <Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Expand All (not appropriate for huge amount of folders)" Padding="4" -->
<!-- Command="{Binding MenuCommand}" CommandParameter="ToolBox_Expand_All"> -->
<!-- <Viewbox Width="16" Height="16" HorizontalAlignment="Center"> -->
<!-- <Canvas Width="24" Height="24"> -->
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource UnfoldIcon}" /> -->
<!-- </Canvas> -->
<!-- </Viewbox> -->
<!-- </Button> -->
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Collapse All" Padding="4"
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Collapse_All">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FoldIcon}"/>
</Canvas>
</Viewbox>
</Button>
</StackPanel>
</Grid>
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0" />
<TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
<TreeView.ContextMenu>
<ContextMenu>
<!-- <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick"> -->
<!-- <MenuItem.Icon> -->
<!-- <Viewbox Width="16" Height="16"> -->
<!-- <Canvas Width="24" Height="24"> -->
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" /> -->
<!-- </Canvas> -->
<!-- </Viewbox> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
<!-- <Separator /> -->
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Properties (.json)" Click="OnFolderSaveClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesAddIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy Directory Path" Click="OnCopyDirectoryPathClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
<Separator Grid.Row="3" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="4" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Packages Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Folders Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsFolderName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=AssetsFolderName, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Archive Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Version, ElementName=AssetsFolderName, FallbackValue='VER_UE4_LATEST', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Archive Version" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</Grid>
</TabItem>
<TabItem Style="{StaticResource TabItemFillSpace}"
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
HeaderStringFormat="{}{0} Packages">
<DockPanel>
<Grid DockPanel.Dock="Top" ZIndex="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SearchIcon}" />
</Canvas>
</Viewbox>
</Grid>
<TextBox Grid.Column="0" Grid.ColumnSpan="2" x:Name="AssetsSearchName" AcceptsTab="False" AcceptsReturn="False"
Padding="25 0 0 0" HorizontalAlignment="Stretch" TextChanged="OnFilterTextChanged"
adonisExtensions:WatermarkExtension.Watermark="Search by name..." />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button ToolTip="Clear Search Filter" Padding="5" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" Click="OnDeleteSearchClick">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource BackspaceIcon}"/>
</Canvas>
</Viewbox>
</Button>
</StackPanel>
</Grid>
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Extract_New_Tab" />
<Binding Path="SelectedItems" />
</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 ExtractIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Export_Data" />
<Binding Path="SelectedItems" />
</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 ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Properties" />
<Binding Path="SelectedItems" />
</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 SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Textures" />
<Binding Path="SelectedItems" />
</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 TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Models" />
<Binding Path="SelectedItems" />
</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 ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Animations" />
<Binding Path="SelectedItems" />
</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 AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Copy">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Package Path" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Path" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Name" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Name" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Directory Path" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Directory_Path" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Path w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Path_No_Extension" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Name w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Name_No_Extension" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Compression, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</Grid>
</DockPanel>
</TabItem>
</TabControl>
</GroupBox>
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Width="4" VerticalAlignment="Stretch"
ResizeBehavior="PreviousAndNext" MouseDoubleClick="OnGridSplitterDoubleClick"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}" />
<GroupBox Grid.Column="2" adonisExtensions:LayerExtension.Layer="2"
Padding="{adonisUi:Space 0}" Background="Transparent">
<Grid Margin="0 0 3 0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static settings:UserSettings.Default}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<controls:CustomRichTextBox Grid.Column="0" Grid.ColumnSpan="3" x:Name="LogRtbName" Style="{StaticResource CustomRichTextBox}" />
<StackPanel Grid.Column="2" Orientation="Vertical" VerticalAlignment="Bottom" Margin="-5 -5 5 5">
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Open Output Folder" Padding="0,4,0,4"
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FolderIcon}" />
</Canvas>
</Viewbox>
</Button>
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Clear Logs" Padding="0,4,0,4"
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Clear_Logs">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TrashIcon}"/>
</Canvas>
</Viewbox>
</Button>
</StackPanel>
</Grid>
</Expander>
</Grid>
</GroupBox>
</Grid>
<StatusBar Grid.Row="3" MinHeight="28" MaxHeight="28"
adonisExtensions:LayerExtension.Layer="3">
<StatusBar.Style>
<Style TargetType="{x:Type StatusBar}" BasedOn="{StaticResource {x:Type StatusBar}}">
<Style.Triggers>
<!--don't mind me, MultiDataTrigger just sucks-->
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Ready}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Completed}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}">
<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.Stopping}">
<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" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Failed}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusChangeAttempted, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Duration="0:0:0.8">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(StatusBar.Background).(SolidColorBrush.Color)" FillBehavior="Stop">
<ColorAnimationUsingKeyFrames.KeyFrames>
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#CE5555" />
<DiscreteColorKeyFrame KeyTime="0:0:0.2" Value="#C22B2B" />
<DiscreteColorKeyFrame KeyTime="0:0:0.4" Value="#CE5555" />
<DiscreteColorKeyFrame KeyTime="0:0:0.6" Value="#C22B2B" />
<DiscreteColorKeyFrame KeyTime="0:0:0.8" Value="#CE5555" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding OperationCancelled, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Duration="0:0:1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(StatusBar.Background).(SolidColorBrush.Color)" FillBehavior="Stop">
<ColorAnimationUsingKeyFrames.KeyFrames>
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="#C22B2B" />
<DiscreteColorKeyFrame KeyTime="0:0:1" Value="#CE5555" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBar.Style>
<StatusBarItem Margin="5,0,0,0" >
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Status.Label}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} …'}" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CanBeCanceled, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True" />
<Condition Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}" />
</MultiDataTrigger.Conditions>
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} … ESC to Cancel'}" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StatusBarItem>
<StatusBarItem Margin="0 0 5 0" HorizontalAlignment="Right">
<StackPanel Orientation="Horizontal">
<StatusBarItem Margin="0 0 10 0" HorizontalContentAlignment="Stretch">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource StatusBarIcon}" />
</Canvas>
</Viewbox>
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Open Sounds Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
</StatusBarItem>
<StatusBarItem Margin="10 0 0 0">
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
</StatusBarItem>
</StackPanel>
</StatusBarItem>
</StatusBar>
</Grid>
</adonisControls:AdonisWindow>

282
FModel/MainWindow.xaml.cs Normal file
View File

@ -0,0 +1,282 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using AdonisUI.Controls;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
using FModel.Views;
using FModel.Views.Resources.Controls;
using ICSharpCode.AvalonEdit.Editing;
namespace FModel;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public static MainWindow YesWeCats;
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
public MainWindow()
{
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder()));
DataContext = _applicationView;
InitializeComponent();
FLogger.Logger = LogRtbName;
YesWeCats = this;
}
private void OnClosing(object sender, CancelEventArgs e)
{
_discordHandler.Dispose();
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
var newOrUpdated = UserSettings.Default.ShowChangelog;
#if !DEBUG
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(true);
#endif
switch (UserSettings.Default.AesReload)
{
case EAesReload.Always:
await _applicationView.CUE4Parse.RefreshAes();
break;
case EAesReload.OncePerDay when UserSettings.Default.CurrentDir.LastAesReload != DateTime.Today:
UserSettings.Default.CurrentDir.LastAesReload = DateTime.Today;
await _applicationView.CUE4Parse.RefreshAes();
break;
}
await ApplicationViewModel.InitOodle();
await ApplicationViewModel.InitZlib();
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
#if !DEBUG
await _applicationView.CUE4Parse.InitInformation();
#endif
await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
Task.Run(() =>
{
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.GameDisplayName);
})
).ConfigureAwait(false);
#if DEBUG
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Athena/Apollo/Maps/UI/Apollo_Terrain_Minimap.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
#endif
}
private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e)
{
RootGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
private void OnWindowKeyDown(object sender, KeyEventArgs e)
{
if (e.OriginalSource is TextBox || e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
return;
if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape)
{
_applicationView.Status.SetStatus(EStatusKind.Stopping);
_threadWorkerView.Cancel();
}
else if (_applicationView.Status.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
OnSearchViewClick(null, null);
else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
_applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage();
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.AddTab();
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.RemoveTab();
else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoLeftTab();
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoRightTab();
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
LeftTabControl.SelectedIndex--;
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
LeftTabControl.SelectedIndex++;
}
private void OnSearchViewClick(object sender, RoutedEventArgs e)
{
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
}
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource is not TabControl tabControl)
return;
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
}
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
{
await _applicationView.CUE4Parse.InitMappings(true);
}
private void OnOpenAvalonFinder()
{
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
AvalonEditor.YesWeSearch.Focus();
AvalonEditor.YesWeSearch.SelectAll();
}
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
LeftTabControl.SelectedIndex++;
}
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not ListBox listBox) return;
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
}
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
}
}
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
});
}
}
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
});
}
}
private async void OnFolderTextureClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.TextureFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
});
}
}
private async void OnFolderModelClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ModelFolder(cancellationToken, folder); });
}
}
private async void OnFolderAnimationClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AnimationFolder(cancellationToken, folder); });
}
}
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
FLogger.Append(ELog.Information, () =>
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
}
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
Clipboard.SetText(folder.PathAtThisPoint);
}
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
{
AssetsSearchName.Text = string.Empty;
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
}
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
return;
var filters = textBox.Text.Trim().Split(' ');
folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase)); };
}
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
UserSettings.Default.LoadingMode = ELoadingMode.Multiple;
_applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems);
}
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
switch (e.Key)
{
case Key.Enter:
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
break;
}
}
}

View File

@ -1,308 +0,0 @@
namespace FModel
{
partial class PAKWindow
{
/// <summary>
/// Variable nécessaire au concepteur.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Nettoyage des ressources utilisées.
/// </summary>
/// <param name="disposing">true si les ressources managées doivent être supprimées ; sinon, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Code généré par le Concepteur Windows Form
/// <summary>
/// Méthode requise pour la prise en charge du concepteur - ne modifiez pas
/// le contenu de cette méthode avec l'éditeur de code.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PAKWindow));
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.PAKsLoad = new System.Windows.Forms.Button();
this.AESKeyLabel = new System.Windows.Forms.Label();
this.AESKeyTextBox = new System.Windows.Forms.TextBox();
this.PAKsComboBox = new System.Windows.Forms.ComboBox();
this.ItemsListBox = new System.Windows.Forms.ListBox();
this.PAKTreeView = new System.Windows.Forms.TreeView();
this.ItemIconPictureBox = new System.Windows.Forms.PictureBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.SaveImageButton = new FModel.SplitButton();
this.ImageContext = new System.Windows.Forms.ContextMenuStrip(this.components);
this.OpenImageTS = new System.Windows.Forms.ToolStripMenuItem();
this.ExtractAssetButton = new FModel.SplitButton();
this.ExtractAsset = new System.Windows.Forms.ContextMenuStrip(this.components);
this.LoadDataTS = new System.Windows.Forms.ToolStripMenuItem();
this.SaveImageTS = new System.Windows.Forms.ToolStripMenuItem();
this.ConsoleRichTextBox = new System.Windows.Forms.RichTextBox();
this.ItemRichTextBox = new System.Windows.Forms.RichTextBox();
this.FilterLabel = new System.Windows.Forms.Label();
this.FilterTextBox = new System.Windows.Forms.TextBox();
this.groupBox1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.ItemIconPictureBox)).BeginInit();
this.groupBox2.SuspendLayout();
this.ImageContext.SuspendLayout();
this.ExtractAsset.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Controls.Add(this.PAKsLoad);
this.groupBox1.Controls.Add(this.AESKeyLabel);
this.groupBox1.Controls.Add(this.AESKeyTextBox);
this.groupBox1.Controls.Add(this.PAKsComboBox);
this.groupBox1.Location = new System.Drawing.Point(12, 12);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(408, 75);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "PAK";
//
// PAKsLoad
//
this.PAKsLoad.Location = new System.Drawing.Point(296, 18);
this.PAKsLoad.Name = "PAKsLoad";
this.PAKsLoad.Size = new System.Drawing.Size(106, 23);
this.PAKsLoad.TabIndex = 3;
this.PAKsLoad.Text = "Load";
this.PAKsLoad.UseVisualStyleBackColor = true;
this.PAKsLoad.Click += new System.EventHandler(this.PAKsLoad_Click);
//
// AESKeyLabel
//
this.AESKeyLabel.AutoSize = true;
this.AESKeyLabel.Location = new System.Drawing.Point(6, 51);
this.AESKeyLabel.Name = "AESKeyLabel";
this.AESKeyLabel.Size = new System.Drawing.Size(52, 13);
this.AESKeyLabel.TabIndex = 2;
this.AESKeyLabel.Text = "AES Key:";
//
// AESKeyTextBox
//
this.AESKeyTextBox.Location = new System.Drawing.Point(64, 47);
this.AESKeyTextBox.Name = "AESKeyTextBox";
this.AESKeyTextBox.Size = new System.Drawing.Size(338, 20);
this.AESKeyTextBox.TabIndex = 1;
this.AESKeyTextBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// PAKsComboBox
//
this.PAKsComboBox.FormattingEnabled = true;
this.PAKsComboBox.Location = new System.Drawing.Point(6, 19);
this.PAKsComboBox.Name = "PAKsComboBox";
this.PAKsComboBox.Size = new System.Drawing.Size(284, 21);
this.PAKsComboBox.TabIndex = 0;
//
// ItemsListBox
//
this.ItemsListBox.FormattingEnabled = true;
this.ItemsListBox.Location = new System.Drawing.Point(12, 389);
this.ItemsListBox.Name = "ItemsListBox";
this.ItemsListBox.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended;
this.ItemsListBox.Size = new System.Drawing.Size(408, 290);
this.ItemsListBox.Sorted = true;
this.ItemsListBox.TabIndex = 1;
this.ItemsListBox.SelectedIndexChanged += new System.EventHandler(this.ItemsListBox_SelectedIndexChanged);
//
// PAKTreeView
//
this.PAKTreeView.Location = new System.Drawing.Point(12, 93);
this.PAKTreeView.Name = "PAKTreeView";
this.PAKTreeView.Size = new System.Drawing.Size(408, 290);
this.PAKTreeView.TabIndex = 2;
this.PAKTreeView.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.PAKTreeView_NodeMouseClick);
//
// ItemIconPictureBox
//
this.ItemIconPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.ItemIconPictureBox.Location = new System.Drawing.Point(572, 18);
this.ItemIconPictureBox.Name = "ItemIconPictureBox";
this.ItemIconPictureBox.Size = new System.Drawing.Size(350, 350);
this.ItemIconPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.ItemIconPictureBox.TabIndex = 3;
this.ItemIconPictureBox.TabStop = false;
//
// groupBox2
//
this.groupBox2.Controls.Add(this.SaveImageButton);
this.groupBox2.Controls.Add(this.ExtractAssetButton);
this.groupBox2.Controls.Add(this.ConsoleRichTextBox);
this.groupBox2.Controls.Add(this.ItemRichTextBox);
this.groupBox2.Controls.Add(this.ItemIconPictureBox);
this.groupBox2.Location = new System.Drawing.Point(426, 12);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(928, 693);
this.groupBox2.TabIndex = 4;
this.groupBox2.TabStop = false;
//
// SaveImageButton
//
this.SaveImageButton.Location = new System.Drawing.Point(810, 635);
this.SaveImageButton.Menu = this.ImageContext;
this.SaveImageButton.Name = "SaveImageButton";
this.SaveImageButton.Size = new System.Drawing.Size(112, 23);
this.SaveImageButton.TabIndex = 11;
this.SaveImageButton.Text = " Save Image";
this.SaveImageButton.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.SaveImageButton.UseVisualStyleBackColor = true;
this.SaveImageButton.Click += new System.EventHandler(this.SaveImageButton_Click);
//
// ImageContext
//
this.ImageContext.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.OpenImageTS});
this.ImageContext.Name = "ImageContext";
this.ImageContext.Size = new System.Drawing.Size(140, 26);
//
// OpenImageTS
//
this.OpenImageTS.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.OpenImageTS.Name = "OpenImageTS";
this.OpenImageTS.Size = new System.Drawing.Size(139, 22);
this.OpenImageTS.Text = "Open Image";
this.OpenImageTS.Click += new System.EventHandler(this.OpenImageTS_Click);
//
// ExtractAssetButton
//
this.ExtractAssetButton.Location = new System.Drawing.Point(810, 664);
this.ExtractAssetButton.Menu = this.ExtractAsset;
this.ExtractAssetButton.Name = "ExtractAssetButton";
this.ExtractAssetButton.Size = new System.Drawing.Size(112, 23);
this.ExtractAssetButton.TabIndex = 10;
this.ExtractAssetButton.Text = " Extract Asset";
this.ExtractAssetButton.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.ExtractAssetButton.UseVisualStyleBackColor = true;
this.ExtractAssetButton.Click += new System.EventHandler(this.ExtractAssetButton_Click);
//
// ExtractAsset
//
this.ExtractAsset.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.LoadDataTS,
this.SaveImageTS});
this.ExtractAsset.Name = "ExtractAsset";
this.ExtractAsset.Size = new System.Drawing.Size(223, 48);
//
// LoadDataTS
//
this.LoadDataTS.Checked = true;
this.LoadDataTS.CheckOnClick = true;
this.LoadDataTS.CheckState = System.Windows.Forms.CheckState.Checked;
this.LoadDataTS.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.LoadDataTS.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.LoadDataTS.Name = "LoadDataTS";
this.LoadDataTS.Size = new System.Drawing.Size(222, 22);
this.LoadDataTS.Text = "Load Data After Serialization";
//
// SaveImageTS
//
this.SaveImageTS.CheckOnClick = true;
this.SaveImageTS.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.SaveImageTS.Name = "SaveImageTS";
this.SaveImageTS.Size = new System.Drawing.Size(222, 22);
this.SaveImageTS.Text = "Save Generated Image";
//
// ConsoleRichTextBox
//
this.ConsoleRichTextBox.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.ConsoleRichTextBox.Location = new System.Drawing.Point(6, 374);
this.ConsoleRichTextBox.Name = "ConsoleRichTextBox";
this.ConsoleRichTextBox.ReadOnly = true;
this.ConsoleRichTextBox.Size = new System.Drawing.Size(922, 229);
this.ConsoleRichTextBox.TabIndex = 6;
this.ConsoleRichTextBox.Text = "";
//
// ItemRichTextBox
//
this.ItemRichTextBox.BackColor = System.Drawing.SystemColors.Window;
this.ItemRichTextBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.ItemRichTextBox.Location = new System.Drawing.Point(6, 18);
this.ItemRichTextBox.Name = "ItemRichTextBox";
this.ItemRichTextBox.ReadOnly = true;
this.ItemRichTextBox.Size = new System.Drawing.Size(560, 350);
this.ItemRichTextBox.TabIndex = 4;
this.ItemRichTextBox.Text = "";
//
// FilterLabel
//
this.FilterLabel.AutoSize = true;
this.FilterLabel.Location = new System.Drawing.Point(18, 688);
this.FilterLabel.Name = "FilterLabel";
this.FilterLabel.Size = new System.Drawing.Size(32, 13);
this.FilterLabel.TabIndex = 6;
this.FilterLabel.Text = "Filter:";
//
// FilterTextBox
//
this.FilterTextBox.Location = new System.Drawing.Point(56, 685);
this.FilterTextBox.Name = "FilterTextBox";
this.FilterTextBox.Size = new System.Drawing.Size(364, 20);
this.FilterTextBox.TabIndex = 5;
this.FilterTextBox.TextChanged += new System.EventHandler(this.FilterTextBox_TextChanged);
//
// PAKWindow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1366, 712);
this.Controls.Add(this.FilterLabel);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.PAKTreeView);
this.Controls.Add(this.FilterTextBox);
this.Controls.Add(this.ItemsListBox);
this.Controls.Add(this.groupBox1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.Name = "PAKWindow";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "FModel";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.PAKWindow_FormClosing);
this.Load += new System.EventHandler(this.PAKWindow_Load);
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.ItemIconPictureBox)).EndInit();
this.groupBox2.ResumeLayout(false);
this.ImageContext.ResumeLayout(false);
this.ExtractAsset.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Button PAKsLoad;
private System.Windows.Forms.Label AESKeyLabel;
private System.Windows.Forms.TextBox AESKeyTextBox;
private System.Windows.Forms.ComboBox PAKsComboBox;
private System.Windows.Forms.ListBox ItemsListBox;
private System.Windows.Forms.TreeView PAKTreeView;
private System.Windows.Forms.PictureBox ItemIconPictureBox;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.RichTextBox ItemRichTextBox;
private System.Windows.Forms.RichTextBox ConsoleRichTextBox;
private System.Windows.Forms.Label FilterLabel;
private System.Windows.Forms.TextBox FilterTextBox;
private SplitButton ExtractAssetButton;
private System.Windows.Forms.ContextMenuStrip ExtractAsset;
private System.Windows.Forms.ToolStripMenuItem LoadDataTS;
private System.Windows.Forms.ContextMenuStrip ImageContext;
private System.Windows.Forms.ToolStripMenuItem OpenImageTS;
private SplitButton SaveImageButton;
private System.Windows.Forms.ToolStripMenuItem SaveImageTS;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,887 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="PAKsComboBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ImageContext.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>134, 17</value>
</metadata>
<metadata name="ExtractAsset.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
Jv81Jif/NCcn/zMoKP8yKCj/MSgo/jAoKFwwKCgAMikpADEoKF0zKCj+NCgo/zQnJ/81Jib/NiYm/zYl
Jv89LC3/yMTE////////////4N3d/009Pv82JCX/NyYm/zclJv82JSb/NSYm/zQnJ/80KCj/Migo/jEo
KFwyKCgAMigoADEnJ10yKCj+NCgo/zUmJ/81JSb/NiUm/zcmJv8+LS3/yMPD////////////5ePj/08/
QP82JCT/NyUm/zgmJ/83Jif/NiUm/zUmJ/8zJyj/Migo/jIoKFwyKCgAMigoADInJ10zKCj+NSgo/zYm
J/83Jib/NyYm/zglJv8/LS3/yMPD////////////5OLi/1A+P/84JCX/OSYm/zgmJv84Jif/NyYm/zYm
J/80Jyj/Mygo/jMoKFwzKCgAMicnADInKF0zKCj+NScn/zYmJv83JSb/NyUm/zglJv8/LC3/yMPD////
////////5OHi/1E+Pv85JCX/OSUm/zkmJ/83JSb/NiUm/zYmJv81Jyf/Mygo/jIoKFwyKCgAMygoADMo
KF00KCj+NSYn/zYlJv83JSb/OCUm/zsnJ/9ALC3/yMPD////////////4+Hh/1A9Pv85JCT/OiUm/zwn
KP84Jif/NiUm/zYmJv81Jif/Mycn/jInJ1wyJycAMycnADInJ100Jyj+NSYn/zcmJv83Jib/OCUm/zom
J/9ALC3/ycPD////////////4+Hh/1NAQP89Jyj/Oycn/zsmJ/84Jib/NyUm/zYmJv81Jif/Mycn/jMo
KFwzKCgAMicnADInJ100KCj+NSYn/zYmJv83Jib/OCUm/zklJv9ALC3/ycPE////////////9vX1/8bA
wP++t7f/bF1e/zcjJP84Jib/OCYn/zYlJv82Jif/NCcn/jMoKFwzKCgAMicnADInJ10zJyf+NSYm/zYl
Jv83Jif/NyUm/zglJv8/LC3/yMPD////////////////////////////hXl6/zUiIv84JSb/NyYm/zYl
Jv81Jif/Mycn/jInJ1wyJycAMygoADIoKF00KCj+NScn/zYlJv83Jif/NyUm/zglJv8/LC3/yMPE////
////////////////////////iX9//zUiI/84Jib/NyYm/zYlJv81Jyf/Mygo/jInJ1wyJycAMykpADMo
KF00KCj+NSgo/zUmJv82Jib/NyYm/zclJv8+LC3/yMPD////////////+vr6/9fT0//Qzc3/e3Bw/zUj
JP83JSb/NiUm/zUmJv81KCj/NCgo/jIoKFwzKCgAMigoADIoKF0yKCj+NCgo/zUmJ/81JSb/NiUm/zcl
Jv8+LS3/yMPD////////////6ejo/1tLTP8/LS7/PCor/zcmJv83Jif/NiUm/zUmJ/80KCj/Migo/jIo
KFwyKCgAMSgoADAnJ10yKCj+Mygo/zQoKP81Jif/NiYn/zclJv8+LS3/yMPD////////////5+Xl/04+
P/8zICH/NSMk/zcmJv83Jif/NSYn/zQnKP8zKCj/Migo/jEoKFwyKCgAMCcnADAnJ10wKCj+Migo/zMo
KP80Jyf/NSYm/zUlJv88LC3/x8PD////////////7evr/3xxcf9nWlv/XE9P/zkpKf81Jib/NCcn/zMn
KP8yKCj/MSgo/jAoKFwwKCgALygoAC8oKF0wKCj+MCcn/zInJ/8zJyf/NCcn/zYnKP88LC3/x8PD////
/////////v7+//j4+P/4+Pj/1tPU/0g6O/8zJib/Mycn/zIoKP8xKCj/Lycn/i8nJ1wvJycALycoAC8o
KF0vKCj+MCcn/zEoKP8yKCj/Mycn/zUoKP86Li7/x8TE////////////////////////////6+rq/1RK
Sv8xJib/Micn/zEnKP8wJyj/Lycn/i8oKFwvKCgALicnAC4nJ14vKCj+Lygo/zAoKP8wKCj/MScn/zEn
J/85Li7/x8XF////////////////////////////9fX1/2JbW/8vJSX/MSgo/y8nJ/8wKCj/Lygo/i8o
KFwvKCgALigoAC4oKFkuJyj9Licn/y8nJ/8wKCj/MCcn/zAnJ/80Kyv/gHp6/6Cbm/+empr/npqa/56a
mv+fmpr/m5eX/1BJSf8uJib/MCgo/y8nJ/8uJyf/Licn/S4oKFcuKCgAMCoqADErKyouKCnXLicn/y4n
J/8wKCj/Lycn/y8nJ/8wKCj/LyYm/y4lJf8uJSX/LiUl/y4lJf8vJib/LSUl/y8nJ/8vKCj/Lygo/y4n
J/8uKCj/Ligo1jArKykvKioA/wD/APwAPwDgAAcAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAA
AQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAKAAAACAA
AABAAAAAAQAgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABQSUkAMSgoADgwMAo1LCxEMyoqpjEoKPUwKCj0MikppTQsLEM5MDEKMSgoAFNO
TgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABMQEAAc2RjADowMBc0KipaMikpsTIoKO0xJyf/LyUl/y8lJf8wJib/Migo7TIp
KbA0KipZOS8vFnVnZwBNQUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAP///wA4Li8AOjIyBDYsLCw2Kyt9NSoqzzInJ/ovJSX/MSYm/0E3N/5kXFz9XlZW/T0z
M/4xJib/MCUl/zEnJ/o0KirPNiwsfDYsLCw8NDQEOTAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAFBKSgAWDQ0AODAwDjQrK0gzKSmgMigo5jIoKP4zKCj/PDIy/mBXV/2YkpL8z83N/fHx
8f7u7u7+ycfH/ZOOjvxeVVX9PDEx/jMoKP8yKCj+Migo5TMpKZ80KytHNi8vDh4VFQBMRkYAAAAAAAAA
AAAAAAAAAAAAADs0NABDPj4BNCwsHjIqKmoyKSnAMigo9DIoKP8yJyf/Mycn/zUpKf5KQED7bWRk/Hty
c/18c3T9e3Jy/Xtxcv19c3T9e3Jy/WtiY/xKQED7NSkp/jMnJ/8zKCj/Mygo/zIoKPQxKCi/MioqaTQt
LR1FP0ABOzQ0AAAAAAA/OjoAKR8fADMrK1AwKCjWMCcn/TEnJ/8xJyf/Mycn/zMnJ/80Jyf/NCYn/zQl
Jv8xISL/MSAh/zIhIv8yIiP/MiIj/zIiI/8yIiP/MyMk/zQlJv80Jif/Mycn/zMnJ/8zJyf/Micn/zEn
J/8wKCj8MCgo1TIqK04qICAAQDo7ADIrKwAzLCwHMCgotTAnJ/8xJyf/MScn/zMoKP81KCn/NCcn/zUm
Jv81JSb/Nyco/1FDRP9NPj//PCwt/zUkJf8zIiP/NSQl/zcmJ/83Jyf/NiYm/zUlJv81Jib/NSco/zQo
KP8yJyf/Micn/zEnJ/8wJyf/MCgoszMsLAYyKysALycnAC8nJwoxKCi/Migo/zIoKP8zJyj/Mycn/zQn
J/81Jib/NSUm/zYlJv85KCn/sqys/+Ti4v/Ev7//oZma/3xxcv9IOTr/NiQl/zcmJv83JSb/NiUm/zUl
Jv81Jib/NCcn/zMnJ/8zKCj/Mygo/zEoKP8wKCi+LycnCS8nJwAvJycALycnCjIoKL8zKCj/Mygo/zQo
KP8zJyf/NiYn/zYmJ/82JSb/NiUm/zkoKf+7trb/////////////////+vr6/3pub/80IiP/OCYm/zgm
J/83JSb/NiUm/zUlJv81Jib/NCcn/zQoKP8zKCj/Mygo/zEoKL0uJycJLycnADAnJwAwJycKMigovzIo
KP80KCj/NCco/zUmJv81JSb/NiUm/zclJv83Jib/Oikp/7u2tv/////////////////9/f3/fHFx/zQh
Iv83JSb/NyUm/zclJv83Jif/NyYm/zUlJv81Jib/NCgo/zMoKP8yKCj/MigovS8nJwkwJycAMScnADEn
JwoxJye/Micn/zMnJ/81Jyf/NSUm/zYlJv83Jib/NyYm/zknJ/88KSr/u7a2//////////////////z8
/P98cHD/NiIj/zkmJv84Jib/OCUm/zknJ/84Jif/NiYm/zYmJv80Jyf/Mycn/zInJ/8yKCi9MigoCTIo
KAAxJycAMCYmCjIoKL80KCj/NSgo/zYnJ/82Jib/NyYm/zcmJ/83JSb/OCYm/zwpKf+8trb/////////
/////////Pz8/3xvcP83IiP/OiYm/zomJ/84JSb/OCYn/zgmJ/83Jib/NiYm/zYmJ/80KCj/NCgo/zMo
Kb0zKCgJMygoADInJwAxJycKMicnvzMnJ/80Jyf/NiYm/zYlJv83JSb/NyUm/zglJv85JSb/PCgp/7y2
tv/////////////////8/Pz/fG5v/zgiI/86JSb/OSUm/zklJv84Jib/NyUm/zYlJv82JSb/NSUm/zQn
J/8zKCj/MigovTEoKAkyKCgAMygoADIoKAozKCi/NCgo/zQmJ/81JSb/NiYm/zclJv83JSb/OSYn/zsm
J/88KCn/vLa2//////////////////z8/P97bW7/OSIj/zolJv85JSb/OyYn/zomJ/84Jib/NiUm/zcm
Jv82Jib/NScn/zMnJ/8yJye9MSgoCTIoKAAzKCgAMigoCjMoKL80KCj/NSYn/zYlJv83Jib/NyUm/zcl
Jv87Jyj/PSco/zwoKf+8trb/////////////////+/v7/3psbf85IiP/OyUm/zolJv88Jyj/PCgo/zgm
Jv82JSb/NiYm/zYmJv81Jif/Mycn/zMnJ70xKCgJMicnADInJwAxJycKMycnvzQnKP81Jib/NiYm/zcm
Jv83Jib/NyUm/zomJ/86Jib/PSgp/7y2tv/////////////////7+/v/emtr/zgiIv87JSb/OiUm/zsm
J/85Jif/OCYm/zclJv83Jib/NiUm/zUmJ/8zJyf/MygovTEoKAkyKCgAMScnADAnJwoyJye/NSgo/zUm
J/82Jib/NyYm/zcmJv84JSb/OSYm/zklJv89KSn/vba2//////////////////7+/v/Jw8P/raWl/6+n
p/9xZGT/OCQl/zglJv84Jif/OCYn/zcmJv81JSb/Nicn/zQnJ/8zKCi9MSgoCTIoKAAxJycAMCcnCjIn
J780Jyf/NSYm/zUlJv82JSb/NyUm/zclJv84JSb/OSUm/zwoKf+8trb/////////////////////////
/////////////5mQkP83IyP/OCUm/zclJv83Jib/NyYm/zUlJv81Jif/Mycn/zInJ70wJycJMScnADEn
JwAwJycKMicnvzMnJ/81Jif/NSUm/zcmJv84Jif/OCYm/zclJv85JSb/PSkp/7y2tv//////////////
////////////////////////m5KS/zYjI/84JSb/OCYn/zcmJv82JSb/NiUm/zUmJ/8zJyf/MicnvTAn
JwkxJycAMScnADAnJwozKCi/NCgo/zQnJ/81JSb/NiUm/zcmJ/83JSb/NyUm/zglJv88KCn/vLa2////
//////////////////////////////////+hmJn/NiMk/zglJv83JSb/NyYm/zYlJv82Jib/NScn/zMo
KP8yJye9MCcnCTEnJwAxJycAMScnCjMoKL80KCj/NSgo/zYmJ/81JSb/NyYn/zcmJ/83JSb/OCUm/zso
Kf+8trb///////////////////////b19f/v7e7/8fHx/6CYmf83JSX/NyUm/zYlJv82JSb/NSUm/zYm
J/81KCj/NCgo/zMoKL0xJycJMScnADEnJwAxJycKMigovzMoKP80KCj/NScn/zUlJv82JSb/NiUm/zcm
Jv84Jib/Oigp/7u2tv/////////////////+////m5KS/1lKS/9cTU7/TT4+/zgmJv83Jif/NyYm/zYl
Jv81JSb/NScn/zQoKP8zKCj/MigovTEnJwkxJycAMCcnAC8nJwoxJye/Micn/zMnJ/80Jyf/NSYm/zUl
Jv82JSb/NyUm/zgmJ/87KSr/u7a2//////////////////7+/v+AdXb/Mh8g/zUjJP82JCX/NyUm/zgn
J/83Jif/NiUm/zUmJ/80Jyf/Mycn/zInJ/8yKCi9MCgoCTEoKAAvJycALSYmCjEoKL8yKCj/Mygo/zUo
KP80Jyf/NiYn/zYmJ/82JSb/NyYn/zopKf+7trb//////////////////f39/4B0df8zISH/NyUl/zcl
Jv82JSb/NyYn/zYmJ/82Jif/NSco/zQoKP8zKCj/Migo/zIoKL0wKCgJMSgoAC8nJwAvJycKLycnvzEn
J/8yKCj/Mygo/zQnJ/80Jyf/NSUm/zUlJv82JSb/OSgp/7u2tv/////////////////9/f3/i4GC/0Y2
N/9IODn/RTY3/zkpKv81JSb/NSYm/zQnJ/8zJyf/Micn/zInJ/8xKCj/MSgovTAoKAkwKCgAMCgoADEo
KAowKCi/MCgo/zEnJ/8xJyf/Mycn/zMnJ/80Jyf/Nicn/zYmJ/84KCn/u7a2////////////////////
///q6en/3dvb/93a2v/T0ND/XlJS/zQkJf80Jyf/Mycn/zMoKP8yKCj/Migo/zAnJ/8vJye9MCgoCTAo
KAAvKCgALygoCi8oKL8wKCj/MCcn/zEnJ/8yKCj/Micn/zMnJ/82KSn/Nygp/zgpKf+6trb/////////
//////////////////////////////z8/P95cHD/MyYm/zQoKP8yJyf/Mico/zEoKP8wKCj/Lycn/y8n
J70vJycJLycnAC8oKAAvKCgKLycovy8nJ/8vJyf/MCgo/zIoKP8yKCj/Micn/zQoKP81KCj/Nioq/7q2
tv///////////////////////////////////////////4yFhf8xJSX/Migo/zEnJ/8xJyf/MCcn/zAn
J/8vJyf/LygovS8oKAkvJygALicnAC0nJwouJye/MCgo/y8oKP8wKCj/MCgo/zEoKP8xJyf/Micn/zIn
J/82Kyv/ure3////////////////////////////////////////////oZyd/zAmJv8yKCj/Migo/zAo
KP8vJyf/MCgo/y8oKP8vKCi9LigoCS4oKAAtJycALScnCi0nJ78uKCj/Licn/y8nJ/8vJyf/MCcn/zAn
J/8xJyf/MScn/zQqKv+sqKj/7e3t/+rq6v/q6ur/6urq/+rq6v/q6en/6urp/+3t7f+no6P/Mikp/zAn
J/8wKCj/MCgo/y8nJ/8vKCj/Licn/y4nJ74uKCgJLigoADMtLQA1LzAFLykprS4nJ/8uJyf/Licn/y8o
KP8wKCj/MCcn/y8nJ/8wJyf/MSgo/0Y+Pv9RSUn/UUhI/1FISf9RSEn/UEhI/1FJSf9RSUn/UElJ/0U+
Pv8wKCj/MCgo/y8oKP8vJyf/Licn/y4nJ/8tJyf/LigoqzQvLwQyLC0AQTw9ACslJQAxKytRLygp5S4n
J/8uJyf/Lygo/zAoKP8vKCj/Lycn/zAnJ/8wKCj/LyYm/y0lJf8uJSX/LiUl/y4lJf8tJSX/LiUl/y8m
Jv8tJSX/LiYm/y8nJ/8vKCj/Lygo/y4nJ/8uKCj/Ligo/y4oKOQwKytPKyQkAEM/PwD/8A///8AD//4A
AH/4AAAfwAAAA8AAAAOAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAA
AAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABwAAAAygA
AAAwAAAAYAAAAAEAIAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFP0AAAAAAADsz
MxQ1LS1bMioqwDAoKPwwKCj8MSkpvzMsLFk4MTIUAAAAAEQ/PwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPzc3AEQ8
PAM4Li4kNCsrcDQqKsQyKSn1Migo/zIoKP8xKCj/MCcn/zEoKPUzKirDNCsrbjcuLyNDPDwCPjY2AAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd29vAC4j
IwBDODgKNy0tPzQqKpMyKCjeMScn/TIoKP8yJyf/MScn/zEnJ/8xJyf/MScn/zEnJ/8xJyf/Migo/TIo
KN00KiqRNy0tPUE3NwoqICEAioODAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEU9
PgBUT08BOjExGTkuL144LS21Mykp8DInJ/8xJyf/MScn/zEnJ/8wJSX/Mygo/kE3N/08MzP9MCYm/zAl
Jf8xJyf/Migo/zEnJ/8xJyf/Mykp7zcsLLM6Ly9cOjExGF1XWAFHP0AAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMPf
3wA4Ly8AQDo6BTcuLi80KiqANCkp0TMpKfozKSn/Mygo/zEnJ/8wJSX/Micn/kI3N/1oYGD8oJub/NHP
z/3Ixsb8lI6O/GFYWPw/NDT9Micn/zAlJf8xJib/Migo/zMoKf80KSn6NCoq0DQrK343Li8uPDY2BTcw
MAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AABMRkYAAAAAADozMw81LS1LMyoqojIoKOcxJyf+MScn/zIoKP8yJyf/NCkp/kE2Nv1kXFz8mpaW+9DP
z/zz8/P+/////////////////v7+/+/v7/7KyMj8lpGR+2JaWvxANTX9NCkp/jMoKP8yJyf/MScn/zIo
KP4yKCjmNCoqoDQsLEk5MjIPEAUFAE1ISAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAPzg4AEY/PwI2Ly8iNCwsbDIqKsEyKCj1Migo/zInJ/8yJyf/Mygo/zMoKP8zJyf/PTMz+mJb
W/mGgID7lpCQ+5eRkfyWj5D8lo+P/JaPj/yWj4/8lo+P/JeQkPyYkZL8lY+P+4R/f/thWlr5PzU1+jQo
KP4zJyf/Micn/zMpKf8zKCj/Mygo/zEoKPQxKSnANCwsazkwMSFHQkECQDk5AAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAD03OABPTE4BNS0uLTIqKo0wKCjbMCcn/DEnJ/8yJyf/Micn/zInJ/8yJyf/Mycn/zMn
J/80KCj/NCcn/zUnJ/80JSb/NCQl/zMjJP8zIyT/MyMk/zMjJP8zIyT/MyMk/zMjJP8zIyT/MyQk/zQl
Jf80Jyf/NCgo/zMnJ/80KCj/Mycn/zMoKP8zKCj/Migo/zInJ/8wJyf/MCgo/DEpKdoyKiqMNS4uLFZV
VAE+ODgAAAAAAAAAAAAAAAAAQj09ADEpKQA1LS09MSkp2zAnJ/8wJyf/MCcn/zEnJ/8xJyf/Micn/zMo
KP8zJyf/Mycn/zQnJ/80Jif/NSUm/zUlJv81JSb/NSUm/zYlJv82JSb/NiUm/zYlJv82JSb/NSUm/zUl
Jv81JSb/NSUm/zYmJv81JSb/NSYm/zQmJ/8zJyf/Mycn/zMnJ/8zJyf/Micn/zIoKP8xJyf/MScn/y8n
J/8wJyf/MSkp2TQtLTowKSkARUBAAAAAAAAAAAAAODExAJ2oqAAxKSmXMCcn/zAnJ/8xJyf/Micn/zEn
J/8yJyj/NSkp/zUpKf80KCj/NCYn/zUlJv81JSb/NiUm/zcmJ/85Kir/Nycn/zQiI/80IiP/NSQl/zYl
Jv82JSb/NiUm/zYlJv82Jib/OCcn/zcnJ/82Jif/NSUm/zUlJv81Jib/NCcn/zUoKP80KCj/Mygo/zIn
J/8xJyf/Micn/zEnJ/8wJyf/MCcn/zEpKZMAAAAAODExAAAAAAAAAAAALycnAC4mJgMwKCirMCgo/zAn
J/8xJyf/MScn/zInJ/8zJyf/NSgo/zQoKP81Jyf/NSUm/zUlJv82Jib/NiUm/zYlJv+BeHj/raen/4d+
f/9nWlv/Tj9A/z0tLv81JCX/NCIj/zYlJv83Jib/OCYn/zcmJ/83Jib/NSUm/zUlJv82Jib/NSUm/zUn
J/80KCj/Mycn/zMnJ/8yJyf/Migo/zEnJ/8wJyf/Lycn/y8nJ6kvJycDLycnAAAAAAAAAAAALycnACwl
JQMxKCirMikp/zIoKP8zKCj/Mygo/zMoKP8zJyf/Mycn/zUmJ/81Jib/NSUm/zUlJv82Jib/NiUm/zUk
Jf+hm5v///////39/f/z8vL/397e/8O/v/+hmpv/dmxs/z4tLv82JCX/NiUm/zcmJv83JSb/NiUm/zYl
Jv82Jib/NSUm/zUlJv81Jyf/NCcn/zMnJ/80KCj/NCkp/zMoKP8yKCj/MSgo/zAoKKgsJSUDLycnAAAA
AAAAAAAALycnACwlJQMxKCirMigp/zMoKP8zKCj/NCgo/zQoKP8zJyf/NCYn/zcnJ/82Jif/NiYm/zYl
Jv83Jib/NiUm/zUkJf+gmpr/////////////////////////////////4N7e/09AQP81IyT/NyUm/zgn
J/84Jif/NyYm/zYlJv83Jib/NSUm/zUlJv81JSb/NCYn/zMnJ/80KCj/NCgp/zMoKP8zKCj/Migo/zAo
KKgqJCQDLycnAAAAAAAAAAAALycnAColJQMyKCirMygp/zMoKP8zKCj/NSgo/zQoKP80Jif/NSUm/zUl
Jv82Jib/NiYm/zcmJv83Jib/NyUm/zYkJf+hmpr/////////////////////////////////4uDh/09A
Qf81IyT/NyUm/zclJv84Jib/NyYm/zcmJv83Jib/NiUm/zYlJv82Jib/NSUm/zQmJ/80KCj/NSgo/zMo
KP8zKCj/Mygo/zEoKKgpJCQDLycnAAAAAAAAAAAAMCcnAC0mJgMyKCirMigo/zIoKP80KCj/NCgo/zQn
J/81JSb/NSUm/zUlJv82JSb/NiUm/zclJv84Jif/OSYn/zckJf+gmZr/////////////////////////
////////4uDg/08/QP82IyT/NyUm/zclJv83JSb/NyUm/zclJv83Jif/OCYn/zcmJv81JSb/NiUm/zUl
Jv80Jyf/NCgo/zMoKP8yKCj/Migo/zEoKKgtJiYDMCcnAAAAAAAAAAAAMScnADEnJwMxJyerMScn/zIn
J/8zJyf/NCcn/zUmJ/82JSb/NSUm/zYlJv83Jib/NiUm/zcmJv85Jyf/Oicn/zglJf+hmZr/////////
////////////////////////4d/f/1A/QP84JCX/OCUm/zglJv84Jib/NyUm/zgmJv85Jyf/OCcn/zgm
J/82JSb/NSUm/zUlJv80Jib/Mycn/zMnJ/8yJyf/Micn/zIoKKgyKCgDMigoAAAAAAAAAAAAMScnADAm
JgMxJyerMykp/zMoKP80Jyj/NSgo/zYmJ/82Jib/NSUm/zgmJ/85Jyf/NyUm/zgmJv86Jyf/Oicn/zgl
Jf+hmZr/////////////////////////////////4d/f/1A/P/84JCX/OSUm/zsmJ/87Jyf/NyUm/zgm
Jv85Jyf/OCcn/zgmJ/83Jif/NyYn/zcnJ/81Jib/NSgo/zQoKP8zKCj/Mygo/zMoKKgyJycDMigoAAAA
AAAAAAAAMScnADAmJgMxJyerMygo/zQoKP80KCj/Nigo/zcmJ/82Jib/NiUm/zcmJv83Jib/NyUm/zgl
Jv84Jib/OSYm/zklJf+hmZr/////////////////////////////////4N7e/1A/P/85JCX/OSUm/zom
Jv86Jib/OCUm/zglJv84Jib/OCYm/zcmJ/82JSb/NiUm/zUlJv81JSb/Nigo/zQoKP80KCj/NCkp/zMo
KKgzKSkDMykpAAAAAAAAAAAAMScnADEnJwMxJyerMicn/zMnJ/80Jyf/Nicn/zYmJv82JSb/NyUm/zYl
Jv82JSb/NyUm/zclJv84JSb/OSUm/zgkJf+impr/////////////////////////////////4N3d/1A+
Pv85JCX/OiUm/zklJv85JSb/OSUm/zglJv84JSb/NyUm/zclJv83JSb/NiUm/zYlJv81JSb/NSYm/zMn
J/80Jyj/Mygo/zIoKKgxJycDMigoAAAAAAAAAAAAMygoADQpKQMzKCirMygo/zQoKP80Jyf/NSUm/zUl
Jv82JSb/NyYm/zcmJv83JSb/OCUm/zkmJ/86Jif/OiYn/zgkJf+impr/////////////////////////
////////393d/1A9Pf86JCX/OyYm/zomJv85JSb/OiUm/zomJ/85Jif/OCYn/zclJv82JSb/NiUm/zcm
J/82Jif/NiYn/zUoKP80Jyf/Micn/zEnJ6gzKCgDMigoAAAAAAAAAAAAMigoADMpKQMyJyerMycn/zQo
KP80Jyf/NSUm/zUlJv83Jib/NyYm/zYlJv83JSb/OCYm/zomJ/87Jif/OyYn/zgkJf+imZr/////////
////////////////////////3tzc/088Pf87JCX/PCYm/zolJv85JSb/OiYm/zsmJ/85Jif/OSYn/zgl
Jv82JSb/NiUm/zcmJ/82Jib/NSUm/zUnJ/8zJyf/Mycn/zInJ6g0KSkDMygoAAAAAAAAAAAAMygoADIp
KQMyKCirNCgo/zQoKP80Jif/NSUm/zUlJv82JSb/NyYm/zYlJv83JSb/OCYm/z0oKP8/KSn/OyYn/zgk
Jf+imZr/////////////////////////////////3tvc/088PP86JCX/PCYm/zslJv85JSb/OiYm/z4o
KP8+KSn/OSYn/zglJv83JSb/NiUm/zcmJv82Jib/NiYm/zUnJ/8zJyf/Mycn/zInJ6gvJycDMScnAAAA
AAAAAAAAMygoADIoKQMzKCirNCgo/zQoKP81Jif/NSUm/zYmJ/84Jif/OCYn/zclJv83JSb/OCYn/zsn
J/89Jyj/OyYn/zkkJf+impr/////////////////////////////////3tvb/088PP87JSX/PCYn/zsl
Jv86JSb/OiYm/zwnJ/88Jyj/OSYn/zglJv83JSb/NiUm/zcmJv82Jib/NiYn/zUmJ/8zJyf/Myco/zMo
KKgvKCcDMigoAAAAAAAAAAAAMScnAC8nJwMyJyerMycn/zQnJ/81Jib/NSUm/zYmJv82JSb/NyYm/zcl
Jv83JSb/OCUm/zomJ/86Jib/OiYm/zkkJf+impr/////////////////////////////////3dra/0w4
OP83ISL/OSMj/zkiI/84IyT/OiUm/zomJ/85Jib/OCYm/zglJv83JSb/NyYm/zcmJv81JSb/NSUm/zUm
J/8zJyf/Mygo/zMoKKgsJSUDMScnAAAAAAAAAAAAMScnAC8nJwMyJyerNCgo/zYpKf81Jif/NSUm/zYm
J/83Jib/NyYn/zclJv84JSb/OCYm/zkmJv85JSb/OSUm/zslJf+jmpr/////////////////////////
////////7Ovr/5mOj/+MgIH/jYGC/46Cg/9yZWX/PCgp/zolJv84JSb/NyUm/zkmJ/84Jif/OCYn/zYl
Jv81JSb/NiYn/zcoKP80KCj/Mygo/zMoKKguJiYDMicnAAAAAAAAAAAAMScnAC8nJwMyJyerNCgo/zQo
KP81Jif/NSUm/zYmJ/83Jib/NyYn/zclJv83JSb/OCUm/zklJv86Jib/OSUm/zolJf+jmpr/////////
///////////////////////////////////+//7//v////////+/urr/Pisr/zklJv85Jib/NyUm/zkm
J/84Jif/OCYn/zYlJv81JSb/NiYm/zUnJ/8zJyf/Mycn/zMoKKguJycDMicoAAAAAAAAAAAAMScnADAn
JwMyJyerMycn/zMnJ/80Jif/NSUm/zUlJv82JSb/NiUm/zclJv83JSb/OCUm/zglJv85JSb/OSUm/zgk
Jf+imZr////////////////////////////////////////////////////////////Au7v/Piss/zkl
Jv84JSb/NyUm/zclJv83JSb/NiUm/zclJv81JSb/NSUm/zQmJ/8zJyf/Mycn/zEnJ6gvJycDMScnAAAA
AAAAAAAAMScnADAnJwMyJyerMycn/zQoJ/80Jyf/NiUm/zUlJv82JSb/OCYn/zgmJ/84Jib/NyUm/zcl
Jv85JSb/OSUm/zklJf+impr/////////////////////////////////////////////////////////
///Cvb7/Piss/zglJv83JSb/OCUm/zgmJ/83Jib/NiUm/zYlJv81JSb/NiYm/zQnJ/80KCj/Mycn/zEn
J6gwJycDMScnAAAAAAAAAAAAMScnAC8mJgMyJyerMygo/zMnJ/80Jyf/NSUm/zUlJv82Jib/OCcn/zgn
J/84Jif/NyUm/zclJv85Jib/OiUm/zolJf+impr/////////////////////////////////////////
///////////////////GwsL/QC0u/zglJv84Jib/OCUm/zkmJ/83Jif/NyYm/zYlJv81JSb/NiYm/zQn
J/8zJyf/Micn/zEnJ6gxJycDMScnAAAAAAAAAAAAMScnAC4lJQMyKCirNCkp/zQoKP80KCj/NSYn/zUl
Jv81JSb/NiUm/zYlJv83JSb/NyUm/zclJv84Jib/OSUm/zgkJf+impr/////////////////////////
///////////////////////////////////MyMj/Qi8w/zclJf84Jib/NyUm/zclJv83JSb/NiUm/zYl
Jv82Jib/Nicn/zQoKP80KCj/Mygo/zIoKKgvJiYDMScnAAAAAAAAAAAAMScnAC4lJQMzKCirNCkp/zUo
KP81KCj/Nico/zYmJv81JSb/NiUm/zgmJ/83Jif/OCYm/zclJv84Jib/OCUm/zgkJf+impr/////////
///////////////////////////////////////////////////V0tL/RjU1/zckJf84Jib/NyUm/zYl
Jv83JSb/NiUm/zUlJv82Jib/Nigo/zUoKP80KCj/Mygo/zIoKKguJSUDMScnAAAAAAAAAAAAMScnAC4l
JQMzKCirMygo/zQoKP80KCj/NSgo/zYmJ/81JSb/NSUm/zcmJv83Jif/NyYm/zglJv84Jib/NyUm/zck
Jf+hmpr/////////////////////////////////+Pf3/8G8vP+yrK3/s62t/7Surv+dlpb/QzMz/zcl
Jf84Jib/NyUm/zclJv82JSb/NSUm/zUlJv82Jif/NSgo/zUoKP80KCj/Mygo/zIoKKgtJCQDMScnAAAA
AAAAAAAAMScnAC8mJQMyKCirMigo/zMoKP80KCj/NCgo/zUmJ/81JSb/NSUm/zYlJv82JSb/NiUm/zcm
Jv84Jib/OCYn/zYkJf+hmZr/////////////////////////////////6ejo/1lJSv84JSb/OSco/zkn
KP85Jyj/OCYm/zglJv84Jif/NyYn/zcmJv82JSb/NSUm/zUlJv81Jyf/NCgo/zQoKP8zKCj/Migo/zIo
KKgvJSUDMScnAAAAAAAAAAAAMCcnAC8nJwMxJyerMScn/zEnJ/8zJyf/Mycn/zQnJ/81Jib/NSUm/zUl
Jv82JSb/NiUm/zcmJv84Jyf/OScn/zclJf+gmZr/////////////////////////////////5+bm/1VG
R/82IyT/NyUm/zclJv83JSb/NyUm/zcmJv84Jif/OCcn/zcmJ/81JSb/NSUm/zUmJv8zJyf/Mycn/zMn
J/8xJyf/MScn/zEoKKgvKCcDMSgoAAAAAAAAAAAALycnAC0nJgMwJyerMigo/zInJ/8yJyf/NCgo/zQo
KP80Jyf/NSUm/zYmJ/83Jyf/NiUm/zcmJv84Jyf/OScn/zclJf+gmZr/////////////////////////
////////5uXl/1RFRv82IyT/NyUm/zkmJ/85Jyf/NyUm/zcmJv84Jyf/OCcn/zcmJ/82Jif/NiYn/zUo
KP8zJyf/NCgo/zInJ/8xJyf/Migo/zEoKKguKCgDMCgoAAAAAAAAAAAALycnAC0mJgMvJyerMykp/zIo
KP8yKCj/NCkp/zUoKP80KCj/NCYn/zYmJ/83Jyf/NSUm/zYmJv83Jif/NyYm/zYlJf+gmZr/////////
////////////////////////5eTk/1RERf82JCX/NyUm/zgmJ/84Jyf/NiUm/zcmJv83Jif/NiYm/zYm
J/81Jib/NScn/zQoKP80Jyf/NSkp/zIoKP8yKCj/Mykp/zEoKKgwKCgDMSgoAAAAAAAAAAAALycnAC8n
JwMvJyerMCcn/zEnJ/8yKCj/Mygp/zQoKP80KCj/Mycn/zQmJ/81JSb/NSUm/zUlJv82JSb/NyUm/zUk
Jf+gmZr/////////////////////////////////5OLi/09AQf8yISH/NCIj/zMiI/8zIiP/NCMk/zYl
Jv81JSb/NSUm/zUmJv80Jif/Mycn/zMnJ/8zJyf/Micn/zEnJ/8yKCj/MSgo/zEoKKgwKCgDMSgoAAAA
AAAAAAAAMCgoADEoKAMwKCirMCgo/zEoKP8xJyf/MScn/zInJ/8zJyf/NCgo/zQnJ/80Jif/NSUm/zYm
J/82Jif/NiUm/zUkJf+gmpr/////////////////////////////////8O/v/5uTlP+If3//h35//4V8
ff+Ee3z/aV5f/zgoKf82Jif/NSUm/zQmJ/80Jyf/Mycn/zQoKP8yKCj/Migo/zIoKP8wJyf/Lycn/y8n
J6gvJycDLycnAAAAAAAAAAAAMCgpADIpKgMwKCirMCgo/zEoKP8xKCj/MScn/zInJ/8yKCj/Mycn/zMn
J/8zJyf/NScn/zYnJ/82Jif/NiYn/zQkJf+gmZr/////////////////////////////////////////
///+/v7//v7+//3+/v//////yMXF/z8wMf81Jib/NSgo/zQnJ/8zJyf/Mycn/zMoKP8yKCj/Migo/zIo
KP8wJyf/Lycn/y8nJ6gzKioDMSgpAAAAAAAAAAAALygoAC8oKAMvJyerLycn/zAnJ/8vJyf/MCcn/zEn
J/8xJyf/Micn/zInJ/8zJyf/NCgo/zcpKf84KSn/NiYn/zQkJf+gmZr/////////////////////////
////////////////////////////////////////2tjY/0k8Pf82KSn/NCgo/zQnJ/8zJyf/Micn/zIo
KP8yJyf/MCcn/zAoKP8vJyf/Lycn/y4nJ6guJycDLicnAAAAAAAAAAAAMCgpADEpKgMwKCirMCgo/zAo
KP8vJyf/MCcn/zEoKP8yKCj/Mygo/zInJ/8yJyf/NCgo/zYpKv83Kir/NSgo/zMlJf+gmpr/////////
////////////////////////////////////////////////////////6Ofn/1VLS/81KCj/NCgo/zMn
J/8xJyf/MScn/zInKP8yKCj/MSgo/zAoKP8vJyf/Lycn/y8oKKguJycDLygoAAAAAAAAAAAALicnAC4n
JwMuJyerLicn/y8nJ/8vJyf/MCcn/zAoKP8xKCj/Migo/zInJ/8xJyf/Mygo/zQoKP80KCj/NCgo/zIm
Jv+fmpr/////////////////////////////////////////////////////////////////8vLy/2Na
Wv8xJSX/Mygo/zEnJ/8yJyf/Micn/zEoKP8vJyf/Lycn/y8nJ/8uJyf/Lygo/y8oKKgtJiYDLycnAAAA
AAAAAAAALicnAC0nJwMuJyerLygo/zApKf8vKCj/MCcn/zAoKP8wJyf/MSgo/zEnJ/8xJyf/Micn/zIo
KP8yJyf/Mycn/zMnJ/+fmpr/////////////////////////////////////////////////////////
////////+vr6/3Rubv8uJCT/MScn/zIoKP8yKCj/MSgo/zAnJ/8vJyf/MCgo/zApKf8vKCj/Lygo/y8o
KKgsJiYDLicnAAAAAAAAAAAALScnAC0nJwMtJyerLygo/zApKf8vKCj/Lycn/zAoKP8wKCj/MCgo/zAn
J/8xJyf/Micn/zInJ/8yJyf/MScn/zInJ/+gm5v/////////////////////////////////////////
/////////////////////////////4mEhP8vJCT/MScn/zIoKf8yKSn/MSgo/zAnJ/8vJyf/Lygo/zAp
Kf8vKCj/Lico/y8oKKgsJycDLigoAAAAAAAAAAAALScnAC0nJwQtJyerLScn/y4nJ/8uJyf/Licn/y4n
J/8vJyf/Lycn/zAnJ/8vJyf/MCcn/zEnJ/8xJyf/MScn/zAmJv+UkJD/7e3t/+zr6//r6+v/6+vr/+vr
6//r6+v/6+vr/+vr6//r6+v/6+vr/+vr6//r6+v/7e3t/5KOjv8wJib/Lycn/y8nJ/8wKCj/MCgo/y8o
KP8uJyf/Licn/y4nJ/8uJyf/LScn/y0nJ6ktJycDLScnAAAAAAAAAAAANC8vAEM/PwEvKSmgLigo/y4n
J/8uJyf/Lycn/y4nJ/8uJyf/Lygo/zAoKP8wJyf/Lycn/y8nJ/8vJyf/MCcn/zEnJ/9EPDz/Vk5O/1ZN
Tf9VTU3/Vk1N/1VNTf9WTU3/Vk1N/1VNTf9VTU3/Vk5O/1ZOTv9VTU3/VE1N/0Q9Pf8vJyf/Lycn/zAn
J/8vJyf/Lycn/y8oKP8uJyf/Lygo/y4nJ/8uJyf/LScn/y4oKJ1PTU0BMy4uAAAAAAAAAAAAWlhZACsk
JAAyLCxfLigo+C0nJ/8uJyf/Licn/y4nJ/8vKCj/MCkp/zApKf8wKCj/Lycn/y8nJ/8wJyf/MCgo/zEo
KP8wJyf/LiUl/y4lJf8uJSX/LyUl/y8lJf8vJSX/LiUl/y4lJf8vJSX/MCYn/zAmJv8uJib/LSUl/y4m
Jv8wJyf/MCgo/zAoKP8vKCj/Lygo/y4nJ/8uJyf/Lico/y0nJ/8tJyf/Ligo9zErLFwrJSUAWVdYAAAA
AAAAAAAAAAAAADYxMQA5NDQOMSssiy8pKe0uKCj/LScn/y4nJ/8uJyf/Lygo/y8oKP8vKCj/Licn/y8n
J/8wKCj/Lycn/zAoKP8wKCj/Lycn/zAnJ/8wJyf/MCgo/zAoKP8wJyf/MCcn/y8nJ/8wJyf/MCgo/zAo
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
AElFTkSuQmCC
</value>
</data>
</root>

View File

@ -1,91 +0,0 @@
// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using FModel;
//
// var itemsIdParser = ItemsIdParser.FromJson(jsonString);
namespace FModel
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class ItemsIdParser
{
[JsonProperty("export_type")]
public string ExportType { get; set; }
[JsonProperty("CharacterParts")]
public string[] CharacterParts { get; set; }
[JsonProperty("HeroDefinition")]
public string HeroDefinition { get; set; }
[JsonProperty("WeaponDefinition")]
public string WeaponDefinition { get; set; }
[JsonProperty("Rarity")]
public string Rarity { get; set; }
[JsonProperty("DisplayName")]
public string DisplayName { get; set; }
[JsonProperty("ShortDescription")]
public string ShortDescription { get; set; }
[JsonProperty("Description")]
public string Description { get; set; }
[JsonProperty("GameplayTags")]
public GameplayTags GameplayTags { get; set; }
[JsonProperty("SmallPreviewImage")]
public PreviewImage SmallPreviewImage { get; set; }
[JsonProperty("LargePreviewImage")]
public PreviewImage LargePreviewImage { get; set; }
}
public partial class GameplayTags
{
[JsonProperty("gameplay_tags")]
public string[] GameplayTagsGameplayTags { get; set; }
}
public partial class PreviewImage
{
[JsonProperty("asset_path_name")]
public string AssetPathName { get; set; }
[JsonProperty("sub_path_string")]
public string SubPathString { get; set; }
}
public partial class ItemsIdParser
{
public static ItemsIdParser[] FromJson(string json) => JsonConvert.DeserializeObject<ItemsIdParser[]>(json, FModel.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this ItemsIdParser[] self) => JsonConvert.SerializeObject(self, FModel.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
}

View File

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FModel
{
static class Program
{
/// <summary>
/// Point d'entrée principal de l'application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new PAKWindow());
}
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Les informations générales relatives à un assembly dépendent de
// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations
// associées à un assembly.
[assembly: AssemblyTitle("FModel")]
[assembly: AssemblyDescription("FModel is a Fortnite .PAK file explorer built in C#")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FModel")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly
// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de
// COM, affectez la valeur true à l'attribut ComVisible sur ce type.
[assembly: ComVisible(false)]
// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM
[assembly: Guid("71a31c47-30bc-4cb5-ad89-81e5008f4beb")]
// Les informations de version pour un assembly se composent des quatre valeurs suivantes :
//
// Version principale
// Version secondaire
// Numéro de build
// Révision
//
// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut
// en utilisant '*', comme indiqué ci-dessous :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -19,10 +19,10 @@ namespace FModel.Properties {
// à l'aide d'un outil, tel que ResGen ou Visual Studio. // à l'aide d'un outil, tel que ResGen ou Visual Studio.
// Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
// avec l'option /str ou régénérez votre projet VS. // avec l'option /str ou régénérez votre projet VS.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { public class Resources {
private static global::System.Resources.ResourceManager resourceMan; private static global::System.Resources.ResourceManager resourceMan;
@ -36,7 +36,7 @@ namespace FModel.Properties {
/// Retourne l'instance ResourceManager mise en cache utilisée par cette classe. /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager { public static global::System.Resources.ResourceManager ResourceManager {
get { get {
if (object.ReferenceEquals(resourceMan, null)) { if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FModel.Properties.Resources", typeof(Resources).Assembly); global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FModel.Properties.Resources", typeof(Resources).Assembly);
@ -51,7 +51,7 @@ namespace FModel.Properties {
/// les recherches de ressources à l'aide de cette classe de ressource fortement typée. /// les recherches de ressources à l'aide de cette classe de ressource fortement typée.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture { public static global::System.Globalization.CultureInfo Culture {
get { get {
return resourceCulture; return resourceCulture;
} }
@ -60,124 +60,14 @@ namespace FModel.Properties {
} }
} }
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap BG512 {
get {
object obj = ResourceManager.GetObject("BG512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Byte[].
/// </summary>
internal static byte[] BurbankBigCondensed_Bold {
get {
object obj = ResourceManager.GetObject("BurbankBigCondensed_Bold", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap C512 {
get {
object obj = ResourceManager.GetObject("C512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap E512 {
get {
object obj = ResourceManager.GetObject("E512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary> /// <summary>
/// Recherche une ressource localisée de type System.Drawing.Icon semblable à (Icône). /// Recherche une ressource localisée de type System.Drawing.Icon semblable à (Icône).
/// </summary> /// </summary>
internal static System.Drawing.Icon FNTools_Logo { public static System.Drawing.Icon FModel {
get { get {
object obj = ResourceManager.GetObject("FNTools_Logo", resourceCulture); object obj = ResourceManager.GetObject("FModel", resourceCulture);
return ((System.Drawing.Icon)(obj)); return ((System.Drawing.Icon)(obj));
} }
} }
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap I512 {
get {
object obj = ResourceManager.GetObject("I512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap L512 {
get {
object obj = ResourceManager.GetObject("L512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap M512 {
get {
object obj = ResourceManager.GetObject("M512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap R512 {
get {
object obj = ResourceManager.GetObject("R512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap T512 {
get {
object obj = ResourceManager.GetObject("T512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap U512 {
get {
object obj = ResourceManager.GetObject("U512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap unknown512 {
get {
object obj = ResourceManager.GetObject("unknown512", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
} }
} }

View File

@ -118,40 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="BG512" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="FModel" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\BG512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\FModel.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="BurbankBigCondensed_Bold" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\BurbankBigCondensed-Bold.otf;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="C512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\C512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="E512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\E512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="FNTools_Logo" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\FNTools_Logo.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="I512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\I512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="L512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\L512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="M512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\M512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="R512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\R512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="T512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\T512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="U512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\U512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="unknown512" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\unknown512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
</root> </root>

View File

@ -1,30 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FModel.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
FModel/Resources/Cataba.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -0,0 +1,39 @@
<SyntaxDefinition name="Changelog" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<RuleSet name="diff">
<Span multiline="false" foreground="#98C379">
<Begin>^\+</Begin>
<End>.*(?:\t|\s{2,})+</End>
</Span>
<Span multiline="false" foreground="#E06C75">
<Begin>^\-</Begin>
<End>.*(?:\t|\s{2,})+</End>
</Span>
<Span multiline="false" foreground="#61AFEF">
<Begin>^\~</Begin>
<End>.*(?:\t|\s{2,})+</End>
</Span>
</RuleSet>
<RuleSet name="doc" ignoreCase="false">
<Span multiline="false" foreground="#7F848E">
<Begin>.*(?:\t|\#{1}|\s{2,})+</Begin>
<End>\r\n</End>
</Span>
<Span multiline="false" underline="true">
<Begin>^[0-9]\..*</Begin>
</Span>
<Keywords underline="true">
<Word>ADDED</Word>
<Word>FIXED</Word>
<Word>REMOVED</Word>
<Word>IMPROVED</Word>
</Keywords>
</RuleSet>
<RuleSet>
<Import ruleSet="diff" />
<Import ruleSet="doc" />
</RuleSet>
</SyntaxDefinition>

195
FModel/Resources/Cpp.xshd Normal file
View File

@ -0,0 +1,195 @@
<?xml version="1.0"?>
<!-- syntaxdefinition for C/C++ 2001 by Andrea Paatz and Mike Krueger -->
<!-- converted to AvalonEdit format by Siegfried Pammer in 2010 -->
<SyntaxDefinition name="C++" extensions=".c;.h;.cc;.cpp;.hpp" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Comment" foreground="Green" />
<Color name="Character" foreground="Fuchsia" />
<Color name="String" foreground="Fuchsia" />
<Color name="Preprocessor" foreground="Green" />
<Color name="Punctuation" foreground="DarkGreen" />
<Color name="MethodName" foreground="MidnightBlue" fontWeight="bold" />
<Color name="Digits" foreground="#F78C6C" />
<Color name="CompoundKeywords" foreground="Black" fontWeight="bold" />
<Color name="This" foreground="Black" fontWeight="bold" />
<Color name="Operators" foreground="#FF008B8B" fontWeight="bold" />
<Color name="Namespace" foreground="#FF008000" fontWeight="bold" />
<Color name="Friend" foreground="#FFA52A2A" />
<Color name="Modifiers" foreground="#FF0000FF" fontWeight="bold" />
<Color name="TypeKeywords" foreground="#FFFF0000" />
<Color name="BooleanConstants" foreground="#FF000000" fontWeight="bold" />
<Color name="Keywords" foreground="#FF0000FF" fontWeight="bold" />
<Color name="LoopKeywords" foreground="#FF0000FF" fontWeight="bold" />
<Color name="JumpKeywords" foreground="#FF000080" />
<Color name="ExceptionHandling" foreground="#FF008080" fontWeight="bold" />
<Color name="ControlFlow" foreground="#FF0000FF" fontWeight="bold" />
<RuleSet ignoreCase="false">
<Rule color="Punctuation">
[?,.;()\[\]{}+\-/%*&lt;&gt;^=~!&amp;]+
</Rule>
<Keywords color="CompoundKeywords">
<Word>__abstract</Word>
<Word>__box</Word>
<Word>__delegate</Word>
<Word>__gc</Word>
<Word>__identifier</Word>
<Word>__nogc</Word>
<Word>__pin</Word>
<Word>__property</Word>
<Word>__sealed</Word>
<Word>__try_cast</Word>
<Word>__typeof</Word>
<Word>__value</Word>
<Word>__event</Word>
<Word>__hook</Word>
<Word>__raise</Word>
<Word>__unhook</Word>
<Word>__interface</Word>
<Word>ref class</Word>
<Word>ref struct</Word>
<Word>value class</Word>
<Word>value struct</Word>
<Word>interface class</Word>
<Word>interface struct</Word>
<Word>enum class</Word>
<Word>enum struct</Word>
<Word>delegate</Word>
<Word>event</Word>
<Word>property</Word>
<Word>abstract</Word>
<Word>override</Word>
<Word>sealed</Word>
<Word>generic</Word>
<Word>where</Word>
<Word>finally</Word>
<Word>for each</Word>
<Word>gcnew</Word>
<Word>in</Word>
<Word>initonly</Word>
<Word>literal</Word>
<Word>nullptr</Word>
</Keywords>
<Keywords color="This">
<Word>this</Word>
</Keywords>
<Keywords color="Operators">
<Word>and</Word>
<Word>and_eq</Word>
<Word>bitand</Word>
<Word>bitor</Word>
<Word>new</Word>
<Word>not</Word>
<Word>not_eq</Word>
<Word>or</Word>
<Word>or_eq</Word>
<Word>xor</Word>
<Word>xor_eq</Word>
</Keywords>
<Keywords color="Namespace">
<Word>using</Word>
<Word>namespace</Word>
</Keywords>
<Keywords color="Friend">
<Word>friend</Word>
</Keywords>
<Keywords color="Modifiers">
<Word>private</Word>
<Word>protected</Word>
<Word>public</Word>
<Word>const</Word>
<Word>volatile</Word>
<Word>static</Word>
</Keywords>
<Keywords color="TypeKeywords">
<Word>bool</Word>
<Word>char</Word>
<Word>unsigned</Word>
<Word>union</Word>
<Word>virtual</Word>
<Word>double</Word>
<Word>float</Word>
<Word>short</Word>
<Word>signed</Word>
<Word>void</Word>
<Word>class</Word>
<Word>enum</Word>
<Word>struct</Word>
</Keywords>
<Keywords color="BooleanConstants">
<Word>false</Word>
<Word>true</Word>
</Keywords>
<Keywords color="LoopKeywords">
<Word>do</Word>
<Word>for</Word>
<Word>while</Word>
</Keywords>
<Keywords color="JumpKeywords">
<Word>break</Word>
<Word>continue</Word>
<Word>goto</Word>
<Word>return</Word>
</Keywords>
<Keywords color="ExceptionHandling">
<Word>catch</Word>
<Word>throw</Word>
<Word>try</Word>
</Keywords>
<Keywords color="ControlFlow">
<Word>case</Word>
<Word>else</Word>
<Word>if</Word>
<Word>switch</Word>
<Word>default</Word>
</Keywords>
<Keywords color="Keywords">
<Word>asm</Word>
<Word>auto</Word>
<Word>compl</Word>
<Word>mutable</Word>
<Word>const_cast</Word>
<Word>delete</Word>
<Word>dynamic_cast</Word>
<Word>explicit</Word>
<Word>export</Word>
<Word>extern</Word>
<Word>inline</Word>
<Word>int</Word>
<Word>long</Word>
<Word>operator</Word>
<Word>register</Word>
<Word>reinterpret_cast</Word>
<Word>sizeof</Word>
<Word>static_cast</Word>
<Word>template</Word>
<Word>typedef</Word>
<Word>typeid</Word>
<Word>typename</Word>
</Keywords>
<Span color="Preprocessor">
<Begin>\#</Begin>
</Span>
<Span color="Comment">
<Begin>//</Begin>
</Span>
<Span color="Comment" multiline="true">
<Begin>/\*</Begin>
<End>\*/</End>
</Span>
<Span color="String">
<Begin>"</Begin>
<End>"</End>
<RuleSet>
<Span begin="\\" end="." />
</RuleSet>
</Span>
<Span color="Character">
<Begin>'</Begin>
<End>'</End>
<RuleSet>
<Span begin="\\" end="." />
</RuleSet>
</Span>
<Rule color="MethodName">[\d\w_]+(?=(\s*\())</Rule>
<Rule color="Digits">\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?</Rule>
</RuleSet>
</SyntaxDefinition>

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

BIN
FModel/Resources/FModel.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
FModel/Resources/Flat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

61
FModel/Resources/Ini.xshd Normal file
View File

@ -0,0 +1,61 @@
<SyntaxDefinition name="Ini" extensions=".cfg;.conf;.ini;.iss;"
xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Bool" foreground="#61AFEF"/>
<Color name="Digits" foreground="#F78C6C"/>
<Color name="Comment" foreground="SeaGreen"/>
<Color name="Punctuation" foreground="#89DDFF"/>
<Color name="String" foreground="#98C379"/>
<Color name="Section" foreground="#7F848E"/>
<Color name="PropertyName" foreground="#FFCB6B"/>
<Color name="Plum" foreground="#DDA0DD" />
<RuleSet name="String">
<Span begin="\\" end="."/>
</RuleSet>
<RuleSet ignoreCase="true">
<Span color="String" multiline="false" ruleSet="String">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="String" multiline="false" ruleSet="String">
<Begin>"</Begin>
<End>"</End>
</Span>
<Span color="Comment" multiline="false">
<Begin>;</Begin>
</Span>
<Span color="Comment" multiline="false">
<Begin>\#</Begin>
</Span>
<Span color="Section" multiline="false">
<Begin>\[</Begin>
<End>\]</End>
</Span>
<Rule color="Punctuation">
[?,;\(\)\[\]{}\+\=\-/%&lt;&gt;^+~!|&amp;]+
</Rule>
<Rule color="Digits">
\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?f?
</Rule>
<Keywords color="Bool" >
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Rule color="PropertyName">
(\=|\))?(\+|-|!|,)?[A-Za-z0-9_. ]+?(?==)
</Rule>
<Rule color="Plum">
(?!=(%|\/))+.*(%|\/)+.+
</Rule>
</RuleSet>
</SyntaxDefinition>

View File

@ -0,0 +1,71 @@
<SyntaxDefinition name="Json" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"
extensions=".json">
<Color name="Bool" foreground="#61AFEF" />
<Color name="Number" foreground="#F78C6C" />
<Color name="String" foreground="#C3E88D" />
<Color name="Null" foreground="#7F848E" />
<Color name="FieldName" foreground="#FFCB6B" />
<Color name="Punctuation" foreground="#89DDFF" />
<RuleSet name="String">
<Span begin="\\" end="."/>
</RuleSet>
<RuleSet name="Object">
<Span color="FieldName" ruleSet="String">
<Begin>"</Begin>
<End>"</End>
</Span>
<Span color="FieldName" ruleSet="String">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="Punctuation" ruleSet="Expression">
<Begin>:</Begin>
</Span>
<Span color="Punctuation">
<Begin>,</Begin>
</Span>
</RuleSet>
<RuleSet name="Array">
<Import ruleSet="Expression"/>
<Span color="Punctuation">
<Begin>,</Begin>
</Span>
</RuleSet>
<RuleSet name="Expression">
<Keywords color="Bool" >
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Keywords color="Null" >
<Word>null</Word>
</Keywords>
<Span color="String" ruleSet="String">
<Begin>"</Begin>
<End>"</End>
</Span>
<Span color="String" ruleSet="String">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="Punctuation" ruleSet="Object" multiline="true">
<Begin>\{</Begin>
<End>\}</End>
</Span>
<Span color="Punctuation" ruleSet="Array" multiline="true">
<Begin>\[</Begin>
<End>\]</End>
</Span>
<Rule color="Number">
\b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?
</Rule>
</RuleSet>
<RuleSet>
<Import ruleSet="Expression"/>
</RuleSet>
</SyntaxDefinition>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Some files were not shown because too many files have changed in this diff Show More