public static IEnumerator LoadMapper() { if (SceneManager.GetActiveScene().name.StartsWith("03")) { yield break; } Settings.Instance.Reminder_Loading360Levels = false; CMInputCallbackInstaller.TestMode = true; yield return(SceneManager.LoadSceneAsync("00_FirstBoot", LoadSceneMode.Single)); PersistentUI.Instance.enableTransitions = false; // On pipeline this may be run fresh if (!Settings.ValidateDirectory(null)) { var firstBootMenu = Object.FindObjectOfType <FirstBootMenu>(); Settings.Instance.BeatSaberInstallation = "/root/bs"; firstBootMenu.HandleGenerateMissingFolders(0); } yield return(new WaitUntil(() => SceneManager.GetActiveScene().name.StartsWith("01") && !SceneTransitionManager.IsLoading)); BeatSaberSongContainer.Instance.song = new BeatSaberSong("testmap", new JSONObject()); var parentSet = new BeatSaberSong.DifficultyBeatmapSet("Lawless"); var diff = new BeatSaberSong.DifficultyBeatmap(parentSet); diff.customData = new JSONObject(); BeatSaberSongContainer.Instance.difficultyData = diff; BeatSaberSongContainer.Instance.loadedSong = AudioClip.Create("Fake", 44100 * 2, 1, 44100, false); SceneTransitionManager.Instance.LoadScene("03_Mapper"); yield return(new WaitUntil(() => !SceneTransitionManager.IsLoading)); }
public void Save(bool auto = false) { PersistentUI.Instance.DisplayMessage($"{(auto ? "Auto " : "")}Saving...", PersistentUI.DisplayMessageType.BOTTOM); SelectionController.RefreshMap(); //Make sure our map is up to date. if (BeatSaberSongContainer.Instance.map._customEvents.Any()) { if (Settings.Instance.Reminder_SavingCustomEvents) { //Example of advanced dialog box options. PersistentUI.Instance.ShowDialogBox("ChroMapper has detected you are using custom events in your map.\n\n" + "The current format for Custom Events goes against BeatSaver's enforced schema.\n" + "If you try to upload this map to BeatSaver, it will fail.", HandleCustomEventsDecision, "Ok", "Don't Remind Me"); } } new Thread(() => //I could very well move this to its own function but I need access to the "auto" variable. { Thread.CurrentThread.IsBackground = true; //Making sure this does not interfere with game thread //Fixes weird shit regarding how people write numbers (20,35 VS 20.35), causing issues in JSON //This should be thread-wide, but I have this set throughout just in case it isnt. Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; //Saving Map Data string originalMap = BeatSaberSongContainer.Instance.map.directoryAndFile; string originalSong = BeatSaberSongContainer.Instance.song.directory; if (auto) { Queue <string> directory = new Queue <string>(originalSong.Split('/').ToList()); directory.Enqueue("autosaves"); directory.Enqueue($"{DateTime.Now:dd-MM-yyyy_HH-mm-ss}"); //timestamp string autoSaveDir = string.Join("/", directory.ToArray()); Debug.Log($"Auto saved to: {autoSaveDir}"); //We need to create the autosave directory before we can save the .dat difficulty into it. System.IO.Directory.CreateDirectory(autoSaveDir); BeatSaberSongContainer.Instance.map.directoryAndFile = $"{autoSaveDir}\\{BeatSaberSongContainer.Instance.difficultyData.beatmapFilename}"; BeatSaberSongContainer.Instance.song.directory = autoSaveDir; } BeatSaberSongContainer.Instance.map.Save(); BeatSaberSongContainer.Instance.map.directoryAndFile = originalMap; BeatSaberSong.DifficultyBeatmapSet set = BeatSaberSongContainer.Instance.difficultyData.parentBeatmapSet; //Grab our set BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Remove(set); //Yeet it out BeatSaberSong.DifficultyBeatmap data = BeatSaberSongContainer.Instance.difficultyData; //Grab our diff data set.difficultyBeatmaps.Remove(data); //Yeet out our difficulty data if (BeatSaberSongContainer.Instance.difficultyData.customData == null) //if for some reason this be null, make new customdata { BeatSaberSongContainer.Instance.difficultyData.customData = new JSONObject(); } set.difficultyBeatmaps.Add(BeatSaberSongContainer.Instance.difficultyData); //Add back our difficulty data BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Add(set); //Add back our difficulty set BeatSaberSongContainer.Instance.song.SaveSong(); //Save BeatSaberSongContainer.Instance.song.directory = originalSong; //Revert directory if it was changed by autosave }).Start(); }
public void CreateNewDifficultyData() { if (!songDifficultySets.Any() || SelectedSet == null) { BeatSaberSong.DifficultyBeatmapSet set = new BeatSaberSong.DifficultyBeatmapSet( CharacteristicDropdownToBeatmapName[characteristicDropdown.value]); songDifficultySets.Add(set); } BeatSaberSong.DifficultyBeatmap data = new BeatSaberSong.DifficultyBeatmap(SelectedSet); songDifficultyData.Add(data); selectedDifficultyIndex = songDifficultyData.IndexOf(data); InitializeDifficultyPanel(selectedDifficultyIndex); PersistentUI.Instance.ShowDialogBox("Be sure to save the difficulty before editing!", null, PersistentUI.DialogBoxPresetType.Ok); }
public void CreateNewDifficultyData() { if (songDifficultySets.Any()) { BeatSaberSong.DifficultyBeatmap data = new BeatSaberSong.DifficultyBeatmap(songDifficultySets[selectedBeatmapSet]); songDifficultyData.Add(data); InitializeDifficultyPanel(); } else { BeatSaberSong.DifficultyBeatmapSet set = new BeatSaberSong.DifficultyBeatmapSet(); songDifficultySets.Add(set); BeatSaberSong.DifficultyBeatmap data = new BeatSaberSong.DifficultyBeatmap(songDifficultySets[selectedBeatmapSet]); songDifficultyData.Add(data); InitializeDifficultyPanel(); } PersistentUI.Instance.DisplayMessage("Be sure to save before editing the map!", PersistentUI.DisplayMessageType.BOTTOM); }
// Use this for initialization void Start() { try { //Init dat stuff clip = BeatSaberSongContainer.Instance.loadedSong; song = BeatSaberSongContainer.Instance.song; data = BeatSaberSongContainer.Instance.map; diff = BeatSaberSongContainer.Instance.difficultyData; offsetMS = (song.songTimeOffset) / 1000; ResetTime(); offsetBeat = currentBeat; gridStartPosition = currentBeat * EditorScaleController.EditorScale; IsPlaying = false; songAudioSource.clip = clip; UpdateMovables(); } catch (Exception e) { Debug.LogException(e); } }
IEnumerator GetSongFromDifficultyData(BeatSaberMap map) { BeatSaberSong.DifficultyBeatmap data = songDifficultyData[selectedDifficultyIndex]; string directory = Song.directory; if (File.Exists(directory + "/" + Song.songFilename)) { if (Song.songFilename.ToLower().EndsWith("ogg") || Song.songFilename.ToLower().EndsWith("egg")) { UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip($"file:///{Uri.EscapeDataString($"{directory}/{Song.songFilename}")}", AudioType.OGGVORBIS); //Escaping should fix the issue where half the people can't open ChroMapper's editor (I believe this is caused by spaces in the directory, hence escaping) yield return(www.SendWebRequest()); Debug.Log("Song loaded!"); AudioClip clip = DownloadHandlerAudioClip.GetContent(www); if (clip == null) { Debug.Log("Error getting Audio data!"); SceneTransitionManager.Instance.CancelLoading("Error getting Audio data!"); } clip.name = "Song"; BeatSaberSongContainer.Instance.loadedSong = clip; BeatSaberSongContainer.Instance.difficultyData = data; //TransitionToEditor(map, clip, data); } else { Debug.Log("Incompatible file type! WTF!?"); SceneTransitionManager.Instance.CancelLoading("Incompatible audio type!"); } } else { SceneTransitionManager.Instance.CancelLoading("Audio file does not exist!"); Debug.Log("Song does not exist! WTF!?"); Debug.Log(directory + "/" + Song.songFilename); } }
public IEnumerator LoadMap() { if (BeatSaberSongContainer.Instance == null) { yield break; } PersistentUI.Instance.LevelLoadSlider.gameObject.SetActive(true); PersistentUI.Instance.LevelLoadSliderLabel.text = ""; yield return(new WaitUntil(() => atsc.gridStartPosition != -1)); //I need a way to find out when Start has been called. song = BeatSaberSongContainer.Instance.song; //Grab songe data diff = BeatSaberSongContainer.Instance.difficultyData; //Set up some local variables int environmentID = 0; int batchSize = Settings.Instance.InitialLoadBatchSize; bool customPlat = false; environmentID = SongInfoEditUI.GetEnvironmentIDFromString(song.environmentName); //Grab platform by name (Official or Custom) if (song.customData != null && song.customData["_customEnvironment"] != null && song.customData["_customEnvironment"].Value != "") { if (SongInfoEditUI.GetCustomPlatformsIndexFromString(song.customData["_customEnvironment"]) >= 0) { environmentID = SongInfoEditUI.GetCustomPlatformsIndexFromString(song.customData["_customEnvironment"]); customPlat = true; } } //Instantiate platform, grab descriptor GameObject platform = (customPlat ? CustomPlatformPrefabs[environmentID] : PlatformPrefabs[environmentID]) ?? PlatformPrefabs[0]; GameObject instantiate = Instantiate(platform, new Vector3(0, -0.5f, -1.5f), Quaternion.identity); PlatformDescriptor descriptor = instantiate.GetComponent <PlatformDescriptor>(); BeatmapEventContainer.ModifyTypeMode = descriptor.SortMode; //Change sort mode //Update Colors Color leftNote = BeatSaberSong.DEFAULT_LEFTNOTE; //Have default note as base if (descriptor.RedColor != BeatSaberSong.DEFAULT_LEFTCOLOR) { leftNote = descriptor.RedColor; //Prioritize platforms } if (diff.colorLeft != BeatSaberSong.DEFAULT_LEFTNOTE) { leftNote = diff.colorLeft; //Then prioritize custom colors } Color rightNote = BeatSaberSong.DEFAULT_RIGHTNOTE; if (descriptor.BlueColor != BeatSaberSong.DEFAULT_RIGHTCOLOR) { rightNote = descriptor.BlueColor; } if (diff.colorRight != BeatSaberSong.DEFAULT_RIGHTNOTE) { rightNote = diff.colorRight; } notesContainer.UpdateColor(leftNote, rightNote); obstaclesContainer.UpdateColor(diff.obstacleColor); if (diff.colorLeft != BeatSaberSong.DEFAULT_LEFTNOTE) { descriptor.RedColor = diff.colorLeft; } if (diff.colorRight != BeatSaberSong.DEFAULT_RIGHTNOTE) { descriptor.BlueColor = diff.colorRight; } if (diff.envColorLeft != BeatSaberSong.DEFAULT_LEFTCOLOR) { descriptor.RedColor = diff.envColorLeft; } if (diff.envColorRight != BeatSaberSong.DEFAULT_RIGHTCOLOR) { descriptor.BlueColor = diff.envColorRight; } PlatformLoadedEvent.Invoke(descriptor); //Trigger event for classes that use the platform map = BeatSaberSongContainer.Instance.map; //Grab map info, do some stuff int noteLaneSize = 2; //Half of it, anyways int noteLayerSize = 3; Queue <BeatmapObject> queuedData = new Queue <BeatmapObject>( //Take all of our object data and combine them for batch loading. map._notes.Concat <BeatmapObject>(map._obstacles).Concat(map._events).Concat(map._BPMChanges).Concat(map._customEvents)); totalObjectsToLoad = queuedData.Count; if (map != null) { while (queuedData.Count > 0) { //Batch loading is loading a certain amount of objects (Batch Size) every frame, so at least ChroMapper remains active. for (int i = 0; i < batchSize; i++) { if (queuedData.Count == 0) { break; } BeatmapObject data = queuedData.Dequeue(); //Dequeue and load them into ChroMapper. if (data is BeatmapNote noteData) { BeatmapNoteContainer beatmapNote = notesContainer.SpawnObject(noteData, out _) as BeatmapNoteContainer; if (noteData._lineIndex >= 1000 || noteData._lineIndex <= -1000 || noteData._lineLayer >= 1000 || noteData._lineLayer <= -1000) { continue; } if (2 - noteData._lineIndex > noteLaneSize) { noteLaneSize = 2 - noteData._lineIndex; } if (noteData._lineIndex - 1 > noteLaneSize) { noteLaneSize = noteData._lineIndex - 1; } if (noteData._lineLayer + 1 > noteLayerSize) { noteLayerSize = noteData._lineLayer + 1; } } else if (data is BeatmapObstacle obstacleData) { BeatmapObstacleContainer beatmapObstacle = obstaclesContainer.SpawnObject(obstacleData, out _) as BeatmapObstacleContainer; if (obstacleData._lineIndex >= 1000 || obstacleData._lineIndex <= -1000) { continue; } if (2 - obstacleData._lineIndex > noteLaneSize) { noteLaneSize = 2 - obstacleData._lineIndex; } if (obstacleData._lineIndex - 1 > noteLaneSize) { noteLaneSize = obstacleData._lineIndex - 1; } } else if (data is MapEvent eventData) { eventsContainer.SpawnObject(eventData, out _); } else if (data is BeatmapBPMChange bpmData) { bpmContainer.SpawnObject(bpmData, out _); } else if (data is BeatmapCustomEvent customData) { customEventsContainer.SpawnObject(customData, out _); } } UpdateSlider(batchSize); yield return(new WaitForEndOfFrame()); } notesContainer.SortObjects(); //Sort these boyes. obstaclesContainer.SortObjects(); eventsContainer.SortObjects(); bpmContainer.SortObjects(); customEventsContainer.SortObjects(); noteGrid.localScale = new Vector3((float)(noteLaneSize * 2) / 10 + 0.01f, 1, 1); //Set note lanes appropriately } PersistentUI.Instance.LevelLoadSlider.gameObject.SetActive(false); //Disable progress bar LevelLoadedEvent?.Invoke(); }
public IEnumerator LoadMap() { if (BeatSaberSongContainer.Instance == null) { yield break; } PersistentUI.Instance.LevelLoadSliderLabel.text = ""; yield return(new WaitUntil(() => atsc.gridStartPosition != -1)); //I need a way to find out when Start has been called. song = BeatSaberSongContainer.Instance.song; //Grab songe data diff = BeatSaberSongContainer.Instance.difficultyData; //Set up some local variables int environmentID = 0; bool customPlat = false; bool directional = false; environmentID = SongInfoEditUI.GetEnvironmentIDFromString(song.environmentName); //Grab platform by name (Official or Custom) if (song.customData != null && ((song.customData["_customEnvironment"] != null && song.customData["_customEnvironment"].Value != ""))) { if (CustomPlatformsLoader.Instance.GetAllEnvironmentIds().IndexOf(song.customData["_customEnvironment"] ?? "") >= 0) { customPlat = true; } } if (rotationController.IsActive && diff.parentBeatmapSet.beatmapCharacteristicName != "Lawless") { environmentID = SongInfoEditUI.GetDirectionalEnvironmentIDFromString(song.allDirectionsEnvironmentName); customPlat = false; directional = true; } //Instantiate platform, grab descriptor GameObject platform = (customPlat ? CustomPlatformsLoader.Instance.LoadPlatform(song.customData["_customEnvironment"], (PlatformPrefabs[environmentID]) ?? PlatformPrefabs[0], null) : PlatformPrefabs[environmentID]) ?? PlatformPrefabs[0]; if (directional) { platform = DirectionalPlatformPrefabs[environmentID]; } GameObject instantiate = null; if (customPlat) { instantiate = platform; } else { Debug.Log("Instanciate nonCustomPlat"); instantiate = Instantiate(platform, PlatformOffset, Quaternion.identity) as GameObject; } PlatformDescriptor descriptor = instantiate.GetComponent <PlatformDescriptor>(); BeatmapEventContainer.ModifyTypeMode = descriptor.SortMode; //Change sort mode //Update Colors Color leftNote = BeatSaberSong.DEFAULT_LEFTNOTE; //Have default note as base if (descriptor.RedColor != BeatSaberSong.DEFAULT_LEFTCOLOR) { leftNote = descriptor.RedColor; //Prioritize platforms } if (diff.colorLeft != BeatSaberSong.DEFAULT_LEFTNOTE) { leftNote = diff.colorLeft; //Then prioritize custom colors } Color rightNote = BeatSaberSong.DEFAULT_RIGHTNOTE; if (descriptor.BlueColor != BeatSaberSong.DEFAULT_RIGHTCOLOR) { rightNote = descriptor.BlueColor; } if (diff.colorRight != BeatSaberSong.DEFAULT_RIGHTNOTE) { rightNote = diff.colorRight; } notesContainer.UpdateColor(leftNote, rightNote); obstaclesContainer.UpdateColor(diff.obstacleColor); if (diff.colorLeft != BeatSaberSong.DEFAULT_LEFTNOTE) { descriptor.RedColor = diff.colorLeft; } if (diff.colorRight != BeatSaberSong.DEFAULT_RIGHTNOTE) { descriptor.BlueColor = diff.colorRight; } if (diff.envColorLeft != BeatSaberSong.DEFAULT_LEFTCOLOR) { descriptor.RedColor = diff.envColorLeft; } if (diff.envColorRight != BeatSaberSong.DEFAULT_RIGHTCOLOR) { descriptor.BlueColor = diff.envColorRight; } PlatformLoadedEvent.Invoke(descriptor); //Trigger event for classes that use the platform loader.UpdateMapData(BeatSaberSongContainer.Instance.map); yield return(StartCoroutine(loader.HardRefresh())); LevelLoadedEvent?.Invoke(); }
private void Start() { song = BeatSaberSongContainer.Instance.song; diff = BeatSaberSongContainer.Instance.difficultyData; map = BeatSaberSongContainer.Instance.map; }
public void Save(bool auto = false) { PersistentUI.Instance.DisplayMessage($"{(auto ? "Auto " : "")}Saving...", PersistentUI.DisplayMessageType.BOTTOM); SelectionController.RefreshMap(); //Make sure our map is up to date. if (BeatSaberSongContainer.Instance.map._customEvents.Any()) { if (Settings.Instance.Saving_CustomEventsSchemaReminder) { //Example of advanced dialog box options. PersistentUI.Instance.ShowDialogBox("ChroMapper has detected you are using custom events in your map.\n\n" + "The current format for Custom Events goes against BeatSaver's enforced schema.\n" + "If you try to upload this map to BeatSaver, it will fail.", HandleCustomEventsDecision, "Ok", "Don't Remind Me"); } } new Thread(() => //I could very well move this to its own function but I need access to the "auto" variable. { Thread.CurrentThread.IsBackground = true; //Making sure this does not interfere with game thread //Saving Map Data string originalMap = BeatSaberSongContainer.Instance.map.directoryAndFile; string originalSong = BeatSaberSongContainer.Instance.song.directory; if (auto) { Queue <string> directory = new Queue <string>(originalSong.Split('/').ToList()); directory.Enqueue("autosaves"); directory.Enqueue($"{DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss")}"); //timestamp string autoSaveDir = string.Join("/", directory.ToArray()); Debug.Log($"Auto saved to: {autoSaveDir}"); //We need to create the autosave directory before we can save the .dat difficulty into it. System.IO.Directory.CreateDirectory(autoSaveDir); BeatSaberSongContainer.Instance.map.directoryAndFile = $"{autoSaveDir}/{BeatSaberSongContainer.Instance.difficultyData.beatmapFilename}"; BeatSaberSongContainer.Instance.song.directory = autoSaveDir; } BeatSaberSongContainer.Instance.map.Save(); BeatSaberSongContainer.Instance.map.directoryAndFile = originalMap; //Saving Map Requirement Info JSONArray requiredArray = new JSONArray(); //Generate suggestions and requirements array JSONArray suggestedArray = new JSONArray(); if (HasChromaEvents()) { suggestedArray.Add(new JSONString("Chroma Lighting Events")); } if (HasMappingExtensions()) { requiredArray.Add(new JSONString("Mapping Extensions")); } if (HasChromaToggle()) { requiredArray.Add(new JSONString("ChromaToggle")); } BeatSaberSong.DifficultyBeatmapSet set = BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Where(x => x == BeatSaberSongContainer.Instance.difficultyData.parentBeatmapSet).First(); //Grab our set BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Remove(set); //Yeet it out BeatSaberSong.DifficultyBeatmap data = set.difficultyBeatmaps.Where(x => x.beatmapFilename == BeatSaberSongContainer.Instance.difficultyData.beatmapFilename).First(); //Grab our diff data set.difficultyBeatmaps.Remove(data); //Yeet out our difficulty data if (BeatSaberSongContainer.Instance.difficultyData.customData == null) //if for some reason this be null, make new customdata { BeatSaberSongContainer.Instance.difficultyData.customData = new JSONObject(); } BeatSaberSongContainer.Instance.difficultyData.customData["_suggestions"] = suggestedArray; //Set suggestions BeatSaberSongContainer.Instance.difficultyData.customData["_requirements"] = requiredArray; //Set requirements set.difficultyBeatmaps.Add(BeatSaberSongContainer.Instance.difficultyData); //Add back our difficulty data BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Add(set); //Add back our difficulty set BeatSaberSongContainer.Instance.song.SaveSong(); //Save BeatSaberSongContainer.Instance.song.directory = originalSong; //Revert directory if it was changed by autosave }).Start(); }
public void Save(bool auto = false) { if (savingThread != null && savingThread.IsAlive) { Debug.LogError(":hyperPepega: :mega: STOP TRYING TO SAVE THE SONG WHILE ITS ALREADY SAVING TO DISK"); return; } var notification = PersistentUI.Instance.DisplayMessage("Mapper", $"{(auto ? "auto" : "")}save.message", PersistentUI.DisplayMessageType.BOTTOM); notification.skipFade = true; notification.waitTime = 5.0f; SelectionController.RefreshMap(); //Make sure our map is up to date. savingThread = new Thread(() => //I could very well move this to its own function but I need access to the "auto" variable. { Thread.CurrentThread.IsBackground = true; //Making sure this does not interfere with game thread //Fixes weird shit regarding how people write numbers (20,35 VS 20.35), causing issues in JSON //This should be thread-wide, but I have this set throughout just in case it isnt. Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; //Saving Map Data string originalMap = BeatSaberSongContainer.Instance.map.directoryAndFile; string originalSong = BeatSaberSongContainer.Instance.song.directory; if (auto) { var directory = originalSong.Split('/').ToList(); directory.Add("autosaves"); directory.Add($"{DateTime.Now:dd-MM-yyyy_HH-mm-ss}"); //timestamp string autoSaveDir = string.Join("/", directory.ToArray()); Debug.Log($"Auto saved to: {autoSaveDir}"); //We need to create the autosave directory before we can save the .dat difficulty into it. Directory.CreateDirectory(autoSaveDir); BeatSaberSongContainer.Instance.map.directoryAndFile = $"{autoSaveDir}\\{BeatSaberSongContainer.Instance.difficultyData.beatmapFilename}"; BeatSaberSongContainer.Instance.song.directory = autoSaveDir; var newDirectoryInfo = new DirectoryInfo(autoSaveDir); currentAutoSaves.Add(newDirectoryInfo); CleanAutosaves(); } BeatSaberSongContainer.Instance.map.Save(); BeatSaberSongContainer.Instance.map.directoryAndFile = originalMap; BeatSaberSong.DifficultyBeatmapSet set = BeatSaberSongContainer.Instance.difficultyData.parentBeatmapSet; //Grab our set BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Remove(set); //Yeet it out BeatSaberSong.DifficultyBeatmap data = BeatSaberSongContainer.Instance.difficultyData; //Grab our diff data set.difficultyBeatmaps.Remove(data); //Yeet out our difficulty data if (BeatSaberSongContainer.Instance.difficultyData.customData == null) //if for some reason this be null, make new customdata { BeatSaberSongContainer.Instance.difficultyData.customData = new JSONObject(); } set.difficultyBeatmaps.Add(BeatSaberSongContainer.Instance.difficultyData); //Add back our difficulty data BeatSaberSongContainer.Instance.song.difficultyBeatmapSets.Add(set); //Add back our difficulty set BeatSaberSongContainer.Instance.song.SaveSong(); //Save BeatSaberSongContainer.Instance.song.directory = originalSong; //Revert directory if it was changed by autosave notification.skipDisplay = true; }); savingThread.Start(); }