2020-02-03 00:01:48 +13:00
using System ;
2019-04-22 18:41:43 +12:00
using System.Collections.Generic ;
2020-02-05 17:58:35 +13:00
using System.Diagnostics ;
2019-04-22 18:41:43 +12:00
using System.IO ;
2020-02-05 17:58:35 +13:00
using System.IO.Compression ;
2020-02-03 00:01:48 +13:00
using System.Threading ;
2019-04-22 18:41:43 +12:00
using System.Threading.Tasks ;
using System.Windows ;
using System.Windows.Controls ;
using System.Windows.Data ;
using System.Windows.Forms ;
2020-02-05 17:58:35 +13:00
using System.Windows.Navigation ;
2020-02-03 00:01:48 +13:00
using static ModAssistant . Http ;
2020-04-22 12:46:28 +12:00
using ModAssistant.Libs ;
2020-05-16 23:31:38 +12:00
using System.Windows.Media.Animation ;
using TextBox = System . Windows . Controls . TextBox ;
2019-04-22 18:41:43 +12:00
namespace ModAssistant.Pages
{
/// <summary>
/// Interaction logic for Mods.xaml
/// </summary>
public sealed partial class Mods : Page
{
public static Mods Instance = new Mods ( ) ;
2020-02-03 00:04:30 +13:00
public List < string > DefaultMods = new List < string > ( ) { "SongCore" , "ScoreSaber" , "BeatSaverDownloader" , "BeatSaverVoting" , "PlaylistCore" , "Survey" } ;
2019-04-22 18:41:43 +12:00
public Mod [ ] ModsList ;
2019-05-13 06:37:29 +12:00
public Mod [ ] AllModsList ;
2019-04-22 18:41:43 +12:00
public static List < Mod > InstalledMods = new List < Mod > ( ) ;
public List < string > CategoryNames = new List < string > ( ) ;
public CollectionView view ;
2019-05-19 03:32:14 +12:00
public bool PendingChanges ;
2019-04-22 18:41:43 +12:00
2020-02-03 15:41:09 +13:00
private readonly SemaphoreSlim _modsLoadSem = new SemaphoreSlim ( 1 , 1 ) ;
2020-02-03 00:01:48 +13:00
2019-04-22 18:41:43 +12:00
public List < ModListItem > ModList { get ; set ; }
public Mods ( )
{
InitializeComponent ( ) ;
}
private void RefreshModsList ( )
{
2019-05-19 03:32:14 +12:00
if ( view ! = null )
2020-02-03 15:41:09 +13:00
{
2019-05-19 03:32:14 +12:00
view . Refresh ( ) ;
2020-02-03 15:41:09 +13:00
}
2019-04-22 18:41:43 +12:00
}
2020-02-20 13:00:24 +13:00
public void RefreshColumns ( )
{
if ( MainWindow . Instance . Main . Content ! = Mods . Instance ) return ;
double viewWidth = ModsListView . ActualWidth ;
double totalSize = 0 ;
GridViewColumn description = null ;
GridView grid = ModsListView . View as GridView ;
if ( grid ! = null )
{
foreach ( var column in grid . Columns )
{
2020-02-28 15:52:20 +13:00
if ( column . Header ? . ToString ( ) = = FindResource ( "Mods:Header:Description" ) . ToString ( ) )
2020-02-20 13:00:24 +13:00
{
description = column ;
}
else
{
totalSize + = column . ActualWidth ;
}
if ( double . IsNaN ( column . Width ) )
{
column . Width = column . ActualWidth ;
column . Width = double . NaN ;
}
}
double descriptionNewWidth = viewWidth - totalSize - 35 ;
description . Width = descriptionNewWidth > 200 ? descriptionNewWidth : 200 ;
}
}
2020-02-03 16:01:32 +13:00
public async Task LoadMods ( )
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
var versionLoadSuccess = await MainWindow . Instance . VersionLoadStatus . Task ;
if ( versionLoadSuccess = = false ) return ;
2019-05-19 03:32:14 +12:00
2020-02-03 00:01:48 +13:00
await _modsLoadSem . WaitAsync ( ) ;
2019-05-19 03:32:14 +12:00
2020-02-03 00:01:48 +13:00
try
{
MainWindow . Instance . InstallButton . IsEnabled = false ;
MainWindow . Instance . GameVersionsBox . IsEnabled = false ;
MainWindow . Instance . InfoButton . IsEnabled = false ;
2019-05-19 03:32:14 +12:00
2020-02-03 00:01:48 +13:00
if ( ModsList ! = null )
{
Array . Clear ( ModsList , 0 , ModsList . Length ) ;
}
2019-05-19 03:32:14 +12:00
2020-02-03 00:01:48 +13:00
if ( AllModsList ! = null )
{
Array . Clear ( AllModsList , 0 , AllModsList . Length ) ;
}
InstalledMods = new List < Mod > ( ) ;
CategoryNames = new List < string > ( ) ;
ModList = new List < ModListItem > ( ) ;
2019-04-22 18:41:43 +12:00
2020-02-03 00:01:48 +13:00
ModsListView . Visibility = Visibility . Hidden ;
if ( App . CheckInstalledMods )
{
2020-02-03 23:26:09 +13:00
MainWindow . Instance . MainText = $"{FindResource(" Mods : CheckingInstalledMods ")}..." ;
2020-02-03 00:01:48 +13:00
await Task . Run ( async ( ) = > await CheckInstalledMods ( ) ) ;
InstalledColumn . Width = double . NaN ;
UninstallColumn . Width = 70 ;
DescriptionColumn . Width = 750 ;
}
else
{
InstalledColumn . Width = 0 ;
UninstallColumn . Width = 0 ;
DescriptionColumn . Width = 800 ;
}
2019-04-22 18:41:43 +12:00
2020-02-03 23:26:09 +13:00
MainWindow . Instance . MainText = $"{FindResource(" Mods : LoadingMods ")}..." ;
2020-02-03 00:01:48 +13:00
await Task . Run ( async ( ) = > await PopulateModsList ( ) ) ;
2019-04-22 18:41:43 +12:00
2020-02-03 00:01:48 +13:00
ModsListView . ItemsSource = ModList ;
2019-04-22 18:41:43 +12:00
2020-02-03 00:01:48 +13:00
view = ( CollectionView ) CollectionViewSource . GetDefaultView ( ModsListView . ItemsSource ) ;
PropertyGroupDescription groupDescription = new PropertyGroupDescription ( "Category" ) ;
view . GroupDescriptions . Add ( groupDescription ) ;
2019-04-22 18:41:43 +12:00
2020-02-03 00:01:48 +13:00
this . DataContext = this ;
2019-05-19 03:32:14 +12:00
2020-02-03 00:01:48 +13:00
RefreshModsList ( ) ;
2020-10-20 02:01:57 +13:00
ModsListView . Visibility = ModList . Count = = 0 ? Visibility . Hidden : Visibility . Visible ;
NoModsGrid . Visibility = ModList . Count = = 0 ? Visibility . Visible : Visibility . Hidden ;
2020-02-03 00:01:48 +13:00
2020-10-20 02:01:57 +13:00
MainWindow . Instance . MainText = $"{FindResource(" Mods : FinishedLoadingMods ")}." ;
MainWindow . Instance . InstallButton . IsEnabled = ModList . Count ! = 0 ;
2020-02-03 00:01:48 +13:00
MainWindow . Instance . GameVersionsBox . IsEnabled = true ;
}
finally
{
_modsLoadSem . Release ( ) ;
}
2019-04-22 18:41:43 +12:00
}
2020-02-03 00:01:48 +13:00
public async Task CheckInstalledMods ( )
2019-12-19 18:24:12 +13:00
{
2020-02-03 00:01:48 +13:00
await GetAllMods ( ) ;
2019-12-19 18:24:12 +13:00
List < string > empty = new List < string > ( ) ;
GetBSIPAVersion ( ) ;
CheckInstallDir ( "IPA/Pending/Plugins" , empty ) ;
CheckInstallDir ( "IPA/Pending/Libs" , empty ) ;
CheckInstallDir ( "Plugins" , empty ) ;
CheckInstallDir ( "Libs" , empty ) ;
}
2020-02-03 00:01:48 +13:00
public async Task GetAllMods ( )
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
var resp = await HttpClient . GetAsync ( Utils . Constants . BeatModsAPIUrl + "mod" ) ;
var body = await resp . Content . ReadAsStringAsync ( ) ;
AllModsList = JsonSerializer . Deserialize < Mod [ ] > ( body ) ;
2019-04-22 18:41:43 +12:00
}
private void CheckInstallDir ( string directory , List < string > blacklist )
{
2020-02-03 00:01:48 +13:00
if ( ! Directory . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , directory ) ) )
{
return ;
2020-02-02 21:38:29 +13:00
}
2019-05-26 20:53:25 +12:00
foreach ( string file in Directory . GetFileSystemEntries ( Path . Combine ( App . BeatSaberInstallDirectory , directory ) ) )
2019-04-22 18:41:43 +12:00
{
2019-05-26 20:53:25 +12:00
if ( File . Exists ( file ) & & Path . GetExtension ( file ) = = ".dll" | | Path . GetExtension ( file ) = = ".manifest" )
2019-04-22 18:41:43 +12:00
{
Mod mod = GetModFromHash ( Utils . CalculateMD5 ( file ) ) ;
if ( mod ! = null )
{
2019-05-26 20:53:25 +12:00
AddDetectedMod ( mod ) ;
}
}
}
}
2019-12-19 18:24:12 +13:00
public void GetBSIPAVersion ( )
2019-05-26 20:53:25 +12:00
{
string InjectorPath = Path . Combine ( App . BeatSaberInstallDirectory , "Beat Saber_Data" , "Managed" , "IPA.Injector.dll" ) ;
if ( ! File . Exists ( InjectorPath ) ) return ;
string InjectorHash = Utils . CalculateMD5 ( InjectorPath ) ;
foreach ( Mod mod in AllModsList )
{
if ( mod . name . ToLower ( ) = = "bsipa" )
{
foreach ( Mod . DownloadLink download in mod . downloads )
{
foreach ( Mod . FileHashes fileHash in download . hashMd5 )
2019-04-22 18:41:43 +12:00
{
2019-05-26 20:53:25 +12:00
if ( fileHash . hash = = InjectorHash )
2019-05-04 23:07:52 +12:00
{
2019-05-26 20:53:25 +12:00
AddDetectedMod ( mod ) ;
2019-05-04 23:07:52 +12:00
}
2019-04-22 18:41:43 +12:00
}
}
}
}
}
2019-05-26 20:53:25 +12:00
private void AddDetectedMod ( Mod mod )
2019-04-22 18:41:43 +12:00
{
2019-05-26 20:53:25 +12:00
if ( ! InstalledMods . Contains ( mod ) )
{
InstalledMods . Add ( mod ) ;
if ( App . SelectInstalledMods & & ! DefaultMods . Contains ( mod . name ) )
{
DefaultMods . Add ( mod . name ) ;
}
}
}
2019-04-22 18:41:43 +12:00
2019-05-26 20:53:25 +12:00
private Mod GetModFromHash ( string hash )
{
2019-05-13 06:37:29 +12:00
foreach ( Mod mod in AllModsList )
2019-04-22 18:41:43 +12:00
{
2020-04-22 10:42:03 +12:00
if ( mod . name . ToLower ( ) ! = "bsipa" & & mod . status ! = "declined" )
2019-05-13 06:37:29 +12:00
{
2019-05-26 20:53:25 +12:00
foreach ( Mod . DownloadLink download in mod . downloads )
2019-05-13 06:37:29 +12:00
{
2019-05-26 20:53:25 +12:00
foreach ( Mod . FileHashes fileHash in download . hashMd5 )
{
if ( fileHash . hash = = hash )
return mod ;
}
2019-05-13 06:37:29 +12:00
}
}
2019-04-22 18:41:43 +12:00
}
2019-05-13 06:37:29 +12:00
return null ;
2019-04-22 18:41:43 +12:00
}
2020-02-03 00:01:48 +13:00
public async Task PopulateModsList ( )
2019-04-22 18:41:43 +12:00
{
2019-05-19 03:50:22 +12:00
try
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
var resp = await HttpClient . GetAsync ( Utils . Constants . BeatModsAPIUrl + Utils . Constants . BeatModsModsOptions + "&gameVersion=" + MainWindow . GameVersion ) ;
var body = await resp . Content . ReadAsStringAsync ( ) ;
ModsList = JsonSerializer . Deserialize < Mod [ ] > ( body ) ;
2019-05-19 03:50:22 +12:00
}
catch ( Exception e )
{
2020-02-02 17:31:45 +13:00
System . Windows . MessageBox . Show ( $"{FindResource(" Mods : LoadFailed ")}.\n\n" + e ) ;
2019-05-19 03:50:22 +12:00
return ;
2019-04-22 18:41:43 +12:00
}
foreach ( Mod mod in ModsList )
{
bool preSelected = mod . required ;
if ( DefaultMods . Contains ( mod . name ) | | ( App . SaveModSelection & & App . SavedMods . Contains ( mod . name ) ) )
{
preSelected = true ;
2020-02-03 00:04:30 +13:00
if ( ! App . SavedMods . Contains ( mod . name ) )
2019-04-22 18:41:43 +12:00
{
App . SavedMods . Add ( mod . name ) ;
}
}
RegisterDependencies ( mod ) ;
ModListItem ListItem = new ModListItem ( )
{
IsSelected = preSelected ,
IsEnabled = ! mod . required ,
ModName = mod . name ,
ModVersion = mod . version ,
2019-05-13 12:57:22 +12:00
ModDescription = mod . description . Replace ( "\r\n" , " " ) . Replace ( "\n" , " " ) ,
2019-04-22 18:41:43 +12:00
ModInfo = mod ,
Category = mod . category
} ;
2019-06-19 08:59:16 +12:00
foreach ( Promotion promo in Promotions . ActivePromotions )
{
if ( mod . name = = promo . ModName )
{
ListItem . PromotionText = promo . Text ;
ListItem . PromotionLink = promo . Link ;
}
}
2019-04-22 18:41:43 +12:00
foreach ( Mod installedMod in InstalledMods )
{
if ( mod . name = = installedMod . name )
{
2019-05-23 07:43:34 +12:00
ListItem . InstalledModInfo = installedMod ;
2019-04-22 18:41:43 +12:00
ListItem . IsInstalled = true ;
ListItem . InstalledVersion = installedMod . version ;
break ;
}
}
mod . ListItem = ListItem ;
ModList . Add ( ListItem ) ;
}
foreach ( Mod mod in ModsList )
{
ResolveDependencies ( mod ) ;
}
}
2020-05-10 09:40:22 +12:00
public async void InstallMods ( )
2019-04-22 18:41:43 +12:00
{
MainWindow . Instance . InstallButton . IsEnabled = false ;
string installDirectory = App . BeatSaberInstallDirectory ;
foreach ( Mod mod in ModsList )
{
2020-05-03 06:38:55 +12:00
// Ignore mods that are newer than installed version
2020-05-09 18:23:04 +12:00
if ( mod . ListItem . GetVersionComparison > 0 ) continue ;
// Ignore mods that are on current version if we aren't reinstalling mods
2020-05-10 09:40:22 +12:00
if ( mod . ListItem . GetVersionComparison = = 0 & & ! App . ReinstallInstalledMods ) continue ;
2020-05-02 14:45:59 +12:00
2019-04-22 18:41:43 +12:00
if ( mod . name . ToLower ( ) = = "bsipa" )
{
2020-02-02 17:31:45 +13:00
MainWindow . Instance . MainText = $"{string.Format((string)FindResource(" Mods : InstallingMod "), mod.name)}..." ;
2020-02-03 00:01:48 +13:00
await Task . Run ( async ( ) = > await InstallMod ( mod , installDirectory ) ) ;
2020-02-02 17:31:45 +13:00
MainWindow . Instance . MainText = $"{string.Format((string)FindResource(" Mods : InstalledMod "), mod.name)}." ;
2019-05-26 20:53:25 +12:00
if ( ! File . Exists ( Path . Combine ( installDirectory , "winhttp.dll" ) ) )
2019-05-22 13:12:34 +12:00
{
await Task . Run ( ( ) = >
Process . Start ( new ProcessStartInfo
{
2019-05-26 20:53:25 +12:00
FileName = Path . Combine ( installDirectory , "IPA.exe" ) ,
2019-05-22 13:12:34 +12:00
WorkingDirectory = installDirectory ,
Arguments = "-n"
} ) . WaitForExit ( )
) ;
}
2020-02-03 00:01:48 +13:00
2019-12-19 18:24:12 +13:00
Pages . Options . Instance . YeetBSIPA . IsEnabled = true ;
2019-04-22 18:41:43 +12:00
}
2020-02-03 00:04:30 +13:00
else if ( mod . ListItem . IsSelected )
2019-04-22 18:41:43 +12:00
{
2020-02-02 17:31:45 +13:00
MainWindow . Instance . MainText = $"{string.Format((string)FindResource(" Mods : InstallingMod "), mod.name)}..." ;
2020-05-02 14:45:59 +12:00
await Task . Run ( async ( ) = > await InstallMod ( mod , Path . Combine ( installDirectory , @"IPA\Pending" ) ) ) ;
MainWindow . Instance . MainText = $"{string.Format((string)FindResource(" Mods : InstalledMod "), mod.name)}." ;
2019-04-22 18:41:43 +12:00
}
}
2020-02-03 00:01:48 +13:00
2020-05-03 06:38:55 +12:00
MainWindow . Instance . MainText = $"{FindResource(" Mods : FinishedInstallingMods ")}." ;
2019-04-22 18:41:43 +12:00
MainWindow . Instance . InstallButton . IsEnabled = true ;
2019-05-13 07:06:26 +12:00
RefreshModsList ( ) ;
2019-04-22 18:41:43 +12:00
}
2020-02-03 00:01:48 +13:00
private async Task InstallMod ( Mod mod , string directory )
2019-04-22 18:41:43 +12:00
{
string downloadLink = null ;
foreach ( Mod . DownloadLink link in mod . downloads )
{
if ( link . type = = "universal" )
{
downloadLink = link . url ;
break ;
2020-02-03 00:01:48 +13:00
}
else if ( link . type . ToLower ( ) = = App . BeatSaberInstallType . ToLower ( ) )
2019-04-22 18:41:43 +12:00
{
downloadLink = link . url ;
break ;
}
}
2020-02-02 21:42:15 +13:00
if ( string . IsNullOrEmpty ( downloadLink ) )
2019-04-22 18:41:43 +12:00
{
2020-02-02 17:31:45 +13:00
System . Windows . MessageBox . Show ( string . Format ( ( string ) FindResource ( "Mods:ModDownloadLinkMissing" ) , mod . name ) ) ;
2019-04-22 18:41:43 +12:00
return ;
}
2020-02-03 00:01:48 +13:00
using ( Stream stream = await DownloadMod ( Utils . Constants . BeatModsURL + downloadLink ) )
using ( ZipArchive archive = new ZipArchive ( stream ) )
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
foreach ( ZipArchiveEntry file in archive . Entries )
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
string fileDirectory = Path . GetDirectoryName ( Path . Combine ( directory , file . FullName ) ) ;
if ( ! Directory . Exists ( fileDirectory ) )
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
Directory . CreateDirectory ( fileDirectory ) ;
}
2019-04-22 18:41:43 +12:00
2020-02-03 00:01:48 +13:00
if ( ! string . IsNullOrEmpty ( file . Name ) )
{
2020-04-06 01:04:51 +12:00
await ExtractFile ( file , Path . Combine ( directory , file . FullName ) , 3.0 , mod . name , 10 ) ;
2019-04-22 18:41:43 +12:00
}
}
}
2019-05-13 07:06:26 +12:00
if ( App . CheckInstalledMods )
{
mod . ListItem . IsInstalled = true ;
mod . ListItem . InstalledVersion = mod . version ;
2019-05-26 20:53:25 +12:00
mod . ListItem . InstalledModInfo = mod ;
2019-05-13 07:06:26 +12:00
}
2019-04-22 18:41:43 +12:00
}
2020-04-06 01:04:51 +12:00
private async Task ExtractFile ( ZipArchiveEntry file , string path , double seconds , string name , int maxTries , int tryNumber = 0 )
{
if ( tryNumber < maxTries )
{
try
{
file . ExtractToFile ( path , true ) ;
}
catch
{
MainWindow . Instance . MainText = $"{string.Format((string)FindResource(" Mods : FailedExtract "), name, seconds, tryNumber + 1, maxTries)}" ;
await Task . Delay ( ( int ) ( seconds * 1000 ) ) ;
await ExtractFile ( file , path , seconds , name , maxTries , tryNumber + 1 ) ;
}
}
else
{
System . Windows . MessageBox . Show ( $"{string.Format((string)FindResource(" Mods : FailedExtractMaxReached "), name, maxTries)}." , "Failed to install " + name ) ;
}
}
2020-02-03 00:01:48 +13:00
private async Task < Stream > DownloadMod ( string link )
2019-04-22 18:41:43 +12:00
{
2020-02-03 00:01:48 +13:00
var resp = await HttpClient . GetAsync ( link ) ;
return await resp . Content . ReadAsStreamAsync ( ) ;
2019-04-22 18:41:43 +12:00
}
private void RegisterDependencies ( Mod dependent )
{
if ( dependent . dependencies . Length = = 0 )
return ;
foreach ( Mod mod in ModsList )
{
foreach ( Mod . Dependency dep in dependent . dependencies )
{
2020-02-02 21:38:29 +13:00
2019-04-22 18:41:43 +12:00
if ( dep . name = = mod . name )
{
dep . Mod = mod ;
mod . Dependents . Add ( dependent ) ;
2020-02-02 21:38:29 +13:00
2019-04-22 18:41:43 +12:00
}
}
}
}
private void ResolveDependencies ( Mod dependent )
{
if ( dependent . ListItem . IsSelected & & dependent . dependencies . Length > 0 )
{
foreach ( Mod . Dependency dependency in dependent . dependencies )
{
if ( dependency . Mod . ListItem . IsEnabled )
{
dependency . Mod . ListItem . PreviousState = dependency . Mod . ListItem . IsSelected ;
dependency . Mod . ListItem . IsSelected = true ;
dependency . Mod . ListItem . IsEnabled = false ;
ResolveDependencies ( dependency . Mod ) ;
}
}
}
}
private void UnresolveDependencies ( Mod dependent )
{
if ( ! dependent . ListItem . IsSelected & & dependent . dependencies . Length > 0 )
{
foreach ( Mod . Dependency dependency in dependent . dependencies )
{
if ( ! dependency . Mod . ListItem . IsEnabled )
{
bool needed = false ;
foreach ( Mod dep in dependency . Mod . Dependents )
{
if ( dep . ListItem . IsSelected )
{
needed = true ;
break ;
}
}
if ( ! needed & & ! dependency . Mod . required )
{
dependency . Mod . ListItem . IsSelected = dependency . Mod . ListItem . PreviousState ;
dependency . Mod . ListItem . IsEnabled = true ;
UnresolveDependencies ( dependency . Mod ) ;
}
}
}
}
}
private void ModCheckBox_Checked ( object sender , RoutedEventArgs e )
{
Mod mod = ( ( sender as System . Windows . Controls . CheckBox ) . Tag as Mod ) ;
mod . ListItem . IsSelected = true ;
ResolveDependencies ( mod ) ;
App . SavedMods . Add ( mod . name ) ;
2020-02-02 21:42:15 +13:00
Properties . Settings . Default . SavedMods = string . Join ( "," , App . SavedMods . ToArray ( ) ) ;
2019-04-22 18:41:43 +12:00
Properties . Settings . Default . Save ( ) ;
RefreshModsList ( ) ;
}
private void ModCheckBox_Unchecked ( object sender , RoutedEventArgs e )
{
Mod mod = ( ( sender as System . Windows . Controls . CheckBox ) . Tag as Mod ) ;
mod . ListItem . IsSelected = false ;
UnresolveDependencies ( mod ) ;
App . SavedMods . Remove ( mod . name ) ;
2020-02-02 21:42:15 +13:00
Properties . Settings . Default . SavedMods = string . Join ( "," , App . SavedMods . ToArray ( ) ) ;
2019-04-22 18:41:43 +12:00
Properties . Settings . Default . Save ( ) ;
RefreshModsList ( ) ;
}
public class Category
{
public string CategoryName { get ; set ; }
public List < ModListItem > Mods = new List < ModListItem > ( ) ;
}
public class ModListItem
{
public string ModName { get ; set ; }
public string ModVersion { get ; set ; }
public string ModDescription { get ; set ; }
public bool PreviousState { get ; set ; }
public bool IsEnabled { get ; set ; }
public bool IsSelected { get ; set ; }
public Mod ModInfo { get ; set ; }
public string Category { get ; set ; }
2019-05-23 07:43:34 +12:00
public Mod InstalledModInfo { get ; set ; }
2019-04-22 18:41:43 +12:00
public bool IsInstalled { get ; set ; }
2020-04-22 12:46:28 +12:00
private SemVersion _installedVersion { get ; set ; }
2019-05-16 08:51:09 +12:00
public string InstalledVersion
{
2019-04-22 18:41:43 +12:00
get
{
2020-05-04 20:40:01 +12:00
if ( ! IsInstalled | | _installedVersion = = null ) return "-" ;
return _installedVersion . ToString ( ) ;
2019-04-22 18:41:43 +12:00
}
set
{
2020-05-04 20:40:01 +12:00
if ( SemVersion . TryParse ( value , out SemVersion tempInstalledVersion ) )
2020-05-02 14:38:33 +12:00
{
2020-05-04 20:40:01 +12:00
_installedVersion = tempInstalledVersion ;
2020-05-02 14:38:33 +12:00
} else
{
_installedVersion = null ;
}
2019-04-22 18:41:43 +12:00
}
}
2019-05-16 08:51:09 +12:00
public string GetVersionColor
{
2019-04-22 18:41:43 +12:00
get
{
2019-05-16 08:51:09 +12:00
if ( ! IsInstalled ) return "Black" ;
2020-04-22 12:46:28 +12:00
return _installedVersion > = ModVersion ? "Green" : "Red" ;
2019-04-22 18:41:43 +12:00
}
}
2019-12-19 16:40:06 +13:00
public string GetVersionDecoration
{
get
{
if ( ! IsInstalled ) return "None" ;
2020-04-22 12:46:28 +12:00
return _installedVersion > = ModVersion ? "None" : "Strikethrough" ;
2019-12-19 16:40:06 +13:00
}
}
2020-05-09 18:23:04 +12:00
public int GetVersionComparison
2020-05-02 14:21:26 +12:00
{
get
{
2020-05-09 18:23:04 +12:00
if ( ! IsInstalled | | _installedVersion < ModVersion ) return - 1 ;
if ( _installedVersion > ModVersion ) return 1 ;
return 0 ;
2020-05-02 14:21:26 +12:00
}
}
2019-04-22 18:41:43 +12:00
public bool CanDelete
{
get
{
return ( ! ModInfo . required & & IsInstalled ) ;
}
}
public string CanSeeDelete
{
get
{
if ( ! ModInfo . required & & IsInstalled )
return "Visible" ;
else
return "Hidden" ;
}
}
2019-06-19 08:59:16 +12:00
public string PromotionText { get ; set ; }
public string PromotionLink { get ; set ; }
public string PromotionMargin
{
get
{
2020-02-02 21:42:15 +13:00
if ( string . IsNullOrEmpty ( PromotionText ) ) return "0" ;
2019-06-19 08:59:16 +12:00
return "0,0,5,0" ;
}
}
2019-04-22 18:41:43 +12:00
}
private void ModsListView_SelectionChanged ( object sender , SelectionChangedEventArgs e )
{
2019-09-04 07:30:04 +12:00
if ( ( Mods . ModListItem ) Mods . Instance . ModsListView . SelectedItem = = null )
{
MainWindow . Instance . InfoButton . IsEnabled = false ;
}
else
{
MainWindow . Instance . InfoButton . IsEnabled = true ;
}
2019-04-22 18:41:43 +12:00
}
2019-12-19 18:24:12 +13:00
public void UninstallBSIPA ( Mod . DownloadLink links )
2019-05-26 20:53:25 +12:00
{
Process . Start ( new ProcessStartInfo
{
FileName = Path . Combine ( App . BeatSaberInstallDirectory , "IPA.exe" ) ,
WorkingDirectory = App . BeatSaberInstallDirectory ,
2019-05-26 21:13:40 +12:00
Arguments = "--revert -n"
2019-05-26 20:53:25 +12:00
} ) . WaitForExit ( ) ;
foreach ( Mod . FileHashes files in links . hashMd5 )
{
string file = files . file . Replace ( "IPA/" , "" ) . Replace ( "Data" , "Beat Saber_Data" ) ;
if ( File . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , file ) ) )
File . Delete ( Path . Combine ( App . BeatSaberInstallDirectory , file ) ) ;
}
2019-12-19 18:24:12 +13:00
Pages . Options . Instance . YeetBSIPA . IsEnabled = false ;
2019-05-26 20:53:25 +12:00
}
2019-04-22 18:41:43 +12:00
private void Uninstall_Click ( object sender , RoutedEventArgs e )
{
Mod mod = ( ( sender as System . Windows . Controls . Button ) . Tag as Mod ) ;
2020-02-02 17:31:45 +13:00
string title = string . Format ( ( string ) FindResource ( "Mods:UninstallBox:Title" ) , mod . name ) ;
string body1 = string . Format ( ( string ) FindResource ( "Mods:UninstallBox:Body1" ) , mod . name ) ;
string body2 = string . Format ( ( string ) FindResource ( "Mods:UninstallBox:Body2" ) , mod . name ) ;
var result = System . Windows . Forms . MessageBox . Show ( $"{body1}\n{body2}" , title , MessageBoxButtons . YesNo ) ;
if ( result = = DialogResult . Yes )
2019-04-22 18:41:43 +12:00
{
2019-12-19 18:24:12 +13:00
UninstallModFromList ( mod ) ;
}
}
2019-05-26 20:53:25 +12:00
2019-12-19 18:24:12 +13:00
private void UninstallModFromList ( Mod mod )
{
UninstallMod ( mod . ListItem . InstalledModInfo ) ;
2019-12-19 18:41:32 +13:00
mod . ListItem . IsInstalled = false ;
mod . ListItem . InstalledVersion = null ;
if ( App . SelectInstalledMods )
{
mod . ListItem . IsSelected = false ;
UnresolveDependencies ( mod ) ;
App . SavedMods . Remove ( mod . name ) ;
2020-02-02 21:42:15 +13:00
Properties . Settings . Default . SavedMods = string . Join ( "," , App . SavedMods . ToArray ( ) ) ;
2019-12-19 18:41:32 +13:00
Properties . Settings . Default . Save ( ) ;
RefreshModsList ( ) ;
}
view . Refresh ( ) ;
2019-12-19 18:24:12 +13:00
}
public void UninstallMod ( Mod mod )
{
Mod . DownloadLink links = null ;
2019-12-19 18:41:32 +13:00
foreach ( Mod . DownloadLink link in mod . downloads )
2019-12-19 18:24:12 +13:00
{
if ( link . type . ToLower ( ) = = "universal" | | link . type . ToLower ( ) = = App . BeatSaberInstallType . ToLower ( ) )
2019-05-04 23:07:52 +12:00
{
2019-12-19 18:24:12 +13:00
links = link ;
break ;
2019-05-04 23:07:52 +12:00
}
2019-04-22 18:41:43 +12:00
}
2020-11-06 05:25:56 +13:00
if ( mod . name . ToLower ( ) = = "bsipa" ) {
var hasIPAExe = File . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , "IPA.exe" ) ) ;
var hasIPADir = Directory . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , "IPA" ) ) ;
if ( hasIPADir & & hasIPAExe ) {
2020-10-28 04:34:14 +13:00
UninstallBSIPA ( links ) ;
}
else
{
System . Windows . Forms . MessageBox . Show ( "BSIPA was not uninstalled as it was not found." , "Failed to uninstall BSIPA." , MessageBoxButtons . OK , MessageBoxIcon . Warning ) ;
}
}
2019-12-19 18:24:12 +13:00
foreach ( Mod . FileHashes files in links . hashMd5 )
{
if ( File . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , files . file ) ) )
File . Delete ( Path . Combine ( App . BeatSaberInstallDirectory , files . file ) ) ;
if ( File . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , "IPA" , "Pending" , files . file ) ) )
File . Delete ( Path . Combine ( App . BeatSaberInstallDirectory , "IPA" , "Pending" , files . file ) ) ;
}
2019-04-22 18:41:43 +12:00
}
2019-06-19 08:59:16 +12:00
private void Hyperlink_RequestNavigate ( object sender , RequestNavigateEventArgs e )
{
Process . Start ( new ProcessStartInfo ( e . Uri . AbsoluteUri ) ) ;
e . Handled = true ;
}
2020-02-20 13:00:24 +13:00
private void Page_Loaded ( object sender , RoutedEventArgs e )
{
RefreshColumns ( ) ;
}
2020-04-10 03:48:51 +12:00
private void CopyText ( object sender , System . Windows . Input . MouseButtonEventArgs e )
{
2020-06-10 01:29:49 +12:00
var textBlock = sender as TextBlock ;
if ( textBlock = = null ) { return ; }
var text = textBlock . Text ;
// Ensure there's text to be copied
if ( string . IsNullOrWhiteSpace ( text ) ) { return ; }
Utils . SetClipboard ( text ) ;
2020-04-10 03:48:51 +12:00
}
2020-05-16 23:31:38 +12:00
private void SearchButton_Click ( object sender , RoutedEventArgs e )
{
if ( SearchBar . Height = = 0 )
{
SearchBar . Focus ( ) ;
Animate ( SearchBar , 0 , 16 , new TimeSpan ( 0 , 0 , 0 , 0 , 300 ) ) ;
Animate ( SearchText , 0 , 16 , new TimeSpan ( 0 , 0 , 0 , 0 , 300 ) ) ;
ModsListView . Items . Filter = new Predicate < object > ( SearchFilter ) ;
}
else
{
Animate ( SearchBar , 16 , 0 , new TimeSpan ( 0 , 0 , 0 , 0 , 300 ) ) ;
Animate ( SearchText , 16 , 0 , new TimeSpan ( 0 , 0 , 0 , 0 , 300 ) ) ;
ModsListView . Items . Filter = null ;
}
}
private void SearchBar_TextChanged ( object sender , TextChangedEventArgs e )
{
ModsListView . Items . Filter = new Predicate < object > ( SearchFilter ) ;
if ( SearchBar . Text . Length > 0 )
{
SearchText . Text = null ;
}
else
{
2020-05-17 09:56:41 +12:00
SearchText . Text = ( string ) FindResource ( "Mods:SearchLabel" ) ;
2020-05-16 23:31:38 +12:00
}
}
private bool SearchFilter ( object mod )
{
ModListItem item = mod as ModListItem ;
if ( item . ModName . ToLower ( ) . Contains ( SearchBar . Text . ToLower ( ) ) ) return true ;
if ( item . ModDescription . ToLower ( ) . Contains ( SearchBar . Text . ToLower ( ) ) ) return true ;
if ( item . ModName . ToLower ( ) . Replace ( " " , string . Empty ) . Contains ( SearchBar . Text . ToLower ( ) . Replace ( " " , string . Empty ) ) ) return true ;
if ( item . ModDescription . ToLower ( ) . Replace ( " " , string . Empty ) . Contains ( SearchBar . Text . ToLower ( ) . Replace ( " " , string . Empty ) ) ) return true ;
return false ;
}
private void Animate ( TextBlock target , double oldHeight , double newHeight , TimeSpan duration )
{
target . Height = oldHeight ;
DoubleAnimation animation = new DoubleAnimation ( newHeight , duration ) ;
target . BeginAnimation ( TextBlock . HeightProperty , animation ) ;
}
private void Animate ( TextBox target , double oldHeight , double newHeight , TimeSpan duration )
{
target . Height = oldHeight ;
DoubleAnimation animation = new DoubleAnimation ( newHeight , duration ) ;
target . BeginAnimation ( TextBox . HeightProperty , animation ) ;
}
2019-04-22 18:41:43 +12:00
}
2020-02-02 21:38:29 +13:00
}