diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b285a22
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+bin/
+obj/
+.idea/
+/packages/
+riderModule.iml
+/_ReSharper.Caches/
\ No newline at end of file
diff --git a/BeatLeaderModifiers.sln b/BeatLeaderModifiers.sln
new file mode 100644
index 0000000..80c784b
--- /dev/null
+++ b/BeatLeaderModifiers.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31424.327
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeatLeaderModifiers", "BeatLeaderModifiers\BeatLeaderModifiers.csproj", "{A652D071-E511-4BD2-B49A-992C12385342}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A652D071-E511-4BD2-B49A-992C12385342}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A652D071-E511-4BD2-B49A-992C12385342}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A652D071-E511-4BD2-B49A-992C12385342}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A652D071-E511-4BD2-B49A-992C12385342}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {63CBE1E9-6AEF-4CE5-9EEA-7FA7DB3AEBCF}
+ EndGlobalSection
+EndGlobal
diff --git a/BeatLeaderModifiers/BeatLeaderModifiers.csproj b/BeatLeaderModifiers/BeatLeaderModifiers.csproj
new file mode 100644
index 0000000..238abe1
--- /dev/null
+++ b/BeatLeaderModifiers/BeatLeaderModifiers.csproj
@@ -0,0 +1,134 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {A652D071-E511-4BD2-B49A-992C12385342}
+ Library
+ Properties
+ BeatLeaderModifiers
+ BeatLeaderModifiers
+ v4.7.2
+ 512
+ true
+ portable
+ ..\Refs
+ $(LocalRefsDir)
+ $(MSBuildProjectDirectory)\
+
+ prompt
+ 4
+ latest
+
+
+ false
+ bin\Debug\
+ DEBUG;TRACE
+
+
+ true
+ bin\Release\
+ prompt
+ 4
+
+
+ True
+
+
+ True
+ True
+
+
+
+ $(BeatSaberDir)\Libs\0Harmony.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\BeatmapCore.dll
+
+
+ $(BeatSaberDir)\Plugins\BSML.dll
+
+
+ $(BeatSaberDir)\Plugins\SiraUtil.dll
+
+
+ $(BeatSaberDir)\Plugins\SongCore.dll
+
+
+
+
+
+
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\HMLib.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\HMUI.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\Unity.TextMeshPro.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.AssetBundleModule.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UI.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIElementsModule.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIModule.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.VRModule.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\Zenject.dll
+
+
+ $(BeatSaberDir)\Beat Saber_Data\Managed\Zenject-usage.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.2.3
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
\ No newline at end of file
diff --git a/BeatLeaderModifiers/BeatLeaderModifiers.csproj.DotSettings b/BeatLeaderModifiers/BeatLeaderModifiers.csproj.DotSettings
new file mode 100644
index 0000000..490218d
--- /dev/null
+++ b/BeatLeaderModifiers/BeatLeaderModifiers.csproj.DotSettings
@@ -0,0 +1,37 @@
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ False
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/BeatLeaderModifiers/BeatLeaderModifiers.csproj.user b/BeatLeaderModifiers/BeatLeaderModifiers.csproj.user
new file mode 100644
index 0000000..d0e407e
--- /dev/null
+++ b/BeatLeaderModifiers/BeatLeaderModifiers.csproj.user
@@ -0,0 +1,8 @@
+
+
+
+ 1.27.0
+ D:\Games\Steam\steamapps\common\Beat Saber v$(BeatSaberVersion)
+ $(BeatSaberDir)\Libs;$(BeatSaberDir)\Plugins;$(BeatSaberDir)\Beat Saber_Data\Managed;
+
+
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Directory.Build.props b/BeatLeaderModifiers/Directory.Build.props
new file mode 100644
index 0000000..02c5dc1
--- /dev/null
+++ b/BeatLeaderModifiers/Directory.Build.props
@@ -0,0 +1,14 @@
+
+
+
+
+ true
+ true
+ true
+
+
+ false
+ true
+ true
+
+
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Directory.Build.targets b/BeatLeaderModifiers/Directory.Build.targets
new file mode 100644
index 0000000..1c2dd75
--- /dev/null
+++ b/BeatLeaderModifiers/Directory.Build.targets
@@ -0,0 +1,96 @@
+
+
+
+
+ 2.0
+
+ false
+
+ $(OutputPath)$(AssemblyName)
+
+ $(OutputPath)Final
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(AssemblyName)
+ $(ArtifactName)-$(PluginVersion)
+ $(ArtifactName)-bs$(GameVersion)
+ $(ArtifactName)-$(CommitHash)
+
+
+
+
+
+
+ $(AssemblyName)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(AssemblyName)
+ $(OutDir)zip\
+
+
+
+
+
+
+
+
+
+
+
+
+ $(BeatSaberDir)\Plugins
+ True
+ Unable to copy assembly to game folder, did you set 'BeatSaberDir' correctly in your 'csproj.user' file? Plugins folder doesn't exist: '$(PluginDir)'.
+
+ Unable to copy to Plugins folder, '$(BeatSaberDir)' does not appear to be a Beat Saber game install.
+
+ Unable to copy to Plugins folder, 'BeatSaberDir' has not been set in your 'csproj.user' file.
+ False
+
+
+
+
+
+
+
+ $(BeatSaberDir)\IPA\Pending\Plugins
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BeatLeaderModifiers/HarmonyPatches/HarmonyHelper.cs b/BeatLeaderModifiers/HarmonyPatches/HarmonyHelper.cs
new file mode 100644
index 0000000..f93378d
--- /dev/null
+++ b/BeatLeaderModifiers/HarmonyPatches/HarmonyHelper.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using HarmonyLib;
+
+namespace BeatLeaderModifiers {
+ public static class HarmonyHelper {
+ private static Harmony _harmony;
+
+ private static bool _initialized;
+
+ private static void LazyInit() {
+ if (_initialized) return;
+ _harmony = new Harmony("BeatLeaderModifiers");
+ _initialized = true;
+ }
+
+ public static void ApplyPatches() {
+ LazyInit();
+ _harmony.PatchAll(Assembly.GetExecutingAssembly());
+ }
+
+ public static void RemovePatches() {
+ if (!_initialized) return;
+ _harmony.UnpatchSelf();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/HarmonyPatches/SetContentPatch.cs b/BeatLeaderModifiers/HarmonyPatches/SetContentPatch.cs
new file mode 100644
index 0000000..90ea3e9
--- /dev/null
+++ b/BeatLeaderModifiers/HarmonyPatches/SetContentPatch.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using HarmonyLib;
+using IPA.Utilities;
+using JetBrains.Annotations;
+
+namespace BeatLeaderModifiers {
+ [HarmonyPatch(typeof(StandardLevelDetailView), "SetContent")]
+ internal class SetContentPatch {
+ #region Prefix
+
+ [UsedImplicitly]
+ private static void Prefix(IBeatmapLevel level) {
+ if (!level.levelID.StartsWith("custom_level")) return;
+
+ if (level.beatmapLevelData.difficultyBeatmapSets.Any(x =>
+ x.beatmapCharacteristic.serializedName.Equals(CharacteristicsManager.BetterScoringCharacteristic.SerializedName))) {
+ return; //TODO: Filter all custom characteristics
+ }
+
+ AddCustomCharacteristic(level);
+ }
+
+ #endregion
+
+ #region AddCustomCharacteristic
+
+ private static async void AddCustomCharacteristic(IBeatmapLevel level) {
+ var characteristicSO = CharacteristicsManager.BetterScoringCharacteristic.CharacteristicSO;
+
+ var difficultyBeatmapSets = new List(level.beatmapLevelData.difficultyBeatmapSets);
+
+ foreach (var originalBeatmapSet in level.beatmapLevelData.difficultyBeatmapSets) {
+ var beatmapSet = new CustomDifficultyBeatmapSet(characteristicSO);
+ var customDifficulties = await CreateCustomDifficulties(originalBeatmapSet.difficultyBeatmaps, beatmapSet);
+ beatmapSet.SetCustomDifficultyBeatmaps(customDifficulties);
+
+ if (beatmapSet.difficultyBeatmaps.Count > 0) {
+ difficultyBeatmapSets.Add(beatmapSet);
+ }
+ }
+
+ ((BeatmapLevelData)level.beatmapLevelData).SetField>("_difficultyBeatmapSets", difficultyBeatmapSets);
+ }
+
+ #endregion
+
+ #region CreateCustomDifficulties
+
+ private static async Task CreateCustomDifficulties(
+ IEnumerable difficultyBeatmaps,
+ IDifficultyBeatmapSet beatmapSet
+ ) {
+ var customDifficulties = new List();
+
+ foreach (var difficultyBeatmap in difficultyBeatmaps) {
+ var beatmapDataBasicInfo = await difficultyBeatmap.GetBeatmapDataBasicInfoAsync();
+ var customBeatmap = new CustomDifficultyBeatmap(
+ difficultyBeatmap.level,
+ beatmapSet,
+ difficultyBeatmap.difficulty,
+ difficultyBeatmap.difficultyRank,
+ difficultyBeatmap.noteJumpMovementSpeed,
+ difficultyBeatmap.noteJumpStartBeatOffset,
+ difficultyBeatmap.level.beatsPerMinute,
+ ((CustomDifficultyBeatmap)difficultyBeatmap).beatmapSaveData,
+ beatmapDataBasicInfo
+ );
+
+ customDifficulties.Add(customBeatmap);
+ }
+
+ return customDifficulties.ToArray();
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Installers/OnAppInitInstaller.cs b/BeatLeaderModifiers/Installers/OnAppInitInstaller.cs
new file mode 100644
index 0000000..05ebf97
--- /dev/null
+++ b/BeatLeaderModifiers/Installers/OnAppInitInstaller.cs
@@ -0,0 +1,11 @@
+using JetBrains.Annotations;
+using Zenject;
+
+namespace BeatLeaderModifiers.Installers {
+ [UsedImplicitly]
+ public class OnAppInitInstaller : Installer {
+ public override void InstallBindings() {
+ Plugin.Log.Debug("OnAppInitInstaller");
+ }
+ }
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Installers/OnGameplayCoreInstaller.cs b/BeatLeaderModifiers/Installers/OnGameplayCoreInstaller.cs
new file mode 100644
index 0000000..b435abe
--- /dev/null
+++ b/BeatLeaderModifiers/Installers/OnGameplayCoreInstaller.cs
@@ -0,0 +1,11 @@
+using JetBrains.Annotations;
+using Zenject;
+
+namespace BeatLeaderModifiers.Installers {
+ [UsedImplicitly]
+ public class OnGameplayCoreInstaller : Installer {
+ public override void InstallBindings() {
+ Plugin.Log.Debug("OnGameplayCoreInstaller");
+ }
+ }
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Installers/OnMenuInstaller.cs b/BeatLeaderModifiers/Installers/OnMenuInstaller.cs
new file mode 100644
index 0000000..4a392b9
--- /dev/null
+++ b/BeatLeaderModifiers/Installers/OnMenuInstaller.cs
@@ -0,0 +1,11 @@
+using JetBrains.Annotations;
+using Zenject;
+
+namespace BeatLeaderModifiers.Installers {
+ [UsedImplicitly]
+ public class OnMenuInstaller : Installer {
+ public override void InstallBindings() {
+ Plugin.Log.Debug("OnMenuInstaller");
+ }
+ }
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Managers/CharacteristicsManager.cs b/BeatLeaderModifiers/Managers/CharacteristicsManager.cs
new file mode 100644
index 0000000..eea0645
--- /dev/null
+++ b/BeatLeaderModifiers/Managers/CharacteristicsManager.cs
@@ -0,0 +1,81 @@
+using System.Linq;
+using IPA.Utilities;
+using SongCore;
+using UnityEngine;
+using SongCoreUtilities = SongCore.Utilities.Utils;
+
+namespace BeatLeaderModifiers;
+
+internal static class CharacteristicsManager {
+ #region Characteritics
+
+ public static readonly CharacteristicDescriptor BetterScoringCharacteristic = new(
+ "BeatLeaderModifiers.Resources.TestIcon.png",
+ "BetterScoring",
+ "BL_BS",
+ "useful hint"
+ );
+
+ #endregion
+
+ #region RegisterCharacteristics
+
+ public static void RegisterCharacteristics() {
+ RegisterCustomCharacteristic(BetterScoringCharacteristic);
+ }
+
+ private static void RegisterCustomCharacteristic(CharacteristicDescriptor characteristicDescriptor) {
+ Collections.RegisterCustomCharacteristic(
+ characteristicDescriptor.Icon,
+ characteristicDescriptor.Name,
+ characteristicDescriptor.HintText,
+ characteristicDescriptor.SerializedName,
+ characteristicDescriptor.Name
+ );
+
+ characteristicDescriptor.CharacteristicSO = CreateCharacteristicSO(characteristicDescriptor);
+ }
+
+ #endregion
+
+ #region CreateCharacteristicSO
+
+ private static BeatmapCharacteristicSO CreateCharacteristicSO(CharacteristicDescriptor characteristicDescriptor) {
+ var beatmapCharacteristicSO = ScriptableObject.CreateInstance();
+
+ beatmapCharacteristicSO.SetField("_icon", characteristicDescriptor.Icon);
+ beatmapCharacteristicSO.SetField("_characteristicNameLocalizationKey", characteristicDescriptor.Name);
+ beatmapCharacteristicSO.SetField("_descriptionLocalizationKey", characteristicDescriptor.HintText);
+ beatmapCharacteristicSO.SetField("_serializedName", characteristicDescriptor.SerializedName);
+ beatmapCharacteristicSO.SetField("_compoundIdPartName", characteristicDescriptor.Name);
+ beatmapCharacteristicSO.SetField("_sortingOrder", 100);
+ beatmapCharacteristicSO.SetField("_containsRotationEvents", false);
+ beatmapCharacteristicSO.SetField("_requires360Movement", false);
+ beatmapCharacteristicSO.SetField("_numberOfColors", 2);
+
+ return beatmapCharacteristicSO;
+ }
+
+ #endregion
+
+ #region CharacteristicDescriptor
+
+ public class CharacteristicDescriptor {
+ public readonly Sprite Icon;
+ public readonly string Name;
+ public readonly string SerializedName;
+ public readonly string HintText;
+ public BeatmapCharacteristicSO CharacteristicSO = default;
+
+ public CharacteristicDescriptor(string icon, string name, string serializedName, string hintText) {
+ Name = name;
+ SerializedName = serializedName;
+ HintText = hintText;
+
+ //TODO: AssetBundle?
+ Icon = Resources.FindObjectsOfTypeAll().FirstOrDefault(it => it.name == icon);
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Plugin.cs b/BeatLeaderModifiers/Plugin.cs
new file mode 100644
index 0000000..48988f6
--- /dev/null
+++ b/BeatLeaderModifiers/Plugin.cs
@@ -0,0 +1,42 @@
+using BeatLeaderModifiers.Installers;
+using IPA;
+using JetBrains.Annotations;
+using SiraUtil.Zenject;
+using IPALogger = IPA.Logging.Logger;
+
+namespace BeatLeaderModifiers {
+ [Plugin(RuntimeOptions.SingleStartInit)]
+ [UsedImplicitly]
+ public class Plugin {
+ #region Init
+
+ internal static IPALogger Log { get; private set; }
+
+ [Init]
+ public Plugin(IPALogger logger, Zenjector zenjector) {
+ Log = logger;
+ zenjector.Install(Location.App);
+ zenjector.Install(Location.Menu);
+ zenjector.Install(Location.GameCore);
+ }
+
+ #endregion
+
+ #region OnApplicationStart
+
+ [OnStart, UsedImplicitly]
+ public void OnApplicationStart() {
+ HarmonyHelper.ApplyPatches();
+ CharacteristicsManager.RegisterCharacteristics();
+ }
+
+ #endregion
+
+ #region OnApplicationQuit
+
+ [OnExit, UsedImplicitly]
+ public void OnApplicationQuit() { }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/BeatLeaderModifiers/Properties/AssemblyInfo.cs b/BeatLeaderModifiers/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..64dc9fa
--- /dev/null
+++ b/BeatLeaderModifiers/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BeatLeaderModifiers")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BeatLeaderModifiers")]
+[assembly: AssemblyCopyright("Copyright © 2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("958b345b-4ae1-404b-bfbc-2555f05e74f1")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.0.1")]
+[assembly: AssemblyFileVersion("0.0.1")]
\ No newline at end of file
diff --git a/BeatLeaderModifiers/manifest.json b/BeatLeaderModifiers/manifest.json
new file mode 100644
index 0000000..b68aa24
--- /dev/null
+++ b/BeatLeaderModifiers/manifest.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json",
+ "id": "BeatLeaderModifiers",
+ "name": "BeatLeaderModifiers",
+ "author": "",
+ "version": "0.0.1",
+ "description": "",
+ "gameVersion": "1.27.0",
+ "dependsOn": {
+ "BSIPA": "^4.2.2",
+ "SiraUtil": "^3.1.0"
+ },
+ "features": []
+}
\ No newline at end of file