diff --git a/ModAssistant.sln b/ModAssistant.sln
new file mode 100644
index 0000000..5643d23
--- /dev/null
+++ b/ModAssistant.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2027
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModAssistant", "ModAssistant\ModAssistant.csproj", "{6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {ACC0D20B-78C5-40AC-BF48-553C41D9987B}
+ EndGlobalSection
+EndGlobal
diff --git a/ModAssistant/App.config b/ModAssistant/App.config
new file mode 100644
index 0000000..1e43f2a
--- /dev/null
+++ b/ModAssistant/App.config
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ModAssistant/App.xaml b/ModAssistant/App.xaml
new file mode 100644
index 0000000..28e6858
--- /dev/null
+++ b/ModAssistant/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/ModAssistant/App.xaml.cs b/ModAssistant/App.xaml.cs
new file mode 100644
index 0000000..88ed435
--- /dev/null
+++ b/ModAssistant/App.xaml.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using ModAssistant;
+
+namespace ModAssistant
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ public static string BeatSaberInstallDirectory;
+ public static string BeatSaberInstallType;
+ public static bool SaveModSelection;
+ public static bool CheckInstalledMods;
+ public static List SavedMods = ModAssistant.Properties.Settings.Default.SavedMods.Split(',').ToList();
+
+
+ private void Application_Startup(object sender, StartupEventArgs e)
+ {
+ BeatSaberInstallDirectory = Utils.GetInstallDir();
+ BeatSaberInstallType = ModAssistant.Properties.Settings.Default.StoreType;
+ SaveModSelection = ModAssistant.Properties.Settings.Default.SaveSelected;
+ //CheckInstalledMods = ModAssistant.Properties.Settings.Default.CheckInstalled;
+
+ if (e.Args.Length == 0)
+ {
+ MainWindow window = new MainWindow();
+ window.Show();
+ }
+ else
+ {
+ ArgumentHandler(e.Args);
+ }
+ }
+
+ private void ArgumentHandler(string[] Args)
+ {
+ Utils.SendNotify(Args[0]);
+ }
+
+ private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
+ {
+ MessageBox.Show("An unhandled exception just occurred: " + e.Exception.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Warning);
+ e.Handled = true;
+ }
+
+ public void RegisterOneClickInstalls ()
+ {
+
+ }
+ }
+}
diff --git a/ModAssistant/Classes/Diagnostics.cs b/ModAssistant/Classes/Diagnostics.cs
new file mode 100644
index 0000000..ccc6947
--- /dev/null
+++ b/ModAssistant/Classes/Diagnostics.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace ModAssistant
+{
+ class Diagnostics
+ {
+ public static string[] ReadFolder(string path, int level = 0)
+ {
+ List entries = new List();
+
+ foreach (string file in Directory.GetFileSystemEntries(path))
+ {
+ string line = String.Empty;
+
+ if (File.Exists(file))
+ {
+ line = Utils.CalculateMD5(file) + " " +LevelSeparator(level) + "├─ " + Path.GetFileName(file);
+ entries.Add(line);
+
+ }
+ else if (Directory.Exists(file))
+ {
+ line = Utils.Constants.MD5Spacer + LevelSeparator(level) + "├─ " + Path.GetFileName(file);
+ entries.Add(line);
+
+ foreach (string entry in ReadFolder(file.Replace(@"\", @"\\"), level + 1))
+ {
+ //MessageBox.Show(entry);
+ entries.Add(entry);
+ }
+
+ }
+ else
+ {
+ MessageBox.Show("! " + file);
+ }
+
+
+ }
+ if (entries.Count > 0)
+ entries[entries.Count - 1] = entries[entries.Count - 1].Replace("├", "└");
+
+ return entries.ToArray();
+ }
+
+ private static string LevelSeparator(int level)
+ {
+ string separator = String.Empty;
+ for(int i = 0; i < level; i++)
+ {
+ separator = separator + "│ ";
+ }
+ return separator;
+ }
+ }
+}
diff --git a/ModAssistant/Classes/Mod.cs b/ModAssistant/Classes/Mod.cs
new file mode 100644
index 0000000..6a85f05
--- /dev/null
+++ b/ModAssistant/Classes/Mod.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using ModAssistant.Pages;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ModAssistant
+{
+ public class Mod
+ {
+ public string name;
+ public string version;
+ public string _id;
+ public string authorId;
+ public string uploadedDate;
+ public string updatedDate;
+ public Author author;
+ public string description;
+ public string link;
+ public string category;
+ public DownloadLink[] downloads;
+ public bool required;
+ public Dependency[] dependencies;
+ public List Dependents = new List();
+ public Mods.ModListItem ListItem;
+
+ public class Author
+ {
+ public string _id;
+ public string username;
+ public string lastLogin;
+ }
+
+ public class DownloadLink
+ {
+ public string type;
+ public string url;
+ public FileHashes[] hashMd5;
+ }
+
+ public class FileHashes
+ {
+ public string hash;
+ public string file;
+ }
+
+ public class Dependency
+ {
+ public string name;
+ public string _id;
+ public Mod Mod;
+ }
+ }
+}
diff --git a/ModAssistant/Classes/OneClickInstaller.cs b/ModAssistant/Classes/OneClickInstaller.cs
new file mode 100644
index 0000000..40d9ffd
--- /dev/null
+++ b/ModAssistant/Classes/OneClickInstaller.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ModAssistant
+{
+ class OneClickInstaller
+ {
+ private const string ModelSaberURLPrefix = "https://modelsaber.com/files";
+
+ private const string CustomAvatarsFolder = "CustomAvatars";
+ private const string CustomSabersFolder = "CustomSabers";
+ private const string CustomPlatformsFolder = "CustomPlatforms";
+
+ private static readonly string[] Protocols = new[] { "modsaber" };
+
+ public static void InstallAsset(string link)
+ {
+ Uri uri = new Uri(link);
+ if (!Protocols.Contains(uri.Scheme)) return;
+
+
+
+ }
+
+ private static bool DownloadAsset(string link, string folder)
+ {
+ string BeatSaberPath = "";
+ if (string.IsNullOrEmpty(BeatSaberPath))
+ {
+ Utils.SendNotify("Beat Saber installation path not found.");
+ return false;
+ }
+ return false;
+ }
+
+
+ }
+}
diff --git a/ModAssistant/Classes/Utils.cs b/ModAssistant/Classes/Utils.cs
new file mode 100644
index 0000000..55affb8
--- /dev/null
+++ b/ModAssistant/Classes/Utils.cs
@@ -0,0 +1,256 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Management;
+using ModAssistant.Properties;
+
+namespace ModAssistant
+{
+ public class Utils
+ {
+ public class Constants
+ {
+ public const string BeatSaberAPPID = "620980";
+ public const string BeatModsAPIUrl = "https://beatmods.com/api/v1/";
+ public const string BeatModsURL = "https://beatmods.com";
+ public const string BeatModsModsOptions = "mod?status=approved";
+ public const string MD5Spacer = " ";
+ }
+
+ public static void SendNotify(string message, string title = "Mod Assistant")
+ {
+ var notification = new System.Windows.Forms.NotifyIcon()
+ {
+ Visible = true,
+ Icon = System.Drawing.SystemIcons.Information,
+ BalloonTipTitle = title,
+ BalloonTipText = message
+ };
+
+ notification.ShowBalloonTip(5000);
+
+ notification.Dispose();
+ }
+
+ public static string CalculateMD5(string filename)
+ {
+ using (var md5 = MD5.Create())
+ {
+ using (var stream = File.OpenRead(filename))
+ {
+ var hash = md5.ComputeHash(stream);
+ return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
+ }
+ }
+ }
+
+ public static string GetInstallDir()
+ {
+ string InstallDir = null;
+
+ InstallDir = Properties.Settings.Default.InstallFolder;
+ if (!String.IsNullOrEmpty(InstallDir))
+ {
+ return InstallDir;
+ }
+
+ InstallDir = GetSteamDir();
+ if (!String.IsNullOrEmpty(InstallDir))
+ {
+ return InstallDir;
+ }
+
+ InstallDir = GetOculusDir();
+ if (!String.IsNullOrEmpty(InstallDir))
+ {
+ return InstallDir;
+ }
+
+ MessageBox.Show("Could not detect your Beat Saber install folder. Please select it manually.");
+
+ InstallDir = GetManualDir();
+ if (!String.IsNullOrEmpty(InstallDir))
+ {
+ return InstallDir;
+ }
+
+ return null;
+ }
+
+ public static string SetDir(string directory, string store)
+ {
+ App.BeatSaberInstallDirectory = directory;
+ App.BeatSaberInstallType = store;
+ Properties.Settings.Default.InstallFolder = directory;
+ Properties.Settings.Default.StoreType = store;
+ Properties.Settings.Default.Save();
+ return directory;
+ }
+
+ public static string GetSteamDir()
+ {
+
+ string SteamInstall = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)?.OpenSubKey("SOFTWARE")?.OpenSubKey("WOW6432Node")?.OpenSubKey("Valve")?.OpenSubKey("Steam")?.GetValue("InstallPath").ToString();
+ if (String.IsNullOrEmpty(SteamInstall))
+ {
+ SteamInstall = Registry.LocalMachine.OpenSubKey("SOFTWARE")?.OpenSubKey("WOW6432Node")?.OpenSubKey("Valve")?.OpenSubKey("Steam")?.GetValue("InstallPath").ToString();
+ }
+ if (String.IsNullOrEmpty(SteamInstall)) return null;
+
+ string vdf = Path.Combine(SteamInstall, @"steamapps\libraryfolders.vdf");
+ if (!File.Exists(@vdf)) return null;
+
+ Regex regex = new Regex("\\s\"\\d\"\\s+\"(.+)\"");
+ List SteamPaths = new List();
+ SteamPaths.Add(Path.Combine(SteamInstall, @"steamapps"));
+
+ using (StreamReader reader = new StreamReader(@vdf))
+ {
+ string line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ Match match = regex.Match(line);
+ if (match.Success)
+ {
+ SteamPaths.Add(Path.Combine(match.Groups[1].Value.Replace(@"\\", @"\"), @"steamapps"));
+ }
+ }
+ }
+
+ regex = new Regex("\\s\"installdir\"\\s+\"(.+)\"");
+ foreach (string path in SteamPaths)
+ {
+ if (File.Exists(Path.Combine(@path, @"appmanifest_" + Constants.BeatSaberAPPID + ".acf")))
+ {
+ using (StreamReader reader = new StreamReader(Path.Combine(@path, @"appmanifest_" + Constants.BeatSaberAPPID + ".acf")))
+ {
+ string line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ Match match = regex.Match(line);
+ if (match.Success)
+ {
+ if (File.Exists(Path.Combine(@path, @"common", match.Groups[1].Value, "Beat Saber.exe")))
+ {
+ return SetDir(Path.Combine(@path, @"common", match.Groups[1].Value), "Steam");
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static string GetOculusDir()
+ {
+ string OculusInstall = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)?.OpenSubKey("SOFTWARE")?.OpenSubKey("Wow6432Node")?.OpenSubKey("Oculus VR, LLC")?.OpenSubKey("Oculus")?.OpenSubKey("Config")?.GetValue("InitialAppLibrary").ToString();
+ if (String.IsNullOrEmpty(OculusInstall)) return null;
+
+ if (!String.IsNullOrEmpty(OculusInstall))
+ {
+ if (File.Exists(Path.Combine(OculusInstall, @"Software\hyperbolic-magnetism-beat-saber", "Beat Saber.exe")))
+ {
+ return SetDir(Path.Combine(OculusInstall + @"Software\hyperbolic-magnetism-beat-saber"), "Oculus");
+ }
+ }
+
+ // Yoinked this code from Umbranox's Mod Manager. Lot's of thanks and love for Umbra <3
+ using (RegistryKey librariesKey = Registry.CurrentUser.OpenSubKey("Software")?.OpenSubKey("Oculus VR, LLC")?.OpenSubKey("Oculus")?.OpenSubKey("Libraries"))
+ {
+ // Oculus libraries uses GUID volume paths like this "\\?\Volume{0fea75bf-8ad6-457c-9c24-cbe2396f1096}\Games\Oculus Apps", we need to transform these to "D:\Game"\Oculus Apps"
+ WqlObjectQuery wqlQuery = new WqlObjectQuery("SELECT * FROM Win32_Volume");
+ ManagementObjectSearcher searcher = new ManagementObjectSearcher(wqlQuery);
+ Dictionary guidLetterVolumes = new Dictionary();
+
+ foreach (ManagementBaseObject disk in searcher.Get())
+ {
+ var diskId = ((string)disk.GetPropertyValue("DeviceID")).Substring(11, 36);
+ var diskLetter = ((string)disk.GetPropertyValue("DriveLetter")) + @"\";
+
+ if (!string.IsNullOrWhiteSpace(diskLetter))
+ {
+ guidLetterVolumes.Add(diskId, diskLetter);
+ }
+ }
+
+ // Search among the library folders
+ foreach (string libraryKeyName in librariesKey.GetSubKeyNames())
+ {
+ using (RegistryKey libraryKey = librariesKey.OpenSubKey(libraryKeyName))
+ {
+ string libraryPath = (string)libraryKey.GetValue("Path");
+ string finalPath = Path.Combine(guidLetterVolumes.First(x => libraryPath.Contains(x.Key)).Value, libraryPath.Substring(49), @"Software\hyperbolic-magnetism-beat-saber");
+
+ if (File.Exists(Path.Combine(finalPath, "Beat Saber.exe")))
+ {
+ return SetDir(finalPath, "Oculus");
+ }
+ }
+ }
+ }
+ return null;
+ }
+ /*
+ public static string GetManualDir()
+ {
+
+ CommonOpenFileDialog dialog = new CommonOpenFileDialog()
+ {
+ IsFolderPicker = true,
+ Multiselect = false,
+ Title = "Select your Beat Saber installation folder"
+ };
+
+ if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
+ {
+ return dialog.FileName;
+ }
+
+ return null;
+ }*/
+
+ public static string GetManualDir()
+ {
+ var dialog = new Microsoft.Win32.SaveFileDialog()
+ {
+ Title = "Select your Beat Saber install folder",
+ Filter = "Directory|*.this.directory",
+ FileName = "select"
+ };
+
+ if (dialog.ShowDialog() == true)
+ {
+ string path = dialog.FileName;
+ path = path.Replace("\\select.this.directory", "");
+ path = path.Replace(".this.directory", "");
+ if (!System.IO.Directory.Exists(path))
+ {
+ System.IO.Directory.CreateDirectory(path);
+ }
+ if (File.Exists(Path.Combine(path, "Beat Saber.exe")))
+ {
+ string store;
+ if (File.Exists(Path.Combine(path, "Beat Saber_Data", "Plugins", "steam_api64.dll")))
+ {
+ store = "Steam";
+ }
+ else
+ {
+ store = "Oculus";
+ }
+ return SetDir(path, store);
+ }
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/ModAssistant/MainWindow.xaml b/ModAssistant/MainWindow.xaml
new file mode 100644
index 0000000..ca2257d
--- /dev/null
+++ b/ModAssistant/MainWindow.xaml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ModAssistant/MainWindow.xaml.cs b/ModAssistant/MainWindow.xaml.cs
new file mode 100644
index 0000000..5a9af60
--- /dev/null
+++ b/ModAssistant/MainWindow.xaml.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+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.Net;
+using System.IO;
+using System.Web.Script.Serialization;
+using System.Runtime.Serialization;
+using ModAssistant.Pages;
+
+namespace ModAssistant
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public static MainWindow Instance;
+ //private delegate void SetStatusCallback(string message);
+ //public static Mods ModWindow = new Mods();
+
+ public string MainText
+ {
+ get
+ {
+ return MainTextBlock.Text;
+ }
+ set
+ {
+ Dispatcher.Invoke(new Action(() => { MainWindow.Instance.MainTextBlock.Text = value; }));
+ }
+ }
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ Instance = this;
+
+ if (Properties.Settings.Default.Agreed)
+ {
+ MainWindow.Instance.ModsButton.IsEnabled = true;
+ }
+
+ //Main.Content = Mods.Instance;
+ Main.Content = Intro.Instance;
+ }
+
+ private void ModsButton_Click(object sender, RoutedEventArgs e)
+ {
+ Main.Content = Mods.Instance;
+ }
+
+ private void IntroButton_Click(object sender, RoutedEventArgs e)
+ {
+ Main.Content = Intro.Instance;
+ }
+
+ private void AboutButton_Click(object sender, RoutedEventArgs e)
+ {
+ Main.Content = About.Instance;
+ }
+
+ private void OptionsButton_Click(object sender, RoutedEventArgs e)
+ {
+ Main.Content = Options.Instance;
+ }
+
+ private void InstallButton_Click(object sender, RoutedEventArgs e)
+ {
+ Mods.Instance.InstallMods();
+ }
+
+ private void InfoButton_Click(object sender, RoutedEventArgs e)
+ {
+ Mods.ModListItem mod = ((Mods.ModListItem)Mods.Instance.ModsListView.SelectedItem);
+ string infoUrl = mod.ModInfo.link;
+ if (String.IsNullOrEmpty(infoUrl))
+ {
+ MessageBox.Show(mod.ModName + " does not have an info page");
+ }
+ else
+ {
+ System.Diagnostics.Process.Start(infoUrl);
+ }
+ }
+ }
+}
diff --git a/ModAssistant/ModAssistant.csproj b/ModAssistant/ModAssistant.csproj
new file mode 100644
index 0000000..5c5cb52
--- /dev/null
+++ b/ModAssistant/ModAssistant.csproj
@@ -0,0 +1,147 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}
+ WinExe
+ ModAssistant
+ ModAssistant
+ v4.6.1
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ Resources\icon.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+ Intro.xaml
+
+
+
+ Mods.xaml
+
+
+ About.xaml
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+
+ MainWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ Options.xaml
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+ PublicSettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ModAssistant/Pages/About.xaml b/ModAssistant/Pages/About.xaml
new file mode 100644
index 0000000..c7e244b
--- /dev/null
+++ b/ModAssistant/Pages/About.xaml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ About Mod Assistant
+
+ I'm Assistant, and I made Mod Assistant as an alternative to the
+
+ Mod Manager
+
+ and
+
+ BeatDrop
+
+ with a few principles in mind:
+
+
+
+
+
+
+
+ If you enjoy this program and would like to support me, please visit my
+
+ donation page
+
+ or my
+
+ Patreon
+
+
+
+
+
+
+
+
+
+
+ vanZeben
+
+
+ Creating BeatMods
+ The current Mod repository
+ Used by this Installer
+
+
+
+
+
+
+ Umbranox
+
+
+ Inspiration
+ Creating the Mod Manager
+
+
+
+
+
+ lolPants
+
+
+ Inspiration
+ Creating ModSaber
+ The first Mod repository
+
+
+ Donate
+
+
+
+
+
+
+
+
+
diff --git a/ModAssistant/Pages/About.xaml.cs b/ModAssistant/Pages/About.xaml.cs
new file mode 100644
index 0000000..01ed5c6
--- /dev/null
+++ b/ModAssistant/Pages/About.xaml.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+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.Windows.Shapes;
+
+namespace ModAssistant.Pages
+{
+ ///
+ /// Interaction logic for Page1.xaml
+ ///
+ public partial class About : Page
+ {
+ public static About Instance = new About();
+
+ public About()
+ {
+ InitializeComponent();
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
+ e.Handled = true;
+ }
+ }
+}
diff --git a/ModAssistant/Pages/Intro.xaml b/ModAssistant/Pages/Intro.xaml
new file mode 100644
index 0000000..26b1253
--- /dev/null
+++ b/ModAssistant/Pages/Intro.xaml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to Mod Assistant
+
+
+
+
+ Beat Saber does not natively support mods. This means:
+
+
+
+ Mods will break every update. This is normal, and not Beat Games' fault.
+
+
+
+ Mods will cause bugs and performance issues. This is not Beat Games' fault.
+
+
+
+ Mods are made for free by people in their free time. Please be patient and understanding.
+
+
+
+ DO NOT leave negative reviews because mods broke. This is not Beat Games' fault. They are not trying to kill mods.
+
+
+
+ If I keep seeing people leave negative reviews because mods broke, I will personally kill mods with a rusty spoon
+
+
+
+ Please read the Beginners Guide on the
+
+ Wiki
+ .
+
+
+
+
+
+
+
+
diff --git a/ModAssistant/Pages/Intro.xaml.cs b/ModAssistant/Pages/Intro.xaml.cs
new file mode 100644
index 0000000..049ade7
--- /dev/null
+++ b/ModAssistant/Pages/Intro.xaml.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+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.Windows.Shapes;
+
+namespace ModAssistant.Pages
+{
+ ///
+ /// Interaction logic for Intro.xaml
+ ///
+ public partial class Intro : Page
+ {
+ public static Intro Instance = new Intro();
+
+ public Intro()
+ {
+ InitializeComponent();
+ }
+
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
+ e.Handled = true;
+ }
+
+ private void Disagree_Click(object sender, RoutedEventArgs e)
+ {
+ MessageBox.Show("Closing Application: You did not agree to terms and conditions.");
+ System.Windows.Application.Current.Shutdown();
+ }
+
+ private void Agree_Click(object sender, RoutedEventArgs e)
+ {
+ MainWindow.Instance.ModsButton.IsEnabled = true;
+ Properties.Settings.Default.Agreed = true;
+ Properties.Settings.Default.Save();
+ }
+ }
+}
diff --git a/ModAssistant/Pages/Mods.xaml b/ModAssistant/Pages/Mods.xaml
new file mode 100644
index 0000000..b4308b1
--- /dev/null
+++ b/ModAssistant/Pages/Mods.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ModAssistant/Pages/Mods.xaml.cs b/ModAssistant/Pages/Mods.xaml.cs
new file mode 100644
index 0000000..0cabf36
--- /dev/null
+++ b/ModAssistant/Pages/Mods.xaml.cs
@@ -0,0 +1,460 @@
+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.Windows.Shapes;
+using System.IO.Compression;
+using System.Diagnostics;
+using System.Windows.Forms;
+
+namespace ModAssistant.Pages
+{
+ ///
+ /// Interaction logic for Mods.xaml
+ ///
+ public sealed partial class Mods : Page
+ {
+ public static Mods Instance = new Mods();
+
+ public string[] DefaultMods = { "SongLoader", "ScoreSaber", "BeatSaverDownloader" };
+ public Mod[] ModsList;
+ public static List InstalledMods = new List();
+ public List CategoryNames = new List();
+ public CollectionView view;
+
+ public List ModList { get; set; }
+
+ public Mods()
+ {
+ InitializeComponent();
+
+ ModList = new List();
+
+ LoadMods();
+ }
+
+ private void RefreshModsList()
+ {
+ view.Refresh();
+ }
+
+ private async void LoadMods()
+ {
+ if (App.CheckInstalledMods)
+ {
+ MainWindow.Instance.MainText = "Checking Installed Mods...";
+ await Task.Run(() => CheckInstalledMods());
+ } 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();
+ MainWindow.Instance.MainText = "Finished Loading Mods.";
+ MainWindow.Instance.InstallButton.IsEnabled = true;
+ }
+
+ private void CheckInstalledMods()
+ {
+ List empty = new List();
+ CheckInstallDir("Plugins", empty);
+ CheckInstallDir(@"IPA\Libs", empty);
+ }
+
+ private void CheckInstallDir(string directory, List blacklist)
+ {
+ foreach (string file in Directory.GetFileSystemEntries(System.IO.Path.Combine(App.BeatSaberInstallDirectory, directory)))
+ {
+ if (File.Exists(file) && System.IO.Path.GetExtension(file) == ".dll")
+ {
+ Mod mod = GetModFromHash(Utils.CalculateMD5(file));
+ if (mod != null)
+ {
+ if (!InstalledMods.Contains(mod))
+ {
+ InstalledMods.Add(mod);
+ }
+ }
+ }
+ }
+ }
+
+ private Mod GetModFromHash(string hash)
+ {
+ string json = string.Empty;
+ Mod[] modMatches;
+
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Utils.Constants.BeatModsAPIUrl + "mod?hash=" + hash);
+ request.AutomaticDecompression = DecompressionMethods.GZip;
+
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ using (Stream stream = response.GetResponseStream())
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ var serializer = new JavaScriptSerializer();
+ modMatches = serializer.Deserialize(reader.ReadToEnd());
+ }
+
+ if (modMatches.Length == 0)
+ return null;
+
+ return modMatches[0];
+ }
+
+ public void PopulateModsList()
+ {
+ string json = string.Empty;
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Utils.Constants.BeatModsAPIUrl + Utils.Constants.BeatModsModsOptions);
+ request.AutomaticDecompression = DecompressionMethods.GZip;
+
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
+ using (Stream stream = response.GetResponseStream())
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ var serializer = new JavaScriptSerializer();
+ ModsList = serializer.Deserialize(reader.ReadToEnd());
+ }
+
+ 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,
+ ModDescription = mod.description.Replace('\n', ' '),
+ ModInfo = mod,
+ Category = mod.category
+ };
+
+ foreach (Mod installedMod in InstalledMods)
+ {
+ if (mod.name == installedMod.name)
+ {
+ 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}.";
+ await Task.Run(() =>
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = System.IO.Path.Combine(installDirectory, "IPA.exe"),
+ WorkingDirectory = installDirectory,
+ Arguments = "-n"
+ }).WaitForExit()
+ );
+ }
+ else if(mod.ListItem.IsSelected)
+ {
+ MainWindow.Instance.MainText = $"Installing {mod.name}...";
+ await Task.Run(() => InstallMod(mod, System.IO.Path.Combine(installDirectory, @"IPA\Pending")));
+ MainWindow.Instance.MainText = $"Installed {mod.name}.";
+ }
+ }
+ MainWindow.Instance.MainText = "Finished installing mods.";
+ MainWindow.Instance.InstallButton.IsEnabled = true;
+ }
+
+ 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;
+ }
+
+ using (MemoryStream stream = new MemoryStream(DownloadMod(Utils.Constants.BeatModsURL + mod.downloads[0].url)))
+ {
+ using (ZipArchive archive = new ZipArchive(stream))
+ {
+ foreach (ZipArchiveEntry file in archive.Entries)
+ {
+ string fileDirectory = System.IO.Path.GetDirectoryName(System.IO.Path.Combine(directory, file.FullName));
+ if (!Directory.Exists(fileDirectory))
+ Directory.CreateDirectory(fileDirectory);
+
+ if(!String.IsNullOrEmpty(file.Name))
+ file.ExtractToFile(System.IO.Path.Combine(directory, file.FullName), true);
+ }
+ }
+ }
+ }
+
+ 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 Mods = new List();
+ }
+
+ 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; }
+
+ public bool IsInstalled { get; set; }
+ private string _installedVersion { get; set; }
+ public string InstalledVersion {
+ get
+ {
+ if (String.IsNullOrEmpty(_installedVersion) || !IsInstalled)
+ {
+ return "-";
+ }
+ else
+ {
+ return _installedVersion;
+ }
+ }
+ set
+ {
+ _installedVersion = value;
+ }
+ }
+ public string IsOutdated {
+ get
+ {
+ if (IsInstalled)
+ {
+ if (InstalledVersion == ModVersion)
+ {
+ return "Green"; //green
+ } else
+ {
+ return "Red"; //red
+ }
+ }
+ else
+ {
+ return "Black"; //grey
+ }
+ }
+ }
+
+ 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;
+ }
+
+ private void Uninstall_Click(object sender, RoutedEventArgs e)
+ {
+ Mod mod = ((sender as System.Windows.Controls.Button).Tag as Mod);
+ if (System.Windows.Forms.MessageBox.Show($"Uninstall {mod.name}?", $"Are you sure you want to remove {mod.name}?\nThis can break other of your mods.", MessageBoxButtons.YesNo) == DialogResult.Yes)
+ {
+ Mod.DownloadLink links = null;
+ foreach (Mod.DownloadLink link in mod.downloads)
+ {
+ if (link.type.ToLower() == "universal" || link.type.ToLower() == App.BeatSaberInstallType.ToLower())
+ {
+ links = link;
+ break;
+ }
+ }
+ foreach (Mod.FileHashes files in links.hashMd5)
+ {
+ File.Delete(System.IO.Path.Combine(App.BeatSaberInstallDirectory, files.file));
+ }
+ mod.ListItem.IsInstalled = false;
+ mod.ListItem.InstalledVersion = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ModAssistant/Pages/Options.xaml b/ModAssistant/Pages/Options.xaml
new file mode 100644
index 0000000..1e2abc1
--- /dev/null
+++ b/ModAssistant/Pages/Options.xaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ModAssistant/Pages/Options.xaml.cs b/ModAssistant/Pages/Options.xaml.cs
new file mode 100644
index 0000000..61d1269
--- /dev/null
+++ b/ModAssistant/Pages/Options.xaml.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+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.Windows.Shapes;
+using System.Globalization;
+
+namespace ModAssistant.Pages
+{
+ ///
+ /// Interaction logic for Options.xaml
+ ///
+ ///
+ public partial class Options : Page
+ {
+ public static Options Instance = new Options();
+
+ public string InstallDirectory { get; set; }
+ public string InstallType { get; set; }
+ public bool SaveSelection { get; set; }
+ public bool CheckInstalledMods { get; set; }
+
+ public Options()
+ {
+ InitializeComponent();
+ InstallDirectory = App.BeatSaberInstallDirectory;
+ InstallType = App.BeatSaberInstallType;
+ SaveSelection = App.SaveModSelection;
+ CheckInstalledMods = App.CheckInstalledMods;
+
+ this.DataContext = this;
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ Utils.GetManualDir();
+ }
+
+ private void Test_Click(object sender, RoutedEventArgs e)
+ {
+ MessageBox.Show(Utils.GetSteamDir());
+ }
+
+ private void SaveSelected_Checked(object sender, RoutedEventArgs e)
+ {
+ Properties.Settings.Default.SaveSelected = true;
+ App.SaveModSelection = true;
+ Properties.Settings.Default.Save();
+ }
+
+ private void SaveSelected_Unchecked(object sender, RoutedEventArgs e)
+ {
+ Properties.Settings.Default.SaveSelected = false;
+ App.SaveModSelection = false;
+ Properties.Settings.Default.Save();
+ }
+
+ private void CheckInstalled_Checked(object sender, RoutedEventArgs e)
+ {
+ Properties.Settings.Default.CheckInstalled = true;
+ App.CheckInstalledMods = true;
+ CheckInstalledMods = true;
+ Properties.Settings.Default.Save();
+ }
+
+ private void CheckInstalled_Unchecked(object sender, RoutedEventArgs e)
+ {
+ Properties.Settings.Default.CheckInstalled = false;
+ App.CheckInstalledMods = false;
+ CheckInstalledMods = false;
+ Properties.Settings.Default.Save();
+ }
+
+ private void ProtocolHandler_Checked(object sender, RoutedEventArgs e)
+ {
+
+ }
+
+ private void ProtocolHandler_Unchecked(object sender, RoutedEventArgs e)
+ {
+
+ }
+ }
+}
diff --git a/ModAssistant/Properties/AssemblyInfo.cs b/ModAssistant/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e07ded2
--- /dev/null
+++ b/ModAssistant/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ModAssistant")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ModAssistant")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ModAssistant/Properties/Resources.Designer.cs b/ModAssistant/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..3e64039
--- /dev/null
+++ b/ModAssistant/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ModAssistant.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModAssistant.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/ModAssistant/Properties/Resources.resx b/ModAssistant/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/ModAssistant/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/ModAssistant/Properties/Settings.Designer.cs b/ModAssistant/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..81fc917
--- /dev/null
+++ b/ModAssistant/Properties/Settings.Designer.cs
@@ -0,0 +1,98 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ModAssistant.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")]
+ public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string InstallFolder {
+ get {
+ return ((string)(this["InstallFolder"]));
+ }
+ set {
+ this["InstallFolder"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string StoreType {
+ get {
+ return ((string)(this["StoreType"]));
+ }
+ set {
+ this["StoreType"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool SaveSelected {
+ get {
+ return ((bool)(this["SaveSelected"]));
+ }
+ set {
+ this["SaveSelected"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool CheckInstalled {
+ get {
+ return ((bool)(this["CheckInstalled"]));
+ }
+ set {
+ this["CheckInstalled"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public string SavedMods {
+ get {
+ return ((string)(this["SavedMods"]));
+ }
+ set {
+ this["SavedMods"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool Agreed {
+ get {
+ return ((bool)(this["Agreed"]));
+ }
+ set {
+ this["Agreed"] = value;
+ }
+ }
+ }
+}
diff --git a/ModAssistant/Properties/Settings.settings b/ModAssistant/Properties/Settings.settings
new file mode 100644
index 0000000..0e62bef
--- /dev/null
+++ b/ModAssistant/Properties/Settings.settings
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
\ No newline at end of file
diff --git a/ModAssistant/Resources/About.png b/ModAssistant/Resources/About.png
new file mode 100644
index 0000000..437031d
Binary files /dev/null and b/ModAssistant/Resources/About.png differ
diff --git a/ModAssistant/Resources/Intro.png b/ModAssistant/Resources/Intro.png
new file mode 100644
index 0000000..81b838f
Binary files /dev/null and b/ModAssistant/Resources/Intro.png differ
diff --git a/ModAssistant/Resources/Mods.png b/ModAssistant/Resources/Mods.png
new file mode 100644
index 0000000..0bb0e29
Binary files /dev/null and b/ModAssistant/Resources/Mods.png differ
diff --git a/ModAssistant/Resources/Options.png b/ModAssistant/Resources/Options.png
new file mode 100644
index 0000000..291248c
Binary files /dev/null and b/ModAssistant/Resources/Options.png differ
diff --git a/ModAssistant/Resources/icon.ico b/ModAssistant/Resources/icon.ico
new file mode 100644
index 0000000..59d8591
Binary files /dev/null and b/ModAssistant/Resources/icon.ico differ