Decompiled source of InFalsusPractice v1.0.0
BepInEx/plugins/InFalsusPractice.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using FastText; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppInterop.Runtime.Runtime; using Il2CppInterop.Runtime.Startup; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Threading; using Microsoft.CodeAnalysis; using UnityEngine; using _E; using _J; using _K; using _b; using _k; using ifapp.Game; using ifapp.Game.Data; using ifapp.Game.Scenes; using ifapp.Game.Scenes.Game; using ifapp.Game.Scenes.Game.UI; using ifapp.Game.UI.Common; using ifapp.Game.UI.Settings; using ifapp.NativeTime; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(/*Could not decode attribute arguments.*/)] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyTitle("In Falsus Practice")] [assembly: AssemblyCompany("SkyFrozen")] [assembly: AssemblyCopyright("Copyright © SkyFrozen")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: SecurityPermission((SecurityAction)8, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : System.Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(/*Could not decode attribute arguments.*/)] internal sealed class RefSafetyRulesAttribute : System.Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace InFalsusPractice { public sealed class ButtonClicks { [field: CompilerGenerated] public int Captured { [CompilerGenerated] get; [CompilerGenerated] private set; } public bool Handle(int id, bool down, bool up, bool enabled, bool inside) { if (down && enabled && inside && Captured == 0) { Captured = id; } if (!up || Captured != id) { return false; } Captured = 0; return enabled && inside; } public void Cancel() { Captured = 0; } } public sealed class CalibrationOffset { [field: CompilerGenerated] public int Baseline { [CompilerGenerated] get; [CompilerGenerated] private set; } [field: CompilerGenerated] public int Applied { [CompilerGenerated] get; [CompilerGenerated] private set; } public CalibrationOffset(int milliseconds) { Baseline = (Applied = milliseconds); } public int AtSpeed(int observed, double speed) { if (observed != Applied) { Baseline = observed; } Applied = (int)Math.Clamp(Math.Round((double)Baseline * speed, (MidpointRounding)1), -2147483648.0, 2147483647.0); return Applied; } } public static class ChartMirror { public static PracticeNote Reflect(PracticeNote note) { note.Lane = 5f - note.Lane; note.EndLane = 5f - note.EndLane; if (note.Kind == PracticeNoteKind.Flick && !note.FlickMirrorInvariant) { note.RightFlick = !note.RightFlick; } return note; } public static List<PracticeNote> ForReview(List<PracticeNote> source, bool mirrored) { if (!mirrored) { return source; } List<PracticeNote> val = new List<PracticeNote>(source.Count); for (int i = 0; i < source.Count; i++) { val.Add(Reflect(source[i])); } return val; } } [DefaultMember("Item")] public sealed class MirroredNotes : System.Collections.Generic.IReadOnlyList<PracticeNote>, System.Collections.Generic.IReadOnlyCollection<PracticeNote>, System.Collections.Generic.IEnumerable<PracticeNote>, System.Collections.IEnumerable { [CompilerGenerated] private sealed class <GetEnumerator>d__6 : System.Collections.Generic.IEnumerator<PracticeNote>, System.IDisposable, System.Collections.IEnumerator { private int <>1__state; private PracticeNote <>2__current; public MirroredNotes <>4__this; private int <i>5__2; PracticeNote System.Collections.Generic.IEnumerator<PracticeNote>.Current { [DebuggerHidden] get { return <>2__current; } } object System.Collections.IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetEnumerator>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void System.IDisposable.Dispose() { } private bool MoveNext() { int num = <>1__state; MirroredNotes mirroredNotes = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <i>5__2 = 0; break; case 1: <>1__state = -1; <i>5__2++; break; } if (<i>5__2 < mirroredNotes.Count) { <>2__current = mirroredNotes[<i>5__2]; <>1__state = 1; return true; } return false; } bool System.Collections.IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void System.Collections.IEnumerator.Reset() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) throw new NotSupportedException(); } } private readonly System.Collections.Generic.IReadOnlyList<PracticeNote> _source; public int Count => ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)_source).Count; public PracticeNote this[int index] => ChartMirror.Reflect(_source[index]); public MirroredNotes(System.Collections.Generic.IReadOnlyList<PracticeNote> source) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) _source = source ?? throw new ArgumentNullException("source"); } [IteratorStateMachine(typeof(<GetEnumerator>d__6))] public System.Collections.Generic.IEnumerator<PracticeNote> GetEnumerator() { for (int i = 0; i < Count; i++) { yield return this[i]; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (System.Collections.IEnumerator)GetEnumerator(); } } public sealed class MirrorPresentation { private System.Collections.Generic.IReadOnlyList<PracticeNote> _source; private System.Collections.Generic.IReadOnlyList<PracticeNote> _view; [field: CompilerGenerated] public bool Known { [CompilerGenerated] get; [CompilerGenerated] private set; } [field: CompilerGenerated] public bool Mirrored { [CompilerGenerated] get; [CompilerGenerated] private set; } public void Reset() { bool known = (Mirrored = false); Known = known; _source = (_view = null); } public void Resolve(bool mirrored) { if (!Known) { Known = true; Mirrored = mirrored; } } public System.Collections.Generic.IReadOnlyList<PracticeNote> Preview(System.Collections.Generic.IReadOnlyList<PracticeNote> source) { if (!Known || source == null || ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)source).Count == 0) { return System.Array.Empty<PracticeNote>(); } if (!Mirrored) { return source; } if (_source != source) { _source = source; _view = new MirroredNotes(source); } return _view; } } public static class ChartOverview { [CompilerGenerated] private sealed class <Read>d__3 : System.Collections.Generic.IEnumerable<PracticeNote>, System.Collections.IEnumerable, System.Collections.Generic.IEnumerator<PracticeNote>, System.IDisposable, System.Collections.IEnumerator { private int <>1__state; private PracticeNote <>2__current; private int <>l__initialThreadId; private LogicalNotePlayer player; public LogicalNotePlayer <>3__player; private List<_fA> <source>5__2; private int <count>5__3; private int <i>5__4; PracticeNote System.Collections.Generic.IEnumerator<PracticeNote>.Current { [DebuggerHidden] get { return <>2__current; } } object System.Collections.IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <Read>d__3(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void System.IDisposable.Dispose() { } private bool MoveNext() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected I4, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected I4, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected I4, but got Unknown int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_019b; } <>1__state = -1; if ((Object)(object)player == (Object)null) { return false; } <source>5__2 = player._Gb()?.Item2; if (<source>5__2 == null || <source>5__2.Count == 0) { return false; } <count>5__3 = <source>5__2.Count; <i>5__4 = 0; goto IL_01ad; IL_01ad: if (<i>5__4 < <count>5__3) { _fA val = <source>5__2[<i>5__4]; int num2 = (int)val._Ae; int num3 = (int)val._be; if (num2 >= 1 && num2 <= 4 && (num3 == 1 || num3 == 2 || num3 == 4 || num3 == 5)) { float x = ((num2 == 1) ? ((float)val._Ce._KD) : Rational(val._Ce)); float endX = ((num2 == 1) ? ((float)val._de._KD) : Rational(val._de)); float width = ((num2 == 1) ? ((float)val._De._KD) : Rational(val._De)); float endWidth = ((num2 == 1) ? ((float)val._ee._KD) : Rational(val._ee)); double num4 = (double)val._Be * 0.001; double end = Math.Max(num4, (double)val._ce * 0.001); <>2__current = ChartProjection.Map(num2, num3, num4, end, x, endX, width, endWidth, (int)val._he); <>1__state = 1; return true; } goto IL_019b; } return false; IL_019b: <i>5__4++; goto IL_01ad; } bool System.Collections.IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void System.Collections.IEnumerator.Reset() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) throw new NotSupportedException(); } [DebuggerHidden] System.Collections.Generic.IEnumerator<PracticeNote> System.Collections.Generic.IEnumerable<PracticeNote>.GetEnumerator() { <Read>d__3 <Read>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <Read>d__ = this; } else { <Read>d__ = new <Read>d__3(0); } <Read>d__.player = <>3__player; return <Read>d__; } [DebuggerHidden] System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (System.Collections.IEnumerator)((System.Collections.Generic.IEnumerable<PracticeNote>)this).GetEnumerator(); } } private static readonly Dictionary<string, string> Titles = new Dictionary<string, string>((IEqualityComparer<string>)(object)StringComparer.OrdinalIgnoreCase); private static bool _titlesLoaded; public static int SourceCount(LogicalNotePlayer player) { return ((player == null) ? ((int?)null) : player._Gb()?.Item2?.Count).GetValueOrDefault(); } [IteratorStateMachine(typeof(<Read>d__3))] public static System.Collections.Generic.IEnumerable<PracticeNote> Read(LogicalNotePlayer player) { if ((Object)(object)player == (Object)null) { yield break; } List<_fA> source = player._Gb()?.Item2; if (source == null || source.Count == 0) { yield break; } int count = source.Count; for (int i = 0; i < count; i++) { _fA val = source[i]; int num = (int)val._Ae; int num2 = (int)val._be; if (num >= 1 && num <= 4 && (num2 == 1 || num2 == 2 || num2 == 4 || num2 == 5)) { float x = ((num == 1) ? ((float)val._Ce._KD) : Rational(val._Ce)); float endX = ((num == 1) ? ((float)val._de._KD) : Rational(val._de)); float width = ((num == 1) ? ((float)val._De._KD) : Rational(val._De)); float endWidth = ((num == 1) ? ((float)val._ee._KD) : Rational(val._ee)); double num3 = (double)val._Be * 0.001; double end = Math.Max(num3, (double)val._ce * 0.001); yield return ChartProjection.Map(num, num2, num3, end, x, endX, width, endWidth, (int)val._he); } } } public static string ChartKey(GameScene scene) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected I4, but got Unknown if ((Object)(object)scene == (Object)null) { return ""; } _nG dN = GameScene._dN; SongInfo val = ((dN != null) ? dN._LYA : null); if (val == null) { return ""; } return val.BaseName + ":" + (int)dN._mYA; } public static string Title(GameScene scene) { object obj; if (!((Object)(object)scene == (Object)null)) { _nG dN = GameScene._dN; if (dN == null) { obj = null; } else { SongInfo lYA = dN._LYA; obj = ((lYA != null) ? lYA.BaseName : null); } if (obj == null) { obj = ""; } } else { obj = ""; } string text = (string)obj; LoadTitles(); string result = default(string); if (!Titles.TryGetValue(text, ref result)) { return text; } return result; } private static float Rational(_BA value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) float num = ((value._lD != 0) ? ((float)value._KD / (float)value._lD) : value._LD); if (!float.IsNaN(num) && !float.IsInfinity(num)) { return num; } return 0f; } private unsafe static void LoadTitles() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (_titlesLoaded) { return; } _titlesLoaded = true; string text = Path.Combine(Paths.ConfigPath, "InFalsusRating", "song-localization.json"); if (!File.Exists(text)) { return; } try { JsonDocument val = JsonDocument.Parse(File.ReadAllText(text), default(JsonDocumentOptions)); try { JsonElement rootElement = val.RootElement; JsonElement val2 = default(JsonElement); if (!((JsonElement)(ref rootElement)).TryGetProperty("songs", ref val2)) { return; } ArrayEnumerator val3 = ((JsonElement)(ref val2)).EnumerateArray(); ArrayEnumerator enumerator = ((ArrayEnumerator)(ref val3)).GetEnumerator(); try { JsonElement val4 = default(JsonElement); JsonElement val5 = default(JsonElement); JsonElement val6 = default(JsonElement); while (((ArrayEnumerator)(ref enumerator)).MoveNext()) { JsonElement current = ((ArrayEnumerator)(ref enumerator)).Current; if (((JsonElement)(ref current)).TryGetProperty("base_name", ref val4) && ((JsonElement)(ref current)).TryGetProperty("titles", ref val5) && (((JsonElement)(ref val5)).TryGetProperty("ja", ref val6) || ((JsonElement)(ref val5)).TryGetProperty("en", ref val6))) { string text2 = ((JsonElement)(ref val4)).GetString(); string text3 = ((JsonElement)(ref val6)).GetString(); if (!string.IsNullOrEmpty(text2) && !string.IsNullOrWhiteSpace(text3)) { Titles[text2] = text3; } } } } finally { ((System.IDisposable)(*(ArrayEnumerator*)(&enumerator))/*cast due to .constrained prefix*/).Dispose(); } } finally { ((System.IDisposable)val)?.Dispose(); } } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (JsonException) { } } } public static class ChartProjection { public static PracticeNote Map(int side, int type, double start, double end, float x, float endX, float width, float endWidth, int nativeFlags = 0) { PracticeNote practiceNote = new PracticeNote(); practiceNote.Start = start; practiceNote.End = Math.Max(start, end); practiceNote.Kind = type switch { 2 => PracticeNoteKind.Hold, 5 => PracticeNoteKind.Sky, 4 => PracticeNoteKind.Flick, _ => PracticeNoteKind.Tap, }; PracticeNote result = practiceNote; result.RightFlick = type == 4 && (nativeFlags & 0x400) != 0; int num = nativeFlags & 0x1400; result.FlickMirrorInvariant = type == 4 && (num == 0 || num == 5120); switch (side) { case 1: result.Surface = PracticeNoteSurface.Floor; result.Width = Clamp(width, 1f, 4f); result.EndWidth = result.Width; result.Lane = Clamp(x, 1f, 5f - result.Width) + (result.Width - 1f) * 0.5f; result.EndLane = result.Lane; break; case 2: case 3: { result.Surface = PracticeNoteSurface.Side; result.Lane = (result.EndLane = ((side != 2) ? 5 : 0)); float width2 = (result.EndWidth = 1f); result.Width = width2; break; } default: result.Surface = PracticeNoteSurface.Air; result.Lane = 0.5f + 4f * Clamp(x, 0f, 1f); result.EndLane = 0.5f + 4f * Clamp(endX, 0f, 1f); result.Width = Clamp(4f * width, 0.1f, 4f); result.EndWidth = Clamp(4f * endWidth, 0.1f, 4f); break; } if (type == 1 || type == 4) { result.End = result.Start; result.EndLane = result.Lane; result.EndWidth = result.Width; } return result; } private static float Clamp(float value, float min, float max) { if (!float.IsFinite(value)) { return min; } return Math.Clamp(value, min, max); } } public enum CoachingGrade : byte { Exact, SmallExact, Near, Break, Unknown } [Flags] public enum CoachingFlags : byte { None = 0, Practice = 1, Seek = 2, Loop = 4, PausedBoundary = 8, Unmatched = 0x10 } public sealed class CoachingEvent { public int NoteIndex = -1; public int SegmentId; public int TimingDirection; public double ChartTime; public double OffsetMs; public double Rate = 1.0; public bool TimingValid; public CoachingGrade Grade; public CoachingFlags Flags; } public sealed class CoachingRun { public string SongKey = ""; public string SongTitle = ""; public string CaptureNote = ""; public double Duration; public bool Completed; public bool PracticeUsed; public bool SeekUsed; public bool SignKnown; public bool CaptureIncomplete; public int ExcludedCaptureNotes; public int MissingJudgementRecords; public List<PracticeNote> Notes = new List<PracticeNote>(); public List<CoachingEvent> Events = new List<CoachingEvent>(); } public struct CoachingGradeCounts { public int SmallExact; public int Near; public int Break; public void Add(CoachingGrade grade) { switch (grade) { case CoachingGrade.SmallExact: SmallExact++; break; case CoachingGrade.Near: Near++; break; case CoachingGrade.Break: Break++; break; } } } public struct CoachingTypeCounts { public CoachingGradeCounts Tap; public CoachingGradeCounts Hold; public CoachingGradeCounts Field; public CoachingGradeCounts Flick; public void Add(PracticeNoteKind kind, CoachingGrade grade) { switch (kind) { case PracticeNoteKind.Tap: Tap.Add(grade); break; case PracticeNoteKind.Hold: Hold.Add(grade); break; case PracticeNoteKind.Sky: Field.Add(grade); break; case PracticeNoteKind.Flick: Flick.Add(grade); break; } } } public sealed class CoachingRecommendation { public string PatternKey = ""; public string Label = ""; public string Evidence = ""; public string Advice = ""; public string AnalysisDetail = ""; public double Start; public double End; public double Priority; public double HistoryWeight = 1.0; public int SmallExact; public int Near; public int Break; public int HistorySongs; public int Occurrences; public double CoreStart; public double CoreEnd; public int CoreNotes; public int CoreExpectedNotes; public int SimilarSmallExact; public int SimilarNear; public int SimilarBreak; public CoachingTypeCounts NoteOutcomes; public CoachingPatternDetail Configuration; internal int SourceOffset; internal int SourceCount; internal string ViewCompact; } public sealed class CoachingReport { public string SongTitle = ""; public string Summary = ""; public string TimingTitle = ""; public string TimingEvidence = ""; public string TimingDisplay = ""; public string ExcludedReason = ""; public string TransientEvidence = ""; public double OffsetMs; public double SpreadMs; public double TimingConfidence; public double SuspectedTransientRate; public double DriftMs; public double CaptureCoverage; public int TimingSamples; public int ObservedNotes; public int UnmatchedEvents; public int DuplicateEvents; public int TransientEvents; public int HistoryPatterns; public int EarlyCount; public int LateCount; public int InterruptedEvents; public int ExcludedCaptureNotes; public int MissingJudgementRecords; public bool HasOffsetEstimate; public bool HasDirectionEstimate; public bool Learned; public bool PracticeExcluded; public bool SystematicBiasCandidate; public List<CoachingRecommendation> Recommendations = new List<CoachingRecommendation>(); public CoachingTypeCounts NoteOutcomes; } public sealed class CoachingMemoryEntry { public string Key = ""; public string Label = ""; public string LastSong = ""; public double ErrorEwma; public double Support; public double LastStart; public double LastEnd; public int Songs; public int Occurrences; public long UpdatedUtcTicks; } public sealed class CoachingMemory { public int Version = 1; public List<CoachingMemoryEntry> Patterns = new List<CoachingMemoryEntry>(); } public static class CoachingAnalysis { private sealed class Sample { public CoachingEvent Event; public Phrase Phrase; public double Weight; public double Factor = 1.0; } private sealed class Phrase { public string Key; public string Label; public double Start; public double End; public double Weight; public double RawWeight; public double Exposure; public int Observed; public int Expected; public int Small; public int Near; public int Break; public int OrderOffset; public CoachingTypeCounts NoteOutcomes; } private sealed class Aggregate { public string Key; public string Label; public double Weight; public double Exposure; public double LearnWeight; public double LearnExposure; public int Occurrences; public int Small; public int Near; public int Break; public int FailingOccurrences; public int LearnOccurrences; public int LearnNear; public int LearnBreak; public Phrase Worst; public Phrase LearnWorst; } public const int MaximumNotes = 30000; public const int MaximumPatterns = 256; private const double SmallWeight = 0.35; private const double NearWeight = 1.0; private const double BreakWeight = 2.2; public static CoachingReport Analyze(CoachingRun run, CoachingMemory memory = null, bool learn = true) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_03e6: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_0802: Unknown result type (might be due to invalid IL or missing references) //IL_0807: Unknown result type (might be due to invalid IL or missing references) //IL_0861: Unknown result type (might be due to invalid IL or missing references) //IL_0866: Unknown result type (might be due to invalid IL or missing references) //IL_0efc: Unknown result type (might be due to invalid IL or missing references) //IL_0f01: Unknown result type (might be due to invalid IL or missing references) //IL_0f1b: Unknown result type (might be due to invalid IL or missing references) //IL_0f20: Unknown result type (might be due to invalid IL or missing references) //IL_10e1: Unknown result type (might be due to invalid IL or missing references) //IL_10e6: Unknown result type (might be due to invalid IL or missing references) if (run == null) { throw new ArgumentNullException("run"); } CoachingReport coachingReport = new CoachingReport { SongTitle = (run.SongTitle ?? ""), ExcludedCaptureNotes = run.ExcludedCaptureNotes, MissingJudgementRecords = run.MissingJudgementRecords }; bool flag = run.PracticeUsed || run.SeekUsed; Phrase[] byNote; List<int> order; List<Phrase> val = BuildPhrases(run.Notes ?? new List<PracticeNote>(), out byNote, out order); List<Sample> val2 = new List<Sample>(); HashSet<int> val3 = new HashSet<int>(); Enumerator<CoachingEvent> enumerator = (run.Events ?? new List<CoachingEvent>()).GetEnumerator(); try { while (enumerator.MoveNext()) { CoachingEvent current = enumerator.Current; if (current != null) { if ((current.Flags & CoachingFlags.PausedBoundary) != CoachingFlags.None) { coachingReport.InterruptedEvents++; } if ((current.Flags & (CoachingFlags.Practice | CoachingFlags.Seek | CoachingFlags.Loop)) != CoachingFlags.None || !double.IsFinite(current.Rate) || Math.Abs(current.Rate - 1.0) > 0.001) { flag = true; } if (current.NoteIndex < 0 || current.NoteIndex >= byNote.Length || byNote[current.NoteIndex] == null || current.Grade == CoachingGrade.Unknown || !double.IsFinite(current.ChartTime) || (current.Flags & CoachingFlags.Unmatched) != CoachingFlags.None) { coachingReport.UnmatchedEvents++; } else if (!val3.Add(current.NoteIndex)) { coachingReport.DuplicateEvents++; } else if (current.Flags == CoachingFlags.None && !(Math.Abs(current.Rate - 1.0) > 0.001)) { val2.Add(new Sample { Event = current, Phrase = byNote[current.NoteIndex], Weight = Weight(current.Grade) }); } } } } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } val2.Sort((Comparison<Sample>)((Sample a, Sample b) => a.Event.ChartTime.CompareTo(b.Event.ChartTime))); coachingReport.ObservedNotes = val2.Count; coachingReport.PracticeExcluded = flag; int num = 0; Enumerator<Phrase> enumerator2 = val.GetEnumerator(); try { while (enumerator2.MoveNext()) { Phrase current2 = enumerator2.Current; num += current2.Expected; } } finally { ((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } coachingReport.CaptureCoverage = ((num == 0) ? 0.0 : Math.Min(1.0, (double)val2.Count / (double)num)); DetectTransient(val2, coachingReport); Enumerator<Sample> enumerator3 = val2.GetEnumerator(); try { while (enumerator3.MoveNext()) { Sample current3 = enumerator3.Current; Phrase phrase = current3.Phrase; phrase.Observed++; phrase.Exposure += 1.0; phrase.Weight += current3.Weight * current3.Factor; phrase.RawWeight += current3.Weight; PracticeNoteKind kind = run.Notes[current3.Event.NoteIndex].Kind; phrase.NoteOutcomes.Add(kind, current3.Event.Grade); coachingReport.NoteOutcomes.Add(kind, current3.Event.Grade); if (current3.Event.Grade == CoachingGrade.SmallExact) { phrase.Small++; } if (current3.Event.Grade == CoachingGrade.Near) { phrase.Near++; } if (current3.Event.Grade == CoachingGrade.Break) { phrase.Break++; } } } finally { ((System.IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } Dictionary<string, Aggregate> val4 = new Dictionary<string, Aggregate>((IEqualityComparer<string>)(object)StringComparer.Ordinal); enumerator2 = val.GetEnumerator(); try { Aggregate aggregate = default(Aggregate); while (enumerator2.MoveNext()) { Phrase current4 = enumerator2.Current; if (current4.Observed < 3 || (double)current4.Observed < (double)current4.Expected * 0.8) { continue; } if (!val4.TryGetValue(current4.Key, ref aggregate)) { aggregate = new Aggregate { Key = current4.Key, Label = current4.Label }; val4.Add(current4.Key, aggregate); } aggregate.Weight += current4.Weight; aggregate.Exposure += current4.Exposure; aggregate.Occurrences++; aggregate.Small += current4.Small; aggregate.Near += current4.Near; aggregate.Break += current4.Break; if (current4.Weight >= 1.0) { aggregate.FailingOccurrences++; } if (aggregate.Worst == null || current4.Weight / Math.Max(1.0, current4.Exposure) > aggregate.Worst.Weight / Math.Max(1.0, aggregate.Worst.Exposure)) { aggregate.Worst = current4; } if (current4.Observed == current4.Expected) { aggregate.LearnWeight += current4.Weight; aggregate.LearnExposure += current4.Exposure; aggregate.LearnOccurrences++; aggregate.LearnNear += current4.Near; aggregate.LearnBreak += current4.Break; if (aggregate.LearnWorst == null || current4.Weight / Math.Max(1.0, current4.Exposure) > aggregate.LearnWorst.Weight / Math.Max(1.0, aggregate.LearnWorst.Exposure)) { aggregate.LearnWorst = current4; } } } } finally { ((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } AnalyzeTiming(run, val2, flag, coachingReport); if (coachingReport.CaptureCoverage < 0.65) { coachingReport.HasOffsetEstimate = (coachingReport.HasDirectionEstimate = (coachingReport.SystematicBiasCandidate = false)); coachingReport.TimingTitle = "延迟:有效覆盖不足"; coachingReport.TimingEvidence = $"有效覆盖 {coachingReport.CaptureCoverage:0%};排除中断或缺失数据后,剩余片段不足以代表整曲。"; if (coachingReport.ObservedNotes == 0 && !string.IsNullOrWhiteSpace(run.CaptureNote)) { coachingReport.TimingTitle = "延迟:尚无有效判定"; coachingReport.TimingEvidence = "采集原因:" + Limit(run.CaptureNote, 100); } } coachingReport.TimingDisplay = coachingReport.TimingEvidence; if (run.Completed && run.CaptureIncomplete && coachingReport.ObservedNotes > 0) { coachingReport.TimingEvidence = $"有效采集 {coachingReport.ObservedNotes}/{num} 音符({coachingReport.CaptureCoverage:0.0%});已排除 {run.ExcludedCaptureNotes} 个未完整采集音符。" + coachingReport.TimingEvidence; } coachingReport.TimingConfidence *= coachingReport.CaptureCoverage; Dictionary<string, CoachingMemoryEntry> val5 = MemoryMap(memory); double num2 = 0.0; double num3 = 0.0; Enumerator<string, Aggregate> enumerator4 = val4.Values.GetEnumerator(); try { while (enumerator4.MoveNext()) { Aggregate current5 = enumerator4.Current; num2 += current5.Weight; num3 += current5.Exposure; } } finally { ((System.IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose(); } double num4 = num2 / Math.Max(1.0, num3); enumerator4 = val4.Values.GetEnumerator(); try { CoachingMemoryEntry coachingMemoryEntry = default(CoachingMemoryEntry); while (enumerator4.MoveNext()) { Aggregate current6 = enumerator4.Current; if (!(current6.Weight <= 0.1)) { bool flag2 = current6.Near == 0 && current6.Break == 0; if (!(coachingReport.SystematicBiasCandidate && flag2) || !(current6.Weight / current6.Exposure <= num4 * 1.25)) { val5.TryGetValue(current6.Key, ref coachingMemoryEntry); double num5 = ((coachingMemoryEntry == null) ? 0.0 : (Math.Min(1.0, coachingMemoryEntry.Support / 48.0) * Math.Min(1.0, (double)coachingMemoryEntry.Songs / 3.0))); double num6 = 1.0 + ((coachingMemoryEntry == null) ? 0.0 : ((coachingMemoryEntry.ErrorEwma - 0.16) * 1.7 * num5)); num6 = Math.Clamp(num6, 0.75, 1.55); double num7 = 1.0 + Math.Min(0.6, (double)Math.Max(0, current6.FailingOccurrences - 1) * 0.15); double num8 = current6.Weight / Math.Max(1.0, current6.Exposure) / 2.2; double num9 = Math.Min(1.0, current6.Exposure / 16.0); Phrase worst = current6.Worst; CoachingRecommendation coachingRecommendation = new CoachingRecommendation(); coachingRecommendation.PatternKey = current6.Key; coachingRecommendation.Label = current6.Label; coachingRecommendation.Start = Math.Max(0.0, worst.Start - 0.6); coachingRecommendation.End = Math.Min(Math.Max(run.Duration, worst.End), worst.End + 0.6); coachingRecommendation.Priority = Math.Clamp(100.0 * num8 * num7 * (0.5 + 0.5 * num9) * num6, 0.0, 100.0); coachingRecommendation.HistoryWeight = num6; coachingRecommendation.SmallExact = worst.Small; coachingRecommendation.Near = worst.Near; coachingRecommendation.Break = worst.Break; coachingRecommendation.HistorySongs = coachingMemoryEntry?.Songs ?? 0; coachingRecommendation.Occurrences = current6.Occurrences; coachingRecommendation.CoreStart = worst.Start; coachingRecommendation.CoreEnd = worst.End; coachingRecommendation.CoreNotes = worst.Observed; coachingRecommendation.CoreExpectedNotes = worst.Expected; coachingRecommendation.NoteOutcomes = worst.NoteOutcomes; coachingRecommendation.SimilarSmallExact = current6.Small; coachingRecommendation.SimilarNear = current6.Near; coachingRecommendation.SimilarBreak = current6.Break; coachingRecommendation.SourceOffset = worst.OrderOffset; coachingRecommendation.SourceCount = worst.Expected; coachingRecommendation.Advice = PracticeAdvice(worst.NoteOutcomes, worst.Near, worst.Break); coachingRecommendation.AnalysisDetail = string.Concat(new string[10] { $"本核心段有效判定:小 E {worst.Small} · Near {worst.Near} · Break {worst.Break}。", TypeEvidence(worst.NoteOutcomes), (run.Completed && !flag && coachingReport.CaptureCoverage == 1.0) ? "全曲 Flick 按音符计:" : "有效样本 Flick 按音符计:", Counts(coachingReport.NoteOutcomes.Flick), "。", $"同类 {current6.Occurrences} 段合计:小 E {current6.Small} · Near {current6.Near} · Break {current6.Break};明显失误 {current6.FailingOccurrences} 段。", (current6.FailingOccurrences >= 2) ? "多段复现,优先练习。" : ((current6.FailingOccurrences == 1) ? "单段证据,暂不认定为稳定弱项。" : "目前仅轻微失误,暂不认定为稳定弱项。"), (coachingMemoryEntry != null) ? $"历史 {coachingMemoryEntry.Songs} 次,权重 {num6:0.00}×。" : "尚无历史对照。", " 判定权重 0.35 / 1 / 2.2,短暂异常另行降权。", $"核心段 {worst.Start:0.0}–{worst.End:0.0} 秒,采集 {worst.Observed}/{worst.Expected} 个音符;建议区间含前后缓冲,缓冲音符不计入核心段。配置名描述组合,不等于该类型出错;长条取单音符最差判定,不等于官方持续判定总数。" }); CoachingRecommendation coachingRecommendation2 = coachingRecommendation; coachingRecommendation2.Evidence = RecommendationText(coachingRecommendation2); coachingReport.Recommendations.Add(coachingRecommendation2); } } } } finally { ((System.IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose(); } coachingReport.Recommendations.Sort((Comparison<CoachingRecommendation>)((CoachingRecommendation a, CoachingRecommendation b) => b.Priority.CompareTo(a.Priority))); if (coachingReport.Recommendations.Count > 5) { coachingReport.Recommendations.RemoveRange(5, coachingReport.Recommendations.Count - 5); } Enumerator<CoachingRecommendation> enumerator5 = coachingReport.Recommendations.GetEnumerator(); try { while (enumerator5.MoveNext()) { CoachingRecommendation current7 = enumerator5.Current; List<CoachingEvent> val6 = new List<CoachingEvent>(8); enumerator3 = val2.GetEnumerator(); try { while (enumerator3.MoveNext()) { Sample current8 = enumerator3.Current; if (current8.Phrase.OrderOffset == current7.SourceOffset && current8.Event.Grade != CoachingGrade.Exact) { val6.Add(current8.Event); } } } finally { ((System.IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose(); } CoachingPatternDetail coachingPatternDetail = CoachingPatterns.Describe((System.Collections.Generic.IReadOnlyList<PracticeNote>)run.Notes, (System.Collections.Generic.IReadOnlyList<int>)order, current7.SourceOffset, current7.SourceCount, (System.Collections.Generic.IReadOnlyList<CoachingEvent>)val6); if (coachingPatternDetail.Name.Length > 0) { current7.Configuration = coachingPatternDetail; current7.Start = Math.Min(current7.Start, Math.Max(0.0, coachingPatternDetail.ContextStart - 0.1)); current7.End = Math.Max(current7.End, Math.Min(Math.Max(run.Duration, coachingPatternDetail.ContextEnd), coachingPatternDetail.ContextEnd + 0.1)); int num10 = current7.Label.LastIndexOf(" · ", (StringComparison)4); current7.Label = coachingPatternDetail.Name + ((num10 >= 0) ? current7.Label.Substring(num10) : ""); current7.Advice = coachingPatternDetail.Drill; current7.Evidence = RecommendationText(current7); } } } finally { ((System.IDisposable)enumerator5/*cast due to .constrained prefix*/).Dispose(); } if (learn && memory != null && run.Completed && !flag && val2.Count >= 24 && coachingReport.CaptureCoverage >= 0.85) { int num11 = 0; enumerator4 = val4.Values.GetEnumerator(); try { CoachingMemoryEntry coachingMemoryEntry2 = default(CoachingMemoryEntry); while (enumerator4.MoveNext()) { Aggregate current9 = enumerator4.Current; if (!(current9.LearnExposure < 8.0)) { if (!val5.TryGetValue(current9.Key, ref coachingMemoryEntry2)) { coachingMemoryEntry2 = new CoachingMemoryEntry { Key = current9.Key, Label = current9.Label }; val5.Add(current9.Key, coachingMemoryEntry2); } double num12 = ((coachingReport.SystematicBiasCandidate && current9.LearnNear == 0 && current9.LearnBreak == 0) ? 0.35 : 1.0); double num13 = Math.Clamp(current9.LearnWeight / current9.LearnExposure / 2.2 * num12, 0.0, 1.0); double num14 = Math.Min(24.0, current9.LearnExposure); double num15 = 0.12 + 0.18 * Math.Min(1.0, num14 / 24.0); coachingMemoryEntry2.ErrorEwma = ((coachingMemoryEntry2.Songs == 0) ? num13 : (coachingMemoryEntry2.ErrorEwma * (1.0 - num15) + num13 * num15)); coachingMemoryEntry2.Support = Math.Min(240.0, coachingMemoryEntry2.Support * 0.98 + num14); coachingMemoryEntry2.Songs = Math.Min(9999, coachingMemoryEntry2.Songs + 1); coachingMemoryEntry2.Occurrences = Math.Min(99999, coachingMemoryEntry2.Occurrences + current9.LearnOccurrences); coachingMemoryEntry2.LastSong = Limit(run.SongKey, 160); coachingMemoryEntry2.LastStart = Math.Max(0.0, current9.LearnWorst.Start - 0.6); coachingMemoryEntry2.LastEnd = Math.Min(Math.Max(run.Duration, current9.LearnWorst.End), current9.LearnWorst.End + 0.6); coachingMemoryEntry2.UpdatedUtcTicks = System.DateTime.UtcNow.Ticks; num11++; } } } finally { ((System.IDisposable)enumerator4/*cast due to .constrained prefix*/).Dispose(); } memory.Patterns = new List<CoachingMemoryEntry>((System.Collections.Generic.IEnumerable<CoachingMemoryEntry>)val5.Values); memory.Patterns.Sort((Comparison<CoachingMemoryEntry>)((CoachingMemoryEntry a, CoachingMemoryEntry b) => b.UpdatedUtcTicks.CompareTo(a.UpdatedUtcTicks))); if (memory.Patterns.Count > 256) { memory.Patterns.RemoveRange(256, memory.Patterns.Count - 256); } coachingReport.Learned = num11 > 0; } coachingReport.HistoryPatterns = (memory?.Patterns?.Count).GetValueOrDefault(); coachingReport.Summary = (flag ? "本局含练习/变速/跳转,仅供片段复盘,不用于延迟结论和正常游玩记忆。" : (run.Completed ? ((coachingReport.Recommendations.Count == 0) ? "本局没有足够的重复失误证据;继续正常游玩积累对照。" : "先练多次复现的配置;孤立失误已降权,后续同类表现会提高或降低优先级。") : (run.CaptureIncomplete ? "部分判定未完整采集:仅供参考,本局不写入长期记忆。" : "未完整结算:仅供参考,本局不写入长期记忆。"))); if (run.Completed && run.CaptureIncomplete && !flag) { coachingReport.Summary += " 已排除未完整采集音符,配置记忆仅采用完整段。"; } if (coachingReport.CaptureCoverage < 0.85) { coachingReport.ExcludedReason = Join(coachingReport.ExcludedReason, $"有效覆盖 {coachingReport.CaptureCoverage:0%},不足 85%,本局不更新配置记忆"); } if (coachingReport.InterruptedEvents > 0) { coachingReport.ExcludedReason = Join(coachingReport.ExcludedReason, $"已排除中断附近 {coachingReport.InterruptedEvents} 个判定"); } if (!string.IsNullOrWhiteSpace(run.CaptureNote)) { coachingReport.ExcludedReason = Join(coachingReport.ExcludedReason, Limit(run.CaptureNote, 240)); } return coachingReport; } private static List<Phrase> BuildPhrases(List<PracticeNote> notes, out Phrase[] byNote, out List<int> order) { int num = Math.Min(30000, notes.Count); byNote = new Phrase[num]; order = new List<int>(num); for (int i = 0; i < num; i++) { if (double.IsFinite(notes[i].Start) && double.IsFinite(notes[i].End) && float.IsFinite(notes[i].Lane)) { order.Add(i); } } order.Sort((Comparison<int>)((int a, int b) => notes[a].Start.CompareTo(notes[b].Start))); List<Phrase> val = new List<Phrase>(); for (int num2 = 0; num2 < order.Count; num2 += 8) { int num3 = Math.Min(order.Count, num2 + 8); int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; int num9 = 0; int num10 = 0; double num11 = 0.0; List<double> val2 = new List<double>(); float num12 = 99f; float num13 = -99f; double num14 = 0.0; for (int num15 = num2; num15 < num3; num15++) { PracticeNote practiceNote = notes[order[num15]]; num12 = Math.Min(num12, practiceNote.Lane); num13 = Math.Max(num13, practiceNote.Lane); num14 = Math.Max(num14, Math.Max(practiceNote.Start, practiceNote.End)); if (practiceNote.Surface == PracticeNoteSurface.Side) { num7++; } if (practiceNote.Kind == PracticeNoteKind.Sky) { num8++; } if (practiceNote.Kind == PracticeNoteKind.Flick) { num9++; } if (practiceNote.Kind == PracticeNoteKind.Hold) { num10++; } if (num15 == num2) { continue; } PracticeNote practiceNote2 = notes[order[num15 - 1]]; double num16 = practiceNote.Start - practiceNote2.Start; double num17 = Math.Sign(practiceNote.Lane - practiceNote2.Lane); if (num16 < 0.025) { num4++; } else { val2.Add(num16); } if (num17 == 0.0) { num6++; continue; } if (num11 != 0.0 && num17 != num11) { num5++; } num11 = num17; } double num18 = ((val2.Count == 0) ? 0.5 : Median(val2)); int num19 = ((num18 < 0.12) ? 4 : ((num18 < 0.2) ? 3 : ((num18 < 0.35) ? 2 : ((num18 < 0.6) ? 1 : 0)))); string text = ((num19 >= 3) ? "高密度" : ((num19 == 2) ? "中密度" : "低密度")); string text2 = ((num8 >= 2) ? "空轨移动" : ((num9 >= 2) ? "滑键组合" : ((num7 >= 3) ? "侧轨组合" : ((num10 >= 3) ? "长键衔接" : ((num4 >= 3) ? "多押组合" : ((num6 >= 4) ? "同轨连打" : ((num5 >= 3) ? "交互连打" : ((num13 - num12 >= 3f) ? "跨轨移动" : "顺序短键")))))))); string key = $"v1/{text2}/{num19}/{Math.Min(2, num4)}/{num13 - num12 >= 3f}/{Math.Min(2, num10)}/{Math.Min(2, num8)}"; string text3 = ((text2 == "滑键组合") ? "含 Flick 混合段" : text2); Phrase phrase = new Phrase { Key = key, Label = text3 + " · " + text, Start = notes[order[num2]].Start, End = num14, Expected = num3 - num2, OrderOffset = num2 }; val.Add(phrase); for (int num20 = num2; num20 < num3; num20++) { byNote[order[num20]] = phrase; } } return val; } private static string Counts(CoachingGradeCounts n) { return $"小 E {n.SmallExact} / Near {n.Near} / Break {n.Break}"; } private static string NonzeroCounts(CoachingGradeCounts n) { string text = ""; if (n.SmallExact > 0) { text = $"小E {n.SmallExact}"; } if (n.Near > 0) { text = text + ((text.Length > 0) ? "、" : "") + $"Near {n.Near}"; } if (n.Break > 0) { text = text + ((text.Length > 0) ? "、" : "") + $"Break {n.Break}"; } return text; } public static string RecommendationText(CoachingRecommendation item, bool compact = false) { string text = NonzeroCounts(new CoachingGradeCounts { SmallExact = item.SmallExact, Near = item.Near, Break = item.Break }); string types = ""; Add("Flick", item.NoteOutcomes.Flick); Add("Tap", item.NoteOutcomes.Tap); Add("Hold", item.NoteOutcomes.Hold); Add("Field", item.NoteOutcomes.Field); string text2 = ((item.Configuration != null) ? ("练法:" + (compact ? item.Configuration.ShortDrill : item.Advice)) : (string.IsNullOrEmpty(item.Advice) ? PracticeAdvice(item.NoteOutcomes, item.Near, item.Break) : item.Advice)); return string.Concat(new string[6] { "本段判定:", (text.Length > 0) ? text : "无明显失误", "。", compact ? "" : "\n", (types.Length > 0) ? ("其中," + types + "。\n") : "", text2 }); [CompilerGenerated] void Add(string name, CoachingGradeCounts grades) { string text3 = NonzeroCounts(grades); if (text3.Length > 0) { types = string.Concat(new string[5] { types, (types.Length > 0) ? ";" : "", name, ":", text3 }); } } } private static string PracticeAdvice(CoachingTypeCounts types, int near, int breaks) { if (types.Flick.Break > 0 || types.Flick.Near > 0) { return "建议:放慢确认滑动方向,再接回地键。"; } if (types.Hold.Break > 0 || types.Hold.Near > 0) { return "建议:分开练按下和松开,再接回整段。"; } if (types.Field.Break > 0 || types.Field.Near > 0) { return "建议:跟准天键移动,再加入地键。"; } if (breaks > 0) { return "建议:以 0.75× 练准落点,稳定后再恢复原速。"; } if (near > 0) { return "建议:放慢拆清节奏,稳定后逐步提速。"; } return "建议:原速找准节奏,反复出错再慢练。"; } private static string TypeEvidence(CoachingTypeCounts n) { return $"本段分类型:Flick {Counts(n.Flick)};Tap {Counts(n.Tap)};Hold {Counts(n.Hold)};Field {Counts(n.Field)}。"; } private static void DetectTransient(List<Sample> samples, CoachingReport report) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, HashSet<Phrase>> val = new Dictionary<string, HashSet<Phrase>>(); Dictionary<string, HashSet<Phrase>> val2 = new Dictionary<string, HashSet<Phrase>>(); Enumerator<Sample> enumerator = samples.GetEnumerator(); try { HashSet<Phrase> val3 = default(HashSet<Phrase>); HashSet<Phrase> val4 = default(HashSet<Phrase>); while (enumerator.MoveNext()) { Sample current = enumerator.Current; if (!val.TryGetValue(current.Phrase.Key, ref val3)) { val3 = new HashSet<Phrase>(); val[current.Phrase.Key] = val3; } val3.Add(current.Phrase); if (current.Weight >= 1.0) { if (!val2.TryGetValue(current.Phrase.Key, ref val4)) { val4 = new HashSet<Phrase>(); val2[current.Phrase.Key] = val4; } val4.Add(current.Phrase); } } } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } HashSet<Phrase> val5 = default(HashSet<Phrase>); for (int i = 0; i < samples.Count; i++) { if (samples[i].Weight < 1.0) { continue; } int j; for (j = i; j + 1 < samples.Count && samples[j + 1].Weight >= 1.0 && samples[j + 1].Event.ChartTime - samples[j].Event.ChartTime <= 1.2; j++) { } int num = j - i + 1; double num2 = samples[j].Event.ChartTime - samples[i].Event.ChartTime; bool flag = i >= 3 && j + 3 < samples.Count; for (int k = 1; k <= 3 && flag; k++) { flag = samples[i - k].Weight < 1.0 && samples[j + k].Weight < 1.0 && samples[i].Event.ChartTime - samples[i - k].Event.ChartTime < 4.0 && samples[j + k].Event.ChartTime - samples[j].Event.ChartTime < 4.0; } if (flag && num <= 6 && num2 <= 2.0) { for (int l = i; l <= j; l++) { Sample sample = samples[l]; string key = sample.Phrase.Key; if (!val2.TryGetValue(key, ref val5) || val5.Count < 2 || !((double)val5.Count / (double)val[key].Count >= 0.35)) { sample.Factor = ((num <= 2) ? 0.35 : 0.6); report.TransientEvents++; } } } i = j; } report.SuspectedTransientRate = ((samples.Count == 0) ? 0.0 : ((double)report.TransientEvents / (double)samples.Count)); report.TransientEvidence = $"短暂异常候选 {report.TransientEvents}/{samples.Count}({report.SuspectedTransientRate:0.0%});前后恢复的孤立/短串失误按 0.35/0.60 降权。可能与操作、识谱、遮挡、疲劳或注意力波动有关,不能从判定证明走神。"; } private static void AnalyzeTiming(CoachingRun run, List<Sample> samples, bool contaminated, CoachingReport report) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) if (contaminated || !run.Completed) { report.TimingTitle = "延迟:本局不作估计"; report.ExcludedReason = (contaminated ? "包含练习、变速、循环或跳转" : (run.CaptureIncomplete ? "判定采集不完整" : "尚未完整结算")); report.TimingEvidence = report.ExcludedReason; return; } List<Sample> val = new List<Sample>(); List<double> val2 = new List<double>(); Enumerator<Sample> enumerator = samples.GetEnumerator(); try { while (enumerator.MoveNext()) { Sample current = enumerator.Current; CoachingEvent coachingEvent = current.Event; if (coachingEvent.TimingValid && double.IsFinite(coachingEvent.OffsetMs) && !(Math.Abs(coachingEvent.OffsetMs) > 250.0) && (int)coachingEvent.Grade < 2 && !(current.Factor < 1.0) && current.Phrase.Observed >= 4 && !(current.Phrase.RawWeight / (double)current.Phrase.Observed > 0.4)) { val.Add(current); val2.Add(coachingEvent.OffsetMs); } } } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (val2.Count < 24) { AnalyzeDirection(run, samples, report, val2.Count); return; } double num = Median(val2); List<double> val3 = new List<double>(val2.Count); Enumerator<double> enumerator2 = val2.GetEnumerator(); try { while (enumerator2.MoveNext()) { double current2 = enumerator2.Current; val3.Add(Math.Abs(current2 - num)); } } finally { ((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } double num2 = Median(val3); double num3 = Math.Max(12.0, 4.4478 * num2); List<double> val4 = new List<double>(); List<double>[] array = new List<double>[4] { new List<double>(), new List<double>(), new List<double>(), new List<double>() }; HashSet<string> val5 = new HashSet<string>(); for (int i = 0; i < val.Count; i++) { Sample sample = val[i]; if (!(Math.Abs(sample.Event.OffsetMs - num) > num3)) { val4.Add(sample.Event.OffsetMs); int num4 = (int)Math.Clamp(sample.Event.ChartTime / Math.Max(1.0, run.Duration) * 4.0, 0.0, 3.0); array[num4].Add(sample.Event.OffsetMs); val5.Add(sample.Phrase.Key); } } report.TimingSamples = val4.Count; if (val4.Count < 24) { report.TimingTitle = "延迟:稳定样本不足"; report.TimingEvidence = $"去除异常后仅 {val4.Count} 个有效判定。"; return; } num = Median(val4); val3.Clear(); enumerator2 = val4.GetEnumerator(); try { while (enumerator2.MoveNext()) { double current3 = enumerator2.Current; val3.Add(Math.Abs(current3 - num)); } } finally { ((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose(); } num2 = Median(val3); int num5 = 0; double num6 = 0.0; double num7 = 0.0; double num8 = 1.0 / 0.0; double num9 = -1.0 / 0.0; for (int j = 0; j < 4; j++) { if (array[j].Count >= 5) { double num10 = Median(array[j]); if (num5 == 0) { num6 = num10; } num7 = num10; num5++; num8 = Math.Min(num8, num10); num9 = Math.Max(num9, num10); } } report.DriftMs = num7 - num6; report.OffsetMs = num; report.SpreadMs = 1.4826 * num2; bool flag = num5 >= 3 && num9 - num8 > Math.Max(18.0, report.SpreadMs * 1.5); report.HasOffsetEstimate = num5 >= 3; report.TimingConfidence = Math.Clamp(Math.Min(1.0, (double)val4.Count / 100.0) * ((double)num5 / 4.0) * ((val5.Count > 1) ? 1.0 : 0.7) * (1.0 - Math.Min(0.7, report.SpreadMs / 100.0)) * (flag ? 0.35 : 1.0), 0.0, 1.0); report.SystematicBiasCandidate = report.HasOffsetEstimate && !flag && val5.Count >= 2 && Math.Abs(num) > Math.Max(6.0, report.SpreadMs * 0.35) && report.TimingConfidence >= 0.35; string text = ((!run.SignKnown) ? "有符号偏移" : ((num > 0.0) ? "偏晚" : "偏早")); report.TimingTitle = ((!report.HasOffsetEstimate) ? "延迟:覆盖不足" : (flag ? "延迟:随进度变化" : ((Math.Abs(num) <= Math.Max(6.0, report.SpreadMs * 0.35)) ? "延迟:未见稳定偏移" : ("延迟:稳定" + text)))); if (!run.SignKnown && !flag && report.HasOffsetEstimate && Math.Abs(num) > Math.Max(6.0, report.SpreadMs * 0.35)) { report.TimingTitle = "延迟:方向待确认"; } report.TimingEvidence = $"中位数 {num:+0.0;-0.0;0.0} ms,离散 {report.SpreadMs:0.0} ms;{val4.Count} 次,覆盖 {num5}/4 时段、{val5.Count} 类配置。" + (flag ? $"前后变化 {report.DriftMs:+0.0;-0.0;0.0} ms,可能是熟练度/疲劳/音画漂移,单局无法区分。" : "稳定偏移也可能来自个人按键习惯,不能据此断定设备延迟。") + " 不自动修改设置。"; } private static void AnalyzeDirection(CoachingRun run, List<Sample> samples, CoachingReport report, int exactCount) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) int[] array = new int[4]; int[] array2 = new int[4]; HashSet<string> val = new HashSet<string>(); Enumerator<Sample> enumerator = samples.GetEnumerator(); try { while (enumerator.MoveNext()) { Sample current = enumerator.Current; CoachingEvent coachingEvent = current.Event; if (!(current.Factor < 1.0) && current.Phrase.Observed >= 4 && !(current.Phrase.RawWeight / (double)current.Phrase.Observed > 0.4) && coachingEvent.Grade != CoachingGrade.Break && coachingEvent.Grade != CoachingGrade.Unknown && Math.Abs(coachingEvent.TimingDirection) == 1) { int num = (int)Math.Clamp(coachingEvent.ChartTime / Math.Max(1.0, run.Duration) * 4.0, 0.0, 3.0); if (coachingEvent.TimingDirection < 0) { array[num]++; report.EarlyCount++; } else { array2[num]++; report.LateCount++; } val.Add(current.Phrase.Key); } } } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } int num2 = report.EarlyCount + report.LateCount; int num3 = 0; int num4 = 0; bool flag = report.LateCount > report.EarlyCount; for (int i = 0; i < 4; i++) { if (array[i] + array2[i] >= 5) { num3++; if ((double)(flag ? array2[i] : array[i]) / (double)(array[i] + array2[i]) >= 0.65) { num4++; } } } double num5 = ((num2 == 0) ? 0.0 : ((double)Math.Max(report.EarlyCount, report.LateCount) / (double)num2)); report.TimingSamples = num2; report.HasDirectionEstimate = num2 >= 24 && num3 >= 3; report.TimingConfidence = Math.Clamp(Math.Min(1.0, (double)num2 / 80.0) * ((double)num3 / 4.0) * ((val.Count > 1) ? 1.0 : 0.7) * Math.Max(0.0, (num5 - 0.5) * 2.0), 0.0, 1.0); bool flag2 = report.HasDirectionEstimate && num5 >= 0.72 && num4 == num3; report.SystematicBiasCandidate = flag2 && val.Count >= 2 && num5 >= 0.8; string text = (flag ? "晚" : "早"); bool flag3 = num2 >= 24 && num5 >= 0.65; report.TimingTitle = ((num2 < 24) ? "早晚倾向:样本不足" : (flag2 ? ("早晚倾向:持续偏" + text) : (flag3 ? ("早晚倾向:" + ((num3 >= 3) ? "本局" : "局部") + "偏" + text) : ((num5 >= 0.55) ? ("早晚倾向:略偏" + text) : "早晚倾向:大致均衡")))); report.ExcludedReason = $"当前只有 {exactCount} 个精确时差值,不能换算毫秒或建议偏移数值。"; double num6 = ((num2 == 0) ? 0.0 : ((double)report.LateCount / (double)num2)); report.TimingEvidence = ((num2 < 24) ? $"已记录 {num2} 次早晚判定,样本较少,暂不作整体判断。" : ($"早晚判定中 Late 占 {num6:0%}(Early {report.EarlyCount} / Late {report.LateCount})。" + (flag2 ? $"{num3} 个有效时段都偏{text},倾向比较持续。" : (flag3 ? $"整体偏{text};{num3} 个有效时段中 {num4} 个同向,部分段落的倾向不同。" : "整体较为接近,部分段落仍可能有不同的早晚倾向。")) + ((num3 < 3) ? "样本集中在少数段落,还不能代表整首。" : ""))); report.TimingEvidence = report.TimingEvidence + ((flag3 || flag2) ? "建议:再对照几首熟悉的曲子;若持续同向,再检查偏移设置。" : "建议:先练推荐片段,节奏稳定后再看早晚倾向。") + "早晚等级不能换算毫秒,也不能单凭一局确定设备延迟。"; } private static double Weight(CoachingGrade grade) { return grade switch { CoachingGrade.Break => 2.2, CoachingGrade.Near => 1.0, CoachingGrade.SmallExact => 0.35, _ => 0.0, }; } private static double Median(List<double> values) { values.Sort(); int count = values.Count; if (count != 0) { if (count % 2 != 0) { return values[count / 2]; } return (values[count / 2 - 1] + values[count / 2]) * 0.5; } return 0.0; } private static Dictionary<string, CoachingMemoryEntry> MemoryMap(CoachingMemory memory) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, CoachingMemoryEntry> val = new Dictionary<string, CoachingMemoryEntry>((IEqualityComparer<string>)(object)StringComparer.Ordinal); if (memory?.Patterns == null) { return val; } Enumerator<CoachingMemoryEntry> enumerator = memory.Patterns.GetEnumerator(); try { while (enumerator.MoveNext()) { CoachingMemoryEntry current = enumerator.Current; if (current != null && !string.IsNullOrEmpty(current.Key) && current.Key.Length <= 160 && double.IsFinite(current.ErrorEwma) && current.ErrorEwma >= 0.0 && current.ErrorEwma <= 1.0 && double.IsFinite(current.Support) && current.Support >= 0.0 && current.Support <= 240.0 && current.Songs >= 0 && current.Songs <= 9999) { val[current.Key] = current; } } return val; } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static string Limit(string text, int length) { if (!string.IsNullOrEmpty(text)) { if (text.Length <= length) { return text; } return text.Substring(0, length); } return ""; } private static string Join(string first, string next) { if (!string.IsNullOrEmpty(first)) { return first + ";" + next; } return next; } } public static class CoachingMemoryStore { private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { IncludeFields = true }; public static CoachingMemory Load(string path) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown try { FileInfo val = new FileInfo(path); if (!((FileSystemInfo)val).Exists || val.Length > 524288) { return new CoachingMemory(); } CoachingMemory coachingMemory = JsonSerializer.Deserialize<CoachingMemory>(File.ReadAllText(path), Options); if (coachingMemory == null || coachingMemory.Version != 1 || coachingMemory.Patterns == null) { return new CoachingMemory(); } coachingMemory.Patterns.RemoveAll((Predicate<CoachingMemoryEntry>)delegate(CoachingMemoryEntry e) { if (e != null && !string.IsNullOrEmpty(e.Key) && e.Key.Length <= 160) { string label = e.Label; if (label == null || label.Length <= 160) { string lastSong = e.LastSong; if ((lastSong == null || lastSong.Length <= 160) && double.IsFinite(e.ErrorEwma) && !(e.ErrorEwma < 0.0) && !(e.ErrorEwma > 1.0) && double.IsFinite(e.Support) && !(e.Support < 0.0) && !(e.Support > 240.0) && e.Songs >= 0 && e.Songs <= 9999 && e.Occurrences >= 0) { return e.Occurrences > 99999; } } } return true; }); coachingMemory.Patterns.Sort((Comparison<CoachingMemoryEntry>)((CoachingMemoryEntry a, CoachingMemoryEntry b) => b.UpdatedUtcTicks.CompareTo(a.UpdatedUtcTicks))); if (coachingMemory.Patterns.Count > 256) { coachingMemory.Patterns.RemoveRange(256, coachingMemory.Patterns.Count - 256); } return coachingMemory; } catch (System.Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException || ex is ArgumentException || ex is NotSupportedException) { return new CoachingMemory(); } } public static bool Save(string path, CoachingMemory memory) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) string text = null; try { if (memory == null || memory.Version != 1 || memory.Patterns == null) { return false; } if (memory.Patterns.Count > 256) { return false; } string fullPath = Path.GetFullPath(path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); Guid val = Guid.NewGuid(); text = fullPath + "." + ((Guid)(ref val)).ToString("N") + ".tmp"; string text2 = JsonSerializer.Serialize<CoachingMemory>(memory, Options); if (text2.Length > 262144) { return false; } File.WriteAllText(text, text2); File.Move(text, fullPath, true); text = null; return true; } catch (System.Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException || ex is ArgumentException || ex is NotSupportedException) { return false; } finally { if (text != null) { try { File.Delete(text); } catch { } } } } } public sealed class CoachingCapture { private readonly struct NoteKey : IEquatable<NoteKey> { private readonly long _start; private readonly long _end; private readonly int _lane; private readonly int _endLane; private readonly int _width; private readonly int _endWidth; private readonly int _kind; private readonly int _surface; private readonly int _right; public NoteKey(PracticeNote n) { _start = (long)Math.Round(n.Start * 1000.0); _end = (long)Math.Round(n.End * 1000.0); _lane = (int)Math.Round((double)(n.Lane * 4096f)); _endLane = (int)Math.Round((double)(n.EndLane * 4096f)); _width = (int)Math.Round((double)(n.Width * 4096f)); _endWidth = (int)Math.Round((double)(n.EndWidth * 4096f)); _kind = (int)n.Kind; _surface = (int)n.Surface; _right = (n.RightFlick ? 1 : 0); } public bool Equals(NoteKey n) { if (_start == n._start && _end == n._end && _lane == n._lane && _endLane == n._endLane && _width == n._width && _endWidth == n._endWidth && _kind == n._kind && _surface == n._surface) { return _right == n._right; } return false; } public override bool Equals(object value) { if (value is NoteKey n) { return Equals(n); } return false; } public override int GetHashCode() { return HashCode.Combine<long, long, int, int, int, int, int, int>(_start, _end, _lane, _endLane, _width, _endWidth, _kind, HashCode.Combine<int, int>(_surface, _right)); } } private const int MaximumRecords = 120000; private Il2CppStructArray<_cH> _array; private List<_fA> _source; private Dictionary<long, Memory<_cH>> _slices; private Dictionary<NoteKey, int> _available; private System.Collections.Generic.IReadOnlyList<PracticeNote> _notes; private int[] _byRecord; private int[] _order; private int[] _nextNote; private double[] _times; private uint[] _seen; private byte[] _grades; private sbyte[] _directions; private readonly List<int> _pending = new List<int>(256); private System.IntPtr _playerPointer; private int _base; private int _count; private int _sourceIndex; private int _metadataIndex; private int _nextDue; private int _pendingCursor; private int _mappedRecordCount; private bool _bound; private bool _ready; private bool _failed; private long _nextBind; [field: CompilerGenerated] public string LastError { [CompilerGenerated] get; [CompilerGenerated] private set; } = ""; [field: CompilerGenerated] public int CapturedRecords { [CompilerGenerated] get; [CompilerGenerated] private set; } public void Reset() { ReleaseNative(); _available = null; _notes = null; _byRecord = (_order = (_nextNote = null)); _times = null; _seen = null; _grades = null; _directions = null; _pending.Clear(); if (_pending.Capacity > 4096) { _pending.Capacity = 256; } int mappedRecordCount = (CapturedRecords = 0); _base = (_count = (_sourceIndex = (_metadataIndex = (_nextDue = (_pendingCursor = (_mappedRecordCount = mappedRecordCount)))))); _bound = (_ready = (_failed = false)); LastError = ""; _nextBind = 0L; } private void ReleaseNative() { _array = null; _source = null; _slices = null; _playerPointer = System.IntPtr.Zero; } public void Invalidate(string reason) { _failed = true; LastError = reason ?? "本局采集被中断"; ReleaseNative(); } public void Observe(LogicalNotePlayer player, System.Collections.Generic.IReadOnlyList<PracticeNote> notes, double time, int state) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) if ((state != 2 && state != 3) || _failed || (Object)(object)player == (Object)null || notes == null || ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count == 0) { return; } try { if (!_bound) { if (Stopwatch.GetTimestamp() < _nextBind) { return; } Bind(player, notes); LastError = ""; } if (((Il2CppObjectBase)player).Pointer != _playerPointer) { throw new InvalidOperationException("谱面已替换,停止读取旧局判定"); } long deadline = Stopwatch.GetTimestamp() + (long)((double)Stopwatch.Frequency * 0.00065); if (!_ready) { Prepare(deadline); } else if (state == 3 && double.IsFinite(time)) { Poll(time, deadline); } } catch (System.Exception ex) { LastError = ex.Message; if (_bound) { _failed = true; ReleaseNative(); } else { ReleaseNative(); _nextBind = Stopwatch.GetTimestamp() + Stopwatch.Frequency / 4; } } } private void Bind(LogicalNotePlayer player, System.Collections.Generic.IReadOnlyList<PracticeNote> notes) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) if (((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count > 30000) { throw new InvalidOperationException("谱面数量超过分析上限"); } System.Collections.Generic.IReadOnlyList<PracticeNote> notes2; if (!(notes is NoteBuffer)) { System.Collections.Generic.IReadOnlyList<PracticeNote> readOnlyList = (System.Collections.Generic.IReadOnlyList<PracticeNote>)CopyNotes(notes); notes2 = readOnlyList; } else { notes2 = notes; } _notes = notes2; _T ve = player._ve; Memory<_cH> val = ((ve != null) ? ve._cd : null); Object val2 = val?._object; if (val2 == null) { throw new InvalidOperationException("判定数组尚未就绪"); } NativeJudgementArray.Validate(((Il2CppObjectBase)val2).Pointer); _array = new Il2CppStructArray<_cH>(((Il2CppObjectBase)val2).Pointer); _base = val._index; _count = ve._Dd; int num = val._length & 0x7FFFFFFF; if (_base < 0 || _count <= 0 || _count > 120000 || _count > num || _base > ((Il2CppArrayBase<_cH>)(object)_array).Length - _count) { throw new InvalidOperationException("判定数组范围不正确"); } _source = player._Gb()?.Item2; _slices = ve._XC; if (_source == null || _slices == null || _source.Count > 120000) { throw new InvalidOperationException("谱面关联数据尚未就绪"); } _grades = new byte[((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count]; _directions = new sbyte[((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count]; _byRecord = new int[_count]; System.Array.Fill<int>(_byRecord, -1); _order = new int[_count]; _times = new double[_count]; _seen = new uint[(_count + 31) / 32]; _available = new Dictionary<NoteKey, int>(((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count); _nextNote = new int[((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count]; int num3 = default(int); for (int num2 = ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count - 1; num2 >= 0; num2--) { NoteKey noteKey = new NoteKey(notes[num2]); _nextNote[num2] = (_available.TryGetValue(noteKey, ref num3) ? num3 : (-1)); _available[noteKey] = num2; } _playerPointer = ((Il2CppObjectBase)player).Pointer; _bound = true; } private unsafe void Prepare(long deadline) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected I4, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected I4, but got Unknown //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_03d8: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected I4, but got Unknown //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num4 = default(int); while (_source != null && _sourceIndex < _source.Count && num < 64) { _fA val = _source[_sourceIndex++]; int num2 = (int)val._Ae; int num3 = (int)val._be; if (num2 >= 1 && num2 <= 4 && (num3 == 1 || num3 == 2 || num3 == 4 || num3 == 5)) { float x = ((num2 == 1) ? ((float)val._Ce._KD) : Rational(val._Ce)); float endX = ((num2 == 1) ? ((float)val._de._KD) : Rational(val._de)); float width = ((num2 == 1) ? ((float)val._De._KD) : Rational(val._De)); float endWidth = ((num2 == 1) ? ((float)val._ee._KD) : Rational(val._ee)); PracticeNote n = ChartProjection.Map(num2, num3, (double)val._Be * 0.001, (double)Math.Max(val._Be, val._ce) * 0.001, x, endX, width, endWidth, (int)val._he); NoteKey noteKey = new NoteKey(n); if (_available.TryGetValue(noteKey, ref num4) && num4 >= 0) { _available[noteKey] = _nextNote[num4]; if (_slices.ContainsKey(val._ZD)) { Memory<_cH> obj = _slices[val._ZD]; Object val2 = obj?._object; int num5 = (obj?._index ?? 0) - _base; int num6 = (obj?._length ?? 0) & 0x7FFFFFFF; System.IntPtr? intPtr = ((val2 != null) ? new System.IntPtr?(((Il2CppObjectBase)val2).Pointer) : ((System.IntPtr?)null)); System.IntPtr pointer = ((Il2CppObjectBase)_array).Pointer; if (!intPtr.HasValue || (intPtr.HasValue && intPtr.GetValueOrDefault() != pointer) || num5 < 0 || num6 < 0 || num5 > _count - num6) { throw new InvalidOperationException("音符判定切片与本局数组不匹配"); } for (int i = num5; i < num5 + num6; i++) { if (_byRecord[i] < 0) { _byRecord[i] = num4; } } } } if ((num & 7) == 7 && Stopwatch.GetTimestamp() >= deadline) { return; } } num++; } if (_sourceIndex < _source.Count) { return; } Il2CppStructArray<_cH> array = _array; try { System.ReadOnlySpan<JudgementRecord> readOnlySpan = NativeJudgementArray.ReadOnly(array); int num7 = 0; while (_metadataIndex < _count && num7 < 512) { int num8 = _metadataIndex++; JudgementRecord judgementRecord = *(JudgementRecord*)readOnlySpan[_base + num8]; if (judgementRecord.Id != num8 || !double.IsFinite(judgementRecord.AbsoluteMs)) { throw new InvalidOperationException("判定编号或时间与本局数组不一致"); } _order[num8] = num8; _times[num8] = judgementRecord.AbsoluteMs * 0.001; if ((num7 & 0x1F) == 31 && Stopwatch.GetTimestamp() >= deadline) { return; } num7++; } } finally { GC.KeepAlive((object)array); } if (_metadataIndex < _count) { return; } System.Array.Sort<int>(_order, (Comparison<int>)([CompilerGenerated] (int a, int b) => _times[a].CompareTo(_times[b]))); _mappedRecordCount = 0; for (int num9 = 0; num9 < _count; num9++) { if (_byRecord[num9] >= 0) { _mappedRecordCount++; } } if (_mappedRecordCount == 0) { throw new InvalidOperationException("判定数组已读取,但没有匹配到谱面音符"); } _available = null; _nextNote = null; _source = null; _slices = null; _ready = true; } private unsafe void Poll(double time, long deadline) { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) Il2CppStructArray<_cH> array = _array; try { System.ReadOnlySpan<JudgementRecord> readOnlySpan = NativeJudgementArray.ReadOnly(array); int num = 0; while (_nextDue < _count && _times[_order[_nextDue]] <= time + 1.0 && num < 512) { int num2 = _order[_nextDue++]; if (_byRecord[num2] >= 0) { _pending.Add(num2); } num++; } int num3 = Math.Min(512, _pending.Count); for (int i = 0; i < num3; i++) { if (_pending.Count <= 0) { break; } if (_pendingCursor >= _pending.Count) { _pendingCursor = 0; } int num4 = _pending[_pendingCursor]; JudgementRecord judgementRecord = *(JudgementRecord*)readOnlySpan[_base + num4]; if (judgementRecord.Id != num4) { throw new InvalidOperationException("判定数组已被另一局替换"); } byte result = judgementRecord.Result; if (judgementRecord.Finalized != 0 && result >= 1 && result <= 6) { uint num5 = (uint)(1 << num4); int num6 = num4 >> 5; if ((_seen[num6] & num5) == 0) { _seen[num6] |= num5; CapturedRecords++; int num7 = _byRecord[num4]; int num8 = Severity(result); int num9 = Severity(_grades[num7]); sbyte b = (sbyte)Direction(result); if (num8 > num9) { _grades[num7] = result; _directions[num7] = b; } else if (num8 == num9 && _directions[num7] != b) { _directions[num7] = 0; } } List<int> pending = _pending; int pendingCursor = _pendingCursor; List<int> pending2 = _pending; pending[pendingCursor] = pending2[pending2.Count - 1]; _pending.RemoveAt(_pending.Count - 1); } else { _pendingCursor++; } if ((i & 0x1F) == 31 && Stopwatch.GetTimestamp() >= deadline) { break; } } } finally { GC.KeepAlive((object)array); } } public CoachingRun Finish(string key, string title, double duration, bool practice, bool completed) { ReleaseNative(); bool flag = _ready && CapturedRecords == _mappedRecordCount; CoachingRun coachingRun = new CoachingRun { SongKey = (key ?? ""), SongTitle = (title ?? ""), Duration = duration, Completed = (completed && _ready && !_failed), CaptureIncomplete = (completed && (!flag || _failed)), PracticeUsed = practice, SignKnown = true }; coachingRun.Notes = (_notes as List<PracticeNote>) ?? ((_notes != null) ? CopyNotes(_notes) : new List<PracticeNote>()); coachingRun.CaptureNote = LastError; if (_ready && !flag) { for (int i = 0; i < _count; i++) { if (_byRecord[i] >= 0 && (_seen[i >> 5] & (uint)(1 << i)) == 0) { int num = _byRecord[i]; if ((_grades[num] & 0x80) == 0) { _grades[num] |= 128; coachingRun.ExcludedCaptureNotes++; } } } coachingRun.MissingJudgementRecords = _mappedRecordCount - CapturedRecords; coachingRun.CaptureNote = coachingRun.CaptureNote + ((coachingRun.CaptureNote.Length > 0) ? ";" : "") + $"已排除 {coachingRun.ExcludedCaptureNotes} 个未完整采集音符(缺 {coachingRun.MissingJudgementRecords} 条判定记录);配置记忆只采用完整采集段"; } if (_grades != null) { for (int j = 0; j < _grades.Length; j++) { if (_grades[j] != 0 && (_grades[j] & 0x80) == 0) { PracticeNote practiceNote = _notes[j]; coachingRun.Events.Add(new CoachingEvent { NoteIndex = j, ChartTime = practiceNote.Start, Grade = Grade(_grades[j]), TimingDirection = ((practiceNote.Kind == PracticeNoteKind.Tap) ? _directions[j] : 0), TimingValid = false, Rate = 1.0 }); } } } if (!_ready && coachingRun.CaptureNote.Length == 0) { coachingRun.CaptureNote = "谱面未完成载入或提前退出,按已采集判定展示,不学习缺失数据"; } Reset(); return coachingRun; } private static List<PracticeNote> CopyNotes(System.Collections.Generic.IReadOnlyList<PracticeNote> notes) { List<PracticeNote> val = new List<PracticeNote>(((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count); for (int i = 0; i < ((System.Collections.Generic.IReadOnlyCollection<PracticeNote>)notes).Count; i++) { val.Add(notes[i]); } return val; } internal static CoachingGrade Grade(byte n) { switch (n) { default: return CoachingGrade.Unknown; case 6: return CoachingGrade.Exact; case 4: case 5: return CoachingGrade.SmallExact; case 2: case 3: return CoachingGrade.Near; case 1: return CoachingGrade.Break; } } internal static int Direction(byte n) { switch (n) { default: return 0; case 3: case 5: return 1; case 2: case 4: return -1; } } private static int Severity(byte n) { switch (n) { default: return 0; case 4: case 5: return 1; case 2: case 3: return 2; case 1: return 3; case 0: return -1; } } private static float Rational(_BA n) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (n._lD == 0) { return n._LD; } return (float)n._KD / (float)n._lD; } } public sealed class CoachingController { private readonly object _memoryLock = new object(); private readonly string _directory = Path.Combine(Paths.ConfigPath, "InFalsusPractice", "coaching"); private CoachingMemory _memory; private System.Threading.Tasks.Task<CoachingReport> _pending; private CoachingRun _queued; private int _epoch; private int _pendingEpoch; private int _queuedEpoch; public void NewRun() { _epoch++; CoachingView.Clear(); } public void CloseView() { _epoch++; CoachingView.Clear(); } public void Submit(CoachingRun run) { if (run != null) { if (_pending != null) { _queued = run; _queuedEpoch = _epoch; } else { Start(run, _epoch); } } } private void Start(CoachingRun run, int epoch) { _pendingEpoch = epoch; _pending = System.Threading.Tasks.Task.Run<CoachingReport>((Func<CoachingReport>)delegate { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown lock (_memoryLock) { if (_memory == null) { _memory = CoachingMemoryStore.Load(Path.Combine(_directory, "patterns-v1.json")); } CoachingReport coachingReport = CoachingAnalysis.Analyze(run, _memory); try { Directory.CreateDirectory(_directory); if (coachingReport.Learned && !CoachingMemoryStore.Save(Path.Combine(_directory, "patterns-v1.json"), _memory)) { coachingReport.Learned = false; coachingReport.ExcludedReason += ";长期记忆写入失败,本次只在内存中更新。"; } JsonSerializerOptions val = new JsonSerializerOptions { IncludeFields = true, WriteIndented = true }; string text = Path.Combine(_directory, "last-report.json"); string text2 = text + ".tmp"; File.WriteAllText(text2, JsonSerializer.Serialize<CoachingReport>(coachingReport, val)); File.Move(text2, text, true); } catch (System.Exception ex) { coachingReport.ExcludedReason = coachingReport.ExcludedReason + ((coachingReport.ExcludedReason.Length > 0) ? ";" : "") + "本次记忆保存失败:" + ((MemberInfo)ex.GetType()).Name; } return coachingReport; } }); } public void Poll() { if (_pending != null && ((System.Threading.Tasks.Task)_pending).IsCompleted) { System.Threading.Tasks.Task<CoachingReport> pending = _pending; _pending = null; if (((System.Threading.Tasks.Task)pending).IsFaulted) { Plugin.Logger.LogWarning((object)("结算分析不可用:" + ((System.Exception)(object)((System.Threading.Tasks.Task)pending).Exception)?.GetBaseException().Message)); } else if (!((System.Threading.Tasks.Task)pending).IsCanceled && _pendingEpoch == _epoch) { CoachingView.SetReport(pending.Result); } if (_queued != null) { CoachingRun queued = _queued; _queued = null; Start(queued, _queuedEpoch); } } } } public sealed class CoachingInterruptions { private readonly List<ValueTuple<double, double>> _ranges = new List<ValueTuple<double, double>>(16); private double _chart; private double _wall; private bool _seen; private bool _blocked; public int Count => _ranges.Count; [field: CompilerGenerated] public bool Overflowed { [CompilerGenerated] get; [CompilerGenerated] private set; } public void Reset() { _ranges.Clear(); bool blocked = (Overflowed = false); _seen = (_blocked = blocked); _chart = (_wall = 0.0); } public void Observe(double chart, double wall, bool paused, bool focused) { if (double.IsFinite(chart) && double.IsFinite(wall)) { bool flag = paused || !focused; if (flag || _blocked || (_seen && wall - _wall > 0.25)) { Add(_seen ? _chart : chart, chart); } _seen = true; _chart = chart; _wall = wall; _blocked = flag; } } private void Add(double from, double to) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) double num = Math.Max(0.0, Math.Min(from, to)); double num2 = Math.Max(0.0, Math.Max(from, to)); if (_ranges.Count > 0) { List<ValueTuple<double, double>> ranges = _ranges; ValueTuple<double, double> val = ranges[ranges.Count - 1]; if (num <= val.Item2 + 0.05 && num2 >= val.Item1 - 0.05) { List<ValueTuple<double, double>> ranges2 = _ranges; ranges2[ranges2.Count - 1] = new ValueTuple<double, double>(Math.Min(num, val.Item1), Math.Max(num2, val.Item2)); return; } } if (_ranges.Count >= 64) { Overflowed = true; } else { _ranges.Add(new ValueTuple<double, double>(num, num2)); } } public bool Contains(double time) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) Enumerator<ValueTuple<double, double>> enumerator = _ranges.GetEnumerator(); try { while (enumerator.MoveNext()) { ValueTuple<double, double> current = enumerator.Current; if (time >= current.Item1 - 1.0 && time <= current.Item2 + 1.0) { return true; } } } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } return false; } } public readonly struct CoachingLayout { public const float Width = 860f; public const float Height = 230f; public const float TabWidth = 186f; public const float TabHeight = 32f; public readonly HudArea Area; public readonly float Scale; public readonly bool Compact; public bool Visible => Area.Valid; public CoachingLayout(int width, int height, bool collapsed, HudArea[] protectedAreas, float preferredX = -1f, float preferredY = -1f) { Area = default(HudArea); Scale = 1f; Compact = collapsed; if (width < 320 || height < 240) { return; } HudArea hudArea = Band(width, height); float num = Math.Min(Math.Clamp((float)height / 980f, 0.5f, 1.35f), Math.Min((hudArea.Right - hudArea.Left - 1f) / 860f, (hudArea.Bottom - hudArea.Top - 1f) / 230f)); if (!collapsed) { for (float num2 = num; num2 >= 0.499f; num2 -= 0.01f) { if (TryPlace(width, height, 860f * num2, 230f * num2, protectedAreas, preferredX, preferredY, out var result)) { Area = result; Scale = num2; return; } } } Compact = true; Scale = num; if (TryPlace(width, height, 186f * num, 32f * num, protectedAreas, preferredX, preferredY, out var result2)) { Area = result2; } } public static bool IsFree(HudArea area, int width, int height, HudArea[] protectedAreas) { if (!area.Valid || area.Left < 8f || area.Top < 8f || area.Right > (float)(width - 8) || area.Bottom > (float)(height - 8)) { return false; } HudArea hudArea = Band(width, height); if (area.Left < hudArea.Left || area.Right > hudArea.Right || area.Top < hudArea.Top || area.Bottom > hudArea.Bottom) { return false; } if (protectedAreas != null) { for (int i = 0; i < protectedAreas.Length; i++) { HudArea hudArea2 = protectedAreas[i]; if (hudArea2.Valid && area.Left < hudArea2.Right + 6f && area.Right > hudArea2.Left - 6f && area.Top < hudArea2.Bottom + 6f && area.Bottom > hudArea2.Top - 6f) { return false; } } } return true; } private static HudArea Band(int width, int height) { return new HudArea((float)width * 0.301f, (float)height * 0.628f, (float)width * 0.696f, (float)height * 0.8f); } public static HudArea CharacterProtection(HudArea projected, int width) { if (!projected.Valid) { return default(HudArea); } return new HudArea(projected.Left, projected.Top, Math.Min(projected.Right, (float)width * 0.334f), projected.Bottom); } private static bool TryPlace(int width, int height, float panelWidth, float panelHeight, HudArea[] protectedAreas, float preferredX, float preferredY, out HudArea result) { result = default(HudArea); int num = ((protectedAreas != null) ? protectedAreas.Length : 0); bool flag = panelHeight < (float)height * 0.06f; HudArea hudArea = Band(width, height); float left = hudArea.Left; float top = hudArea.Top; float right = hudArea.Right; float bottom = hudArea.Bottom; if (right - left < panelWidth || bottom - top < panelHeight) { return false; } float num2 = ((preferredX >= 0f) ? preferredX : ((left + right - panelWidth) * 0.5f)); float num3 = ((preferredY >= 0f) ? preferredY : (flag ? (bottom - panelHeight - 4f) : ((top + bottom - panelHeight) * 0.5f))); float num4 = 3.4028235E+38f; int num5 = num * 2 + 3; for (int i = 0; i < num5; i++) { float num6 = Math.Clamp(i switch { 2 => right - panelWidth, 1 => left, 0 => num2, _ => ((i - 3) % 2 == 0) ? (protectedAreas[(i - 3) / 2].Left - panelWidth - 7f) : (protectedAreas[(i - 3) / 2].Right + 7f), }, left, Math.Max(left, right - panelWidth)); for (int j = 0; j < num5; j++) { float num7 = Math.Clamp(j switch { 2 => bottom - panelHeight, 1 => top, 0 => num3, _ => ((j - 3) % 2 == 0) ? (protectedAreas[(j - 3) / 2].Top - panelHeight - 7f) : (protectedAreas[(j - 3) / 2].Bottom + 7f), }, top, Math.Max(top, bottom - panelHeight)); HudArea hudArea2 = new HudArea(num6, num7, num6 + panelWidth, num7 + panelHeight); if (IsFree(hudArea2, width, height, protectedAreas)) { float num8 = Math.Abs(num6 - num2) + Math.Abs(num7 - num3) * 1.25f; if (!(num8 >= num4)) { num4 = num8; result = hudArea2; } } } } return result.Valid; } } public sealed class CoachingModule { private readonly CoachingController _controller = new CoachingController(); internal readonly CoachingResultObserver Results = new CoachingResultObserver(); internal readonly CoachingCapture Capture = new CoachingCapture(); internal readonly CoachingInterruptions Interruptions = new CoachingInterruptions(); internal bool Pending; internal bool CaptureStartedLogged; internal string CaptureErrorLogged = ""; internal double LastPlayingTime; public void NewRun() { _controller.NewRun(); Results.Clear(); Capture.Reset(); Interruptions.Reset(); Pending = (CaptureStartedLogged = false); CaptureErrorLogged = ""; LastPlayingTime = 0.0; } public void Poll() { _controller.Poll(); } public void CloseView() { _controller.CloseView(); } public void ExitResults() { Results.Close(); Results.TakeExit(); CloseView(); } public void Draw(double now, bool hidden, bool enabled) { bool inResult = enabled && Results.Read(now); if (Results.TakeExit()) { CloseView(); } CoachingView.Draw(inResult, hidden, Results.Protected); } public void Finish(string chartKey, string title, double duration, bool tainted, bool mirrored = false) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!Pending) { Capture.Reset(); return; } Pending = false; try { CoachingRun coachingRun = Capture.Finish(chartKey, title, duration, tainted, LastPlayingTime >= Math.Max(0.0, duration - 2.0)); coachingRun.Notes = ChartMirror.ForReview(coachingRun.Notes, mirrored); Enumerator<CoachingEvent> enumerator = coachingRun.Events.GetEnumerator(); try { while (enumerator.MoveNext()) { CoachingEvent current = enumerator.Current; if (Interruptions.Contains(current.ChartTime)) { current.Flags |= CoachingFlags.PausedBoundary; } } } finally { ((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (Interruptions.Overflowed) { coachingRun.Completed = false; coachingRun.CaptureNote += ";中断次数超过分析上限,本局不学习。"; } _controller.Submit(coachingRun); Plugin.LogInfo(string.Concat(new string[8] {