Bläddra i källkod

Feature remember levelpack (#53)

* Level pack selection logic.
* Level pack ID stored in settings.
* Fix last level selected on reloads.
* Remember last level pack selected.
* Move level pack update selection logic into own function.
* Update level pack selection when returning from a song.
* Separate level pack database updates from regular song list processing.
Only update level packs when we have a good reason too (SongLoader events
for example).
* Fix search/playlist filters from permanently filtering songs if you
filter, leave, enter the song list.
* Clear filters when switching level packs.
* Version bump 3.0.4-Beta-4.
Halsafar 6 år sedan
förälder
incheckning
a11c12d6dc

+ 53 - 31
SongBrowserPlugin/DataAccess/SongBrowserModel.cs

@@ -223,36 +223,9 @@ namespace SongBrowserPlugin
         /// </summary>
         public void UpdateLevelRecords()
         {
-            // get a default beatmap characteristic...
-            if (this.CurrentBeatmapCharacteristicSO == null)
-            {
-                // TODO - this probably needs to be queried or passed in earlier, hack for now
-                // StandardBeatmapCharacteristic
-                Logger.Info("No Beatmap Characteristic selected... selecting default...");
-                this.CurrentBeatmapCharacteristicSO = Resources.FindObjectsOfTypeAll<BeatmapCharacteristicCollectionSO>().FirstOrDefault().beatmapCharacteristics[0];
-            }
-
             Stopwatch timer = new Stopwatch();
             timer.Start();
 
-            BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll<BeatmapLevelPackSO>();
-            foreach (BeatmapLevelPackSO levelPack in levelPacks)
-            {
-                Logger.Debug("Attempting to get song list from levelPack: {0}...", levelPack);
-                var beatmapLevelPack = levelPack as BeatmapLevelPackSO;
-
-                // TODO - need to rethink interface here, not all level packs can be cast this high, some sort functions need it.
-                //      - this helps prevent DLC from breaking everything
-                if (beatmapLevelPack == null)
-                {                    
-                    continue;
-                }
-                
-                _levelPackToSongs[levelPack.packName] = (beatmapLevelPack.beatmapLevelCollection as BeatmapLevelCollectionSO).GetPrivateField<BeatmapLevelSO[]>("_beatmapLevels").ToList();
-                Logger.Debug("Got {0} songs from level collections...", _levelPackToSongs[levelPack.packName].Count);
-                //_levelPackToSongs[levelPack.packName].ForEach(x => Logger.Debug("{0} by {1} = {2}", x.name, x.levelAuthorName, x.levelID));
-            }
-
             // Calculate some information about the custom song dir
             String customSongsPath = Path.Combine(Environment.CurrentDirectory, CUSTOM_SONGS_DIR);
             String revSlashCustomSongPath = customSongsPath.Replace('\\', '/');
@@ -366,6 +339,46 @@ namespace SongBrowserPlugin
         }
 
         /// <summary>
+        /// Get a copy of the unfiltered, unsorted list of songs from level packs.
+        /// </summary>
+        public void UpdateLevelPackOriginalLists()
+        {
+            BeatmapLevelPackSO[] levelPacks = Resources.FindObjectsOfTypeAll<BeatmapLevelPackSO>();
+            foreach (BeatmapLevelPackSO levelPack in levelPacks)
+            {
+                Logger.Debug("Attempting to get song list from levelPack: {0}...", levelPack);
+                var beatmapLevelPack = levelPack as BeatmapLevelPackSO;
+
+                // TODO - need to rethink interface here, not all level packs can be cast this high, some sort functions need it.
+                //      - this helps prevent DLC from breaking everything
+                if (beatmapLevelPack == null)
+                {
+                    continue;
+                }
+
+                _levelPackToSongs[levelPack.packName] = (beatmapLevelPack.beatmapLevelCollection as BeatmapLevelCollectionSO).GetPrivateField<BeatmapLevelSO[]>("_beatmapLevels").ToList();
+                Logger.Debug("Got {0} songs from level collections...", _levelPackToSongs[levelPack.packName].Count);
+                //_levelPackToSongs[levelPack.packName].ForEach(x => Logger.Debug("{0} by {1} = {2}", x.name, x.levelAuthorName, x.levelID));
+            }
+        }
+
+        /// <summary>
+        /// SongLoader doesn't fire event when we delete a song.
+        /// </summary>
+        /// <param name="levelPack"></param>
+        /// <param name="levelId"></param>
+        public void RemoveSongFromLevelPack(IBeatmapLevelPack levelPack, String levelId)
+        {
+            if (!_levelPackToSongs.ContainsKey(levelPack.packName))
+            {
+                Logger.Debug("Trying to remove song from level pack [{0}] but we do not have any information on it...", levelPack.packName);
+                return;
+            }
+
+            _levelPackToSongs[levelPack.packName].RemoveAll(x => x.levelID == levelId);
+        }
+
+        /// <summary>
         /// Update the gameplay play counts.
         /// </summary>
         /// <param name="gameplayMode"></param>
@@ -407,7 +420,7 @@ namespace SongBrowserPlugin
             // bail
             if (scoreSaberDataFile == null)
             {
-                Logger.Warning("Cannot fetch song difficulty for score saber data...");
+                Logger.Warning("Score saber data is not ready yet...");
                 return;
             }
 
@@ -507,7 +520,7 @@ namespace SongBrowserPlugin
         /// <param name="pack"></param>
         public void SetCurrentLevelPack(IBeatmapLevelPack pack)
         {
-            Logger.Debug("Setting level packs back to their original values!");
+            Logger.Debug("Setting current level pack [{0}]: {1}", pack.packID, pack.packName);
 
             this.ResetLevelPacks();
 
@@ -524,6 +537,9 @@ namespace SongBrowserPlugin
                 Logger.Debug("Owned level pack...  Enabling SongBrowser...");
                 _isPreviewLevelPack = false;
             }
+
+            this.Settings.currentLevelPackId = pack.packID;
+            this.Settings.Save();
         }
 
         /// <summary>
