From 5b4a6a9c1b4b483d47cf8ec47d701b30124e75e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kuznetsov Date: Mon, 17 May 2021 22:27:27 +0200 Subject: [PATCH] Fork initial: VRCMelonManager based on ModAssistant 1.1.18 --- .github/FUNDING.yml | 5 +- .github/workflows/dotnet.yml | 14 +- ModAssistant/App.config | 72 -- .../Classes/External Interfaces/BeatSaver.cs | 364 -------- .../Classes/External Interfaces/ModelSaber.cs | 34 - .../Classes/External Interfaces/Playlists.cs | 126 --- .../Classes/External Interfaces/Utils.cs | 79 -- ModAssistant/Classes/Mod.cs | 53 -- ModAssistant/Classes/OneClickInstaller.cs | 151 ---- ModAssistant/Localisation/de.xaml | 252 ------ ModAssistant/Localisation/fr.xaml | 258 ------ ModAssistant/Localisation/it.xaml | 252 ------ ModAssistant/Localisation/ko.xaml | 251 ------ ModAssistant/Localisation/nb.xaml | 255 ------ ModAssistant/Localisation/nl.xaml | 250 ------ ModAssistant/Localisation/ru.xaml | 252 ------ ModAssistant/Localisation/sv.xaml | 252 ------ ModAssistant/Localisation/zh.xaml | 250 ------ ModAssistant/OneClickStatus.xaml | 114 --- ModAssistant/OneClickStatus.xaml.cs | 71 -- ModAssistant/Pages/Mods.xaml.cs | 799 ------------------ ModAssistant/Pages/Options.xaml.cs | 417 --------- ModAssistant/Resources/icon.ico | Bin 124093 -> 0 bytes ModAssistant/Themes/BSMG.xaml | 103 --- ModAssistant/Themes/BSMG/Sidebar.png | Bin 6156 -> 0 bytes README.md | 136 +-- ModAssistant.sln => VRCMelonAssistant.sln | 9 +- VRCMelonAssistant/App.config | 39 + {ModAssistant => VRCMelonAssistant}/App.xaml | 4 +- .../App.xaml.cs | 61 +- .../Classes/Diagnostics.cs | 2 +- .../Classes/HardcodedCategories.cs | 98 +++ .../Classes/Http.cs | 4 +- .../Classes/HyperlinkExtensions.cs | 4 +- VRCMelonAssistant/Classes/InstallHandlers.cs | 109 +++ .../Classes/Languages.cs | 6 +- VRCMelonAssistant/Classes/Mod.cs | 39 + .../Classes/Promotions.cs | 6 +- .../Classes/Themes.cs | 16 +- .../Classes/Updater.cs | 14 +- .../Classes/Utils.cs | 72 +- .../Libs/semver/IntExtensions.cs | 2 +- .../Libs/semver/SemVersion.cs | 2 +- .../Localisation/en-DEBUG.xaml | 5 +- .../Localisation/en.xaml | 158 ++-- .../MainWindow.xaml | 27 +- .../MainWindow.xaml.cs | 164 +--- VRCMelonAssistant/ModInfoWindow.xaml | 26 + VRCMelonAssistant/ModInfoWindow.xaml.cs | 49 ++ .../Pages/About.xaml | 65 +- .../Pages/About.xaml.cs | 4 +- .../Pages/Intro.xaml | 27 +- .../Pages/Intro.xaml.cs | 20 +- .../Pages/Invalid.xaml | 4 +- .../Pages/Invalid.xaml.cs | 4 +- .../Pages/Loading.xaml | 16 +- .../Pages/Loading.xaml.cs | 2 +- .../Pages/Mods.xaml | 21 +- VRCMelonAssistant/Pages/Mods.xaml.cs | 539 ++++++++++++ .../Pages/Options.xaml | 195 +---- VRCMelonAssistant/Pages/Options.xaml.cs | 185 ++++ .../Properties/AssemblyInfo.cs | 8 +- .../Properties/Resources.Designer.cs | 4 +- .../Properties/Resources.resx | 0 .../Properties/Settings.Designer.cs | 100 +-- .../Properties/Settings.settings | 31 +- VRCMelonAssistant/Resources/AppResources.xaml | 8 + .../Resources/Icons.xaml | 30 +- VRCMelonAssistant/Resources/icon.ico | Bin 0 -> 370070 bytes .../Styles/Button.xaml | 0 .../Styles/CheckBox.xaml | 0 .../Styles/ComboBox.xaml | 0 .../Styles/ComboBoxItem.xaml | 0 .../Styles/GridViewColumnHeader.xaml | 0 .../Styles/Label.xaml | 0 .../Styles/ListView.xaml | 0 .../Styles/ListViewItem.xaml | 0 .../Styles/Menu.xaml | 0 .../Styles/MenuItem.xaml | 0 .../Styles/RepeatButton.xaml | 4 +- .../Styles/ScrollBar.xaml | 20 +- .../Styles/TextBlock.xaml | 0 .../Styles/Thumb.xaml | 2 +- .../Styles/ToggleButton.xaml | 0 .../Themes/Dark.xaml | 4 +- .../Themes/Default Scrollbar.xaml | 0 .../Themes/Light Pink.xaml | 4 +- .../Themes/Light.xaml | 4 +- .../Themes/Ugly Kulu-Ya-Ku.xaml | 6 +- .../VRCMelonAssistant.csproj | 121 +-- VRCMelonAssistant/packages.config | 10 + 91 files changed, 1494 insertions(+), 5670 deletions(-) delete mode 100644 ModAssistant/App.config delete mode 100644 ModAssistant/Classes/External Interfaces/BeatSaver.cs delete mode 100644 ModAssistant/Classes/External Interfaces/ModelSaber.cs delete mode 100644 ModAssistant/Classes/External Interfaces/Playlists.cs delete mode 100644 ModAssistant/Classes/External Interfaces/Utils.cs delete mode 100644 ModAssistant/Classes/Mod.cs delete mode 100644 ModAssistant/Classes/OneClickInstaller.cs delete mode 100644 ModAssistant/Localisation/de.xaml delete mode 100644 ModAssistant/Localisation/fr.xaml delete mode 100644 ModAssistant/Localisation/it.xaml delete mode 100644 ModAssistant/Localisation/ko.xaml delete mode 100644 ModAssistant/Localisation/nb.xaml delete mode 100644 ModAssistant/Localisation/nl.xaml delete mode 100644 ModAssistant/Localisation/ru.xaml delete mode 100644 ModAssistant/Localisation/sv.xaml delete mode 100644 ModAssistant/Localisation/zh.xaml delete mode 100644 ModAssistant/OneClickStatus.xaml delete mode 100644 ModAssistant/OneClickStatus.xaml.cs delete mode 100644 ModAssistant/Pages/Mods.xaml.cs delete mode 100644 ModAssistant/Pages/Options.xaml.cs delete mode 100644 ModAssistant/Resources/icon.ico delete mode 100644 ModAssistant/Themes/BSMG.xaml delete mode 100644 ModAssistant/Themes/BSMG/Sidebar.png rename ModAssistant.sln => VRCMelonAssistant.sln (66%) create mode 100644 VRCMelonAssistant/App.config rename {ModAssistant => VRCMelonAssistant}/App.xaml (95%) rename {ModAssistant => VRCMelonAssistant}/App.xaml.cs (70%) rename {ModAssistant => VRCMelonAssistant}/Classes/Diagnostics.cs (98%) create mode 100644 VRCMelonAssistant/Classes/HardcodedCategories.cs rename {ModAssistant => VRCMelonAssistant}/Classes/Http.cs (93%) rename {ModAssistant => VRCMelonAssistant}/Classes/HyperlinkExtensions.cs (88%) create mode 100644 VRCMelonAssistant/Classes/InstallHandlers.cs rename {ModAssistant => VRCMelonAssistant}/Classes/Languages.cs (95%) create mode 100644 VRCMelonAssistant/Classes/Mod.cs rename {ModAssistant => VRCMelonAssistant}/Classes/Promotions.cs (73%) rename {ModAssistant => VRCMelonAssistant}/Classes/Themes.cs (97%) rename {ModAssistant => VRCMelonAssistant}/Classes/Updater.cs (91%) rename {ModAssistant => VRCMelonAssistant}/Classes/Utils.cs (85%) rename {ModAssistant => VRCMelonAssistant}/Libs/semver/IntExtensions.cs (98%) rename {ModAssistant => VRCMelonAssistant}/Libs/semver/SemVersion.cs (99%) rename {ModAssistant => VRCMelonAssistant}/Localisation/en-DEBUG.xaml (98%) rename {ModAssistant => VRCMelonAssistant}/Localisation/en.xaml (56%) rename {ModAssistant => VRCMelonAssistant}/MainWindow.xaml (90%) rename {ModAssistant => VRCMelonAssistant}/MainWindow.xaml.cs (50%) create mode 100644 VRCMelonAssistant/ModInfoWindow.xaml create mode 100644 VRCMelonAssistant/ModInfoWindow.xaml.cs rename {ModAssistant => VRCMelonAssistant}/Pages/About.xaml (87%) rename {ModAssistant => VRCMelonAssistant}/Pages/About.xaml.cs (95%) rename {ModAssistant => VRCMelonAssistant}/Pages/Intro.xaml (92%) rename {ModAssistant => VRCMelonAssistant}/Pages/Intro.xaml.cs (65%) rename {ModAssistant => VRCMelonAssistant}/Pages/Invalid.xaml (97%) rename {ModAssistant => VRCMelonAssistant}/Pages/Invalid.xaml.cs (90%) rename {ModAssistant => VRCMelonAssistant}/Pages/Loading.xaml (83%) rename {ModAssistant => VRCMelonAssistant}/Pages/Loading.xaml.cs (96%) rename {ModAssistant => VRCMelonAssistant}/Pages/Mods.xaml (88%) create mode 100644 VRCMelonAssistant/Pages/Mods.xaml.cs rename {ModAssistant => VRCMelonAssistant}/Pages/Options.xaml (52%) create mode 100644 VRCMelonAssistant/Pages/Options.xaml.cs rename {ModAssistant => VRCMelonAssistant}/Properties/AssemblyInfo.cs (89%) rename {ModAssistant => VRCMelonAssistant}/Properties/Resources.Designer.cs (94%) rename {ModAssistant => VRCMelonAssistant}/Properties/Resources.resx (100%) rename {ModAssistant => VRCMelonAssistant}/Properties/Settings.Designer.cs (55%) rename {ModAssistant => VRCMelonAssistant}/Properties/Settings.settings (50%) create mode 100644 VRCMelonAssistant/Resources/AppResources.xaml rename {ModAssistant => VRCMelonAssistant}/Resources/Icons.xaml (72%) create mode 100644 VRCMelonAssistant/Resources/icon.ico rename {ModAssistant => VRCMelonAssistant}/Styles/Button.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/CheckBox.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/ComboBox.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/ComboBoxItem.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/GridViewColumnHeader.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/Label.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/ListView.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/ListViewItem.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/Menu.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/MenuItem.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/RepeatButton.xaml (95%) rename {ModAssistant => VRCMelonAssistant}/Styles/ScrollBar.xaml (96%) rename {ModAssistant => VRCMelonAssistant}/Styles/TextBlock.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Styles/Thumb.xaml (95%) rename {ModAssistant => VRCMelonAssistant}/Styles/ToggleButton.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Themes/Dark.xaml (97%) rename {ModAssistant => VRCMelonAssistant}/Themes/Default Scrollbar.xaml (100%) rename {ModAssistant => VRCMelonAssistant}/Themes/Light Pink.xaml (97%) rename {ModAssistant => VRCMelonAssistant}/Themes/Light.xaml (97%) rename {ModAssistant => VRCMelonAssistant}/Themes/Ugly Kulu-Ya-Ku.xaml (97%) rename ModAssistant/ModAssistant.csproj => VRCMelonAssistant/VRCMelonAssistant.csproj (68%) create mode 100644 VRCMelonAssistant/packages.config diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 121233f..e710c2a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1 @@ -patreon: BeatSaberMods -ko_fi: N4N8JX7B -liberapay: Assistant -custom: ['https://paypal.me/AssistantMoe', 'https://bs.assistant.moe/Donate/'] +custom: ['https://github.com/Assistant/ModAssistant'] diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index af1248e..c368946 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,18 +16,18 @@ jobs: - name: Install dependencies run: msbuild -t:restore - name: Build project - run: msbuild ModAssistant/ModAssistant.csproj /t:Build /p:Configuration=Release + run: msbuild VRCMelonAssistant/VRCMelonAssistant.csproj /t:Build /p:Configuration=Release - name: Cleanup release shell: bash run: | - find "ModAssistant/bin/Release" -type f ! -name "ModAssistant.exe" -delete - cp "LICENSE" "ModAssistant/bin/Release/LICENSE.ModAssistant.txt" + find "VRCMelonAssistant/bin/Release" -type f ! -name "VRCMelonAssistant.exe" -delete + cp "LICENSE" "VRCMelonAssistant/bin/Release/LICENSE.VRCMelonAssistant.txt" - name: Upload Build if: startsWith(github.ref, 'refs/tags/') == false uses: actions/upload-artifact@v2 with: - name: ModAssistant-${{ github.sha }} - path: ./ModAssistant/bin/Release/ + name: VRCMelonAssistant-${{ github.sha }} + path: ./VRCMelonAssistant/bin/Release/ - name: Extract Release Version if: startsWith(github.ref, 'refs/tags/') id: get_version @@ -39,5 +39,5 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - name: Mod Assistant v${{ steps.get_version.outputs.version }} - files: ./ModAssistant/bin/Release/ModAssistant.exe + name: VRChat Melon Assistant v${{ steps.get_version.outputs.version }} + files: ./VRCMelonAssistant/bin/Release/VRCMelonAssistant.exe diff --git a/ModAssistant/App.config b/ModAssistant/App.config deleted file mode 100644 index 7a673b8..0000000 --- a/ModAssistant/App.config +++ /dev/null @@ -1,72 +0,0 @@ - - - - -
-
- - - - - - - - - - - - - - - False - - - True - - - False - - - False - - - False - - - - - - - - - True - - - - - - - - - True - - - False - - - - - - - - - - - - - - - - - - diff --git a/ModAssistant/Classes/External Interfaces/BeatSaver.cs b/ModAssistant/Classes/External Interfaces/BeatSaver.cs deleted file mode 100644 index 54dfc81..0000000 --- a/ModAssistant/Classes/External Interfaces/BeatSaver.cs +++ /dev/null @@ -1,364 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using System.Web; -using System.Windows; -using static ModAssistant.Http; - -namespace ModAssistant.API -{ - public class BeatSaver - { - private const string BeatSaverURLPrefix = "https://beatsaver.com"; - private static readonly string CustomSongsFolder = Path.Combine("Beat Saber_Data", "CustomLevels"); - private const bool BypassDownloadCounter = false; - - public static async Task GetFromKey(string Key, bool showNotification = true) - { - if (showNotification && App.OCIWindow != "No") OneClickInstaller.Status.Show(); - return await GetMap(Key, "key", showNotification); - } - - public static async Task GetFromHash(string Hash, bool showNotification = true) - { - if (showNotification && App.OCIWindow != "No") OneClickInstaller.Status.Show(); - return await GetMap(Hash, "hash", showNotification); - } - - private static async Task GetMap(string id, string type, bool showNotification) - { - string urlSegment; - switch (type) - { - case "hash": - urlSegment = "/api/maps/by-hash/"; - break; - case "key": - urlSegment = "/api/maps/detail/"; - break; - default: - return null; - } - - BeatSaverMap map = new BeatSaverMap - { - Success = false - }; - - if (showNotification) Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), id)}"); - try - { - BeatSaverApiResponse beatsaver = await GetResponse(BeatSaverURLPrefix + urlSegment + id); - if (beatsaver != null && beatsaver.map != null) - { - map.response = beatsaver; - map.Name = await InstallMap(beatsaver.map, showNotification); - map.Success = true; - } - } - catch (Exception e) - { - ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e.Message}", "ERROR"); - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Failed"), (map.Name ?? id))}"); - App.CloseWindowOnFinish = false; - } - return map; - } - - private static async Task GetResponse(string url, bool showNotification = true, int retries = 3) - { - if (retries == 0) - { - ModAssistant.Utils.Log($"Max tries reached: Skipping {url}", "ERROR"); - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitSkip"), url)}"); - App.CloseWindowOnFinish = false; - throw new Exception("Max retries allowed"); - } - - BeatSaverApiResponse response = new BeatSaverApiResponse(); - try - { - var resp = await HttpClient.GetAsync(url); - response.statusCode = resp.StatusCode; - response.ratelimit = GetRatelimit(resp.Headers); - string body = await resp.Content.ReadAsStringAsync(); - - if ((int)resp.StatusCode == 429) - { - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitHit"), response.ratelimit.ResetTime.ToLocalTime())}"); - await response.ratelimit.Wait(); - return await GetResponse(url, showNotification, retries - 1); - } - - if (response.statusCode == HttpStatusCode.OK) - { - response.map = JsonSerializer.Deserialize(body); - return response; - } - else - { - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Failed"), url)}"); - App.CloseWindowOnFinish = false; - return response; - } - } - catch (Exception e) - { - if (showNotification) - { - MessageBox.Show($"{Application.Current.FindResource("OneClick:MapDownloadFailed")}\n\n" + e); - } - return null; - } - } - - public static async Task InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true) - { - string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip"; - string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})") - .Split(ModAssistant.Utils.Constants.IllegalCharacters)); - string directory = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, mapName); - -#pragma warning disable CS0162 // Unreachable code detected - if (BypassDownloadCounter) - { - await Utils.DownloadAsset(BeatSaverURLPrefix + Map.directDownload, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification, true); - } - else - { - await Utils.DownloadAsset(BeatSaverURLPrefix + Map.downloadURL, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification, true); - } -#pragma warning restore CS0162 // Unreachable code detected - - if (File.Exists(zip)) - { - string mimeType = MimeMapping.GetMimeMapping(zip); - - if (!mimeType.StartsWith("application/x-zip")) - { - ModAssistant.Utils.Log($"Failed extracting BeatSaver map: {zip} \n| Content: {string.Join("\n", File.ReadAllLines(zip))}", "ERROR"); - throw new Exception("File not a zip."); - } - - try - { - using (FileStream stream = new FileStream(zip, FileMode.Open)) - using (ZipArchive archive = new ZipArchive(stream)) - { - foreach (ZipArchiveEntry file in archive.Entries) - { - string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName)); - if (!Directory.Exists(fileDirectory)) - { - Directory.CreateDirectory(fileDirectory); - } - - if (!string.IsNullOrEmpty(file.Name)) - { - file.ExtractToFile(Path.Combine(directory, file.FullName), true); - } - } - } - } - catch (Exception e) - { - File.Delete(zip); - ModAssistant.Utils.Log($"Failed extracting BeatSaver map: {zip} | Error: {e} \n| Content: {string.Join("\n", File.ReadAllLines(zip))}", "ERROR"); - throw new Exception("File extraction failed."); - } - File.Delete(zip); - } - else - { - if (showNotification) - { - string line1 = (string)Application.Current.FindResource("OneClick:SongDownload:Failed"); - string line2 = (string)Application.Current.FindResource("OneClick:SongDownload:NetworkIssues"); - string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle"); - MessageBox.Show($"{line1}\n{line2}", title); - } - throw new Exception("Zip file not found."); - } - return mapName; - } - - public static BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers) - { - BeatSaverRatelimit ratelimit = new BeatSaverRatelimit(); - - if (headers.TryGetValues("Rate-Limit-Remaining", out IEnumerable _remaining)) - { - var Remaining = _remaining.GetEnumerator(); - Remaining.MoveNext(); - ratelimit.Remaining = int.Parse(Remaining.Current); - Remaining.Dispose(); - } - - if (headers.TryGetValues("Rate-Limit-Reset", out IEnumerable _reset)) - { - var Reset = _reset.GetEnumerator(); - Reset.MoveNext(); - ratelimit.Reset = int.Parse(Reset.Current); - ratelimit.ResetTime = UnixTimestampToDateTime((long)ratelimit.Reset); - Reset.Dispose(); - } - - if (headers.TryGetValues("Rate-Limit-Total", out IEnumerable _total)) - { - var Total = _total.GetEnumerator(); - Total.MoveNext(); - ratelimit.Total = int.Parse(Total.Current); - Total.Dispose(); - } - - return ratelimit; - } - - public static DateTime UnixTimestampToDateTime(double unixTime) - { - DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond); - return new DateTime(unixStart.Ticks + unixTimeStampInTicks, DateTimeKind.Utc); - } - - public static async Task Download(string url, string output, int retries = 3) - { - if (retries == 0) - { - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitSkip"), url)}"); - App.CloseWindowOnFinish = false; - ModAssistant.Utils.Log($"Max tries reached: Couldn't download {url}", "ERROR"); - throw new Exception("Max retries allowed"); - } - - var resp = await HttpClient.GetAsync(url); - - if ((int)resp.StatusCode == 429) - { - var ratelimit = GetRatelimit(resp.Headers); - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitHit"), ratelimit.ResetTime.ToLocalTime())}"); - - await ratelimit.Wait(); - await Download(url, output, retries - 1); - } - - using (var stream = await resp.Content.ReadAsStreamAsync()) - using (var fs = new FileStream(output, FileMode.OpenOrCreate, FileAccess.Write)) - { - await stream.CopyToAsync(fs); - } - } - -#pragma warning disable IDE1006 // Naming Styles - public class BeatSaverMap - { - public BeatSaverApiResponse response { get; set; } - public bool Success { get; set; } - public string Name { get; set; } - } - - public class BeatSaverApiResponse - { - public HttpStatusCode statusCode { get; set; } - public BeatSaverRatelimit ratelimit { get; set; } - public BeatSaverApiResponseMap map { get; set; } - } - - public class BeatSaverRatelimit - { - public int? Remaining { get; set; } - public int? Total { get; set; } - public int? Reset { get; set; } - public DateTime ResetTime { get; set; } - public async Task Wait() - { - await Task.Delay(new TimeSpan(Math.Max(ResetTime.Ticks - DateTime.UtcNow.Ticks, 0))); - } - } - - public class BeatSaverApiResponseMap - { - public Metadata metadata { get; set; } - public Stats stats { get; set; } - public string description { get; set; } - public DateTime? deletedAt { get; set; } - public string _id { get; set; } - public string key { get; set; } - public string name { get; set; } - public Uploader uploader { get; set; } - public DateTime uploaded { get; set; } - public string hash { get; set; } - public string directDownload { get; set; } - public string downloadURL { get; set; } - public string coverURL { get; set; } - - public class Difficulties - { - public bool easy { get; set; } - public bool normal { get; set; } - public bool hard { get; set; } - public bool expert { get; set; } - public bool expertPlus { get; set; } - } - - public class Metadata - { - public Difficulties difficulties { get; set; } - public Characteristic[] characteristics { get; set; } - public double duration { get; set; } - public string songName { get; set; } - public string songSubName { get; set; } - public string songAuthorName { get; set; } - public string levelAuthorName { get; set; } - public double bpm { get; set; } - } - - public class Characteristic - { - public string name { get; set; } - public CharacteristicDifficulties difficulties { get; set; } - } - - public class CharacteristicDifficulties - { - public Difficulty easy { get; set; } - public Difficulty normal { get; set; } - public Difficulty hard { get; set; } - public Difficulty expert { get; set; } - public Difficulty expertPlus { get; set; } - } - - public class Difficulty - { - public double? duration { get; set; } - public double? length { get; set; } - public double bombs { get; set; } - public double notes { get; set; } - public double obstacles { get; set; } - public double njs { get; set; } - public double njsOffset { get; set; } - } - - public class Stats - { - public int downloads { get; set; } - public int plays { get; set; } - public int downVotes { get; set; } - public int upVotes { get; set; } - public double heat { get; set; } - public double rating { get; set; } - } - - public class Uploader - { - public string _id { get; set; } - public string username { get; set; } - } - } - } -} -#pragma warning restore IDE1006 // Naming Styles diff --git a/ModAssistant/Classes/External Interfaces/ModelSaber.cs b/ModAssistant/Classes/External Interfaces/ModelSaber.cs deleted file mode 100644 index f3da082..0000000 --- a/ModAssistant/Classes/External Interfaces/ModelSaber.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace ModAssistant.API -{ - class ModelSaber - { - private const string ModelSaberURLPrefix = "https://modelsaber.com/files/"; - private const string CustomAvatarsFolder = "CustomAvatars"; - private const string CustomSabersFolder = "CustomSabers"; - private const string CustomPlatformsFolder = "CustomPlatforms"; - private const string CustomBloqsFolder = "CustomNotes"; - - public static async Task GetModel(Uri uri) - { - switch (uri.Host) - { - case "avatar": - await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomAvatarsFolder); - break; - case "saber": - await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomSabersFolder); - break; - case "platform": - await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomPlatformsFolder); - break; - case "bloq": - await Utils.DownloadAsset(ModelSaberURLPrefix + uri.Host + uri.AbsolutePath, CustomBloqsFolder); - break; - } - } - - } -} diff --git a/ModAssistant/Classes/External Interfaces/Playlists.cs b/ModAssistant/Classes/External Interfaces/Playlists.cs deleted file mode 100644 index 93f3d5a..0000000 --- a/ModAssistant/Classes/External Interfaces/Playlists.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Web; -using System.Windows; -using static ModAssistant.Http; - -namespace ModAssistant.API -{ - public class Playlists - { - private const string BSaberURLPrefix = "https://bsaber.com/PlaylistAPI/"; - private const string PlaylistsFolder = "Playlists"; - private static readonly string BeatSaberPath = Utils.BeatSaberPath; - - public static void CreatePlaylistsFolder() - { - string playlistsPath = Path.Combine(BeatSaberPath, PlaylistsFolder); - Directory.CreateDirectory(playlistsPath); - } - - public static async Task DownloadAll(Uri uri) - { - switch (uri.Host) - { - case "playlist": - Uri url = new Uri($"{uri.LocalPath.Trim('/')}"); - string filename = await Get(url); - await DownloadFrom(filename); - break; - } - } - - public static async Task Get(Uri url) - { - string filename = HttpUtility.UrlDecode(url.Segments.Last()); - string absolutePath = Path.Combine(BeatSaberPath, PlaylistsFolder, filename); - try - { - CreatePlaylistsFolder(); - await Utils.DownloadAsset(url.ToString(), PlaylistsFolder, filename); - - return absolutePath; - } - catch - { - return null; - } - } - - public static async Task DownloadFrom(string file) - { - CreatePlaylistsFolder(); - - if (Path.Combine(BeatSaberPath, PlaylistsFolder) != Path.GetDirectoryName(file)) - { - string destination = Path.Combine(BeatSaberPath, PlaylistsFolder, Path.GetFileName(file)); - File.Copy(file, destination, true); - } - - int Errors = 0; - int Minimum = 0; - int Value = 0; - - Playlist playlist = JsonSerializer.Deserialize(File.ReadAllText(file)); - int Maximum = playlist.songs.Length; - - foreach (Playlist.Song song in playlist.songs) - { - API.BeatSaver.BeatSaverMap response = new BeatSaver.BeatSaverMap(); - if (!string.IsNullOrEmpty(song.hash)) - { - response = await BeatSaver.GetFromHash(song.hash, false); - } - else if (!string.IsNullOrEmpty(song.key)) - { - response = await BeatSaver.GetFromKey(song.key, false); - } - Value++; - - if (response.Success) - { - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))} {response.Name}"); - } - else - { - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}"); - ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash} | ({response?.response?.ratelimit?.Remaining})"); - App.CloseWindowOnFinish = false; - await Task.Delay(3 * 1000); - Errors++; - } - } - Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}"); - } - - private static string TextProgress(int min, int max, int value) - { - if (max == value) - { - return $" {string.Concat(Enumerable.Repeat("▒", 10))} [{value}/{max}]"; - } - int interval = (int)Math.Floor((double)value / (((double)max - (double)min) / (double)10)); - return $" {string.Concat(Enumerable.Repeat("▒", interval))}{string.Concat(Enumerable.Repeat("░", 10 - interval))} [{value}/{max}]"; - } - -#pragma warning disable IDE1006 // Naming Styles - class Playlist - { - public string playlistTitle { get; set; } - public string playlistAuthor { get; set; } - public string image { get; set; } - public Song[] songs { get; set; } - - public class Song - { - public string key { get; set; } - public string hash { get; set; } - public string songName { get; set; } - public string uploader { get; set; } - } - } - } -} -#pragma warning restore IDE1006 // Naming Styles diff --git a/ModAssistant/Classes/External Interfaces/Utils.cs b/ModAssistant/Classes/External Interfaces/Utils.cs deleted file mode 100644 index e9a0cbf..0000000 --- a/ModAssistant/Classes/External Interfaces/Utils.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using System.Windows; - -namespace ModAssistant.API -{ - public class Utils - { - public static readonly string BeatSaberPath = App.BeatSaberInstallDirectory; - - public static void SetMessage(string message) - { - if (App.OCIWindow != "No") - { - if (App.window == null) - { - if (App.OCIWindow == "No") OneClickStatus.Instance = null; - if (OneClickStatus.Instance == null) return; - - OneClickStatus.Instance.MainText = message; - } - else - { - MainWindow.Instance.MainText = message; - } - } - } - - public static async Task DownloadAsset(string link, string folder, bool showNotifcation, string fileName = null) - { - await DownloadAsset(link, folder, fileName, null, showNotifcation); - } - - public static async Task DownloadAsset(string link, string folder, string fileName = null, string displayName = null) - { - await DownloadAsset(link, folder, fileName, displayName, true); - } - - public static async Task DownloadAsset(string link, string folder, string fileName, string displayName, bool showNotification, bool beatsaver = false) - { - if (string.IsNullOrEmpty(BeatSaberPath)) - { - ModAssistant.Utils.SendNotify((string)Application.Current.FindResource("OneClick:InstallDirNotFound")); - } - try - { - Directory.CreateDirectory(Path.Combine(BeatSaberPath, folder)); - if (string.IsNullOrEmpty(fileName)) - { - fileName = WebUtility.UrlDecode(Path.Combine(BeatSaberPath, folder, new Uri(link).Segments.Last())); - } - else - { - fileName = WebUtility.UrlDecode(Path.Combine(BeatSaberPath, folder, fileName)); - } - if (string.IsNullOrEmpty(displayName)) - { - displayName = Path.GetFileNameWithoutExtension(fileName); - } - - if (beatsaver) await BeatSaver.Download(link, fileName); - else await ModAssistant.Utils.Download(link, fileName); - - if (showNotification) - { - SetMessage(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName)); - } - } - catch - { - SetMessage((string)Application.Current.FindResource("OneClick:AssetInstallFailed")); - App.CloseWindowOnFinish = false; - } - } - } -} diff --git a/ModAssistant/Classes/Mod.cs b/ModAssistant/Classes/Mod.cs deleted file mode 100644 index 5b526fa..0000000 --- a/ModAssistant/Classes/Mod.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using ModAssistant.Pages; - -namespace ModAssistant -{ - public class Mod - { - public string name; - public string version; - public string gameVersion; - public string _id; - public string status; - 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 deleted file mode 100644 index 6ab19d2..0000000 --- a/ModAssistant/Classes/OneClickInstaller.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using Microsoft.Win32; - -namespace ModAssistant -{ - class OneClickInstaller - { - private static readonly string[] Protocols = new[] { "modelsaber", "beatsaver", "bsplaylist" }; - public static OneClickStatus Status = new OneClickStatus(); - - public static async Task InstallAsset(string link) - { - Uri uri = new Uri(link); - if (!Protocols.Contains(uri.Scheme)) return; - - switch (uri.Scheme) - { - case "modelsaber": - await ModelSaber(uri); - break; - case "beatsaver": - await BeatSaver(uri); - break; - case "bsplaylist": - await Playlist(uri); - break; - } - if (App.OCIWindow != "No") - { - Status.StopRotation(); - API.Utils.SetMessage((string)Application.Current.FindResource("OneClick:Done")); - } - if (App.OCIWindow == "Close") - { - Application.Current.Shutdown(); - } - } - - private static async Task BeatSaver(Uri uri) - { - string Key = uri.Host; - await API.BeatSaver.GetFromKey(Key); - } - - private static async Task ModelSaber(Uri uri) - { - if (App.OCIWindow != "No") Status.Show(); - API.Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), System.Web.HttpUtility.UrlDecode(uri.Segments.Last()))}"); - await API.ModelSaber.GetModel(uri); - } - - private static async Task Playlist(Uri uri) - { - if (App.OCIWindow != "No") Status.Show(); - await API.Playlists.DownloadAll(uri); - } - - public static void Register(string Protocol, bool Background = false, string Description = null) - { - if (IsRegistered(Protocol) == true) - return; - try - { - if (Utils.IsAdmin) - { - RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true); - if (ProtocolKey == null) - ProtocolKey = Registry.ClassesRoot.CreateSubKey(Protocol, true); - RegistryKey CommandKey = ProtocolKey.CreateSubKey(@"shell\open\command", true); - if (CommandKey == null) - CommandKey = Registry.ClassesRoot.CreateSubKey(@"shell\open\command", true); - - if (ProtocolKey.GetValue("OneClick-Provider", "").ToString() != "ModAssistant") - { - if (Description != null) - { - ProtocolKey.SetValue("", Description, RegistryValueKind.String); - } - ProtocolKey.SetValue("URL Protocol", "", RegistryValueKind.String); - ProtocolKey.SetValue("OneClick-Provider", "ModAssistant", RegistryValueKind.String); - CommandKey.SetValue("", $"\"{Utils.ExePath}\" \"--install\" \"%1\""); - } - - Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:ProtocolHandler:Registered"), Protocol)); - } - else - { - Utils.StartAsAdmin($"\"--register\" \"{Protocol}\" \"{Description}\""); - } - } - catch (Exception e) - { - MessageBox.Show(e.ToString()); - } - - if (Background) - Application.Current.Shutdown(); - else - Pages.Options.Instance.UpdateHandlerStatus(); - } - - public static void Unregister(string Protocol, bool Background = false) - { - if (IsRegistered(Protocol) == false) - return; - try - { - if (Utils.IsAdmin) - { - using (RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol, true)) - { - if (ProtocolKey != null - && ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant") - { - Registry.ClassesRoot.DeleteSubKeyTree(Protocol); - } - } - - Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:ProtocolHandler:Unregistered"), Protocol)); - } - else - { - Utils.StartAsAdmin($"\"--unregister\" \"{Protocol}\""); - } - - } - catch (Exception e) - { - MessageBox.Show(e.ToString()); - } - - if (Background) - Application.Current.Shutdown(); - else - Pages.Options.Instance.UpdateHandlerStatus(); - } - - public static bool IsRegistered(string Protocol) - { - RegistryKey ProtocolKey = Registry.ClassesRoot.OpenSubKey(Protocol); - if (ProtocolKey != null - && ProtocolKey.GetValue("OneClick-Provider", "").ToString() == "ModAssistant") - return true; - else - return false; - } - } -} diff --git a/ModAssistant/Localisation/de.xaml b/ModAssistant/Localisation/de.xaml deleted file mode 100644 index aadf7fe..0000000 --- a/ModAssistant/Localisation/de.xaml +++ /dev/null @@ -1,252 +0,0 @@ - - i18n:de-DE - - - Der Beat Saber Installationsordner konnte nicht gefunden werden! - Drücke OK um es erneut zu versuchen, oder Abbrechen um das Programm zu beenden. - Ungültiges Argument! '{0}' benötigt eine Option. - Unbekanntes Argument. Beende Mod Assistant. - Eine nicht behandelte Ausnahme ist aufgetreten - Ausnahme - - - Mod Assistant - Intro - Mods - Über - Optionen - Spiel Version - Version - Mod Info - Installieren/ - Aktualisieren - Spielversion konnte nicht geladen werden, der Mods Tab wird nicht verfügbar sein. - Neue Spielversion gefunden! - Es scheint ein Spiel Update gegeben zu haben. - Bitte prüfe ob unten links die richtige Version ausgewählt ist! - Kein Mod ausgewählt! - {0} hat keine Informationsseite. - - - Intro - Willkommen bei Mod Assistant - Bitte lies diese Seite vollständig und aufmerksam! - - Durch Nutzung des Programms wird bestätigt, dass folgende Bedingungen gelesen und akzeptiert wurden: - - - Beat Saber - unterstützt normalerweise keine Mods. Das heißt: - - - Mods - werden nach jedem Update nicht mehr funktionieren. Dies ist normal, und - die Schuld liegt nicht bei Beat Games. - - - Mods - werden Fehler und Leistungsprobleme verursachen. Die Schuld - liegt nicht bei Beat Games. - - - Mods werden - kostenlos von Leuten in deren - Freizeit erstellt. Bitte sei geduldig und verständnisvoll. - - - Bitte gib KEINE schlechten Bewertungen weil die Mods nicht funktionieren. Die Schuld - liegt nicht bei Beat Games. - Sie versuchen nicht die Mods zu unterbinden. - - - Wenn ich weiterhin schlecht Bewertungen - wegen nicht funktionierenden Mods sehe, - - Werde ich persönlich die Mods mit einem rostigen Löffel töten - - - Bitte lies den Einsteiger Leitfaden im - - Wiki - . - - Annehmen - Ablehnen - Programm wird beendet: Du hast den Bedingungen nicht zugestimmt. - Versionsliste konnte nicht geladen werden - Mods Tab deaktiviert. Bitte Programm neu starten um es nochmal zu versuchen. - Du kannst jetzt den Mods Tab benutzen! - - - Mods - Name - Installiert - Neuste - Beschreibung - Entfernen - Entfernen - Modliste konnte nicht geladen werden - Prüfe installierte Mods - Lade Mods - Laden der Mods abgeschlossen - Für diese Version von Beat Saber sind keine Mods verfügbar. - Installiere {0} - {0} installiert - Mod Installation abgeschlossen - Downloadlink für {0} konnte nicht gefunden werden - {0} entfernen? - Bist du dir sicher das du {0} entfernen möchtest? - Dies kann die anderen Mods unbrauchbar machen - Fehler beim Extrahieren von {0}, neuer Versuch in {1} Sekunden. ({2}/{3}) - Fehler beim Extrahieren von {0} nach {1} Versuchen, wird übersprungen. Dieser Mod funktioniert möglicherweise nicht richtig, also gehe auf eigenes Risiko vor - Suchen... - Deinstallieren von BSIPA ist fehlgeschlagen - BSIPA Installation wurde nicht gefunden, Deinstallation wurde übersprungen. - - - Über - Über Mod Assistant - Ich bin Assistant und ich habe Mod Assistant zum Verwalten von Mods nach folgenden Prinzipien erstellt: - Einfachheit - Portabilität - Nur eine Datei - Verantwortungsbewusster Umgang - - Wenn dir das Programm gefällt und du mich unterstützen möchtest, dann besuche meine - - Spendenseite - - oder mein - - Patreon - - - Besonderer Dank ♥ - Spenden - Kopf tätscheln - Umarmungen - - - Optionen - Einstellungen - Installationsordner - Ordner wählen - Ordner öffnen - Ausgewählte Mods speichern - Installierte Mods prüfen - Installierte Mods auswählen - Installierte Mods neu installieren - OneClick™ Installation aktivieren - BeatSaver - ModelSaber - Playlists - Schließe Fenster wenn fertig - Spiel-Typ - Steam - Oculus - Werkzeuge - Playlist installieren - Installiere Playlist: {0} - Titel fehlgeschlagen: {0} - [{0} Fehler] Playlist Installation abgeschlossen: {1} - Zeige OneClick Installations-Fenster - Ja - Schließen - Nein - Diagnose - Log öffnen - AppData öffnen - BSIPA entfernen - Mods entfernen - Design - Exportieren - Log wird hochgeladen - Log URL in die Zwischenablage kopiert! - Log Hochladen fehlgeschlagen! - Log Hochladen fehlgeschlagen! - Log Datei konnte nicht zu Teknik hochgeladen werden, bitte nochmal versuchen oder die Datei manuell senden. - Lade Liste der Mods - Suche BSIPA Version - BSIPA entfernt - Alle Mods entfernen? - Bist du dir sicher das du ALLE Mods entfernen möchtest? - Dies kann nicht rückgängig gemacht werden. - Alle Mods entfernt - Aktuelles Design wurde entfernt, gehe zurück zum Standart... - Designs Ordner nicht gefunden! Versuche die Vorlage zu exportieren... - AppData Ordner nicht gefunden! Versuche dein Spiel zu starten. - - - Lade Mods - - - Ungültig - Ungültige Installation erkannt - Die SPielinstallation ist beschädigt oder anderweitig ungültig - Dies kann passieren wenn dein Spiel eine Raubkopie ist oder eine Raubkopie über eine legitime Version kopiert wurde - - Falls dein Spiel eine Raubkopie ist, - bitte kaufe das Spiel - - HIER - - . - - - Wenn dein Spiel - keine Raubkopie ist, bitte - - mach eine saubere Neuinstallation - . - - - Falls das nicht hilft, frage im - #pc-help Kanal in der - - BSMG - . - - Falls du eine Raubkopie hattest aber das Spiel jetzt gekauft hast - Ordner auswählen - Muss Mod Assistant neu gestartet werden wenn eine legitime Version installiert wurde - - - Map Details konnten nicht geladen werden. - Titel konnte nicht geladen werden. - Titel konnte nicht geladen werden. - Möglicherweise gibt es Probleme mit BeatSaver oder deiner Internetverbindung. - Herunterladen der Titel ZIP fehlgeschlagen - Beat Saber Installationspfad nicht gefunden. - Installiert: {0} - Installation fehlgeschlagen. - {0} OneClick™ Installation Handler registriert! - {0} OneClick™ Installation Handler entfernt! - Installiere: {0} - Maximale Anzahl an Versuchen erreicht: Überspringe {0} - Wiederholungs-Limit erreicht. Setze fort in {0} - Download fehlgeschlagen: {0} - Abgeschlossen - - - Design nicht gefunden, gehe zurück zum Standard Design... - Design gesetzt auf: {0}. - {0} existiert nicht. - Designvorlage "{0}" in Design Ordner gespeichert. - Designvorlage existiert bereits! - Fehler beim Laden der .xaml Datei von Design {0}: {1} - - - Konnte nicht auf Aktualisierungen prüfen. - Konnte Aktualisierung nicht herunterladen. - - - Mod Assistant - Beat Saber Installationsordner konnte nicht erkannt werden. Bitte manuell auswählen. - Mod Assistant muss diese Aufgabe mit Administrator Rechten ausführen. Bitte nochmal versuchen. - Wähle den Beat Saber Installationsordner aus - Ordner konnte nicht geöffnet werden: {0} - diff --git a/ModAssistant/Localisation/fr.xaml b/ModAssistant/Localisation/fr.xaml deleted file mode 100644 index 6f96283..0000000 --- a/ModAssistant/Localisation/fr.xaml +++ /dev/null @@ -1,258 +0,0 @@ - - i18n:fr-FR - - - Impossible de trouver le dossier d'installation de Beat Saber ! - Appuyez sur OK pour réessayer, ou Annuler pour fermer l'application. - Argument invalide ! '{0}' nécessite une option. - Argument non reconnu. Fermeture de Mod Assistant. - Une exception non gérée est survenue - Exception - - - Mod Assistant - Intro - Mods - À propos - Options - Version du jeu - Version - Info sur le mod - Installer - ou Mettre à jour - Impossible de charger les versions du jeu, l'onglet Mods sera indisponible. - Nouvelle version du jeu détectée ! - Il semble que le jeu a été mis à jour. - Veuillez vous assurer que la bonne version soit sélectionnée en bas à gauche ! - Aucun mod sélectionné ! - {0} n'a pas de page informative. - - - Intro - Bienvenue sur Mod Assistant - Veuillez lire cette page entièrement et avec attention - - En utilisant ce programme, vous attestez que vous avez lu et accepté les modalités suivantes : - - - Beat Saber - ne supporte - pas nativement les mods. Cela signifie que : - - - Les mods - dysfonctionneront à chaque mise à jour. C'est normal, et ce - n'est - pas la faute de Beat Games. - - - Les mods - causeront des bugs et des problèmes de performance. Ce - n'est - pas la faute de Beat Games. - - - Les mods sont créés - gratuitement par des gens sur leur - temps libre. Veuillez être patient et compréhensif. - - - NE laissez - PAS de commentaires négatifs parce que les mods ne fonctionnent plus. Ce - n'est - pas la faute de Beat Games. - Ils n'essaient pas de faire disparaître les mods. - - - Si je continue de voir des gens laisser des commentaires négatifs - parce que les mods ne fonctionnent plus, - - J'irai personnellement liquider les mods avec une cuillère rouillée - - - Veuillez lire le Guide du Débutant sur le - - Wiki - . - - J'accepte - Je refuse - Fermeture de l'application : vous n'avez pas accepté les modalités et conditions. - Impossible de télécharger la liste des versions - Onglet Mods désactivé. Veuillez relancer pour réessayer. - Vous pouvez désormais utiliser l'onglet Mods ! - - - Mods - Nom - Installé - Récent - Description - Désinstaller - Désinstaller - Impossible de charger la liste des mods - Vérification des mods installés - Chargement des mods - Fin : mods chargés - Aucun mod n'est disponible pour cette version de Beat Saber - Installation de {0} - {0} installé - Fin : mods installés - Impossible de trouver le lien de téléchargement de {0} - Désinstaller {0} ? - Êtes-vous sûr de vouloir supprimer {0} ? - Cela pourrait faire dysfonctionner d'autres mods installés - Échec de l'extraction de {0}, nouvelle tentative dans {1} secondes. ({2}/{3}) - Échec de l'extraction de {0} après le maximum de tentatives ({1}), abandon. Ce mod pourrait ne pas fonctionner correctement, continuez à vos propres risques - Recherche... - Échec de la désinstallation de BSIPA - BSIPA installation pas trouvée, opération de désinstallation ignorée. - - - À propos - À propos de Mod Assistant - Je suis Assistant, et je réalise Mod Assistant pour l'assistance aux mods, avec quelques principes en tête : - Simplicité - Portabilité - Exécutable unique - Utilisation responsable - - Si vous aimez ce programme et souhaitez me soutenir, veuillez visiter ma - - page de don - - ou mon - - Patreon - - - Remerciements particuliers ♥ - Faire un don - Caresses-tête - Câlins - - - Options - Paramètres - Dossier d'installation - Sélectionner un dossier - Ouvrir le dossier - Sauvegarder les mods sélectionnés - Détecter les mods installés - Sélectionner les mods installés - Réinstaller les mods installés - Activer les installations OneClick™ - BeatSaver - ModelSaber - Playlists - Fermer la fenêtre à la fin - Type du jeu - Steam - Oculus - Outils - Installer une playlist - Installation de la playlist : {0} - Échec de la musique : {0} - [{0} échecs] Installation de la playlist terminée : {1} - Afficher la fenêtre de l'installateur OneClick - Oui - Fermer - Non - Diagnostic - Ouvrir les logs - Ouvrir AppData - Désinstaller BSIPA - Supprimer tous les mods - Thème de l'application - Exporter le modèle - Envoi des logs - URL des logs copiée dans le presse-papier ! - L'envoi des logs a échoué - L'envoi des logs a échoué ! - Impossible d'envoyer le fichier de logs à Teknik, veuillez réessayer ou envoyer le fichier manuellement. - Récupération de la liste des mods - Découverte de la version de BSIPA - BSIPA désinstallé - Désinstaller les mods ? - Êtes-vous sûr de vouloir supprimer TOUS les mods ? - Cela ne peut pas être annulé. - Tous les mods ont été désinstallés - Le thème actuel a été supprimé, passage au thème par défaut... - Dossier Themes non trouvé ! Essayez d'exporter le modèle... - Dossier AppData non trouvé ! Essayez de lancer votre jeu. - - - Chargement des mods - - - Invalide - Installation invalide détectée - Votre installation du jeu est corrompue sinon invalide - Cela peut survenir si votre copie du jeu est piratée, ou si vous copiez une copie piratée sur votre installation légitime - - Si votre copie du jeu est piratée, - veuillez acheter le jeu - - ICI - - . - - - Si votre copie du jeu - n'est - pas piratée, veuillez - - faire une installation propre - . - - - Si cela n'a pas fonctionné, demandez de l'aide au support dans le canal textuel - #pc-help dans - - BSMG - . - - Si vous utilisiez une version piratée mais avez acheté le jeu depuis - Sélectionner un dossier - Vous devez relancer Mod Assistant après avoir choisi l'installation légitime - - - Impossible de récupérer les détails de la map. - Impossible de télécharger la musique. - Impossible de télécharger la musique. - Il pourrait y avoir des problèmes avec BeatSaver ou votre connexion Internet. - Échec du téléchargement du ZIP de la musique - Chemin de l'installation de Beat Saber non trouvé. - Installé : {0} - Échec de l'installation. - {0} : gestionnaires d'installation OneClick™ inscrits ! - {0} : gestionnaires d'installation OneClick™ désinscrits ! - Installation de : {0} - Maximum de tentatives atteint : {0} passé - Limite atteinte. Reprise dans {0} - Téléchargement échoué : {0} - Fait - - - Thème non trouvé, passage au thème par défaut... - Thème défini sur {0}. - {0} n'existe pas. - Modèle du thème "{0}" sauvegardé dans le dossier Themes. - Le modèle du thème existe déjà ! - Échec du chargement du fichier .xaml pour le thème {0} : {1} - - - Impossible de vérifier les mises à jour. - Impossible de télécharger la mise à jour. - - - Mod Assistant - Impossible de détecter le dossier d'installation de Beat Saber. Veuillez le sélectionner manuellement. - Mod Assistant a besoin de lancer cette tâche en administrateur. Veuillez réessayer. - Sélectionnez le dossier d'installation de Beat Saber - Impossible d'ouvrir le dossier : {0} - diff --git a/ModAssistant/Localisation/it.xaml b/ModAssistant/Localisation/it.xaml deleted file mode 100644 index b5667ad..0000000 --- a/ModAssistant/Localisation/it.xaml +++ /dev/null @@ -1,252 +0,0 @@ - - i18n:it-IT - - - Impossibile trovare la directory d'installazione di Beat Saber - Premi OK per riprovare, oppure Cancel per chiudere l'applicazione. - Argomento non valido! '{0}' ha bisogno di un'opzione. - Argomento non riconosciuto. Chiusura di Mod Assistant. - Si è appena verificata un'eccezione non gestita - Eccezione - - - Mod Assistant - Introduzione - Mod - Info - Opzioni - Versione del gioco - Versione - Info sulla mod - Installa - o Aggiorna - Non è stato possibile caricare le versioni del gioco, il menù Mod non sarà disponibile. - Rilevata nuova versione del gioco! - Sembra che ci sia stato un aggiornamento del gioco. - Per piacere verifica che sia selezionata la versione corretta nel menù in basso a sinistra - Nessuna mod selezionata! - {0} non ha una pagina di informazioni. - - - Introduzione - Benvenuto/a in Mod Assistant - Ti invitiamo a leggere questa pagina fino alla fine e con cautela - - Utilizzando questo programma, attesti di accettare i seguenti termini: - - - Beat Saber - non supporta nativamente le mod. Ciò significa che: - - - Le mod - smetteranno di funzionare ad ogni aggiornamento. È assolutamente normale, e - non è colpa di Beat Games. - - - Le mod - causeranno bug e problemi di prestazioni. Questa - non è colpa di Beat Games. - - - Le mod sono fatte - gratuitamente da sviluppatori nel loro - tempo libero. Ti invitiamo ad essere paziente e di comprendere la situazione. - - - NON lasciare feedback negativi solo perche le mod smettono di funzionare. Questa - non è colpa Beat Games. - Non è loro intenzione "rompere" le mod. - - - Se continuerò a trovare persone che lasciano feedback negativi - perchè le mod smettono di funzionare, - - Mi assicurerò di farle sparire dalla circolazione - - - Ti invitiamo a leggere la guida introduttiva sulla - - Wiki - . - - Accetto - Non accetto - Chiusura dell'app: Non hai accettato i termini e condizioni. - Non sono riuscito a scaricare la lista delle versioni - Menù delle mod disattivato. Ti invitiamo a riprovare riavviando il programma. - Ora puoi utilizzare il menù Mod! - - - Mod - Nome - Versione installata - Ultima versione - Descrizione - Disinstalla - Disinstalla - Impossibile caricare la lista delle mod - Controllo le mod installate - Carico le mod - Caricamento delle mod completato - No mods available for this version of Beat Saber - Installazione di {0} in corso - {0} installato - Installazione delle mod completata - Impossibile trovare il link di download per {0} - Vuoi disinstallare {0}? - Sei sicuro di voler disinstallare {0}? - Continuando, altre mod potrebbero smettere di funzionare - Impossibile estrarre {0}, prossimo tentativo in {1} secondi. ({2}/{3}) - Non sono riuscito ad estrarre {0} dopo aver raggiunto il numero massimo di tentativi ({1}), salto questa mod. Questa protrebbe anche non funzionare correttamente, quindi procedi a tuo rischio e pericolo - Cerca... - Failed to Uninstall BSIPA - BSIPA installation not found, uninstall operation skipped. - - - Info - Info su Mod Assistant - Ciao, sono Assistant, ed ho creato Mod Assistant per aiutarmi con le mod, con alcuni principi in testa: - Semplicità d'uso - Portabilità - Un solo eseguibile - Uso responsabile - - Se ti piace questo programma, e volessi supportarmi, puoi visitare la mia - - pagina delle donazioni - - oppure il mio - - Patreon - - - Ringraziamenti speciali ♥ - Donazioni - Carezze - Abbracci - - - Opzioni - Impostazioni - Directory d'Installazione - Seleziona Cartella - Apri Cartella - Salva le Mod Selezionate - Rileva le Mod Installate - Seleziona le Mod Installate - Reinstalla le Mod - Attiva OneClick™ - BeatSaver - ModelSaber - Playlists - Close window when finished - Tipo di Installazione - Steam - Oculus - Strumenti - Installa Playlist - Installazione Playlist: {0} - Installazione canzone fallita: {0} - [{0} fails] Installazione playlist terminata: {1} - Show OneClick Installer Window - Yes - Close - No - Diagnostica - Apri il Log - Apri AppData - Disinstalla BSIPA - Rimuovi Tutte le Mod - Tema dell'app - Esporta Template - Caricamento del Log - URL del Log copiato negli Appunti! - Caricamento del Log Fallito - Caricamento del Log Fallito! - Non sono riuscito a caricare il Log su Teknik, ti invitiamo a riprovare oppure puoi caricare il file manualmente. - Prendo la lista delle Mod - Cerco la versione di BSIPA - BSIPA Disinstallato - Disinstallare tutte le Mod? - Sei sicuro di voler rimuovere TUTTE le mod? - Questa azione non può essere annullata. - Tutte le Mod sono state Disinstallate - Il tema corrente è stato rimosso, torno al principale... - Cartella del tema non trovata! Prova ad esportare il template... - Cartella AppData non trovata! Prova ad avviare il gioco. - - - Caricamento delle Mod - - - Non Valida - Installazione non valida rilevata - La tua installazione di BeatSaber è corrotta o invalida - Ciò accade se hai una copia piratata, oppure se hai installato una versione originale senza rimuovere quella piratata - - Se la tua copia è piratata, - ti invitiamo a compare il gioco - - QUI - - . - - - Se la tua copia del gioco - non è piratata, ti invitiamo a - - reinstallare il gioco - . - - - Se questo non aiuta, puoi sempre chiedere aiuto nel canale - #pc-help all'interno del - - Server Discord di BSMG - . - - Se avevi la versione piratata, ma hai comprato il gioco - Seleziona cartella - Dovrai riavviare Mod Assistant dopo aver cambiato la directory d'Installazione - - - Impossibile estrarre i dettagli della mappa. - Impossibile scaricare il brano. - Non sono riuscito a scaricare il brano. - Ci possono essere problemi con BeatSaver e/o la tua connessione ad Internet. - Download del file ZIP della mappa fallito - Directory d'installazione di Beat Saber non trovata. - Installato: {0} - Non sono riuscito ad installare la mappa. - {0} Registrazione dei gestori OneClick™ riuscita! - {0} De-Regitrazione dei gestori OneClick™ riuscita! - Installing: {0} - Max tries reached: Skipping {0} - Ratelimit hit. Resuming in {0} - Download failed: {0} - Done'd - - - Tema non trovato, ritorno al tema predefinito... - Tema impostato su {0}. - {0} non esiste. - Template del tema "{0}" salvato nella cartella Themes. - Template del tema già esistente! - Impossibile caricare il file .xaml per il tema {0}: {1} - - - Impossibile controllare gli aggiornamenti. - Impossibile scaricare l'aggiornamento. - - - Mod Assistant - Impossibile determinare automaticamente la directory d'installazione di Beat Saber. Ti invitiamo a selezionarla manualmente. - Mod Assistant ha bisogno di eseguire questa azione come Amministratore. Ti invitiamo a riprovare. - Seleziona la directory d'installazione di Beat Saber - Impossibile aprire la seguente cartella: {0} - diff --git a/ModAssistant/Localisation/ko.xaml b/ModAssistant/Localisation/ko.xaml deleted file mode 100644 index 4005192..0000000 --- a/ModAssistant/Localisation/ko.xaml +++ /dev/null @@ -1,251 +0,0 @@ - - i18n:ko-KR - - - 비트세이버 설치 폴더를 찾을 수 없습니다! - OK를 눌러 재시도하거나, Cancel을 눌러 프로그램을 종료할 수 있습니다. - 유효하지 않은 인수입니다! '{0}'은 옵션을 필요로 합니다. - 인식할 수 없는 인수입니다. 모드 어시스턴트를 종료합니다. - 처리되지 않은 예외가 발생했습니다. - 예외 - - - 모드 어시스턴트 - 인트로 - 모드 - 정보 - 옵션 - 게임 버전 - 버전 - 모드 설명 - 설치 - 또는 업데이트 - 게임 버전을 로드할 수 없었기 때문에 모드 탭이 비활성화됩니다. - 새로운 게임 버전이 감지되었습니다! - 게임 업데이트가 있었던 것 같습니다. - 왼쪽 아래에 선택된 버전이 맞는 버전인지 다시 한번 확인해주세요! - 아무런 모드도 선택되지 않았습니다! - {0}는 설명 페이지를 가지고 있지 않습니다. - - - 인트로 - 모드 어시스턴트에 어서오세요 - 이 페이지를 정독해주세요 - - 이 프로그램을 사용하려면 다음 약관을 읽고 동의해야 합니다: - - - 비트세이버는 공식적으로 모드를 - 지원하지 않습니다. - - - 이것은 모드들이 매 업데이트마다 - 망가진다는 것을 의미합니다. 이것은 일반적이며, Beat Games의 탓이 - 아닙니다. - - - 모드들은 버그와 성능 문제를 - 발생시킵니다. 이것은 Beat Games의 탓이 - 아닙니다. - - - 모드들은 - 무료로 만들어졌으며 모더들의 - 소중한 시간의 결과물입니다. 기다림을 갖고 이해해주세요. - - - 모드가 망가진 것 때문에 게임에 대한 부정적인 의견을 남기지 마세요. 이것은 Beat Games의 탓이 - 아닙니다. - Beat Games는 모드를 죽이려 하지 않습니다. - - - 만일 사람들이 모드가 - 망가진 것을 이유로 부정적인 의견을 남기는 것이 계속된다면, - - 쥐도새도 모르게 모드가 사라질지도 모릅니다. - - - - 위키 - 에 있는 초보자 가이드를 읽어주세요. - - 동의합니다 - 거부합니다 - 어플리케이션을 종료합니다: 당신은 약관에 동의하지 않았습니다. - 버전 리스트를 다운로드할 수 없었습니다 - 모드 탭이 비활성화되었습니다. 어시스턴트를 재시작해주세요. - 이제 모드 탭을 사용할 수 있습니다! - - - 모드 - 이름 - 설치 버전 - 최신 버전 - 설명 - 제거 - 제거 - 모드 목록을 불러올 수 없었습니다 - 설치된 모드들을 확인하고 있습니다 - 모드들을 불러오고 있습니다 - 모드들을 불러왔습니다 - 이 비트세이버 버전에선 사용할 수 있는 모드가 없습니다 - Installing {0} - Installed {0} - 모드 설치를 마쳤습니다 - {0}를 위한 다운로드 링크를 찾을 수 없었습니다 - {0}를 제거하시겠습니까? - 정말로 {0}를 제거하시겠습니까? - 다른 모드를 사용 못하게 만들 수도 있습니다. - {0}를 추출하는데 실패했습니다. {1}초 안에 재시도합니다. ({2}/{3}) - ({1})회동안 {0}를 추출하는데 실패했습니다. 이 모드가 제대로 작동하지 않을지도 모릅니다 - 검색 중... - BSIPA 제거에 실패하였습니다 - 설치된 BSIPA 를 찾을 수 없습니다. 제거하지 못했습니다. - - - 정보 - 모드 어시스턴트에 대하여 - I'm Assistant, and I made Mod Assistant for mod assistance, with a few principles in mind: - Simplicity - Portability - Single Executable - Responsible use - - If you enjoy this program and would like to support me, please visit my - - donation page - - or my - - Patreon - - - Special Thanks ♥ - Donate - Headpats - Hugs - - - 옵션 - 설정 - 설치 폴더 - 폴더 선택 - 폴더 열기 - 선택된 모드 저장 - 설치된 모드 감지 - 설치된 모드 선택 - 설치된 모드 재설치 - OneClick™ 설치 활성화 - BeatSaver - ModelSaber - Playlists - 완료되면 창 닫기 - 게임 유형 - Steam - Oculus - 도구 - 재생목록 설치 - 재생목록 설치 중 : {0} - 실패한 노래: {0} - [{0} 실패] 재생목록 설치 완료: {1} - OneClick™ 설치 윈도우 보기 - - 닫기 - 아니오 - 진단 - 로그 열기 - 앱데이터 열기 - BSIPA 삭제 - 모든 모드 삭제 - 어플리케이션 테마 - 템플릿 추출 - 로그 제출 - 로그 URL이 클립보드에 복사되었습니다! - 로그 제출에 실패하였습니다 - 로그 제출에 실패하였습니다! - Teknik에게 로그 파일을 제출하는데에 실패하였습니다. 재시도하거나 수동으로 파일을 보내주세요. - 모드 목록을 얻는중입니다 - BSIPA 버전을 찾는중입니다 - BSIPA가 제거되었습니다 - 모든 모드를 제거할까요? - 정말로 모든 모드를 제거할까요? - 이것은 취소할 수 없습니다. - 모든 모드가 제거되었습니다 - 현재 테마를 제거중입니다. 원래 테마로 돌아갑니다... - 테마 폴더를 찾을 수 없습니다! 템플릿으로 내보냅니다... - AppData 폴더를 찾을 수 없습니다! 게임을 실행해보세요. - - - 모드 로딩중 - - - 잘못된 프로그램 - 잘못된 프로그램 설치가 감지되었습니다 - 게임이 손상되었거나 다른 이유로 잘못된 것 같습니다 - 이 오류는 게임이 불법적인 경로로 받아졌거나, 불법적인 경로로 받아진 게임을 당신의 정상적인 게임에 덮어씌워졌을 때에 발생합니다 - - 만일 당신이 게임을 불법적인 경로로 받았다면, - - - 여기서 - - 게임을 구매해주세요. - - - 만일 당신의 게임이 불법적인 경로로 받아진게 - 아니라면, - - 클린재설치 - 를 해주세요. - - - 만일 그것들이 도움되지 않았다면, - - BSMG - 의 - #pc-help 채널에서 도움을 구하세요. - - 만일 불법적인 경로로 받아진 게임을 가지고 있었다가 게임을 구매했다면, - 폴더를 선택해주세요 - 정상적인 설치 이후 모드 어시스턴트를 재시작할 필요가 있습니다 - - - 맵의 세부정보를 얻어올 수 없었습니다. - 곡을 받아올 수 없었습니다. - 곡을 받아올 수 없었습니다. - BeatSaver 또는 당신의 인터넷 연결에 문제가 있는 것 같습니다. - 노래 ZIP 파일을 받는 데에 실패했습니다. - 비트세이버 설치 폴더를 찾을 수 없었습니다. - 설치됨: {0} - 설치에 실패하였습니다. - {0} OneClick™ 설치 관리자가 등록되었습니다! - {0} OneClick™ 설치 관리자가 등록 취소되었습니다! - 설치중: {0} - 최대 시도 횟수 도달: {0} 건너뜀 - 요청 제한 도달. {0} 재시도 - 다운로드 실패: {0} - 완료 - - - 테마를 찾을 수 없어, 기본 테마로 돌아갑니다... - {0} 테마로 설정합니다. - {0}는 존재하지 않습니다. - 템플릿 테마 "{0}"가 템플릿 폴더에 저장되었습니다. - 템플릿 테마가 이미 존재합니다! - 테마를 위한 .xaml 파일을 불러오지 못했습니다 {0}: {1} - - - 업데이트를 확인할 수 없습니다. - 업데이트를 다운로드할 수 없습니다. - - - 모드 어시스턴트 - 비트세이버 설치 폴더를 찾을 수 없습니다. 수동으로 선택해주세요. - 모드 어시스턴트는 이 작업을 관리자 권한으로 실행하는 것을 필요로 합니다. 다시 시도해주세요. - 비트세이버 설치 폴더를 선택해주세요 - 폴더를 열 수 없습니다: {0} - diff --git a/ModAssistant/Localisation/nb.xaml b/ModAssistant/Localisation/nb.xaml deleted file mode 100644 index 0c7d1a3..0000000 --- a/ModAssistant/Localisation/nb.xaml +++ /dev/null @@ -1,255 +0,0 @@ - - i18n:nb-NO - - - Fant ikke installasjonsmappen til Beat Saber! - Trykk Ok for å prøve igjen eller Avbryt for å avslutte. - Feil parameter! '{0}' krever en verdi. - Ukjent parameter. Lukker Mod Assistant. - Et uhåndtert unntak har oppstått - Unntak - - - Mod Assistant - Intro - Mods - Om - Oppsett - Spillversjon - Versjon - Modinformation - Installer - eller oppdater - Klarte ikke hente spillversjonen, Mod-fanen er derfor ikke tilgjengelig. - Ny spillversjon er tilgjengelig! - Spillet ser ut til å ha fått en oppdatering. - Dobbeltsjekk at rett versjon er valgt nede i venstre hjørnet! - Ingen mods er valgt! - {0} Denne mod har ingen informasjonsside. - - - Intro - Velkommen til Mod Assistant - Vennligst les hele denne siden nøye. - - Ved å benytte dette programmet bekrefter du at du har lest og godkjent følgende vilkår: - - - Beat Saber - har ikke innebygget støtte for mods. - Det betyr at: - - - Mods kommer til å - slutte å fungere etter hver oppdatering. Dette er standard og - ikke Beat Games sin feil. - - - Mods - kommer til å - forårsake feil og ytelseproblemer - . Dette er - ikke Beat Games sin feil. - - - Mods utvikles - gratis av mennesker på deres - fritid. Vennligst ha tålmodighet og vis forståelse for det. - - - IKKE SKRIV negative anmeldelser på grunn av at mods slutter å fungere. Det er - ikke Beat Games sin feil. - De prøver ikke å "drepe" mods. - - - Hvis jeg ser mennesker som skriver negative anmeldelser - på grunn av at mods slutter å fungere, kommer jeg - - personlig til å avlive hele moddingvirksomheten med en rusten skje. - - - Vennligst les guiden på - - Wikisiden - . - - Godkjenner - Nekter - Lukker programmet: Du godkjente ikke vilkårene. - Klarte ikke å hente liste med versjoner. - Mod-fanen er ikke tilgjengelig. Vennligst start på nytt for å prøve igjen. - Du har nå tilgang til Mod-fanen! - - - Mods - Navn - Installert - Siste - Beskrivelse - Avinstallere - Avinstaller - Klarte ikke laste mod-listen - Sjekker installerte mods - Laster inn Mods - Mods er lastet - Ingen mods er tilgjengelig for denne versjonen av Beat Saber - Installerer {0} - Installerte {0} - Mods er installert - Fant ingen nedlastningslenke for {0} - Avinstallere {0}? - Er du sikker på at du vil avinstallere {0}? - Dette kan forårsake at andre mods slutter å fungere - Utpakking av {0} feilet, prøver igjen om {1} sekunder. ({2}/{3}) - Utpakking av {0} feilet alle forsøk ({1}), hopper over. Denne moden fungerer muligens ikke, så fortsett på eget ansvar. - Søk... - Klarte ikke avinstallere BSIPA - Fant ingen BSIPA-installasjon, hopper over avinstallering. - - - Om - Om Mod Assistant - Jeg er Assistant, og jeg lagde Mod Assistant for mod-assistanse med noen grunnleggende prinsipper: - Enkelhet - Portabilitet - En enkeltstående, kjørbar fil - Ansvarsfull bruk - - Om du liker programmet og vil støtte meg, kan du besøke min - - donasjonsside - - eller min - - Patreon - - - Ekstra takk ♥ - Donere - Klapp på hodet - Klem - - - Innstillinger - Oppsett - Installationsmappe - Velg mappe - Åpne mappe - Lagre valgte mods - Sjekk installerte mods - Velg installerte mods - Reinstaller installerte mods - Aktiver OneClick™-installasjoner - BeatSaver - ModelSaber - Spillelister - Lukk vinduet når det er ferdig - Spillvariant - Steam - Oculus - Verktøy - Installer spilleliste - Installerer spillelisten: {0} - Mislykket låt: {0} - [{0} feil] Ferdig med å installere spillelister: {1} - Vis OneClick installasjonsvindu - Ja - Lukk - Nei - Diagnostikkverktøy - Åpne logg - Åpne AppData - Avinstallere BSIPA - Fjern alle mods - Applikasjonstema - Eksporter mal - Laster opp logg - Logg-URL ble kopiert! - Opplasting av logg feilet - Opplasting av logg feilet! - Klarte ikke laste opp loggen til Teknik, prøv igjen eller last den opp manuelt - Henter mod-liste - Leter etter BSIPA-versjon - BSIPA ble avinstallert - Avinstallere alle mods? - Er du sikker på at du vil avinstallere alle mods? - Du kan ikke angre. - Alle mods er nå avinstallert. - Temaet du benytter har blitt slettet, bytter til standardtema... - Fant ikke temamappen! Prøv å eksportere malen... - Fant ikke AppData-mappen! Prøv å starte spillet. - - - Laster mods - - - Ugyldig - Fant en ugyldig installasjon - Spillinstallasjonen din er brukket eller fungerer ikke av andre årsaker. - Dette kan skje dersom du har en piratversjon av spillet eller kopiert en priatversjon over din gyldige versjon av spillet. - - Hvis du har en piratversjon, - vennligst kjøp spillet - - HER - - . - - - Om din kopi av spillet - ikke er piratkopiert, vennligst - - installer spillet på nytt - . - - - Hvis ingenting hjelper kan du be om hjelp i - #pc-help -kanalen i - - BSMG - . - - Hvis du har spilt med en piratversjon før og kjøpt spillet i senere tid - Velg mappe - Du må starte Mod Assistant på nytt etter du har byttet til en legitim installasjon - - - Kunne ikke laste kartdetaljer. - Kunne ikke laste ned låta. - Kunne ikke laste ned låta. - Det kan være problemer med BeatSaver eller din internettoppkobling. - Klarte ikke laste ned ZIP-filen til låta. - Fant ikke installasjonsmappen til Beat Saber. - Installerte: {0} - Klarte ikke installere. - {0} OneClick™-installeringshåndterer ble registrert! - {0} OneClick™-installeringshåndterer ble avregistrert! - Installerer: {0} - Maks antall forsøk oppbrukt: hopper over {0} - For mange forsøk på kort tid. Fortsetter om {0} - Nedlasting mislykkes: {0} - Ferdig - - - Fant ikke temaet, går tilbake til standardtema... - Tema ble installert på {0}. - {0} eksisterer ikke. - Temamal "{0}" ble lagret i temamappen. - Temamalen finnes allerede! - Klarte ikke laste .xaml-filen for tema {0}: {1} - - - Klarte ikke søke etter oppdateringer. - Klarte ikke laste ned oppdateringen. - - - Mod Assistant - Fant ikke installasjonsmappen til Beat Saber. Velg den manuelt. - Mod Assistant må kjøres med adminrettigheter. Prøv igjen. - Velg installasjonsmappen til Beat Saber - Klarte ikke åpne mappen: {0} - diff --git a/ModAssistant/Localisation/nl.xaml b/ModAssistant/Localisation/nl.xaml deleted file mode 100644 index 5af8405..0000000 --- a/ModAssistant/Localisation/nl.xaml +++ /dev/null @@ -1,250 +0,0 @@ - - i18n:nl-NL - - - Beat Saber installatiemap niet gevonden! - Klik OK om het opnieuw te proberen of Annuleren om de toepassing af te sluiten. - Ongeldig argument! '{0}' heeft een optie nodig. - Niet herkend argument. Mod Assistant sluit af. - Een onverwachte foutcode is zojuist opgetreden - Foutcode - - - Mod Assistant - Introductie - Mods - Over - Opties - Game Versie - Versie - Mod Info - Installeren - of Update - Kan de game versie niet laden, Mods tabblad zal onbeschikbaar zijn. - Nieuwe Game Versie Gedetecteerd! - Het lijkt erop dat er een game update is geweest. - Controleer alstublieft of de correcte versie geselecteerd is linksonder in de hoek - Geen mod geselecteerd! - {0} heeft geen info pagina. - - - Introductie - Welkom bij Mod Assistant - Lees deze pagina alstublieft volledig en aandachtig door - - Door het gebruiken van dit programma verklaar ik de volgende voorwaarden te hebben gelezen en hiermee akkoord te gaan: - - - Beat Saber - Heeft geen ingebouwde ondersteuning voor mods, dit betekent dat: - - - Mods - Elke update niet meer werken, dit is normaal en niet de fout van Beat Games. - - - Mods - zullen bugs en prestatievermindering veroorzaken. Dit is niet een fout van Beat Games. - - - Mods worden - gratis gemaakt door mensen in hun - vrije tijd. Wees alstublieft geduldig en begripvol. - - - Laat GEEN negatieve beoordelingen achter op Beat Saber omdat mods niet meer werken. Dit is - niet de fout van Beat Games. - Ze proberen niet mods ontoegankelijk te maken. - - - Als ik blijf zien dat mensen negatieve reviews achterlaten - omdat mods niet meer werken, - - zal ik persoonlijk met een roestige lepel mods niet meer laten werken - - - Lees alstublieft de 'Beginners Guide' op de - - Wiki - . (Engels) - - Accepteer - Weiger - Sluit toepassing af: U accepteerde de voorwaarden niet. - Kon de versielijst niet downloaden - Mods tabblad uitgeschakeld. Herstart het programma om het opnieuw te proberen. - U kunt nu het mods tabblad gebruiken! - - - Mods - Naam - Geïnstalleerd - Recentst - Beschrijving - Deïnstalleer - Deïnstalleer - Kon de modlijst niet laden - Geïnstalleerde mods controleren - Mods Laden - Klaar met mods laden - Geen mods beschikbaar voor deze versie van Beat Saber - {0} wordt geïnstalleerd - {0} is geïnstalleerd - Klaar met mods installeren - Kon de download link voor {0} niet vinden - {0} deïnstalleren? - Weet u zeker dat u {0} wil verwijderen? - Dit zou uw andere mods niet meer kunnen laten werken - Kon {0} niet uitpakken, probeer opniew over {1} seconden. ({2}/{3}) - Kon {0} niet uitpakken na maximaal aantal pogingen ({1}), deze wordt nu overgeslagen. Deze mod werkt misschien niet goed, dus ga verder op eigen risico - Zoek... - BSIPA deïnstallatie mislukt - BSIPA installatie niet gevonden, deïnstallatie overgeslagen. - - - Over - Over Mod Assistant - Ik ben Assistant, en ik heb Mod Assistant gemaakt om te assisteren met mods, met een aantal principes als basis: - Eenvoud - Draagbaarheid - Eén enkel uitvoerbaar bestand - Verantwoordelijk gebruik - - Als u dit programma nuttig vindt en mij graag wil steunen, ga dan naar mijn - - donatie pagina - - of mijn - - Patreon - - - Bijzondere dank ♥ - Doneer - Aai over je bol - Knuffels - - - Opties - Instellingen - Installatiemap - Selecteer map - Open map - Sla geselecteerde mods op - Detecteer geïnstalleerde mods - Selecteer geïnstalleerde mods - Geïnstalleerde mods herinstalleren - Activeer OneClick™ Installaties - BeatSaver - ModelSaber - Afspeellijsten - Venster sluiten na afronding - Game Type - Steam - Oculus - Hulpmiddelen - Installeer afspeellijst - Afspeellijst installeren: {0} - Mislukt nummer: {0} - [{0} mislukkingen] Afspeellijst geïnstalleerd: {1} - Toon OneClick Installatie Scherm - Ja - Sluit - Nee - Diagnostiek - Open Logs - Open AppData - Deïnstalleer BSIPA - Verwijder Alle Mods - Toepassingsthema - Exporteer sjabloon - Log aan het uploaden - Log URL gekopieerd naar klembord! - Log Upload Mislukt - Log upload mislukt! - Kon het log bestand niet uploaden naar Teknik, probeer het alstublieft opnieuw of upload het bestand handmatig. - Mod Lijst Ophalen - BSIPA versie vinden - BSIPA Gedeïnstalleerd - Deïnstalleer ALLE mods? - Weet u zeker dat u ALLE mods wil deïnstalleren? - Dit kan niet ongedaan gemaakt worden. - Alle mods gedeïnstalleerd - Huidig thema is verwijderd, terugvallen op standaard... - Thema map niet gevonden! Probeer het sjabloon te exporteren... - AppData map niet gevonden! Probeer uw spel te starten. - - - Mods Laden - - - Ongeldig - Ongeldige Installatie Gedetecteerd - Uw game installatie is corrupt of ongeldig - Dit kan gebeuren als u een illegale versie heeft of een illegale versie over de legitieme versie heeft gekopieerd - - Als uw game versie illegaal gedownload is, - koop alstublieft de game - - HIER - - . - - - Als uw game versie - niet illegaal gedownload is, doe dan alstublieft - - een "schone" installatie - . - - - Als dat allebei niet helpt, vraag om hulp in het - #pc-help kanaal in - - BSMG - . (Engels) - - Als u een illegaal gedownloade versie van het spel had, maar nu het spel heeft gekocht - Selecteer map - Moet u Mod Assistant opnieuw starten na het wisselen naar de legitieme installatie - - - Kon map details niet ophalen. - Kon het nummer niet downloaden. - Kon het nummer niet downloaden. - Er kunnen problemen zijn met BeatSaver of uw internet verbinding. - Kon de ZIP van het nummer niet downloaden - Kon het Beat Saber installatiepad niet vinden. - {0} Geïnstalleerd - Installatie mislukt. - {0} OneClick™ Installatie koppeling geregistreerd! - {0} OneClick™ Installatie koppeling verwijderd! - {0} wordt geïnstalleerd - Maximaal aantal pogingen bereikt: {0} wordt overgeslagen - Snelheidslimiet bereikt. Hervat over {0} - Download mislukt: {0} - Voltooid - - - Thema niet gevonden, teruggevallen op standaard thema... - Thema ingesteld op {0}. - {0} bestaat niet. - Themasjabloon "{0}" opgeslagen in de Thema map. - Themasjabloon bestaat al! - Laden van .xaml bestand voor {0} mislukt: {1} - - - Kon niet controleren op updates. - Kon update niet downloaden. - - - Mod Assistant - Kon uw Beat Saber installatiemap niet vinden. Selecteer deze alstublieft handmatig. - Mod Assistant moet deze taak als administrator uitvoeren. Probeer het alstublieft opnieuw. - Selecteer uw Beat Saber installatiemap - Kan map niet openen: {0} - diff --git a/ModAssistant/Localisation/ru.xaml b/ModAssistant/Localisation/ru.xaml deleted file mode 100644 index 2de26af..0000000 --- a/ModAssistant/Localisation/ru.xaml +++ /dev/null @@ -1,252 +0,0 @@ - - i18n:ru-RU - - - Не получается найти папку с установленным Beat Saber! - Нажмите ОК, чтобы попробовать снова или ЗАКРЫТЬ, чтобы закрыть приложение. - Недопустимый аргумент! '{0}' требуется выбор. - Нераспознанный аргумент. Закрытие Mod Assistant. - Произошло необработанное исключениеd - Исключение - - - Mod Assistant - Начало - Модификации - Информация - Настройки - Версия игры - Версия - Информация о модификации - Установить - или обновить - Не удаётся загрузить список версий игры, Вкладка с модификациями недоступна. - Обнаружена новая версия игры! - Похоже на то, что было обновление игры. - Пожалуйста, проверьте дважды, что выбрана корректная версия игры в нижнем левом углу! - Нет выбранных модификаций! - {0} Не имеет страницы с информацией. - - - Вступление - Добро пожаловать в Mod Assistant - Прочитайте эту страницу полностью и внимательно - - Используя эту программу, вы прочитали и принимаете эти условия: - - - Beat Saber - не имеет нативной поддержки модификаций. Это означает: - - - Модификации - могут прекращать работоспособность каждое обновление. Это нормально, и - это не вина Beat Games. - - - Модификации - могут вызывать ошибки и проблемы с производительностью. Это - не вина Beat Games. - - - Модификации создаются - бесплатно людьми в их - свободное время. Пожалуйста, будьте терпиливыми и взаимопонимающими. - - - НЕ оставляйте отрицательные отзывы из-за того, что модификация не работает. Это - не вина Beat Games. - Beat Games не пытается уничтожить модификации. - - - Если я увижу людей, которые продолжают оставлять негативные комментарии, - потому что модификации не работают, - - Я лично уничтожу модификации ржавой ложкой - - - Пожалуйста, прочитайте инструкцию для начинающих на - - Вики - . - - Я согласен - Я не согласен - Приложение закрывается: Вы не приняли пользовательскую политику соглашений. - Не удаётся получить список версий - Вкладка с модификациями недоступна. Пожалуйста перезапустите, чтобы попробовать снова. - Теперь вы можете использовать вкладку с модификациями! - - - Модификации - Название - Установленная - Последняя - Описание - Удалить - Удалить - Не удается загрузить список модификаций - Проверка установленных модификаций - Загрузка модификаций - Загрузка модификаций окончена - No mods available for this version of Beat Saber - Установка {0} - Установлено {0} - Установка модификаций окончена. - Не удаётся получить ссылку на скачивание {0} - Удаление {0}? - Вы уверены, что хотите удалить {0}? - Это может сломать остальные модификации - Не удалось извлечь {0}, попробуйте снова через {1} секунд. ({2}/{3}) - Не удалось извлечь {0} после попыток ({1}), пропускается. Эта модификация может работать некорректно, используйте на свой страх и риск - Поиск... - Failed to Uninstall BSIPA - BSIPA installation not found, uninstall operation skipped. - - - Информация - Про Mod Assistant - Я Ассистент, и я создал Mod Assistant для помощи модификациям с некоторыми личными принципами: - Простота - Портативность - Приложение одним файлом - Ответственное использование - - Если вам нравится программа и вы хотите меня поддержать, пожалуйста, посетите - - страницу для пожертвований - - или мой - - Patreon - - - Отдельное спасибо ♥ - Поддержать - Погладить - Обнять - - - Опции - Настройки - Папка установки - Выбрать папку - Открыть папку - Сохранить выбранные модификации - Обнаружение установленных модификаций - Выбрать установленные модификации - Переустановить установленные модификации - Включить OneClick™ установки - BeatSaver - ModelSaber - Плейлисты - Закрыть окно после окончания установки - Тип игры - Steam - Oculus - Инструменты - Установить плейлист - Установка плейлиста: {0} - Ошибка с песней: {0} - [{0} ошибок] Установка плейлиста окончена: {1} - Показать OneClick окно установок - Да - Закрывать - Нет - Диагностика - Открыть логи - Открыть AppData - Удалить BSIPA - Удалить все модификации - Тема приложения - Экспортировать шаблон - Загрузка логов - Ссылка на лог успешно скопирована! - Загрузка логов не удалась - Загрузка логов не удалась! - Не удаётся загрузить файл с логом на Teknik, пожалуйста, попробуйте снова или отправьте файл вручную. - Получаем список модификаций - Поиск BSIPA версии - BSIPA удалён - Удалить все модификации? - Вы уверены, что хотите удалить ВСЕ модификации? - Это действие нельзя отменить. - Все модификации удалены! - Текущая тема была удалена, возвращаемся к стандартной теме... - Папка с темами не найдена! Пробую экспортировать шаблон... - Папка AppData не найдена! Попробуйте запустить игру. - - - Загрузка модификаций - - - Недействительно - Обнаружена недействительная установка - Ваша установленная игра сломана или недействительная - Это могло произойти, если у вас нелицензионная копия игры, или вы скопировали нелицензионную копию поверх лицензионной - - Если ваша копия игры нелицензионная, - пожалуйста, купите игру - - ТУТ - - . - - - Если ваша копия игры - лицензионная, пожалуйста - - переустановите игру заново - . - - - Если из этого ничего вам не помогает, попросите помощи в - #pc-help канал в - - BSMG - . - - Если вы использовали нелицензионную версию игру, но купили игру - выберете папку - Вам будет необходимо перезапустить Mod Assistant после того, как вы выберете папку с лицензионной копией игры - - - Не удаётся получить информацию о карте. - Не удаётся загрузить песню. - Не удаётся загрузить песню. - Возможно это ошибки с BeatSaver или вашим интернет соединением. - Ошибка с загрузкой песни ZIP - Установочный путь к Beat Saber не найден. - Установлено: {0} - Ошибка установки. - {0} OneClick™ установки зарегистрированы - {0} OneClick™ установки не зарегистрированы! - Установка: {0} - Достигнуто максимальное кол-во попыток: Пропускаем {0} - Достигнут лимит. Возобновление через {0} - Скачивание неудалось: {0} - Завершено - - - Тема не найдена, возвращаемся к стандартной теме... - Установлена тема: {0}. - {0} не существует. - Шаблон темы "{0}" сохранён в папку с темами. - Шаблон темы уже существует! - Не удаётся загрузить .xaml файл для темы {0}: {1} - - - Не удаётся проверить обновления. - Не удаётся загрузить обновление. - - - Mod Assistant - Не удаётся обнаружить папку с Beat Saber. Пожалуйста, укажите путь вручную. - Mod Assistant требует запустить эту задачу с правами администратора. Пожалуйста, попробуйте заново. - Укажите папку с установленным Beat Saber - Не удаётся открыть папку: {0} - diff --git a/ModAssistant/Localisation/sv.xaml b/ModAssistant/Localisation/sv.xaml deleted file mode 100644 index 1dd880f..0000000 --- a/ModAssistant/Localisation/sv.xaml +++ /dev/null @@ -1,252 +0,0 @@ - - i18n:sv-SE - - - Det gick inte att hitta din Beat Saber-installationsmapp! - Tryck på OK för att försöka igen, eller Cancel för att avsluta applikationen. - Invalid argument! '{0}' behöver ett alternativ. - Unrecognized argument. Stänger Mod Assistant. - Ett oväntat fel har precis inträffat - Undantag - - - Mod Assistant - Intro - Mods - Om - Inställningar - Spelversion - Version - Modinformation - Installera - eller Uppdatera - Det gick inte att ladda spelversionerna, Modfliken är ej tillgänglig. - Ny spelversion tillgänglig! - Spelet verkar ha uppdaterats. - Var vänlig dubbelkolla att rätt version är vald i det nedre vänstra hörnet! - Inga mods valda! - {0} Denna mod har ingen informationssida. - - - Intro - Välkommen till Mod Assistant - Var vänlig läs hela denna sida noggrant. - - Genom att använda detta program intygar du att du har läst och godkänt följande villkor: - - - Beat Saber - Har inte ursprungligt stöd för mods. Vilket betyder att: - - - Mods - kommer sluta att fungera efter varje uppdatering. Detta är standard, och - inte Beat Games fel. - - - Mods - kommer att orsaka buggar och prestandaproblem. Detta är - inte Beat Games fel. - - - Mods utvecklas - gratis av människor på deras - fritid. Var vänlig ha tålamod och visa förståelse för det. - - - LÄMNA INTE negativa recensioner för att mods slutat att fungera. Det är - inte Beat Games fel. - De försöker inte "döda" mods. - - - Om jag ser människor lämna negativa recensioner - för att mods slutat fungera, kommer jag - - personligen avliva hela moddingverksamheten med en rostig sked. - - - Var vänlig och läs nybörjarguiden på - - Wikisidan - . - - Godkänner - Nekar - Stänger applikationen: Du godkände ej villkoren. - Det gick inte att hämta listan över versioner. - Modfliken otillgänglig. Var vänlig starta om för att försöka igen. - Du har nu tillgång till Modfliken! - - - Mods - Namn - Installerad - Senaste - Beskrivning - Avinstallera - Avinstallera - Det gick inte att ladda modlistan - Kollar efter installerade mods - Laddar Mods - Mods färdigladdade! - Inga mods tillgängliga för denna Beat Saber-version - Installerar {0} - Installerat {0} - Mods färdiginstallerade! - Det gick inte att hitta någon nedladdningslänk för {0} - Avinstallera {0}? - Är du säker på att du vill ta bort {0}? - Detta kan leda till att dina andra mods slutar att fungera - Extrahering misslyckades {0}, försöker igen om {1} sekunder. ({2}/{3}) - Misslyckades med att extrahera {0} efter maxantalet försök ({1}), hoppar över. Denna mod kanske inte fungerar som den ska så fortsätt på egen risk. - Sök... - Misslyckades med att avinstallera BSIPA - BSIPA-installation hittades inte, hoppar över avinstallation. - - - Om - Om Mod Assistant - Jag är Assistant, och jag skapade Mod Assistant för just att assistera med mods, med några grundläggande principer i åtanke: - Enkelhet - Portabilitet - En enkel, körbar fil - Ansvarsfullt användande - - Om du gillar programmet och vill stödja mig, besök gärna min - - donationssida - - eller min - - Patreon - - - Särskilt Tack ♥ - Donera - Klapp på huvudet - Krama - - - Inställningar - Alternativ - Installationsmapp - Välj Mapp - Öppna Mapp - Spara Valda Mods - Skanna Efter Installerade Mods - Välj Installerade Mods - Ominstallera Installerade Mods - Aktivera OneClick™-Installationer - BeatSaver - ModelSaber - Spellistor - Stäng fönstret efter nedladdning - Spelvariant - Steam - Oculus - Verktyg - Installera spellista - Installerar spellista: {0} - Misslyckad låt: {0} - [{0} fails] Färdig med installation av spellistor: {1} - Visa OneClick-installationsfönstret - Ja - Stäng - Nej - Diagnostikverktyg - Öppna Loggar - Öppna AppData - Avinstallera BSIPA - Radera Alla Mods - Applikationstema - Exportera Mall - Laddar Upp Logg - Logg-URL kopierad! - Uppladdning Av Logg Misslyckades - Uppladdning Av Logg Misslyckades! - Det gick inte att ladda upp loggfilen till Teknik, var god försök igen eller skicka filen manuellt. - Hämtar Lista Över Mods - Letar efter BSIPA-Version - BSIPA Avinstallerat - Avinstallera alla mods? - Är du säker på att du vill radera alla mods? - Detta går inte att ångra. - Alla Mods avinstallerade. - Nuvarande tema borttaget, återställer till standardtemat... - Det gick inte att hitta temamappen! Testa att exportera mallen... - Det gick inte att hitta AppData-mappen! Testa att starta spelet. - - - Laddar Mods - - - Ogiltig - Ogiltig Installation Upptäckt - Din spelinstallation är korrupt eller på annat sätt ogiltig. - Detta kan hända om din kopia av spelet är piratkopierad, eller om du klistrat in en piratkopia över din lagliga installation. - - Om din kopia av spelet är piratkopierad, - var vänlig och köp spelet - - HÄR - - . - - - Om din kopia av spelet - inte är piratkopierad, var vänlig och - - ominstallera från grunden - . - - - Om inget hjälper, be om hjälp i - #pc-help -kanalen i - - BSMG - . - - Om du brukade spela på en piratkopierad version men sedan dess har köpt spelet. - Välj Mapp - Du kommer att behöva starta om Mod Assistant efter att du har bytt till en legitim version. - - - Det gick inte att hämta mapdetaljerna. - Det gick inte att ladda ned låten. - Det gick inte att ladda ned låten. - Det kan vara strul med BeatSaver eller din internetuppkoppling. - Misslyckades med att ladda ned låtens ZIP-fil. - Det gick inte att hitta installationsvägen för Beat Saber. - Installerade: {0} - Misslyckades med att installera. - {0} OneClick™-installeringshanterare registrerad! - {0} OneClick™-installeringshanterare avregistrerad! - Installerar: {0} - Maximalt antal försök uppnådda: hoppar över {0} - Ratelimit nådd. Fortsätter om {0} - Nedladdning misslyckades: {0} - Färdig - - - Temat hittades inte, återställer till standardtemat... - Tema inställt på {0}. - {0} existerar inte. - Temamall "{0}" sparad till mappen för teman. - Temamallen finns redan! - Misslyckades med att ladda .xaml -filen för tema {0}: {1} - - - Det gick inte att söka efter uppdateringar. - Det gick inte att ladda ned uppdateringen. - - - Mod Assistant - Kunde inte hitta din Beat Saber-installationsmapp. Var vänlig välj den manuellt. - Mod Assistant behöver köra detta med Adminbehörigheter. Var vänlig försök igen. - Välj din Beat Saber-installationsmapp - Kan inte öppna mapp: {0} - diff --git a/ModAssistant/Localisation/zh.xaml b/ModAssistant/Localisation/zh.xaml deleted file mode 100644 index f6df35a..0000000 --- a/ModAssistant/Localisation/zh.xaml +++ /dev/null @@ -1,250 +0,0 @@ - - i18n:zh-Hans - - - 找不到您的Beat Saber安装路径! - 点确定重试,或点取消关闭软件。 - 无效参数!'{0}'需要一个选项。 - 无法识别的参数。关闭Mod Assistant。 - 刚刚发生了一个未处理的异常 - 例外 - - - Mod Assistant - 简介 - Mod - 关于 - 选项 - 游戏版本 - 软件版本 - Mod信息 - 安装 - 或更新 - 无法获取游戏版本,Mod选项卡将不可用。 - 检测到新的游戏版本! - 似乎游戏更新了, - 请仔细检查左下角是否选择了正确的游戏版本! - 没有选择Mod! - {0}没有信息页。 - - - 简介 - 欢迎使用Mod Assistant - 请仔细阅读本页 - - 通过使用此程序,证明您已阅读并同意以下条款: - - - Beat Saber并不能原生支持Mod。 这意味着: - - - Mod将会在每次游戏更新后无法使用。这很正常,并不是游戏开发商的问题。 - - - Mod会导致出现BUG或者性能问题。这也不是游戏开发商的问题。 - - - 爱好者在空闲时间用爱发电制作了这些Mod,请保持耐心,等待Mod更新。 - - - 请勿由于Mod不可用而发表差评,Beat Games不会封杀Mod。 - - - 如果我继续看到因为Mod不可用而留下的差评,我会亲自干掉Mod。 - - 我们发现 围城 的网站未经作者同意擅自打包贩卖歌曲、模型,并抹黑社区中的成员。 - - 请各位玩家引以为鉴,不要攻击其他玩家或支持这种行为,谢谢配合。 - - - 请务必阅读 - - 新手入门指南 - 、 - - 详细教程与问题解答 - - ,以及 - - 英文教程 - 。 - - 同意 - 拒绝 - 关闭软件:您不同意此条款。 - 版本列表加载失败 - 已禁用Mod选项卡,请尝试重新打开软件后多等待一会再操作,或按照简介页面的中文教程解决。 - 你现在可以安装Mod了! - - - Mod - 名称 - 已安装 - 最新 - 介绍 - 卸载 - 卸载 - Mod列表加载失败 - 正在检查已安装的Mod - 正在加载Mod列表 - 已加载Mod列表 - 当前版本的游戏暂时没有Mod,请等待Mod更新 - 正在下载安装{0} - 已安装{0} - Mod安装完成 - {0}找不到下载地址! - 卸载{0}? - 你确定要移除{0}? - 这可能会导致其他Mod不可用。 - {0}解压失败,将在{1}秒后重试。({2}/{3}) - {0}在重试{1}次后仍然无法解压,将被跳过。注意,这个Mod可能无法使用。 - 搜索... - BSIPA卸载失败 - 找不到BSIPA,已跳过卸载操作。 - - - 关于 - 关于Mod Assistant - 我是Assistant,为了方便安装MOD,我制作了Mod Assistant。有以下特性: - 简单易用 - 可移植性 - 单一文件 - 负责任地使用 - - 如果你喜欢这个项目并且想支持我,请访问我的 - - 捐助页面 - - 或我的 - - Patreon - - - 特别感谢 ♥ - 捐助 - 摸摸头 - 抱抱 - - - 选项 - 设置 - 安装路径 - 选择路径 - 打开路径 - 保存选中的Mod - 检查已安装的Mod - 选中已安装的Mod - 重新安装已有Mod - 设置OneClick™网页一键添加资源 - [歌曲] BeatSaver - [模型] ModelSaber - [歌单] Playlists - 添加完成后关闭窗口 - 游戏平台 - Steam - Oculus - 工具 - 将歌单下载进游戏(仅限playlist文件) - 正在添加歌单:{0} - 失败歌曲:{0} - [{0}失败]添加{1}完成 - OneClick窗口显示方式: - 显示 - 完成后关闭 - 隐藏 - 诊断工具 - 打开日志 - 打开游戏存档 - 卸载BSIPA - 移除所有Mod - 软件主题 - 导出模板 - 正在上传日志 - 日志网址已复制到剪贴板! - 日志上传完成! - 日志上传失败! - 无法将日志文件上传到Teknik,请重试或手动发送文件。 - 正在获取Mod列表 - 正在检查BSIPA版本 - 已卸载BSIPA - 卸载所有Mod? - 你确定要移除所有Mod? - 这将无法撤销。 - 已卸载所有Mod - 当前主题已被删除,恢复为默认... - 找不到主题文件夹!请尝试导出模板 - 找不到游戏存档路径!请尝试启动游戏后重试。 - - - 正在加载Mod... - - - 无效 - 检测到无效的安装 - 您的游戏安装已损坏或无效 - 如果您的游戏是盗版的,或者您使用盗版游戏替换了正版,则可能会发生这种情况。 - - 如果您的游戏是盗版的, - 请在 - - 这里 - - 购买游戏。 - - - 如果您的游戏 - 不是盗版,请 - - 清洁安装 - 。 - - - 如果这些方法没有帮助,请在BSMG的 - - Discord - #pc-help频道寻求帮助。 - - 如果您曾经使用盗版,但之后购买了正版游戏 - 选择路径 - 更改为正版游戏之后,您需要重新启动Mod Assistant - - - 无法获取详情。 - 无法下载歌曲。 - 无法下载歌曲。 - 可能您的互联网连接或BeatSaver存在问题。 - 下载歌曲压缩包失败。 - 找不到Beat Saber安装路径。 - 已添加:{0} - 添加失败。 - {0} OneClick™ 一键添加处理程序已注册! - {0} OneClick™ 一键添加处理程序已移除! - 正在下载:{0} - 超过重试次数,跳过:{0} - 下载超时,重试:{0} - 下载失败:{0} - 执行完毕 - - - 找不到主题,恢复为默认主题... - 主题设置为{0}. - {0}不存在 - 模板主题"{0}"保存到主题文件夹。 - 模板主题已存在! - 无法加载主题的.xaml文件 {0}: {1} - - - 无法检查更新。 - 无法下载更新。 - - - Mod Assistant - 检测不到您的Beat Saber安装路径,请手动选择。 - Mod Assistant需要以管理员身份运行此任务,请重试。 - 选择您的Beat Saber安装路径 - 无法打开路径:{0} - diff --git a/ModAssistant/OneClickStatus.xaml b/ModAssistant/OneClickStatus.xaml deleted file mode 100644 index 088322d..0000000 --- a/ModAssistant/OneClickStatus.xaml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ModAssistant/OneClickStatus.xaml.cs b/ModAssistant/OneClickStatus.xaml.cs deleted file mode 100644 index a562a0a..0000000 --- a/ModAssistant/OneClickStatus.xaml.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Data; - -namespace ModAssistant -{ - /// - /// Interaction logic for OneClickStatus.xaml - /// - public partial class OneClickStatus : Window - { - public static OneClickStatus Instance; - - public string HistoryText - { - get - { - return HistoryTextBlock.Text; - } - set - { - Dispatcher.Invoke(new Action(() => { Instance.HistoryTextBlock.Text = value; })); - } - } - public string MainText - { - get - { - return MainTextBlock.Text; - } - set - { - Dispatcher.Invoke(new Action(() => - { - Instance.MainTextBlock.Text = value; - Instance.HistoryTextBlock.Text = string.IsNullOrEmpty(MainText) ? $"{value}" : $"{value}\n{HistoryText}"; - })); - } - } - - public OneClickStatus() - { - InitializeComponent(); - Instance = App.OCIWindow != "No" ? this : null; - } - - public void StopRotation() - { - Ring.Style = null; - } - } - - [ValueConversion(typeof(double), typeof(double))] - public class DivideDoubleByTwoConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - if (targetType != typeof(double)) - { - throw new InvalidOperationException("The target must be a double"); - } - double d = (double)value; - return ((double)d) / 2; - } - - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - throw new NotSupportedException(); - } - } -} diff --git a/ModAssistant/Pages/Mods.xaml.cs b/ModAssistant/Pages/Mods.xaml.cs deleted file mode 100644 index 827dc3c..0000000 --- a/ModAssistant/Pages/Mods.xaml.cs +++ /dev/null @@ -1,799 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Forms; -using System.Windows.Media.Animation; -using System.Windows.Navigation; -using ModAssistant.Libs; -using static ModAssistant.Http; -using TextBox = System.Windows.Controls.TextBox; - -namespace ModAssistant.Pages -{ - /// - /// Interaction logic for Mods.xaml - /// - public sealed partial class Mods : Page - { - public static Mods Instance = new Mods(); - - public List DefaultMods = new List() { "SongCore", "ScoreSaber", "BeatSaverDownloader", "BeatSaverVoting", "PlaylistCore", "Survey" }; - public Mod[] ModsList; - public Mod[] AllModsList; - public static List InstalledMods = new List(); - public List CategoryNames = new List(); - public CollectionView view; - public bool PendingChanges; - - private readonly SemaphoreSlim _modsLoadSem = new SemaphoreSlim(1, 1); - - public List ModList { get; set; } - - public Mods() - { - InitializeComponent(); - } - - private void RefreshModsList() - { - if (view != null) - { - view.Refresh(); - } - } - - public void RefreshColumns() - { - if (MainWindow.Instance.Main.Content != Instance) return; - double viewWidth = ModsListView.ActualWidth; - double totalSize = 0; - GridViewColumn description = null; - - if (ModsListView.View is GridView grid) - { - foreach (var column in grid.Columns) - { - if (column.Header?.ToString() == FindResource("Mods:Header:Description").ToString()) - { - description = column; - } - else - { - totalSize += column.ActualWidth; - } - if (double.IsNaN(column.Width)) - { - column.Width = column.ActualWidth; - column.Width = double.NaN; - } - } - double descriptionNewWidth = viewWidth - totalSize - 35; - description.Width = descriptionNewWidth > 200 ? descriptionNewWidth : 200; - } - } - - public async Task LoadMods() - { - var versionLoadSuccess = await MainWindow.Instance.VersionLoadStatus.Task; - if (versionLoadSuccess == false) return; - - await _modsLoadSem.WaitAsync(); - - try - { - MainWindow.Instance.InstallButton.IsEnabled = false; - MainWindow.Instance.GameVersionsBox.IsEnabled = false; - MainWindow.Instance.InfoButton.IsEnabled = false; - - if (ModsList != null) - { - Array.Clear(ModsList, 0, ModsList.Length); - } - - if (AllModsList != null) - { - Array.Clear(AllModsList, 0, AllModsList.Length); - } - - InstalledMods = new List(); - CategoryNames = new List(); - ModList = new List(); - - ModsListView.Visibility = Visibility.Hidden; - - if (App.CheckInstalledMods) - { - MainWindow.Instance.MainText = $"{FindResource("Mods:CheckingInstalledMods")}..."; - await Task.Run(async () => await CheckInstalledMods()); - InstalledColumn.Width = double.NaN; - UninstallColumn.Width = 70; - DescriptionColumn.Width = 750; - } - else - { - InstalledColumn.Width = 0; - UninstallColumn.Width = 0; - DescriptionColumn.Width = 800; - } - - MainWindow.Instance.MainText = $"{FindResource("Mods:LoadingMods")}..."; - await Task.Run(async () => await PopulateModsList()); - - ModsListView.ItemsSource = ModList; - - view = (CollectionView)CollectionViewSource.GetDefaultView(ModsListView.ItemsSource); - PropertyGroupDescription groupDescription = new PropertyGroupDescription("Category"); - view.GroupDescriptions.Add(groupDescription); - - this.DataContext = this; - - RefreshModsList(); - ModsListView.Visibility = ModList.Count == 0 ? Visibility.Hidden : Visibility.Visible; - NoModsGrid.Visibility = ModList.Count == 0 ? Visibility.Visible : Visibility.Hidden; - - MainWindow.Instance.MainText = $"{FindResource("Mods:FinishedLoadingMods")}."; - MainWindow.Instance.InstallButton.IsEnabled = ModList.Count != 0; - MainWindow.Instance.GameVersionsBox.IsEnabled = true; - } - finally - { - _modsLoadSem.Release(); - } - } - - public async Task CheckInstalledMods() - { - await GetAllMods(); - - GetBSIPAVersion(); - CheckInstallDir("IPA/Pending/Plugins"); - CheckInstallDir("IPA/Pending/Libs"); - CheckInstallDir("Plugins"); - CheckInstallDir("Libs"); - } - - public async Task GetAllMods() - { - var resp = await HttpClient.GetAsync(Utils.Constants.BeatModsAPIUrl + "mod"); - var body = await resp.Content.ReadAsStringAsync(); - AllModsList = JsonSerializer.Deserialize(body); - } - - private void CheckInstallDir(string directory) - { - if (!Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, directory))) - { - return; - } - - foreach (string file in Directory.GetFileSystemEntries(Path.Combine(App.BeatSaberInstallDirectory, directory))) - { - if (File.Exists(file) && Path.GetExtension(file) == ".dll" || Path.GetExtension(file) == ".manifest") - { - Mod mod = GetModFromHash(Utils.CalculateMD5(file)); - if (mod != null) - { - AddDetectedMod(mod); - } - } - } - } - - public void GetBSIPAVersion() - { - string InjectorPath = Path.Combine(App.BeatSaberInstallDirectory, "Beat Saber_Data", "Managed", "IPA.Injector.dll"); - if (!File.Exists(InjectorPath)) return; - - string InjectorHash = Utils.CalculateMD5(InjectorPath); - foreach (Mod mod in AllModsList) - { - if (mod.name.ToLower() == "bsipa") - { - foreach (Mod.DownloadLink download in mod.downloads) - { - foreach (Mod.FileHashes fileHash in download.hashMd5) - { - if (fileHash.hash == InjectorHash) - { - AddDetectedMod(mod); - } - } - } - } - } - } - - private void AddDetectedMod(Mod mod) - { - if (!InstalledMods.Contains(mod)) - { - InstalledMods.Add(mod); - if (App.SelectInstalledMods && !DefaultMods.Contains(mod.name)) - { - DefaultMods.Add(mod.name); - } - } - } - - private Mod GetModFromHash(string hash) - { - foreach (Mod mod in AllModsList) - { - if (mod.name.ToLower() != "bsipa" && mod.status != "declined") - { - foreach (Mod.DownloadLink download in mod.downloads) - { - foreach (Mod.FileHashes fileHash in download.hashMd5) - { - if (fileHash.hash == hash) - return mod; - } - } - } - } - - return null; - } - - public async Task PopulateModsList() - { - try - { - var resp = await HttpClient.GetAsync(Utils.Constants.BeatModsAPIUrl + Utils.Constants.BeatModsModsOptions + "&gameVersion=" + MainWindow.GameVersion); - var body = await resp.Content.ReadAsStringAsync(); - ModsList = JsonSerializer.Deserialize(body); - } - catch (Exception e) - { - System.Windows.MessageBox.Show($"{FindResource("Mods:LoadFailed")}.\n\n" + e); - return; - } - - 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("\r\n", " ").Replace("\n", " "), - ModInfo = mod, - Category = mod.category - }; - - foreach (Promotion promo in Promotions.ActivePromotions) - { - if (mod.name == promo.ModName) - { - ListItem.PromotionText = promo.Text; - ListItem.PromotionLink = promo.Link; - } - } - - foreach (Mod installedMod in InstalledMods) - { - if (mod.name == installedMod.name) - { - ListItem.InstalledModInfo = installedMod; - 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) - { - // Ignore mods that are newer than installed version - if (mod.ListItem.GetVersionComparison > 0) continue; - - // Ignore mods that are on current version if we aren't reinstalling mods - if (mod.ListItem.GetVersionComparison == 0 && !App.ReinstallInstalledMods) continue; - - if (mod.name.ToLower() == "bsipa") - { - MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstallingMod"), mod.name)}..."; - await Task.Run(async () => await InstallMod(mod, installDirectory)); - MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstalledMod"), mod.name)}."; - if (!File.Exists(Path.Combine(installDirectory, "winhttp.dll"))) - { - await Task.Run(() => - Process.Start(new ProcessStartInfo - { - FileName = Path.Combine(installDirectory, "IPA.exe"), - WorkingDirectory = installDirectory, - Arguments = "-n" - }).WaitForExit() - ); - } - - Options.Instance.YeetBSIPA.IsEnabled = true; - } - else if (mod.ListItem.IsSelected) - { - MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstallingMod"), mod.name)}..."; - await Task.Run(async () => await InstallMod(mod, Path.Combine(installDirectory, @"IPA\Pending"))); - MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:InstalledMod"), mod.name)}."; - } - } - - MainWindow.Instance.MainText = $"{FindResource("Mods:FinishedInstallingMods")}."; - MainWindow.Instance.InstallButton.IsEnabled = true; - RefreshModsList(); - } - - private async Task 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(string.Format((string)FindResource("Mods:ModDownloadLinkMissing"), mod.name)); - return; - } - - using (Stream stream = await DownloadMod(Utils.Constants.BeatModsURL + downloadLink)) - using (ZipArchive archive = new ZipArchive(stream)) - { - foreach (ZipArchiveEntry file in archive.Entries) - { - string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName)); - if (!Directory.Exists(fileDirectory)) - { - Directory.CreateDirectory(fileDirectory); - } - - if (!string.IsNullOrEmpty(file.Name)) - { - await ExtractFile(file, Path.Combine(directory, file.FullName), 3.0, mod.name, 10); - } - } - } - - if (App.CheckInstalledMods) - { - mod.ListItem.IsInstalled = true; - mod.ListItem.InstalledVersion = mod.version; - mod.ListItem.InstalledModInfo = mod; - } - } - - private async Task ExtractFile(ZipArchiveEntry file, string path, double seconds, string name, int maxTries, int tryNumber = 0) - { - if (tryNumber < maxTries) - { - try - { - file.ExtractToFile(path, true); - } - catch - { - MainWindow.Instance.MainText = $"{string.Format((string)FindResource("Mods:FailedExtract"), name, seconds, tryNumber + 1, maxTries)}"; - await Task.Delay((int)(seconds * 1000)); - await ExtractFile(file, path, seconds, name, maxTries, tryNumber + 1); - } - } - else - { - System.Windows.MessageBox.Show($"{string.Format((string)FindResource("Mods:FailedExtractMaxReached"), name, maxTries)}.", "Failed to install " + name); - } - } - - private async Task DownloadMod(string link) - { - var resp = await HttpClient.GetAsync(link); - return await resp.Content.ReadAsStreamAsync(); - } - - 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 Mod InstalledModInfo { get; set; } - public bool IsInstalled { get; set; } - private SemVersion _installedVersion { get; set; } - public string InstalledVersion - { - get - { - if (!IsInstalled || _installedVersion == null) return "-"; - return _installedVersion.ToString(); - } - set - { - if (SemVersion.TryParse(value, out SemVersion tempInstalledVersion)) - { - _installedVersion = tempInstalledVersion; - } - else - { - _installedVersion = null; - } - } - } - - public string GetVersionColor - { - get - { - if (!IsInstalled) return "Black"; - return _installedVersion >= ModVersion ? "Green" : "Red"; - } - } - - public string GetVersionDecoration - { - get - { - if (!IsInstalled) return "None"; - return _installedVersion >= ModVersion ? "None" : "Strikethrough"; - } - } - - public int GetVersionComparison - { - get - { - if (!IsInstalled || _installedVersion < ModVersion) return -1; - if (_installedVersion > ModVersion) return 1; - return 0; - } - } - - public bool CanDelete - { - get - { - return (!ModInfo.required && IsInstalled); - } - } - - public string CanSeeDelete - { - get - { - if (!ModInfo.required && IsInstalled) - return "Visible"; - else - return "Hidden"; - } - } - - public string PromotionText { get; set; } - public string PromotionLink { get; set; } - public string PromotionMargin - { - get - { - if (string.IsNullOrEmpty(PromotionText)) return "0"; - return "0,0,5,0"; - } - } - } - - private void ModsListView_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if ((Mods.ModListItem)Instance.ModsListView.SelectedItem == null) - { - MainWindow.Instance.InfoButton.IsEnabled = false; - } - else - { - MainWindow.Instance.InfoButton.IsEnabled = true; - } - } - - public void UninstallBSIPA(Mod.DownloadLink links) - { - Process.Start(new ProcessStartInfo - { - FileName = Path.Combine(App.BeatSaberInstallDirectory, "IPA.exe"), - WorkingDirectory = App.BeatSaberInstallDirectory, - Arguments = "--revert -n" - }).WaitForExit(); - - foreach (Mod.FileHashes files in links.hashMd5) - { - string file = files.file.Replace("IPA/", "").Replace("Data", "Beat Saber_Data"); - if (File.Exists(Path.Combine(App.BeatSaberInstallDirectory, file))) - File.Delete(Path.Combine(App.BeatSaberInstallDirectory, file)); - } - Options.Instance.YeetBSIPA.IsEnabled = false; - } - - private void Uninstall_Click(object sender, RoutedEventArgs e) - { - Mod mod = ((sender as System.Windows.Controls.Button).Tag as Mod); - - string title = string.Format((string)FindResource("Mods:UninstallBox:Title"), mod.name); - string body1 = string.Format((string)FindResource("Mods:UninstallBox:Body1"), mod.name); - string body2 = string.Format((string)FindResource("Mods:UninstallBox:Body2"), mod.name); - var result = System.Windows.Forms.MessageBox.Show($"{body1}\n{body2}", title, MessageBoxButtons.YesNo); - - if (result == DialogResult.Yes) - { - UninstallModFromList(mod); - } - } - - private void UninstallModFromList(Mod mod) - { - UninstallMod(mod.ListItem.InstalledModInfo); - mod.ListItem.IsInstalled = false; - mod.ListItem.InstalledVersion = null; - if (App.SelectInstalledMods) - { - mod.ListItem.IsSelected = false; - UnresolveDependencies(mod); - App.SavedMods.Remove(mod.name); - Properties.Settings.Default.SavedMods = string.Join(",", App.SavedMods.ToArray()); - Properties.Settings.Default.Save(); - RefreshModsList(); - } - view.Refresh(); - } - - public void UninstallMod(Mod mod) - { - 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; - } - } - if (mod.name.ToLower() == "bsipa") - { - var hasIPAExe = File.Exists(Path.Combine(App.BeatSaberInstallDirectory, "IPA.exe")); - var hasIPADir = Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "IPA")); - - if (hasIPADir && hasIPAExe) - { - UninstallBSIPA(links); - } - else - { - var title = (string)FindResource("Mods:UninstallBSIPANotFound:Title"); - var body = (string)FindResource("Mods:UninstallBSIPANotFound:Body"); - - System.Windows.Forms.MessageBox.Show(body, title, MessageBoxButtons.OK, MessageBoxIcon.Warning); - } - } - foreach (Mod.FileHashes files in links.hashMd5) - { - if (File.Exists(Path.Combine(App.BeatSaberInstallDirectory, files.file))) - File.Delete(Path.Combine(App.BeatSaberInstallDirectory, files.file)); - if (File.Exists(Path.Combine(App.BeatSaberInstallDirectory, "IPA", "Pending", files.file))) - File.Delete(Path.Combine(App.BeatSaberInstallDirectory, "IPA", "Pending", files.file)); - } - } - - private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)); - e.Handled = true; - } - - private void Page_Loaded(object sender, RoutedEventArgs e) - { - RefreshColumns(); - } - - private void CopyText(object sender, System.Windows.Input.MouseButtonEventArgs e) - { - if (!(sender is TextBlock textBlock)) return; - var text = textBlock.Text; - - // Ensure there's text to be copied - if (string.IsNullOrWhiteSpace(text)) return; - - Utils.SetClipboard(text); - } - - private void SearchButton_Click(object sender, RoutedEventArgs e) - { - if (SearchBar.Height == 0) - { - SearchBar.Focus(); - Animate(SearchBar, 0, 16, new TimeSpan(0, 0, 0, 0, 300)); - Animate(SearchText, 0, 16, new TimeSpan(0, 0, 0, 0, 300)); - ModsListView.Items.Filter = new Predicate(SearchFilter); - } - else - { - Animate(SearchBar, 16, 0, new TimeSpan(0, 0, 0, 0, 300)); - Animate(SearchText, 16, 0, new TimeSpan(0, 0, 0, 0, 300)); - ModsListView.Items.Filter = null; - } - } - - private void SearchBar_TextChanged(object sender, TextChangedEventArgs e) - { - ModsListView.Items.Filter = new Predicate(SearchFilter); - if (SearchBar.Text.Length > 0) - { - SearchText.Text = null; - } - else - { - SearchText.Text = (string)FindResource("Mods:SearchLabel"); - } - } - - private bool SearchFilter(object mod) - { - ModListItem item = mod as ModListItem; - if (item.ModName.ToLower().Contains(SearchBar.Text.ToLower())) return true; - if (item.ModDescription.ToLower().Contains(SearchBar.Text.ToLower())) return true; - if (item.ModName.ToLower().Replace(" ", string.Empty).Contains(SearchBar.Text.ToLower().Replace(" ", string.Empty))) return true; - if (item.ModDescription.ToLower().Replace(" ", string.Empty).Contains(SearchBar.Text.ToLower().Replace(" ", string.Empty))) return true; - return false; - } - - private void Animate(TextBlock target, double oldHeight, double newHeight, TimeSpan duration) - { - target.Height = oldHeight; - DoubleAnimation animation = new DoubleAnimation(newHeight, duration); - target.BeginAnimation(HeightProperty, animation); - } - - private void Animate(TextBox target, double oldHeight, double newHeight, TimeSpan duration) - { - target.Height = oldHeight; - DoubleAnimation animation = new DoubleAnimation(newHeight, duration); - target.BeginAnimation(HeightProperty, animation); - } - } -} diff --git a/ModAssistant/Pages/Options.xaml.cs b/ModAssistant/Pages/Options.xaml.cs deleted file mode 100644 index dfe605c..0000000 --- a/ModAssistant/Pages/Options.xaml.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using Path = System.IO.Path; - -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 bool SelectInstalledMods { get; set; } - public bool ReinstallInstalledMods { get; set; } - public bool ModelSaberProtocolHandlerEnabled { get; set; } - public bool BeatSaverProtocolHandlerEnabled { get; set; } - public bool PlaylistsProtocolHandlerEnabled { get; set; } - public bool CloseWindowOnFinish { get; set; } - public string LogURL { get; private set; } - public string OCIWindow { get; set; } - - public Options() - { - InitializeComponent(); - - OCIWindow = App.OCIWindow; - if (!string.IsNullOrEmpty(OCIWindow)) - { - UpdateOCIWindow(OCIWindow); - } - if (!CheckInstalledMods) - { - SelectInstalled.IsEnabled = false; - ReinstallInstalled.IsEnabled = false; - } - - UpdateHandlerStatus(); - this.DataContext = this; - } - - public void UpdateHandlerStatus() - { - ModelSaberProtocolHandlerEnabled = OneClickInstaller.IsRegistered("modelsaber"); - BeatSaverProtocolHandlerEnabled = OneClickInstaller.IsRegistered("beatsaver"); - PlaylistsProtocolHandlerEnabled = OneClickInstaller.IsRegistered("bsplaylist"); - } - - private void SelectDirButton_Click(object sender, RoutedEventArgs e) - { - Utils.GetManualDir(); - DirectoryTextBlock.Text = InstallDirectory; - GameTypeTextBlock.Text = InstallType; - } - - private void OpenDirButton_Click(object sender, RoutedEventArgs e) - { - Utils.OpenFolder(InstallDirectory); - } - - 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(); - SelectInstalled.IsEnabled = true; - ReinstallInstalled.IsEnabled = true; - - if (MainWindow.ModsOpened) - { - Mods.Instance.PendingChanges = true; - } - } - - private void CheckInstalled_Unchecked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.CheckInstalled = false; - App.CheckInstalledMods = false; - CheckInstalledMods = false; - Properties.Settings.Default.Save(); - SelectInstalled.IsEnabled = false; - ReinstallInstalled.IsEnabled = false; - - if (MainWindow.ModsOpened) - { - Mods.Instance.PendingChanges = true; - } - } - - private void CloseWindowOnFinish_Checked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.CloseWindowOnFinish = true; - App.CloseWindowOnFinish = true; - CloseWindowOnFinish = true; - Properties.Settings.Default.Save(); - } - - private void CloseWindowOnFinish_Unchecked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.CloseWindowOnFinish = false; - App.CloseWindowOnFinish = false; - CloseWindowOnFinish = false; - Properties.Settings.Default.Save(); - } - - public void ModelSaberProtocolHandler_Checked(object sender, RoutedEventArgs e) - { - OneClickInstaller.Register("modelsaber", Description: "URL:ModelSaber OneClick Install"); - } - - public void ModelSaberProtocolHandler_Unchecked(object sender, RoutedEventArgs e) - { - OneClickInstaller.Unregister("modelsaber"); - } - - public void BeatSaverProtocolHandler_Checked(object sender, RoutedEventArgs e) - { - OneClickInstaller.Register("beatsaver", Description: "URL:BeatSaver OneClick Install"); - } - - public void BeatSaverProtocolHandler_Unchecked(object sender, RoutedEventArgs e) - { - OneClickInstaller.Unregister("beatsaver"); - } - public void PlaylistsProtocolHandler_Checked(object sender, RoutedEventArgs e) - { - OneClickInstaller.Register("bsplaylist", Description: "URL:BeatSaver Playlist OneClick Install"); - } - - public void PlaylistsProtocolHandler_Unchecked(object sender, RoutedEventArgs e) - { - OneClickInstaller.Unregister("bsplaylist"); - } - - private void SelectInstalled_Checked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.SelectInstalled = true; - App.SelectInstalledMods = true; - SelectInstalledMods = true; - Properties.Settings.Default.Save(); - } - - private void SelectInstalled_Unchecked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.SelectInstalled = false; - App.SelectInstalledMods = false; - SelectInstalledMods = false; - Properties.Settings.Default.Save(); - } - - private void ReinstallInstalled_Checked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.ReinstallInstalled = true; - App.ReinstallInstalledMods = true; - ReinstallInstalledMods = true; - Properties.Settings.Default.Save(); - } - - private void ReinstallInstalled_Unchecked(object sender, RoutedEventArgs e) - { - Properties.Settings.Default.ReinstallInstalled = false; - App.ReinstallInstalledMods = false; - ReinstallInstalledMods = false; - Properties.Settings.Default.Save(); - } - - private async void OpenLogsDirButton_Click(object sender, RoutedEventArgs e) - { - try - { - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:UploadingLog")}..."; - await Task.Run(async () => await UploadLog()); - - Process.Start(LogURL); - Utils.SetClipboard(LogURL); - MainWindow.Instance.MainText = (string)Application.Current.FindResource("Options:LogUrlCopied"); - } - catch (Exception exception) - { - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:LogUploadFailed")}."; - - string title = (string)Application.Current.FindResource("Options:LogUploadFailed:Title"); - string body = (string)Application.Current.FindResource("Options:LogUploadFailed:Body"); - MessageBox.Show($"{body}\n ================= \n" + exception, title); - Utils.OpenFolder(Path.Combine(InstallDirectory, "Logs")); - } - } - - private async Task UploadLog() - { - const string DateFormat = "yyyy-mm-dd HH:mm:ss"; - DateTime now = DateTime.Now; - string logPath = Path.GetDirectoryName(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath); - string Log = Path.Combine(logPath, "log.log"); - string GameLog = File.ReadAllText(Path.Combine(InstallDirectory, "Logs", "_latest.log")); - string Separator = File.Exists(Log) ? $"\n\n=============================================\n============= Mod Assistant Log =============\n=============================================\n\n" : string.Empty; - string ModAssistantLog = File.Exists(Log) ? File.ReadAllText(Log) : string.Empty; - - var nvc = new List>() - { - new KeyValuePair("title", $"_latest.log ({now.ToString(DateFormat)})"), - new KeyValuePair("expireUnit", "hour"), - new KeyValuePair("expireLength", "5"), - new KeyValuePair("code", $"{GameLog}{Separator}{ModAssistantLog}"), - }; - - string[] items = new string[nvc.Count]; - - for (int i = 0; i < nvc.Count; i++) - { - KeyValuePair item = nvc[i]; - items[i] = WebUtility.UrlEncode(item.Key) + "=" + WebUtility.UrlEncode(item.Value); - } - - StringContent content = new StringContent(string.Join("&", items), null, "application/x-www-form-urlencoded"); - HttpResponseMessage resp = await Http.HttpClient.PostAsync(Utils.Constants.TeknikAPIUrl + "Paste", content); - string body = await resp.Content.ReadAsStringAsync(); - - Utils.TeknikPasteResponse TeknikResponse = Http.JsonSerializer.Deserialize(body); - LogURL = TeknikResponse.result.url; - } - - private void OpenAppDataButton_Click(object sender, RoutedEventArgs e) - { - string location = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - "AppData", "LocalLow", "Hyperbolic Magnetism"); - if (Directory.Exists(location)) - { - Utils.OpenFolder(location); - } - else - { - MessageBox.Show((string)Application.Current.FindResource("Options:AppDataNotFound")); - } - } - - private async void YeetBSIPAButton_Click(object sender, RoutedEventArgs e) - { - if (Mods.Instance.AllModsList == null) - { - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:GettingModList")}..."; - await Task.Run(async () => await Mods.Instance.GetAllMods()); - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:FindingBSIPAVersion")}..."; - await Task.Run(() => Mods.Instance.GetBSIPAVersion()); - } - foreach (Mod mod in Mods.InstalledMods) - { - if (mod.name.ToLower() == "bsipa") - { - Mods.Instance.UninstallMod(mod); - break; - } - } - - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:BSIPAUninstalled")}..."; - } - private async void YeetModsButton_Click(object sender, RoutedEventArgs e) - { - string title = (string)Application.Current.FindResource("Options:YeetModsBox:Title"); - string line1 = (string)Application.Current.FindResource("Options:YeetModsBox:RemoveAllMods"); - string line2 = (string)Application.Current.FindResource("Options:YeetModsBox:CannotBeUndone"); - - var resp = System.Windows.Forms.MessageBox.Show($"{line1}\n{line2}", title, System.Windows.Forms.MessageBoxButtons.YesNo); - if (resp == System.Windows.Forms.DialogResult.Yes) - { - - if (Mods.Instance.AllModsList == null) - { - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:GettingModList")}..."; - await Task.Run(async () => await Mods.Instance.CheckInstalledMods()); - } - foreach (Mod mod in Mods.InstalledMods) - { - Mods.Instance.UninstallMod(mod); - } - if (Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "Plugins"))) - Directory.Delete(Path.Combine(App.BeatSaberInstallDirectory, "Plugins"), true); - if (Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "Libs"))) - Directory.Delete(Path.Combine(App.BeatSaberInstallDirectory, "Libs"), true); - if (Directory.Exists(Path.Combine(App.BeatSaberInstallDirectory, "IPA"))) - Directory.Delete(Path.Combine(App.BeatSaberInstallDirectory, "IPA"), true); - - MainWindow.Instance.MainText = $"{Application.Current.FindResource("Options:AllModsUninstalled")}..."; - } - } - - private void ApplicationThemeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if ((sender as ComboBox).SelectedItem == null) - { - Themes.ApplyWindowsTheme(); - MainWindow.Instance.MainText = (string)Application.Current.FindResource("Options:CurrentThemeRemoved"); - } - else - { - Themes.ApplyTheme((sender as ComboBox).SelectedItem.ToString()); - } - } - - public void LanguageSelectComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if ((sender as ComboBox).SelectedItem == null) - { - // Apply default language - Console.WriteLine("Applying default language"); - Languages.LoadLanguage("en"); - } - else - { - // Get the matching language from the LoadedLanguages array, then try and use it - var languageName = (sender as ComboBox).SelectedItem.ToString(); - var selectedLanguage = Languages.LoadedLanguages.Find(language => language.NativeName.CompareTo(languageName) == 0); - if (Languages.LoadLanguage(selectedLanguage.Name)) - { - Properties.Settings.Default.LanguageCode = selectedLanguage.Name; - Properties.Settings.Default.Save(); - if (Languages.FirstRun) - { - Languages.FirstRun = false; - } - else - { - Process.Start(Utils.ExePath, App.Arguments); - Application.Current.Dispatcher.Invoke(() => { Application.Current.Shutdown(); }); - } - } - } - } - - private void ApplicationThemeExportTemplate_Click(object sender, RoutedEventArgs e) - { - Themes.WriteThemeToDisk("Ugly Kulu-Ya-Ku"); - Themes.LoadThemes(); - } - - private void ApplicationThemeOpenThemesFolder_Click(object sender, RoutedEventArgs e) - { - if (Directory.Exists(Themes.ThemeDirectory)) - { - Utils.OpenFolder(Themes.ThemeDirectory); - } - else - { - MessageBox.Show((string)Application.Current.FindResource("Options:ThemeFolderNotFound")); - } - } - - private void InstallPlaylistButton_Click(object sender, RoutedEventArgs e) - { - string playlistFile = Utils.GetManualFile(); - if (File.Exists(playlistFile)) - { - Task.Run(() => { API.Playlists.DownloadFrom(playlistFile).Wait(); }); - } - } - - private void ShowOCIWindowComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - ComboBox comboBox = sender as ComboBox; - if (comboBox.SelectedItem != null) - { - ComboBoxItem comboBoxItem = (ComboBoxItem)comboBox.SelectedItem; - UpdateOCIWindow(comboBoxItem.Tag.ToString()); - } - } - - public void UpdateOCIWindow(string state) - { - ComboBox comboBox = ShowOCIWindowComboBox; - if (comboBox != null) - { - if (state == "Yes") comboBox.SelectedIndex = 0; - else if (state == "Close") comboBox.SelectedIndex = 1; - else if (state == "No") comboBox.SelectedIndex = 2; - else return; - } - if (!string.IsNullOrEmpty(state)) - { - OCIWindow = App.OCIWindow = Properties.Settings.Default.OCIWindow = state; - Properties.Settings.Default.Save(); - } - } - } -} diff --git a/ModAssistant/Resources/icon.ico b/ModAssistant/Resources/icon.ico deleted file mode 100644 index 1ceb54cebdd2e598d3364e560fc0646239e49e5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124093 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 diff --git a/ModAssistant/Themes/BSMG.xaml b/ModAssistant/Themes/BSMG.xaml deleted file mode 100644 index 1794b4a..0000000 --- a/ModAssistant/Themes/BSMG.xaml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - #DCDDDE - #36393F - #2F3136 - #202225 - #202225 - #7289DA - #44347C - #DCDDDE - #40444B - #32353B - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - UniformToFill - - Top - - diff --git a/ModAssistant/Themes/BSMG/Sidebar.png b/ModAssistant/Themes/BSMG/Sidebar.png deleted file mode 100644 index ffece861fea7d95400ea3c3589bcfd8402330798..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6156 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 or Update button. Likewise, click the Uninstall button to remove any mods. -3. Mods are installed to `IPA/Pending` until the game is run. Boot the game to complete mod installation. +Download the newest build from the release section and run it. This application auto-updates when launched, there is no need to download a new release each time. +Then, simply check off the mods that you wish to install or update and click the Install or Update button. Likewise, click the Uninstall button to remove any mods. ## Themes -
- Light -
-

