├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ └── dotnet.yml
├── LICENSE
├── ModAssistant.sln
├── ModAssistant
├── App.config
├── App.xaml
├── App.xaml.cs
├── Classes
│ ├── Diagnostics.cs
│ ├── External Interfaces
│ │ ├── BeatSaver.cs
│ │ ├── ModelSaber.cs
│ │ ├── Playlists.cs
│ │ └── Utils.cs
│ ├── Http.cs
│ ├── HyperlinkExtensions.cs
│ ├── Languages.cs
│ ├── Mod.cs
│ ├── OneClickInstaller.cs
│ ├── Promotions.cs
│ ├── Themes.cs
│ ├── Updater.cs
│ └── Utils.cs
├── Libs
│ └── semver
│ │ ├── IntExtensions.cs
│ │ └── SemVersion.cs
├── Localisation
│ ├── cs.xaml
│ ├── de.xaml
│ ├── en-DEBUG.xaml
│ ├── en.xaml
│ ├── es.xaml
│ ├── fr.xaml
│ ├── it.xaml
│ ├── ja.xaml
│ ├── ko.xaml
│ ├── nb.xaml
│ ├── nl.xaml
│ ├── pl.xaml
│ ├── pt.xaml
│ ├── ru.xaml
│ ├── sv.xaml
│ ├── th.xaml
│ └── zh.xaml
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── ModAssistant.csproj
├── OneClickStatus.xaml
├── OneClickStatus.xaml.cs
├── Pages
│ ├── About.xaml
│ ├── About.xaml.cs
│ ├── Intro.xaml
│ ├── Intro.xaml.cs
│ ├── Invalid.xaml
│ ├── Invalid.xaml.cs
│ ├── Loading.xaml
│ ├── Loading.xaml.cs
│ ├── Mods.xaml
│ ├── Mods.xaml.cs
│ ├── Options.xaml
│ └── Options.xaml.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Resources
│ ├── Icons.xaml
│ └── icon.ico
├── Styles
│ ├── Button.xaml
│ ├── CheckBox.xaml
│ ├── ComboBox.xaml
│ ├── ComboBoxItem.xaml
│ ├── GridViewColumnHeader.xaml
│ ├── Label.xaml
│ ├── ListView.xaml
│ ├── ListViewItem.xaml
│ ├── Menu.xaml
│ ├── MenuItem.xaml
│ ├── RepeatButton.xaml
│ ├── ScrollBar.xaml
│ ├── TextBlock.xaml
│ ├── Thumb.xaml
│ └── ToggleButton.xaml
└── Themes
│ ├── Anniversary.xaml
│ ├── Anniversary
│ └── Background.png
│ ├── BSMG.xaml
│ ├── BSMG
│ └── Sidebar.png
│ ├── Dark.xaml
│ ├── Default Scrollbar.xaml
│ ├── Light Pink.xaml
│ ├── Light.xaml
│ └── Ugly Kulu-Ya-Ku.xaml
├── README.md
└── tools
├── README.md
└── generate_translation_stubs.py
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.cs]
12 | dotnet_sort_system_directives_first = true
13 | dotnet_separate_import_directive_groups = false
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [*.yml]
19 | indent_size = 2
20 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: BeatSaberMods
2 | ko_fi: N4N8JX7B
3 | liberapay: Assistant
4 | custom: ['https://paypal.me/AssistantMoe', 'https://bs.assistant.moe/Donate/']
5 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: .NET Build
2 | on:
3 | push:
4 | pull_request:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: windows-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 | - name: Setup msbuild
15 | uses: microsoft/setup-msbuild@v1.1.3
16 | - name: Install dependencies
17 | run: msbuild -t:restore
18 | - name: Build project
19 | run: msbuild ModAssistant/ModAssistant.csproj /t:Build /p:Configuration=Release
20 | - name: Cleanup release
21 | shell: bash
22 | run: |
23 | find "ModAssistant/bin/Release" -type f ! -name "ModAssistant.exe" -delete
24 | cp "LICENSE" "ModAssistant/bin/Release/LICENSE.ModAssistant.txt"
25 | - name: Upload Build
26 | if: startsWith(github.ref, 'refs/tags/') == false
27 | uses: actions/upload-artifact@v3
28 | with:
29 | name: ModAssistant-${{ github.sha }}
30 | path: ./ModAssistant/bin/Release/
31 | - name: Extract Release Version
32 | if: startsWith(github.ref, 'refs/tags/')
33 | id: get_version
34 | shell: bash
35 | run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
36 | - name: Release
37 | if: startsWith(github.ref, 'refs/tags/')
38 | uses: softprops/action-gh-release@v1
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 | with:
42 | name: Mod Assistant v${{ steps.get_version.outputs.version }}
43 | files: ./ModAssistant/bin/Release/ModAssistant.exe
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2020 Assistant
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ModAssistant.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2027
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModAssistant", "ModAssistant\ModAssistant.csproj", "{6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {ACC0D20B-78C5-40AC-BF48-553C41D9987B}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/ModAssistant/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | False
22 |
23 |
24 | True
25 |
26 |
27 | False
28 |
29 |
30 | False
31 |
32 |
33 | False
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | True
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | False
52 |
53 |
54 | True
55 |
56 |
57 | False
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/ModAssistant/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/Diagnostics.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Windows;
4 |
5 | namespace ModAssistant
6 | {
7 | class Diagnostics
8 | {
9 | public static string[] ReadFolder(string path, int level = 0)
10 | {
11 | List entries = new List();
12 |
13 | foreach (string file in Directory.GetFileSystemEntries(path))
14 | {
15 | string line = string.Empty;
16 |
17 | if (File.Exists(file))
18 | {
19 | line = Utils.CalculateMD5(file) + " " + LevelSeparator(level) + "├─ " + Path.GetFileName(file);
20 | entries.Add(line);
21 |
22 | }
23 | else if (Directory.Exists(file))
24 | {
25 | line = Utils.Constants.MD5Spacer + LevelSeparator(level) + "├─ " + Path.GetFileName(file);
26 | entries.Add(line);
27 |
28 | foreach (string entry in ReadFolder(file.Replace(@"\", @"\\"), level + 1))
29 | {
30 | //MessageBox.Show(entry);
31 | entries.Add(entry);
32 | }
33 |
34 | }
35 | else
36 | {
37 | MessageBox.Show("! " + file);
38 | }
39 | }
40 | if (entries.Count > 0)
41 | {
42 | entries[entries.Count - 1] = entries[entries.Count - 1].Replace("├", "└");
43 | }
44 |
45 | return entries.ToArray();
46 | }
47 |
48 | private static string LevelSeparator(int level)
49 | {
50 | string separator = string.Empty;
51 | for (int i = 0; i < level; i++)
52 | {
53 | separator += "│ ";
54 | }
55 | return separator;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/External Interfaces/ModelSaber.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ModAssistant.API
5 | {
6 | class ModelSaber
7 | {
8 | private const string ModelSaberURLPrefix = "https://modelsaber.com/files/";
9 | private const string CustomAvatarsFolder = "CustomAvatars";
10 | private const string CustomSabersFolder = "CustomSabers";
11 | private const string CustomPlatformsFolder = "CustomPlatforms";
12 | private const string CustomBloqsFolder = "CustomNotes";
13 |
14 | public static async Task GetModel(Uri uri)
15 | {
16 | switch (uri.Host)
17 | {
18 | case "avatar":
19 | await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomAvatarsFolder);
20 | break;
21 | case "saber":
22 | await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomSabersFolder);
23 | break;
24 | case "platform":
25 | await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomPlatformsFolder);
26 | break;
27 | case "bloq":
28 | await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomBloqsFolder);
29 | break;
30 | }
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/External Interfaces/Playlists.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows;
6 | using static ModAssistant.Http;
7 |
8 | namespace ModAssistant.API
9 | {
10 | public class Playlists
11 | {
12 | private const string BSaberURLPrefix = "https://bsaber.com/PlaylistAPI/";
13 | private const string PlaylistsFolder = "Playlists";
14 | private static readonly string BeatSaberPath = Utils.BeatSaberPath;
15 |
16 | public static void CreatePlaylistsFolder()
17 | {
18 | string playlistsPath = Path.Combine(BeatSaberPath, PlaylistsFolder);
19 | Directory.CreateDirectory(playlistsPath);
20 | }
21 |
22 | public static async Task DownloadAll(Uri uri)
23 | {
24 | switch (uri.Host)
25 | {
26 | case "playlist":
27 | Uri url = new Uri($"{uri.PathAndQuery.Trim('/')}");
28 | string filename = await Get(url);
29 | await DownloadFrom(filename);
30 | break;
31 | }
32 | }
33 |
34 | public static async Task Get(Uri url)
35 | {
36 | try
37 | {
38 | CreatePlaylistsFolder();
39 | string filename = await Utils.DownloadAsset(url.ToString(), PlaylistsFolder, preferContentDisposition: true);
40 | return Path.Combine(BeatSaberPath, PlaylistsFolder, filename);
41 | }
42 | catch
43 | {
44 | return null;
45 | }
46 | }
47 |
48 | public static async Task DownloadFrom(string file)
49 | {
50 | CreatePlaylistsFolder();
51 |
52 | if (Path.Combine(BeatSaberPath, PlaylistsFolder) != Path.GetDirectoryName(file))
53 | {
54 | string destination = Path.Combine(BeatSaberPath, PlaylistsFolder, Path.GetFileName(file));
55 | File.Copy(file, destination, true);
56 | }
57 |
58 | int Errors = 0;
59 | int Minimum = 0;
60 | int Value = 0;
61 |
62 | Playlist playlist = JsonSerializer.Deserialize(File.ReadAllText(file));
63 | int Maximum = playlist.songs.Length;
64 |
65 | foreach (Playlist.Song song in playlist.songs)
66 | {
67 | API.BeatSaver.BeatSaverMap response = new BeatSaver.BeatSaverMap();
68 | if (!string.IsNullOrEmpty(song.hash))
69 | {
70 | response = await BeatSaver.GetFromHash(song.hash, false);
71 | }
72 | else if (!string.IsNullOrEmpty(song.key))
73 | {
74 | response = await BeatSaver.GetFromKey(song.key, false);
75 | }
76 | Value++;
77 |
78 | if (response.Success)
79 | {
80 | Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))} {response.Name}");
81 | }
82 | else
83 | {
84 | Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}");
85 | ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash} | ({response?.response?.ratelimit?.Remaining})");
86 | App.CloseWindowOnFinish = false;
87 | await Task.Delay(3 * 1000);
88 | Errors++;
89 | }
90 | }
91 | Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}");
92 | }
93 |
94 | private static string TextProgress(int min, int max, int value)
95 | {
96 | if (max == value)
97 | {
98 | return $" {string.Concat(Enumerable.Repeat("▒", 10))} [{value}/{max}]";
99 | }
100 | int interval = (int)Math.Floor((double)value / (((double)max - (double)min) / (double)10));
101 | return $" {string.Concat(Enumerable.Repeat("▒", interval))}{string.Concat(Enumerable.Repeat("░", 10 - interval))} [{value}/{max}]";
102 | }
103 |
104 | #pragma warning disable IDE1006 // Naming Styles
105 | class Playlist
106 | {
107 | public string playlistTitle { get; set; }
108 | public string playlistAuthor { get; set; }
109 | public string image { get; set; }
110 | public Song[] songs { get; set; }
111 |
112 | public class Song
113 | {
114 | public string key { get; set; }
115 | public string hash { get; set; }
116 | public string songName { get; set; }
117 | public string uploader { get; set; }
118 | }
119 | }
120 | }
121 | }
122 | #pragma warning restore IDE1006 // Naming Styles
123 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/External Interfaces/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 |
8 | namespace ModAssistant.API
9 | {
10 | public class Utils
11 | {
12 | public static readonly string BeatSaberPath = App.BeatSaberInstallDirectory;
13 |
14 | public static void SetMessage(string message)
15 | {
16 | if (App.OCIWindow != "No" || App.OCIWindow != "Notify")
17 | {
18 | if (App.window == null)
19 | {
20 | if (App.OCIWindow == "No" || App.OCIWindow == "Notify") OneClickStatus.Instance = null;
21 | if (OneClickStatus.Instance == null) return;
22 |
23 | OneClickStatus.Instance.MainText = message;
24 | }
25 | else
26 | {
27 | MainWindow.Instance.MainText = message;
28 | }
29 | }
30 | }
31 |
32 | public static async Task DownloadAsset(string link, string folder, string fileName = null, string displayName = null, bool showNotification = true, bool beatsaver = false, bool preferContentDisposition = false)
33 | {
34 | if (string.IsNullOrEmpty(BeatSaberPath))
35 | {
36 | ModAssistant.Utils.SendNotify((string)Application.Current.FindResource("OneClick:InstallDirNotFound"));
37 | }
38 | try
39 | {
40 | var parentDir = Path.Combine(BeatSaberPath, folder);
41 | Directory.CreateDirectory(parentDir);
42 |
43 | if (string.IsNullOrEmpty(fileName))
44 | {
45 | fileName = new Uri(link).Segments.Last();
46 | }
47 |
48 | if (beatsaver)
49 | {
50 | fileName = WebUtility.UrlDecode(Path.Combine(parentDir, fileName));
51 | await BeatSaver.Download(link, fileName);
52 | }
53 | else
54 | {
55 | fileName = await ModAssistant.Utils.Download(link, parentDir, fileName, preferContentDisposition);
56 | }
57 |
58 | if (string.IsNullOrEmpty(displayName))
59 | {
60 | displayName = Path.GetFileNameWithoutExtension(fileName);
61 | }
62 |
63 | if (showNotification)
64 | {
65 | SetMessage(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName));
66 | }
67 |
68 | return fileName;
69 | }
70 | catch
71 | {
72 | SetMessage((string)Application.Current.FindResource("OneClick:AssetInstallFailed"));
73 | App.CloseWindowOnFinish = false;
74 | return null;
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/Http.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Web.Script.Serialization;
5 |
6 | namespace ModAssistant
7 | {
8 | static class Http
9 | {
10 | private static HttpClient _client = null;
11 |
12 | public static HttpClient HttpClient
13 | {
14 | get
15 | {
16 | if (_client != null) return _client;
17 |
18 | var handler = new HttpClientHandler()
19 | {
20 | AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
21 | };
22 |
23 | _client = new HttpClient(handler)
24 | {
25 | Timeout = TimeSpan.FromSeconds(240),
26 | };
27 |
28 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
29 | _client.DefaultRequestHeaders.Add("User-Agent", "ModAssistant/" + App.Version);
30 |
31 | return _client;
32 | }
33 | }
34 |
35 | public static JavaScriptSerializer JsonSerializer = new JavaScriptSerializer()
36 | {
37 | MaxJsonLength = int.MaxValue,
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/HyperlinkExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using System.Windows.Documents;
4 |
5 | namespace ModAssistant
6 | {
7 | public static class HyperlinkExtensions
8 | {
9 | public static bool GetIsExternal(DependencyObject obj)
10 | {
11 | return (bool)obj.GetValue(IsExternalProperty);
12 | }
13 |
14 | public static void SetIsExternal(DependencyObject obj, bool value)
15 | {
16 | obj.SetValue(IsExternalProperty, value);
17 | }
18 |
19 | public static readonly DependencyProperty IsExternalProperty = DependencyProperty.RegisterAttached("IsExternal", typeof(bool), typeof(HyperlinkExtensions), new UIPropertyMetadata(false, OnIsExternalChanged));
20 |
21 | private static void OnIsExternalChanged(object sender, DependencyPropertyChangedEventArgs args)
22 | {
23 | var hyperlink = sender as Hyperlink;
24 |
25 | if ((bool)args.NewValue)
26 | hyperlink.RequestNavigate += Hyperlink_RequestNavigate;
27 | else
28 | hyperlink.RequestNavigate -= Hyperlink_RequestNavigate;
29 | }
30 |
31 | private static void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
32 | {
33 | Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
34 | e.Handled = true;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/Languages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Windows;
7 | using ModAssistant.Pages;
8 |
9 | namespace ModAssistant
10 | {
11 | internal class Languages
12 | {
13 | public static string LoadedLanguage { get; private set; }
14 | public static List LoadedLanguages => availableCultures.ToList();
15 | public static bool FirstRun = true;
16 | private static readonly string[] availableLanguageCodes = { "cs", "de", "en", "es", "fr", "it", "ja", "ko", "nb", "nl", "pl", "pt", "ru", "sv", "th", "zh" };
17 | private static IEnumerable availableCultures;
18 |
19 | public static void LoadLanguages()
20 | {
21 | var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
22 |
23 | // Get CultureInfo for any of the available translations
24 | availableCultures = allCultures.Where(cultureInfo => availableLanguageCodes.Any(code => code.Equals(cultureInfo.Name)));
25 |
26 | string savedLanguageCode = Properties.Settings.Default.LanguageCode;
27 | if (!LoadLanguage(savedLanguageCode))
28 | {
29 | // If no language code was saved, load system language
30 | if (!LoadLanguage(CultureInfo.CurrentUICulture.Name))
31 | {
32 | _ = LoadLanguage("en");
33 | }
34 | }
35 |
36 | UpdateUI(LoadedLanguage);
37 | }
38 |
39 | public static void UpdateUI(string languageCode)
40 | {
41 | if (Options.Instance != null && Options.Instance.LanguageSelectComboBox != null)
42 | {
43 | Options.Instance.LanguageSelectComboBox.ItemsSource = availableCultures.Select(cultureInfo => cultureInfo.NativeName).ToList();
44 | Options.Instance.LanguageSelectComboBox.SelectedIndex = LoadedLanguages.FindIndex(cultureInfo => cultureInfo.Name.Equals(languageCode));
45 | }
46 | }
47 |
48 | public static ResourceDictionary LanguagesDict
49 | {
50 | get
51 | {
52 | return Application.Current.Resources.MergedDictionaries[1];
53 | }
54 | }
55 |
56 | public static bool LoadLanguage(string languageCode)
57 | {
58 | if (string.IsNullOrEmpty(languageCode))
59 | {
60 | return false;
61 | }
62 |
63 | try
64 | {
65 | LanguagesDict.Source = new Uri($"Localisation/{languageCode}.xaml", UriKind.Relative);
66 | LoadedLanguage = languageCode;
67 | return true;
68 | }
69 | catch (IOException)
70 | {
71 | if (languageCode.Contains("-"))
72 | {
73 | return LoadLanguage(languageCode.Split('-').First());
74 | }
75 |
76 | return false;
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/Mod.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ModAssistant.Pages;
3 |
4 | namespace ModAssistant
5 | {
6 | public class Mod
7 | {
8 | public string name;
9 | public string version;
10 | public string gameVersion;
11 | public string _id;
12 | public string status;
13 | public string authorId;
14 | public string uploadedDate;
15 | public string updatedDate;
16 | public Author author;
17 | public string description;
18 | public string link;
19 | public string category;
20 | public DownloadLink[] downloads;
21 | public bool required;
22 | public Dependency[] dependencies;
23 | public List Dependents = new List();
24 | public Mods.ModListItem ListItem;
25 |
26 | public class Author
27 | {
28 | public string _id;
29 | public string username;
30 | public string lastLogin;
31 | }
32 |
33 | public class DownloadLink
34 | {
35 | public string type;
36 | public string url;
37 | public FileHashes[] hashMd5;
38 | }
39 |
40 | public class FileHashes
41 | {
42 | public string hash;
43 | public string file;
44 | }
45 |
46 | public class Dependency
47 | {
48 | public string name;
49 | public string _id;
50 | public Mod Mod;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/OneClickInstaller.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using System.Windows;
5 | using System.Drawing;
6 | using Microsoft.Win32;
7 | using System.Windows.Forms;
8 | using Application = System.Windows.Application;
9 | using MessageBox = System.Windows.MessageBox;
10 |
11 | namespace ModAssistant
12 | {
13 | class OneClickInstaller
14 | {
15 | private static readonly string[] Protocols = new[] { "modelsaber", "beatsaver", "bsplaylist" };
16 | public static OneClickStatus Status = new OneClickStatus();
17 |
18 | public static async Task InstallAsset(string link)
19 | {
20 | Uri uri = new Uri(link);
21 | if (!Protocols.Contains(uri.Scheme)) return;
22 |
23 | switch (uri.Scheme)
24 | {
25 | case "modelsaber":
26 | await ModelSaber(uri);
27 | break;
28 | case "beatsaver":
29 | await BeatSaver(uri);
30 | break;
31 | case "bsplaylist":
32 | await Playlist(uri);
33 | break;
34 | }
35 | if (App.OCIWindow != "No")
36 | {
37 | Status.StopRotation();
38 | API.Utils.SetMessage((string)Application.Current.FindResource("OneClick:Done"));
39 | }
40 | if (App.OCIWindow == "Notify")
41 | {
42 | Utils.SendNotify((string)Application.Current.FindResource("OneClick:DoneNotify"));
43 | Application.Current.Shutdown();
44 | }
45 | if (App.OCIWindow == "Close")
46 | {
47 | Application.Current.Shutdown();
48 | }
49 |
50 |
51 | }
52 |
53 | private static async Task BeatSaver(Uri uri)
54 | {
55 | string Key = uri.Host;
56 | await API.BeatSaver.GetFromKey(Key);
57 | }
58 |
59 | private static async Task ModelSaber(Uri uri)
60 | {
61 | if (App.OCIWindow != "No" && App.OCIWindow != "Notify") Status.Show();
62 | API.Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), System.Web.HttpUtility.UrlDecode(uri.Segments.Last()))}");
63 | await API.ModelSaber.GetModel(uri);
64 | }
65 |
66 | private static async Task Playlist(Uri uri)
67 | {
68 | if (App.OCIWindow != "No" && App.OCIWindow != "Notify") Status.Show();
69 | if (App.OCIWindow == "Notify") Utils.SendNotify((string)Application.Current.FindResource("OneClick:StartDownloadPlaylist"));
70 | await API.Playlists.DownloadAll(uri);
71 | }
72 |
73 | public static void Register(string Protocol, bool Background = false, string Description = null)
74 | {
75 | if (IsRegistered(Protocol) == true)
76 | return;
77 | try
78 | {
79 | if (Utils.IsAdmin)
80 | {
81 | RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true);
82 | if (ProtocolKey == null)
83 | ProtocolKey = Registry.ClassesRoot.CreateSubKey(Protocol, true);
84 | RegistryKey CommandKey = ProtocolKey.CreateSubKey(@"shell\open\command", true);
85 | if (CommandKey == null)
86 | CommandKey = Registry.ClassesRoot.CreateSubKey(@"shell\open\command", true);
87 |
88 | if (ProtocolKey.GetValue("OneClick-Provider", "").ToString() != "ModAssistant")
89 | {
90 | if (Description != null)
91 | {
92 | ProtocolKey.SetValue("", Description, RegistryValueKind.String);
93 | }
94 | ProtocolKey.SetValue("URL Protocol", "", RegistryValueKind.String);
95 | ProtocolKey.SetValue("OneClick-Provider", "ModAssistant", RegistryValueKind.String);
96 | CommandKey.SetValue("", $"\"{Utils.ExePath}\" \"--install\" \"%1\"");
97 | }
98 |
99 | Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:ProtocolHandler:Registered"), Protocol));
100 | }
101 | else
102 | {
103 | Utils.StartAsAdmin($"\"--register\" \"{Protocol}\" \"{Description}\"");
104 | }
105 | }
106 | catch (Exception e)
107 | {
108 | MessageBox.Show(e.ToString());
109 | }
110 |
111 | if (Background)
112 | Application.Current.Shutdown();
113 | else
114 | Pages.Options.Instance.UpdateHandlerStatus();
115 | }
116 |
117 | public static void Unregister(string Protocol, bool Background = false)
118 | {
119 | if (IsRegistered(Protocol) == false)
120 | return;
121 | try
122 | {
123 | if (Utils.IsAdmin)
124 | {
125 | using (RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true))
126 | {
127 | if (ProtocolKey != null
128 | && ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant")
129 | {
130 | Registry.ClassesRoot.DeleteSubKeyTree(Protocol);
131 | }
132 | }
133 |
134 | Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:ProtocolHandler:Unregistered"), Protocol));
135 | }
136 | else
137 | {
138 | Utils.StartAsAdmin($"\"--unregister\" \"{Protocol}\"");
139 | }
140 |
141 | }
142 | catch (Exception e)
143 | {
144 | MessageBox.Show(e.ToString());
145 | }
146 |
147 | if (Background)
148 | Application.Current.Shutdown();
149 | else
150 | Pages.Options.Instance.UpdateHandlerStatus();
151 | }
152 |
153 | public static bool IsRegistered(string Protocol)
154 | {
155 | RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol);
156 | if (ProtocolKey != null
157 | && ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant")
158 | return true;
159 | else
160 | return false;
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/Promotions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ModAssistant
4 | {
5 | class Promotions
6 | {
7 | public static Promotion[] List =
8 | {
9 | new Promotion
10 | {
11 | ModName = "YUR Fit Calorie Tracker",
12 | Links = new List(){
13 | new PromotionLink{Text = "Join our Discord!", Link = "https://yur.chat", TextAfterLink = " Or find us on "},
14 | new PromotionLink{Text = "iOS", Link = "https://bit.ly/yuriphone", TextAfterLink = " and " },
15 | new PromotionLink{Text = "Android", Link = "https://play.google.com/store/apps/details?id=com.yur", TextAfterLink = "!" }
16 | },
17 | Active = false
18 | }
19 | };
20 | }
21 |
22 | class Promotion
23 | {
24 | public string ModName;
25 | public List Links;
26 | public bool Active;
27 | }
28 |
29 | class PromotionLink
30 | {
31 | public string Text;
32 | public string Link;
33 | public string TextAfterLink;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ModAssistant/Classes/Updater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using System.Windows;
6 | using static ModAssistant.Http;
7 |
8 | namespace ModAssistant
9 | {
10 | class Updater
11 | {
12 | private static readonly string APILatestURL = "https://api.github.com/repos/bsmg/ModAssistant/releases/latest";
13 |
14 | private static Update LatestUpdate;
15 | private static Version CurrentVersion;
16 | private static Version LatestVersion;
17 | private static bool NeedsUpdate = false;
18 | private static readonly string NewExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "ModAssistant.exe");
19 | private static readonly string Arguments = App.Arguments;
20 |
21 | #pragma warning disable CS0162 // Unreachable code detected
22 | public static async Task CheckForUpdate()
23 | {
24 | #if DEBUG
25 | return false;
26 | #endif
27 |
28 | var resp = await HttpClient.GetAsync(APILatestURL);
29 | var body = await resp.Content.ReadAsStringAsync();
30 | LatestUpdate = JsonSerializer.Deserialize(body);
31 |
32 | LatestVersion = new Version(LatestUpdate.tag_name.Substring(1));
33 | CurrentVersion = new Version(App.Version);
34 |
35 | return (LatestVersion > CurrentVersion);
36 | }
37 | #pragma warning restore CS0162 // Unreachable code detected
38 |
39 | public static async Task Run()
40 | {
41 | if (Path.GetFileName(Utils.ExePath).Equals("ModAssistant.old.exe")) RunNew();
42 | try
43 | {
44 | NeedsUpdate = await CheckForUpdate();
45 | }
46 | catch
47 | {
48 | Utils.SendNotify((string)Application.Current.FindResource("Updater:CheckFailed"));
49 | }
50 |
51 | if (NeedsUpdate) await StartUpdate();
52 | }
53 |
54 | public static async Task StartUpdate()
55 | {
56 | string OldExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "ModAssistant.old.exe");
57 | string DownloadLink = null;
58 |
59 | foreach (Update.Asset asset in LatestUpdate.assets)
60 | {
61 | if (asset.name == "ModAssistant.exe")
62 | {
63 | DownloadLink = asset.browser_download_url;
64 | }
65 | }
66 |
67 | if (string.IsNullOrEmpty(DownloadLink))
68 | {
69 | Utils.SendNotify((string)Application.Current.FindResource("Updater:DownloadFailed"));
70 | }
71 | else
72 | {
73 | if (File.Exists(OldExe))
74 | {
75 | File.Delete(OldExe);
76 | }
77 |
78 | File.Move(Utils.ExePath, OldExe);
79 |
80 | await Utils.Download(DownloadLink, "", NewExe);
81 | RunNew();
82 | }
83 | }
84 |
85 | private static void RunNew()
86 | {
87 | Process.Start(NewExe, Arguments);
88 | Application.Current.Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
89 | }
90 | }
91 |
92 | public class Update
93 | {
94 | public string url;
95 | public string assets_url;
96 | public string upload_url;
97 | public string html_url;
98 | public int id;
99 | public string node_id;
100 | public string tag_name;
101 | public string target_commitish;
102 | public string name;
103 | public bool draft;
104 | public User author;
105 | public bool prerelease;
106 | public string created_at;
107 | public string published_at;
108 | public Asset[] assets;
109 | public string tarball_url;
110 | public string zipball_url;
111 | public string body;
112 |
113 | public class Asset
114 | {
115 | public string url;
116 | public int id;
117 | public string node_id;
118 | public string name;
119 | public string label;
120 | public User uploader;
121 | public string content_type;
122 | public string state;
123 | public int size;
124 | public string created_at;
125 | public string updated_at;
126 | public string browser_download_url;
127 | }
128 |
129 | public class User
130 | {
131 | public string login;
132 | public int id;
133 | public string node_id;
134 | public string avatar_url;
135 | public string gravatar_id;
136 | public string url;
137 | public string html_url;
138 | public string followers_url;
139 | public string following_url;
140 | public string gists_url;
141 | public string starred_url;
142 | public string subscriptions_url;
143 | public string organizations_url;
144 | public string repos_url;
145 | public string events_url;
146 | public string received_events_url;
147 | public string type;
148 | public bool site_admin;
149 |
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/ModAssistant/Libs/semver/IntExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2013 Max Hauser
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 |
23 | using System.Text;
24 |
25 | namespace ModAssistant.Libs
26 | {
27 | internal static class IntExtensions
28 | {
29 | ///
30 | /// The number of digits in a non-negative number. Returns 1 for all
31 | /// negative numbers. That is ok because we are using it to calculate
32 | /// string length for a for numbers that
33 | /// aren't supposed to be negative, but when they are it is just a little
34 | /// slower.
35 | ///
36 | ///
37 | /// This approach is based on https://stackoverflow.com/a/51099524/268898
38 | /// where the poster offers performance benchmarks showing this is the
39 | /// fastest way to get a number of digits.
40 | ///
41 | public static int Digits(this int n)
42 | {
43 | if (n < 10) return 1;
44 | if (n < 100) return 2;
45 | if (n < 1_000) return 3;
46 | if (n < 10_000) return 4;
47 | if (n < 100_000) return 5;
48 | if (n < 1_000_000) return 6;
49 | if (n < 10_000_000) return 7;
50 | if (n < 100_000_000) return 8;
51 | if (n < 1_000_000_000) return 9;
52 | return 10;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ModAssistant/OneClickStatus.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
57 |
63 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
86 |
91 |
92 |
97 |
102 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/ModAssistant/OneClickStatus.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Data;
4 |
5 | namespace ModAssistant
6 | {
7 | ///
8 | /// Interaction logic for OneClickStatus.xaml
9 | ///
10 | public partial class OneClickStatus : Window
11 | {
12 | public static OneClickStatus Instance;
13 |
14 | public string HistoryText
15 | {
16 | get
17 | {
18 | return HistoryTextBlock.Text;
19 | }
20 | set
21 | {
22 | Dispatcher.Invoke(new Action(() => { Instance.HistoryTextBlock.Text = value; }));
23 | }
24 | }
25 | public string MainText
26 | {
27 | get
28 | {
29 | return MainTextBlock.Text;
30 | }
31 | set
32 | {
33 | Dispatcher.Invoke(new Action(() =>
34 | {
35 | Instance.MainTextBlock.Text = value;
36 | Instance.HistoryTextBlock.Text = string.IsNullOrEmpty(MainText) ? $"{value}" : $"{value}\n{HistoryText}";
37 | }));
38 | }
39 | }
40 |
41 | public OneClickStatus()
42 | {
43 | InitializeComponent();
44 | Instance = (App.OCIWindow == "No" && App.OCIWindow == "Notify") ? null : this;
45 | }
46 |
47 | public void StopRotation()
48 | {
49 | Ring.Style = null;
50 | }
51 | }
52 |
53 | [ValueConversion(typeof(double), typeof(double))]
54 | public class DivideDoubleByTwoConverter : IValueConverter
55 | {
56 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
57 | {
58 | if (targetType != typeof(double))
59 | {
60 | throw new InvalidOperationException("The target must be a double");
61 | }
62 | double d = (double)value;
63 | return ((double)d) / 2;
64 | }
65 |
66 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
67 | {
68 | throw new NotSupportedException();
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/About.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading.Tasks;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Forms;
7 | using System.Windows.Navigation;
8 | using static ModAssistant.Http;
9 | using MessageBox = System.Windows.Forms.MessageBox;
10 |
11 | namespace ModAssistant.Pages
12 | {
13 | ///
14 | /// Interaction logic for Page1.xaml
15 | ///
16 | public partial class About : Page
17 | {
18 | public static About Instance = new About();
19 |
20 | public About()
21 | {
22 | InitializeComponent();
23 | }
24 |
25 | private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
26 | {
27 | Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
28 | e.Handled = true;
29 | }
30 |
31 | private async void HeadpatsButton_Click(object sender, RoutedEventArgs e)
32 | {
33 | PatButton.IsEnabled = false;
34 | var success = await Task.Run(async () => await HeadPat());
35 |
36 | PatUp.IsOpen = success;
37 | if (!success) PatButton.IsEnabled = true;
38 | }
39 |
40 | private async void HugsButton_Click(object sender, RoutedEventArgs e)
41 | {
42 | HugButton.IsEnabled = false;
43 | var success = await Task.Run(async () => await Hug());
44 |
45 | HugUp.IsOpen = success;
46 | if (!success) HugButton.IsEnabled = true;
47 | }
48 |
49 | private async Task WeebCDN(string type)
50 | {
51 | var resp = await HttpClient.GetAsync(Utils.Constants.WeebCDNAPIURL + type);
52 | var body = await resp.Content.ReadAsStringAsync();
53 |
54 | var response = JsonSerializer.Deserialize(body);
55 | return response.url;
56 | }
57 |
58 | private async Task HeadPat()
59 | {
60 | try
61 | {
62 | var image = await WeebCDN("pat");
63 | PatImage.Load(image);
64 |
65 | return true;
66 | }
67 | catch (Exception ex)
68 | {
69 | var buttonType = (string)FindResource("About:HeadpatsButton");
70 | var title = string.Format((string)FindResource("About:ImageError:Title"), buttonType);
71 | var message = string.Format((string)FindResource("About:ImageError:Message"), buttonType);
72 |
73 | MessageBox.Show($"{message}\n{ex.Message}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
74 | return false;
75 | }
76 | }
77 |
78 | private async Task Hug()
79 | {
80 | try
81 | {
82 | var image = await WeebCDN("hug");
83 | HugImage.Load(image);
84 |
85 | return true;
86 | }
87 | catch (Exception ex)
88 | {
89 | var buttonType = (string)FindResource("About:HugsButton");
90 | var title = string.Format((string)FindResource("About:ImageError:Title"), buttonType);
91 | var message = string.Format((string)FindResource("About:ImageError:Message"), buttonType);
92 |
93 | MessageBox.Show($"{message}\n{ex.Message}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
94 | return false;
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Intro.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
42 |
47 |
48 |
49 |
50 |
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 |
71 |
72 |
73 |
74 |
79 |
80 |
81 |
82 |
87 |
88 |
89 |
90 |
95 |
96 |
97 |
98 |
103 |
104 |
105 |
106 |
112 |
120 |
125 |
126 |
134 |
139 |
140 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Intro.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Navigation;
5 |
6 | namespace ModAssistant.Pages
7 | {
8 | ///
9 | /// Interaction logic for Intro.xaml
10 | ///
11 | public partial class Intro : Page
12 | {
13 | public static Intro Instance = new Intro();
14 |
15 | public Intro()
16 | {
17 | InitializeComponent();
18 | }
19 |
20 | private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
21 | {
22 | Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
23 | e.Handled = true;
24 | }
25 |
26 | private void Disagree_Click(object sender, RoutedEventArgs e)
27 | {
28 | MainWindow.Instance.ModsButton.IsEnabled = false;
29 | Properties.Settings.Default.Agreed = false;
30 | Properties.Settings.Default.Save();
31 | MessageBox.Show((string)FindResource("Intro:ClosingApp"));
32 | Application.Current.Shutdown();
33 | }
34 |
35 | private void Agree_Click(object sender, RoutedEventArgs e)
36 | {
37 | if (string.IsNullOrEmpty(MainWindow.GameVersion))
38 | {
39 | string line1 = (string)FindResource("Intro:VersionDownloadFailed");
40 | string line2 = (string)FindResource("Intro:ModsTabDisabled");
41 |
42 | MessageBox.Show($"{line1}.\n{line2}");
43 | }
44 | else
45 | {
46 | MainWindow.Instance.ModsButton.IsEnabled = true;
47 |
48 | string text = (string)FindResource("Intro:ModsTabEnabled");
49 | Utils.SendNotify(text);
50 | MainWindow.Instance.MainText = text;
51 | }
52 | Properties.Settings.Default.Agreed = true;
53 | Properties.Settings.Default.Save();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Invalid.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
40 |
47 |
54 |
55 |
61 |
62 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
81 |
82 |
88 |
94 |
95 | :
96 |
97 |
102 |
109 |
110 |
118 |
124 |
125 | .
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Invalid.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Navigation;
5 |
6 | namespace ModAssistant.Pages
7 | {
8 | ///
9 | /// Interaction logic for Invalid.xaml
10 | ///
11 | public partial class Invalid : Page
12 | {
13 | public static Invalid Instance = new Invalid();
14 | public string InstallDirectory { get; set; }
15 |
16 | public Invalid()
17 | {
18 | InitializeComponent();
19 | InstallDirectory = App.BeatSaberInstallDirectory;
20 | DirectoryTextBlock.Text = InstallDirectory;
21 | }
22 |
23 | private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
24 | {
25 | Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
26 | e.Handled = true;
27 | }
28 |
29 | private void SelectDirButton_Click(object sender, RoutedEventArgs e)
30 | {
31 | InstallDirectory = Utils.GetManualDir();
32 | DirectoryTextBlock.Text = InstallDirectory;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Loading.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
52 |
58 |
65 |
66 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Loading.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Controls;
3 | using System.Windows.Data;
4 |
5 | namespace ModAssistant.Pages
6 | {
7 | ///
8 | /// Interaction logic for Loading.xaml
9 | ///
10 | public partial class Loading : Page
11 | {
12 | public static Loading Instance = new Loading();
13 |
14 | public Loading()
15 | {
16 | InitializeComponent();
17 | }
18 | }
19 |
20 | [ValueConversion(typeof(double), typeof(double))]
21 | public class DivideDoubleByTwoConverter : IValueConverter
22 | {
23 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
24 | {
25 | if (targetType != typeof(double))
26 | {
27 | throw new InvalidOperationException("The target must be a double");
28 | }
29 | double d = (double)value;
30 | return ((double)d) / 2;
31 | }
32 |
33 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
34 | {
35 | throw new NotSupportedException();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ModAssistant/Pages/Mods.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 | .
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
36 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
67 |
68 |
69 |
70 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
129 |
130 |
131 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/ModAssistant/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("Mod Assistant")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("Mod Assistant")]
15 | [assembly: AssemblyCopyright("Copyright © Assistant 2019")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.1.35.0")]
55 | [assembly: AssemblyFileVersion("1.1.35.0")]
56 |
--------------------------------------------------------------------------------
/ModAssistant/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ModAssistant.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModAssistant.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/ModAssistant/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 |
--------------------------------------------------------------------------------
/ModAssistant/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ModAssistant.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.1.0.0")]
16 | public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 |
26 | [global::System.Configuration.UserScopedSettingAttribute()]
27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
28 | [global::System.Configuration.DefaultSettingValueAttribute("")]
29 | public string InstallFolder {
30 | get {
31 | return ((string)(this["InstallFolder"]));
32 | }
33 | set {
34 | this["InstallFolder"] = value;
35 | }
36 | }
37 |
38 | [global::System.Configuration.UserScopedSettingAttribute()]
39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
40 | [global::System.Configuration.DefaultSettingValueAttribute("")]
41 | public string StoreType {
42 | get {
43 | return ((string)(this["StoreType"]));
44 | }
45 | set {
46 | this["StoreType"] = value;
47 | }
48 | }
49 |
50 | [global::System.Configuration.UserScopedSettingAttribute()]
51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
52 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
53 | public bool SaveSelected {
54 | get {
55 | return ((bool)(this["SaveSelected"]));
56 | }
57 | set {
58 | this["SaveSelected"] = value;
59 | }
60 | }
61 |
62 | [global::System.Configuration.UserScopedSettingAttribute()]
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
64 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
65 | public bool CheckInstalled {
66 | get {
67 | return ((bool)(this["CheckInstalled"]));
68 | }
69 | set {
70 | this["CheckInstalled"] = value;
71 | }
72 | }
73 |
74 | [global::System.Configuration.UserScopedSettingAttribute()]
75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
76 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
77 | public string SavedMods {
78 | get {
79 | return ((string)(this["SavedMods"]));
80 | }
81 | set {
82 | this["SavedMods"] = value;
83 | }
84 | }
85 |
86 | [global::System.Configuration.UserScopedSettingAttribute()]
87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
88 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
89 | public bool Agreed {
90 | get {
91 | return ((bool)(this["Agreed"]));
92 | }
93 | set {
94 | this["Agreed"] = value;
95 | }
96 | }
97 |
98 | [global::System.Configuration.UserScopedSettingAttribute()]
99 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
100 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
101 | public bool SelectInstalled {
102 | get {
103 | return ((bool)(this["SelectInstalled"]));
104 | }
105 | set {
106 | this["SelectInstalled"] = value;
107 | }
108 | }
109 |
110 | [global::System.Configuration.UserScopedSettingAttribute()]
111 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
112 | [global::System.Configuration.DefaultSettingValueAttribute("")]
113 | public string GameVersion {
114 | get {
115 | return ((string)(this["GameVersion"]));
116 | }
117 | set {
118 | this["GameVersion"] = value;
119 | }
120 | }
121 |
122 | [global::System.Configuration.UserScopedSettingAttribute()]
123 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
124 | [global::System.Configuration.DefaultSettingValueAttribute("")]
125 | public string AllGameVersions {
126 | get {
127 | return ((string)(this["AllGameVersions"]));
128 | }
129 | set {
130 | this["AllGameVersions"] = value;
131 | }
132 | }
133 |
134 | [global::System.Configuration.UserScopedSettingAttribute()]
135 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
136 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
137 | public bool UpgradeRequired {
138 | get {
139 | return ((bool)(this["UpgradeRequired"]));
140 | }
141 | set {
142 | this["UpgradeRequired"] = value;
143 | }
144 | }
145 |
146 | [global::System.Configuration.UserScopedSettingAttribute()]
147 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
148 | [global::System.Configuration.DefaultSettingValueAttribute("")]
149 | public string LastTab {
150 | get {
151 | return ((string)(this["LastTab"]));
152 | }
153 | set {
154 | this["LastTab"] = value;
155 | }
156 | }
157 |
158 | [global::System.Configuration.UserScopedSettingAttribute()]
159 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
160 | [global::System.Configuration.DefaultSettingValueAttribute("")]
161 | public string SelectedTheme {
162 | get {
163 | return ((string)(this["SelectedTheme"]));
164 | }
165 | set {
166 | this["SelectedTheme"] = value;
167 | }
168 | }
169 |
170 | [global::System.Configuration.UserScopedSettingAttribute()]
171 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
172 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
173 | public bool AppliedAnniversaryTheme
174 | {
175 | get
176 | {
177 | return ((bool)(this["AppliedAnniversaryTheme"]));
178 | }
179 | set
180 | {
181 | this["AppliedAnniversaryTheme"] = value;
182 | }
183 | }
184 |
185 | [global::System.Configuration.UserScopedSettingAttribute()]
186 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
187 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
188 | public bool ReinstallInstalled {
189 | get {
190 | return ((bool)(this["ReinstallInstalled"]));
191 | }
192 | set {
193 | this["ReinstallInstalled"] = value;
194 | }
195 | }
196 |
197 | [global::System.Configuration.UserScopedSettingAttribute()]
198 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
199 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
200 | public bool CloseWindowOnFinish {
201 | get {
202 | return ((bool)(this["CloseWindowOnFinish"]));
203 | }
204 | set {
205 | this["CloseWindowOnFinish"] = value;
206 | }
207 | }
208 |
209 | [global::System.Configuration.UserScopedSettingAttribute()]
210 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
211 | [global::System.Configuration.DefaultSettingValueAttribute("")]
212 | public string LanguageCode {
213 | get {
214 | return ((string)(this["LanguageCode"]));
215 | }
216 | set {
217 | this["LanguageCode"] = value;
218 | }
219 | }
220 |
221 | [global::System.Configuration.UserScopedSettingAttribute()]
222 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
223 | [global::System.Configuration.DefaultSettingValueAttribute("")]
224 | public string OCIWindow {
225 | get {
226 | return ((string)(this["OCIWindow"]));
227 | }
228 | set {
229 | this["OCIWindow"] = value;
230 | }
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/ModAssistant/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | False
13 |
14 |
15 | True
16 |
17 |
18 | False
19 |
20 |
21 | False
22 |
23 |
24 | False
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | True
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | True
43 |
44 |
45 | False
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/ModAssistant/Resources/Icons.xaml:
--------------------------------------------------------------------------------
1 |
2 | F1 M512,512z M0,0z M458.4,64.3C400.6,15.7 311.3,23 256,79.3 200.7,23 111.4,15.6 53.6,64.3 -21.6,127.6 -10.6,230.8 43,285.5L218.4,464.2C228.4,474.4 241.8,480.1 256,480.1 270.3,480.1 283.6,474.5 293.6,464.3L469,285.6C522.5,230.9,533.7,127.7,458.4,64.3z M434.8,251.8L259.4,430.5C257,432.9,255,432.9,252.6,430.5L77.2,251.8C40.7,214.6 33.3,144.2 84.5,101.1 123.4,68.4 183.4,73.3 221,111.6L256,147.3 291,111.6C328.8,73.1 388.8,68.4 427.5,101 478.6,144.1 471,214.9 434.8,251.8z
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | F1 M512,512z M0,0z M256,8C119.043,8 8,119.083 8,256 8,392.997 119.043,504 256,504 392.957,504 504,392.997 504,256 504,119.083 392.957,8 256,8z M256,118C279.196,118 298,136.804 298,160 298,183.196 279.196,202 256,202 232.804,202 214,183.196 214,160 214,136.804 232.804,118 256,118z M312,372C312,378.627,306.627,384,300,384L212,384C205.373,384,200,378.627,200,372L200,348C200,341.373,205.373,336,212,336L224,336 224,272 212,272C205.373,272,200,266.627,200,260L200,236C200,229.373,205.373,224,212,224L276,224C282.627,224,288,229.373,288,236L288,336 300,336C306.627,336,312,341.373,312,348L312,372z
12 |
13 |
14 |
15 |
16 |
17 | F1 M512,512z M0,0z M487.4,315.7L444.8,291.1C449.1,267.9,449.1,244.1,444.8,220.9L487.4,196.3C492.3,193.5 494.5,187.7 492.9,182.3 481.8,146.7 462.9,114.5 438.2,87.7 434.4,83.6 428.2,82.6 423.4,85.4L380.8,110C362.9,94.6,342.3,82.7,320,74.9L320,25.8C320,20.2 316.1,15.3 310.6,14.1 273.9,5.9 236.3,6.3 201.4,14.1 195.9,15.3 192,20.2 192,25.8L192,75C169.8,82.9,149.2,94.8,131.2,110.1L88.7,85.5C83.8,82.7 77.7,83.6 73.9,87.8 49.2,114.5 30.3,146.7 19.2,182.4 17.5,187.8 19.8,193.6 24.7,196.4L67.3,221C63,244.2,63,268,67.3,291.2L24.7,315.8C19.8,318.6 17.6,324.4 19.2,329.8 30.3,365.4 49.2,397.6 73.9,424.4 77.7,428.5 83.9,429.5 88.7,426.7L131.3,402.1C149.2,417.5,169.8,429.4,192.1,437.2L192.1,486.4C192.1,492 196,496.9 201.5,498.1 238.2,506.3 275.8,505.9 310.7,498.1 316.2,496.9 320.1,492 320.1,486.4L320.1,437.2C342.3,429.3,362.9,417.4,380.9,402.1L423.5,426.7C428.4,429.5 434.5,428.6 438.3,424.4 463,397.7 481.9,365.5 493,329.8 494.5,324.3 492.3,318.5 487.4,315.7z M256,336C211.9,336 176,300.1 176,256 176,211.9 211.9,176 256,176 300.1,176 336,211.9 336,256 336,300.1 300.1,336 256,336z
18 |
19 |
20 |
21 |
22 |
23 | F1 M512,512z M0,0z M416,48L416,464C416,490.51,394.51,512,368,512L144,512C117.49,512,96,490.51,96,464L96,48C96,21.49,117.49,0,144,0L368,0C394.51,0,416,21.49,416,48z M512,106L512,118A6,6,0,0,1,506,124L488,124 488,130A6,6,0,0,1,482,136L440,136 440,88 482,88A6,6,0,0,1,488,94L488,100 506,100A6,6,0,0,1,512,106z M512,202L512,214A6,6,0,0,1,506,220L488,220 488,226A6,6,0,0,1,482,232L440,232 440,184 482,184A6,6,0,0,1,488,190L488,196 506,196A6,6,0,0,1,512,202z M512,298L512,310A6,6,0,0,1,506,316L488,316 488,322A6,6,0,0,1,482,328L440,328 440,280 482,280A6,6,0,0,1,488,286L488,292 506,292A6,6,0,0,1,512,298z M512,394L512,406A6,6,0,0,1,506,412L488,412 488,418A6,6,0,0,1,482,424L440,424 440,376 482,376A6,6,0,0,1,488,382L488,388 506,388A6,6,0,0,1,512,394z M30,376L72,376 72,424 30,424A6,6,0,0,1,24,418L24,412 6,412A6,6,0,0,1,0,406L0,394A6,6,0,0,1,6,388L24,388 24,382A6,6,0,0,1,30,376z M30,280L72,280 72,328 30,328A6,6,0,0,1,24,322L24,316 6,316A6,6,0,0,1,0,310L0,298A6,6,0,0,1,6,292L24,292 24,286A6,6,0,0,1,30,280z M30,184L72,184 72,232 30,232A6,6,0,0,1,24,226L24,220 6,220A6,6,0,0,1,0,214L0,202A6,6,0,0,1,6,196L24,196 24,190A6,6,0,0,1,30,184z M30,88L72,88 72,136 30,136A6,6,0,0,1,24,130L24,124 6,124A6,6,0,0,1,0,118L0,106A6,6,0,0,1,6,100L24,100 24,94A6,6,0,0,1,30,88z
24 |
25 |
26 |
27 |
28 |
29 | F1 M4000,4000z M0,0z M1833.4,1475.6C1827.5,1490 1818.3,1503.3 1805.8,1514.3 1762.7,1551.9 1697.3,1547.4 1659.7,1504.3 1628.8,1468.8 1626.4,1418.3 1650.5,1380.6L1170.6,1131.3 1336.5,1597.2C1394,1576.7 1460.7,1591.9 1503.2,1640.7 1558.7,1704.4 1552.1,1801.1 1488.4,1856.6 1473.5,1869.6 1456.7,1879.1 1439.1,1885.3L1814.9,2940.7 2747.8,2941 2875.1,2016.8 1833.4,1475.6z
30 |
31 |
32 |
33 |
34 |
35 | F1 M4000,4000z M0,0z M3384.4,1915.4C3385.5,2231.2 3278.1,2552.4 3057.5,2818.2 2570.1,3405.4 1714.7,3499.3 1146.9,3028 579.1,2556.7 514.1,1698.6 1001.5,1111.5 1222.2,845.7 1518.2,681 1828.8,624 1491,666 1167.9,831.7 933.3,1114.2 444.1,1703.5 525.3,2577.8 1114.5,3067 1703.7,3556.2 2578.1,3475.1 3067.3,2885.8 3301.9,2603.2 3405.2,2255.1 3384.4,1915.4z
36 |
37 |
38 |
39 |
40 |
41 | F1 M4000,4000z M0,0z M3710.9,2000L3870,1840.9 3667.5,1638.4C3501.6,870.1 2818.3,294.3 2000.3,294.3 1182.4,294.3 499.1,870 333.2,1638.2L333,1637.9 130,1840.9 289.1,2000 130,2159.1 333,2362.1 333.2,2361.8C499.2,3130 1182.5,3705.6 2000.3,3705.6 2818.3,3705.6 3501.6,3129.8 3667.5,2361.5L3870,2159 3710.9,2000z M2000.3,3489.8C1177.5,3489.8 510.5,2822.8 510.5,2000 510.5,1177.2 1177.5,510.2 2000.3,510.2 2823.1,510.2 3490.1,1177.2 3490.1,2000 3490.1,2822.8 2823.1,3489.8 2000.3,3489.8z
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/ModAssistant/Resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsmg/ModAssistant/db773630264d7d29cb43b04de5d161828a6a0eb3/ModAssistant/Resources/icon.ico
--------------------------------------------------------------------------------
/ModAssistant/Styles/Button.xaml:
--------------------------------------------------------------------------------
1 |
2 |
46 |
47 |
48 |
90 |
91 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/CheckBox.xaml:
--------------------------------------------------------------------------------
1 |
2 |
75 |
76 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/ComboBox.xaml:
--------------------------------------------------------------------------------
1 |
2 |
103 |
104 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/ComboBoxItem.xaml:
--------------------------------------------------------------------------------
1 |
2 |
81 |
82 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/GridViewColumnHeader.xaml:
--------------------------------------------------------------------------------
1 |
2 |
51 |
52 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/Label.xaml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/ListView.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/ListViewItem.xaml:
--------------------------------------------------------------------------------
1 |
2 |
42 |
43 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/Menu.xaml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/MenuItem.xaml:
--------------------------------------------------------------------------------
1 |
2 |
66 |
67 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/RepeatButton.xaml:
--------------------------------------------------------------------------------
1 |
2 |
42 |
58 |
59 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/TextBlock.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/Thumb.xaml:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
--------------------------------------------------------------------------------
/ModAssistant/Styles/ToggleButton.xaml:
--------------------------------------------------------------------------------
1 |
2 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/Anniversary.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | #3C54AB
10 | #F5E1E7
11 | #D0617C
12 | #F5E1E7
13 | #CE5F7A
14 | #E18FAB
15 | #D0617C
16 | #3C54AB
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | 0
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | UniformToFill
98 |
99 | Bottom
100 |
101 |
102 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/Anniversary/Background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsmg/ModAssistant/db773630264d7d29cb43b04de5d161828a6a0eb3/ModAssistant/Themes/Anniversary/Background.png
--------------------------------------------------------------------------------
/ModAssistant/Themes/BSMG.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | #DCDDDE
10 | #36393F
11 | #2F3136
12 | #202225
13 | #202225
14 | #7289DA
15 | #44347C
16 | #DCDDDE
17 | #40444B
18 | #32353B
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | 0
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | UniformToFill
100 |
101 | Top
102 |
103 |
104 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/BSMG/Sidebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsmg/ModAssistant/db773630264d7d29cb43b04de5d161828a6a0eb3/ModAssistant/Themes/BSMG/Sidebar.png
--------------------------------------------------------------------------------
/ModAssistant/Themes/Dark.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #E0E0E0
7 | #2E2E2E
8 | #0F0F0F
9 | #696969
10 | #454545
11 | #696969
12 | #AEAEAE
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 0
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | UniformToFill
94 |
95 | Bottom
96 |
97 |
98 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/Default Scrollbar.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 0
16 |
17 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/Light Pink.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | 0
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | UniformToFill
82 |
83 | Bottom
84 |
85 |
86 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/Light.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #101010
7 | #C8C8C8
8 | #F3F3F3
9 | Gray
10 | #AEAEAE
11 | #C0C0C0
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 0
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | UniformToFill
93 |
94 | Bottom
95 |
96 |
97 |
--------------------------------------------------------------------------------
/ModAssistant/Themes/Ugly Kulu-Ya-Ku.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 | #75f6ff
22 | #4FC653
23 | #7A2E63
24 | #75f6ff
25 | #508F4E
26 | #CA85B1
27 | #FF51B6
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 0
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | UniformToFill
114 |
115 | Bottom
116 |
117 |
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/Assistant/ModAssistant/releases/latest)
2 | [](https://github.com/Assistant/ModAssistant/releases/latest)
3 |
4 | Mod Assistant is a PC mod installer for Beat Saber. It uses mods from [BeatMods](https://beatmods.com/).
5 |
6 | * [Features](#Features)
7 | * [Usage](#Usage)
8 | * [Themes](#Themes)
9 | * [Custom Themes](#Custom-Themes)
10 | * [Built In](#Built-In)
11 | * [Packaged `.mat` Files](#Packaged-mat-Files)
12 | * [Loose Folder Themes](#Loose-Folder-Themes)
13 | * [Overriding Themes](#Overriding-Themes)
14 | * [Common Issues](#Common-Issues)
15 |
16 |
17 | ## Features
18 |
19 | Mod Assistant boasts a rich feature set, some of which include:
20 | * Dependency resolution
21 | * Installed mod detection
22 | * Mod uninstallation
23 | * OneClick™ Install support
24 | * Complex theming engine
25 | * Localization support
26 | * Headpats and Hugs
27 |
28 | ## Usage
29 | Download the newest installer from the release section and run it. This application auto-updates when launched, there is no need to download a new release each time.
30 |
31 | 1. **Run the game at least once before trying to mod the game!** This applies to reinstalling your game too. All mods are moved into an `Old X.X.X Plugins` folder on first launch to avoid version mismatches, so make sure to do this before installing mods on a fresh version.
32 | 2. Once that's done, simply check off the mods that you wish to install and click the Install or Update button. Likewise, click the Uninstall button to remove any mods.
33 | 3. Mods are installed to `IPA/Pending` until the game is run. Boot the game to complete mod installation.
34 |
35 |
36 | ## Themes
37 |
38 | Light
39 |
45 |
46 |
47 |
48 | Dark
49 |
55 |
56 |
57 |
58 | BSMG
59 |
65 |
66 |
67 |
68 | Light Pink
69 |
75 |
76 |
77 |
78 | Your own!
79 |
85 |
86 |
87 | ### Custom Themes
88 | Custom themes are located in a folder called `Themes` in the same folder as `ModAssistant.exe`. Mod Assistant can load themes from one of three sources.
89 |
90 | ### Built In
91 | These come with the program and you can't change them, however you can overwrite them by creating one of the other two theme types with the same name.
92 |
93 | If you have a particularly popular theme, you can submit a [Pull Request](https://github.com/bsmg/ModAssistant/pulls) to add your theme as a built-in theme.
94 |
95 | ### Packaged `.mat` Files
96 | These are pre-packaged theme files. Under the hood they are renamed`.zip` files, and the file structure is the same as that of `Folders` themes. These will be overwritten by `Folders` themes with the same name.
97 | To create one follow the instructions on `Folders` themes, and zip the files up into a zip archive, and rename it to `.mat`.
98 |
99 | ### Loose Folder Themes
100 | These will overwrite all other themes, and are loaded from a folder named after the theme. There are 4 types of files you can include:
101 |
102 | * `Theme.xaml`
103 | * This file determines the colors and styling of the theme.
104 | * The filename isn't important, but the `.xaml` file extension is.
105 | * To see an example of this file press the Export Template button in the `Options` page. It will create a folder in `Themes` called `Ugly Kulu-Ya-Ku`. You can open that file to use as a template for your own themes, or just use it.
106 |
107 | * `Waifu.png`
108 | * This will be loaded as the background image.
109 | * It will be centered, and you can pick how to stretch it in the associated `.xaml` file.
110 | * The filename isn't important, but the `.png` file extension is.
111 | * `Waifu.side.png`
112 | * This will be loaded as the left side image. It will be left aligned, and you can pick its vertical alignment in the associated `.xaml` file.
113 | * The filename isn't important, but the `.side.png` file extension is.
114 | * `Video.{mp4, webm, mkv, avi, m2ts}`
115 | * This will be loaded as a background video, with sound.
116 | * The filename isn't important, but the file extension must be supported (`.mp4`, `.webm`, `.mkv`, `.avi`, `.m2ts`)
117 | * Whether the file works or not will depend on what codecs the file has, and whether those are available on your machine.
118 |
119 | ### Overriding Themes
120 | You can mix and match parts from different themes by giving them the same name.
121 | The priority in which they will be used is `Loose Folder Themes` > `Packaged .mat files` > `Built in`. Overriding themes will only change the files that are included.
122 |
123 | Examples:
124 | * Adding `/Themes/Dark.mat` which includes `.png` and `.xaml` files will override both those aspects of the `Dark` theme.
125 | * Adding `/Themes/Dark/image.png` will use that image as the background for the `Dark` theme, overriding both the built in theme and `Dark.mat` if it exists.
126 |
127 |
128 | ## Common Issues
129 | **I hit install but I don't see anything in game!**
130 | Double check that you followed the [Usage](#usage) instructions correctly.
131 | Make sure you're looking in the right place. Sometimes mod menus move as modding libraries/practices change.
132 |
133 | **I don't see a certain mod in the mods list!**
134 | Mod Assistant uses mods from [BeatMods](https://beatmods.com/) and shows whatever is available for download. If you need to install a mod manually, please refer to the [Beat Saber Modding Group Wiki](https://bsmg.wiki/pc-modding.html#manual-installation).
135 |
136 | **I hit install but now my game won't launch, I can't click any buttons, I only see a black screen, etc**
137 | Please visit the [Beat Saber Modding Group](https://discord.gg/beatsabermods) `#pc-help` channels. Check the pinned messages or ask for help and see if you can work things out.
138 |
139 | ## Credits
140 | semver by Max Hauser
141 | https://github.com/maxhauser/semver
142 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | # ModAssistant Tools
2 | > These are tools used to build / improve ModAssistant. **They are not to be distributed with the compiled binary.**
3 |
4 | ## Translation Stubs
5 | Use `generate_translation_stubs.py` to read the English locale file and generate missing strings for all other locales. Requires Python 3.6 or above.
6 |
7 | Simply run `python ./tools/generate_translation_stubs.py` and commit the stubs.
8 |
--------------------------------------------------------------------------------
/tools/generate_translation_stubs.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import re
4 | from shutil import copyfile
5 |
6 | # --------------- Configuration ---------------- #
7 | master_file = "./ModAssistant/Localisation/en.xaml"
8 | source_dir = "./ModAssistant/Localisation"
9 | target_dir = source_dir
10 | comment = " "
11 | # --------------- Configuration ---------------- #
12 |
13 |
14 | def read_file(filepath):
15 | """
16 | Read file content
17 | :param filepath:
18 | :return:
19 | """
20 | with open(filepath, "r", encoding="utf-8") as file:
21 | data = file.read()
22 | return data
23 |
24 |
25 | def find_files(root_directory):
26 | """
27 | Find all files to be processed
28 | :param root_directory:
29 | :return:
30 | """
31 | for root, ds, fs in os.walk(root_directory):
32 | for fn in fs:
33 | fullname = os.path.join(root, fn)
34 | master_name = os.path.basename(master_file)
35 | if master_name in fullname:
36 | continue
37 | yield fullname
38 |
39 |
40 | def search(regex, m_string, group=1):
41 | """
42 | Search for the first item that matches by regular expression
43 | :param regex:
44 | :param m_string:
45 | :return:
46 | """
47 | found = re.compile(regex, re.M).search(m_string)
48 | result = ""
49 | if found:
50 | result = found.group(group)
51 | return result
52 |
53 |
54 | def write_file(filepath, content):
55 | """
56 | Write text content to file
57 | :param filepath:
58 | :param content:
59 | :return:
60 | """
61 | if not os.path.exists(target_dir):
62 | os.makedirs(target_dir, exist_ok=True)
63 | with open(filepath, "w", encoding="utf-8") as f:
64 | f.write(content)
65 |
66 |
67 | def find_items(data):
68 | keys_regex = r"<\s*([^\s>]+)\s.*Key[^>]+>"
69 | keys_matches = re.finditer(keys_regex, data, re.M)
70 | items = []
71 | for n, m in enumerate(keys_matches, start=1):
72 | if len(items):
73 | last_dict = items[-1]
74 | if len(last_dict):
75 | last_dict['end'] = m.start()
76 | items.append({"match": m[0], "name": m[1], "start": m.end()})
77 | items[-1]['end'] = len(data)
78 | for item in items:
79 | item_text = data[item['start']: item['end']]
80 | end_tag = "%s>" % item['name']
81 | full_tag = item['match'] + item_text[:item_text.rfind(end_tag) + len(end_tag)]
82 | item['end_tag'] = end_tag
83 | item['full_tag'] = full_tag
84 | return items
85 |
86 |
87 | def wrapper_key(match, content):
88 | """
89 | Try to find the comment behind the key
90 | :param match:
91 | :param content:
92 | :return:
93 | """
94 | regex = "(\s*" + re.escape(match) + "(\ *<\!\-\-[^\-]+\-\->)?\s*)"
95 | with_comment = search(r"" + str(regex), content, 1)
96 | # print(regex)
97 | # print(match)
98 | # print(with_comment)
99 | # print("-----")
100 | if with_comment:
101 | match = with_comment
102 | return match
103 |
104 |
105 | def main():
106 | """
107 | Cycle processing of xaml files except master_file
108 | :return:
109 | """
110 | master_data = read_file(master_file)
111 | print("The master file is", master_file)
112 | items = find_items(master_data)
113 |
114 | for f in find_files(source_dir):
115 | content = master_data + ""
116 | xml_data = read_file(f)
117 | xml_dict = {}
118 | for xml_item in find_items(xml_data):
119 | xml_dict[xml_item['match']] = xml_item['full_tag']
120 | for item in items:
121 | # print(f)
122 | # if "OneClick:Done" in item['match'] and "fr.xaml" in f:
123 | # print("----")
124 | # pass
125 | if item['match'] in xml_dict.keys():
126 | match = wrapper_key(xml_dict[item['match']], xml_data)
127 | master_match = wrapper_key(item['full_tag'], content)
128 | # print(master_match)
129 | content = content.replace(master_match, match)
130 | else:
131 | pre_blanks = search(r"(\s*)" + re.escape(item['full_tag']), master_data)
132 | content = re.sub(r"\s*" + re.escape(item['full_tag']), pre_blanks + item['full_tag'] + comment, content)
133 | # Put the processed files in the "dist" directory
134 | filepath = f.replace(source_dir, target_dir)
135 | write_file(filepath, content)
136 | print("Processing", f)
137 | # Copy "master_file" to the target directory
138 | if source_dir != target_dir:
139 | copyfile(master_file, master_file.replace(source_dir, target_dir))
140 |
141 |
142 | if __name__ == '__main__':
143 | main()
144 | print("done'd!")
145 |
--------------------------------------------------------------------------------