@@ -563,9 +579,15 @@ namespace SongBrowserPlugin
                 return;
             }
 
-            if (_levelPackToSongs.Count == 0 || this._currentLevelPack == null || !this._levelPackToSongs.ContainsKey(this._currentLevelPack.packName))
+            if (_levelPackToSongs.Count == 0)
+            {
+                Logger.Debug("Cannot process songs yet, level packs have not been processed...");
+                return;
+            }
+
+            if (this._currentLevelPack == null || !this._levelPackToSongs.ContainsKey(this._currentLevelPack.packName))
             {
-                Logger.Debug("Cannot process songs yet, songs infos have not been processed...");
+                Logger.Debug("Cannot process songs yet, no level pack selected...");
                 return;
             }
 

+ 1 - 0
SongBrowserPlugin/DataAccess/SongBrowserSettings.cs

@@ -55,6 +55,7 @@ namespace SongBrowserPlugin.DataAccess
         public String currentDirectory = default(String);
         public String currentPlaylistFile = default(String);
         public String currentEditingPlaylistFile = default(String);
+        public String currentLevelPackId = default(String);
 
         public bool randomInstantQueue = false;
         public bool deleteNumberedSongFolder = true;

+ 1 - 1
SongBrowserPlugin/Logging/Logger.cs

