2021-10-02 17:01:09 +00:00
using System ;
2021-10-08 13:16:51 +00:00
using System.Collections.Generic ;
2021-10-02 17:01:09 +00:00
using System.Diagnostics ;
using System.IO ;
using System.IO.Compression ;
using System.Linq ;
using System.Net ;
using System.Text.Json ;
using System.Text.Json.Serialization ;
2022-06-01 21:45:55 +00:00
using System.Threading ;
2021-10-08 13:16:51 +00:00
using System.Threading.Tasks ;
2022-11-06 11:33:52 +00:00
using JetBrains.Annotations ;
2023-11-15 03:09:50 +00:00
using MsBox.Avalonia.Dto ;
2021-10-08 13:16:51 +00:00
using ReactiveUI.Fody.Helpers ;
2022-11-06 11:33:52 +00:00
using Wabbajack.Common ;
2022-06-01 21:45:55 +00:00
using Wabbajack.Downloaders.Http ;
2022-06-02 00:12:13 +00:00
using Wabbajack.DTOs.Logins ;
using Wabbajack.Networking.Http.Interfaces ;
2022-06-01 21:45:55 +00:00
using Wabbajack.Networking.NexusApi ;
2021-10-08 13:16:51 +00:00
using Wabbajack.Paths ;
using Wabbajack.Paths.IO ;
2022-10-08 03:43:44 +00:00
#pragma warning disable SYSLIB0014
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
namespace Wabbajack.Launcher.ViewModels ;
public class MainWindowViewModel : ViewModelBase
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
private readonly WebClient _client = new ( ) ;
private readonly List < string > _errors = new ( ) ;
2022-06-01 21:45:55 +00:00
private ( Version Version , long Size , Func < Task < Uri > > Uri ) _version ;
2021-10-23 16:51:17 +00:00
public Uri GITHUB_REPO = new ( "https://api.github.com/repos/wabbajack-tools/wabbajack/releases" ) ;
2022-06-01 21:45:55 +00:00
private readonly NexusApi _nexusApi ;
private readonly HttpDownloader _downloader ;
2022-06-02 00:12:13 +00:00
private readonly ITokenProvider < NexusApiState > _tokenProvider ;
2021-10-23 16:51:17 +00:00
2022-06-02 00:12:13 +00:00
public MainWindowViewModel ( NexusApi nexusApi , HttpDownloader downloader , ITokenProvider < NexusApiState > tokenProvider )
2021-10-02 17:01:09 +00:00
{
2022-06-01 21:45:55 +00:00
_nexusApi = nexusApi ;
2021-10-23 16:51:17 +00:00
Status = "Checking for new versions" ;
2022-06-01 21:45:55 +00:00
_downloader = downloader ;
2022-06-02 00:12:13 +00:00
_tokenProvider = tokenProvider ;
2021-10-23 16:51:17 +00:00
var tsk = CheckForUpdates ( ) ;
}
[Reactive] public string Status { get ; set ; }
2021-10-08 13:16:51 +00:00
2021-10-23 16:51:17 +00:00
private async Task CheckForUpdates ( )
{
2022-06-23 23:28:57 +00:00
await VerifyCurrentLocation ( ) ;
2022-11-06 11:33:52 +00:00
2021-10-23 16:51:17 +00:00
_client . Headers . Add ( "user-agent" , "Wabbajack Launcher" ) ;
Status = "Selecting Release" ;
try
2021-10-08 13:16:51 +00:00
{
2022-06-02 00:12:13 +00:00
if ( _tokenProvider . HaveToken ( ) )
{
2022-06-13 14:35:52 +00:00
try
{
_version = await GetNexusReleases ( CancellationToken . None ) ;
}
catch ( Exception )
{
_errors . Add ( "Nexus error" ) ;
}
2022-06-02 00:12:13 +00:00
}
if ( _version = = default )
2021-10-23 16:51:17 +00:00
{
2022-06-01 21:45:55 +00:00
_version = await GetGithubRelease ( CancellationToken . None ) ;
}
2021-10-08 13:16:51 +00:00
}
2021-10-23 16:51:17 +00:00
catch ( Exception ex )
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
_errors . Add ( ex . Message ) ;
await FinishAndExit ( ) ;
}
2021-10-02 17:01:09 +00:00
2022-06-01 21:45:55 +00:00
if ( _version = = default )
2021-10-23 16:51:17 +00:00
{
2022-06-01 21:45:55 +00:00
_errors . Add ( "Unable to find releases" ) ;
2021-10-23 16:51:17 +00:00
await FinishAndExit ( ) ;
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
Status = "Looking for Updates" ;
2021-10-02 17:01:09 +00:00
2022-06-01 21:45:55 +00:00
var baseFolder = KnownFolders . CurrentDirectory . Combine ( _version . Version . ToString ( ) ) ;
2021-10-02 17:01:09 +00:00
2022-06-01 21:45:55 +00:00
if ( baseFolder . Combine ( "Wabbajack.exe" ) . FileExists ( ) ) await FinishAndExit ( ) ;
2021-10-02 17:01:09 +00:00
2022-06-01 21:45:55 +00:00
Status = $"Getting download Uri for {_version.Version}" ;
var uri = await _version . Uri ( ) ;
2021-10-02 17:01:09 +00:00
2022-06-13 14:35:52 +00:00
/ *
2022-06-08 03:48:13 +00:00
var archive = new Archive ( )
{
Name = $"{_version.Version}.zip" ,
Size = _version . Size ,
State = new Http { Url = uri }
} ;
2022-11-06 11:33:52 +00:00
2022-06-08 03:48:13 +00:00
await using var stream = await _downloader . GetChunkedSeekableStream ( archive , CancellationToken . None ) ;
var rdr = new ZipReader ( stream , true ) ;
var entries = ( await rdr . GetFiles ( ) ) . OrderBy ( d = > d . FileOffset ) . ToArray ( ) ;
foreach ( var file in entries )
{
if ( file . FileName . EndsWith ( "/" ) | | file . FileName . EndsWith ( "\\" ) ) continue ;
var relPath = file . FileName . ToRelativePath ( ) ;
Status = $"Extracting: {relPath.FileName}" ;
var outPath = baseFolder . Combine ( relPath ) ;
if ( ! outPath . Parent . DirectoryExists ( ) )
outPath . Parent . CreateDirectory ( ) ;
2022-11-06 11:33:52 +00:00
2022-06-08 03:48:13 +00:00
await using var of = outPath . Open ( FileMode . Create , FileAccess . Write , FileShare . None ) ;
await rdr . Extract ( file , of , CancellationToken . None ) ;
2022-06-13 14:35:52 +00:00
} * /
2022-06-08 03:48:13 +00:00
2022-11-06 11:33:52 +00:00
2021-10-23 16:51:17 +00:00
var wc = new WebClient ( ) ;
2022-11-06 11:33:52 +00:00
wc . DownloadProgressChanged + = ( sender , args ) = > UpdateProgress ( $"{_version.Version}" , sender , args ) ;
2022-06-01 21:45:55 +00:00
Status = $"Downloading {_version.Version} ..." ;
2021-10-23 16:51:17 +00:00
byte [ ] data ;
try
{
2022-06-01 21:45:55 +00:00
data = await wc . DownloadDataTaskAsync ( uri ) ;
2021-10-23 16:51:17 +00:00
}
catch ( Exception ex )
{
_errors . Add ( ex . Message ) ;
// Something went wrong so fallback to original URL
2021-10-02 17:01:09 +00:00
try
{
2022-06-01 21:45:55 +00:00
data = await wc . DownloadDataTaskAsync ( uri ) ;
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
catch ( Exception ex2 )
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
_errors . Add ( ex2 . Message ) ;
await FinishAndExit ( ) ;
throw ; // avoid unsigned variable 'data'
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
try
{
2022-06-01 21:45:55 +00:00
using var zip = new ZipArchive ( new MemoryStream ( data ) , ZipArchiveMode . Read ) ;
foreach ( var entry in zip . Entries )
2021-10-02 17:01:09 +00:00
{
2022-06-01 21:45:55 +00:00
Status = $"Extracting: {entry.Name}" ;
var outPath = baseFolder . Combine ( entry . FullName . ToRelativePath ( ) ) ;
if ( ! outPath . Parent . DirectoryExists ( ) )
outPath . Parent . CreateDirectory ( ) ;
if ( entry . FullName . EndsWith ( "/" ) | | entry . FullName . EndsWith ( "\\" ) )
continue ;
await using var o = entry . Open ( ) ;
await using var of = outPath . Open ( FileMode . Create , FileAccess . Write , FileShare . None ) ;
await o . CopyToAsync ( of ) ;
2021-10-02 17:01:09 +00:00
}
}
2021-10-23 16:51:17 +00:00
catch ( Exception ex )
{
_errors . Add ( ex . Message ) ;
}
2022-11-06 11:33:52 +00:00
try
{
await InstallWebView ( ) ;
}
catch ( Exception e )
{
_errors . Add ( e . Message ) ;
}
await FinishAndExit ( ) ;
}
[UriString]
private const string WebViewDownloadLink = "https://go.microsoft.com/fwlink/p/?LinkId=2124703" ;
private async Task InstallWebView ( CancellationToken cancellationToken = default )
{
var setupPath = KnownFolders . WabbajackAppLocal . Combine ( "MicrosoftEdgeWebview2Setup.exe" ) ;
if ( setupPath . FileExists ( ) ) return ;
var wc = new WebClient ( ) ;
wc . DownloadProgressChanged + = ( sender , args ) = > UpdateProgress ( "WebView2" , sender , args ) ;
Status = "Downloading WebView2 Runtime" ;
byte [ ] data ;
try
{
data = await wc . DownloadDataTaskAsync ( WebViewDownloadLink ) ;
}
catch ( Exception ex )
2021-10-23 16:51:17 +00:00
{
2022-11-06 11:33:52 +00:00
_errors . Add ( ex . Message ) ;
2021-10-23 16:51:17 +00:00
await FinishAndExit ( ) ;
2022-11-06 11:33:52 +00:00
throw ;
2021-10-23 16:51:17 +00:00
}
2022-11-06 11:33:52 +00:00
await setupPath . WriteAllBytesAsync ( new Memory < byte > ( data ) , cancellationToken ) ;
var process = new ProcessHelper
{
2022-11-08 10:47:45 +00:00
Path = setupPath ,
Arguments = new [ ] { "/silent /install" }
2022-11-06 11:33:52 +00:00
} ;
await process . Start ( ) ;
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2022-06-23 23:28:57 +00:00
private async Task VerifyCurrentLocation ( )
{
2022-06-24 22:16:40 +00:00
try
{
var entryPoint = KnownFolders . EntryPoint ;
2023-07-16 21:22:56 +00:00
if ( KnownFolders . IsInSpecialFolder ( entryPoint ) | | entryPoint . Depth < = 1 )
2022-06-24 22:16:40 +00:00
{
2023-11-15 03:09:50 +00:00
var msg = MsBox . Avalonia . MessageBoxManager
. GetMessageBoxStandard ( new MessageBoxStandardParams ( )
2022-06-24 22:16:40 +00:00
{
Topmost = true ,
ShowInCenter = true ,
ContentTitle = "Wabbajack Launcher: Bad startup path" ,
ContentMessage =
2023-10-12 18:33:06 +00:00
"Cannot start in the root of a drive, or protected folder locations such as Downloads, Desktop etc.\nPlease move Wabbajack to another folder, creating a new folder if necessary ( example : C:\\Wabbajack\\), outside of these locations."
2022-06-24 22:16:40 +00:00
} ) ;
2023-11-15 03:09:50 +00:00
var result = await msg . ShowAsync ( ) ;
2022-06-24 22:16:40 +00:00
Environment . Exit ( 1 ) ;
}
}
catch ( Exception ex )
2022-06-23 23:28:57 +00:00
{
2022-06-24 22:16:40 +00:00
Status = ex . Message ;
2023-11-15 03:09:50 +00:00
var msg = MsBox . Avalonia . MessageBoxManager
. GetMessageBoxStandard ( new MessageBoxStandardParams ( )
2022-06-24 22:16:40 +00:00
{
Topmost = true ,
ShowInCenter = true ,
ContentTitle = "Wabbajack Launcher: Error" ,
ContentMessage = ex . ToString ( )
} ) ;
2023-11-15 03:09:50 +00:00
var result = await msg . ShowAsync ( ) ;
2022-06-23 23:28:57 +00:00
Environment . Exit ( 1 ) ;
}
}
2022-11-06 11:33:52 +00:00
[ContractAnnotation("=> halt")]
2021-10-23 16:51:17 +00:00
private async Task FinishAndExit ( )
{
try
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Status = "Launching..." ;
2022-09-20 02:03:54 +00:00
var wjFolder = KnownFolders . CurrentDirectory . EnumerateDirectories ( recursive : false )
2021-10-23 16:51:17 +00:00
. OrderByDescending ( v = >
Version . TryParse ( v . FileName . ToString ( ) , out var ver ) ? ver : new Version ( 0 , 0 , 0 , 0 ) )
. FirstOrDefault ( ) ;
2022-09-19 23:47:54 +00:00
if ( wjFolder = = default )
{
_errors . Add ( "No WJ install found" ) ;
throw new Exception ( "No WJ install found" ) ;
}
2021-10-23 16:51:17 +00:00
var filename = wjFolder . Combine ( "Wabbajack.exe" ) ;
await CreateBatchFile ( filename ) ;
var info = new ProcessStartInfo
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
FileName = filename . ToString ( ) ,
Arguments = string . Join ( " " ,
Environment . GetCommandLineArgs ( ) . Skip ( 1 ) . Select ( s = > s . Contains ( ' ' ) ? '\"' + s + '\"' : s ) ) ,
WorkingDirectory = wjFolder . ToString ( )
} ;
Process . Start ( info ) ;
}
2022-09-19 23:47:54 +00:00
catch ( Exception ex )
2021-10-23 16:51:17 +00:00
{
2022-09-19 23:47:54 +00:00
_errors . Add ( ex . Message ) ;
2021-10-23 16:51:17 +00:00
if ( _errors . Count = = 0 )
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Status = "Failed: Unknown error" ;
await Task . Delay ( 10000 ) ;
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
foreach ( var error in _errors )
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Status = "Failed: " + error ;
await Task . Delay ( 10000 ) ;
2021-10-02 17:01:09 +00:00
}
}
2021-10-23 16:51:17 +00:00
finally
2021-10-02 17:01:09 +00:00
{
2021-10-23 16:51:17 +00:00
Environment . Exit ( 0 ) ;
2021-10-02 17:01:09 +00:00
}
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
private async Task CreateBatchFile ( AbsolutePath filename )
{
2022-09-19 23:47:54 +00:00
try
{
filename = filename . Parent . Combine ( "wabbajack-cli.exe" ) ;
var data = $"\" { filename } \ " %*" ;
var file = Path . Combine ( Directory . GetCurrentDirectory ( ) , "wabbajack-cli.bat" ) ;
if ( File . Exists ( file ) & & await File . ReadAllTextAsync ( file ) = = data ) return ;
await File . WriteAllTextAsync ( file , data ) ;
}
catch ( Exception ex )
{
_errors . Add ( $"Creating Batch File : {ex}" ) ;
}
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2022-11-06 11:33:52 +00:00
private void UpdateProgress ( string what , object sender , DownloadProgressChangedEventArgs e )
2021-10-23 16:51:17 +00:00
{
2022-11-06 11:33:52 +00:00
Status = $"Downloading {what} ({e.ProgressPercentage}%)..." ;
2021-10-23 16:51:17 +00:00
}
2021-10-02 17:01:09 +00:00
2022-06-01 21:45:55 +00:00
private async Task < ( Version Version , long Size , Func < Task < Uri > > Uri ) > GetGithubRelease ( CancellationToken token )
{
var releases = await GetGithubReleases ( ) ;
2022-11-06 11:33:52 +00:00
2022-06-01 21:45:55 +00:00
var version = releases . Select ( r = >
{
if ( r . Tag . Split ( "." ) . Length = = 4 & & Version . TryParse ( r . Tag , out var v ) )
return ( v , r ) ;
return ( new Version ( 0 , 0 , 0 , 0 ) , r ) ;
} )
. OrderByDescending ( r = > r . Item1 )
. FirstOrDefault ( ) ;
2022-11-06 11:33:52 +00:00
2022-06-01 21:45:55 +00:00
var asset = version . r . Assets . FirstOrDefault ( a = > a . Name = = version . Item1 + ".zip" ) ;
if ( asset = = null )
{
Status = $"Error, no asset found for Github Release {version.r}" ;
return default ;
}
return ( version . Item1 , asset . Size , async ( ) = > asset ! . BrowserDownloadUrl ) ;
}
2022-11-06 11:33:52 +00:00
2022-06-01 21:45:55 +00:00
private async Task < Release [ ] > GetGithubReleases ( )
2021-10-23 16:51:17 +00:00
{
Status = "Checking GitHub Repository" ;
var data = await _client . DownloadStringTaskAsync ( GITHUB_REPO ) ;
Status = "Parsing Response" ;
return JsonSerializer . Deserialize < Release [ ] > ( data ) ! ;
}
2022-11-06 11:33:52 +00:00
2022-06-01 21:45:55 +00:00
private async Task < ( Version Version , long Size , Func < Task < Uri > > uri ) > GetNexusReleases ( CancellationToken token )
{
Status = "Checking Nexus for updates" ;
if ( ! await _nexusApi . IsPremium ( token ) )
return default ;
2022-11-06 11:33:52 +00:00
2022-06-01 21:45:55 +00:00
var data = await _nexusApi . ModFiles ( "site" , 403 , token ) ;
Status = "Parsing Response" ;
//return JsonSerializer.Deserialize<Release[]>(data)!;
2022-10-01 13:02:47 +00:00
var found = data . info . Files . Where ( f = > f . CategoryId = = 5 | | f . CategoryId = = 3 )
2022-06-01 21:45:55 +00:00
. Where ( f = > f . Name . EndsWith ( ".zip" ) )
. Select ( f = > Version . TryParse ( f . Name [ . . ^ 4 ] , out var version ) ? ( version , f . SizeInBytes ? ? f . Size , f . FileId ) : default )
. FirstOrDefault ( f = > f ! = default ) ;
if ( found = = default ) return default ;
return ( found . version , found . Item2 , async ( ) = >
{
var link = await _nexusApi . DownloadLink ( "site" , 403 , found . FileId , token ) ;
return link . info . First ( ) . URI ;
} ) ;
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
private class Release
{
[JsonPropertyName("tag_name")] public string Tag { get ; set ; }
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
[JsonPropertyName("assets")] public Asset [ ] Assets { get ; set ; }
}
2021-10-02 17:01:09 +00:00
2021-10-23 16:51:17 +00:00
private class Asset
{
[JsonPropertyName("browser_download_url")]
public Uri BrowserDownloadUrl { get ; set ; }
2021-10-08 13:16:51 +00:00
2021-10-23 16:51:17 +00:00
[JsonPropertyName("name")] public string Name { get ; set ; }
2022-06-01 21:45:55 +00:00
2022-11-06 11:33:52 +00:00
2022-06-01 21:45:55 +00:00
[JsonPropertyName("size")] public long Size { get ; set ; }
2021-10-02 17:01:09 +00:00
}
2022-11-06 11:33:52 +00:00
}