feat: Make Stride.StorageTool as Gui Tool (#2479)

* feat: Make Stride.StorageTool as Gui Tool

* fixup! feat: Make Stride.StorageTool as Gui Tool

* fixup! feat: Make Stride.StorageTool as Gui Tool

* Update README.md

---------

Co-authored-by: Nicolas Musset <musset.nicolas@gmail.com>
This commit is contained in:
Jakub Ławreszuk 2024-10-08 17:44:05 +02:00 коммит произвёл GitHub
Родитель e890e27901
Коммит 2af6925a59
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
16 изменённых файлов: 399 добавлений и 410 удалений

Просмотреть файл

@ -107,11 +107,16 @@
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
</ItemGroup>
<!-- Avalonia dependencies -->
<PropertyGroup>
<AvaloniaVersion>11.0.6</AvaloniaVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.6" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.0.6" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.0.6" />
<PackageVersion Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
</ItemGroup>
<!-- Windows/WPF dependencies -->

1
sources/tools/Stride.StorageTool/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
nupkg/*

Просмотреть файл

@ -0,0 +1,11 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Stride.StorageTool.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
</Application>

Просмотреть файл

@ -0,0 +1,24 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Stride.StorageTool;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var bundlePath = desktop.Args?.Length > 0 ? desktop.Args[0] : null;
desktop.MainWindow = new MainWindow(bundlePath);
}
base.OnFrameworkInitializationCompleted();
}
}

Просмотреть файл

@ -0,0 +1,33 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Stride.StorageTool.MainWindow"
Title="Stride.StorageTool">
<Grid RowDefinitions="Auto,*">
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_Open bundle (Ctrl+O)" HotKey="Ctrl+O" Click="OpenBundle" />
<Separator/>
<MenuItem Header="_Exit (Ctrl+Q)" HotKey="Ctrl+Q" Click="Exit" />
</MenuItem>
</Menu>
<StackPanel VerticalAlignment="Center" Grid.Row="1" Name="WelcomePanel">
<TextBlock
FontSize="32"
FontWeight="SemiBold"
HorizontalAlignment="Center"
Text="Stride StorageTool" />
<TextBlock
FontSize="28"
TextAlignment="Center"
HorizontalAlignment="Center"
Text="Choose File > Open Bundle to open the .bundle file"/>
</StackPanel>
<DataGrid Grid.Row="1" Margin="20" Name="ObjectDataGrid" IsReadOnly="True"
GridLinesVisibility="All"
AutoGenerateColumns="True"
BorderThickness="1" BorderBrush="Gray"/>
</Grid>
</Window>

Просмотреть файл

@ -0,0 +1,78 @@
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Stride.Core.Storage;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Stride.StorageTool;
public partial class MainWindow : Window
{
public MainWindow(string? bundlePath)
{
InitializeComponent();
ObjectDataGrid.IsVisible = false;
WelcomePanel.IsVisible = true;
if (File.Exists(bundlePath))
{
using FileStream fs = File.OpenRead(bundlePath);
LoadObjectEntries(fs);
}
}
private void Exit(object sender, RoutedEventArgs e)
{
Environment.Exit(0);
}
private async void OpenBundle(object sender, RoutedEventArgs e)
{
var bundleFiles = await this.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
{
FileTypeFilter =
[
new("Bundle package") { Patterns = ["*.bundle"], }
],
Title = "Choose .bundle package",
AllowMultiple = false,
});
if(bundleFiles.Count == 0)
return;
var bundleFile = bundleFiles[0];
var bundleStream = await bundleFile.OpenReadAsync();
LoadObjectEntries(bundleStream);
}
private void LoadObjectEntries(Stream bundleStream)
{
BundleDescription bundle = BundleOdbBackend.ReadBundleDescription(bundleStream);
var objectInfos = bundle.Objects.ToDictionary(x => x.Key, x => x.Value);
var entries = new List<ObjectEntry>();
foreach (var locationIds in bundle.Assets)
{
var entry = new ObjectEntry { Location = locationIds.Key, Id = locationIds.Value.ToString() };
if (objectInfos.TryGetValue(locationIds.Value, out var objectInfo))
{
entry.Size = objectInfo.EndOffset - objectInfo.StartOffset;
entry.SizeNotCompressed = objectInfo.SizeNotCompressed;
}
entries.Add(entry);
}
ObjectDataGrid.IsVisible = true;
WelcomePanel.IsVisible = false;
ObjectDataGrid.ItemsSource = entries;
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Stride.Core;
using Stride.Core.IO;
using Stride.Core.Storage;
namespace Stride.StorageTool;
/// <summary>
/// Description of an object entry in the bundle.
/// </summary>
public class ObjectEntry
{
public string Location { get; set; }
public string Id { get; set; }
public long Size { get; set; }
public long SizeNotCompressed { get; set; }
}

Просмотреть файл

@ -1,107 +1,21 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Avalonia;
using System;
using System.IO;
using System.Reflection;
using System.Security.Principal;
using Microsoft.Win32;
using Mono.Options;
namespace Stride.StorageTool
namespace Stride.StorageTool;
class Program
{
/// <summary>
/// Tool to manage storage/bundles.
/// </summary>
class Program
{
static void Main(string[] args)
{ var exeName = Path.GetFileName(Assembly.GetExecutingAssembly().Location);
var showHelp = false;
int exitCode = 0;
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
var p = new OptionSet
{
"Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) All Rights Reserved",
"Storage Tool - Version: "
+
String.Format(
"{0}.{1}.{2}",
typeof(Program).Assembly.GetName().Version.Major,
typeof(Program).Assembly.GetName().Version.Minor,
typeof(Program).Assembly.GetName().Version.Build) + string.Empty,
string.Format("Usage: {0} command [options]*", exeName),
string.Empty,
"=== command ===",
string.Empty,
"view [bundleFile]",
"register",
"=== Options ===",
string.Empty,
{ "h|help", "Show this message and exit", v => showHelp = v != null },
};
try
{
var commandArgs = p.Parse(args);
if (showHelp)
{
p.WriteOptionDescriptions(Console.Out);
Environment.Exit(0);
}
if (commandArgs.Count == 0)
throw new OptionException("Expecting a command", "");
var command = commandArgs[0];
switch (command)
{
case "view":
if (commandArgs.Count != 2)
{
throw new OptionException("View command expecting a path to bundle file","");
}
StorageToolApp.View(commandArgs[1]);
break;
case "register":
//[HKEY_CURRENT_USER\Software\Classes\.bundle]
//@="bundlefile"
//[HKEY_CURRENT_USER\Software\Classes\bundlefile]
//@="Stride Bundle file Extension"
//[HKEY_CURRENT_USER\Software\Classes\bundlefile\shell\View\command]
//@="StorageTool.exe %1"
var classesKey = Registry.CurrentUser.OpenSubKey("Software\\Classes", RegistryKeyPermissionCheck.ReadWriteSubTree);
var bundleKey = classesKey.CreateSubKey(".bundle");
bundleKey.SetValue(null, "bundlefile");
var bundlefileKey = classesKey.CreateSubKey("bundlefile");
bundlefileKey.SetValue(null, "Stride Bundle file Extension");
var commandKey = bundlefileKey.CreateSubKey("shell").CreateSubKey("View").CreateSubKey("command");
commandKey.SetValue(null, Assembly.GetExecutingAssembly().Location + " view %1");
break;
default:
throw new OptionException(string.Format("Invalid command [{0}]", command), "");
}
}
catch (Exception e)
{
LogError("{0}: {1}", exeName, e is OptionException || e is StorageAppException ? e.Message : e.ToString());
if (e is OptionException)
p.WriteOptionDescriptions(Console.Out);
}
Environment.Exit(exitCode);
}
public static void LogError(string message, params object[] args)
{
var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message, args);
Console.ForegroundColor = color;
}
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

Просмотреть файл

@ -1,28 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("65f48abe-ccce-4321-bdc1-f2202cea8656")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,22 @@
# Stride.StorageTool
This is simple Stride Asset Bundle previewer.
Please, read the article in the documentation to learn more : https://doc.stride3d.net/latest/en/manual/engine/assets/asset-bundles.html
## Installation
To install globally on your system use the `install-tool.ps1` powershell script. Script should install and associate
`stride-bundle` command with .bundle extension.
1. Step: Type `powershell.exe` and run it with administrator privilages.
> [!NOTE]
> Are you running `powershell` first time ?
> Make sure to enable execution of your scripts first.
> Type following command:
> ```ps1
> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
> ```
2. Step: Drag and drop `install-tool.ps1`
Done!

Просмотреть файл

@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
namespace Stride.StorageTool
{
/// <summary>
/// Class StorageAppException.
/// </summary>
public class StorageAppException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="StorageAppException" /> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public StorageAppException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StorageAppException" /> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public StorageAppException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

Просмотреть файл

@ -1,95 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Stride.Core;
using Stride.Core.IO;
using Stride.Core.Storage;
namespace Stride.StorageTool
{
/// <summary>
/// Description of an object entry in the bundle.
/// </summary>
public class ObjectEntry
{
public string Location { get; set; }
public ObjectId Id { get; set; }
public long Size { get; set; }
public long SizeNotCompressed { get; set; }
public override string ToString()
{
return string.Format("{0}\t{1}\t{2}\t{3}", Location, Id, Size, SizeNotCompressed);
}
}
/// <summary>
/// Utility class to manipulate storage and bundles.
/// </summary>
public class StorageToolApp
{
/// <summary>
/// List the specified bundle to a txt file and opens it.
/// </summary>
/// <param name="bundlePath">The bundle path.</param>
public static void View(string bundlePath)
{
var entries = GetBundleListing(bundlePath);
var dumpFilePath = bundlePath + ".txt";
var text = new StringBuilder();
foreach (var entry in entries)
{
text.AppendLine(entry.ToString());
}
File.WriteAllText(dumpFilePath, text.ToString());
System.Diagnostics.Process.Start(dumpFilePath);
}
/// <summary>
/// Gets the listing from a bundle.
/// </summary>
/// <param name="bundlePath">The bundle path.</param>
/// <returns>System.Collections.Generic.List&lt;Stride.StorageTool.ObjectEntry&gt;.</returns>
private static List<ObjectEntry> GetBundleListing(string bundlePath)
{
if (bundlePath == null) throw new ArgumentNullException("bundlePath");
if (Path.GetExtension(bundlePath) != BundleOdbBackend.BundleExtension) throw new StorageAppException("Invalid bundle file [{0}] not having extension [{1}]".ToFormat(bundlePath, BundleOdbBackend.BundleExtension));
if (!File.Exists(bundlePath)) throw new StorageAppException("Bundle file [{0}] not found".ToFormat(bundlePath));
BundleDescription bundle;
using (var stream = File.OpenRead(bundlePath))
{
bundle = BundleOdbBackend.ReadBundleDescription(stream);
}
var objectInfos = bundle.Objects.ToDictionary(x => x.Key, x => x.Value);
var entries = new List<ObjectEntry>();
foreach (var locationIds in bundle.Assets)
{
var entry = new ObjectEntry { Location = locationIds.Key, Id = locationIds.Value };
BundleOdbBackend.ObjectInfo objectInfo;
if (objectInfos.TryGetValue(entry.Id, out objectInfo))
{
entry.Size = objectInfo.EndOffset - objectInfo.StartOffset;
entry.SizeNotCompressed = objectInfo.SizeNotCompressed;
}
entries.Add(entry);
}
return entries;
}
}
}

Просмотреть файл

@ -1,7 +0,0 @@
REGEDIT4
[HKEY_CLASSES_ROOT\.bundle]
@="bundlefile"
[HKEY_CLASSES_ROOT\bundlefile]
@="Stride Bundle file Extension"
[HKEY_CLASSES_ROOT\bundlefile\shell\View\command]
@="RegSrv32.EXE %1"

Просмотреть файл

@ -1,20 +1,25 @@
<Project>
<Import Project="..\..\targets\Stride.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(StrideEditorTargetFramework)</TargetFramework>
<StrideBuildTags>WindowsTools</StrideBuildTags>
<StrideCompilerTargetsEnable Condition="'$(StridePackageBuild)' == 'true'">false</StrideCompilerTargetsEnable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Prefer32Bit>false</Prefer32Bit>
<OutputType>WinExe</OutputType>
<TargetFramework>$(StrideXplatEditorTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<PackAsTool>true</PackAsTool>
<ToolCommandName>stride-bundle</ToolCommandName>
<OutputPath>./nupkg</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" />
<PackageReference Include="Mono.Options" />
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Desktop" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.Fonts.Inter" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Stride.Core.Serialization\Stride.Core.Serialization.csproj" />

Просмотреть файл

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Stride.StorageTool.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

Просмотреть файл

@ -0,0 +1,11 @@
dotnet pack;
dotnet tool install -g --add-source ./nupkg stride.storagetool;
# Associate file with our program for Windows
if( $ENV:OS -eq 'Windows_NT')
{
cmd /c assoc .bundle=bundlefile;
$path = """"+ (get-command stride-bundle).Path+"""";
cmd /c ftype bundlefile=$path """%1""";
}