@@ -14,7 +14,7 @@ namespace SongBrowserPlugin.Logging
     public class Logger
     {
         private static readonly string LoggerName = "SongBrowserPlugin";
-        private static readonly LogLevel LogLevel = LogLevel.Info;
+        private static readonly LogLevel LogLevel = LogLevel.Trace;
         private static readonly ConsoleColor DefaultFgColor = ConsoleColor.Gray;
 
         private static void ResetForegroundColor()

+ 1 - 1
SongBrowserPlugin/Plugin.cs

@@ -14,7 +14,7 @@ namespace SongBrowserPlugin
 {
     public class Plugin : IPlugin
     {
-        public const string VERSION_NUMBER = "3.0.3";
+        public const string VERSION_NUMBER = "3.0.4-Beta-4";
 
         public string Name
         {

+ 13 - 0
SongBrowserPlugin/SongBrowserApplication.cs

@@ -75,6 +75,7 @@ namespace SongBrowserPlugin
             else
             {
                 SongLoader.SongsLoadedEvent += OnSongLoaderLoadedSongs;
+                _songBrowserUI.UpdateLevelPackModel();
             }
         }
 
@@ -88,6 +89,7 @@ namespace SongBrowserPlugin
             Logger.Trace("OnSongLoaderLoadedSongs-SongBrowserApplication()");
             try
             {
+                _songBrowserUI.UpdateLevelPackModel();
                 _songBrowserUI.UpdateLevelDataModel();
                 _songBrowserUI.RefreshSongList();
             }
@@ -212,6 +214,17 @@ namespace SongBrowserPlugin
         {
             Logger.Trace("HandleModeSelection()");
             this._songBrowserUI.CreateUI(mode);
+            StartCoroutine(this.UpdateBrowserUI());
+        }
+
+        /// <summary>
+        /// Wait until the end of the frame to finish updating everything.
+        /// </summary>
+        /// <returns></returns>
+        public IEnumerator UpdateBrowserUI()
+        {
+            yield return new WaitForEndOfFrame();
+
             this._songBrowserUI.UpdateLevelDataModel();
             this._songBrowserUI.RefreshSongList();
         }

+ 224 - 43
SongBrowserPlugin/UI/Browser/SongBrowserUI.cs

@@ -11,12 +11,10 @@ using System.IO;
 using SongLoaderPlugin;
 using System.Security.Cryptography;
 using System.Text;
-using System.Text.RegularExpressions;
 using TMPro;
 using Logger = SongBrowserPlugin.Logging.Logger;
 using SongBrowserPlugin.DataAccess.BeatSaverApi;
-using CustomUI.BeatSaber;
-using SongBrowserPlugin.Internals;
+using System.Collections;
 
 namespace SongBrowserPlugin.UI
 {
@@ -164,7 +162,7 @@ namespace SongBrowserPlugin.UI
                 _levelPackLevelsViewController = _levelSelectionFlowCoordinator.GetPrivateField<LevelPackLevelsViewController>("_levelPackLevelsViewController");
                 Logger.Debug("Acquired LevelPackLevelsViewController [{0}]", _levelPackLevelsViewController.GetInstanceID());
 
-                _levelPackLevelsTableView = this._levelPackLevelsViewController.GetComponentInChildren<LevelPackLevelsTableView>();
+                _levelPackLevelsTableView = this._levelPackLevelsViewController.GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView");
                 Logger.Debug("Acquired LevelPackLevelsTableView [{0}]", _levelPackLevelsTableView.GetInstanceID());
 
                 _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
@@ -216,16 +214,6 @@ namespace SongBrowserPlugin.UI
 
                 this.ResizeStatsPanel();
 
-                // make sure the quick scroll buttons don't desync with regular scrolling
-                _tableViewPageDownButton.onClick.AddListener(delegate ()
-                {
-                    this.RefreshQuickScrollButtons();
-                });
-                _tableViewPageUpButton.onClick.AddListener(delegate ()
-                {
-                    this.RefreshQuickScrollButtons();
-                });
-
                 _uiCreated = true;
                 Logger.Debug("Done Creating UI...");
             }
@@ -516,23 +504,60 @@ namespace SongBrowserPlugin.UI
         /// </summary>
         private void InstallHandlers()
         {
-            // handlers
+            // level pack, level, difficulty handlers, characteristics
             TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelPackLevelsTableView, "_tableView");
+
+            tableView.didSelectCellWithIdxEvent -= HandleDidSelectTableViewRow;
             tableView.didSelectCellWithIdxEvent += HandleDidSelectTableViewRow;
+
+            _levelPackLevelsViewController.didSelectLevelEvent -= OnDidSelectLevelEvent;
             _levelPackLevelsViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
-            _levelDifficultyViewController.didSelectDifficultyEvent += OnDidSelectDifficultyEvent;
 
-            var packListTableView = _levelPacksTableView;
+            _levelDifficultyViewController.didSelectDifficultyEvent -= OnDidSelectDifficultyEvent;
+            _levelDifficultyViewController.didSelectDifficultyEvent += OnDidSelectDifficultyEvent;
 
+            _levelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
             _levelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
+            _levelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
             _levelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
-                  
+
+            _beatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent -= OnDidSelectBeatmapCharacteristic;
             _beatmapCharacteristicSelectionViewController.didSelectBeatmapCharacteristicEvent += OnDidSelectBeatmapCharacteristic;
+
+            // make sure the quick scroll buttons don't desync with regular scrolling
+            _tableViewPageDownButton.onClick.AddListener(delegate ()
+            {
+                this.RefreshQuickScrollButtons();
+            });
+            _tableViewPageUpButton.onClick.AddListener(delegate ()
+            {
+                this.RefreshQuickScrollButtons();
+            });
+
+            // finished level
+            ResultsViewController resultsViewController = _levelSelectionFlowCoordinator.GetPrivateField<ResultsViewController>("_resultsViewController");
+            resultsViewController.continueButtonPressedEvent += ResultsViewController_continueButtonPressedEvent;
+        }
+
+        /// <summary>
+        /// Handle updating the level pack selection after returning from a song.
+        /// </summary>
+        /// <param name="obj"></param>
+        private void ResultsViewController_continueButtonPressedEvent(ResultsViewController obj)
+        {
+            StartCoroutine(this.UpdateLevelPackSelectionEndOfFrame());
+        }
+
+        public IEnumerator UpdateLevelPackSelectionEndOfFrame()
+        {
+            yield return new WaitForEndOfFrame();
+
+            this.UpdateLevelPackSelection();
+            SelectAndScrollToLevel(_levelPackLevelsTableView, _model.LastSelectedLevelId);
         }
 
         /// <summary>
         /// Handler for level pack selection.
-        /// UNUSED
         /// </summary>
         /// <param name="arg1"></param>
         /// <param name="arg2"></param>
@@ -542,11 +567,9 @@ namespace SongBrowserPlugin.UI
 
             try
             {
-                if (this._model.Settings.filterMode == SongFilterMode.Playlist)
-                {
-                    this._model.Settings.filterMode = SongFilterMode.None;
-                    this._model.Settings.Save();
-                }
+                // reset filter mode always here
+                this._model.Settings.filterMode = SongFilterMode.None;
+                this._model.Settings.Save();
 
                 this._model.SetCurrentLevelPack(arg2);
                 this._model.ProcessSongList();
@@ -641,8 +664,7 @@ namespace SongBrowserPlugin.UI
             }
 
             //Scroll to start of the list
-            var levelsTableView = _levelPackLevelsViewController.GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView");
-            TableView listTableView = levelsTableView.GetPrivateField<TableView>("_tableView");
+            TableView listTableView = _levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
             listTableView.ScrollToCellWithIdx(0, TableView.ScrollPositionType.Beginning, false);
         }
 
@@ -815,23 +837,25 @@ namespace SongBrowserPlugin.UI
                     {
                         try
                         {
-                            var levelsTableView = _levelPackLevelsViewController.GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView");
-                            List<IPreviewBeatmapLevel> levels = levelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack").beatmapLevelCollection.beatmapLevels.ToList();
+                            // determine the index we are deleting so we can keep the cursor near the same spot after
+                            List<IPreviewBeatmapLevel> levels = _levelPackLevelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack").beatmapLevelCollection.beatmapLevels.ToList();
                             int selectedIndex = levels.FindIndex(x => x.levelID == _levelDetailViewController.selectedDifficultyBeatmap.level.levelID);
 
-                            SongDownloader.Instance.DeleteSong(new Song(SongLoader.CustomLevels.First(x => x.levelID == _levelDetailViewController.selectedDifficultyBeatmap.level.levelID)));
+                            // we are only deleting custom levels, find the song, delete it
+                            var song = new Song(SongLoader.CustomLevels.First(x => x.levelID == _levelDetailViewController.selectedDifficultyBeatmap.level.levelID));
+                            SongDownloader.Instance.DeleteSong(song);
 
                             if (selectedIndex > -1)
                             {
-                                int removedLevels = levels.RemoveAll(x => x.levelID == _levelDetailViewController.selectedDifficultyBeatmap.level.levelID);
-                                Logger.Log("Removed " + removedLevels + " level(s) from song list!");
+                                this._model.RemoveSongFromLevelPack(this._model.CurrentLevelPack, _levelDetailViewController.selectedDifficultyBeatmap.level.levelID);
+                                Logger.Log("Removed {0} from custom song list!", song.songName);
 
                                 this.UpdateLevelDataModel();
                                 this.RefreshSongList();
 
-                                TableView listTableView = levelsTableView.GetPrivateField<TableView>("_tableView");
+                                TableView listTableView = _levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
                                 listTableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, false);
-                                levelsTableView.SetPrivateField("_selectedRow", selectedIndex);
+                                _levelPackLevelsTableView.SetPrivateField("_selectedRow", selectedIndex);
                                 listTableView.SelectCellWithIdx(selectedIndex, true);
                             }
                         }
@@ -1027,6 +1051,7 @@ namespace SongBrowserPlugin.UI
             BeatmapDifficulty difficulty = this._levelDifficultyViewController.selectedDifficulty;
             string njsText;
             string difficultyString = difficulty.ToString();
+            Logger.Debug(difficultyString);
 
             //Grab NJS for difficulty
             //Default to 10 if a standard level
@@ -1275,6 +1300,109 @@ namespace SongBrowserPlugin.UI
         }
 
         /// <summary>