-

-

-

-
-
- -
- Dark -
-

-

-

-

-
-
- -
- BSMG -
-

-

-

-

-
-
- -
- Light Pink -
-

-

-

-

-
-
- -
- Your own! -
-

-

-

-

-
-
- -### Custom Themes -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. - -### Built In -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. - -If you have a particularly popular theme, you can submit a [Pull Request](https://github.com/Assistant/ModAssistant/pulls) to add your theme as a built-in theme. - -### Packaged `.mat` Files -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. -To create one follow the instructions on `Folders` themes, and zip the files up into a zip archive, and rename it to `.mat`. - -### Loose Folder Themes -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: - -* `Theme.xaml` - * This file determines the colors and styling of the theme. - * The filename isn't important, but the `.xaml` file extension is. - * 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. - -* `Waifu.png` - * This will be loaded as the background image. - * It will be centered, and you can pick how to stretch it in the associated `.xaml` file. - * The filename isn't important, but the `.png` file extension is. -* `Waifu.side.png` - * 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. - * The filename isn't important, but the `.side.png` file extension is. -* `Video.{mp4, webm, mkv, avi, m2ts}` - * This will be loaded as a background video, with sound. - * The filename isn't important, but the file extension must be supported (`.mp4`, `.webm`, `.mkv`, `.avi`, `.m2ts`) - * Whether the file works or not will depend on what codecs the file has, and whether those are available on your machine. - -### Overriding Themes -You can mix and match parts from different themes by giving them the same name. -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. - -Examples: -* Adding `/Themes/Dark.mat` which includes `.png` and `.xaml` files will override both those aspects of the `Dark` theme. -* 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. - +VRChat Melon Assistant should support themes for Mod Assistant. Check [its README](https://github.com/Assistant/ModAssistant#themes) for more info on theming. +However, this is not a supported feature. If something doesn't work, don't complain to either Assistant or knah. ## Common Issues **I hit install but I don't see anything in game!** - Double check that you followed the [Usage](#usage) instructions correctly. - Make sure you're looking in the right place. Sometimes mod menus move as modding libraries/practices change. + Double check that you followed the [Usage](#usage) instructions correctly. + Make sure you're looking in the right place. Sometimes mod menus move as modding libraries/practices change. + Additionally, make sure the proper VRChat installation directory is selected in option tab. **I don't see a certain mod in the mods list!** - 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). + VRChat Melon Assistant uses mods from VRChat Modding Group and shows whatever is available for download. It's recommended to avoid non-VRCMG mods due to rampant spread of malware disguised as mods. **I hit install but now my game won't launch, I can't click any buttons, I only see a black screen, etc** - 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 out things out. + Please visit the [VRChat Modding Group](https://discord.gg/rCqKSvR) `#help-and-support` channel. Check the pinned messages or ask for help and see if you can work out things out. ## Credits -semver by Max Hauser +Lemon icon from Twitter Emoji +https://github.com/twitter/twemoji + +Original Mod Assistant is made by Assistant and used under the terms of MIT License. +https://github.com/Assistant/ModAssistant + +semver by Max Hauser https://github.com/maxhauser/semver diff --git a/ModAssistant.sln b/VRCMelonAssistant.sln similarity index 66% rename from ModAssistant.sln rename to VRCMelonAssistant.sln index 5643d23..24642de 100644 --- a/ModAssistant.sln +++ b/VRCMelonAssistant.sln @@ -3,7 +3,14 @@ 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}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCMelonAssistant", "VRCMelonAssistant\VRCMelonAssistant.csproj", "{6A224B82-40DA-40B3-94DC-EFBEC2BDDA39}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8DBEE4E3-1033-433D-B1F3-F7D789808141}" +ProjectSection(SolutionItems) = preProject + README.md = README.md + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml + .github\FUNDING.yml = .github\FUNDING.yml +EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/VRCMelonAssistant/App.config b/VRCMelonAssistant/App.config new file mode 100644 index 0000000..01bb179 --- /dev/null +++ b/VRCMelonAssistant/App.config @@ -0,0 +1,39 @@ + + + + +
+ + + + + + + + + + + + + + + False + + + True + + + + + + + + + False + + + + + + + \ No newline at end of file diff --git a/ModAssistant/App.xaml b/VRCMelonAssistant/App.xaml similarity index 95% rename from ModAssistant/App.xaml rename to VRCMelonAssistant/App.xaml index 2b4ab8a..e642160 100644 --- a/ModAssistant/App.xaml +++ b/VRCMelonAssistant/App.xaml @@ -1,5 +1,5 @@ + + diff --git a/ModAssistant/App.xaml.cs b/VRCMelonAssistant/App.xaml.cs similarity index 70% rename from ModAssistant/App.xaml.cs rename to VRCMelonAssistant/App.xaml.cs index ac397d0..928aeca 100644 --- a/ModAssistant/App.xaml.cs +++ b/VRCMelonAssistant/App.xaml.cs @@ -6,60 +6,50 @@ using System.Reflection; using System.Threading.Tasks; using System.Windows; -namespace ModAssistant +namespace VRCMelonAssistant { /// /// 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 bool SelectInstalledMods; - public static bool ReinstallInstalledMods; + public static string VRChatInstallDirectory; + public static string VRChatInstallType; + public static bool CloseWindowOnFinish; public static string Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - public static List SavedMods = ModAssistant.Properties.Settings.Default.SavedMods.Split(',').ToList(); public static MainWindow window; public static string Arguments; public static bool Update = true; public static bool GUI = true; - public static string OCIWindow; private async void Application_Startup(object sender, StartupEventArgs e) { // Set SecurityProtocol to prevent crash with TLS ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - if (ModAssistant.Properties.Settings.Default.UpgradeRequired) + if (VRCMelonAssistant.Properties.Settings.Default.UpgradeRequired) { - ModAssistant.Properties.Settings.Default.Upgrade(); - ModAssistant.Properties.Settings.Default.UpgradeRequired = false; - ModAssistant.Properties.Settings.Default.Save(); + VRCMelonAssistant.Properties.Settings.Default.Upgrade(); + VRCMelonAssistant.Properties.Settings.Default.UpgradeRequired = false; + VRCMelonAssistant.Properties.Settings.Default.Save(); } Version = Version.Substring(0, Version.Length - 2); - OCIWindow = ModAssistant.Properties.Settings.Default.OCIWindow; - if (string.IsNullOrEmpty(OCIWindow)) - { - OCIWindow = "Yes"; - } Pages.Options options = Pages.Options.Instance; options.InstallDirectory = - BeatSaberInstallDirectory = Utils.GetInstallDir(); + VRChatInstallDirectory = Utils.GetInstallDir(); Languages.LoadLanguages(); - while (string.IsNullOrEmpty(BeatSaberInstallDirectory)) + while (string.IsNullOrEmpty(VRChatInstallDirectory)) { string title = (string)Current.FindResource("App:InstallDirDialog:Title"); string body = (string)Current.FindResource("App:InstallDirDialog:OkCancel"); if (System.Windows.Forms.MessageBox.Show(body, title, System.Windows.Forms.MessageBoxButtons.OKCancel) == System.Windows.Forms.DialogResult.OK) { - BeatSaberInstallDirectory = Utils.GetManualDir(); + VRChatInstallDirectory = Utils.GetManualDir(); } else { @@ -68,21 +58,12 @@ namespace ModAssistant } options.InstallType = - BeatSaberInstallType = ModAssistant.Properties.Settings.Default.StoreType; - options.SaveSelection = - SaveModSelection = ModAssistant.Properties.Settings.Default.SaveSelected; - options.CheckInstalledMods = - CheckInstalledMods = ModAssistant.Properties.Settings.Default.CheckInstalled; - options.SelectInstalledMods = - SelectInstalledMods = ModAssistant.Properties.Settings.Default.SelectInstalled; - options.ReinstallInstalledMods = - ReinstallInstalledMods = ModAssistant.Properties.Settings.Default.ReinstallInstalled; + VRChatInstallType = VRCMelonAssistant.Properties.Settings.Default.StoreType; options.CloseWindowOnFinish = - CloseWindowOnFinish = ModAssistant.Properties.Settings.Default.CloseWindowOnFinish; + CloseWindowOnFinish = VRCMelonAssistant.Properties.Settings.Default.CloseWindowOnFinish; await ArgumentHandler(e.Args); await Init(); - options.UpdateOCIWindow(OCIWindow); } private async Task Init() @@ -122,10 +103,6 @@ namespace ModAssistant { Utils.SendNotify(string.Format((string)Current.FindResource("App:InvalidArgument"), "--install")); } - else - { - await OneClickInstaller.InstallAsset(args[1]); - } if (CloseWindowOnFinish) { @@ -152,8 +129,8 @@ namespace ModAssistant { if (Languages.LoadLanguage(args[1])) { - ModAssistant.Properties.Settings.Default.LanguageCode = args[1]; - ModAssistant.Properties.Settings.Default.Save(); + VRCMelonAssistant.Properties.Settings.Default.LanguageCode = args[1]; + VRCMelonAssistant.Properties.Settings.Default.Save(); Languages.UpdateUI(args[1]); } } @@ -166,10 +143,6 @@ namespace ModAssistant { Utils.SendNotify(string.Format((string)Current.FindResource("App:InvalidArgument"), "--register")); } - else - { - OneClickInstaller.Register(args[1], true, args[2]); - } Update = false; GUI = false; @@ -181,10 +154,6 @@ namespace ModAssistant { Utils.SendNotify(string.Format((string)Current.FindResource("App:InvalidArgument"), "--unregister")); } - else - { - OneClickInstaller.Unregister(args[1], true); - } Update = false; GUI = false; diff --git a/ModAssistant/Classes/Diagnostics.cs b/VRCMelonAssistant/Classes/Diagnostics.cs similarity index 98% rename from ModAssistant/Classes/Diagnostics.cs rename to VRCMelonAssistant/Classes/Diagnostics.cs index 4edab33..dbfdd52 100644 --- a/ModAssistant/Classes/Diagnostics.cs +++ b/VRCMelonAssistant/Classes/Diagnostics.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Windows; -namespace ModAssistant +namespace VRCMelonAssistant { class Diagnostics { diff --git a/VRCMelonAssistant/Classes/HardcodedCategories.cs b/VRCMelonAssistant/Classes/HardcodedCategories.cs new file mode 100644 index 0000000..361ed26 --- /dev/null +++ b/VRCMelonAssistant/Classes/HardcodedCategories.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; + +namespace VRCMelonAssistant +{ + public static class HardcodedCategories + { + private static readonly Dictionary> CategoryContents = new() + { + {"Safety & Security", new() {"Advanced Safety", "Finitizer", "True Shader Anticrash", "Safety-Presets"}}, + {"Core mods and libraries", new() {"UI Expansion Kit", "ActionMenuApi", "VRCModUpdater.Loader"}}, + {"All-in-one mods", new() {"emmVRCLoader"}}, + {"Camera mods", new() { + "CameraMinus", "DesktopCamera", "BetterSteadycam", "ITR's Melon Cameras", "CameraResChanger", + "LocalCameraMod", "Lag Free Screenshots" + }}, + {"Performance & Fidelity", new() { + "Core Limiter", "MirrorResolutionUnlimiter", "AvatarHider", "Runtime Graphics Settings", + "GamePriority", "FrameFocus", "ClearVRAM" + }}, + {"Utilities & Tweaks", new() { + "ReloadAvatars", "KeyboardPaste", "No Outlines", "UnmuteSound", "SparkleBeGone", + "BTKSAImmersiveHud", "OGTrustRanks", "ToggleMicIcon", "Friends+ home", + "MicSensitivity", "CloningBeGone", "ToggleFullScreen", "View Point Tweaker" + }}, + {"Hardware support", new() {"LeapMotionExtension", "ThumbParams", "VRCFaceTracking"}}, + {"Dynamic bones", new() { + "ImmersiveTouch", "Dynamic Bones Safety", "MultiplayerDynamicBonesMod", "Multiplayer Dynamic Bones", + }}, + {"World tweaks", new() { + "PostProcessing", "NearClipPlaneAdj", "RemoveChairs", "ComponentToggle", "No Grabby Hands", "AOOverride" + }}, + {"Fixes", new() {"Invite+ fix", "CursorLockFix", "DownloadFix",}}, + {"New features & Overhauls", new() { + "IKTweaks", "JoinNotifier", "FBT Saver", "BTKSANameplateMod", "AdvancedInvites", "VRCVideoLibrary", + "BTKSASelfPortrait", "OldMate", "BetterLoadingScreen", "Loading Screen Pictures", "FavCat", + "ActionMenuUtils", "WorldPredownload", "AskToPortal", "Headlight", "ITR's Player Tracer", + "InstanceHistory", "PortableMirrorMod", "VRCBonesController" + }}, + {"UI mods", new() { + "Particle and DynBone limiter settings UI", "CalibrateConfirm", "Emoji Page Buttons", + "UserInfoExtensions", "MLConsoleViewer", "OwO Mod", "ActiveBackground", "PlayerList", "ComfyVRMenu", + "DiscordMute", "MicToggle", "VRCPlusPet" + }}, + {"Movement", new() { + "TeleporterVR", "ImmobilizePlayerMod", "TrackingRotator", "OculusPlayspaceMover", + "ITR's Gravity Controller", "QMFreeze", "Double-Tap Runner", "Player Rotater", + }}, + {"Very Niche Mods", new() {"HWIDPatch", "No Steam. At all.", "Vertex Animation Remover", "LocalPlayerPrefs", "BTKSAGestureMod",}} + }; + + private static readonly Dictionary CategoryDescriptions = new() + { + {"Safety & Security", "Crash less, block annoyances"}, + {"Core mods and libraries", "Other mods might require these"}, + {"All-in-one mods", "It does a lot of stuff"}, + {"Camera mods", "For all your screenshot or streaming needs"}, + {"Performance & Fidelity", "Improve performance or make the game look better"}, + {"Utilities & Tweaks", "Small mods that address specific issues"}, + {"Hardware support", "For all exotic hardware out there"}, + {"Dynamic bones", "Mods that affect jiggly bits"}, + {"World tweaks", "Change aspects of the world you're in"}, + {"Fixes", "It's not a bug, it's a feature"}, + {"New features & Overhauls", "Mods that introduce new features or significantly change existing ones"}, + {"UI mods", "Modify the user interface or introduce new functionality to it"}, + {"Movement", "Move in new exciting ways"}, + {"Very Niche Mods", "Only use these if you're really sure you need them"} + }; + + private static readonly Dictionary ModNameToCategory = new(); + + static HardcodedCategories() + { + foreach (var keyValuePair in CategoryContents) + foreach (var s in keyValuePair.Value) + ModNameToCategory.Add(s.ToLowerInvariant(), keyValuePair.Key); + } + + public static string GetCategoryFor(Mod mod) + { + foreach (var alias in mod.aliases) + { + if (ModNameToCategory.TryGetValue(alias.ToLowerInvariant(), out var result)) return result; + } + + foreach (var version in mod.versions) + { + if (ModNameToCategory.TryGetValue(version.name.ToLowerInvariant(), out var result)) return result; + } + + return null; + } + + public static string GetCategoryDescription(string category) + { + return CategoryDescriptions.TryGetValue(category, out var result) ? result : ""; + } + } +} diff --git a/ModAssistant/Classes/Http.cs b/VRCMelonAssistant/Classes/Http.cs similarity index 93% rename from ModAssistant/Classes/Http.cs rename to VRCMelonAssistant/Classes/Http.cs index 6ccac42..653f492 100644 --- a/ModAssistant/Classes/Http.cs +++ b/VRCMelonAssistant/Classes/Http.cs @@ -3,7 +3,7 @@ using System.Net; using System.Net.Http; using System.Web.Script.Serialization; -namespace ModAssistant +namespace VRCMelonAssistant { static class Http { @@ -26,7 +26,7 @@ namespace ModAssistant }; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; - _client.DefaultRequestHeaders.Add("User-Agent", "ModAssistant/" + App.Version); + _client.DefaultRequestHeaders.Add("User-Agent", "VRCMelonAssistant/" + App.Version); return _client; } diff --git a/ModAssistant/Classes/HyperlinkExtensions.cs b/VRCMelonAssistant/Classes/HyperlinkExtensions.cs similarity index 88% rename from ModAssistant/Classes/HyperlinkExtensions.cs rename to VRCMelonAssistant/Classes/HyperlinkExtensions.cs index 40956f6..1de35c5 100644 --- a/ModAssistant/Classes/HyperlinkExtensions.cs +++ b/VRCMelonAssistant/Classes/HyperlinkExtensions.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using System.Windows; using System.Windows.Documents; -namespace ModAssistant +namespace VRCMelonAssistant { public static class HyperlinkExtensions { @@ -28,7 +28,7 @@ namespace ModAssistant hyperlink.RequestNavigate -= Hyperlink_RequestNavigate; } - private static void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) + public static void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) { Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)); e.Handled = true; diff --git a/VRCMelonAssistant/Classes/InstallHandlers.cs b/VRCMelonAssistant/Classes/InstallHandlers.cs new file mode 100644 index 0000000..b6aece9 --- /dev/null +++ b/VRCMelonAssistant/Classes/InstallHandlers.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using VRCMelonAssistant.Pages; + +namespace VRCMelonAssistant +{ + public static class InstallHandlers + { + public static bool IsMelonLoaderInstalled() + { + return File.Exists(Path.Combine(App.VRChatInstallDirectory, "version.dll")) && File.Exists(Path.Combine(App.VRChatInstallDirectory, "MelonLoader", "Dependencies", "Bootstrap.dll")); + } + + public static bool RemoveMelonLoader() + { + MainWindow.Instance.MainText = $"{(string)App.Current.FindResource("Mods:UnInstallingMelonLoader")}..."; + + try + { + var versionDllPath = Path.Combine(App.VRChatInstallDirectory, "version.dll"); + var melonLoaderDirPath = Path.Combine(App.VRChatInstallDirectory, "MelonLoader"); + + if (File.Exists(versionDllPath)) + File.Delete(versionDllPath); + if (Directory.Exists(melonLoaderDirPath)) + Directory.Delete(melonLoaderDirPath, true); + } + catch (Exception ex) + { + MessageBox.Show($"{App.Current.FindResource("Mods:UninstallMLFailed")}.\n\n" + ex); + return false; + } + + return true; + } + + public static async Task InstallMelonLoader() + { + if (!RemoveMelonLoader()) return; + + try + { + MainWindow.Instance.MainText = $"{(string) App.Current.FindResource("Mods:DownloadingMelonLoader")}..."; + + using var installerZip = await DownloadFileToMemory("https://github.com/LavaGang/MelonLoader/releases/download/v0.3.0/MelonLoader.x64.zip"); + using var zipReader = new ZipArchive(installerZip, ZipArchiveMode.Read); + + MainWindow.Instance.MainText = $"{(string) App.Current.FindResource("Mods:UnpackingMelonLoader")}..."; + + foreach (var zipArchiveEntry in zipReader.Entries) + { + var targetFileName = Path.Combine(App.VRChatInstallDirectory, zipArchiveEntry.FullName); + Directory.CreateDirectory(Path.GetDirectoryName(targetFileName)); + using var targetFile = File.OpenWrite(targetFileName); + using var entryStream = zipArchiveEntry.Open(); + await entryStream.CopyToAsync(targetFile); + } + + Directory.CreateDirectory(Path.Combine(App.VRChatInstallDirectory, "Mods")); + Directory.CreateDirectory(Path.Combine(App.VRChatInstallDirectory, "Plugins")); + } + catch (Exception ex) + { + MessageBox.Show($"{App.Current.FindResource("Mods:InstallMLFailed")}.\n\n" + ex); + } + } + + internal static async Task DownloadFileToMemory(string link) + { + using var resp = await Http.HttpClient.GetAsync(link); + return await resp.Content.ReadAsStreamAsync(); + } + + public static async Task InstallMod(Mod mod) + { + string downloadLink = mod.versions[0].downloadlink; + + if (string.IsNullOrEmpty(downloadLink)) + { + MessageBox.Show(string.Format((string)App.Current.FindResource("Mods:ModDownloadLinkMissing"), mod.versions[0].name)); + return; + } + + if (mod.installedFilePath != null) + File.Delete(mod.installedFilePath); + + var modUri = new Uri(downloadLink); + var targetFilePath = Path.Combine(App.VRChatInstallDirectory, mod.versions[0].IsPlugin ? "Plugins" : "Mods", + mod.versions[0].IsBroken ? "Broken" : "", modUri.Segments.Last()); + + Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)); + + using (Stream stream = await DownloadFileToMemory(downloadLink)) + { + using var targetFile = File.OpenWrite(targetFilePath); + await stream.CopyToAsync(targetFile); + } + + mod.ListItem.IsInstalled = true; + mod.installedFilePath = targetFilePath; + mod.ListItem.InstalledVersion = mod.versions[0].modversion; + mod.ListItem.InstalledModInfo = mod; + } + } +} diff --git a/ModAssistant/Classes/Languages.cs b/VRCMelonAssistant/Classes/Languages.cs similarity index 95% rename from ModAssistant/Classes/Languages.cs rename to VRCMelonAssistant/Classes/Languages.cs index c70abe5..b238d89 100644 --- a/ModAssistant/Classes/Languages.cs +++ b/VRCMelonAssistant/Classes/Languages.cs @@ -4,16 +4,16 @@ using System.Globalization; using System.IO; using System.Linq; using System.Windows; -using ModAssistant.Pages; +using VRCMelonAssistant.Pages; -namespace ModAssistant +namespace VRCMelonAssistant { class Languages { public static string LoadedLanguage { get; private set; } public static List LoadedLanguages { get => availableCultures.ToList(); } public static bool FirstRun = true; - private static readonly string[] availableLanguageCodes = { "de", "en", "fr", "it", "ko", "nb", "nl", "ru", "sv", "zh" }; + private static readonly string[] availableLanguageCodes = { "en" }; private static IEnumerable availableCultures; public static void LoadLanguages() diff --git a/VRCMelonAssistant/Classes/Mod.cs b/VRCMelonAssistant/Classes/Mod.cs new file mode 100644 index 0000000..81698f5 --- /dev/null +++ b/VRCMelonAssistant/Classes/Mod.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using VRCMelonAssistant.Pages; + +namespace VRCMelonAssistant +{ + public class Mod + { + public int _id; + public string uploaddate; + public string category; + public string[] aliases; + public ModVersion[] versions; + public Mods.ModListItem ListItem; + public string installedFilePath; + public string installedVersion; + public bool installedInBrokenDir; + + public class ModVersion + { + public int _version; + public string name; + public string modversion; + public string modtype; + public string author; + public string description; + public string downloadlink; + public string sourcelink; + public string hash; + public string updatedate; + public string vrchatversion; + public string loaderversion; + public int approvalStatus; + + public bool IsBroken => approvalStatus == 2; + public bool IsPlugin => modtype.Equals("plugin", StringComparison.InvariantCultureIgnoreCase); + } + } +} diff --git a/ModAssistant/Classes/Promotions.cs b/VRCMelonAssistant/Classes/Promotions.cs similarity index 73% rename from ModAssistant/Classes/Promotions.cs rename to VRCMelonAssistant/Classes/Promotions.cs index ae1ce7e..ba0055f 100644 --- a/ModAssistant/Classes/Promotions.cs +++ b/VRCMelonAssistant/Classes/Promotions.cs @@ -1,4 +1,4 @@ -namespace ModAssistant +namespace VRCMelonAssistant { class Promotions { @@ -6,9 +6,9 @@ namespace ModAssistant { new Promotion { - ModName = "YUR Fit Calorie Tracker", + ModName = "emmVRCLoader", Text = "Join our Discord!", - Link = "https://yur.chat" + Link = "https://discord.gg/emmvrc" } }; } diff --git a/ModAssistant/Classes/Themes.cs b/VRCMelonAssistant/Classes/Themes.cs similarity index 97% rename from ModAssistant/Classes/Themes.cs rename to VRCMelonAssistant/Classes/Themes.cs index a5b483d..82c32a0 100644 --- a/ModAssistant/Classes/Themes.cs +++ b/VRCMelonAssistant/Classes/Themes.cs @@ -9,9 +9,9 @@ using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Win32; -using ModAssistant.Pages; +using VRCMelonAssistant.Pages; -namespace ModAssistant +namespace VRCMelonAssistant { public class Themes { @@ -23,7 +23,7 @@ namespace ModAssistant /// Local dictionary of Resource Dictionaries mapped by their names. /// private static readonly Dictionary loadedThemes = new Dictionary(); - private static readonly List preInstalledThemes = new List { "Light", "Dark", "BSMG", "Light Pink" }; + private static readonly List preInstalledThemes = new List { "Light", "Dark", "Light Pink" }; /// /// Index of "LoadedTheme" in App.xaml @@ -140,7 +140,7 @@ namespace ModAssistant } /// - /// Applies a loaded theme to ModAssistant. + /// Applies a loaded theme to VRCMelonAssistant. /// /// Name of the theme. /// Send message to MainText (default: true). @@ -209,7 +209,7 @@ namespace ModAssistant * Writing it as is instead of using XAMLWriter keeps the source as is with comments, spacing, and organization. * Using XAMLWriter would compress it into an unorganized mess. */ - using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream($"ModAssistant.Themes.{themeName}.xaml")) + using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream($"VRCMelonAssistant.Themes.{themeName}.xaml")) using (FileStream writer = new FileStream($@"{ThemeDirectory}\\{themeName}\\{themeName}.xaml", FileMode.Create)) { byte[] buffer = new byte[s.Length]; @@ -392,7 +392,7 @@ namespace ModAssistant /* * Check to see if the lengths of each file are different. If they are, overwrite what currently exists. * The reason we are also checking LoadedTheme against the name variable is to prevent overwriting a file that's - * already being used by ModAssistant and causing a System.IO.IOException. + * already being used by VRCMelonAssistant and causing a System.IO.IOException. */ FileInfo existingInfo = new FileInfo(videoName); if (existingInfo.Length != file.Length && LoadedTheme != name) @@ -449,7 +449,7 @@ namespace ModAssistant { var assembly = Assembly.GetExecutingAssembly(); var resourceNames = assembly.GetManifestResourceNames(); - var desiredResourceName = $"ModAssistant.Themes.{themeName}.{imageName}.png"; + var desiredResourceName = $"VRCMelonAssistant.Themes.{themeName}.{imageName}.png"; // Don't attempt to access non-existent manifest resources if (!resourceNames.Contains(desiredResourceName)) @@ -508,8 +508,6 @@ namespace ModAssistant ChangeColor(icons, "InfoIconColor", "info_circleDrawingGroup"); ChangeColor(icons, "OptionsIconColor", "cogDrawingGroup"); ChangeColor(icons, "ModsIconColor", "microchipDrawingGroup"); - ChangeColor(icons, "LoadingIconColor", "loadingInnerDrawingGroup"); - ChangeColor(icons, "LoadingIconColor", "loadingMiddleDrawingGroup"); ChangeColor(icons, "LoadingIconColor", "loadingOuterDrawingGroup"); } diff --git a/ModAssistant/Classes/Updater.cs b/VRCMelonAssistant/Classes/Updater.cs similarity index 91% rename from ModAssistant/Classes/Updater.cs rename to VRCMelonAssistant/Classes/Updater.cs index 7f88eb1..ca364f7 100644 --- a/ModAssistant/Classes/Updater.cs +++ b/VRCMelonAssistant/Classes/Updater.cs @@ -3,19 +3,19 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; using System.Windows; -using static ModAssistant.Http; +using static VRCMelonAssistant.Http; -namespace ModAssistant +namespace VRCMelonAssistant { class Updater { - private static readonly string APILatestURL = "https://api.github.com/repos/Assistant/ModAssistant/releases/latest"; + private static readonly string APILatestURL = "https://api.github.com/repos/knah/VRCMelonAssistant/releases/latest"; private static Update LatestUpdate; private static Version CurrentVersion; private static Version LatestVersion; private static bool NeedsUpdate = false; - private static readonly string NewExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "ModAssistant.exe"); + private static readonly string NewExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "VRCMelonAssistant.exe"); private static readonly string Arguments = App.Arguments; #pragma warning disable CS0162 // Unreachable code detected @@ -38,7 +38,7 @@ namespace ModAssistant public static async Task Run() { - if (Path.GetFileName(Utils.ExePath).Equals("ModAssistant.old.exe")) RunNew(); + if (Path.GetFileName(Utils.ExePath).Equals("VRCMelonAssistant.old.exe")) RunNew(); try { NeedsUpdate = await CheckForUpdate(); @@ -53,12 +53,12 @@ namespace ModAssistant public static async Task StartUpdate() { - string OldExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "ModAssistant.old.exe"); + string OldExe = Path.Combine(Path.GetDirectoryName(Utils.ExePath), "VRCMelonAssistant.old.exe"); string DownloadLink = null; foreach (Update.Asset asset in LatestUpdate.assets) { - if (asset.name == "ModAssistant.exe") + if (asset.name == "VRCMelonAssistant.exe") { DownloadLink = asset.browser_download_url; } diff --git a/ModAssistant/Classes/Utils.cs b/VRCMelonAssistant/Classes/Utils.cs similarity index 85% rename from ModAssistant/Classes/Utils.cs rename to VRCMelonAssistant/Classes/Utils.cs index 1978b42..c7baaec 100644 --- a/ModAssistant/Classes/Utils.cs +++ b/VRCMelonAssistant/Classes/Utils.cs @@ -12,9 +12,10 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; -using static ModAssistant.Http; +using VRCMelonAssistant.Pages; +using static VRCMelonAssistant.Http; -namespace ModAssistant +namespace VRCMelonAssistant { public class Utils { @@ -23,14 +24,9 @@ namespace ModAssistant public class Constants { - public const string BeatSaberAPPID = "620980"; - public const string BeatModsAPIUrl = "https://beatmods.com/api/v1/"; - public const string TeknikAPIUrl = "https://api.teknik.io/v1/"; - public const string BeatModsURL = "https://beatmods.com"; - public const string BeatModsVersions = "https://versions.beatmods.com/versions.json"; - public const string BeatModsAlias = "https://alias.beatmods.com/aliases.json"; + public const string VRChatAppId = "438100"; + public const string VRCMGModsJson = "https://api.vrcmg.com/v0/mods.json"; public const string WeebCDNAPIURL = "https://pat.assistant.moe/api/v1.0/"; - public const string BeatModsModsOptions = "mod?status=approved"; public const string MD5Spacer = " "; public static readonly char[] IllegalCharacters = new char[] { @@ -42,20 +38,6 @@ namespace ModAssistant }; } - public class TeknikPasteResponse - { - public Result result; - public class Result - { - public string id; - public string url; - public string title; - public string syntax; - public DateTime? expiration; - public string password; - } - } - public class WeebCDNRandomResponse { public int index; @@ -125,8 +107,8 @@ namespace ModAssistant if (!string.IsNullOrEmpty(InstallDir) && Directory.Exists(InstallDir) - && Directory.Exists(Path.Combine(InstallDir, "Beat Saber_Data", "Plugins")) - && File.Exists(Path.Combine(InstallDir, "Beat Saber.exe"))) + && Directory.Exists(Path.Combine(InstallDir, "VRChat_Data", "Plugins")) + && File.Exists(Path.Combine(InstallDir, "VRChat.exe"))) { return InstallDir; } @@ -164,13 +146,14 @@ namespace ModAssistant public static string SetDir(string directory, string store) { - App.BeatSaberInstallDirectory = directory; - App.BeatSaberInstallType = store; + App.VRChatInstallDirectory = directory; + App.VRChatInstallType = store; Pages.Options.Instance.InstallDirectory = directory; Pages.Options.Instance.InstallType = store; Properties.Settings.Default.InstallFolder = directory; Properties.Settings.Default.StoreType = store; Properties.Settings.Default.Save(); + MainWindow.Instance.MarkModsPageForRefresh(); return directory; } @@ -210,9 +193,9 @@ namespace ModAssistant regex = new Regex("\\s\"installdir\"\\s+\"(.+)\""); foreach (string path in SteamPaths) { - if (File.Exists(Path.Combine(@path, @"appmanifest_" + Constants.BeatSaberAPPID + ".acf"))) + if (File.Exists(Path.Combine(@path, @"appmanifest_" + Constants.VRChatAppId + ".acf"))) { - using (StreamReader reader = new StreamReader(Path.Combine(@path, @"appmanifest_" + Constants.BeatSaberAPPID + ".acf"))) + using (StreamReader reader = new StreamReader(Path.Combine(@path, @"appmanifest_" + Constants.VRChatAppId + ".acf"))) { string line; while ((line = reader.ReadLine()) != null) @@ -220,7 +203,7 @@ namespace ModAssistant Match match = regex.Match(line); if (match.Success) { - if (File.Exists(Path.Combine(@path, @"common", match.Groups[1].Value, "Beat Saber.exe"))) + if (File.Exists(Path.Combine(@path, @"common", match.Groups[1].Value, "VRChat.exe"))) { return SetDir(Path.Combine(@path, @"common", match.Groups[1].Value), "Steam"); } @@ -234,7 +217,7 @@ namespace ModAssistant public static string GetVersion() { - string filename = Path.Combine(App.BeatSaberInstallDirectory, "Beat Saber_Data", "globalgamemanagers"); + string filename = Path.Combine(App.VRChatInstallDirectory, "VRChat_Data", "globalgamemanagers"); using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) { byte[] file = File.ReadAllBytes(filename); @@ -258,9 +241,9 @@ namespace ModAssistant if (!string.IsNullOrEmpty(OculusInstall)) { - if (File.Exists(Path.Combine(OculusInstall, "Software", "hyperbolic-magnetism-beat-saber", "Beat Saber.exe"))) + if (File.Exists(Path.Combine(OculusInstall, "Software", "vrchat-vrchat", "VRChat.exe"))) { - return SetDir(Path.Combine(OculusInstall, "Software", "hyperbolic-magnetism-beat-saber"), "Oculus"); + return SetDir(Path.Combine(OculusInstall, "Software", "vrchat-vrchat"), "Oculus"); } } @@ -294,8 +277,8 @@ namespace ModAssistant string GUIDLetter = guidLetterVolumes.FirstOrDefault(x => libraryPath.Contains(x.Key)).Value; if (!string.IsNullOrEmpty(GUIDLetter)) { - string finalPath = Path.Combine(GUIDLetter, libraryPath.Substring(49), @"Software\hyperbolic-magnetism-beat-saber"); - if (File.Exists(Path.Combine(finalPath, "Beat Saber.exe"))) + string finalPath = Path.Combine(GUIDLetter, libraryPath.Substring(49), @"Software\vrchat-vrchat"); + if (File.Exists(Path.Combine(finalPath, "VRChat.exe"))) { return SetDir(finalPath, "Oculus"); } @@ -323,10 +306,10 @@ namespace ModAssistant path = path.Replace("\\select.this.directory", ""); path = path.Replace(".this.directory", ""); path = path.Replace("\\select.directory", ""); - if (File.Exists(Path.Combine(path, "Beat Saber.exe"))) + if (File.Exists(Path.Combine(path, "VRChat.exe"))) { string store; - if (File.Exists(Path.Combine(path, "Beat Saber_Data", "Plugins", "steam_api64.dll"))) + if (File.Exists(Path.Combine(path, "VRChat_Data", "Plugins", "steam_api64.dll"))) { store = "Steam"; } @@ -356,21 +339,6 @@ namespace ModAssistant return null; } - public static bool IsVoid() - { - string directory = App.BeatSaberInstallDirectory; - string pluginsDirectory = Path.Combine(directory, "Beat Saber_Data", "Plugins"); - - if (File.Exists(Path.Combine(directory, "IGG-GAMES.COM.url")) || - File.Exists(Path.Combine(directory, "SmartSteamEmu.ini")) || - File.Exists(Path.Combine(directory, "GAMESTORRENT.CO.url")) || - File.Exists(Path.Combine(pluginsDirectory, "BSteam crack.dll")) || - File.Exists(Path.Combine(pluginsDirectory, "HUHUVR_steam_api64.dll")) || - Directory.GetFiles(pluginsDirectory, "*.ini", SearchOption.TopDirectoryOnly).Where(x => Path.GetFileName(x) != "desktop.ini").Any()) - return true; - return false; - } - public static byte[] StreamToArray(Stream input) { byte[] buffer = new byte[16 * 1024]; diff --git a/ModAssistant/Libs/semver/IntExtensions.cs b/VRCMelonAssistant/Libs/semver/IntExtensions.cs similarity index 98% rename from ModAssistant/Libs/semver/IntExtensions.cs rename to VRCMelonAssistant/Libs/semver/IntExtensions.cs index b715259..e8feb74 100644 --- a/ModAssistant/Libs/semver/IntExtensions.cs +++ b/VRCMelonAssistant/Libs/semver/IntExtensions.cs @@ -22,7 +22,7 @@ THE SOFTWARE. using System.Text; -namespace ModAssistant.Libs +namespace VRCMelonAssistant.Libs { internal static class IntExtensions { diff --git a/ModAssistant/Libs/semver/SemVersion.cs b/VRCMelonAssistant/Libs/semver/SemVersion.cs similarity index 99% rename from ModAssistant/Libs/semver/SemVersion.cs rename to VRCMelonAssistant/Libs/semver/SemVersion.cs index 94a2541..ef0677a 100644 --- a/ModAssistant/Libs/semver/SemVersion.cs +++ b/VRCMelonAssistant/Libs/semver/SemVersion.cs @@ -29,7 +29,7 @@ using System.Security.Permissions; #endif using System.Text.RegularExpressions; -namespace ModAssistant.Libs +namespace VRCMelonAssistant.Libs { /// /// A semantic version implementation. diff --git a/ModAssistant/Localisation/en-DEBUG.xaml b/VRCMelonAssistant/Localisation/en-DEBUG.xaml similarity index 98% rename from ModAssistant/Localisation/en-DEBUG.xaml rename to VRCMelonAssistant/Localisation/en-DEBUG.xaml index a8de611..44f9ef1 100644 --- a/ModAssistant/Localisation/en-DEBUG.xaml +++ b/VRCMelonAssistant/Localisation/en-DEBUG.xaml @@ -1,7 +1,7 @@ i18n:en-DEBUG @@ -101,9 +101,6 @@ Options:SelectInstalledMods Options:Reinstall Installed Mods Options:EnableOneClickInstalls - Options:BeatSaver - Options:ModelSaber - Options:Playlists Options:CloseWindow Options:GameType Options:GameType:Steam diff --git a/ModAssistant/Localisation/en.xaml b/VRCMelonAssistant/Localisation/en.xaml similarity index 56% rename from ModAssistant/Localisation/en.xaml rename to VRCMelonAssistant/Localisation/en.xaml index 7322b23..3611a59 100644 --- a/ModAssistant/Localisation/en.xaml +++ b/VRCMelonAssistant/Localisation/en.xaml @@ -1,20 +1,20 @@ i18n:en-US - Couldn't find your Beat Saber install folder! + Couldn't find your VRChat install folder! Press OK to try again, or Cancel to close application. Invalid argument! '{0}' requires an option. - Unrecognized argument. Closing Mod Assistant. + Unrecognized argument. Closing VRChat Melon Assistant. An unhandled exception just occurred Exception - Mod Assistant + VRChat Melon Assistant Intro Mods About @@ -24,33 +24,33 @@ Mod Info Install or Update - Could not load game versions, Mods tab will be unavailable. - New Game Version Detected! - It looks like there's been a game update. - Please double check that the correct version is selected at the bottom left corner! No mod selected! {0} does not have an info page. Intro - Welcome to Mod Assistant + Welcome to VRChat Melon Assistant Please read this page entirely and carefully By using this program attest to have read and agree to the following terms: - Beat Saber - does not natively support mods. This means: + VRChat + explicitly forbids modding the game client in their Terms of Service. This means: + + + Modding the client + can lead to bans. Mods available via this installer are manually checked to minimize the chance of that happening, but the risk is always there. Mods will break every update. This is normal, and - not Beat Games' fault. + not VRChat's fault. Mods will cause bugs and performance issues. This is - not Beat Games' fault. + not VRChat's fault. Mods are made for @@ -58,21 +58,21 @@ 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. + DO NOT complain to VRChat Team because mods broke. This is + not VRChat's fault. + They are not trying to kill mods. Most likely. - If I keep seeing people leave negative reviews + If I keep seeing people complain to the devs because mods broke, I will personally kill mods with a rusty spoon - Please read the Beginners Guide on the - - Wiki - . + Feel free to join the + + VRChat Modding Group discord + for more info, help, and support. I Agree Disagree @@ -93,37 +93,34 @@ Checking installed mods Loading Mods Finished loading mods - No mods available for this version of Beat Saber + No mods available Installing {0} Installed {0} + Uninstalling MelonLoader + Downloading MelonLoader + Unpacking MelonLoader Finished installing mods Could not find download link for {0} Uninstall {0}? Are you sure you want to remove {0}? This could break your other mods + Error uninstalling mod. Make sure VRChat is not running. + Error uninstalling MelonLoader. Make sure VRChat is not running. + Error installing MelonLoader. Try using the standalone installer. Failed to extract {0}, trying again in {1} seconds. ({2}/{3}) Failed to extract {0} after max attempts ({1}), skipping. This mod might not work properly so proceed at your own risk Search... - Failed to Uninstall BSIPA - BSIPA installation not found, uninstall operation skipped. About - About Mod Assistant - I'm Assistant, and I made Mod Assistant for mod assistance, with a few principles in mind: - Simplicity - Portability - Single Executable - Responsible use + About VRChat Melon Assistant + VRChat Melon Assistant is a fork of Assistant's Mod Assistant for Beat Saber. - If you enjoy this program and would like to support me, please visit my - - donation page - - or my - - Patreon + Visit + + Mod Assistant GitHub repo + to appreciate their hard work and/or throw some money at them! Special Thanks ♥ Donate @@ -140,10 +137,6 @@ Detect Installed Mods Select Installed Mods Reinstall Installed Mods - Enable OneClick™ Installs - BeatSaver - ModelSaber - Playlists Close window when finished Game Type Steam @@ -153,29 +146,24 @@ Installing Playlist: {0} Failed song: {0} [{0} fails] Finished Installing Playlist: {1} - Show OneClick Installer Window - Yes - Close - No Diagnostics Open Logs Open AppData - Uninstall BSIPA Remove All Mods + Remove MelonLoader + Install MelonLoader Application Theme Export Template - Uploading Log - Log URL Copied To Clipboard! - Uploading Log Failed - Uploading log failed! - Could not upload log file to Teknik, please try again or send the file manually. Getting Mod List - Finding BSIPA Version - BSIPA Uninstalled Uninstall All Mods? Are you sure you want to remove ALL mods? This cannot be undone. All Mods Uninstalled + Uninstall MelonLoader? + Are you sure you want to uninstall MelonLoader? + All your mods and mod data will stay intact. + MelonLoader Uninstalled + MelonLoader Installed Current theme has been removed, reverting to default... Themes folder not found! Try exporting the template... AppData folder not found! Try launching your game. @@ -183,54 +171,6 @@ Loading Mods - - Invalid - Invalid Installation Detected - Your game installation is corrupted or otherwise invalid - This can happen if your copy of the game is pirated, or if you copied a pirated copy over your legit install - - If your copy of the game is pirated, - please purchase the game - - HERE - - . - - - If your copy of the game is - not pirated, please - - do a clean install - . - - - If those don't help, ask for support in the - #pc-help channel in - - BSMG - . - - If you used to have a pirated version but have since bought the game - Select Folder - You will need to restart Mod Assistant after changing to the legit install - - - Could not get map details. - Could not download the song. - Could not download the song. - There might be issues with BeatSaver or your internet connection. - Failed to download song ZIP - Beat Saber installation path not found. - Installed: {0} - Failed to install. - {0} OneClick™ Install handlers registered! - {0} OneClick™ Install handlers unregistered! - Installing: {0} - Max tries reached: Skipping {0} - Ratelimit hit. Resuming in {0} - Download failed: {0} - Done'd - Theme not found, reverting to default theme... Theme set to {0}. @@ -244,9 +184,17 @@ Couldn't download update. - Mod Assistant - Could not detect your Beat Saber install folder. Please select it manually. - Mod Assistant needs to run this task as Admin. Please try again. - Select your Beat Saber install folder + VRChat Melon Assistant + Could not detect your VRChat install folder. Please select it manually. + VRChat Melon Assistant needs to run this task as Admin. Please try again. + Select your VRChat install folder Can't open folder: {0} + + Mod info for {0} + Author: {0} + Unknown author + No description available + Download link: + Source code: + Internal ID: {0} {1} diff --git a/ModAssistant/MainWindow.xaml b/VRCMelonAssistant/MainWindow.xaml similarity index 90% rename from ModAssistant/MainWindow.xaml rename to VRCMelonAssistant/MainWindow.xaml index 017fa92..fbdbfeb 100644 --- a/ModAssistant/MainWindow.xaml +++ b/VRCMelonAssistant/MainWindow.xaml @@ -1,9 +1,9 @@ - + @@ -135,25 +135,6 @@ - - - - - : - - - - - @@ -204,7 +185,7 @@