12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250 |
- using IPA.Utilities;
- using SongCore.Data;
- using SongCore.OverrideClasses;
- using SongCore.Utilities;
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using UnityEngine;
- using UnityEngine.SceneManagement;
- using static SongCore.Arcache.ArcacheLoader;
- using LogSeverity = IPA.Logging.Logger.Level;
- namespace SongCore
- {
- public class Loader : MonoBehaviour
- {
- static Loader()
- {
- InitFastLoad();
- }
- // Actions for loading and refreshing beatmaps
- public static event Action<Loader> LoadingStartedEvent;
- public static event Action<Loader, ConcurrentDictionary<string, CustomPreviewBeatmapLevel>> SongsLoadedEvent;
- public static event Action OnLevelPacksRefreshed;
- public static event Action DeletingSong;
- public static ConcurrentDictionary<string, CustomPreviewBeatmapLevel> CustomLevels = new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>();
- public static ConcurrentDictionary<string, CustomPreviewBeatmapLevel> CustomWIPLevels = new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>();
- public static ConcurrentDictionary<string, CustomPreviewBeatmapLevel> CachedWIPLevels = new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>();
- public static List<SeperateSongFolder> SeperateSongFolders = new List<SeperateSongFolder>();
- public static SongCoreCustomLevelCollection CustomLevelsCollection { get; private set; }
- public static SongCoreCustomLevelCollection WIPLevelsCollection { get; private set; }
- public static SongCoreCustomLevelCollection CachedWIPLevelCollection { get; private set; }
- public static SongCoreCustomBeatmapLevelPack CustomLevelsPack { get; private set; }
- public static SongCoreCustomBeatmapLevelPack WIPLevelsPack { get; private set; }
- public static SongCoreCustomBeatmapLevelPack CachedWIPLevelsPack { get; private set; }
- public static SongCoreBeatmapLevelPackCollectionSO CustomBeatmapLevelPackCollectionSO { get; private set; }
- private static readonly ConcurrentDictionary<string, OfficialSongEntry> OfficialSongs = new ConcurrentDictionary<string, OfficialSongEntry>();
- private static readonly ConcurrentDictionary<string, CustomPreviewBeatmapLevel> CustomLevelsById =
- new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>();
- public static bool AreSongsLoaded { get; private set; }
- public static bool AreSongsLoading { get; private set; }
- public static float LoadingProgress { get; internal set; }
- internal ProgressBar _progressBar;
- private HMTask _loadingTask;
- private bool _loadingCancelled;
- private static CustomLevelLoader _customLevelLoader;
- public static BeatmapLevelsModel BeatmapLevelsModelSO
- {
- get
- {
- if (_beatmapLevelsModel == null) _beatmapLevelsModel = Resources.FindObjectsOfTypeAll<BeatmapLevelsModel>().FirstOrDefault();
- return _beatmapLevelsModel;
- }
- }
- internal static BeatmapLevelsModel _beatmapLevelsModel;
- public static Sprite defaultCoverImage;
- public static CachedMediaAsyncLoader cachedMediaAsyncLoaderSO { get; private set; }
- public static BeatmapCharacteristicCollectionSO beatmapCharacteristicCollection { get; private set; }
- public static Loader Instance;
- public static void OnLoad()
- {
- if (Instance != null)
- {
- _beatmapLevelsModel = null;
- Instance.RefreshLevelPacks();
- return;
- }
- new GameObject("SongCore Loader").AddComponent<Loader>();
- }
- private void Awake()
- {
- Instance = this;
- _progressBar = ProgressBar.Create();
- MenuLoaded();
- Hashing.ReadCachedSongHashes();
- Hashing.ReadCachedAudioData();
- DontDestroyOnLoad(gameObject);
- BS_Utils.Utilities.BSEvents.menuSceneLoaded += MenuLoaded;
- Initialize();
- }
- private void Initialize()
- {
- if (Directory.Exists(Converter.oldFolderPath))
- Converter.PrepareExistingLibrary();
- else
- RefreshSongs();
- }
- internal void MenuLoaded()
- {
- if (AreSongsLoading)
- {
- //Scene changing while songs are loading. Since we are using a separate thread while loading, this is bad and could cause a crash.
- //So we have to stop loading.
- if (_loadingTask != null)
- {
- _loadingTask.Cancel();
- _loadingCancelled = true;
- AreSongsLoading = false;
- LoadingProgress = 0;
- StopAllCoroutines();
- _progressBar.ShowMessage("Loading cancelled\n<size=80%>Press Ctrl+R to refresh</size>");
- Logging.Log("Loading was cancelled by player since they loaded another scene.");
- }
- }
- BS_Utils.Gameplay.Gamemode.Init();
- if (_customLevelLoader == null)
- {
- _customLevelLoader = Resources.FindObjectsOfTypeAll<CustomLevelLoader>().FirstOrDefault();
- if (_customLevelLoader)
- {
- defaultCoverImage = _customLevelLoader.GetField<Sprite, CustomLevelLoader>("_defaultPackCover");
- cachedMediaAsyncLoaderSO = _customLevelLoader.GetField<CachedMediaAsyncLoader, CustomLevelLoader>("_cachedMediaAsyncLoader");
- beatmapCharacteristicCollection = _customLevelLoader.GetField<BeatmapCharacteristicCollectionSO, CustomLevelLoader>("_beatmapCharacteristicCollection");
- }
- else
- {
- var defaultCoverTex = Texture2D.blackTexture;
- defaultCoverImage = Sprite.Create(defaultCoverTex, new Rect(0f, 0f,
- defaultCoverTex.width, defaultCoverTex.height), new Vector2(0.5f, 0.5f));
- }
- }
- }
- /// <summary>
- /// This fuction will add/remove Level Packs from the Custom Levels tab if applicable
- /// </summary>
- public void RefreshLevelPacks()
- {
- CustomLevelsCollection?.UpdatePreviewLevels(CustomLevels?.Values?.OrderBy(l => l.songName).ToArray());
- WIPLevelsCollection?.UpdatePreviewLevels(CustomWIPLevels?.Values?.OrderBy(l => l.songName).ToArray());
- CachedWIPLevelCollection?.UpdatePreviewLevels(CachedWIPLevels?.Values?.OrderBy(l => l.songName).ToArray());
- if (CachedWIPLevelsPack != null)
- {
- if (CachedWIPLevels.Count > 0 && !CustomBeatmapLevelPackCollectionSO._customBeatmapLevelPacks.Contains(CachedWIPLevelsPack))
- {
- CustomBeatmapLevelPackCollectionSO.AddLevelPack(CachedWIPLevelsPack);
- }
- else if (CachedWIPLevels.Count == 0 && CustomBeatmapLevelPackCollectionSO._customBeatmapLevelPacks.Contains(CachedWIPLevelsPack))
- {
- CustomBeatmapLevelPackCollectionSO.RemoveLevelPack(CachedWIPLevelsPack);
- }
- }
- foreach (var folderEntry in SeperateSongFolders)
- {
- if (folderEntry.SongFolderEntry.Pack == FolderLevelPack.NewPack)
- {
- folderEntry.LevelCollection.UpdatePreviewLevels(folderEntry.Levels.Values.OrderBy(l => l.songName).ToArray());
- if (folderEntry.Levels.Count > 0 || (folderEntry is ModSeperateSongFolder && (folderEntry as ModSeperateSongFolder).AlwaysShow))
- {
- if (!CustomBeatmapLevelPackCollectionSO._customBeatmapLevelPacks.Contains(folderEntry.LevelPack))
- CustomBeatmapLevelPackCollectionSO.AddLevelPack(folderEntry.LevelPack);
- }
- // else if (CustomBeatmapLevelPackCollectionSO._customBeatmapLevelPacks.Contains(folderEntry.LevelPack))
- // CustomBeatmapLevelPackCollectionSO._customBeatmapLevelPacks.Remove(folderEntry.LevelPack);
- }
- }
- BeatmapLevelsModelSO.SetField<BeatmapLevelsModel, IBeatmapLevelPackCollection>("_customLevelPackCollection", CustomBeatmapLevelPackCollectionSO as IBeatmapLevelPackCollection);
- BeatmapLevelsModelSO.UpdateAllLoadedBeatmapLevelPacks();
- BeatmapLevelsModelSO.UpdateLoadedPreviewLevels();
- var filterNav = Resources.FindObjectsOfTypeAll<LevelFilteringNavigationController>().FirstOrDefault();
- // filterNav.InitPlaylists();
- // filterNav.UpdatePlaylistsData();
- if (filterNav.isActiveAndEnabled)
- filterNav?.UpdateCustomSongs();
- // AttemptReselectCurrentLevelPack(filterNav);
- OnLevelPacksRefreshed?.Invoke();
- }
- internal void AttemptReselectCurrentLevelPack(LevelFilteringNavigationController controller)
- {
- /*
- var collectionview = Resources.FindObjectsOfTypeAll<LevelCollectionViewController>().FirstOrDefault();
- var levelflow = Resources.FindObjectsOfTypeAll<LevelSelectionFlowCoordinator>().FirstOrDefault();
- var pack = levelflow.GetProperty<IBeatmapLevelPack>("selectedBeatmapLevelPack");
- IBeatmapLevelPack[] sectionpacks = new IBeatmapLevelPack[0];
- var selectedcategory = levelflow.GetProperty<SelectLevelCategoryViewController.LevelCategory>("selectedLevelCategory");
- switch (selectedcategory)
- {
- case SelectLevelCategoryViewController.LevelCategory.OstAndExtras:
- sectionpacks = controller.GetField<IBeatmapLevelPack[]>("_ostBeatmapLevelPacks");
- break;
- case SelectLevelCategoryViewController.LevelCategory.MusicPacks:
- sectionpacks = controller.GetField<IBeatmapLevelPack[]>("_musicPacksBeatmapLevelPacks");
- break;
- case SelectLevelCategoryViewController.LevelCategory.CustomSongs:
- sectionpacks = controller.GetField<IBeatmapLevelPack[]>("_customLevelPacks");
- break;
- case SelectLevelCategoryViewController.LevelCategory.All:
- sectionpacks = controller.GetField<IBeatmapLevelPack[]>("_allBeatmapLevelPacks");
- break;
- case SelectLevelCategoryViewController.LevelCategory.Favorites:
- return;
- }
- if (!sectionpacks.ToList().Contains(pack))
- pack = sectionpacks.FirstOrDefault();
- if (pack == null) return;
- controller.Setup(SongPackMask.all, pack, selectedcategory, false, true);
- */
- //controller.SelectAnnotatedBeatmapLevelCollection(pack);
- // collectionview.SetData(pack.beatmapLevelCollection, pack.packName, pack.coverImage, false, controller.GetField<GameObject>("_currentNoDataInfoPrefab"));
- }
- public void RefreshSongs(bool fullRefresh = true)
- {
- if (SceneManager.GetActiveScene().name == "GameCore") return;
- if (AreSongsLoading) return;
- Logging.Log(fullRefresh ? "Starting full song refresh" : "Starting song refresh");
- AreSongsLoaded = false;
- AreSongsLoading = true;
- LoadingProgress = 0;
- _loadingCancelled = false;
- if (LoadingStartedEvent != null)
- {
- try
- {
- LoadingStartedEvent(this);
- }
- catch (Exception e)
- {
- Logging.Log("Some plugin is throwing exception from the LoadingStartedEvent!", IPA.Logging.Logger.Level.Error);
- Logging.Log(e.ToString(), IPA.Logging.Logger.Level.Error);
- }
- }
- RetrieveAllSongs(fullRefresh);
- }
- private void RetrieveAllSongs(bool fullRefresh)
- {
- var stopwatch = new Stopwatch();
- #region ClearAllDictionaries
- // Clear all beatmap dictionaries on full refresh
- if (fullRefresh)
- {
- CustomBeatmapLevelPackCollectionSO = null;
- CustomLevels.Clear();
- CustomWIPLevels.Clear();
- CachedWIPLevels.Clear();
- Collections.levelHashDictionary.Clear();
- Collections.hashLevelDictionary.Clear();
- foreach (var folder in SeperateSongFolders) folder.Levels.Clear();
- }
- #endregion ClearAllDictionaries
- var foundSongPaths = fullRefresh
- ? new ConcurrentDictionary<string, bool>()
- : new ConcurrentDictionary<string, bool>(Hashing.cachedSongHashData.Keys.ToDictionary(x => x, _ => false));
- var baseProjectPath = CustomLevelPathHelper.baseProjectPath;
- var customLevelsPath = CustomLevelPathHelper.customLevelsDirectoryPath;
- Action job = delegate
- {
- #region AddOfficialBeatmaps
- try
- {
- void AddOfficialPackCollection(IBeatmapLevelPackCollection packCollection)
- {
- foreach (var pack in packCollection.beatmapLevelPacks)
- {
- foreach (var level in pack.beatmapLevelCollection.beatmapLevels)
- {
- OfficialSongs[level.levelID] = new OfficialSongEntry()
- {
- LevelPackCollection = packCollection,
- LevelPack = pack,
- PreviewBeatmapLevel = level
- };
- }
- }
- }
- OfficialSongs.Clear();
- AddOfficialPackCollection(BeatmapLevelsModelSO.ostAndExtrasPackCollection);
- AddOfficialPackCollection(BeatmapLevelsModelSO.dlcBeatmapLevelPackCollection);
- }
- catch (Exception ex)
- {
- Logging.logger.Error($"Error populating official songs: {ex.Message}");
- Logging.logger.Debug(ex);
- }
- #endregion AddOfficialBeatmaps
- #region AddCustomBeatmaps
- try
- {
- #region DirectorySetup
- var path = CustomLevelPathHelper.baseProjectPath;
- path = path.Replace('\\', '/');
- if (!Directory.Exists(customLevelsPath))
- {
- Directory.CreateDirectory(customLevelsPath);
- }
- if (!Directory.Exists(baseProjectPath + "/CustomWIPLevels"))
- {
- Directory.CreateDirectory(baseProjectPath + "/CustomWIPLevels");
- }
- #endregion DirectorySetup
- #region CacheZipWIPs
- // Get zip files in CustomWIPLevels and extract them to Cache folder
- if (fullRefresh)
- {
- try
- {
- var wipPath = Path.Combine(path, "CustomWIPLevels");
- var cachePath = Path.Combine(path, "CustomWIPLevels", "Cache");
- CacheZIPs(cachePath, wipPath);
- var cacheFolders = Directory.GetDirectories(cachePath).ToArray();
- LoadCachedZIPs(cacheFolders, fullRefresh, CachedWIPLevels);
- }
- catch (Exception ex)
- {
- Logging.logger.Error("Failed To Load Cached WIP Levels: " + ex);
- }
- }
- #endregion CacheZipWIPs
- #region CacheSeperateZIPs
- if (fullRefresh)
- {
- foreach (var songFolder in SeperateSongFolders)
- {
- if (songFolder.SongFolderEntry.CacheZIPs && songFolder.CacheFolder != null)
- {
- var cacheFolder = songFolder.CacheFolder;
- try
- {
- CacheZIPs(cacheFolder.SongFolderEntry.Path, songFolder.SongFolderEntry.Path);
- }
- catch (Exception ex)
- {
- Logging.logger.Error("Failed To Load Cached WIP Levels: " + ex);
- }
- }
- }
- }
- #endregion CacheSeperateZIPs
- //Thread.Sleep(1000 * 1); // waiting for ui, dont hang up
- stopwatch.Start();
- #region LoadCustomLevels
- // Get Levels from CustomLevels and CustomWIPLevels folders
- var songFolders = ExtractCustomLevelFoldersFromArchive().Concat(Directory.GetDirectories(Path.Combine(path, "CustomWIPLevels"))).ToArray();
- var loadedData = new ConcurrentBag<string>();
- var processedSongsCount = 0;
- Parallel.ForEach(songFolders, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount - 1 }, (folder) =>
- {
- var result = Path.Combine(folder, "info.dat");
- if (ArchiveExists(result) == false)
- {
- Logging.Log("Folder: '" + folder + "' is missing info.dat files!", LogSeverity.Notice);
- return;
- }
- //string[] results;
- //try
- //{
- // results = Directory.GetFiles(folder, "info.dat", SearchOption.TopDirectoryOnly);
- //}
- //catch (DirectoryNotFoundException ex)
- //{
- // Logging.Log($"Skipping missing or corrupt folder: '{folder}'", LogSeverity.Warning);
- // return;
- //}
- //if (results.Length == 0)
- //{
- // Logging.Log("Folder: '" + folder + "' is missing info.dat files!", LogSeverity.Notice);
- // return;
- //}
- //foreach (var result in results)
- {
- try
- {
- var songPath = Path.GetDirectoryName(result.Replace('\\', '/'));
- if (ExtractParentDirectoryName(songPath) != "Backups")
- {
- if (!fullRefresh)
- {
- if (CustomLevels.TryGetValue(songPath, out var c))
- {
- if (c != null)
- {
- loadedData.Add(c.levelID);
- goto jumpOutLoop;
- }
- }
- }
- var wip = songPath.Contains("CustomWIPLevels");
- var saveData = GetStandardLevelInfoSaveData(songPath);
- if (saveData != null)
- {
- if (_loadingCancelled) return;
- var level = LoadSongAndAddToDictionaries(saveData, songPath);
- if (level != null)
- {
- if (!wip)
- {
- CustomLevelsById[level.levelID] = level;
- CustomLevels[songPath] = level;
- }
- else
- CustomWIPLevels[songPath] = level;
- foundSongPaths.TryAdd(songPath, false);
- }
- }
- // if (loadedData.Any(x => x == saveData.))
- // {
- // Logging.Log("Duplicate song found at " + songPath, LogSeverity.Notice);
- // continue;
- // }
- // loadedData.Add(saveDat);
- //HMMainThreadDispatcher.instance.Enqueue(delegate
- //{
- }
- }
- catch (Exception e)
- {
- Logging.Log("Failed to load song folder: " + result, LogSeverity.Error);
- Logging.Log(e.ToString(), LogSeverity.Error);
- }
- }
- jumpOutLoop:
- LoadingProgress = (float)Interlocked.Increment(ref processedSongsCount) / songFolders.Length;
- });
- #endregion LoadCustomLevels
- #region LoadSeperateFolders
- // Load beatmaps in Seperate Song Folders (created in folders.xml or by other mods)
- // Assign beatmaps to their respective pack (custom levels, wip levels, or seperate)
- for (var k = 0; k < SeperateSongFolders.Count; k++)
- {
- try
- {
- var entry = SeperateSongFolders[k];
- Instance._progressBar.ShowMessage("Loading " + (SeperateSongFolders.Count - k) + " Additional Song folders");
- if (!Directory.Exists(entry.SongFolderEntry.Path)) continue;
- var entryFolders = Directory.GetDirectories(entry.SongFolderEntry.Path).ToList();
- float i2 = 0;
- foreach (var folder in entryFolders)
- {
- i2++;
- // Search for an info.dat in the beatmap folder
- string[] results;
- try
- {
- results = Directory.GetFiles(folder, "info.dat", SearchOption.TopDirectoryOnly);
- }
- catch (DirectoryNotFoundException ex)
- {
- Logging.Log($"Skipping missing or corrupt folder: '{folder}'", LogSeverity.Warning);
- continue;
- }
- if (results.Length == 0)
- {
- Logging.Log("Folder: '" + folder + "' is missing info.dat files!", LogSeverity.Notice);
- continue;
- }
- foreach (var result in results)
- {
- try
- {
- // On quick refresh: Check if the beatmap directory is already present in the respective beatmap dictionary
- // If it is already present on a non full refresh, it will be ignored (changes to the beatmap will not be applied)
- var songPath = Path.GetDirectoryName(result.Replace('\\', '/'));
- if (!fullRefresh)
- {
- if (entry.SongFolderEntry.Pack == FolderLevelPack.NewPack && SearchBeatmapInMapPack(entry.Levels, songPath)) continue;
- else if (entry.SongFolderEntry.Pack == FolderLevelPack.CustomLevels && SearchBeatmapInMapPack(CustomLevels, songPath)) continue;
- else if (entry.SongFolderEntry.Pack == FolderLevelPack.CustomWIPLevels && SearchBeatmapInMapPack(CustomWIPLevels, songPath)) continue;
- else if (entry.SongFolderEntry.Pack == FolderLevelPack.CachedWIPLevels && SearchBeatmapInMapPack(CachedWIPLevels, songPath)) continue;
- }
- if (entry.SongFolderEntry.Pack == FolderLevelPack.CustomLevels || (entry.SongFolderEntry.Pack == FolderLevelPack.NewPack && entry.SongFolderEntry.WIP == false))
- {
- if (AssignBeatmapToSeperateFolder(CustomLevels, songPath, entry.Levels)) continue;
- if (AssignBeatmapToSeperateFolder(CustomWIPLevels, songPath, entry.Levels)) continue;
- if (AssignBeatmapToSeperateFolder(CachedWIPLevels, songPath, entry.Levels)) continue;
- }
- var saveData = GetStandardLevelInfoSaveData(songPath);
- if (saveData == null)
- {
- // Logging.Log("Null save data", LogSeverity.Notice);
- continue;
- }
- var count = i2;
- //HMMainThreadDispatcher.instance.Enqueue(delegate
- //{
- if (_loadingCancelled) return;
- var level = LoadSongAndAddToDictionaries(saveData, songPath, entry.SongFolderEntry);
- if (level != null)
- {
- entry.Levels[songPath] = level;
- CustomLevelsById[level.levelID] = level;
- foundSongPaths.TryAdd(songPath, false);
- }
- LoadingProgress = count / entryFolders.Count;
- //});
- }
- catch (Exception e)
- {
- Logging.Log("Failed to load song folder: " + result, LogSeverity.Error);
- Logging.Log(e.ToString(), LogSeverity.Error);
- }
- }
- }
- }
- catch (Exception ex)
- {
- Logging.Log($"Failed to load Seperate Folder{SeperateSongFolders[k].SongFolderEntry.Name}" + ex, LogSeverity.Error);
- }
- }
- #endregion LoadSeperateFolders
- }
- catch (Exception e)
- {
- Logging.Log("RetrieveAllSongs failed:", LogSeverity.Error);
- Logging.Log(e.ToString(), LogSeverity.Error);
- }
- #endregion AddCustomBeatmaps
- };
- Action finish = delegate
- {
- #region CountBeatmapsAndUpdateLevelPacks
- stopwatch.Stop();
- var songCount = CustomLevels.Count + CustomWIPLevels.Count;
- var songCountWSF = songCount;
- foreach (var f in SeperateSongFolders)
- songCount += f.Levels.Count;
- Logging.Log($"Loaded {songCount} new songs ({songCountWSF}) in CustomLevels | {songCount - songCountWSF} in seperate folders) in {stopwatch.Elapsed.TotalSeconds} seconds");
- try
- {
- //Handle LevelPacks
- if (CustomBeatmapLevelPackCollectionSO == null || CustomBeatmapLevelPackCollectionSO.beatmapLevelPacks.Length == 0)
- {
- #region AddSeperateFolderBeatmapsToRespectivePacks
- var beatmapLevelPackCollectionSO = Resources.FindObjectsOfTypeAll<BeatmapLevelPackCollectionSO>().FirstOrDefault();
- CustomBeatmapLevelPackCollectionSO = SongCoreBeatmapLevelPackCollectionSO.CreateNew(); // (beatmapLevelPackCollectionSO);
- foreach (var folderEntry in SeperateSongFolders)
- {
- switch (folderEntry.SongFolderEntry.Pack)
- {
- case FolderLevelPack.CustomLevels:
- CustomLevels = new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>(CustomLevels.Concat(folderEntry.Levels.Where(x => !CustomLevels.ContainsKey(x.Key))).ToDictionary(x => x.Key, x => x.Value));
- break;
- case FolderLevelPack.CustomWIPLevels:
- CustomWIPLevels = new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>(CustomWIPLevels.Concat(folderEntry.Levels.Where(x => !CustomWIPLevels.ContainsKey(x.Key))).ToDictionary(x => x.Key, x => x.Value));
- break;
- case FolderLevelPack.CachedWIPLevels:
- CachedWIPLevels = new ConcurrentDictionary<string, CustomPreviewBeatmapLevel>(CachedWIPLevels.Concat(folderEntry.Levels.Where(x => !CachedWIPLevels.ContainsKey(x.Key))).ToDictionary(x => x.Key, x => x.Value));
- break;
- default:
- break;
- }
- }
- #endregion AddSeperateFolderBeatmapsToRespectivePacks
- Logging.logger.Info("XXYYZZ AddSeperateFolderBeatmapsToRespectivePacks done");
- #region CreateLevelPacks
- // Create level collections and level packs
- // Add level packs to the custom levels pack collection
- CustomLevelsCollection = new SongCoreCustomLevelCollection(CustomLevels.Values.ToArray());
- WIPLevelsCollection = new SongCoreCustomLevelCollection(CustomWIPLevels.Values.ToArray());
- CachedWIPLevelCollection = new SongCoreCustomLevelCollection(CachedWIPLevels.Values.ToArray());
- CustomLevelsPack = new SongCoreCustomBeatmapLevelPack(CustomLevelLoader.kCustomLevelPackPrefixId + "CustomLevels", "Custom Levels", defaultCoverImage, CustomLevelsCollection);
- WIPLevelsPack = new SongCoreCustomBeatmapLevelPack(CustomLevelLoader.kCustomLevelPackPrefixId + "CustomWIPLevels", "WIP Levels", UI.BasicUI.WIPIcon, WIPLevelsCollection);
- CachedWIPLevelsPack = new SongCoreCustomBeatmapLevelPack(CustomLevelLoader.kCustomLevelPackPrefixId + "CachedWIPLevels", "Cached WIP Levels", UI.BasicUI.WIPIcon, CachedWIPLevelCollection);
- CustomBeatmapLevelPackCollectionSO.AddLevelPack(CustomLevelsPack);
- CustomBeatmapLevelPackCollectionSO.AddLevelPack(WIPLevelsPack);
- CustomBeatmapLevelPackCollectionSO.AddLevelPack(CachedWIPLevelsPack);
- #endregion CreateLevelPacks
- Logging.logger.Info("XXYYZZ CreateLevelPacks done");
- }
- //Level Packs
- RefreshLevelPacks();
- Logging.logger.Info("XXYYZZ RefreshLevelPacks return");
- }
- catch (Exception ex)
- {
- Logging.logger.Error("Failed to Setup LevelPacks: " + ex);
- }
- #endregion CountBeatmapsAndUpdateLevelPacks
- AreSongsLoaded = true;
- AreSongsLoading = false;
- LoadingProgress = 1;
- _loadingTask = null;
- try
- {
- var arr = SongsLoadedEvent.GetInvocationList();
- Logging.logger.Info($"XXYYZZ SongsLoadedEvent.GetInvocationList : {arr.Length}");
- foreach (var item in arr)
- {
- Logging.logger.Info($"XXYYZZ -> {item.Method.DeclaringType.AssemblyQualifiedName}::{item.Method}");
- item.DynamicInvoke(this, CustomLevels);
- Logging.logger.Info($"XXYYZZ <- Return");
- }
- }
- catch (Exception e)
- {
- Logging.logger.Error($"XXYYZZ SongsLoadedEvent.GetInvocationList:{e.Message}");
- }
- //SongsLoadedEvent?.Invoke(this, CustomLevels);
- // Write our cached hash info and
- //Hashing.UpdateCachedHashesInternal(foundSongPaths.Keys);
- //Hashing.UpdateCachedAudioDataInternal(foundSongPaths.Keys);
- Logging.logger.Info("XXYYZZ SaveExtraSongData Call");
- SongCore.Collections.SaveExtraSongData();
- Logging.logger.Info("XXYYZZ Leave finish task");
- };
- _loadingTask = new HMTask(job, finish);
- _loadingTask.Run();
- }
- public static StandardLevelInfoSaveData GetStandardLevelInfoSaveData(string path)
- {
- var text = ReadJson(path + "/info.dat");
- return StandardLevelInfoSaveData.DeserializeFromJSONString(text);
- }
- /// <summary>
- /// Delete a beatmap (is only used by other mods)
- /// </summary>
- /// <param name="folderPath">Directory of the beatmap</param>
- /// <param name="deleteFolder">Option to delete the base folder of the beatmap</param>
- public void DeleteSong(string folderPath, bool deleteFolder = true)
- {
- DeletingSong?.Invoke();
- //Remove the level from SongCore Collections
- try
- {
- if (CustomLevels.TryRemove(folderPath, out var level))
- {
- }
- else if (CustomWIPLevels.TryRemove(folderPath, out level))
- {
- }
- else if (CachedWIPLevels.TryRemove(folderPath, out level))
- {
- }
- else
- {
- foreach (var folderEntry in SeperateSongFolders)
- {
- if (folderEntry.Levels.TryRemove(folderPath, out level))
- {
- }
- }
- }
- if (level != null)
- {
- if (Collections.levelHashDictionary.ContainsKey(level.levelID))
- {
- var hash = Collections.hashForLevelID(level.levelID);
- Collections.levelHashDictionary.TryRemove(level.levelID, out _);
- if (Collections.hashLevelDictionary.ContainsKey(hash))
- {
- Collections.hashLevelDictionary[hash].Remove(level.levelID);
- if (Collections.hashLevelDictionary[hash].Count == 0)
- Collections.hashLevelDictionary.TryRemove(hash, out _);
- }
- }
- Hashing.UpdateCachedHashes(new HashSet<string>((CustomLevels.Keys.Concat(CustomWIPLevels.Keys))));
- }
- //Delete the directory
- if (deleteFolder)
- if (Directory.Exists(folderPath))
- {
- Directory.Delete(folderPath, true);
- }
- RefreshLevelPacks();
- }
- catch (Exception ex)
- {
- Logging.Log("Exception trying to Delete song: " + folderPath, LogSeverity.Error);
- Logging.Log(ex.ToString(), LogSeverity.Error);
- }
- }
- /*
- public void RetrieveNewSong(string folderPath)
- {
- try
- {
- bool wip = false;
- if (folderPath.Contains("CustomWIPLevels"))
- wip = true;
- StandardLevelInfoSaveData saveData = GetStandardLevelInfoSaveData(folderPath);
- var level = LoadSong(saveData, folderPath, out string hash);
- if (level != null)
- {
- if (!wip)
- CustomLevels[folderPath] = level;
- else
- CustomWIPLevels[folderPath] = level;
- if (!Collections.levelHashDictionary.ContainsKey(level.levelID))
- {
- Collections.levelHashDictionary.Add(level.levelID, hash);
- if (Collections.hashLevelDictionary.ContainsKey(hash))
- Collections.hashLevelDictionary[hash].Add(level.levelID);
- else
- {
- var levels = new List<string>();
- levels.Add(level.levelID);
- Collections.hashLevelDictionary.Add(hash, levels);
- }
- }
- }
- HashSet<string> paths = new HashSet<string>( Hashing.cachedSongHashData.Keys);
- paths.Add(folderPath);
- Hashing.UpdateCachedHashes(paths);
- RefreshLevelPacks();
- }
- catch (Exception ex)
- {
- Logging.Log("Failed to Retrieve New Song from: " + folderPath, LogSeverity.Error);
- Logging.Log(ex.ToString(), LogSeverity.Error);
- }
- }
- */
- /// <summary>
- /// Load a beatmap, gather all beatmap information and create beatmap preview
- /// </summary>
- /// <param name="saveData">Save data of beatmap</param>
- /// <param name="songPath">Directory of beatmap</param>
- /// <param name="hash">Resulting hash for the beatmap, may contain beatmap folder name or 'WIP' at the end</param>
- /// <param name="folderEntry">Folder entry for beatmap folder</param>
- /// <returns></returns>
- public static CustomPreviewBeatmapLevel LoadSong(StandardLevelInfoSaveData saveData, string songPath, out string hash, SongFolderEntry folderEntry = null)
- {
- CustomPreviewBeatmapLevel result;
- var wip = songPath.Contains("CustomWIPLevels");
- if (folderEntry != null)
- {
- if ((folderEntry.Pack == FolderLevelPack.CustomWIPLevels) || (folderEntry.Pack == FolderLevelPack.CachedWIPLevels))
- wip = true;
- else if (folderEntry.WIP)
- wip = true;
- }
- hash = Hashing.GetCustomLevelHash(saveData, songPath);
- try
- {
- var folderName = ExtractParentDirectoryName(songPath);
- var levelID = CustomLevelLoader.kCustomLevelPrefixId + hash;
- // Fixed WIP status for duplicate song hashes
- if (Collections.levelHashDictionary.ContainsKey(levelID + (wip ? " WIP" : "")))
- levelID += "_" + folderName;
- if (wip) levelID += " WIP";
- var songName = saveData.songName;
- var songSubName = saveData.songSubName;
- var songAuthorName = saveData.songAuthorName;
- var levelAuthorName = saveData.levelAuthorName;
- var beatsPerMinute = saveData.beatsPerMinute;
- var songTimeOffset = saveData.songTimeOffset;
- var shuffle = saveData.shuffle;
- var shufflePeriod = saveData.shufflePeriod;
- var previewStartTime = saveData.previewStartTime;
- var previewDuration = saveData.previewDuration;
- var environmentSceneInfo = _customLevelLoader.LoadEnvironmentInfo(saveData.environmentName, false);
- var allDirectionEnvironmentInfo = _customLevelLoader.LoadEnvironmentInfo(saveData.allDirectionsEnvironmentName, true);
- var list = new List<PreviewDifficultyBeatmapSet>();
- foreach (var difficultyBeatmapSet in saveData.difficultyBeatmapSets)
- {
- var beatmapCharacteristicBySerializedName = beatmapCharacteristicCollection.GetBeatmapCharacteristicBySerializedName(difficultyBeatmapSet.beatmapCharacteristicName);
- var array = new BeatmapDifficulty[difficultyBeatmapSet.difficultyBeatmaps.Length];
- for (var j = 0; j < difficultyBeatmapSet.difficultyBeatmaps.Length; j++)
- {
- BeatmapDifficulty beatmapDifficulty;
- difficultyBeatmapSet.difficultyBeatmaps[j].difficulty.BeatmapDifficultyFromSerializedName(out beatmapDifficulty);
- array[j] = beatmapDifficulty;
- }
- list.Add(new PreviewDifficultyBeatmapSet(beatmapCharacteristicBySerializedName, array));
- }
- result = new CustomPreviewBeatmapLevel(defaultCoverImage, saveData, songPath,
- cachedMediaAsyncLoaderSO, cachedMediaAsyncLoaderSO, levelID, songName, songSubName,
- songAuthorName, levelAuthorName, beatsPerMinute, songTimeOffset, shuffle, shufflePeriod,
- previewStartTime, previewDuration, environmentSceneInfo, allDirectionEnvironmentInfo, list.ToArray());
- GetSongDuration(result, songPath, Path.Combine(songPath, saveData.songFilename));
- //Task.Factory.StartNew(() => { GetSongDuration(result, songPath, Path.Combine(songPath, saveData.songFilename));});
- }
- catch
- {
- Logging.Log("Failed to Load Song: " + songPath, LogSeverity.Error);
- result = null;
- }
- return result;
- }
- /// <summary>
- /// Refresh songs on "R" key, full refresh on "Ctrl"+"R"
- /// </summary>
- private void Update()
- {
- if (Input.GetKeyDown(KeyCode.R))
- {
- RefreshSongs(Input.GetKey(KeyCode.LeftControl));
- }
- }
- #region HelperFunctionsZIP
- /// <summary>
- /// Extracts beatmap ZIP files to the cache folder
- /// </summary>
- /// <param name="cachePath">Directory of cache folder</param>
- /// <param name="songFolderPath">Directory of folder containing the zips</param>
- private void CacheZIPs(string cachePath, string songFolderPath)
- {
- if (!Directory.Exists(cachePath))
- Directory.CreateDirectory(cachePath);
- var cache = new DirectoryInfo(cachePath);
- foreach (var file in cache.GetFiles())
- file.Delete();
- foreach (var folder in cache.GetDirectories())
- folder.Delete(true);
- var zips = Directory.GetFiles(songFolderPath, "*.zip", SearchOption.TopDirectoryOnly);
- foreach (var zip in zips)
- {
- var unzip = new Unzip(zip);
- try
- {
- unzip.ExtractToDirectory(cachePath + "/" + new FileInfo(zip).Name);
- }
- catch (Exception ex)
- {
- Logging.logger.Warn("Failed to extract zip: " + zip + ": " + ex);
- }
- unzip.Dispose();
- }
- }
- /// <summary>
- /// Loads the beatmaps of the cached
- /// </summary>
- /// <param name="cacheFolders">Directory of cache folder</param>
- /// <param name="fullRefresh"></param>
- /// <param name="BeatmapDictionary"></param>
- /// <param name="folderEntry"></param>
- private void LoadCachedZIPs(string[] cacheFolders, bool fullRefresh, ConcurrentDictionary<string, CustomPreviewBeatmapLevel> BeatmapDictionary, SongFolderEntry folderEntry = null)
- {
- foreach (var cachedFolder in cacheFolders)
- {
- string[] results;
- try
- {
- results = Directory.GetFiles(cachedFolder, "info.dat", SearchOption.TopDirectoryOnly);
- }
- catch (DirectoryNotFoundException ex)
- {
- Logging.Log($"Skipping missing or corrupt folder: '{cachedFolder}'", LogSeverity.Warning);
- continue;
- }
- if (results.Length == 0)
- {
- Logging.Log("Folder: '" + cachedFolder + "' is missing info.dat files!", LogSeverity.Notice);
- continue;
- }
- foreach (var result in results)
- {
- try
- {
- var songPath = Path.GetDirectoryName(result.Replace('\\', '/'));
- if (!fullRefresh && BeatmapDictionary != null)
- {
- if (SearchBeatmapInMapPack(BeatmapDictionary, songPath)) continue;
- }
- var saveData = GetStandardLevelInfoSaveData(songPath);
- if (saveData == null)
- {
- continue;
- }
- HMMainThreadDispatcher.instance.Enqueue(delegate
- {
- if (_loadingCancelled) return;
- var level = LoadSong(saveData, songPath, out var hash, folderEntry);
- if (level != null)
- {
- BeatmapDictionary[songPath] = level;
- }
- });
- }
- catch (Exception ex)
- {
- Logging.logger.Notice("Failed to load song from " + cachedFolder + ": " + ex);
- }
- }
- }
- }
- #endregion HelperFunctionsZIP
- #region HelperFunctionsLoading
- private bool SearchBeatmapInMapPack(ConcurrentDictionary<string, CustomPreviewBeatmapLevel> mapPack, string songPath)
- {
- if (mapPack.TryGetValue(songPath, out var c))
- {
- if (c != null) return true;
- }
- return false;
- }
- private bool AssignBeatmapToSeperateFolder(ConcurrentDictionary<string, CustomPreviewBeatmapLevel> mapPack, string songPath, ConcurrentDictionary<string, CustomPreviewBeatmapLevel> seperateFolder)
- {
- if (mapPack.TryGetValue(songPath, out var c))
- {
- if (c != null)
- {
- seperateFolder[songPath] = c;
- return true;
- }
- }
- return false;
- }
- private CustomPreviewBeatmapLevel LoadSongAndAddToDictionaries(StandardLevelInfoSaveData saveData, string songPath, SongFolderEntry entry = null)
- {
- var level = LoadSong(saveData, songPath, out var hash, entry);
- if (level != null)
- {
- if (!Collections.levelHashDictionary.ContainsKey(level.levelID))
- {
- // Add level to LevelHash-Dictionary
- Collections.levelHashDictionary.TryAdd(level.levelID, hash);
- // Add hash to HashLevel-Dictionary
- if (Collections.hashLevelDictionary.TryGetValue(hash, out var levels))
- levels.Add(level.levelID);
- else
- {
- levels = new List<string>();
- levels.Add(level.levelID);
- Collections.hashLevelDictionary.TryAdd(hash, levels);
- }
- }
- }
- return level;
- }
- #endregion HelperFunctionsLoading
- #region HelperFunctionsSearching
- /// <summary>
- /// Attempts to get a beatmap by LevelId. Returns null a matching level isn't found.
- /// </summary>
- /// <param name="levelId"></param>
- /// <returns></returns>
- public static IPreviewBeatmapLevel GetLevelById(string levelId)
- {
- if (string.IsNullOrEmpty(levelId))
- return null;
- IPreviewBeatmapLevel level = null;
- if (levelId.StartsWith("custom_level_"))
- {
- if (CustomLevelsById.TryGetValue(levelId, out var customLevel))
- level = customLevel;
- }
- else if (OfficialSongs.TryGetValue(levelId, out var song))
- {
- level = song.PreviewBeatmapLevel;
- }
- return level;
- }
- /// <summary>
- /// Attempts to get a custom level by hash (case-insensitive). Returns null a matching custom level isn't found.
- /// </summary>
- /// <param name="hash"></param>
- /// <returns></returns>
- public static CustomPreviewBeatmapLevel GetLevelByHash(string hash)
- {
- if (string.IsNullOrEmpty(hash))
- return null;
- CustomLevelsById.TryGetValue("custom_level_" + hash.ToUpper(), out var level);
- return level;
- }
- private static BeatmapDataLoader loader = new BeatmapDataLoader();
- private static void GetSongDuration(CustomPreviewBeatmapLevel level, string songPath, string oggfile)
- {
- try
- {
- var levelid = level.levelID;
- float length = 0;
- if (Hashing.cachedAudioData.TryGetValue(songPath, out var data))
- {
- if (data.id == levelid)
- length = data.duration;
- }
- if (length == 0)
- {
- try
- {
- length = GetLengthFromOgg(oggfile);
- }
- catch (Exception ex)
- {
- length = -1;
- }
- if (length <= 1)
- {
- // janky, but whatever
- Logging.logger.Warn($"Failed to parse song length from Ogg file, Approximating using Map length. Song: {level.customLevelPath}");
- length = GetLengthFromMap(level, songPath);
- }
- }
- if (data != null)
- {
- data.duration = length;
- data.id = levelid;
- }
- else
- {
- Hashing.cachedAudioData[songPath] = new AudioCacheData(levelid, length);
- }
- // Logging.logger.Debug($"{length}");
- level.SetField<CustomPreviewBeatmapLevel, float>("_songDuration", length);
- }
- catch (Exception ex)
- {
- Logging.logger.Warn("Failed to Parse Song Duration" + ex);
- }
- }
- public static float GetLengthFromMap(CustomPreviewBeatmapLevel level, string songPath)
- {
- var diff = level.standardLevelInfoSaveData.difficultyBeatmapSets.First().difficultyBeatmaps.Last().beatmapFilename;
- var beatmapsave = BeatmapSaveData.DeserializeFromJSONString(ReadJson(Path.Combine(songPath, diff)));
- float highestTime = 0;
- if (beatmapsave.notes.Count > 0)
- highestTime = beatmapsave.notes.Max(x => x.time);
- else if (beatmapsave.events.Count > 0)
- highestTime = beatmapsave.events.Max(x => x.time);
- return loader.GetRealTimeFromBPMTime(highestTime, level.beatsPerMinute, level.shuffle, level.shufflePeriod);
- }
- private static byte[] oggBytes = new byte[] { 0x4F, 0x67, 0x67, 0x53, 0x00, 0x04 };
- public static float GetLengthFromOgg(string oggFile)
- {
- if (GetCachedOggLength(oggFile, out var len)) return len;
- using (var fs = File.OpenRead(oggFile))
- using (var br = new BinaryReader(fs, Encoding.ASCII))
- {
- /*
- * Tries to find the array of bytes from the stream
- */
- bool FindBytes(byte[] bytes, int searchLength)
- {
- for (var i = 0; i < searchLength; i++)
- {
- var b = br.ReadByte();
- if (b != bytes[0]) continue;
- var by = br.ReadBytes(bytes.Length - 1);
- // hardcoded 6 bytes compare, is fine because all inputs used are 6 bytes
- // bitwise AND the last byte to read only the flag bit for lastSample searching
- // shouldn't cause issues finding rate, hopefully
- if (by[0] == bytes[1] && by[1] == bytes[2] && by[2] == bytes[3] && by[3] == bytes[4] && (by[4] & bytes[5]) == bytes[5]) return true;
- var index = Array.IndexOf(@by, bytes[0]);
- if (index != -1)
- {
- fs.Position += index - (bytes.Length - 1);
- i += index;
- }
- else
- i += (bytes.Length - 1);
- }
- return false;
- }
- var rate = -1;
- long lastSample = -1;
- //Skip Capture Pattern
- fs.Position = 24;
- //{0x76, 0x6F, 0x72, 0x62, 0x69, 0x73} = "vorbis" in byte values
- var foundVorbis = FindBytes(new byte[] { 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 }, 256);
- if (foundVorbis)
- {
- fs.Position += 5;
- rate = br.ReadInt32();
- }
- else
- {
- Logging.logger.Warn($"could not find rate for {oggFile}");
- return -1;
- }
- /*
- * this finds the last occurrence of OggS in the file by checking for a bit flag (0x04)
- * reads in blocks determined by seekBlockSize
- * 6144 does not add significant overhead and speeds up the search significantly
- */
- const int seekBlockSize = 6144;
- const int seekTries = 10; // 60 KiB should be enough for any sane ogg file
- for (var i = 0; i < seekTries; i++)
- {
- var seekPos = (i + 1) * seekBlockSize * -1;
- var overshoot = Math.Max((int)(-seekPos - fs.Length), 0);
- if (overshoot >= seekBlockSize)
- {
- break;
- }
- fs.Seek(seekPos + overshoot, SeekOrigin.End);
- var foundOggS = FindBytes(oggBytes, seekBlockSize - overshoot);
- if (foundOggS)
- {
- lastSample = br.ReadInt64();
- break;
- }
- }
- if (lastSample == -1)
- {
- Logging.logger.Warn($"could not find lastSample for {oggFile}");
- return -1;
- }
- var length = lastSample / (float)rate;
- return length;
- }
- }
- /// <summary>
- /// Attempts to get an official level by LevelId. Returns false if a matching level isn't found.
- /// </summary>
- /// <param name="levelId"></param>
- /// <returns></returns>
- public static bool TryGetOfficialLevelById(string levelId, out OfficialSongEntry song)
- {
- if (string.IsNullOrEmpty(levelId))
- {
- song = default(OfficialSongEntry);
- return false;
- }
- return OfficialSongs.TryGetValue(levelId, out song);
- }
- #endregion HelperFunctionsSearching
- public struct OfficialSongEntry
- {
- public IBeatmapLevelPackCollection LevelPackCollection;
- public IBeatmapLevelPack LevelPack;
- public IPreviewBeatmapLevel PreviewBeatmapLevel;
- }
- }
- }
|