+        /// Acquire the level pack collection.
+        /// </summary>
+        /// <returns></returns>
+        private IBeatmapLevelPackCollection GetLevelPackCollection()
+        {
+            if (_levelPackViewController == null)
+            {
+                return null;
+            }
+
+            IBeatmapLevelPackCollection levelPackCollection = _levelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
+            return levelPackCollection;
+        }
+
+        /// <summary>
+        /// Get the currently selected level pack within the LevelPackLevelViewController hierarchy.
+        /// </summary>
+        /// <returns></returns>
+        private IBeatmapLevelPack GetCurrentSelectedLevelPackFromBeatSaber()
+        {
+            if (_levelPackLevelsTableView == null)
+            {
+                return null;
+            }
+
+            var pack = _levelPackLevelsTableView.GetPrivateField<IBeatmapLevelPack>("_pack");
+            return pack;
+        }
+
+        /// <summary>
+        /// Get level pack by level pack id.
+        /// </summary>
+        /// <param name="levelPackId"></param>
+        /// <returns></returns>
+        private IBeatmapLevelPack GetLevelPackByPackId(String levelPackId)
+        {
+            IBeatmapLevelPackCollection levelPackCollection = GetLevelPackCollection();
+            if (levelPackCollection == null)
+            {
+                return null;
+            }
+
+            IBeatmapLevelPack levelPack = levelPackCollection.beatmapLevelPacks.ToList().First(x => x.packID == levelPackId);
+            return levelPack;
+        }
+
+        /// <summary>
+        /// Get level pack index by level pack id.
+        /// </summary>
+        /// <param name="levelPackId"></param>
+        /// <returns></returns>
+        private int GetLevelPackIndexByPackId(String levelPackId)
+        {
+            IBeatmapLevelPackCollection levelPackCollection = GetLevelPackCollection();
+            if (levelPackCollection == null)
+            {
+                return -1;
+            }
+
+            int index = levelPackCollection.beatmapLevelPacks.ToList().FindIndex(x => x.packID == levelPackId);
+            return index;
+        }
+
+        /// <summary>
+        /// Select a level pack.
+        /// </summary>
+        /// <param name="levelPackId"></param>
+        public void SelectLevelPack(String levelPackId)
+        {
+            Logger.Trace("SelectLevelPack({0})", levelPackId);
+
+            try
+            {
+                var levelPacks = GetLevelPackCollection();
+                var index = GetLevelPackIndexByPackId(levelPackId);
+                var pack = GetLevelPackByPackId(levelPackId);
+
+                if (index < 0)
+                {
+                    Logger.Debug("Cannot select level packs yet...");
+                    return;
+                }
+
+                Logger.Info("Selecting level pack index: {0}", pack.packName);
+                var tableView = _levelPacksTableView.GetPrivateField<TableView>("_tableView");
+
+                _levelPacksTableView.SelectCellWithIdx(index);
+                tableView.SelectCellWithIdx(index, true);
+                tableView.ScrollToCellWithIdx(0, TableView.ScrollPositionType.Beginning, false);
+                for (int i = 0; i < index; i++)
+                {
+                    tableView.PageScrollDown();
+                }
+
+                Logger.Debug("Done selecting level pack!");
+            }
+            catch (Exception e)
+            {
+                Logger.Exception(e);
+            }
+        }
+
+        /// <summary>
         /// Scroll TableView to proper row, fire events.
         /// </summary>
         /// <param name="table"></param>
