// ReSharper disable CheckNamespace // ReSharper disable InconsistentNaming using System.Xml.Serialization; using Manager.MaiStudio.Serialize; using MonoMod; using MoreChartFormats; using MoreChartFormats.MaiSxt; using MoreChartFormats.Simai; using MoreChartFormats.Simai.Errors; namespace Manager; class patch_NotesReader : NotesReader { private new FormatType checkFormat(string fileName) => Path.GetExtension(fileName) switch { ".simai" => FormatType.FORMAT_M2S, ".srt" => FormatType.FORMAT_SRT, ".szt" => FormatType.FORMAT_SZT, ".sct" => FormatType.FORMAT_SCT, ".sdt" => FormatType.FORMAT_SDT, _ => FormatType.FORMAT_MA2, }; [MonoModIgnore] private extern bool loadMa2(string fileName, LoadType loadType = LoadType.LOAD_FULL); [MonoModReplace] public new bool load(string fileName, LoadType loadType = LoadType.LOAD_FULL) { if (!File.Exists(fileName)) { return false; } var format = checkFormat(fileName); return format switch { FormatType.FORMAT_MA2 => loadMa2(fileName, loadType), FormatType.FORMAT_SRT or FormatType.FORMAT_SZT or FormatType.FORMAT_SCT or FormatType.FORMAT_SDT => loadSxt(format, fileName), FormatType.FORMAT_M2S => loadSimai(fileName), _ => false, }; } private bool loadSxt(FormatType format, string fileName) { init(_playerID); fillDummyHeader(fileName); _loadType = LoadType.LOAD_FULL; try { // HACK: we are assuming that the chart file is stored in the same folder // as the Music.xml, which it must be due to how this game loads assets. // refer to `Manager.MaiStudio.Serialize.FilePath.AddPath(string parentPath)`. // // There must be a better way to get the song's BPM... var musicFolder = Path.GetDirectoryName(fileName); if (musicFolder == null) { throw new Exception("Music.xml is contained in the root directory?!"); } var serializer = new XmlSerializer(typeof(MusicData)); using (var musicXml = File.OpenRead(Path.Combine(musicFolder, "Music.xml"))) { var music = (MusicData)serializer.Deserialize(musicXml); var bpmChangeData = new BPMChangeData { bpm = music.bpm, time = new NotesTime(0, 0, this), }; _composition._bpmList.Add(bpmChangeData); calcBPMList(); } var content = File.ReadAllText(fileName); var refs = new NotesReferences { Reader = this, Header = _header, Composition = _composition, Notes = _note, }; SxtReaderBase reader = format == FormatType.FORMAT_SRT ? new SrtReader(refs) : new SxtReader(refs); reader.Deserialize(content); calcAll(); } catch (Exception e) { System.Console.WriteLine("[MoreChartFormats] [SXT] Could not load SXT chart: {0}", e); return false; } return true; } private bool loadSimai(string fileName) { init(_playerID); fillDummyHeader(fileName); _loadType = LoadType.LOAD_FULL; try { System.Console.WriteLine("[MoreChartFormats] [Simai] Tokenizing chart"); var tokens = SimaiReader.Tokenize(File.ReadAllText(fileName)); var refs = new NotesReferences { Reader = this, Header = _header, Composition = _composition, Notes = _note, }; System.Console.WriteLine("[MoreChartFormats] [Simai] Processing BPM changes"); SimaiReader.ReadBpmChanges(tokens, refs); calcBPMList(); System.Console.WriteLine("[MoreChartFormats] [Simai] Reading chart"); SimaiReader.Deserialize(tokens, refs); System.Console.WriteLine("[MoreChartFormats] [Simai] Mirroring chart and calculating timings"); foreach (var note in _note._noteData) { note.time.calcMsec(this); note.end.calcMsec(this); if (note.type.isTouch() && note.touchArea is TouchSensorType.D or TouchSensorType.E) { note.startButtonPos = ConvertMirrorTouchEPosition(note.startButtonPos); } else { note.startButtonPos = ConvertMirrorPosition(note.startButtonPos); } if (note.type.isSlide() || note.type == NotesTypeID.Def.ConnectSlide) { note.slideData.shoot.time.calcMsec(this); note.slideData.arrive.time.calcMsec(this); note.slideData.targetNote = ConvertMirrorPosition(note.slideData.targetNote); note.slideData.type = ConvertMirrorSlide(note.slideData.type); } } #if DEBUG System.Console.WriteLine("[MoreChartFormats] [Simai] Calculating chart data"); #endif calcAll(); #if DEBUG System.Console.WriteLine("[MoreChartFormats] [Simai] Loaded {0} notes", _total.GetAllNoteNum()); System.Console.WriteLine("[MoreChartFormats] [Simai] > {0} taps", _total.GetTapNum()); System.Console.WriteLine("[MoreChartFormats] [Simai] > {0} holds", _total.GetHoldNum()); System.Console.WriteLine("[MoreChartFormats] [Simai] > {0} slides", _total.GetSlideNum()); System.Console.WriteLine("[MoreChartFormats] [Simai] > {0} touches", _total.GetTouchNum()); System.Console.WriteLine("[MoreChartFormats] [Simai] > {0} break", _total.GetBreakNum()); #endif } catch (SimaiException e) { System.Console.WriteLine($"[MoreChartFormats] [Simai] There was an error loading the chart at line {e.line}, col {e.character}: {e} "); return false; } catch (Exception e) { System.Console.WriteLine($"[MoreChartFormats] [Simai] There was an error loading the chart: {e}"); return false; } return true; } private void fillDummyHeader(string fileName) { _header._notesName = fileName; _header._resolutionTime = ReaderConst.DEFAULT_RESOLUTION_TIME; _header._version[0].major = 0; _header._version[0].minor = 0; _header._version[0].release = 0; _header._version[1].major = 1; _header._version[1].minor = 4; _header._version[0].release = 0; _header._metInfo.denomi = 4; _header._metInfo.num = 4; _header._clickFirst = ReaderConst.DEFAULT_RESOLUTION_TIME; // The game doesn't care if a non-fes-mode chart uses utage mechanics. // It's just a flag. _header._isFes = false; } private void calcAll() { calcNoteTiming(); calcEach(); calcSlide(); calcEndTiming(); calcBPMInfo(); calcBarList(); calcSoflanList(); calcClickList(); calcTotal(); } }