示例#1
0
        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));
        }
示例#2
0
    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();
    }
示例#3
0
 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);
 }
示例#4
0
 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);
     }
 }
示例#6
0
    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);
        }
    }
示例#7
0
    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();
    }
示例#8
0
    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;
 }
示例#10
0
    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();
    }
示例#11
0
    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();
    }