@@ -1309,11 +1437,12 @@ namespace SongBrowserPlugin.UI
             selectedIndex = levels.FindIndex(x => x.levelID == levelID);
             if (selectedIndex >= 0)
             {
-                Logger.Debug("Scrolling to idx: {0}", selectedIndex);
+                // the header counts as an index...
+                selectedIndex += 1;
+
+                Logger.Debug("Scrolling level list to idx: {0}", selectedIndex);
 
-                TableView listTableView = _levelPackLevelsViewController
-                    .GetPrivateField<LevelPackLevelsTableView>("_levelPackLevelsTableView")
-                    .GetPrivateField<TableView>("_tableView");
+                TableView tableView = _levelPackLevelsTableView.GetPrivateField<TableView>("_tableView");
 
                 var scrollPosType = TableView.ScrollPositionType.Center;
                 if (selectedIndex == 0)
@@ -1325,7 +1454,10 @@ namespace SongBrowserPlugin.UI
                     scrollPosType = TableView.ScrollPositionType.End;
                 }
 
-                listTableView.ScrollToCellWithIdx(selectedIndex, scrollPosType, true);
+                _levelPackLevelsTableView.HandleDidSelectRowEvent(tableView, selectedIndex);
+                tableView.ScrollToCellWithIdx(selectedIndex, TableView.ScrollPositionType.Beginning, true);
+                tableView.SelectCellWithIdx(selectedIndex);
+
                 RefreshQuickScrollButtons();
 
                 _lastRow = selectedIndex;
