2019-04-22 18:41:43 +12:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net ;
using System.Text ;
using System.Threading.Tasks ;
using System.Web.Script.Serialization ;
using System.Windows ;
using System.Windows.Controls ;
using System.Windows.Data ;
using System.Windows.Documents ;
using System.Windows.Input ;
using System.Windows.Media ;
using System.Windows.Media.Imaging ;
using System.Windows.Navigation ;
using System.IO.Compression ;
using System.Diagnostics ;
using System.Windows.Forms ;
namespace ModAssistant.Pages
{
/// <summary>
/// Interaction logic for Mods.xaml
/// </summary>
public sealed partial class Mods : Page
{
public static Mods Instance = new Mods ( ) ;
2019-05-04 23:07:52 +12:00
public List < string > DefaultMods = new List < string > ( ) { "SongLoader" , "ScoreSaber" , "BeatSaverDownloader" } ;
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
public List < ModListItem > ModList { get ; set ; }
public Mods ( )
{
InitializeComponent ( ) ;
}
private void RefreshModsList ( )
{
2019-05-19 03:32:14 +12:00
if ( view ! = null )
view . Refresh ( ) ;
2019-04-22 18:41:43 +12:00
}
2019-05-19 03:32:14 +12:00
public async void LoadMods ( )
2019-04-22 18:41:43 +12:00
{
2019-05-19 03:32:14 +12:00
MainWindow . Instance . InstallButton . IsEnabled = false ;
MainWindow . Instance . GameVersionsBox . IsEnabled = false ;
if ( ModsList ! = null )
Array . Clear ( ModsList , 0 , ModsList . Length ) ;
if ( AllModsList ! = null )
Array . Clear ( AllModsList , 0 , AllModsList . Length ) ;
InstalledMods = new List < Mod > ( ) ;
CategoryNames = new List < string > ( ) ;
ModList = new List < ModListItem > ( ) ;
ModsListView . Visibility = Visibility . Hidden ;
2019-04-22 18:41:43 +12:00
if ( App . CheckInstalledMods )
{
MainWindow . Instance . MainText = "Checking Installed Mods..." ;
await Task . Run ( ( ) = > CheckInstalledMods ( ) ) ;
2019-05-19 03:32:14 +12:00
InstalledColumn . Width = Double . NaN ;
UninstallColumn . Width = 70 ;
DescriptionColumn . Width = 750 ;
2019-04-22 18:41:43 +12:00
} else
{
InstalledColumn . Width = 0 ;
UninstallColumn . Width = 0 ;
DescriptionColumn . Width = 800 ;
}
MainWindow . Instance . MainText = "Loading Mods..." ;
await Task . Run ( ( ) = > PopulateModsList ( ) ) ;
ModsListView . ItemsSource = ModList ;
view = ( CollectionView ) CollectionViewSource . GetDefaultView ( ModsListView . ItemsSource ) ;
PropertyGroupDescription groupDescription = new PropertyGroupDescription ( "Category" ) ;
view . GroupDescriptions . Add ( groupDescription ) ;
this . DataContext = this ;
RefreshModsList ( ) ;
2019-05-19 03:32:14 +12:00
ModsListView . Visibility = Visibility . Visible ;
2019-04-22 18:41:43 +12:00
MainWindow . Instance . MainText = "Finished Loading Mods." ;
2019-05-19 03:32:14 +12:00
2019-04-22 18:41:43 +12:00
MainWindow . Instance . InstallButton . IsEnabled = true ;
2019-05-19 03:32:14 +12:00
MainWindow . Instance . GameVersionsBox . IsEnabled = true ;
2019-04-22 18:41:43 +12:00
}
private void CheckInstalledMods ( )
{
2019-05-13 06:37:29 +12:00
string json = string . Empty ;
HttpWebRequest request = ( HttpWebRequest ) WebRequest . Create ( Utils . Constants . BeatModsAPIUrl + "mod" ) ;
request . AutomaticDecompression = DecompressionMethods . GZip ;
request . UserAgent = "ModAssistant/" + App . Version ;
using ( HttpWebResponse response = ( HttpWebResponse ) request . GetResponse ( ) )
using ( Stream stream = response . GetResponseStream ( ) )
using ( StreamReader reader = new StreamReader ( stream ) )
{
var serializer = new JavaScriptSerializer ( ) ;
AllModsList = serializer . Deserialize < Mod [ ] > ( reader . ReadToEnd ( ) ) ;
}
2019-04-22 18:41:43 +12:00
List < string > empty = new List < string > ( ) ;
CheckInstallDir ( "Plugins" , empty ) ;
2019-05-26 20:53:25 +12:00
CheckInstallDir ( "Libs" , empty ) ;
GetBSIPAVersion ( ) ;
CheckInstallDir ( "IPA/Pending/Plugins" , empty ) ;
CheckInstallDir ( "IPA/Pending/Libs" , empty ) ;
2019-04-22 18:41:43 +12:00
}
private void CheckInstallDir ( string directory , List < string > blacklist )
{
2019-05-26 20:53:25 +12:00
if ( ! Directory . Exists ( Path . Combine ( App . BeatSaberInstallDirectory , directory ) ) )
2019-05-22 09:20:51 +12:00
return ;
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 ) ;
}
}
}
}
private void GetBSIPAVersion ( )
{
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
{
2019-05-26 20:53:25 +12:00
if ( mod . name . ToLower ( ) ! = "bsipa" )
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
}
public void PopulateModsList ( )
{
string json = string . Empty ;
2019-05-19 03:32:14 +12:00
HttpWebRequest request = ( HttpWebRequest ) WebRequest . Create ( Utils . Constants . BeatModsAPIUrl + Utils . Constants . BeatModsModsOptions + "&gameVersion=" + MainWindow . GameVersion ) ;
2019-04-22 18:41:43 +12:00
request . AutomaticDecompression = DecompressionMethods . GZip ;
2019-05-05 03:05:48 +12:00
request . UserAgent = "ModAssistant/" + App . Version ;
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
{
2019-05-19 03:50:22 +12:00
using ( HttpWebResponse response = ( HttpWebResponse ) request . GetResponse ( ) )
using ( Stream stream = response . GetResponseStream ( ) )
using ( StreamReader reader = new StreamReader ( stream ) )
{
var serializer = new JavaScriptSerializer ( ) ;
ModsList = serializer . Deserialize < Mod [ ] > ( reader . ReadToEnd ( ) ) ;
}
}
catch ( Exception e )
{
System . Windows . MessageBox . Show ( "Could not load mods list.\n\n" + e ) ;
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 ;
if ( ! App . SavedMods . Contains ( mod . name ) )
{
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
} ;
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 ) ;
}
}
public async void InstallMods ( )
{
MainWindow . Instance . InstallButton . IsEnabled = false ;
string installDirectory = App . BeatSaberInstallDirectory ;
foreach ( Mod mod in ModsList )
{
if ( mod . name . ToLower ( ) = = "bsipa" )
{
MainWindow . Instance . MainText = $"Installing {mod.name}..." ;
await Task . Run ( ( ) = > InstallMod ( mod , installDirectory ) ) ;
MainWindow . Instance . MainText = $"Installed {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 ( )
) ;
}
2019-04-22 18:41:43 +12:00
}
else if ( mod . ListItem . IsSelected )
{
MainWindow . Instance . MainText = $"Installing {mod.name}..." ;
2019-05-26 20:53:25 +12:00
await Task . Run ( ( ) = > InstallMod ( mod , Path . Combine ( installDirectory , @"IPA\Pending" ) ) ) ;
2019-04-22 18:41:43 +12:00
MainWindow . Instance . MainText = $"Installed {mod.name}." ;
}
}
MainWindow . Instance . MainText = "Finished installing mods." ;
MainWindow . Instance . InstallButton . IsEnabled = true ;
2019-05-13 07:06:26 +12:00
RefreshModsList ( ) ;
2019-04-22 18:41:43 +12:00
}
private void InstallMod ( Mod mod , string directory )
{
string downloadLink = null ;
foreach ( Mod . DownloadLink link in mod . downloads )
{
if ( link . type = = "universal" )
{
downloadLink = link . url ;
break ;
} else if ( link . type . ToLower ( ) = = App . BeatSaberInstallType . ToLower ( ) )
{
downloadLink = link . url ;
break ;
}
}
if ( String . IsNullOrEmpty ( downloadLink ) )
{
System . Windows . MessageBox . Show ( $"Could not find download link for {mod.name}" ) ;
return ;
}
2019-05-23 19:31:51 +12:00
using ( MemoryStream stream = new MemoryStream ( DownloadMod ( Utils . Constants . BeatModsURL + downloadLink ) ) )
2019-04-22 18:41:43 +12:00
{
using ( ZipArchive archive = new ZipArchive ( stream ) )
{
foreach ( ZipArchiveEntry file in archive . Entries )
{
2019-05-26 20:53:25 +12:00
string fileDirectory = Path . GetDirectoryName ( Path . Combine ( directory , file . FullName ) ) ;
2019-04-22 18:41:43 +12:00
if ( ! Directory . Exists ( fileDirectory ) )
Directory . CreateDirectory ( fileDirectory ) ;
if ( ! String . IsNullOrEmpty ( file . Name ) )
2019-05-26 20:53:25 +12:00
file . ExtractToFile ( Path . Combine ( directory , file . FullName ) , true ) ;
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
}
private byte [ ] DownloadMod ( string link )
{
byte [ ] zip = new WebClient ( ) . DownloadData ( link ) ;
return zip ;
}
private void RegisterDependencies ( Mod dependent )
{
if ( dependent . dependencies . Length = = 0 )
return ;
foreach ( Mod mod in ModsList )
{
foreach ( Mod . Dependency dep in dependent . dependencies )
{
if ( dep . name = = mod . name )
{
dep . Mod = mod ;
mod . Dependents . Add ( dependent ) ;
}
}
}
}
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 ) ;
Properties . Settings . Default . SavedMods = String . Join ( "," , App . SavedMods . ToArray ( ) ) ;
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 ) ;
Properties . Settings . Default . SavedMods = String . Join ( "," , App . SavedMods . ToArray ( ) ) ;
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 ; }
private string _installedVersion { get ; set ; }
2019-05-16 08:51:09 +12:00
public string InstalledVersion
{
2019-04-22 18:41:43 +12:00
get
{
2019-05-16 08:51:09 +12:00
return ( String . IsNullOrEmpty ( _installedVersion ) | | ! IsInstalled ) ? "-" : _installedVersion ;
2019-04-22 18:41:43 +12:00
}
set
{
_installedVersion = value ;
}
}
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" ;
return InstalledVersion = = ModVersion ? "Green" : "Red" ;
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" ;
}
}
}
private void ModsListView_SelectionChanged ( object sender , SelectionChangedEventArgs e )
{
MainWindow . Instance . InfoButton . IsEnabled = true ;
}
2019-05-26 20:53:25 +12:00
private void UninstallBSIPA ( Mod . DownloadLink links )
{
Process . Start ( new ProcessStartInfo
{
FileName = Path . Combine ( App . BeatSaberInstallDirectory , "IPA.exe" ) ,
WorkingDirectory = App . BeatSaberInstallDirectory ,
Arguments = "--revert"
} ) . 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-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 ) ;
2019-05-24 05:13:18 +12:00
Mod installedMod = mod . ListItem . InstalledModInfo ;
2019-05-04 23:07:52 +12:00
if ( System . Windows . Forms . MessageBox . Show ( $"Are you sure you want to remove {mod.name}?\nThis could break your other mods." , $"Uninstall {mod.name}?" , MessageBoxButtons . YesNo ) = = DialogResult . Yes )
2019-04-22 18:41:43 +12:00
{
Mod . DownloadLink links = null ;
2019-05-24 05:13:18 +12:00
foreach ( Mod . DownloadLink link in installedMod . downloads )
2019-04-22 18:41:43 +12:00
{
if ( link . type . ToLower ( ) = = "universal" | | link . type . ToLower ( ) = = App . BeatSaberInstallType . ToLower ( ) )
{
links = link ;
break ;
}
}
2019-05-26 20:53:25 +12:00
if ( installedMod . name . ToLower ( ) = = "bsipa" )
UninstallBSIPA ( links ) ;
2019-04-22 18:41:43 +12:00
foreach ( Mod . FileHashes files in links . hashMd5 )
{
2019-05-26 20:53:25 +12:00
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-05-26 20:53:25 +12:00
2019-04-22 18:41:43 +12:00
mod . ListItem . IsInstalled = false ;
mod . ListItem . InstalledVersion = null ;
2019-05-04 23:07:52 +12:00
if ( App . SelectInstalledMods )
{
mod . ListItem . IsSelected = false ;
UnresolveDependencies ( mod ) ;
App . SavedMods . Remove ( mod . name ) ;
Properties . Settings . Default . SavedMods = String . Join ( "," , App . SavedMods . ToArray ( ) ) ;
Properties . Settings . Default . Save ( ) ;
RefreshModsList ( ) ;
}
2019-04-22 19:11:07 +12:00
view . Refresh ( ) ;
2019-04-22 18:41:43 +12:00
}
}
}
}