tools/installer: migrate TR1X installer
This migrates the TR1X installer to use the new common library.
6
justfile
|
@ -55,11 +55,11 @@ tr1-build-win-installer: (tr1-image-win-installer "0") (_docker_run "r
|
|||
tr1-package-linux target='release': (tr1-build-linux target) (_docker_run "rrdash/tr1x-linux" "package")
|
||||
tr1-package-win target='release': (tr1-build-win target) (_docker_run "rrdash/tr1x" "package")
|
||||
tr1-package-win-all target='release': (tr1-build-win target) (tr1-build-win-config) (_docker_run "rrdash/tr1x" "package")
|
||||
tr1-package-win-installer target='release': (tr1-build-win target) (tr1-build-win-config) (_docker_run "rrdash/tr1x" "package" "-o" "tools/tr1/installer/Installer/Resources/release.zip") (tr1-build-win-installer)
|
||||
tr1-package-win-installer target='release': (tr1-build-win target) (tr1-build-win-config) (_docker_run "rrdash/tr1x" "package" "-o" "tools/installer/TR1X_Installer/Resources/release.zip") (tr1-build-win-installer)
|
||||
#!/bin/sh
|
||||
git checkout "tools/tr1/installer/Installer/Resources/release.zip"
|
||||
git checkout "tools/installer/TR1X_Installer/Resources/release.zip"
|
||||
exe_name=TR1X-$(tools/get_version 1)-Installer.exe
|
||||
cp tools/tr1/installer/out/TR1X_Installer.exe "${exe_name}"
|
||||
cp tools/installer/out/TR1X_Installer.exe "${exe_name}"
|
||||
echo "Created ${exe_name}"
|
||||
|
||||
tr2-image-linux force="1": (_docker_build "tools/tr2/docker/game-linux/Dockerfile" "rrdash/tr2x-linux" force)
|
||||
|
|
4
tools/installer/TR1X_Installer/App.xaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<Application
|
||||
x:Class="TR1X_Installer.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"/>
|
22
tools/installer/TR1X_Installer/App.xaml.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Windows;
|
||||
using TR1X_Installer.Installers;
|
||||
using TRX_InstallerLib.Controls;
|
||||
using TRX_InstallerLib.Installers;
|
||||
|
||||
namespace TR1X_Installer;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
Current.MainWindow = new TRXInstallWindow(new List<IInstallSource>
|
||||
{
|
||||
new SteamInstallSource(),
|
||||
new GOGInstallSource(),
|
||||
new TombATIInstallSource(),
|
||||
new CDRomInstallSource(),
|
||||
new TR1XInstallSource(),
|
||||
});
|
||||
Current.MainWindow.Show();
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using TRX_InstallerLib.Installers;
|
||||
using TRX_InstallerLib.Utils;
|
||||
|
||||
namespace Installer.Installers;
|
||||
namespace TR1X_Installer.Installers;
|
||||
|
||||
public class CDRomInstallSource : BaseInstallSource
|
||||
{
|
||||
|
@ -47,7 +46,7 @@ public class CDRomInstallSource : BaseInstallSource
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool IsDownloadingUnfinishedBusinessNeeded(string sourceDirectory)
|
||||
public override bool IsDownloadingExpansionNeeded(string sourceDirectory)
|
||||
{
|
||||
return true;
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
using DiscUtils.Iso9660;
|
||||
using DiscUtils.Streams;
|
||||
using Installer.Utils;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using TRX_InstallerLib.Installers;
|
||||
using TRX_InstallerLib.Models;
|
||||
using TRX_InstallerLib.Utils;
|
||||
|
||||
namespace Installer.Installers;
|
||||
namespace TR1X_Installer.Installers;
|
||||
|
||||
public class GOGInstallSource : BaseInstallSource
|
||||
{
|
||||
|
@ -50,7 +48,7 @@ public class GOGInstallSource : BaseInstallSource
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"Could not read CUE {cuePath}:\n{e.Message}");
|
||||
throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_cue_failure"], cuePath, e.Message));
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -60,7 +58,7 @@ public class GOGInstallSource : BaseInstallSource
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"Could not convert BIN to ISO: {e.Message}");
|
||||
throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_converting_bin_failure"], e.Message));
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -73,14 +71,14 @@ public class GOGInstallSource : BaseInstallSource
|
|||
{
|
||||
MaximumValue = 1,
|
||||
CurrentValue = 0,
|
||||
Description = "Scanning the source directory",
|
||||
Description = Language.Instance.Controls!["progress_scanning_source"],
|
||||
});
|
||||
var filesToExtract = GetFilesToExtract(reader.Root);
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
MaximumValue = filesToExtract.Count(),
|
||||
CurrentValue = 0,
|
||||
Description = "Preparing to extract the ISO",
|
||||
Description = Language.Instance.Controls!["progress_preparing_extract"],
|
||||
});
|
||||
foreach (var path in filesToExtract)
|
||||
{
|
||||
|
@ -90,7 +88,7 @@ public class GOGInstallSource : BaseInstallSource
|
|||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
|
||||
|
||||
using SparseStream sourceStream = reader.OpenFile(path, FileMode.Open, FileAccess.Read);
|
||||
var readAllByte = new Byte[sourceStream.Length];
|
||||
var readAllByte = new byte[sourceStream.Length];
|
||||
sourceStream.Read(readAllByte, 0, readAllByte.Length);
|
||||
|
||||
using FileStream targetStream = new(targetPath, FileMode.Create);
|
||||
|
@ -102,13 +100,13 @@ public class GOGInstallSource : BaseInstallSource
|
|||
{
|
||||
MaximumValue = filesToExtract.Count(),
|
||||
CurrentValue = ++currentProgress,
|
||||
Description = $"Extracting {path}",
|
||||
Description = string.Format(Language.Instance.Controls!["progress_extracting"], path)
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"Could not open converted ISO: {e.Message}");
|
||||
throw new ApplicationException(string.Format(Language.Instance.Controls!["progress_converting_iso_failure"], e.Message));
|
||||
}
|
||||
|
||||
File.Delete(isoPath);
|
||||
|
@ -121,7 +119,7 @@ public class GOGInstallSource : BaseInstallSource
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool IsDownloadingUnfinishedBusinessNeeded(string sourceDirectory)
|
||||
public override bool IsDownloadingExpansionNeeded(string sourceDirectory)
|
||||
{
|
||||
return true;
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
using Microsoft.Win32;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Installer.Installers;
|
||||
namespace TR1X_Installer.Installers;
|
||||
|
||||
public class SteamInstallSource : GOGInstallSource
|
||||
{
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using TRX_InstallerLib.Installers;
|
||||
using TRX_InstallerLib.Utils;
|
||||
|
||||
namespace Installer.Installers;
|
||||
namespace TR1X_Installer.Installers;
|
||||
|
||||
public class TR1XInstallSource : BaseInstallSource
|
||||
{
|
||||
|
@ -27,10 +26,7 @@ public class TR1XInstallSource : BaseInstallSource
|
|||
|
||||
public override string SuggestedInstallationDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return InstallUtils.GetPreviousInstallationPath() ?? base.SuggestedInstallationDirectory;
|
||||
}
|
||||
get => InstallUtils.GetPreviousInstallationPath() ?? base.SuggestedInstallationDirectory;
|
||||
}
|
||||
|
||||
public override bool IsImportingSavesSupported => true;
|
||||
|
@ -57,7 +53,7 @@ public class TR1XInstallSource : BaseInstallSource
|
|||
return !Directory.Exists(Path.Combine(sourceDirectory, "music"));
|
||||
}
|
||||
|
||||
public override bool IsDownloadingUnfinishedBusinessNeeded(string sourceDirectory)
|
||||
public override bool IsDownloadingExpansionNeeded(string sourceDirectory)
|
||||
{
|
||||
return !File.Exists(Path.Combine(sourceDirectory, "data", "cat.phd"));
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using TRX_InstallerLib.Installers;
|
||||
using TRX_InstallerLib.Utils;
|
||||
|
||||
namespace Installer.Installers;
|
||||
namespace TR1X_Installer.Installers;
|
||||
|
||||
public class TombATIInstallSource : BaseInstallSource
|
||||
{
|
||||
|
@ -48,7 +47,7 @@ public class TombATIInstallSource : BaseInstallSource
|
|||
return !Directory.Exists(Path.Combine(sourceDirectory, "music"));
|
||||
}
|
||||
|
||||
public override bool IsDownloadingUnfinishedBusinessNeeded(string sourceDirectory)
|
||||
public override bool IsDownloadingExpansionNeeded(string sourceDirectory)
|
||||
{
|
||||
return !File.Exists(Path.Combine(sourceDirectory, "data", "cat.phd"));
|
||||
}
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
12
tools/installer/TR1X_Installer/Resources/Lang/en.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Controls": {
|
||||
"window_title_main": "TR1X Installer",
|
||||
"step_source_content": "TR1X requires original game files to run.\nPlease choose the source location where to install the data files from.\nIf you're upgrading an existing installation, please choose TR1X.",
|
||||
"step_settings_music_content": "Neither the Steam nor GOG versions of the game ship with the full soundtrack found on the PlayStation or Saturn retail releases. This option lets you download the missing tracks automatically (164 MB). The legality of these files is disputable; the most legal way to import the music to PC is to rip the audio tracks yourself from a physical PlayStation or Saturn disc.",
|
||||
"step_settings_expansion_heading": "Download Unfinished Business expansion pack",
|
||||
"step_settings_expansion_content": "The Unfinished Business expansion pack was made freeware. However, the Steam and GOG versions do not ship it. This option lets you download the expansion files automatically (6 MB).",
|
||||
"step_settings_expansion_music": "Fan-made edition (includes music triggers)",
|
||||
"step_settings_expansion_vanilla": "Original edition (does not include music triggers)",
|
||||
"step_settings_saves_content": "Imports existing savegame files. Only TombATI and TR1X savegame format is supported at this time."
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
4
tools/installer/TR1X_Installer/Resources/const.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"Game": "TR1X",
|
||||
"AllowExpansionTypeSelection": true
|
||||
}
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
56
tools/installer/TR1X_Installer/TR1X_Installer.csproj
Normal file
|
@ -0,0 +1,56 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<AssemblyName>TR1X_Installer</AssemblyName>
|
||||
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<EnableCompressionInSingleFile>false</EnableCompressionInSingleFile>
|
||||
<SelfContained>false</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
|
||||
<ProjectReference Include="..\TRX_InstallerLib\TRX_InstallerLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\const.json" />
|
||||
<None Remove="Resources\icon.ico" />
|
||||
<None Remove="Resources\Lang\en.json" />
|
||||
<None Remove="Resources\release.zip" />
|
||||
<None Remove="Resources\side1.jpg" />
|
||||
<None Remove="Resources\side2.jpg" />
|
||||
<None Remove="Resources\side3.jpg" />
|
||||
<None Remove="Resources\side4.jpg" />
|
||||
<None Remove="Resources\TombATI.png" />
|
||||
<None Remove="Resources\TR1X.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\CDRom.png" />
|
||||
<Resource Include="Resources\GOG.png" />
|
||||
<Resource Include="Resources\icon.ico" />
|
||||
<Resource Include="Resources\side1.jpg" />
|
||||
<Resource Include="Resources\side2.jpg" />
|
||||
<Resource Include="Resources\side3.jpg" />
|
||||
<Resource Include="Resources\side4.jpg" />
|
||||
<Resource Include="Resources\Steam.png" />
|
||||
<Resource Include="Resources\TombATI.png" />
|
||||
<Resource Include="Resources\TR1X.png" />
|
||||
<EmbeddedResource Include="Resources\const.json" />
|
||||
<EmbeddedResource Include="Resources\Lang\en.json" />
|
||||
<EmbeddedResource Include="Resources\release.zip" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -5,6 +5,8 @@ VisualStudioVersion = 17.11.35219.272
|
|||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TRX_InstallerLib", "TRX_InstallerLib\TRX_InstallerLib.csproj", "{27F08E8C-2910-4682-B8BC-96ED4C1ECE54}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TR1X_Installer", "TR1X_Installer\TR1X_Installer.csproj", "{5B32640D-3997-472F-A1BA-FCE4128E0688}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -15,6 +17,10 @@ Global
|
|||
{27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27F08E8C-2910-4682-B8BC-96ED4C1ECE54}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5B32640D-3997-472F-A1BA-FCE4128E0688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5B32640D-3997-472F-A1BA-FCE4128E0688}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5B32640D-3997-472F-A1BA-FCE4128E0688}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5B32640D-3997-472F-A1BA-FCE4128E0688}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Installer.Models;
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
using TRX_InstallerLib.Models;
|
||||
using WC = System.Windows.Controls;
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
using Installer.Models;
|
||||
using TRX_InstallerLib.Installers;
|
||||
using TRX_InstallerLib.Utils;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Installer.Models;
|
||||
namespace TRX_InstallerLib.Models;
|
||||
|
||||
public class LogEventArgs
|
||||
{
|
||||
|
|
|
@ -6,21 +6,23 @@
|
|||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\const.json" />
|
||||
<None Remove="Resources\Lang\en.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\const.json" />
|
||||
<EmbeddedResource Include="Resources\Lang\en.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\styles.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Resource>
|
||||
<EmbeddedResource Include="Resources\const.json" />
|
||||
<EmbeddedResource Include="Resources\Lang\en.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
set -x
|
||||
set -e
|
||||
|
||||
cd /app/tools/tr1/installer/
|
||||
cd /app/tools/installer/
|
||||
|
||||
export DOTNET_CLI_HOME="/tmp/DOTNET_CLI_HOME"
|
||||
|
||||
shopt -s globstar
|
||||
rm -rf **/bin **/obj **/out/*
|
||||
dotnet restore
|
||||
dotnet publish -c Release -o out
|
||||
dotnet publish TR1X_Installer -c Release -o out
|
||||
|
|
3
tools/tr1/installer/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
bin/
|
||||
obj/
|
||||
out/
|
|
@ -1,8 +0,0 @@
|
|||
<Application x:Class="Installer.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="Controls/MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary />
|
||||
</Application.Resources>
|
||||
</Application>
|
|
@ -1,12 +0,0 @@
|
|||
using System.Windows;
|
||||
|
||||
namespace Installer
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<UserControl
|
||||
x:Class="Installer.Controls.FinishStepControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="clr-namespace:Installer.Models" d:DataContext="{d:DesignInstance Type=models:FinishStep}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}">
|
||||
Step 4: Done
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Margin="0,0,0,12" TextWrapping="Wrap">
|
||||
Installation complete. To configure more advanced features, you can edit the JSON files in the cfg/ directory with a text editor.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Margin="0,0,0,12">
|
||||
Happy raiding :)
|
||||
</TextBlock>
|
||||
|
||||
<CheckBox IsChecked="{Binding FinishSettings.OpenGameDirectory}" Content="Open game directory after closing this window" Margin="0,0,0,12" />
|
||||
<CheckBox IsChecked="{Binding FinishSettings.LaunchGame}" Content="Launch the game after closing this window" Margin="0,0,0,12" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
|
@ -1,11 +0,0 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace Installer.Controls;
|
||||
|
||||
public partial class FinishStepControl : UserControl
|
||||
{
|
||||
public FinishStepControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
<UserControl
|
||||
x:Class="Installer.Controls.InstallSettingsStepControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Installer.Models"
|
||||
xmlns:utils="clr-namespace:Installer.Utils"
|
||||
d:DataContext="{d:DesignInstance Type=models:InstallSettingsStep}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}">
|
||||
Step 2: Installation options
|
||||
</TextBlock>
|
||||
|
||||
<ScrollViewer Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Auto">
|
||||
<ScrollViewer.Template>
|
||||
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||
<Grid x:Name="Grid" Background="{TemplateBinding Background}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollContentPresenter
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
x:Name="PART_ScrollContentPresenter"
|
||||
CanContentScroll="{TemplateBinding CanContentScroll}"
|
||||
CanHorizontallyScroll="False"
|
||||
CanVerticallyScroll="False"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
Content="{TemplateBinding Content}"
|
||||
Margin="{TemplateBinding Padding}" />
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
x:Name="Corner"
|
||||
Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
|
||||
<ScrollBar
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
x:Name="PART_VerticalScrollBar"
|
||||
AutomationProperties.AutomationId="VerticalScrollBar"
|
||||
Cursor="Arrow"
|
||||
Minimum="0"
|
||||
Maximum="{TemplateBinding ScrollableHeight}"
|
||||
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
|
||||
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||
Margin="12,0,0,0" />
|
||||
<ScrollBar
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
x:Name="PART_HorizontalScrollBar"
|
||||
AutomationProperties.AutomationId="HorizontalScrollBar"
|
||||
Orientation="Horizontal"
|
||||
Cursor="Arrow"
|
||||
Minimum="0"
|
||||
Maximum="{TemplateBinding ScrollableWidth}"
|
||||
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
|
||||
ViewportSize="{TemplateBinding ViewportWidth}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ScrollViewer.Template>
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
<CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.DownloadMusic}" IsEnabled="{Binding InstallSettings.IsDownloadingMusicNeeded}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Download music tracks
|
||||
<Run Foreground="ForestGreen" Text="{Binding InstallSettings.IsDownloadingMusicNeeded, Converter={utils:ConditionalMarkupConverter TrueValue='', FalseValue='(already found)'}, Mode=OneWay}" />
|
||||
<LineBreak />
|
||||
<Run Style="{StaticResource small}">
|
||||
Neither the Steam nor GOG versions of the game ship with the
|
||||
full soundtrack found on the PlayStation or Saturn retail
|
||||
releases. This option lets you download the missing tracks
|
||||
automatically (164 MB). The legality of these files is
|
||||
disputable; the most legal way to import the music to PC is to
|
||||
rip the audio tracks yourself from a physical PlayStation or
|
||||
Saturn disc.
|
||||
</Run>
|
||||
</TextBlock>
|
||||
</CheckBox>
|
||||
|
||||
<CheckBox VerticalAlignment="Center" Margin="0,0,0,6" IsChecked="{Binding InstallSettings.DownloadUnfinishedBusiness}" IsEnabled="{Binding InstallSettings.IsDownloadingUnfinishedBusinessNeeded}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel.Resources>
|
||||
<utils:ComparisonConverter x:Key="ComparisonConverter" />
|
||||
</StackPanel.Resources>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,6">
|
||||
Download Unfinished Business expansion pack
|
||||
<Run Foreground="ForestGreen" Text="{Binding InstallSettings.IsDownloadingUnfinishedBusinessNeeded, Converter={utils:ConditionalMarkupConverter TrueValue='', FalseValue='(already found)'}, Mode=OneWay}" />
|
||||
<LineBreak />
|
||||
<Run Style="{StaticResource small}">
|
||||
The Unfinished Business expansion pack was made freeware. However, the Steam and GOG versions do not ship it. This option lets you download the expansion files automatically (6 MB).
|
||||
</Run>
|
||||
</TextBlock>
|
||||
<RadioButton IsEnabled="{Binding InstallSettings.DownloadUnfinishedBusiness}" Style="{StaticResource small}" Margin="0,0,0,6" Content="Fan-made edition (includes music triggers)"
|
||||
IsChecked="{Binding Path=InstallSettings.UnfinishedBusinessType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static models:UBPackType.Music}}"/>
|
||||
<RadioButton IsEnabled="{Binding InstallSettings.DownloadUnfinishedBusiness}" Style="{StaticResource small}" Margin="0,0,0,6" Content="Original edition (does not include music triggers)"
|
||||
IsChecked="{Binding Path=InstallSettings.UnfinishedBusinessType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static models:UBPackType.Vanilla}}"/>
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
|
||||
<CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.ImportSaves}" IsEnabled="{Binding InstallSettings.InstallSource.IsImportingSavesSupported}">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Import saves
|
||||
<LineBreak />
|
||||
<Run Style="{StaticResource small}">
|
||||
Imports existing savegame files. Only TombATI and TR1X savegame format is supported at this time.
|
||||
</Run>
|
||||
</TextBlock>
|
||||
</CheckBox>
|
||||
|
||||
<CheckBox VerticalAlignment="Center" Margin="0,0,0,12" IsChecked="{Binding InstallSettings.CreateDesktopShortcut}">
|
||||
Create desktop shortcut
|
||||
</CheckBox>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" VerticalAlignment="Center" Margin="0,0,12,0" Padding="0" Content="Destination folder:" />
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding InstallSettings.TargetDirectory}" TextTrimming="CharacterEllipsis" />
|
||||
<Button Grid.Column="2" VerticalAlignment="Center" Margin="12,0,0,0" Command="{Binding ChooseLocationCommand}" Content="Change..." />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -1,11 +0,0 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace Installer.Controls;
|
||||
|
||||
public partial class InstallSettingsStepControl : UserControl
|
||||
{
|
||||
public InstallSettingsStepControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
<UserControl
|
||||
x:Class="Installer.Controls.InstallSourceControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Installer.Models"
|
||||
xmlns:utils="clr-namespace:Installer.Utils"
|
||||
d:DataContext="{d:DesignInstance Type=models:InstallSourceViewModel}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<utils:BoolToVisibilityConverter
|
||||
x:Key="BoolToVisibleConverter"
|
||||
FalseValue="Hidden"
|
||||
TrueValue="Visible" />
|
||||
<utils:BoolToVisibilityConverter
|
||||
x:Key="BoolToHiddenConverter"
|
||||
TrueValue="Hidden"
|
||||
FalseValue="Visible" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="48" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Margin="0,0,12,0">
|
||||
<Image Source="{Binding InstallSource.ImageSource}" Height="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualWidth}" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Padding="0" Margin="0,-3,12,3" VerticalAlignment="Top" Style="{StaticResource subHeading}" Text="{Binding InstallSource.SourceName}" Height="16" />
|
||||
<TextBlock Grid.Column="1" Padding="0" Margin="0,-3,0,3" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{StaticResource subHeadingFound}" Visibility="{Binding IsAvailable, Converter={StaticResource BoolToVisibleConverter}}" Text="Found" />
|
||||
<TextBlock Grid.Column="1" Padding="0" Margin="0,-3,0,3" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{StaticResource subHeadingNotFound}" Visibility="{Binding IsAvailable, Converter={StaticResource BoolToHiddenConverter}}" Text="Not found" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Text="{Binding SourceDirectory, TargetNullValue='(no folder selected)'}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="6,0,0,0">
|
||||
<Hyperlink Command="{Binding ChooseLocationCommand}" >(change)</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -1,11 +0,0 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace Installer.Controls;
|
||||
|
||||
public partial class InstallSourceControl : UserControl
|
||||
{
|
||||
public InstallSourceControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<UserControl
|
||||
x:Class="Installer.Controls.InstallStepControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Installer.Models"
|
||||
d:DataContext="{d:DesignInstance Type=models:InstallStep}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Vertical">
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}">
|
||||
Step 3: Installing
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Margin="0,0,0,12" Text="{Binding Description}" />
|
||||
|
||||
<ProgressBar VerticalAlignment="Center" Margin="0,0,0,12" Value="{Binding CurrentProgress}" Maximum="{Binding MaximumProgress}" MinHeight="16" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBox
|
||||
x:Name="logTextBox"
|
||||
Grid.Row="1"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"/>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -1,41 +0,0 @@
|
|||
using Installer.Models;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Installer.Controls;
|
||||
|
||||
public partial class InstallStepControl : UserControl
|
||||
{
|
||||
public InstallStepControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += (object sender, DependencyPropertyChangedEventArgs e) =>
|
||||
{
|
||||
var dataContext = DataContext as InstallStep;
|
||||
if (dataContext is not null)
|
||||
{
|
||||
string? lastMessage = null;
|
||||
dataContext.Logger.LogEvent += (object sender, LogEventArgs e) =>
|
||||
{
|
||||
if (e.Message != lastMessage)
|
||||
{
|
||||
lastMessage = e.Message;
|
||||
AppendMessage(e.Message);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void AppendMessage(string message)
|
||||
{
|
||||
logTextBox.Dispatcher.Invoke(() =>
|
||||
{
|
||||
logTextBox.AppendText(message + Environment.NewLine);
|
||||
logTextBox.Focus();
|
||||
logTextBox.CaretIndex = logTextBox.Text.Length;
|
||||
logTextBox.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
<Window
|
||||
x:Class="Installer.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Installer.Controls"
|
||||
xmlns:models="clr-namespace:Installer.Models"
|
||||
xmlns:utils="clr-namespace:Installer.Utils"
|
||||
d:DataContext="{d:DesignInstance Type=models:MainWindowViewModel}"
|
||||
mc:Ignorable="d"
|
||||
Title="TR1X Installer"
|
||||
MinWidth="480"
|
||||
MinHeight="360"
|
||||
Width="{Binding WindowWidth, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, FallbackValue=640}"
|
||||
Height="500"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<utils:BoolToVisibilityConverter
|
||||
x:Key="BoolToHiddenConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
<utils:BoolToVisibilityConverter
|
||||
x:Key="BoolToVisibleConverter"
|
||||
FalseValue="Collapsed"
|
||||
TrueValue="Visible" />
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Source="{Binding CurrentStep.SidebarImage}"
|
||||
Visibility="{Binding IsSidebarVisible, Converter={StaticResource BoolToVisibleConverter}}" />
|
||||
|
||||
<Border Grid.Row="0" Grid.Column="1">
|
||||
<ContentControl Content="{Binding CurrentStep}" Margin="12,0,12,12">
|
||||
<ContentControl.Resources>
|
||||
<DataTemplate DataType="{x:Type models:SourceStep}">
|
||||
<controls:SourceStepControl />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type models:InstallSettingsStep}">
|
||||
<controls:InstallSettingsStepControl />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type models:InstallStep}">
|
||||
<controls:InstallStepControl />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type models:FinishStep}">
|
||||
<controls:FinishStepControl />
|
||||
</DataTemplate>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}">
|
||||
<Setter Property="Margin" Value="12,0,0,0" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<StackPanel Margin="12" Orientation="Horizontal" Grid.Column="1">
|
||||
<Button
|
||||
Command="{Binding GoToPreviousStepCommand}"
|
||||
Visibility="{Binding IsFinalStep, Converter={StaticResource BoolToHiddenConverter}}"
|
||||
Content="_Back" />
|
||||
<Button
|
||||
Command="{Binding GoToNextStepCommand}"
|
||||
Visibility="{Binding IsFinalStep, Converter={StaticResource BoolToHiddenConverter}}"
|
||||
Content="_Next" />
|
||||
<Button
|
||||
Command="{Binding CloseWindowCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
Content="{Binding IsFinalStep, Converter={utils:ConditionalMarkupConverter TrueValue='_Close', FalseValue='_Cancel'}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -1,13 +0,0 @@
|
|||
using Installer.Models;
|
||||
using System.Windows;
|
||||
|
||||
namespace Installer;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowViewModel();
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
<UserControl
|
||||
x:Class="Installer.Controls.SourceStepControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Installer.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" MinHeight="30" />
|
||||
<RowDefinition Height="Auto" MinHeight="30" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,12" Style="{StaticResource heading}">
|
||||
Step 1: Choose installation source
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" VerticalAlignment="Center" Margin="0,0,0,10">
|
||||
TR1X requires original game files to run.
|
||||
<LineBreak />
|
||||
Please choose the source location where to install the data files from.
|
||||
<LineBreak />
|
||||
If you're upgrading an existing installation, please choose TR1X.
|
||||
</TextBlock>
|
||||
|
||||
<ListView
|
||||
BorderThickness="0"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding InstallationSources}"
|
||||
SelectedItem="{Binding SelectedInstallationSource, Mode=TwoWay}"
|
||||
VerticalContentAlignment="Top"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ScrollViewer.CanContentScroll="False"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ListViewItem}">
|
||||
<Setter Property="Padding" Value="6" />
|
||||
<Setter Property="Margin" Value="0,0,0,6" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:InstallSourceControl />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -1,11 +0,0 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace Installer.Controls;
|
||||
|
||||
public partial class SourceStepControl : UserControl
|
||||
{
|
||||
public SourceStepControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWpf>true</UseWpf>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<AssemblyName>TR1X_Installer</AssemblyName>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<EnableDefaultPageItems>false</EnableDefaultPageItems>
|
||||
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<EnableCompressionInSingleFile>false</EnableCompressionInSingleFile>
|
||||
<SelfContained>false</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="**/*.cs" Exclude="bin/**/*.cs;obj/**/*.cs;**/*.xaml.cs" />
|
||||
<Page Update="**/*.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Compile Include="**/*.xaml.cs" Exclude="bin/**/*.xaml.cs;obj/**/*.xaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="release.zip" />
|
||||
<None Remove="Resources\CDRom.png" />
|
||||
<None Remove="Resources\GOG.png" />
|
||||
<None Remove="Resources\side1.jpg" />
|
||||
<None Remove="Resources\side2.jpg" />
|
||||
<None Remove="Resources\side3.jpg" />
|
||||
<None Remove="Resources\side4.jpg" />
|
||||
<None Remove="Resources\Steam.png" />
|
||||
<None Remove="Resources\Styles.xaml" />
|
||||
<None Remove="Resources\TR1X.png" />
|
||||
<None Remove="Resources\TombATI.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\CDRom.png" />
|
||||
<Resource Include="Resources\side1.jpg" />
|
||||
<Resource Include="Resources\side2.jpg" />
|
||||
<Resource Include="Resources\side3.jpg" />
|
||||
<Resource Include="Resources\side4.jpg" />
|
||||
<Resource Include="Resources\Steam.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\GOG.png" />
|
||||
<EmbeddedResource Include="Resources\release.zip" />
|
||||
<Resource Include="Resources\TR1X.png" />
|
||||
<Resource Include="Resources\TombATI.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Include="Controls\FinishStepControl.xaml" />
|
||||
<Page Include="Controls\InstallSourceControl.xaml" />
|
||||
<Page Include="Controls\InstallStepControl.xaml" />
|
||||
<Page Include="Controls\MainWindow.xaml" />
|
||||
<Page Include="Controls\SourceStepControl.xaml" />
|
||||
<Page Include="Controls\InstallSettingsStepControl.xaml" />
|
||||
<Page Include="Resources\Styles.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<KnownFrameworkReference Update="Microsoft.WindowsDesktop.App" IsWindowsOnly="false" />
|
||||
<KnownFrameworkReference Update="Microsoft.WindowsDesktop.App.WPF" IsWindowsOnly="false" />
|
||||
<KnownFrameworkReference Update="Microsoft.WindowsDesktop.App.WindowsForms" IsWindowsOnly="false" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,43 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Installer.Installers;
|
||||
|
||||
public abstract class BaseInstallSource : IInstallSource
|
||||
{
|
||||
public abstract IEnumerable<string> DirectoriesToTry { get; }
|
||||
|
||||
public virtual string ImageSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"pack://application:,,,/TR1X_Installer;component/Resources/{SourceName}.png";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract bool IsImportingSavesSupported { get; }
|
||||
public abstract string SourceName { get; }
|
||||
|
||||
public virtual string SuggestedInstallationDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "TR1X");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task CopyOriginalGameFiles(
|
||||
string sourceDirectory,
|
||||
string targetDirectory,
|
||||
IProgress<InstallProgress> progress,
|
||||
bool importSaves
|
||||
);
|
||||
|
||||
public abstract bool IsDownloadingMusicNeeded(string sourceDirectory);
|
||||
|
||||
public abstract bool IsDownloadingUnfinishedBusinessNeeded(string sourceDirectory);
|
||||
|
||||
public abstract bool IsGameFound(string sourceDirectory);
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Installer.Installers;
|
||||
|
||||
public interface IInstallSource
|
||||
{
|
||||
public IEnumerable<string> DirectoriesToTry { get; }
|
||||
|
||||
public string ImageSource { get; }
|
||||
|
||||
public string SourceName { get; }
|
||||
|
||||
public string SuggestedInstallationDirectory { get; }
|
||||
|
||||
public Task CopyOriginalGameFiles(
|
||||
string sourceDirectory,
|
||||
string targetDirectory,
|
||||
IProgress<InstallProgress> progress,
|
||||
bool importSaves
|
||||
);
|
||||
|
||||
bool IsDownloadingMusicNeeded(string sourceDirectory);
|
||||
|
||||
bool IsDownloadingUnfinishedBusinessNeeded(string sourceDirectory);
|
||||
|
||||
public bool IsGameFound(string sourceDirectory);
|
||||
|
||||
bool IsImportingSavesSupported { get; }
|
||||
};
|
|
@ -1,106 +0,0 @@
|
|||
using Installer.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Installer.Installers;
|
||||
|
||||
public class InstallExecutor
|
||||
{
|
||||
private static readonly string _resourceBaseURL = "https://lostartefacts.dev/aux/tr1x";
|
||||
|
||||
public InstallExecutor(InstallSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public IInstallSource? InstallSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.InstallSource;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteInstall(IProgress<InstallProgress> progress)
|
||||
{
|
||||
if (_settings.SourceDirectory is null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
if (_settings.TargetDirectory is null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
await CopyOriginalGameFiles(_settings.SourceDirectory, _settings.TargetDirectory, progress);
|
||||
await CopyTR1XFiles(_settings.TargetDirectory, progress);
|
||||
if (_settings.DownloadMusic)
|
||||
{
|
||||
await DownloadMusicFiles(_settings.TargetDirectory, progress);
|
||||
}
|
||||
|
||||
if (_settings.DownloadUnfinishedBusiness)
|
||||
{
|
||||
await DownloadUnfinishedBusinessFiles(_settings.TargetDirectory, _settings.UnfinishedBusinessType, progress);
|
||||
}
|
||||
if (_settings.CreateDesktopShortcut)
|
||||
{
|
||||
CreateDesktopShortcut(_settings.TargetDirectory);
|
||||
}
|
||||
|
||||
progress.Report(new InstallProgress { Description = "Finished", Finished = true });
|
||||
}
|
||||
|
||||
protected async Task CopyOriginalGameFiles(string sourceDirectory, string targetDirectory, IProgress<InstallProgress> progress)
|
||||
{
|
||||
if (_settings.InstallSource is null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
await _settings.InstallSource.CopyOriginalGameFiles(sourceDirectory, targetDirectory, progress, _settings.ImportSaves);
|
||||
}
|
||||
|
||||
protected static async Task CopyTR1XFiles(string targetDirectory, IProgress<InstallProgress> progress)
|
||||
{
|
||||
InstallUtils.StoreInstallationPath(targetDirectory);
|
||||
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
CurrentValue = 0,
|
||||
MaximumValue = 1,
|
||||
Description = "Opening embedded ZIP",
|
||||
});
|
||||
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceName = assembly.GetManifestResourceNames().Where(n => n.EndsWith("release.zip")).First();
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName)
|
||||
?? throw new ApplicationException($"Could not open embedded ZIP.");
|
||||
await InstallUtils.ExtractZip(stream, targetDirectory, progress, overwrite: true);
|
||||
}
|
||||
|
||||
protected static void CreateDesktopShortcut(string targetDirectory)
|
||||
{
|
||||
InstallUtils.CreateDesktopShortcut("TR1X", Path.Combine(targetDirectory, "TR1X.exe"));
|
||||
if (File.Exists(Path.Combine(targetDirectory, "data", "cat.phd")))
|
||||
{
|
||||
InstallUtils.CreateDesktopShortcut("TR1X - UB", Path.Combine(targetDirectory, "TR1X.exe"), new[] { "-gold" });
|
||||
}
|
||||
}
|
||||
|
||||
protected static async Task DownloadMusicFiles(string targetDirectory, IProgress<InstallProgress> progress)
|
||||
{
|
||||
await InstallUtils.DownloadZip($"{_resourceBaseURL}/music.zip", targetDirectory, progress);
|
||||
}
|
||||
|
||||
protected static async Task DownloadUnfinishedBusinessFiles(string targetDirectory, UBPackType type, IProgress<InstallProgress> progress)
|
||||
{
|
||||
await InstallUtils.DownloadZip(
|
||||
$"{_resourceBaseURL}/trub-{type.ToString().ToLower()}.zip",
|
||||
targetDirectory, progress);
|
||||
}
|
||||
|
||||
private readonly InstallSettings _settings;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Installer.Installers;
|
||||
|
||||
public class InstallProgress
|
||||
{
|
||||
public int? CurrentValue { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public bool Finished { get; set; }
|
||||
public int? MaximumValue { get; set; }
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
using Installer.Utils;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Installer.Installers;
|
||||
|
||||
public static class InstallUtils
|
||||
{
|
||||
private static readonly string _legacyStorageKey = @"Software\Tomb1Main";
|
||||
private static readonly string _registryStorageKey = @"Software\TR1X";
|
||||
|
||||
public static async Task CopyDirectoryTree(
|
||||
string sourceDirectory,
|
||||
string targetDirectory,
|
||||
IProgress<InstallProgress> progress,
|
||||
Func<string, bool>? filterCallback = null,
|
||||
Func<string, bool>? overwriteCallback = null
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
progress.Report(new InstallProgress { Description = "Scanning directory" });
|
||||
var files = Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories);
|
||||
var currentProgress = 0;
|
||||
var maximumProgress = files.Length;
|
||||
foreach (var sourcePath in files)
|
||||
{
|
||||
if (filterCallback is not null && !filterCallback(sourcePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var relPath = Path.GetRelativePath(sourceDirectory, sourcePath);
|
||||
var targetPath = Path.Combine(targetDirectory, relPath);
|
||||
var isSamePath = string.Equals(Path.GetFullPath(sourcePath), Path.GetFullPath(targetPath), StringComparison.OrdinalIgnoreCase);
|
||||
if (!File.Exists(targetPath) || (overwriteCallback is not null && overwriteCallback(sourcePath)) && !isSamePath)
|
||||
{
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
CurrentValue = currentProgress,
|
||||
MaximumValue = maximumProgress,
|
||||
Description = $"Copying {relPath}",
|
||||
});
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
|
||||
await Task.Run(() => File.Copy(sourcePath, targetPath, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
CurrentValue = currentProgress,
|
||||
MaximumValue = maximumProgress,
|
||||
Description = $"Copying {relPath} - skipped",
|
||||
});
|
||||
}
|
||||
|
||||
currentProgress++;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"Could not extract ZIP:\n{e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateDesktopShortcut(string name, string targetPath, string[]? args = null)
|
||||
{
|
||||
var shortcutPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), $"{name}.lnk");
|
||||
ShortcutUtils.CreateShortcut(shortcutPath, targetPath, "Tomb Raider I: Community Edition", args);
|
||||
}
|
||||
|
||||
public static async Task<byte[]> DownloadFile(string url, IProgress<InstallProgress> progress)
|
||||
{
|
||||
HttpProgressClient wc = new();
|
||||
progress.Report(new InstallProgress { Description = $"Initializing download of {url}" });
|
||||
wc.DownloadProgressChanged += (totalBytesToReceive, bytesReceived) =>
|
||||
{
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
CurrentValue = (int)bytesReceived,
|
||||
MaximumValue = (int)totalBytesToReceive,
|
||||
Description = $"Downloading {url}",
|
||||
});
|
||||
};
|
||||
return await wc.DownloadDataTaskAsync(new Uri(url));
|
||||
}
|
||||
|
||||
public static async Task DownloadZip(
|
||||
string url,
|
||||
string targetDirectory,
|
||||
IProgress<InstallProgress> progress
|
||||
)
|
||||
{
|
||||
var response = await DownloadFile(url, progress);
|
||||
using var stream = new MemoryStream(response);
|
||||
await ExtractZip(stream, targetDirectory, progress);
|
||||
}
|
||||
|
||||
public static async Task ExtractZip(
|
||||
Stream stream,
|
||||
string targetDirectory,
|
||||
IProgress<InstallProgress> progress,
|
||||
Func<string, bool>? filterCallback = null,
|
||||
bool overwrite = false
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var zip = new ZipArchive(stream);
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
Description = "Scanning embedded ZIP",
|
||||
});
|
||||
var currentProgress = 0;
|
||||
var maximumProgress = zip.Entries.Count;
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
if (new Regex(@"[\\/]$").IsMatch(entry.FullName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (filterCallback is not null && !filterCallback(entry.FullName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var targetPath = Path.Combine(
|
||||
targetDirectory,
|
||||
new Regex(@"[\\/]").Replace(entry.FullName, Path.DirectorySeparatorChar.ToString()));
|
||||
|
||||
if (!File.Exists(targetPath) || overwrite)
|
||||
{
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
CurrentValue = currentProgress,
|
||||
MaximumValue = maximumProgress,
|
||||
Description = $"Extracting {entry.FullName}",
|
||||
});
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
|
||||
await Task.Run(() => entry.ExtractToFile(targetPath, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
CurrentValue = currentProgress,
|
||||
MaximumValue = maximumProgress,
|
||||
Description = $"Extracting {entry.FullName} - skipped",
|
||||
});
|
||||
}
|
||||
|
||||
currentProgress++;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"Could not extract ZIP:\n{e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetDesktopShortcutDirectories()
|
||||
{
|
||||
foreach (
|
||||
var shortcutPath in Directory.EnumerateFiles(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "*.lnk"
|
||||
)
|
||||
)
|
||||
{
|
||||
string? lnkPath;
|
||||
try
|
||||
{
|
||||
lnkPath = ShortcutUtils.GetLnkTargetPath(shortcutPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (lnkPath is not null)
|
||||
{
|
||||
var dirName = Path.GetDirectoryName(lnkPath);
|
||||
if (dirName is not null)
|
||||
{
|
||||
yield return dirName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void StoreInstallationPath(string installPath)
|
||||
{
|
||||
RenameLegacyStorage();
|
||||
using var key = Registry.CurrentUser.CreateSubKey(_registryStorageKey);
|
||||
key?.SetValue("InstallPath", installPath);
|
||||
}
|
||||
|
||||
public static string? GetPreviousInstallationPath()
|
||||
{
|
||||
RenameLegacyStorage();
|
||||
using var key = Registry.CurrentUser.OpenSubKey(_registryStorageKey);
|
||||
return key?.GetValue("InstallPath")?.ToString();
|
||||
}
|
||||
|
||||
private static void RenameLegacyStorage()
|
||||
{
|
||||
// Added in #1411 - to be removed in the future.
|
||||
using var legacyKey = Registry.CurrentUser.OpenSubKey(_legacyStorageKey);
|
||||
if (legacyKey is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var currentKey = Registry.CurrentUser.OpenSubKey(_registryStorageKey);
|
||||
if (currentKey is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var destinationKey = Registry.CurrentUser.CreateSubKey(_registryStorageKey);
|
||||
foreach (string valueName in legacyKey.GetValueNames())
|
||||
{
|
||||
object? objValue = legacyKey.GetValue(valueName);
|
||||
if (objValue is not null)
|
||||
{
|
||||
RegistryValueKind valueKind = legacyKey.GetValueKind(valueName);
|
||||
destinationKey.SetValue(valueName, objValue, valueKind);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.CurrentUser.DeleteSubKey(_legacyStorageKey);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
namespace Installer.Models;
|
||||
|
||||
public class FinishSettings : BaseNotifyPropertyChanged
|
||||
{
|
||||
public bool LaunchGame
|
||||
{
|
||||
get => _launchGame;
|
||||
set
|
||||
{
|
||||
if (value != _launchGame)
|
||||
{
|
||||
_launchGame = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool OpenGameDirectory
|
||||
{
|
||||
get => _openGameDirectory;
|
||||
set
|
||||
{
|
||||
if (value != _openGameDirectory)
|
||||
{
|
||||
_openGameDirectory = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _launchGame = false;
|
||||
private bool _openGameDirectory = true;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
namespace Installer.Models;
|
||||
|
||||
public class FinishStep : BaseNotifyPropertyChanged, IStep
|
||||
{
|
||||
public FinishStep(FinishSettings finishSettings)
|
||||
{
|
||||
FinishSettings = finishSettings;
|
||||
}
|
||||
|
||||
public bool CanProceedToNextStep => false;
|
||||
public bool CanProceedToPreviousStep => false;
|
||||
public FinishSettings FinishSettings { get; }
|
||||
public string SidebarImage => "pack://application:,,,/TR1X_Installer;component/Resources/side4.jpg";
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public interface IStep : INotifyPropertyChanged
|
||||
{
|
||||
bool CanProceedToNextStep { get; }
|
||||
bool CanProceedToPreviousStep { get; }
|
||||
string SidebarImage { get; }
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
using Installer.Installers;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public class InstallSettings : BaseNotifyPropertyChanged
|
||||
{
|
||||
public bool CreateDesktopShortcut
|
||||
{
|
||||
get => _createDesktopShortcut;
|
||||
set
|
||||
{
|
||||
if (value != _createDesktopShortcut)
|
||||
{
|
||||
_createDesktopShortcut = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DownloadMusic
|
||||
{
|
||||
get => _downloadMusic;
|
||||
set
|
||||
{
|
||||
if (value != _downloadMusic)
|
||||
{
|
||||
_downloadMusic = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DownloadUnfinishedBusiness
|
||||
{
|
||||
get => _downloadUnfinishedBusiness;
|
||||
set
|
||||
{
|
||||
if (value != _downloadUnfinishedBusiness)
|
||||
{
|
||||
_downloadUnfinishedBusiness = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UBPackType UnfinishedBusinessType
|
||||
{
|
||||
get => _unfinishedBusinessType;
|
||||
set
|
||||
{
|
||||
if (value != _unfinishedBusinessType)
|
||||
{
|
||||
_unfinishedBusinessType = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ImportSaves
|
||||
{
|
||||
get => _importSaves;
|
||||
set
|
||||
{
|
||||
if (value != _importSaves)
|
||||
{
|
||||
_importSaves = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IInstallSource? InstallSource
|
||||
{
|
||||
get => _installSource;
|
||||
set
|
||||
{
|
||||
if (value != _installSource)
|
||||
{
|
||||
_installSource = value;
|
||||
DownloadMusic = SourceDirectory is not null && (_installSource?.IsDownloadingMusicNeeded(SourceDirectory) ?? false);
|
||||
DownloadUnfinishedBusiness = SourceDirectory is not null && (_installSource?.IsDownloadingUnfinishedBusinessNeeded(SourceDirectory) ?? false);
|
||||
ImportSaves = _installSource?.IsImportingSavesSupported ?? false;
|
||||
TargetDirectory = _installSource?.SuggestedInstallationDirectory;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDownloadingMusicNeeded
|
||||
{
|
||||
get
|
||||
{
|
||||
return SourceDirectory is not null && (InstallSource?.IsDownloadingMusicNeeded(SourceDirectory) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDownloadingUnfinishedBusinessNeeded
|
||||
{
|
||||
get
|
||||
{
|
||||
return SourceDirectory is not null && (InstallSource?.IsDownloadingUnfinishedBusinessNeeded(SourceDirectory) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public string? SourceDirectory
|
||||
{
|
||||
get => _sourceDirectory;
|
||||
set
|
||||
{
|
||||
if (value != _sourceDirectory)
|
||||
{
|
||||
_sourceDirectory = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? TargetDirectory
|
||||
{
|
||||
get => _targetDirectory;
|
||||
set
|
||||
{
|
||||
if (value != _targetDirectory)
|
||||
{
|
||||
_targetDirectory = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _createDesktopShortcut = true;
|
||||
private bool _downloadMusic;
|
||||
private bool _downloadUnfinishedBusiness;
|
||||
private UBPackType _unfinishedBusinessType;
|
||||
private bool _importSaves;
|
||||
private IInstallSource? _installSource;
|
||||
private string? _sourceDirectory;
|
||||
private string? _targetDirectory;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using Installer.Utils;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public class InstallSettingsStep : BaseNotifyPropertyChanged, IStep
|
||||
{
|
||||
public InstallSettingsStep(InstallSettings installSettings)
|
||||
{
|
||||
InstallSettings = installSettings;
|
||||
InstallSettings.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
NotifyPropertyChanged(nameof(CanProceedToNextStep));
|
||||
};
|
||||
}
|
||||
|
||||
public bool CanProceedToNextStep => InstallSettings.TargetDirectory != null;
|
||||
public bool CanProceedToPreviousStep => true;
|
||||
|
||||
public ICommand ChooseLocationCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _chooseLocationCommand ??= new RelayCommand(ChooseLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public InstallSettings InstallSettings { get; }
|
||||
public string SidebarImage => "pack://application:,,,/TR1X_Installer;component/Resources/side2.jpg";
|
||||
private RelayCommand? _chooseLocationCommand;
|
||||
|
||||
private void ChooseLocation()
|
||||
{
|
||||
var result = FileBrowser.Browse(InstallSettings.TargetDirectory);
|
||||
if (result is not null)
|
||||
{
|
||||
InstallSettings.TargetDirectory = result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using Installer.Installers;
|
||||
using Installer.Utils;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public class InstallSourceViewModel : BaseNotifyPropertyChanged
|
||||
{
|
||||
public InstallSourceViewModel(IInstallSource source)
|
||||
{
|
||||
InstallSource = source;
|
||||
|
||||
foreach (var directory in source.DirectoriesToTry)
|
||||
{
|
||||
if (InstallSource.IsGameFound(directory))
|
||||
{
|
||||
SourceDirectory = directory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand ChooseLocationCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _chooseLocationCommand ??= new RelayCommand(ChooseLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public IInstallSource InstallSource { get; private set; }
|
||||
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
return SourceDirectory != null && InstallSource.IsGameFound(SourceDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
public string? SourceDirectory
|
||||
{
|
||||
get => _sourceDirectory;
|
||||
set
|
||||
{
|
||||
if (value != _sourceDirectory)
|
||||
{
|
||||
_sourceDirectory = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(IsAvailable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RelayCommand? _chooseLocationCommand;
|
||||
private string? _sourceDirectory;
|
||||
|
||||
private void ChooseLocation()
|
||||
{
|
||||
var result = FileBrowser.Browse(SourceDirectory);
|
||||
if (result is not null)
|
||||
{
|
||||
SourceDirectory = result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
using Installer.Installers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public class InstallStep : BaseNotifyPropertyChanged, IStep
|
||||
{
|
||||
public InstallStep(InstallSettings installSettings)
|
||||
{
|
||||
Logger = new Logger();
|
||||
InstallSettings = installSettings;
|
||||
}
|
||||
|
||||
public bool CanProceedToNextStep
|
||||
{
|
||||
get => _canProceedToNextStep;
|
||||
set
|
||||
{
|
||||
if (value != _canProceedToNextStep)
|
||||
{
|
||||
_canProceedToNextStep = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanProceedToPreviousStep => false;
|
||||
|
||||
public int CurrentProgress
|
||||
{
|
||||
get { return _currentProgress; }
|
||||
set
|
||||
{
|
||||
if (value != _currentProgress)
|
||||
{
|
||||
_currentProgress = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string? Description
|
||||
{
|
||||
get => _description;
|
||||
set
|
||||
{
|
||||
if (value != _description)
|
||||
{
|
||||
_description = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InstallSettings InstallSettings { get; }
|
||||
public Logger Logger { get; }
|
||||
|
||||
public int MaximumProgress
|
||||
{
|
||||
get { return _maximumProgress; }
|
||||
set
|
||||
{
|
||||
if (value != _maximumProgress)
|
||||
{
|
||||
_maximumProgress = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(CanProceedToNextStep));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SidebarImage => "pack://application:,,,/TR1X_Installer;component/Resources/side3.jpg";
|
||||
|
||||
public void RunInstall()
|
||||
{
|
||||
var progress = new Progress<InstallProgress>();
|
||||
progress.ProgressChanged += (sender, progress) =>
|
||||
{
|
||||
if (progress.CurrentValue is not null && progress.MaximumValue is not null)
|
||||
{
|
||||
CurrentProgress = progress.CurrentValue.Value;
|
||||
MaximumProgress = progress.MaximumValue.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentProgress = progress.Finished ? 1 : 0;
|
||||
MaximumProgress = 1;
|
||||
}
|
||||
Description = progress.Description;
|
||||
if (progress.Description is not null)
|
||||
{
|
||||
Logger.RaiseLogEvent(progress.Description);
|
||||
}
|
||||
if (progress.Finished)
|
||||
{
|
||||
CanProceedToNextStep = true;
|
||||
}
|
||||
};
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var executor = new InstallExecutor(InstallSettings);
|
||||
await executor.ExecuteInstall(progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.RaiseLogEvent(ex.ToString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private bool _canProceedToNextStep;
|
||||
private int _currentProgress = 0;
|
||||
private string? _description;
|
||||
private int _maximumProgress = 1;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
namespace Installer.Models;
|
||||
|
||||
public class LogEventArgs
|
||||
{
|
||||
public LogEventArgs(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Message { get; }
|
||||
}
|
||||
|
||||
public class Logger
|
||||
{
|
||||
public delegate void LogEventHandler(object sender, LogEventArgs e);
|
||||
|
||||
public event LogEventHandler? LogEvent;
|
||||
|
||||
public void RaiseLogEvent(string message)
|
||||
{
|
||||
LogEvent?.Invoke(this, new LogEventArgs(message));
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
using Installer.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public class MainWindowViewModel : BaseNotifyPropertyChanged
|
||||
{
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
_sourceStep = new SourceStep();
|
||||
_currentStep = _sourceStep;
|
||||
_installSettings = new InstallSettings();
|
||||
}
|
||||
|
||||
public ICommand CloseWindowCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _closeWindowCommand ??= new RelayCommand<Window>(CloseWindow);
|
||||
}
|
||||
}
|
||||
|
||||
public IStep CurrentStep
|
||||
{
|
||||
get { return _currentStep; }
|
||||
set
|
||||
{
|
||||
_currentStep = value;
|
||||
_goToPreviousStepCommand?.RaiseCanExecuteChanged();
|
||||
_goToNextStepCommand?.RaiseCanExecuteChanged();
|
||||
_currentStep.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
_goToPreviousStepCommand?.RaiseCanExecuteChanged();
|
||||
_goToNextStepCommand?.RaiseCanExecuteChanged();
|
||||
};
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(IsFinalStep));
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand GoToNextStepCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _goToNextStepCommand ??= new RelayCommand(GoToNextStep, CanGoToNextStep);
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand GoToPreviousStepCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return _goToPreviousStepCommand ??= new RelayCommand(GoToPreviousStep, CanGoToPreviousStep);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFinalStep
|
||||
{
|
||||
get
|
||||
{
|
||||
return CurrentStep is FinishStep;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSidebarVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return WindowWidth >= 500;
|
||||
}
|
||||
}
|
||||
|
||||
public int WindowWidth
|
||||
{
|
||||
get => _windowWidth;
|
||||
set
|
||||
{
|
||||
if (value != _windowWidth)
|
||||
{
|
||||
_windowWidth = value;
|
||||
NotifyPropertyChanged(nameof(IsSidebarVisible));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const bool _autoFinishInstallStep = false;
|
||||
|
||||
private RelayCommand<Window>? _closeWindowCommand;
|
||||
|
||||
private IStep _currentStep;
|
||||
private FinishSettings? _finishSettings;
|
||||
private RelayCommand? _goToNextStepCommand;
|
||||
private RelayCommand? _goToPreviousStepCommand;
|
||||
private readonly InstallSettings _installSettings;
|
||||
private readonly IStep _sourceStep;
|
||||
private int _windowWidth;
|
||||
|
||||
private bool CanGoToNextStep()
|
||||
{
|
||||
return CurrentStep.CanProceedToNextStep;
|
||||
}
|
||||
|
||||
private bool CanGoToPreviousStep()
|
||||
{
|
||||
return CurrentStep.CanProceedToPreviousStep;
|
||||
}
|
||||
|
||||
private void CloseWindow(Window? window)
|
||||
{
|
||||
if (_finishSettings is not null && _finishSettings.LaunchGame)
|
||||
{
|
||||
if (_installSettings.TargetDirectory is null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
Process.Start(Path.Combine(_installSettings.TargetDirectory, "TR1X.exe"));
|
||||
}
|
||||
if (_finishSettings is not null && _finishSettings.OpenGameDirectory)
|
||||
{
|
||||
if (_installSettings.TargetDirectory is null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
Process.Start("explorer.exe", _installSettings.TargetDirectory);
|
||||
}
|
||||
window?.Close();
|
||||
}
|
||||
|
||||
private void GoToNextStep()
|
||||
{
|
||||
if (CurrentStep is SourceStep sourceStep)
|
||||
{
|
||||
var installSource = sourceStep.SelectedInstallationSource!.InstallSource;
|
||||
_installSettings.InstallSource = installSource;
|
||||
_installSettings.SourceDirectory = sourceStep.SelectedInstallationSource.SourceDirectory;
|
||||
CurrentStep = new InstallSettingsStep(_installSettings);
|
||||
}
|
||||
else if (CurrentStep is InstallSettingsStep targetStep)
|
||||
{
|
||||
var installStep = new InstallStep(targetStep.InstallSettings);
|
||||
installStep.RunInstall();
|
||||
installStep.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
if (_autoFinishInstallStep && installStep.CanProceedToNextStep)
|
||||
{
|
||||
_finishSettings = new FinishSettings();
|
||||
CurrentStep = new FinishStep(_finishSettings);
|
||||
}
|
||||
};
|
||||
CurrentStep = installStep;
|
||||
}
|
||||
else if (CurrentStep is InstallStep)
|
||||
{
|
||||
_finishSettings = new FinishSettings();
|
||||
CurrentStep = new FinishStep(_finishSettings);
|
||||
}
|
||||
}
|
||||
|
||||
private void GoToPreviousStep()
|
||||
{
|
||||
CurrentStep = _sourceStep;
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
using Installer.Installers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Installer.Models;
|
||||
|
||||
public class SourceStep : BaseNotifyPropertyChanged, IStep
|
||||
{
|
||||
public SourceStep()
|
||||
{
|
||||
InstallationSources = new ObservableCollection<InstallSourceViewModel>
|
||||
{
|
||||
// NOTE: the order also decides which installation source will be selected by default
|
||||
new(new SteamInstallSource()),
|
||||
new(new GOGInstallSource()),
|
||||
new(new TombATIInstallSource()),
|
||||
new(new TR1XInstallSource()),
|
||||
new(new CDRomInstallSource()),
|
||||
};
|
||||
|
||||
foreach (var installationSource in InstallationSources)
|
||||
{
|
||||
installationSource.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
NotifyPropertyChanged(nameof(InstallationSources));
|
||||
if (installationSource == selectedInstallationSource)
|
||||
{
|
||||
NotifyPropertyChanged(nameof(SelectedInstallationSource));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var source in InstallationSources)
|
||||
{
|
||||
if (source.IsAvailable)
|
||||
{
|
||||
// TR1X comes last and always trumps any other installation source
|
||||
SelectedInstallationSource = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanProceedToNextStep
|
||||
{
|
||||
get
|
||||
{
|
||||
return SelectedInstallationSource != null && SelectedInstallationSource.IsAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanProceedToPreviousStep => false;
|
||||
public IEnumerable<InstallSourceViewModel> InstallationSources { get; private set; }
|
||||
|
||||
public InstallSourceViewModel? SelectedInstallationSource
|
||||
{
|
||||
get => selectedInstallationSource;
|
||||
set
|
||||
{
|
||||
if (value != selectedInstallationSource)
|
||||
{
|
||||
selectedInstallationSource = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(CanProceedToNextStep));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SidebarImage => "pack://application:,,,/TR1X_Installer;component/Resources/side1.jpg";
|
||||
private InstallSourceViewModel? selectedInstallationSource;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace Installer.Models;
|
||||
|
||||
public enum UBPackType
|
||||
{
|
||||
Music,
|
||||
Vanilla,
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="{x:Type Button}" x:Key="ButtonStyle">
|
||||
<Setter Property="Padding" Value="10,5" />
|
||||
</Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}" />
|
||||
<Style x:Key="heading" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="20" />
|
||||
</Style>
|
||||
<Style x:Key="subHeading" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style x:Key="subHeadingFound" TargetType="TextBlock" BasedOn="{StaticResource subHeading}">
|
||||
<Setter Property="Foreground" Value="ForestGreen" />
|
||||
</Style>
|
||||
<Style x:Key="subHeadingNotFound" TargetType="TextBlock" BasedOn="{StaticResource subHeading}">
|
||||
<Setter Property="Foreground" Value="Firebrick" />
|
||||
</Style>
|
||||
<Style x:Key="normal" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
</Style>
|
||||
<Style x:Key="small">
|
||||
<Setter Property="Control.FontSize" Value="10" />
|
||||
<Setter Property="Run.FontSize" Value="10" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Image">
|
||||
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
Before Width: | Height: | Size: 50 KiB |
|
@ -1,14 +0,0 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Installer;
|
||||
|
||||
public abstract class BaseNotifyPropertyChanged : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public static class BinaryReaderExtensions
|
||||
{
|
||||
public static string ReadNullTerminatedString(this BinaryReader stream)
|
||||
{
|
||||
string str = "";
|
||||
char ch;
|
||||
while ((int)(ch = stream.ReadChar()) != 0)
|
||||
{
|
||||
str += ch;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static string ReadSystemCodepageString(this BinaryReader stream)
|
||||
{
|
||||
var length = stream.ReadUInt16();
|
||||
return Encoding.Default.GetString(stream.ReadBytes(length));
|
||||
}
|
||||
|
||||
public static string ReadUtf16String(this BinaryReader stream)
|
||||
{
|
||||
var length = stream.ReadUInt16();
|
||||
return Encoding.Unicode.GetString(stream.ReadBytes(length * 2));
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
[ValueConversion(typeof(bool), typeof(Visibility))]
|
||||
public class BoolToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public BoolToVisibilityConverter()
|
||||
{
|
||||
FalseValue = Visibility.Hidden;
|
||||
TrueValue = Visibility.Visible;
|
||||
}
|
||||
|
||||
public Visibility FalseValue { get; set; }
|
||||
public Visibility TrueValue { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return (bool)value ? TrueValue : FalseValue;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public class ComparisonConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value.Equals(parameter);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return (bool)value ? parameter : Binding.DoNothing;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public sealed class ConditionalMarkupConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
public object FalseValue { get; set; } = new();
|
||||
public object TrueValue { get; set; } = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is true ? TrueValue : FalseValue;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public class CueFile
|
||||
{
|
||||
public readonly List<CueTrack> TrackList = new();
|
||||
|
||||
public CueFile(string cueFilePath)
|
||||
{
|
||||
_cueFilePath = cueFilePath;
|
||||
string cueFileContent;
|
||||
using (TextReader cueReader = new StreamReader(cueFilePath))
|
||||
{
|
||||
cueFileContent = cueReader.ReadToEnd();
|
||||
}
|
||||
|
||||
MatchCollection fileMatches = _fileGroupRegex.Matches(cueFileContent);
|
||||
if (fileMatches.Count == 0)
|
||||
{
|
||||
throw new ApplicationException($"Could not parse {cueFilePath}: no tracks were found");
|
||||
}
|
||||
|
||||
foreach (Match fileMatch in fileMatches.Cast<Match>())
|
||||
{
|
||||
var binFilePath = GetBinFilePath(fileMatch.Groups["name"].Value.Trim('"'));
|
||||
var matches = _trackRegex.Matches(fileMatch.Groups["content"].Value);
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
throw new ApplicationException($"Could not parse {cueFilePath}: no tracks were found");
|
||||
}
|
||||
|
||||
CueTrack? track = null;
|
||||
CueTrack? prevTrack = null;
|
||||
foreach (Match trackMatch in matches.Cast<Match>())
|
||||
{
|
||||
track = new CueTrack(
|
||||
binFilePath,
|
||||
int.Parse(trackMatch.Groups["track"].Value),
|
||||
trackMatch.Groups["mode"].Value,
|
||||
trackMatch.Groups["time"].Value);
|
||||
|
||||
if (prevTrack != null)
|
||||
{
|
||||
prevTrack.Stop = track.StartPosition - 1;
|
||||
prevTrack.StopSector = track.StartSector;
|
||||
}
|
||||
TrackList.Add(track);
|
||||
prevTrack = track;
|
||||
}
|
||||
|
||||
if (track == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
track.Stop = GetBinFileLength(binFilePath);
|
||||
track.StopSector = track.Stop / CueTrack.SectorLength;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex _fileGroupRegex = new(
|
||||
@"^file\s+(?<name>""[^""]+""|[^""\s]+)\s+(?<mode>\w+)\s+(?<content>(.(?!^file))*)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
|
||||
|
||||
private static readonly Regex _trackRegex = new(@"track\s+?(?<track>\d+?)\s+?(?<mode>\S+?)[\s$]+?index\s+?\d+?\s+?(?<time>\S*)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Multiline);
|
||||
|
||||
private readonly string _cueFilePath;
|
||||
|
||||
private static long GetBinFileLength(string binFilePath)
|
||||
{
|
||||
FileInfo fileInfo = new(binFilePath);
|
||||
return fileInfo.Length;
|
||||
}
|
||||
|
||||
private string GetBinFilePath(string name)
|
||||
{
|
||||
var cueDirectory = Path.GetDirectoryName(_cueFilePath)!;
|
||||
string result = Path.Combine(cueDirectory, Path.GetFileName(name));
|
||||
if (!File.Exists(result))
|
||||
{
|
||||
result = Path.Combine(cueDirectory, Path.GetFileNameWithoutExtension(_cueFilePath) + ".bin");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
using Installer.Installers;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public class CueTrack
|
||||
{
|
||||
public const int SectorLength = 2352;
|
||||
public bool Audio;
|
||||
public long StopSector;
|
||||
public bool SwapAudioByteOrder;
|
||||
public bool TruncatePsx;
|
||||
public bool WavFormat;
|
||||
|
||||
public CueTrack(string binFilePath, int trackNumber, string mode, string time)
|
||||
{
|
||||
BinFilePath = binFilePath;
|
||||
TrackNumber = trackNumber;
|
||||
SetMode(mode);
|
||||
StartSector = ToFrames(time);
|
||||
}
|
||||
|
||||
public enum TrackExtension
|
||||
{
|
||||
ISO, CDR, WAV, UGH
|
||||
}
|
||||
|
||||
public string BinFilePath { get; private set; }
|
||||
public int BlockSize { get; private set; }
|
||||
public int BlockStart { get; private set; }
|
||||
public TrackExtension FileExtension { get; private set; }
|
||||
|
||||
public long StartPosition
|
||||
{
|
||||
get { return StartSector * SectorLength; }
|
||||
}
|
||||
|
||||
public long StartSector { get; private set; }
|
||||
|
||||
public long Stop { get; set; }
|
||||
|
||||
public long TotalBytes
|
||||
{
|
||||
get { return (StopSector - StartSector + 1) * BlockSize; }
|
||||
}
|
||||
|
||||
public int TrackNumber { get; private set; }
|
||||
|
||||
public void Write(string targetPath, IProgress<Installers.InstallProgress> progress)
|
||||
{
|
||||
using FileStream fileStream = OpenBinFile();
|
||||
try
|
||||
{
|
||||
using Stream stream = File.OpenWrite(targetPath);
|
||||
if (Audio && WavFormat)
|
||||
{
|
||||
byte[] header = MakeWavHeader(TotalBytes);
|
||||
stream.Write(header, 0, header.Length);
|
||||
}
|
||||
long currentPosition = StartPosition;
|
||||
long sector = StartSector;
|
||||
long convertedBytes = 0;
|
||||
|
||||
byte[] buf = new byte[SectorLength];
|
||||
while (sector <= StopSector && fileStream.Read(buf, 0, SectorLength) > 0)
|
||||
{
|
||||
if (Audio && SwapAudioByteOrder)
|
||||
{
|
||||
DoByteSwap(buf);
|
||||
}
|
||||
|
||||
stream.Write(buf, BlockStart, BlockSize);
|
||||
currentPosition += SectorLength;
|
||||
convertedBytes += BlockSize;
|
||||
|
||||
if (currentPosition / SectorLength % 500 == 0)
|
||||
{
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
MaximumValue = (int)TotalBytes,
|
||||
CurrentValue = (int)convertedBytes,
|
||||
Description = "Converting BIN to ISO"
|
||||
});
|
||||
}
|
||||
|
||||
sector++;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException(string.Format(" Could not write to track file {0}: {1}", targetPath, e.Message));
|
||||
}
|
||||
|
||||
progress.Report(new InstallProgress
|
||||
{
|
||||
MaximumValue = (int)TotalBytes,
|
||||
CurrentValue = (int)TotalBytes,
|
||||
Description = "Converting BIN to ISO",
|
||||
});
|
||||
}
|
||||
|
||||
private static byte[] MakeWavHeader(long length)
|
||||
{
|
||||
const int WAV_RIFF_HLEN = 12;
|
||||
const int WAV_FORMAT_HLEN = 24;
|
||||
const int WAV_DATA_HLEN = 8;
|
||||
const int WAV_HEADER_LEN = WAV_RIFF_HLEN + WAV_FORMAT_HLEN + WAV_DATA_HLEN;
|
||||
|
||||
MemoryStream memoryStream = new(WAV_HEADER_LEN);
|
||||
using (BinaryWriter writer = new(memoryStream))
|
||||
{
|
||||
// RIFF header
|
||||
writer.Write("RIFF".ToCharArray());
|
||||
uint dwordValue = (uint)length + WAV_DATA_HLEN + WAV_FORMAT_HLEN + 4;
|
||||
writer.Write(dwordValue); // length of file, starting from WAVE
|
||||
writer.Write("WAVE".ToCharArray());
|
||||
// FORMAT header
|
||||
writer.Write("fmt ".ToCharArray());
|
||||
dwordValue = 0x10; // length of FORMAT header
|
||||
writer.Write(dwordValue);
|
||||
ushort wordValue = 0x01; // constant
|
||||
writer.Write(wordValue);
|
||||
wordValue = 0x02; // channels
|
||||
writer.Write(wordValue);
|
||||
dwordValue = 44100; // sample rate
|
||||
writer.Write(dwordValue);
|
||||
dwordValue = 44100 * 4; // bytes per second
|
||||
writer.Write(dwordValue);
|
||||
wordValue = 4; // bytes per sample
|
||||
writer.Write(wordValue);
|
||||
wordValue = 2 * 8; // bits per channel
|
||||
writer.Write(wordValue);
|
||||
// DATA header
|
||||
writer.Write("data".ToCharArray());
|
||||
dwordValue = (uint)length;
|
||||
writer.Write(dwordValue);
|
||||
}
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
private static long ToFrames(string time)
|
||||
{
|
||||
string[] segs = time.Split(':');
|
||||
|
||||
int mins = int.Parse(segs[0]);
|
||||
int secs = int.Parse(segs[1]);
|
||||
int frames = int.Parse(segs[2]);
|
||||
|
||||
return (mins * 60 + secs) * 75 + frames;
|
||||
}
|
||||
|
||||
private void DoByteSwap(byte[] buf)
|
||||
{
|
||||
// swap low and high bytes
|
||||
int p = BlockStart;
|
||||
int ep = BlockSize;
|
||||
while (p < ep)
|
||||
{
|
||||
(buf[p + 1], buf[p]) = (buf[p], buf[p + 1]);
|
||||
p += 2;
|
||||
}
|
||||
}
|
||||
|
||||
private FileStream OpenBinFile()
|
||||
{
|
||||
FileStream fileStream;
|
||||
try
|
||||
{
|
||||
fileStream = File.OpenRead(BinFilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"Could not open BIN {BinFilePath}: {e.Message}");
|
||||
}
|
||||
try
|
||||
{
|
||||
fileStream.Seek(StartPosition, SeekOrigin.Begin);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException(string.Format("Could not seek to track location: {0}", e.Message));
|
||||
}
|
||||
return fileStream;
|
||||
}
|
||||
|
||||
private void SetMode(string mode)
|
||||
{
|
||||
Audio = false;
|
||||
BlockStart = 0;
|
||||
FileExtension = TrackExtension.ISO;
|
||||
|
||||
switch (mode.ToUpper())
|
||||
{
|
||||
case "AUDIO":
|
||||
BlockSize = 2352;
|
||||
Audio = true;
|
||||
FileExtension = WavFormat ? TrackExtension.WAV : TrackExtension.CDR;
|
||||
break;
|
||||
|
||||
case "MODE1/2352":
|
||||
BlockStart = 16;
|
||||
BlockSize = 2048;
|
||||
break;
|
||||
|
||||
case "MODE2/2336":
|
||||
// WAS 2352 in V1.361B still work? What if MODE2/2336 single track bin, still 2352 sectors?
|
||||
BlockStart = 16;
|
||||
BlockSize = 2336;
|
||||
break;
|
||||
|
||||
case "MODE2/2352":
|
||||
if (TruncatePsx)
|
||||
{
|
||||
// PSX: truncate from 2352 to 2336 byte tracks
|
||||
BlockSize = 2336;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal MODE2/2352
|
||||
BlockStart = 24;
|
||||
BlockSize = 2048;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BlockSize = 2352;
|
||||
FileExtension = TrackExtension.UGH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System.Windows.Forms;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public class FileBrowser
|
||||
{
|
||||
public static string? Browse(string? initialDirectory)
|
||||
{
|
||||
using var dlg = new FolderBrowserDialog()
|
||||
{
|
||||
Description = "Choose directory",
|
||||
SelectedPath = initialDirectory,
|
||||
ShowNewFolderButton = true,
|
||||
};
|
||||
if (dlg.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
return dlg.SelectedPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public class HttpProgressClient
|
||||
{
|
||||
public delegate void ProgressChangedHandler(long totalBytesToReceive, long bytesReceived);
|
||||
public event ProgressChangedHandler? DownloadProgressChanged;
|
||||
|
||||
public async Task<byte[]> DownloadDataTaskAsync(Uri uri)
|
||||
{
|
||||
using HttpClient client = new();
|
||||
client.DefaultRequestHeaders.CacheControl = new()
|
||||
{
|
||||
NoCache = true
|
||||
};
|
||||
|
||||
HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
long totalBytes = response.Content.Headers.ContentLength ?? 0;
|
||||
|
||||
using Stream contentStream = await response.Content.ReadAsStreamAsync();
|
||||
return await ProcessContentStream(totalBytes, contentStream);
|
||||
}
|
||||
|
||||
private async Task<byte[]> ProcessContentStream(long totalBytes, Stream contentStream)
|
||||
{
|
||||
long totalBytesRead = 0;
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
using MemoryStream outputStream = new();
|
||||
while (true)
|
||||
{
|
||||
int bytesRead = await contentStream.ReadAsync(buffer);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await outputStream.WriteAsync(buffer.AsMemory(0, bytesRead));
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
DownloadProgressChanged?.Invoke(totalBytes, totalBytesRead);
|
||||
}
|
||||
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
public RelayCommand(Action execute, Func<bool>? canExecute)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public RelayCommand(Action execute)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = null;
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
CommandManager.RequerySuggested += value;
|
||||
_canExecuteChanged += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
CommandManager.RequerySuggested -= value;
|
||||
_canExecuteChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return _canExecute == null || _canExecute();
|
||||
}
|
||||
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
_execute();
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
_canExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private readonly Func<bool>? _canExecute;
|
||||
private readonly Action _execute;
|
||||
|
||||
private EventHandler? _canExecuteChanged;
|
||||
}
|
||||
|
||||
public class RelayCommand<T> : ICommand
|
||||
{
|
||||
public RelayCommand(Action<T?> execute, Func<T?, bool>? canExecute)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public RelayCommand(Action<T?> execute)
|
||||
{
|
||||
_execute = execute;
|
||||
_canExecute = null;
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
CommandManager.RequerySuggested += value;
|
||||
_canExecuteChanged += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
CommandManager.RequerySuggested -= value;
|
||||
_canExecuteChanged -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return _canExecute == null || _canExecute((T?)parameter);
|
||||
}
|
||||
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
_execute((T?)parameter);
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
_canExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private readonly Func<T?, bool>? _canExecute;
|
||||
private readonly Action<T?> _execute;
|
||||
|
||||
private EventHandler? _canExecuteChanged;
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Installer.Utils;
|
||||
|
||||
public static class ShortcutUtils
|
||||
{
|
||||
public static void CreateShortcut(string shortcutPath, string targetPath, string name, string[]? args = null)
|
||||
{
|
||||
var fileInfo = File.Exists(targetPath) ? new FileInfo(targetPath) : null;
|
||||
using var stream = File.Open(Path.ChangeExtension(shortcutPath, "lnk"), FileMode.Create);
|
||||
using var bw = new BinaryWriter(stream);
|
||||
|
||||
void writeShellLinkHeader()
|
||||
{
|
||||
// HeaderSize
|
||||
bw.Write((Int32)0x4C);
|
||||
|
||||
// LinkCLSID
|
||||
bw.Write(new byte[] { 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 });
|
||||
|
||||
// LinkFlags
|
||||
bw.Write((Int32)(
|
||||
(1 << 0) // HasLinkTargetIDList
|
||||
| (1 << 2) // HasName
|
||||
| (1 << 3) // HasRelativePath
|
||||
| (1 << 4) // HasWorkingDir
|
||||
| (1 << 5) // HasArguments
|
||||
| (1 << 7) // IsUnicode
|
||||
| (1 << 8) // ForceNoLinkInfo
|
||||
));
|
||||
|
||||
if (fileInfo is not null)
|
||||
{
|
||||
bw.Write((Int32)fileInfo.Attributes); // FileAttributes
|
||||
bw.Write((Int64)fileInfo.CreationTimeUtc.ToFileTime()); // CreationTime
|
||||
bw.Write((Int64)fileInfo.LastAccessTimeUtc.ToFileTime()); // AccessTime
|
||||
bw.Write((Int64)fileInfo.LastWriteTimeUtc.ToFileTime()); // WriteTime
|
||||
bw.Write((Int32)fileInfo.Length); // FileSize
|
||||
}
|
||||
else
|
||||
{
|
||||
bw.Write((Int32)0); // FileAttributes
|
||||
bw.Write((Int64)0); // CreationTime
|
||||
bw.Write((Int64)0); // AccessTime
|
||||
bw.Write((Int64)0); // WriteTime
|
||||
bw.Write((Int32)0); // FileSize
|
||||
}
|
||||
bw.Write((Int32)0); // IconIndex
|
||||
bw.Write((Int32)1); // ShowCommand - SW_SHOWNORMAL
|
||||
bw.Write((Int16)0); // HotKey
|
||||
bw.Write((Int16)0); // Reserved1
|
||||
bw.Write((Int32)0); // Reserved2
|
||||
bw.Write((Int32)0); // Reserved3
|
||||
}
|
||||
|
||||
void writeLinkTargetIDList()
|
||||
{
|
||||
var idListSizePos = (int)bw.BaseStream.Position;
|
||||
bw.Write((UInt16)0); // IDListSize
|
||||
|
||||
// CLSID for this computer
|
||||
bw.Write((Int16)(0x12 + 2));
|
||||
bw.Write(new byte[] { 0x1F, 0x50, 0xE0, 0x4F, 0xD0, 0x20, 0xEA, 0x3A, 0x69, 0x10, 0xA2, 0xD8, 0x08, 0x00, 0x2B, 0x30, 0x30, 0x9D });
|
||||
|
||||
// Root directory
|
||||
var rootPrefix = "/";
|
||||
var root = Path.GetPathRoot(targetPath)!;
|
||||
var rootIdData = Encoding.Default.GetBytes(rootPrefix + root)
|
||||
.Concat(Enumerable.Repeat((byte)0, 21).ToArray())
|
||||
.Concat(new byte[] { 0x00 }).ToArray();
|
||||
bw.Write((Int16)(rootIdData.Length + 2));
|
||||
bw.Write(rootIdData);
|
||||
|
||||
var targetLeafPrefix = fileInfo is not null && (fileInfo.Attributes & FileAttributes.Directory) != 0
|
||||
? new byte[] { 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
|
||||
: new byte[] { 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
var targetLeaf = Path.GetRelativePath(root, targetPath);
|
||||
var targetLeafIdData = targetLeafPrefix.Concat(Encoding.Default.GetBytes(targetLeaf)).Concat(new byte[] { 0x00 }).ToArray();
|
||||
bw.Write((Int16)(targetLeafIdData.Length + 2));
|
||||
bw.Write(targetLeafIdData);
|
||||
|
||||
var idListSize = (int)bw.BaseStream.Position - idListSizePos;
|
||||
|
||||
bw.Write((Int16)0);
|
||||
|
||||
// fix offsets
|
||||
// IDListSize
|
||||
bw.Seek(idListSizePos, SeekOrigin.Begin);
|
||||
bw.Write((Int16)idListSize);
|
||||
|
||||
// restore pos
|
||||
bw.Seek(idListSizePos + idListSize + 2, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
void writeStringData()
|
||||
{
|
||||
// NAME
|
||||
bw.Write((Int16)name.Length);
|
||||
bw.Write(Encoding.Unicode.GetBytes(name));
|
||||
|
||||
// RELATIVE_PATH
|
||||
var relativePath = Path.GetFileName(targetPath);
|
||||
bw.Write((Int16)relativePath.Length);
|
||||
bw.Write(Encoding.Unicode.GetBytes(relativePath));
|
||||
|
||||
// WORKING_DIR
|
||||
var targetDir = Path.GetDirectoryName(targetPath)!;
|
||||
bw.Write((Int16)targetDir.Length);
|
||||
bw.Write(Encoding.Unicode.GetBytes(targetDir));
|
||||
|
||||
// ARGUMENTS
|
||||
var cmdline = args is null ? "" : string.Join(
|
||||
" ",
|
||||
args.Select(
|
||||
arg =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg))
|
||||
return arg;
|
||||
string value = Regex.Replace(arg, @"(\\*)" + "\"", @"$1\$0");
|
||||
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");
|
||||
return value;
|
||||
}
|
||||
).ToArray()
|
||||
);
|
||||
bw.Write((Int16)cmdline.Length);
|
||||
bw.Write(Encoding.Unicode.GetBytes(cmdline));
|
||||
}
|
||||
|
||||
writeShellLinkHeader();
|
||||
writeLinkTargetIDList();
|
||||
writeStringData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// .NET Core compatible .lnk reader.
|
||||
/// MS Documentation:
|
||||
/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943?redirectedfrom=MSDN
|
||||
/// </summary>
|
||||
public static string? GetLnkTargetPath(string filepath)
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(filepath));
|
||||
|
||||
var headerSize = br.ReadUInt32();
|
||||
if (headerSize != 0x4C)
|
||||
{
|
||||
throw new ApplicationException("Invalid LNK signature");
|
||||
}
|
||||
|
||||
br.ReadBytes(0x10); // skip LinkCLSID
|
||||
|
||||
// LinkFlags
|
||||
var linkFlags = br.ReadUInt32();
|
||||
|
||||
br.ReadBytes(4); // skip FileAttributes
|
||||
br.ReadBytes(8); // skip CreationTime
|
||||
br.ReadBytes(8); // skip AccessTime
|
||||
br.ReadBytes(8); // skip WriteTime
|
||||
br.ReadBytes(4); // skip FileSize
|
||||
br.ReadBytes(4); // skip IconIndex
|
||||
br.ReadBytes(4); // skip ShowCommand
|
||||
br.ReadBytes(2); // skip Hotkey
|
||||
br.ReadBytes(2); // skip Reserved
|
||||
br.ReadBytes(4); // skip Reserved2
|
||||
br.ReadBytes(4); // skip Reserved3
|
||||
|
||||
var hasLinkTargetIDList = (linkFlags & (1 << 0)) != 0;
|
||||
var hasLinkInfo = (linkFlags & (1 << 1)) != 0;
|
||||
var hasName = (linkFlags & (1 << 2)) != 0;
|
||||
var hasRelativePath = (linkFlags & (1 << 3)) != 0;
|
||||
var hasWorkingDir = (linkFlags & (1 << 4)) != 0;
|
||||
var isUnicode = (linkFlags & (1 << 7)) != 0;
|
||||
|
||||
// if the HasLinkTargetIDList bit, skip LinkTargetIDList
|
||||
if (hasLinkTargetIDList)
|
||||
{
|
||||
var skip = br.ReadUInt16();
|
||||
br.ReadBytes(skip);
|
||||
}
|
||||
|
||||
if (hasLinkInfo)
|
||||
{
|
||||
// get the number of bytes the path contains
|
||||
var linkInfoSize = br.ReadUInt32();
|
||||
br.ReadBytes(4); // skip LinkInfoHeaderSize
|
||||
br.ReadBytes(4); // skip LinkInfoFlags
|
||||
br.ReadBytes(4); // skip VolumeIDOffset
|
||||
|
||||
// Find the location of the LocalBasePath position
|
||||
var localPathBaseOffset = br.ReadUInt32();
|
||||
// Skip to the path position
|
||||
// (subtract the length of the read (4 bytes), the length of the skip (12 bytes), and
|
||||
// the length of the localPathBaseOffset read (4 bytes) from the localPathBaseOffset)
|
||||
br.ReadBytes((int)localPathBaseOffset - 0x14);
|
||||
var size = linkInfoSize - localPathBaseOffset - 0x02;
|
||||
var bytePath = br.ReadBytes((int)size);
|
||||
var path = Encoding.UTF8.GetString(bytePath, 0, bytePath.Length);
|
||||
return path;
|
||||
}
|
||||
|
||||
if (hasName)
|
||||
{
|
||||
var _ = isUnicode ? br.ReadSystemCodepageString() : br.ReadUtf16String(); // skip Name
|
||||
}
|
||||
|
||||
string? relativePath = null;
|
||||
if (hasRelativePath)
|
||||
{
|
||||
relativePath = isUnicode ? br.ReadSystemCodepageString() : br.ReadUtf16String();
|
||||
}
|
||||
|
||||
string? workingDir = null;
|
||||
if (hasWorkingDir)
|
||||
{
|
||||
workingDir = isUnicode ? br.ReadSystemCodepageString() : br.ReadUtf16String();
|
||||
}
|
||||
|
||||
if (workingDir is not null && relativePath is not null)
|
||||
{
|
||||
return Path.Combine(workingDir, relativePath);
|
||||
}
|
||||
if (workingDir is not null)
|
||||
{
|
||||
return workingDir;
|
||||
}
|
||||
if (relativePath is not null)
|
||||
{
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), relativePath);
|
||||
}
|
||||
|
||||
throw new ApplicationException("Unable to determine link target path");
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32421.90
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Installer", "Installer\Installer.csproj", "{2BD62007-7A9F-4DFF-9045-545BA9151DC1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2BD62007-7A9F-4DFF-9045-545BA9151DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2BD62007-7A9F-4DFF-9045-545BA9151DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2BD62007-7A9F-4DFF-9045-545BA9151DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2BD62007-7A9F-4DFF-9045-545BA9151DC1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3D36B2BD-DD06-4A3A-B36D-B56D83846FAB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|