@@ -1345,15 +1477,15 @@ namespace SongBrowserPlugin.UI
             {
                 Logger.Trace("UpdateLevelDataModel()");
 
-                if (_model.CurrentLevelPack == null && _levelPackViewController != null)
+                // get a current beatmap characteristic...
+                if (_model.CurrentBeatmapCharacteristicSO == null && _beatmapCharacteristicSelectionViewController != null)
                 {
-                    // TODO - is this acceptable?  review....
-                    Logger.Debug("No level pack selected, acquiring the first available...");
-                    var levelPackCollection = _levelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
-                    this._model.SetCurrentLevelPack(levelPackCollection.beatmapLevelPacks[0]);
+                    _model.CurrentBeatmapCharacteristicSO = _beatmapCharacteristicSelectionViewController.GetPrivateField<BeatmapCharacteristicSO>("_selectedBeatmapCharacteristic");
                 }
 
                 _model.UpdateLevelRecords();
+
+                UpdateLevelPackSelection();
             }
             catch (Exception e)
             {
@@ -1361,6 +1493,55 @@ namespace SongBrowserPlugin.UI
             }
         }
 
+        /// <summary>
+        /// Update the level pack model.
+        /// </summary>
+        public void UpdateLevelPackModel()
+        {
+            _model.UpdateLevelPackOriginalLists();
+        }
+
+        /// <summary>
+        /// Logic for fixing BeatSaber's level pack selection bugs.
+        /// </summary>
+        public void UpdateLevelPackSelection()
+        {
+            if (_levelPackViewController != null)
+            {
+                IBeatmapLevelPack currentSelected = GetCurrentSelectedLevelPackFromBeatSaber();
+                Logger.Debug("Current selected level pack: {0}", currentSelected);
+
+                if (String.IsNullOrEmpty(_model.Settings.currentLevelPackId))
+                {
+                    if (currentSelected == null)
+                    {
+                        Logger.Debug("No level pack selected, acquiring the first available...");
+                        var levelPackCollection = _levelPackViewController.GetPrivateField<IBeatmapLevelPackCollection>("_levelPackCollection");
+                        currentSelected = levelPackCollection.beatmapLevelPacks[0];
+                    }
+                    this._model.SetCurrentLevelPack(currentSelected);
+                }
+                else if (currentSelected == null || (currentSelected.packID != _model.Settings.currentLevelId))
+                {
+                    Logger.Debug("Automatically selecting level pack: {0}", _model.Settings.currentLevelPackId);
+
+                    // HACK - BeatSaber seems to always go back to OST1 internally.
+                    //      - Lets force it to the last pack id but not have SongBrowser functions fire.
+                    // Turn off our event processing
+                    _levelPackViewController.didSelectPackEvent -= _levelPackViewController_didSelectPackEvent;
+                    _levelPacksTableView.didSelectPackEvent -= _levelPacksTableView_didSelectPackEvent;
+
+                    var levelPack = GetLevelPackByPackId(_model.Settings.currentLevelPackId);
+                    this.SelectLevelPack(_model.Settings.currentLevelPackId);
+                    this._model.SetCurrentLevelPack(levelPack);
+                    this._model.ProcessSongList();
+
+                    _levelPacksTableView.didSelectPackEvent += _levelPacksTableView_didSelectPackEvent;
+                    _levelPackViewController.didSelectPackEvent += _levelPackViewController_didSelectPackEvent;
+                }
+            }
+        }
+
         //Pull njs from a difficulty, based on private function from SongLoader
         public float GetNoteJump(string json)
         {