├── NoodleManagerXTests
├── Usings.cs
├── Mods
│ ├── json
│ │ ├── invalid_version.json
│ │ ├── mods_many_versions.json
│ │ └── current_list.json
│ ├── TestParseModInfoList.cs
│ ├── TestUtils.cs
│ └── TestRealFiles.cs
└── NoodleManagerXTests.csproj
├── NoodleManagerX
├── icon-nm.ico
├── Assets
│ ├── NoodleBanner.png
│ ├── ProgressBar.png
│ ├── NoodleManager.png
│ ├── Included
│ │ ├── icon-nm.ico
│ │ └── searchbar.svg
│ ├── SearchSortFilter.png
│ ├── Fonts
│ │ ├── NotoSans-Bold.ttf
│ │ └── NotoSans-Regular.ttf
│ ├── Icons
│ │ ├── youtube.xaml
│ │ ├── play.xaml
│ │ ├── maximize.xaml
│ │ ├── stop.xaml
│ │ ├── minimize.xaml
│ │ ├── bracket.xaml
│ │ ├── twitter.xaml
│ │ ├── youtube.svg
│ │ ├── blacklist.xaml
│ │ ├── close.xaml
│ │ ├── search.xaml
│ │ ├── quest.xaml
│ │ ├── twitch.xaml
│ │ ├── pc.xaml
│ │ ├── download.xaml
│ │ ├── twitter.svg
│ │ ├── delete.xaml
│ │ ├── quest.svg
│ │ ├── minimize.svg
│ │ ├── pc.svg
│ │ ├── stop.svg
│ │ ├── play.svg
│ │ ├── bracket.svg
│ │ ├── maximize.svg
│ │ ├── twitch.svg
│ │ ├── blacklist.svg
│ │ ├── close.svg
│ │ ├── search.svg
│ │ ├── download.svg
│ │ ├── delete.svg
│ │ ├── github.xaml
│ │ └── github.svg
│ ├── pagination.svg
│ └── QuestAccess.svg
├── Resources
│ ├── classdata.tpk
│ ├── UpdateHelper.exe
│ ├── classdata_large.tpk
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── References
│ └── MediaDevices.dll
├── FodyWeavers.xml
├── nuget.config
├── Mods
│ ├── ModPage.cs
│ ├── ModVersionSelection.cs
│ └── ModDownloadSource.cs
├── Models
│ ├── Mods
│ │ ├── ModInfoList.cs
│ │ ├── ModDependencyInfo.cs
│ │ ├── ModInfo.cs
│ │ └── ModVersion.cs
│ ├── Playlists
│ │ ├── PlaylistMap.cs
│ │ ├── PlaylistItem.cs
│ │ ├── PlaylistFile.cs
│ │ └── PlaylistHandler.cs
│ ├── Settings.cs
│ ├── AvatarHandler.cs
│ ├── Stages
│ │ ├── StageFile.cs
│ │ ├── StageHandler.cs
│ │ ├── StageFileInfo.cs
│ │ └── StageItem.cs
│ ├── MtpDevice.cs
│ ├── PlaybackHandler.cs
│ ├── LocalItem.cs
│ ├── DownloadScheduler.cs
│ ├── MapHandler.cs
│ └── StorageAbstraction.cs
├── App.axaml
├── Properties
│ └── PublishProfiles
│ │ ├── Windows_x64.pubxml
│ │ ├── Osx_x64.pubxml
│ │ └── Linux_x64.pubxml
├── App.axaml.cs
├── Utils
│ └── SemVersionJsonConverter.cs
├── MessageBox.axaml
├── Program.cs
├── FodyWeavers.xsd
├── MessageBox.axaml.cs
├── NoodleManagerX.csproj
├── MainWindow.axaml.cs
└── ThirdParty
│ └── MelonLoader
│ └── UnityInformationHandler.cs
├── UpdateHelper
├── UpdateHelper.csproj
├── Properties
│ └── PublishProfiles
│ │ └── MainPublishProfile.pubxml
└── Program.cs
├── README.md
├── .gitattributes
├── NoodleManagerX.sln
├── .gitignore
└── QuestAccess.svg
/NoodleManagerXTests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
--------------------------------------------------------------------------------
/NoodleManagerX/icon-nm.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/icon-nm.ico
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/NoodleBanner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/NoodleBanner.png
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/ProgressBar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/ProgressBar.png
--------------------------------------------------------------------------------
/NoodleManagerX/Resources/classdata.tpk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Resources/classdata.tpk
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/NoodleManager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/NoodleManager.png
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Included/icon-nm.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/Included/icon-nm.ico
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/SearchSortFilter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/SearchSortFilter.png
--------------------------------------------------------------------------------
/NoodleManagerX/References/MediaDevices.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/References/MediaDevices.dll
--------------------------------------------------------------------------------
/NoodleManagerX/Resources/UpdateHelper.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Resources/UpdateHelper.exe
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Fonts/NotoSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/Fonts/NotoSans-Bold.ttf
--------------------------------------------------------------------------------
/NoodleManagerX/Resources/classdata_large.tpk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Resources/classdata_large.tpk
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Fonts/NotoSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tommaier123/NoodleManagerX/HEAD/NoodleManagerX/Assets/Fonts/NotoSans-Regular.ttf
--------------------------------------------------------------------------------
/NoodleManagerX/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/NoodleManagerX/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/NoodleManagerX/Mods/ModPage.cs:
--------------------------------------------------------------------------------
1 | using NoodleManagerX.Models;
2 | using NoodleManagerX.Models.Mods;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace NoodleManagerX.Mods
10 | {
11 | class ModPage : GenericPage
12 | {
13 | public List Mods { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Mods/ModInfoList.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace NoodleManagerX.Models.Mods
9 | {
10 | public class ModInfoList
11 | {
12 | [JsonProperty("availableMods")]
13 | public List AvailableMods { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/NoodleManagerX/App.axaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/Mods/json/invalid_version.json:
--------------------------------------------------------------------------------
1 | {
2 | "availableMods": [
3 | {
4 | "id": "TestBadVersion",
5 | "name": "Test invalid version",
6 | "author": "bookdude13",
7 | "description": "Invalid semver",
8 | "versions": [
9 | {
10 | "version": "1.2.3.4",
11 | "downloadUrl": "localhost",
12 | "dependencies": []
13 | }
14 | ]
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Playlists/PlaylistMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace NoodleManagerX.Models.Playlists
8 | {
9 | [Serializable]
10 | public struct PlaylistMap
11 | {
12 | public string hash;
13 |
14 | public string name;
15 |
16 | public string author;
17 |
18 | public string beatmapper;
19 |
20 | public int difficulty;
21 |
22 | public float trackDuration;
23 |
24 | public long addedTime;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/NoodleManagerX/Mods/ModVersionSelection.cs:
--------------------------------------------------------------------------------
1 | using NoodleManagerX.Models.Mods;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace NoodleManagerX.Mods
9 | {
10 | public class ModVersionSelection
11 | {
12 | public string ModId { get; private set; }
13 | public ModVersion ModVersion { get; private set; }
14 |
15 | public ModVersionSelection(string modId, ModVersion modVersion)
16 | {
17 | ModId = modId;
18 | ModVersion = modVersion;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Settings.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 | using ReactiveUI.Fody.Helpers;
3 | using System.Runtime.Serialization;
4 |
5 | namespace NoodleManagerX.Models
6 | {
7 | [DataContract]
8 | class Settings : ReactiveObject
9 | {
10 | [Reactive][DataMember] public string synthDirectory { get; set; } = "";
11 | [Reactive][DataMember] public bool allowConverts { get; set; } = false;
12 | [Reactive][DataMember] public bool ignoreUpdates { get; set; } = false;
13 | [Reactive][DataMember] public bool getBetas { get; set; } = false;
14 | [Reactive][DataMember] public int previewVolume { get; set; } = 50;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Properties/PublishProfiles/Windows_x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | Any CPU
9 | bin\Release\publish\Windows
10 | FileSystem
11 | net6.0-windows
12 | win-x64
13 | true
14 | true
15 | true
16 |
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Properties/PublishProfiles/Osx_x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | Any CPU
9 | bin\Release\publish\Osx
10 | FileSystem
11 | net5.0
12 | osx-x64
13 | true
14 | True
15 |
16 |
--------------------------------------------------------------------------------
/NoodleManagerX/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace NoodleManagerX
6 | {
7 | public class App : Application
8 | {
9 | public override void Initialize()
10 | {
11 | AvaloniaXamlLoader.Load(this);
12 | }
13 |
14 | public override void OnFrameworkInitializationCompleted()
15 | {
16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
17 | {
18 | desktop.MainWindow = new MainWindow();
19 | }
20 |
21 | base.OnFrameworkInitializationCompleted();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/NoodleManagerX/Properties/PublishProfiles/Linux_x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | Any CPU
9 | bin\Release\publish\Linux
10 | FileSystem
11 | net5.0
12 | linux-x64
13 | true
14 | True
15 |
16 |
--------------------------------------------------------------------------------
/UpdateHelper/UpdateHelper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net5.0-windows
6 | en
7 | true
8 | true
9 | link
10 | embedded
11 | AnyCPU;x84
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/UpdateHelper/Properties/PublishProfiles/MainPublishProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Release
8 | Any CPU
9 | C:\Users\Max\source\repos\tommaier123\NoodleManagerX\NoodleManagerX\Resources
10 | FileSystem
11 | net5.0-windows
12 | win-x86
13 | True
14 | False
15 | true
16 |
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Playlists/PlaylistItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.Serialization;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace NoodleManagerX.Models.Playlists
9 | {
10 | [DataContract]
11 | public class PlaylistItem : GenericItem
12 | {
13 | [DataMember] public List items { get; set; }
14 | public string duration { get { return items != null ? items.Count().ToString() : "0"; } }
15 |
16 | public override string target { get; set; } = "CustomPlaylists";
17 | public override ItemType itemType { get; set; } = ItemType.Playlist;
18 | }
19 |
20 | [DataContract]
21 | public class PlaylistEntryItem
22 | {
23 | [DataMember] public int id { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Mods/ModDependencyInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NoodleManagerX.Utils;
3 | using Semver;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace NoodleManagerX.Models.Mods
11 | {
12 | public class ModDependencyInfo
13 | {
14 | [JsonProperty("id", Required = Required.Always)]
15 | public string Id { get; set; } = "N/A";
16 |
17 | [JsonProperty("minVersion", Required = Required.Always)]
18 | [JsonConverter(typeof(SemVersionJsonConverter))]
19 | public SemVersion MinVersion { get; set; } = null;
20 |
21 | [JsonProperty("maxVersion", Required = Required.Default)]
22 | [JsonConverter(typeof(SemVersionJsonConverter))]
23 | public SemVersion MaxVersion { get; set; } = null;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/NoodleManagerX/Mods/ModDownloadSource.cs:
--------------------------------------------------------------------------------
1 | using NoodleManagerX.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace NoodleManagerX.Mods
9 | {
10 | class ModDownloadSource
11 | {
12 | public static string GetModItemBaseDownloadUrl()
13 | {
14 | return $"{GetModsBaseUrl()}/Downloads";
15 | }
16 |
17 | public static string GetModsBaseUrl()
18 | {
19 | return $"https://raw.githubusercontent.com/bookdude13/SRModsList/{GetModListBranch()}/SynthRiders";
20 | }
21 |
22 | private static string GetModListBranch()
23 | {
24 | var usingBetas = MainViewModel.s_instance?.settings?.getBetas ?? false;
25 | return usingBetas ? "dev" : "master";
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/NoodleManagerX/Utils/SemVersionJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Semver;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace NoodleManagerX.Utils
10 | {
11 | public class SemVersionJsonConverter : JsonConverter
12 | {
13 | public override SemVersion ReadJson(JsonReader reader, Type objectType, SemVersion existingValue, bool hasExistingValue, JsonSerializer serializer)
14 | {
15 | string s = (string)reader.Value;
16 | var parsed = SemVersion.Parse(s, SemVersionStyles.OptionalMinorPatch);
17 | return parsed;
18 | }
19 |
20 | public override void WriteJson(JsonWriter writer, SemVersion value, JsonSerializer serializer)
21 | {
22 | writer.WriteValue(value.ToString());
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Mods/ModInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace NoodleManagerX.Models.Mods
9 | {
10 | public class ModInfo
11 | {
12 | [JsonProperty("id", Required = Required.Always)]
13 | public string Id { get; set; } = "N/A";
14 |
15 | [JsonProperty("name", Required = Required.Always)]
16 | public string Name { get; set; } = "N/A";
17 |
18 | [JsonProperty("author", Required = Required.Always)]
19 | public string Author { get; set; } = "N/A";
20 |
21 | [JsonProperty("description", Required = Required.Always)]
22 | public string Description { get; set; } = "";
23 |
24 | [JsonProperty("versions", Required = Required.Always)]
25 | public List Versions { get; set; } = new List();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/Mods/TestParseModInfoList.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NoodleManagerX.Models.Mods;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace NoodleManagerXTests.Mods
10 | {
11 | internal class TestParseModInfoList
12 | {
13 | [Test]
14 | public void Test_ParseModInfoList_VariousValidVersions()
15 | {
16 | string rawJson = TestUtils.GetTestJsonFileContents("mods_many_versions.json");
17 | ModInfoList? modList = JsonConvert.DeserializeObject(rawJson);
18 | Assert.That(modList, Is.Not.Null);
19 | }
20 |
21 | [Test]
22 | public void Test_ParseModInfoList_BadOnlyMajor()
23 | {
24 | string rawJson = TestUtils.GetTestJsonFileContents("invalid_version.json");
25 | Assert.Throws(() => JsonConvert.DeserializeObject(rawJson));
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/youtube.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/play.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/NoodleManagerX/MessageBox.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/NoodleManagerX/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 | using Avalonia.Svg.Skia;
4 | using System;
5 |
6 | namespace NoodleManagerX
7 | {
8 | class Program
9 | {
10 | // Initialization code. Don't use any Avalonia, third-party APIs or any
11 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
12 | // yet and stuff might break.
13 | public static void Main(string[] args) => BuildAvaloniaApp()
14 | .StartWithClassicDesktopLifetime(args);
15 |
16 | // Avalonia configuration, don't remove; also used by visual designer.
17 | public static AppBuilder BuildAvaloniaApp()
18 | {
19 | #if DEBUG
20 | //so the designer doesn't break
21 | GC.KeepAlive(typeof(SvgImageExtension).Assembly);
22 | GC.KeepAlive(typeof(Avalonia.Svg.Skia.Svg).Assembly);
23 | #endif
24 |
25 |
26 | return AppBuilder.Configure()
27 | .UseReactiveUI()
28 | .UsePlatformDetect()
29 | .LogToTrace();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/AvatarHandler.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 |
5 | namespace NoodleManagerX.Models
6 | {
7 | class AvatarHandler : GenericHandler
8 | {
9 | public override ItemType itemType { get; set; } = ItemType.Avatar;
10 | public override string apiEndpoint { get; set; } = "https://synthriderz.com/api/models/avatars";
11 | public override string folder { get; set; } = "Avatars";
12 | public override string[] extensions { get; set; } = { ".vrm" };
13 |
14 | public override dynamic DeserializePage(string json)
15 | {
16 | return JsonConvert.DeserializeObject(json);
17 | }
18 | }
19 |
20 | [DataContract]
21 | class AvatarItem : GenericItem
22 | {
23 | public override string target { get; set; } = "Avatars";
24 | public override ItemType itemType { get; set; } = ItemType.Avatar;
25 | }
26 |
27 | #pragma warning disable 0649
28 | class AvatarPage : GenericPage
29 | {
30 | public List data;
31 | }
32 | #pragma warning restore 0649
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/maximize.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/stop.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Stages/StageFile.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Runtime.Serialization;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace NoodleManagerX.Models.Stages
10 | {
11 | [Serializable]
12 | public struct StageFile
13 | {
14 | [DataMember]
15 | [JsonProperty("id")]
16 | public int Id;
17 |
18 | [DataMember]
19 | [JsonProperty("filename")]
20 | public string Filename;
21 |
22 | [DataMember]
23 | [JsonProperty("extension")]
24 | public string Extension;
25 |
26 | [DataMember]
27 | [JsonProperty("filesize")]
28 | public int Size;
29 |
30 | [DataMember]
31 | [JsonProperty("visibility")]
32 | public string Visibility;
33 |
34 | [DataMember]
35 | [JsonProperty("cdn_url")]
36 | public string Url;
37 |
38 | [DataMember]
39 | [JsonProperty("download_count")]
40 | public int DownloadCount;
41 |
42 | [DataMember]
43 | [JsonProperty("created_at")]
44 | public DateTime CreatedAt;
45 |
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/minimize.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/bracket.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Stages/StageHandler.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NoodleManagerX.Models.Playlists;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Reflection;
6 | using System.Runtime.Serialization;
7 | using System.Threading.Tasks;
8 |
9 | namespace NoodleManagerX.Models.Stages
10 | {
11 | class StageHandler : GenericHandler
12 | {
13 | public override ItemType itemType { get; set; } = ItemType.Stage;
14 | public override string apiEndpoint { get; set; } = "https://synthriderz.com/api/models/stages";
15 | public override string folder { get; set; } = "CustomStages";
16 | public override string join { get; set; } = "files&join=files.file&join=experience_beatmap&join=id";
17 | public override string[] extensions { get; set; } = { ".stage", ".stagedroid"};
18 |
19 | public override Task LoadLocalItems()
20 | {
21 | return base.LoadLocalItems();
22 | }
23 |
24 | public override dynamic DeserializePage(string json)
25 | {
26 | return JsonConvert.DeserializeObject(json);
27 | }
28 | }
29 |
30 | #pragma warning disable 0649
31 | class StagePage : GenericPage
32 | {
33 | public List data;
34 | }
35 | #pragma warning restore 0649
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/NoodleManagerX/FodyWeavers.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
12 |
13 |
14 |
15 |
16 | A comma-separated list of error codes that can be safely ignored in assembly verification.
17 |
18 |
19 |
20 |
21 | 'false' to turn off automatic generation of the XML Schema file.
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Mods/ModVersion.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NoodleManagerX.Models;
3 | using NoodleManagerX.Utils;
4 | using Semver;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Runtime.Serialization;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace NoodleManagerX.Models.Mods
13 | {
14 | public class ModVersion
15 | {
16 | [JsonProperty("version", Required = Required.Always)]
17 | [JsonConverter(typeof(SemVersionJsonConverter))]
18 | public SemVersion Version { get; set; } = null;
19 |
20 | [JsonProperty("downloadUrl", Required = Required.Always)]
21 | public string DownloadUrl { get; set; } = "";
22 |
23 | [JsonProperty("dependencies", Required = Required.Always)]
24 | public List Dependencies { get; set; } = new List();
25 |
26 | // TODO make this nested. Assumes max depth of 1 for dependencies right now
27 | public bool HasDependency(string dependencyModId)
28 | {
29 | foreach (var dependency in this.Dependencies)
30 | {
31 | if (dependencyModId == dependency.Id)
32 | {
33 | return true;
34 | }
35 | }
36 |
37 | return false;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/twitter.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/youtube.svg:
--------------------------------------------------------------------------------
1 |
2 |
40 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Playlists/PlaylistFile.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace NoodleManagerX.Models.Playlists
7 | {
8 | ///
9 | /// This is the PlaylistItem used in Synth Riders, saved to .playlist file
10 | ///
11 | [Serializable]
12 | public struct PlaylistFile
13 | {
14 | [JsonProperty("dataString")]
15 | public List Songs;
16 |
17 | public int SelectedIconIndex;
18 |
19 | public int SelectedTexture;
20 |
21 | [JsonProperty("namePlaylist")]
22 | public string Name;
23 |
24 | [JsonProperty("description")]
25 | public string Description;
26 |
27 | [JsonProperty("gradientTop")]
28 | public string GradientTop;
29 |
30 | [JsonProperty("gradientDown")]
31 | public string GradientDown;
32 |
33 | [JsonProperty("colorTitle")]
34 | public string TitleColorString;
35 |
36 | [JsonProperty("colorTexture")]
37 | public string TextureColorString;
38 |
39 | [JsonProperty("creationDate")]
40 | public string CreationDate;
41 |
42 | public bool IsEqual(PlaylistFile target)
43 | {
44 | if (Songs == null || target.Songs == null || Songs.Count != target.Songs.Count)
45 | {
46 | return false;
47 | }
48 |
49 | if (Name.Equals(target.Name))
50 | {
51 | return Songs.SequenceEqual(target.Songs);
52 | }
53 |
54 | return false;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/blacklist.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/close.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/search.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Stages/StageFileInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NoodleManagerX.Models.Playlists;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Runtime.Serialization;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace NoodleManagerX.Models.Stages
11 | {
12 | [Serializable]
13 | public class StageFileInfo
14 | {
15 | [DataMember]
16 | [JsonProperty("id")]
17 | public int Id;
18 |
19 | ///
20 | /// Can be "normal", "spin" or "experience"
21 | ///
22 | [DataMember]
23 | [JsonProperty("mode")]
24 | public string Mode;
25 |
26 | ///
27 | /// Can be "pc" or "quest"
28 | ///
29 | [DataMember]
30 | [JsonProperty("platform")]
31 | public string Platform;
32 |
33 | [DataMember]
34 | [JsonProperty("type")]
35 | public string StageType;
36 |
37 | [DataMember]
38 | [JsonProperty("version")]
39 | public int Version;
40 |
41 | [DataMember]
42 | [JsonProperty("file")]
43 | public StageFile File;
44 |
45 | public bool IsPc()
46 | {
47 | return Platform == "pc";
48 | }
49 |
50 | public bool IsQuest()
51 | {
52 | return Platform == "quest";
53 | }
54 |
55 | public bool IsNormalStage()
56 | {
57 | return Mode == "normal";
58 | }
59 |
60 | public bool IsSpinStage()
61 | {
62 | return Mode == "spin";
63 | }
64 |
65 | public bool IsExperienceStage()
66 | {
67 | return Mode == "experience";
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/quest.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/NoodleManagerXTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0-windows
5 | enable
6 | enable
7 |
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | PreserveNewest
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 | PreserveNewest
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/twitch.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/pc.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/download.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NoodleManagerX
2 | [](https://github.com/tommaier123/NoodleManagerX/releases/latest)
3 | [](https://github.com/tommaier123/NoodleManagerX/releases/latest)
4 | [](https://www.twitch.tv/Nova_Max_)
5 | [](https://twitter.com/Nova_Max_)
6 |
7 | ## I am no longer actively maintaining this project. Therefore, I can give no guarantee about support, bug fixes, or security issues coming from the end-of-life .NET runtime or any libraries. Use at your own risk.
8 | ### DM me on Discord to be added as a maintainer.
9 |
10 | This is a standalone app to download Synth Riders custom content for PC and Quest.
11 | Other android based headsets should also work but have not been tested.
12 | A version for Mac and Linux is being worked on.
13 |
14 | ### [Click to download](https://github.com/tommaier123/NoodleManagerX/releases/latest) and check out the [Wiki](https://github.com/tommaier123/NoodleManagerX/wiki) on how to install or use the NoodleManagerX
15 |
16 |
17 |
18 | ## Features:
19 | - Quest support over MTP, no developer mode necessary (should also work for other android based headsets)
20 | - Pagination (access all songs)
21 | - Searching, sorting and filtering
22 | - Improved speed due to multi-threading
23 | - Progress bar for downloads
24 | - Multi download with get page and get all
25 | - Blacklist
26 | - Audio preview
27 | - Removing corrupted maps automatically
28 | - Redownloading updated maps automatically
29 | - Map timestamps are set so the ordering in game is correct (only on PC)
30 | - Auto updating
31 | - Fixed all known bugs of the old NoodleManager
32 |
33 | ## Beta Features
34 | - Playlists
35 | - Avatars
36 | - Stages
37 | - Mods
38 |
39 | ## Work in progress:
40 | - Cross-platform 70%
41 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/delete.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/Mods/TestUtils.cs:
--------------------------------------------------------------------------------
1 | using NoodleManagerX.Models.Mods;
2 | using Semver;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace NoodleManagerXTests.Mods
10 | {
11 | public class TestUtils
12 | {
13 | public static string GetTestJsonFileContents(string filename)
14 | {
15 | var fullPath = Path.Combine(".", "Mods", "json", filename);
16 | return File.ReadAllText(fullPath);
17 | }
18 |
19 | public static ModInfo CreateTestMod(string id, List versions)
20 | {
21 | return new ModInfo
22 | {
23 | Id = id,
24 | Name = id + " name",
25 | Author = "Foo",
26 | Description = "Some mod",
27 | Versions = versions,
28 | };
29 | }
30 |
31 | public static ModVersion CreateTestModVersion(string version, List? dependencies = null)
32 | {
33 | return new ModVersion
34 | {
35 | Version = SemVersion.Parse(version, SemVersionStyles.Any),
36 | DownloadUrl = "localhost",
37 | Dependencies = dependencies ?? new(),
38 | };
39 | }
40 |
41 | public static ModDependencyInfo CreateTestDependency(string id, string minVersion, string? maxVersion = null)
42 | {
43 | return new ModDependencyInfo
44 | {
45 | Id = id,
46 | MinVersion = SemVersion.Parse(minVersion, SemVersionStyles.Any),
47 | MaxVersion = maxVersion == null ? null : SemVersion.Parse(maxVersion, SemVersionStyles.Any),
48 | };
49 | }
50 | public static void AssertVersionsEqual(SemVersion v1, string v2)
51 | {
52 | var semVersion2 = SemVersion.Parse(v2, SemVersionStyles.Any);
53 | Assert.That(v1.ComparePrecedenceTo(semVersion2), Is.EqualTo(0), $"SemVersion {v1} != {v2}");
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/Mods/json/mods_many_versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "availableMods": [
3 | {
4 | "id": "SRVoting",
5 | "name": "SRVoting",
6 | "author": "bookdude13",
7 | "description": "Add voting buttons for custom maps in-game",
8 | "versions": [
9 | {
10 | "version": "1.2.0",
11 | "downloadUrl": "https://github.com/bookdude13/SRVoting/releases/download/v1.2.0/SRVoting_v1.2.0.zip",
12 | "dependencies": [
13 | {
14 | "id": "SRModCore",
15 | "minVersion": "1.0.0"
16 | }
17 | ]
18 | }
19 | ]
20 | },
21 | {
22 | "id": "SRModCore",
23 | "name": "SRModCore",
24 | "author": "bookdude13",
25 | "description": "Shared library for various SynthRiders mods",
26 | "versions": [
27 | {
28 | "version": "1.0.0",
29 | "downloadUrl": "https://github.com/bookdude13/SRModCore/releases/download/v1.0.0/SRModCore_v1.0.0.zip",
30 | "dependencies": []
31 | }
32 | ]
33 | },
34 | {
35 | "id": "TestMajor",
36 | "name": "Test versions major",
37 | "author": "bookdude13",
38 | "description": "Only major version",
39 | "versions": [
40 | {
41 | "version": "1",
42 | "downloadUrl": "localhost",
43 | "dependencies": []
44 | }
45 | ]
46 | },
47 | {
48 | "id": "TestFull",
49 | "name": "Test versions full",
50 | "author": "bookdude13",
51 | "description": "Full version",
52 | "versions": [
53 | {
54 | "version": "1.2.3",
55 | "downloadUrl": "localhost",
56 | "dependencies": []
57 | }
58 | ]
59 | },
60 | {
61 | "id": "TestAlpha",
62 | "name": "Test versions alpha",
63 | "author": "bookdude13",
64 | "description": "Alpha version",
65 | "versions": [
66 | {
67 | "version": "1.2.3-alpha",
68 | "downloadUrl": "localhost",
69 | "dependencies": []
70 | }
71 | ]
72 | }
73 | ]
74 | }
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/quest.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
58 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/minimize.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
68 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/pc.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
62 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/stop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
72 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
70 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/bracket.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
70 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/maximize.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
72 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/Mods/TestRealFiles.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NoodleManagerX.Models.Mods;
3 | using NoodleManagerX.Mods;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace NoodleManagerXTests.Mods
11 | {
12 | internal class TestRealFiles
13 | {
14 | [Test]
15 | public void Test_LatestVersion_AllAutoSelect_ParsedAndResolved()
16 | {
17 | string rawJson = TestUtils.GetTestJsonFileContents("current_list.json");
18 | ModInfoList? modList = JsonConvert.DeserializeObject(rawJson);
19 | Assert.That(modList, Is.Not.Null);
20 |
21 | var graph = new ModDependencyGraph();
22 | graph.LoadMods(modList.AvailableMods);
23 |
24 | var selections = new List
25 | {
26 | new ModVersionSelection("SRModsList", null),
27 | new ModVersionSelection("Trashbin", null),
28 | new ModVersionSelection("MultiplayerSongGrabber", null),
29 | new ModVersionSelection("CustomSounds", null),
30 | new ModVersionSelection("SRVoting", null),
31 | new ModVersionSelection("SRPerformanceMeter", null),
32 | new ModVersionSelection("SynthRiders-Websockets-Mod", null),
33 | };
34 | graph.Resolve(selections);
35 |
36 | Assert.Multiple(() =>
37 | {
38 | Assert.That(graph.State, Is.EqualTo(ModDependencyGraph.ResolvedState.RESOLVED));
39 | Assert.That(graph.ResolvedVersions, Has.Count.EqualTo(8));
40 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["SRModsList"].Version, "1.0.1");
41 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["Trashbin"].Version, "1.2.0");
42 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["MultiplayerSongGrabber"].Version, "1.0.0");
43 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["CustomSounds"].Version, "1.1.0");
44 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["SRVoting"].Version, "1.2.0");
45 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["SRPerformanceMeter"].Version, "1.2.1");
46 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["SynthRiders-Websockets-Mod"].Version, "1.0.2");
47 | TestUtils.AssertVersionsEqual(graph.ResolvedVersions["SRModCore"].Version, "1.0.0");
48 | });
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/twitch.svg:
--------------------------------------------------------------------------------
1 |
2 |
43 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/blacklist.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
74 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
75 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
77 |
--------------------------------------------------------------------------------
/NoodleManagerX/MessageBox.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Markup.Xaml;
3 | using NoodleManagerX.Models;
4 | using System;
5 | using System.Threading.Tasks;
6 |
7 | namespace MsgBox
8 | {
9 | public class MessageBox : Window
10 | {
11 | public enum MessageBoxButtons
12 | {
13 | Ok,
14 | OkCancel,
15 | YesNo,
16 | YesNoCancel
17 | }
18 |
19 | public enum MessageBoxResult
20 | {
21 | Ok,
22 | Cancel,
23 | Yes,
24 | No
25 | }
26 |
27 | public MessageBox()
28 | {
29 | AvaloniaXamlLoader.Load(this);
30 | }
31 |
32 | public static async Task Show(Window parent, string text, string title, MessageBoxButtons buttons)
33 | {
34 | var msgbox = new MessageBox()
35 | {
36 | Title = title
37 | };
38 | msgbox.FindControl("Text").Text = text;
39 | var buttonPanel = msgbox.FindControl("Buttons");
40 |
41 | var res = MessageBoxResult.Ok;
42 |
43 | void AddButton(string caption, MessageBoxResult r, bool def = false)
44 | {
45 | var btn = new Button { Content = caption };
46 | btn.Click += (_, __) =>
47 | {
48 | res = r;
49 | msgbox.Close();
50 | };
51 | buttonPanel.Children.Add(btn);
52 | if (def)
53 | res = r;
54 | }
55 |
56 | if (buttons == MessageBoxButtons.Ok || buttons == MessageBoxButtons.OkCancel)
57 | AddButton("Ok", MessageBoxResult.Ok, true);
58 | if (buttons == MessageBoxButtons.YesNo || buttons == MessageBoxButtons.YesNoCancel)
59 | {
60 | AddButton("Yes", MessageBoxResult.Yes);
61 | AddButton("No", MessageBoxResult.No, true);
62 | }
63 |
64 | if (buttons == MessageBoxButtons.OkCancel || buttons == MessageBoxButtons.YesNoCancel)
65 | AddButton("Cancel", MessageBoxResult.Cancel, true);
66 |
67 |
68 | var tcs = new TaskCompletionSource();
69 | msgbox.Closed += delegate { tcs.TrySetResult(res); };
70 | try
71 | {
72 | if (parent != null)
73 | {
74 | await msgbox.ShowDialog(parent);
75 | }
76 | else
77 | {
78 | msgbox.Show();
79 | }
80 | }
81 | catch (Exception ex)
82 | {
83 | MainViewModel.Log(ex.Message);
84 | }
85 |
86 | return await tcs.Task;
87 | }
88 |
89 |
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/pagination.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
80 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Playlists/PlaylistHandler.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Threading;
2 | using DynamicData;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Runtime.Serialization;
10 | using System.Threading.Tasks;
11 |
12 | namespace NoodleManagerX.Models.Playlists
13 | {
14 | class PlaylistHandler : GenericHandler
15 | {
16 | public override ItemType itemType { get; set; } = ItemType.Playlist;
17 | public override string join { get; set; } = "items||id";
18 | public override string apiEndpoint { get; set; } = "https://synthriderz.com/api/playlists";
19 | public override string folder { get; set; } = "CustomPlaylists";
20 | public override string[] extensions { get; set; } = { ".playlist" };
21 |
22 | public override dynamic DeserializePage(string json)
23 | {
24 | return JsonConvert.DeserializeObject(json);
25 | }
26 |
27 | public override Task LoadLocalItems()
28 | {
29 | return base.LoadLocalItems();
30 | }
31 |
32 | private PlaylistFile? GetPlaylistFileFromPath(string path)
33 | {
34 | try
35 | {
36 | using Stream stream = StorageAbstraction.ReadFile(path);
37 | using StreamReader contents = new StreamReader(stream);
38 | JsonSerializer serializer = new JsonSerializer();
39 | PlaylistFile playlist = (PlaylistFile)serializer.Deserialize(contents, typeof(PlaylistFile));
40 | return playlist;
41 | }
42 | catch (Exception e) { MainViewModel.Log(MethodBase.GetCurrentMethod(), e); }
43 |
44 | return null;
45 | }
46 |
47 | public override Task GetLocalItem(string path)
48 | {
49 | try
50 | {
51 | if (StorageAbstraction.FileExists(path))
52 | {
53 | PlaylistFile? playlist = GetPlaylistFileFromPath(path);
54 | if (playlist == null)
55 | {
56 | MainViewModel.Log($"Failed to load playlist {path} from file");
57 | return Task.FromResult(false);
58 | }
59 |
60 | var localItem = new LocalItem(-1, playlist.Value.Name, Path.GetFileName(path), StorageAbstraction.GetLastWriteTime(path), itemType);
61 |
62 | MainViewModel.s_instance.localItems.Add(localItem);
63 | return Task.FromResult(true);
64 | }
65 | }
66 | catch (Exception e) { MainViewModel.Log(MethodBase.GetCurrentMethod(), e); }
67 | return Task.FromResult(false);
68 | }
69 | }
70 |
71 | #pragma warning disable 0649
72 | class PlaylistPage : GenericPage
73 | {
74 | public List data;
75 | }
76 | #pragma warning restore 0649
77 |
78 | struct PlaylistNameFile
79 | {
80 | public string Name { get; set; }
81 | public string FilePath { get; set; }
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/NoodleManagerX.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32112.339
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoodleManagerX", "NoodleManagerX\NoodleManagerX.csproj", "{5B4DBD3E-3920-43F7-96B5-00CC7737E702}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UpdateHelper", "UpdateHelper\UpdateHelper.csproj", "{1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoodleManagerXTests", "NoodleManagerXTests\NoodleManagerXTests.csproj", "{54F5ADCB-3C93-41F0-BDC8-2522F0274347}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|x64 = Debug|x64
16 | Release|Any CPU = Release|Any CPU
17 | Release|x64 = Release|x64
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Debug|x64.ActiveCfg = Debug|x64
23 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Debug|x64.Build.0 = Debug|x64
24 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Release|x64.ActiveCfg = Release|x64
27 | {5B4DBD3E-3920-43F7-96B5-00CC7737E702}.Release|x64.Build.0 = Release|x64
28 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Debug|x64.ActiveCfg = Debug|x64
31 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Debug|x64.Build.0 = Debug|x64
32 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Release|x64.ActiveCfg = Release|x64
35 | {1C53E0DB-F28A-4601-B6AE-CFBDE4A24901}.Release|x64.Build.0 = Release|x64
36 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Debug|x64.ActiveCfg = Debug|Any CPU
39 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Debug|x64.Build.0 = Debug|Any CPU
40 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Release|x64.ActiveCfg = Release|Any CPU
43 | {54F5ADCB-3C93-41F0-BDC8-2522F0274347}.Release|x64.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {B9C91276-92F7-4161-8EB5-1848234BF357}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/UpdateHelper/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Security.Principal;
6 | using System.Threading;
7 | using static System.Environment;
8 |
9 | namespace UpdateHelper
10 | {
11 | internal class Program
12 | {
13 | static void Main(string[] args)
14 | {
15 | try
16 | {
17 | if (args.Length >= 1)
18 | {
19 | bool isElevated;
20 | using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
21 | {
22 | WindowsPrincipal principal = new WindowsPrincipal(identity);
23 | isElevated = principal.IsInRole(WindowsBuiltInRole.Administrator);
24 | }
25 | if (isElevated) Log("Running as administrator");
26 | else Log("Running as user");
27 |
28 | string targetPath = args[0].Trim('\"');
29 |
30 | string tmpPath = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "NoodleManagerX.exe");
31 | Log("Updating from " + tmpPath + " to " + targetPath);
32 |
33 | bool canWrite = false;
34 | int attempts = 0;
35 | while (!canWrite && attempts < 20)
36 | {
37 | using (var fs = new FileStream(targetPath, FileMode.Open))
38 | {
39 | canWrite = fs.CanWrite;
40 | }
41 | if (!canWrite)
42 | {
43 | Thread.Sleep(200);
44 | attempts++;
45 | }
46 | }
47 |
48 | File.Move(tmpPath, targetPath, true);
49 |
50 | Process proc = new Process();
51 | proc.StartInfo.FileName = targetPath;
52 | proc.StartInfo.UseShellExecute = true;
53 | proc.Start();
54 |
55 | Log("Updated successful");
56 | }
57 | }
58 | catch (Exception e) { Log(MethodBase.GetCurrentMethod(), e); }
59 | }
60 |
61 | public static void Log(MethodBase m, Exception e)
62 | {
63 | Log("Error " + m.Name + " " + e.Message + Environment.NewLine + e.TargetSite + Environment.NewLine + e.StackTrace);
64 | }
65 |
66 | public static void Log(string message)
67 | {
68 | try
69 | {
70 | Console.WriteLine(message);
71 |
72 | string directory = Path.Combine(Environment.GetFolderPath(SpecialFolder.ApplicationData), "NoodleManagerX");
73 | if (!Directory.Exists(directory))
74 | {
75 | Directory.CreateDirectory(directory);
76 | }
77 |
78 | using (StreamWriter sw = File.AppendText(Path.Combine(directory, "UpdateLog.txt")))
79 | {
80 | sw.Write(DateTime.Now.ToString("dd'.'MM HH':'mm':'ss") + " " + message + Environment.NewLine);
81 | }
82 | }
83 | catch { }
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/MtpDevice.cs:
--------------------------------------------------------------------------------
1 | using MediaDevices;
2 | using System;
3 | using System.Linq;
4 | using Path = System.IO.Path;
5 |
6 | namespace NoodleManagerX.Models
7 | {
8 | static class MtpDevice
9 | {
10 | public static MediaDevice device;
11 | public static string path = "";
12 | public static bool connected = false;
13 |
14 | public static void Connect(bool isCommand = false)
15 | {
16 | if (!connected)
17 | {
18 | var devices = MediaDevice.GetDevices();
19 | MainViewModel.Log(devices.Count() + " mtp devices connected");
20 | foreach (MediaDevice d in devices)
21 | {
22 | try
23 | {
24 | d.Connect();
25 | string[] directories = d.GetDirectories(@"\");
26 | foreach (string directory in directories)
27 | {
28 | string dir = Path.Combine(directory, "SynthRidersUC");
29 | if (d.DirectoryExists(dir))
30 | {
31 | MainViewModel.Log("Synth Riders device found " + d.FriendlyName);
32 | device = d;
33 | path = dir;
34 | connected = true;
35 | MainViewModel.s_instance.questConnected = true;
36 | d.DeviceRemoved += DeviceRemoved;
37 | if (isCommand) MainViewModel.s_instance.ReloadLocalSources();
38 | return;
39 | }
40 | }
41 | d.Disconnect();
42 | }
43 | catch { }
44 | }
45 | if (isCommand)
46 | {
47 | if (devices.Count() > 0)
48 | {
49 | MainViewModel.s_instance.OpenErrorDialog("Found " + devices.Count() + " MTP devices but none of them had the SynthRidersUC folder:" + Environment.NewLine + String.Join(Environment.NewLine, devices.Select(x => x.FriendlyName + " (" + x.Description + ")")) + Environment.NewLine + "If your device is not listed above make sure to allow storage access on the headset");
50 | }
51 | else
52 | {
53 | MainViewModel.s_instance.OpenErrorDialog("No MTP devices found" + Environment.NewLine + "Make sure to allow storage access on the headset");
54 | }
55 | }
56 | }
57 | }
58 |
59 | private static void DeviceRemoved(object sender, MediaDeviceEventArgs e)
60 | {
61 | Disconnected();
62 | }
63 |
64 | private static void Disconnected()
65 | {
66 | MainViewModel.Log("Quest disconnected");
67 | device.DeviceRemoved -= DeviceRemoved;
68 | connected = false;
69 | MainViewModel.s_instance.questConnected = false;
70 | MainViewModel.s_instance.ReloadLocalSources();
71 | }
72 |
73 | public static void Disconnect()
74 | {
75 | if (connected)
76 | {
77 | device.Disconnect();
78 | if (!MainViewModel.s_instance.closing) Disconnected();
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
70 |
--------------------------------------------------------------------------------
/NoodleManagerX/NoodleManagerX.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 |
6 |
7 | WinExe
8 |
9 |
10 |
11 | net6.0-windows
12 | icon-nm.ico
13 | en
14 | true
15 | true
16 | link
17 | embedded
18 | AnyCPU;x64
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | True
55 | True
56 | Resources.resx
57 |
58 |
59 |
60 |
61 | ResXFileCodeGenerator
62 | Resources.Designer.cs
63 |
64 |
65 |
66 |
77 |
78 |
--------------------------------------------------------------------------------
/NoodleManagerX/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Data.Converters;
4 | using Avalonia.Input;
5 | using Avalonia.Input.Raw;
6 | using Avalonia.Markup.Xaml;
7 | using Avalonia.Media;
8 | using NoodleManagerX.Models;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Globalization;
12 | using System.Linq;
13 |
14 | namespace NoodleManagerX
15 | {
16 | public class MainWindow : Window
17 | {
18 | public static MainWindow s_instance;
19 | public static BrushConverter BrushConverter = new BrushConverter();
20 |
21 | private Grid blackBar;
22 | private bool lastleftclick = false;
23 | private bool lastHandled = false;
24 | private PixelPoint lastposition;
25 |
26 | public MainWindow()
27 | {
28 | s_instance = this;
29 |
30 | this.DataContext = new MainViewModel();
31 |
32 | InitializeComponent();
33 |
34 | blackBar = this.FindControl("BlackBar");
35 |
36 | TextBox searchBox = this.FindControl("SearchBox");
37 | searchBox.KeyDown += SearchBoxKeyEvent;
38 |
39 | #if DEBUG
40 | this.AttachDevTools();
41 | #endif
42 | #pragma warning disable 0618
43 | Application.Current.InputManager.Process.Subscribe(x =>
44 | {
45 | if (x is RawPointerEventArgs rawpointerevent)
46 | {
47 | MouseDevice mouse = (MouseDevice)x.Device;
48 | bool leftclick = rawpointerevent.InputModifiers == RawInputModifiers.LeftMouseButton;
49 |
50 | //Point mouseonclient = this.PointToClient(mouse.Position);
51 |
52 | if (!lastleftclick && leftclick && blackBar.IsPointerOver)//rising edge
53 | {
54 | lastleftclick = true;
55 | lastposition = mouse.Position;
56 | lastHandled = x.Handled;
57 | mouse = (MouseDevice)x.Device;
58 | }
59 | else if (lastleftclick && leftclick)//hold
60 | {
61 | if (!lastHandled)
62 | {
63 | PixelPoint p = new PixelPoint(this.Position.X + mouse.Position.X - lastposition.X, this.Position.Y + mouse.Position.Y - (int)lastposition.Y);
64 | lastposition = mouse.Position;
65 | this.Position = p;
66 | }
67 | }
68 | else//falling edge
69 | {
70 | lastleftclick = false;
71 | lastHandled = false;
72 | }
73 | }
74 | });
75 | #pragma warning disable 0618
76 | }
77 |
78 | private void SearchBoxKeyEvent(object sender, KeyEventArgs e)
79 | {
80 | if (e.Key.Equals(Key.Enter))
81 | {
82 | ((MainViewModel)this.DataContext).GetPage();
83 | }
84 | }
85 |
86 | private void InitializeComponent()
87 | {
88 | AvaloniaXamlLoader.Load(this);
89 | }
90 |
91 | public static Brush GetBrush(string colorResource)
92 | {
93 | try
94 | {
95 | return s_instance.Resources[colorResource] as Brush;
96 | }
97 | catch { }
98 |
99 | return (Brush)BrushConverter.ConvertFromString("#ff00ff");
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/PlaybackHandler.cs:
--------------------------------------------------------------------------------
1 | using NAudio.Wave;
2 | using System;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 | using YoutubeExplode;
8 |
9 |
10 | namespace NoodleManagerX.Models
11 | {
12 | static class PlaybackHandler
13 | {
14 | public static MapItem currentlyPlaying;
15 | private static YoutubeClient youtubeClient = new YoutubeClient();
16 | private const string regex = @"^.*((youtu\.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*";
17 | private static WaveOutEvent waveout;
18 |
19 | public static void Play(MapItem item)
20 | {
21 | Task.Run(async () =>
22 | {
23 | if (currentlyPlaying == item)
24 | {
25 | Stop();
26 | return;
27 | }
28 | string url = item.youtube_url;
29 | if (!String.IsNullOrEmpty(url))
30 | {
31 | GroupCollection matches = Regex.Match(url, regex).Groups;
32 | string id = matches[matches.Count - 1].Value;
33 |
34 | if (currentlyPlaying != null) Stop();
35 | currentlyPlaying = item;
36 | item.playing = true;
37 | MapItem playing = currentlyPlaying;
38 |
39 | try
40 | {
41 | var streamManifest = await youtubeClient.Videos.Streams.GetManifestAsync(id);
42 |
43 | var streamInfo = streamManifest.GetAudioOnlyStreams().OrderBy(x => x.Bitrate).FirstOrDefault();
44 |
45 | using (var mf = new MediaFoundationReader(streamInfo.Url))
46 | using (waveout = new WaveOutEvent())
47 | {
48 | waveout.Init(mf);
49 | SetVolume(MainViewModel.s_instance.previewVolume);
50 | waveout.Play();
51 | while (waveout.PlaybackState == PlaybackState.Playing)
52 | {
53 | await Task.Delay(100);
54 | }
55 | }
56 | StopPlaying(playing);
57 | }
58 | catch (Exception e)
59 | {
60 | MainViewModel.Log(MethodBase.GetCurrentMethod(), e);
61 | Stop();
62 | }
63 | }
64 | });
65 | }
66 |
67 | public static void SetVolume(int volume)
68 | {
69 | if (waveout != null)
70 | {
71 | waveout.Volume = volume / 100f;
72 | }
73 | }
74 |
75 | public static void Stop()
76 | {
77 |
78 | try
79 | {
80 | if (waveout != null)
81 | {
82 | waveout.Stop();
83 | }
84 | }
85 | catch (Exception e)
86 | {
87 | MainViewModel.Log(MethodBase.GetCurrentMethod(), e);
88 | }
89 | finally
90 | {
91 | StopPlaying(currentlyPlaying);
92 | }
93 | }
94 |
95 | private static void StopPlaying(MapItem playing)
96 | {
97 | if (playing != null) playing.playing = false;
98 | if (currentlyPlaying == playing)
99 | {
100 | currentlyPlaying = null;
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/NoodleManagerX/Resources/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace NoodleManagerX.Resources {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NoodleManagerX.Resources.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Byte[].
65 | ///
66 | internal static byte[] classdata {
67 | get {
68 | object obj = ResourceManager.GetObject("classdata", resourceCulture);
69 | return ((byte[])(obj));
70 | }
71 | }
72 |
73 | ///
74 | /// Looks up a localized resource of type System.Byte[].
75 | ///
76 | internal static byte[] classdata_large {
77 | get {
78 | object obj = ResourceManager.GetObject("classdata_large", resourceCulture);
79 | return ((byte[])(obj));
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/github.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/LocalItem.cs:
--------------------------------------------------------------------------------
1 | using NoodleManagerX.Models.Mods;
2 | using NoodleManagerX.Models.Stages;
3 | using NoodleManagerX.Mods;
4 | using Semver;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace NoodleManagerX.Models
13 | {
14 | public class LocalItem
15 | {
16 | public LocalItem(int id, string hash, string filename, DateTime modifiedTime, ItemType itemType)
17 | {
18 | this.id = id;
19 | this.hash = hash;
20 | this.filename = filename;
21 | this.modifiedTime = modifiedTime;
22 | this.itemType = itemType;
23 | }
24 |
25 |
26 | public int id = -1;
27 | public string hash = "";
28 | public string filename = "";
29 | public DateTime modifiedTime = new DateTime();
30 | public ItemType itemType = ItemType.init;
31 |
32 | public SemVersion ItemVersion
33 | {
34 | get
35 | {
36 | if (String.IsNullOrEmpty(filename))
37 | {
38 | return null;
39 | }
40 |
41 | if (filename.StartsWith(hash + "_") && filename.EndsWith(".synthmod"))
42 | {
43 | var versionStr = filename.Substring(hash.Length + 1, filename.Length - hash.Length - 1 - ".synthmod".Length);
44 | try
45 | {
46 | return SemVersion.Parse(versionStr, SemVersionStyles.Any);
47 | }
48 | catch (Exception ex)
49 | {
50 | MainViewModel.Log($"Failed to parse version '{versionStr}' for mod {hash}");
51 | }
52 | }
53 | return null;
54 | }
55 | }
56 |
57 | public bool CheckEquality(GenericItem item, bool checkHash = false)
58 | {
59 | if (item != null && itemType == item.itemType)
60 | {
61 | if (itemType == ItemType.Map && id != -1)
62 | {
63 | return this.id == item.id && (!checkHash || hash == ((MapItem)item).hash);
64 | }
65 | else if (itemType == ItemType.Mod && id != -1)
66 | {
67 | // id isn't set for mods, so always compare with hash (which is set to the modinfo id)
68 | // Version is located within filename. Filename is always {modId}_{version}.synthmod
69 | var modItem = (ModItem)item;
70 | var isSameVersion = ItemVersion == null ||
71 | modItem.InstalledVersion?.Version == null ||
72 | ItemVersion.ComparePrecedenceTo(modItem.InstalledVersion?.Version) == 0;
73 | return hash == modItem.ModInfo?.Id && isSameVersion;
74 | }
75 | else if (itemType == ItemType.Playlist)
76 | {
77 | // The hash is set to the playlistName from within the playlist file.
78 | // This matches the GenericItem.name field when pulling from the Z site
79 | return hash == item.name;
80 | }
81 | else if (itemType == ItemType.Stage)
82 | {
83 | // Consider them equal if the local filename matches _any_ of the stage's files
84 | var stageItem = (StageItem)item;
85 | return stageItem.Files.Where(fileInfo => fileInfo.File.Filename == filename).Any();
86 | }
87 | else
88 | {
89 | return filename == item.filename;
90 | }
91 | }
92 | return false;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/NoodleManagerXTests/Mods/json/current_list.json:
--------------------------------------------------------------------------------
1 | {
2 | "availableMods": [
3 | {
4 | "id": "SRModsList",
5 | "name": "SRModsList",
6 | "author": "Mondanzo",
7 | "description": "Add a new button to the main menu to open a mod list",
8 | "versions": [
9 | {
10 | "version": "1.0.1",
11 | "downloadUrl": "https://github.com/Mondanzo/SRModsList/releases/download/1.0.1/SRModsList.dll",
12 | "dependencies": []
13 | }
14 | ]
15 | },
16 | {
17 | "id": "Trashbin",
18 | "name": "Trashbin",
19 | "author": "Goinn",
20 | "description": "Adds a button to delete songs for Synth Riders [PCVR]. Deleted songs will be added to NoodleManagerX's blacklist",
21 | "versions": [
22 | {
23 | "version": "1.2.0",
24 | "downloadUrl": "https://github.com/Goinn/Trashbin/releases/download/V1.2.0/Trashbin.dll",
25 | "dependencies": []
26 | }
27 | ]
28 | },
29 | {
30 | "id": "MultiplayerSongGrabber",
31 | "name": "MultiplayerSongGrabber",
32 | "author": "Goinn",
33 | "description": "Download missing custom songs with the click of a button inside a multiplayer lobby! For Synth Riders [PCVR]",
34 | "versions": [
35 | {
36 | "version": "1.0.0",
37 | "downloadUrl": "https://github.com/Goinn/MultiplayerSongGrabber/releases/download/V1.0.0/MultiplayerSongGrabber.dll",
38 | "dependencies": []
39 | }
40 | ]
41 | },
42 | {
43 | "id": "CustomSounds",
44 | "name": "CustomSounds",
45 | "author": "Goinn",
46 | "description": "Allows you to customize most sounds from Synth Riders [PCVR]",
47 | "versions": [
48 | {
49 | "version": "1.1.0",
50 | "downloadUrl": "https://github.com/Goinn/CustomSounds/releases/download/v1.1.0/CustomSound.dll",
51 | "dependencies": []
52 | }
53 | ]
54 | },
55 | {
56 | "id": "SRModCore",
57 | "name": "SRModCore",
58 | "author": "bookdude13",
59 | "description": "Shared library used by various SynthRiders mods",
60 | "versions": [
61 | {
62 | "version": "1.0.0",
63 | "downloadUrl": "https://github.com/bookdude13/SRModCore/releases/download/v1.0.0/SRModCore_v1.0.0.zip",
64 | "dependencies": []
65 | }
66 | ]
67 | },
68 | {
69 | "id": "SRVoting",
70 | "name": "SRVoting",
71 | "author": "bookdude13",
72 | "description": "In-game up/down voting UI for custom songs",
73 | "versions": [
74 | {
75 | "version": "1.2.0",
76 | "downloadUrl": "https://github.com/bookdude13/SRVoting/releases/download/v1.2.0/SRVoting_v1.2.0.zip",
77 | "dependencies": [
78 | {
79 | "id": "SRModCore",
80 | "minVersion": "1.0.0"
81 | }
82 | ]
83 | }
84 | ]
85 | },
86 | {
87 | "id": "SRPerformanceMeter",
88 | "name": "PerformanceMeter",
89 | "author": "bookdude13",
90 | "description": "View additional playthrough statistics/info in-game after each song",
91 | "versions": [
92 | {
93 | "version": "1.2.1",
94 | "downloadUrl": "https://github.com/bookdude13/SRPerformanceMeter/releases/download/v1.2.1/PerformanceMeter_v1.2.1.zip",
95 | "dependencies": [
96 | {
97 | "id": "SynthRiders-Websockets-Mod",
98 | "minVersion": "1.0.2"
99 | }
100 | ]
101 | }
102 | ]
103 | },
104 | {
105 | "id": "SynthRiders-Websockets-Mod",
106 | "name": "SynthRiders-Websockets-Mod",
107 | "author": "KK964",
108 | "description": "Emits game events through websockets",
109 | "versions": [
110 | {
111 | "version": "1.0.2",
112 | "downloadUrl": "https://github.com/KK964/SynthRiders-Websockets-Mod/releases/download/v1.0.2/SynthRidersWebsockets.zip",
113 | "dependencies": []
114 | }
115 | ]
116 | }
117 | ]
118 | }
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Icons/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
40 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/DownloadScheduler.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Threading;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Collections.Specialized;
6 | using System.Threading.Tasks;
7 |
8 | namespace NoodleManagerX.Models
9 | {
10 | static class DownloadScheduler
11 | {
12 | public static ObservableCollection queue = new ObservableCollection();
13 | public static ObservableCollection downloading = new ObservableCollection();
14 |
15 | public const int downloadTasks = 4;
16 | public const int maxDownloadAttempts = 3;
17 | public static int toDownload = 0;
18 |
19 | private static volatile bool running = false;
20 |
21 | static DownloadScheduler()
22 | {
23 | queue.CollectionChanged += QueueChanged;
24 | }
25 |
26 | public static void Download(GenericItem item)
27 | {
28 | var supportedDownloadTypes = new HashSet { ItemType.Map, ItemType.Mod, ItemType.Playlist, ItemType.Stage, ItemType.Avatar };
29 | if (supportedDownloadTypes.Contains(item.itemType) && !item.blacklisted)
30 | {
31 | toDownload++;
32 | item.downloadAttempts = 1;
33 | queue.Add(item);
34 | }
35 | else
36 | {
37 | Console.WriteLine("Not queueing item since it's not a map, playlist, stage or mod");
38 | }
39 | }
40 |
41 | public static void Requeue(GenericItem item)
42 | {
43 | if (item.downloadAttempts < maxDownloadAttempts)
44 | {
45 | MainViewModel.Log("Requeueing " + item.filename);
46 | item.downloadAttempts++;
47 | queue.Add(item);
48 | }
49 | else
50 | {
51 | MainViewModel.Log("Timeout " + item.filename);
52 | }
53 | }
54 |
55 | public static void Remove(GenericItem item)
56 | {
57 | downloading.Remove(item);
58 | if (downloading.Count == 0 && !MainViewModel.s_instance.closing)//if the database is saved while closing it gets corrupted
59 | {
60 | _ = MainViewModel.s_instance.SaveLocalItems();
61 | _ = Dispatcher.UIThread.InvokeAsync(() =>
62 | {
63 | MainViewModel.s_instance.progress = 0;
64 | MainViewModel.s_instance.progressText = null;
65 | });
66 | }
67 | else
68 | {
69 | _ = Dispatcher.UIThread.InvokeAsync(() =>
70 | {
71 | MainViewModel.s_instance.progress = Math.Min(100 - (int)((queue.Count + downloading.Count) / (toDownload * 0.01f)), 1);
72 | MainViewModel.s_instance.progressText = "Downloading: " + MainViewModel.s_instance.progress + "% [" + downloading.Count + 1 + "](" + (toDownload - queue.Count) + "/" + toDownload + ")";
73 | });
74 | }
75 | }
76 |
77 | private static void QueueChanged(object sender, NotifyCollectionChangedEventArgs e)
78 | {
79 | Task.Run(async () =>
80 | {
81 | if (queue.Count > 0 && !running)
82 | {
83 | running = true;
84 | await Task.Delay(100);//not sure why this is necessary for requeueing. Seems like queuecount is !=0 but the queue is empty?
85 | MainViewModel.Log("Started queue check");
86 | while (queue.Count > 0)
87 | {
88 | if (downloading.Count < downloadTasks)
89 | {
90 | if (queue[0] != null && !queue[0].downloading)
91 | {
92 | _ = Task.Run(queue[0].Download);
93 | downloading.Add(queue[0]);
94 | }
95 | queue.Remove(queue[0]);
96 | }
97 | else
98 | {
99 | await Task.Delay(500);
100 | }
101 | }
102 | running = false;
103 | MainViewModel.Log("Stopped queue check");
104 | }
105 | });
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/NoodleManagerX/ThirdParty/MelonLoader/UnityInformationHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Runtime.InteropServices;
5 | using System.Runtime.CompilerServices;
6 | using System.Linq;
7 | using AssetsTools.NET;
8 | using AssetsTools.NET.Extra;
9 | using NoodleManagerX.Models;
10 |
11 | namespace NoodleManagerX.ThirdParty.MelonLoader
12 | {
13 | // Taken from https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader/InternalUtils/UnityInformationHandler.cs
14 | // Uses the Apache 2.0 License
15 | // Unnecessary checks and fallback data retrieval logic removed
16 | public static class UnityInformationHandler
17 | {
18 | public static string GameName { get; private set; } = "UNKNOWN";
19 | public static string GameDeveloper { get; private set; } = "UNKNOWN";
20 | public static string GameVersion { get; private set; } = "0";
21 |
22 | internal static void Setup(string gameDataDirectory)
23 | {
24 | //AssetsManager assetsManager = new AssetsManager();
25 | //ReadGameInfo(gameDataDirectory, assetsManager);
26 | //assetsManager.UnloadAll();
27 | }
28 |
29 | private static void ReadGameInfo(string gameDataDirectory, AssetsManager assetsManager)
30 | {
31 | AssetsFileInstance instance = null;
32 | try
33 | {
34 | string bundlePath = Path.Combine(gameDataDirectory, "globalgamemanagers");
35 | if (!File.Exists(bundlePath))
36 | {
37 | MainViewModel.Log("Couldn't find globalgamemanagers file for game version");
38 | return;
39 | }
40 |
41 | instance = assetsManager.LoadAssetsFile(bundlePath, true);
42 | if (instance == null)
43 | {
44 | MainViewModel.Log("Couldn't load assets file");
45 | return;
46 | }
47 |
48 | assetsManager.LoadIncludedClassPackage();
49 | if (!instance.file.typeTree.hasTypeTree)
50 | {
51 | assetsManager.LoadClassDatabaseFromPackage(instance.file.typeTree.unityVersion);
52 | }
53 |
54 | List assetFiles = instance.table.GetAssetsOfType(129);
55 | if (assetFiles.Count > 0)
56 | {
57 | AssetFileInfoEx playerSettings = assetFiles.First();
58 |
59 | AssetTypeInstance assetTypeInstance = null;
60 | try
61 | {
62 | assetTypeInstance = assetsManager.GetTypeInstance(instance, playerSettings);
63 | }
64 | catch (Exception ex)
65 | {
66 | assetsManager.LoadIncludedLargeClassPackage();
67 | assetsManager.LoadClassDatabaseFromPackage(instance.file.typeTree.unityVersion);
68 | assetTypeInstance = assetsManager.GetTypeInstance(instance, playerSettings);
69 | }
70 |
71 | if (assetTypeInstance != null)
72 | {
73 | AssetTypeValueField playerSettings_baseField = assetTypeInstance.GetBaseField();
74 |
75 | AssetTypeValueField bundleVersion = playerSettings_baseField.Get("bundleVersion");
76 | if (bundleVersion != null)
77 | {
78 | GameVersion = bundleVersion.GetValue().AsString();
79 | }
80 |
81 | AssetTypeValueField companyName = playerSettings_baseField.Get("companyName");
82 | if (companyName != null)
83 | {
84 | GameDeveloper = companyName.GetValue().AsString();
85 | }
86 |
87 | AssetTypeValueField productName = playerSettings_baseField.Get("productName");
88 | if (productName != null)
89 | {
90 | GameName = productName.GetValue().AsString();
91 | }
92 | }
93 | }
94 | }
95 | catch (Exception ex)
96 | {
97 | MainViewModel.Log("Failed to Initialize Assets Manager! " + ex.Message);
98 | }
99 |
100 | if (instance != null)
101 | {
102 | instance.file.Close();
103 | }
104 | }
105 |
106 | public static ClassDatabasePackage LoadIncludedClassPackage(this AssetsManager assetsManager)
107 | {
108 | ClassDatabasePackage classPackage = null;
109 | using (MemoryStream mstream = new MemoryStream(Resources.Resources.classdata))
110 | classPackage = assetsManager.LoadClassPackage(mstream);
111 | return classPackage;
112 | }
113 |
114 | public static ClassDatabasePackage LoadIncludedLargeClassPackage(this AssetsManager assetsManager)
115 | {
116 | ClassDatabasePackage classPackage = null;
117 | using (MemoryStream mstream = new MemoryStream(Resources.Resources.classdata_large))
118 | classPackage = assetsManager.LoadClassPackage(mstream);
119 | return classPackage;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/Included/searchbar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
168 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/Stages/StageItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Mime;
6 | using System.Net;
7 | using System.Reflection;
8 | using System.Runtime.Serialization;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Web;
12 | using NoodleManagerX.Models.Mods;
13 | using System.Net.Http;
14 | using Newtonsoft.Json;
15 | using Avalonia.Threading;
16 |
17 | namespace NoodleManagerX.Models.Stages
18 | {
19 | [DataContract]
20 | class StageItem : GenericItem
21 | {
22 | public override string target { get; set; } = "CustomStages";
23 | public override ItemType itemType { get; set; } = ItemType.Stage;
24 |
25 | [DataMember]
26 | [JsonProperty("files")]
27 | public List Files { get; set; } = new List();
28 |
29 | [DataMember]
30 | [JsonProperty("experience_beatmap")]
31 | public MapItem ExperienceBeatmap = null;
32 |
33 | public List GetDownloadFilesForCurrentDevice()
34 | {
35 | return Files
36 | .Where(file => MtpDevice.connected ? file.IsQuest() : file.IsPc())
37 | .Select(file => file.File)
38 | .ToList();
39 | }
40 |
41 | public override void Delete()
42 | {
43 | bool deletedAll = true;
44 | var deviceFiles = GetDownloadFilesForCurrentDevice();
45 | deviceFiles.ForEach(stageFile => deletedAll = Delete(stageFile.Filename) && deletedAll);
46 | if (deletedAll)
47 | {
48 | _ = Dispatcher.UIThread.InvokeAsync(() =>
49 | {
50 | downloaded = false;
51 | });
52 | }
53 | }
54 |
55 | public override async Task UpdateDownloaded(bool forceUpdate = false)
56 | {
57 | var numDownloaded = 0;
58 | var expectedFiles = GetDownloadFilesForCurrentDevice();
59 | foreach (var file in expectedFiles)
60 | {
61 | List matchingLocalItems = MainViewModel.s_instance.localItems
62 | .Where(x => x != null && x.filename == file.Filename)
63 | .ToList();
64 | matchingLocalItems.Sort();
65 |
66 | if (matchingLocalItems.Count() > 0)
67 | {
68 | if (matchingLocalItems.Count() > 1)
69 | {
70 | matchingLocalItems = matchingLocalItems.Where(x => x.itemType == itemType).OrderByDescending(x => x.modifiedTime).ToList();
71 | foreach (LocalItem l in matchingLocalItems.Skip(1))
72 | {
73 | MainViewModel.Log("Old version deleted of " + l.filename);
74 | _ = Delete(l.filename);
75 | }
76 | }
77 |
78 | numDownloaded++;
79 |
80 | filename = matchingLocalItems[0].filename;
81 | needsUpdate = DateTime.Compare(updatedAt, matchingLocalItems[0].modifiedTime) > 0;
82 | if (needsUpdate) MainViewModel.Log("Date difference detected for " + this.filename);
83 | }
84 | }
85 |
86 | if (expectedFiles.Count == 0)
87 | {
88 | visible = false;
89 | }
90 | if (numDownloaded == expectedFiles.Count)
91 | {
92 | await Dispatcher.UIThread.InvokeAsync(() =>
93 | {
94 | downloaded = true;
95 | });
96 | }
97 | else
98 | {
99 | await Dispatcher.UIThread.InvokeAsync(() =>
100 | {
101 | downloaded = false;
102 | needsUpdate = false;
103 | });
104 | }
105 |
106 | if (needsUpdate || forceUpdate)
107 | {
108 | DownloadScheduler.Download(this);
109 | }
110 | }
111 |
112 | public override void UpdateBlacklisted()
113 | {
114 | var platformFiles = GetDownloadFilesForCurrentDevice();
115 | foreach (var file in platformFiles)
116 | {
117 | if (!String.IsNullOrEmpty(file.Filename) && MainViewModel.s_instance.blacklist.Contains(file.Filename))
118 | {
119 | _ = Dispatcher.UIThread.InvokeAsync(() =>
120 | {
121 | blacklisted = true;
122 | });
123 | return;
124 | }
125 | }
126 |
127 | // Not found in blacklist
128 | _ = Dispatcher.UIThread.InvokeAsync(() =>
129 | {
130 | blacklisted = false;
131 | });
132 | }
133 |
134 | protected async override Task RawDownloadAndSave()
135 | {
136 | var path = "";
137 |
138 | // Try to download all modes of this stage.
139 | // Use the first mode in the list as the identifier (path)
140 |
141 | var downloadFiles = GetDownloadFilesForCurrentDevice();
142 | foreach (StageFile stageFile in downloadFiles)
143 | {
144 | try
145 | {
146 | string url = stageFile.Url;
147 | if (url == null)
148 | {
149 | url = "https://synthriderz.com" + download_url + "?file_id=" + stageFile.Id;
150 | }
151 |
152 | using HttpClient client = new HttpClient();
153 | using var rawResponse = await client.GetStreamAsync(url);
154 | using MemoryStream str = await CopyStreamToMemoryStream(rawResponse);
155 | if (String.IsNullOrEmpty(filename))
156 | {
157 | filename = stageFile.Filename;
158 | }
159 |
160 | path = Path.Combine(target, stageFile.Filename);
161 | await StorageAbstraction.WriteFile(str, path);
162 | }
163 | catch (Exception e)
164 | {
165 | MainViewModel.Log(MethodBase.GetCurrentMethod(), e);
166 | }
167 | }
168 |
169 | return path;
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/NoodleManagerX/Resources/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | classdata.tpk;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
123 |
124 |
125 | classdata_large.tpk;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
126 |
127 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/MapHandler.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using ReactiveUI;
4 | using ReactiveUI.Fody.Helpers;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO.Compression;
8 | using System.Reactive;
9 | using System.Reflection;
10 | using System.Runtime.Serialization;
11 | using System.Threading.Tasks;
12 | using MemoryStream = System.IO.MemoryStream;
13 | using Path = System.IO.Path;
14 | using Stream = System.IO.Stream;
15 |
16 | namespace NoodleManagerX.Models
17 | {
18 | class MapHandler : GenericHandler
19 | {
20 | public override ItemType itemType { get; set; } = ItemType.Map;
21 | public override Dictionary queryFields { get; set; } = new Dictionary() { { "text_search", "$tsQuery" }, { "user.username", "$contL" } };
22 | public override string select { get; set; } = "title,artist,mapper,duration,difficulties,hash,youtube_url,beat_saber_convert";
23 | public override string selectDownload { get; set; } = "hash,beat_saber_convert";
24 | public override string apiEndpoint { get; set; } = "https://synthriderz.com/api/beatmaps";
25 | public override string folder { get; set; } = "CustomSongs";
26 | public override string[] extensions { get; set; } = { ".synth" };
27 |
28 | public override async Task GetLocalItem(string path)
29 | {
30 | try
31 | {
32 | if (StorageAbstraction.FileExists(path))
33 | {
34 | using (Stream stream = StorageAbstraction.ReadFile(path))
35 | using (ZipArchive archive = new ZipArchive(stream))
36 | {
37 | foreach (ZipArchiveEntry entry in archive.Entries)
38 | {
39 | if (entry.FullName == "synthriderz.meta.json")
40 | {
41 | using (System.IO.StreamReader sr = new System.IO.StreamReader(entry.Open()))
42 | {
43 | LocalItem localItem = JsonConvert.DeserializeObject(await sr.ReadToEndAsync());
44 | localItem.filename = Path.GetFileName(path);
45 | localItem.modifiedTime = StorageAbstraction.GetLastWriteTime(path);
46 | localItem.itemType = ItemType.Map;
47 | MainViewModel.s_instance.localItems.Add(localItem);
48 | return true;
49 | }
50 | }
51 | }
52 |
53 | if (MainViewModel.s_instance.pruning)
54 | {
55 | MainViewModel.Log("Deleting old file without metadata " + Path.GetFileName(path));
56 | StorageAbstraction.DeleteFile(path);
57 | }
58 | }
59 | }
60 | }
61 | catch (Exception e)
62 | {
63 | MainViewModel.Log(MethodBase.GetCurrentMethod(), e);
64 | MainViewModel.Log("Deleting corrupted file " + Path.GetFileName(path));
65 | try { StorageAbstraction.DeleteFile(path); }
66 | catch (Exception ee) { MainViewModel.Log(MethodBase.GetCurrentMethod(), ee); }
67 | }
68 |
69 | return false;
70 | }
71 |
72 | public override dynamic DeserializePage(string json)
73 | {
74 | return JsonConvert.DeserializeObject(json);
75 | }
76 | }
77 |
78 | [DataContract]
79 | class MapItem : GenericItem
80 | {
81 | public override string target { get; set; } = "CustomSongs";
82 | public override ItemType itemType { get; set; } = ItemType.Map;
83 |
84 | [DataMember] public string title { get; set; }
85 | [DataMember] public string artist { get; set; }
86 | [DataMember] public string mapper { get; set; }
87 | [DataMember] public string duration { get; set; }
88 | [DataMember] public string[] difficulties { get; set; }
89 | [DataMember] public string hash { get; set; }
90 | [DataMember] public string youtube_url { get; set; }
91 | [Reactive] public bool playing { get; set; } = false;
92 | public ReactiveCommand playPreviewCommand { get; set; }
93 |
94 | public override string display_title
95 | {
96 | get { return title; }
97 | }
98 | public override string display_creator
99 | {
100 | get { return mapper; }
101 | }
102 | public override string display_preview
103 | {
104 | get { return youtube_url; }
105 | }
106 | public override string[] display_difficulties
107 | {
108 | get { return difficulties; }
109 | }
110 |
111 | [OnDeserialized]
112 | private void OnDeserializedMethod(StreamingContext context)
113 | {
114 | if (filename != null && PlaybackHandler.currentlyPlaying?.filename == filename)
115 | {
116 | playing = PlaybackHandler.currentlyPlaying.playing;
117 | PlaybackHandler.currentlyPlaying = this;
118 | }
119 |
120 | playPreviewCommand = ReactiveCommand.Create((() =>
121 | {
122 | PlaybackHandler.Play(this);
123 | }));
124 | }
125 |
126 | public override async Task CopyStreamToMemoryStream(Stream stream, bool closeOriginal = true)
127 | {
128 | MemoryStream ms = await base.CopyStreamToMemoryStream(stream);
129 |
130 | using (ZipArchive archive = new ZipArchive(ms, ZipArchiveMode.Update, true))
131 | {
132 | foreach (ZipArchiveEntry zipEntry in archive.Entries)
133 | {
134 | if (zipEntry.FullName == "synthriderz.meta.json")
135 | {
136 | return ms;
137 | }
138 | }
139 |
140 | MainViewModel.Log("Creating metadata for " + filename);
141 | JObject metadata = new JObject(new JProperty("id", id), new JProperty("hash", hash));
142 |
143 | ZipArchiveEntry entry = archive.CreateEntry("synthriderz.meta.json");
144 | using (System.IO.StreamWriter writer = new System.IO.StreamWriter(entry.Open()))
145 | {
146 | writer.Write(metadata.ToString(Formatting.None));
147 | }
148 |
149 | return ms;
150 | }
151 | }
152 | }
153 |
154 | #pragma warning disable 0649
155 | class MapPage : GenericPage
156 | {
157 | public List data;
158 | }
159 | #pragma warning restore 0649
160 | }
161 |
--------------------------------------------------------------------------------
/NoodleManagerX/Models/StorageAbstraction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace NoodleManagerX.Models
7 | {
8 |
9 | class StorageAbstraction
10 | {
11 | static object MtpDeviceLock = new object();
12 |
13 | public static async Task WriteFile(MemoryStream stream, string path)
14 | {
15 | if (DirectoryExists(Path.GetDirectoryName(path)))
16 | {
17 | if (FileExists(path))
18 | {
19 | DeleteFile(path);
20 | }
21 |
22 | stream.Position = 0;
23 |
24 | if (MtpDevice.connected)
25 | {//remove temp file once media devices works properely with memory stream
26 | string tempFile = Path.GetTempFileName();
27 | using (FileStream fs = new FileStream(tempFile, FileMode.Create, FileAccess.ReadWrite))
28 | {
29 | await stream.CopyToAsync(fs);
30 | fs.Position = 0;
31 |
32 | lock (MtpDeviceLock)
33 | {
34 | MtpDevice.device.UploadFile(fs, Path.Combine(MtpDevice.path, path));
35 | }
36 | }
37 | try { File.Delete(tempFile); } catch { }//really doesn't matter if something goes wrong, windows will clean temp files automatically
38 | }
39 | else
40 | {
41 | using (FileStream file = new FileStream(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path), FileMode.Create, FileAccess.Write))
42 | {
43 | await stream.CopyToAsync(file);
44 | }
45 | }
46 | stream.Flush();
47 | stream.Close();
48 | }
49 | }
50 |
51 | public static async Task CreateDirectory(string path)
52 | {
53 | if (MtpDevice.connected)
54 | {
55 | lock (MtpDeviceLock)
56 | {
57 | MtpDevice.device.CreateDirectory(Path.Combine(MtpDevice.path, path));
58 | }
59 | }
60 | else
61 | {
62 | Directory.CreateDirectory(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path));
63 | }
64 | }
65 |
66 | public static Stream ReadFile(string path)
67 | {
68 | if (MtpDevice.connected)
69 | {
70 | lock (MtpDeviceLock)
71 | {
72 | Stream ms = new MemoryStream();
73 | MtpDevice.device.DownloadFile(Path.Combine(MtpDevice.path, path), ms);
74 | ms.Position = 0;
75 | return ms;
76 | }
77 | }
78 | else
79 | {
80 | return new FileStream(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path), FileMode.Open, FileAccess.Read);
81 | }
82 | }
83 |
84 | public static bool FileExists(string path)
85 | {
86 | if (MtpDevice.connected)
87 | {
88 | lock (MtpDeviceLock)
89 | {
90 | return MtpDevice.device.FileExists(Path.Combine(MtpDevice.path, path));
91 | }
92 | }
93 | else
94 | {
95 | return File.Exists(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path));
96 | }
97 | }
98 |
99 | public static string GetFullComputerPath(string pathRelativeToSynthDir)
100 | {
101 | return Path.Combine(MainViewModel.s_instance.settings.synthDirectory, pathRelativeToSynthDir);
102 | }
103 |
104 | public static void DeleteFile(string path)
105 | {
106 | if (MtpDevice.connected)
107 | {
108 | lock (MtpDeviceLock)
109 | {
110 | MtpDevice.device.DeleteFile(Path.Combine(MtpDevice.path, path));
111 | }
112 | }
113 | else
114 | {
115 | File.Delete(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path));
116 | }
117 | }
118 |
119 | public static bool DirectoryExists(string path)
120 | {
121 | if (MtpDevice.connected)
122 | {
123 | lock (MtpDeviceLock)
124 | {
125 | return MtpDevice.device.DirectoryExists(Path.Combine(MtpDevice.path, path));
126 | }
127 | }
128 | else
129 | {
130 | return Directory.Exists(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path));
131 | }
132 | }
133 |
134 | public static string[] GetFilesInDirectory(string path)
135 | {
136 | if (DirectoryExists(path))
137 | {
138 | if (MtpDevice.connected)
139 | {
140 | lock (MtpDeviceLock)
141 | {
142 | return MtpDevice.device.GetFiles(Path.Combine(MtpDevice.path, path)).Select(x => Path.Combine(path, Path.GetFileName(x))).ToArray();
143 | }
144 | }
145 | else
146 | {
147 | return Directory.GetFiles(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path)).Select(x => Path.Combine(path, Path.GetFileName(x))).ToArray();
148 | }
149 | }
150 | else return new string[0];
151 | }
152 |
153 | public static DateTime GetLastWriteTime(string path)
154 | {
155 | if (MtpDevice.connected)
156 | {
157 | lock (MtpDeviceLock)
158 | {
159 | return MtpDevice.device.GetFileInfo(Path.Combine(MtpDevice.path, path)).LastWriteTime.Value;
160 | }
161 | }
162 | else
163 | {
164 | return File.GetLastWriteTime(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path));
165 | }
166 | }
167 |
168 | public static void SetLastWriteTime(DateTime timestamp, string path)
169 | {
170 | if (MtpDevice.connected)
171 | {
172 | //no idea how to change timestamp over mtp
173 | }
174 | else
175 | {
176 | File.SetLastWriteTime(Path.Combine(MainViewModel.s_instance.settings.synthDirectory, path), timestamp);
177 | }
178 | }
179 |
180 | public static bool CanDownload(bool silent = false)
181 | {
182 | if (MtpDevice.connected) return true;
183 | else
184 | {
185 | return MainViewModel.s_instance.CheckDirectory(MainViewModel.s_instance.settings.synthDirectory, !silent);
186 | }
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | # *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
--------------------------------------------------------------------------------
/QuestAccess.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
200 |
--------------------------------------------------------------------------------
/NoodleManagerX/Assets/QuestAccess.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
200 |
--------------------------------------------------------------------------------