// Delete a mod by its index. The event is invoked before the mod is removed from the list. // Deletes from filesystem as well as from internal data. // Updates indices of later mods. public void DeleteMod(int idx) { var mod = this[idx]; if (Directory.Exists(mod.ModPath.FullName)) { try { Directory.Delete(mod.ModPath.FullName, true); PluginLog.Debug("Deleted directory {Directory:l} for {Name:l}.", mod.ModPath.FullName, mod.Name); } catch (Exception e) { PluginLog.Error($"Could not delete the mod {mod.ModPath.Name}:\n{e}"); } } ModPathChanged.Invoke(ModPathChangeType.Deleted, mod, mod.ModPath, null); _mods.RemoveAt(idx); foreach (var remainingMod in _mods.Skip(idx)) { --remainingMod.Index; } PluginLog.Debug("Deleted mod {Name:l}.", mod.Name); }
private unsafe void ReloadCharacterResources() { var oldResources = new IntPtr[NumResources]; var resources = new IntPtr(&CharacterUtility->Resources); var pResources = ( void ** )resources.ToPointer(); Marshal.Copy(resources, oldResources, 0, NumResources); LoadDataFiles(CharacterUtility); for (var i = 0; i < NumResources; i++) { var handle = ( ResourceHandle * )oldResources[i]; if (oldResources[i].ToPointer() == pResources[i]) { PluginLog.Debug($"Unchanged resource: {ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}"); (( ResourceHandle * )oldResources[i])->DecRef(); continue; } PluginLog.Debug("Freeing " + $"{ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}, replaced with " + $"{ResourceToPath( ( byte* )pResources[ i ] )}"); UnloadCharacterResource(oldResources[i]); // Temporary fix against crashes? if (handle->RefCount <= 0) { handle->RefCount = 1; handle->IncRef(); handle->RefCount = 1; } } }
private void StoreLog(string msg) { if (_config.DebugLogEnabled) { PluginLog.Debug($"[Store] {msg}"); } }
public unsafe void ReloadCharacterResources() { var oldResources = new IntPtr[NumResources]; var resources = new IntPtr(&CharacterResourceManagerPtr->Resources); var pResources = ( void ** )resources.ToPointer(); Marshal.Copy(resources, oldResources, 0, NumResources); LoadCharacterResources(CharacterResourceManagerPtr); for (var i = 0; i < NumResources; i++) { if (oldResources[i].ToPointer() == pResources[i]) { PluginLog.Debug($"Unchanged resource: {ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}"); continue; } PluginLog.Debug("Freeing " + $"{ResourceToPath( ( byte* )oldResources[ i ].ToPointer() )}, replaced with " + $"{ResourceToPath( ( byte* )pResources[ i ] )}"); UnloadCharacterResource(oldResources[i]); } }
public static void Log(object message) { foreach (var m in SplitMessage(message)) { PluginLog.Debug($"{m}"); } }
/// <inheritdoc/> public async override Task Execute(CancellationToken token) { PluginLog.Debug($"Executing: {this.Text}"); Service.ChatManager.SendMessage(this.Text); await this.PerformWait(token); }
public override void Render(AdvRadialMenu radialMenu) { if (radialMenu.RadialMenuItem(this.Title)) { PluginLog.Debug($"{cljb.Where(x => x.NameEnglish.ToString().Equals(this.Title)).First().NameEnglish.ToString()}"); MZRadialMenu.Instance.ExecuteCommand($"/gs change \"{cljb.Where(x => x.NameEnglish.ToString().Equals(this.Title)).First().NameEnglish.ToString()}\""); } }
public MusicManager() { SignatureHelper.Initialise(this); var framework = Dalamud.Framework.Address.BaseAddress; var musicManagerOffset = *( int * )(_musicInitCallLocation + 3); _musicManager = *( IntPtr * )(framework + musicManagerOffset); PluginLog.Debug("MusicManager found at 0x{Location:X16}", _musicManager.ToInt64()); }
// Reload the whole filesystem from currently loaded mods and the current sort order file. // Used on construction and on mod rediscoveries. private void Reload() { if (Load(new FileInfo(ModFileSystemFile), Penumbra.ModManager, ModToIdentifier, ModToName)) { Save(); } PluginLog.Debug("Reloaded mod filesystem."); }
public void SetPose(int which, byte toWhat) { if (toWhat == UnchangedPose) return; if (toWhat == DefaultPose) { toWhat = _defaultPoses[which]; } else if (toWhat >= NumPoses[which]) { PluginLog.Error($"Higher pose requested than possible for {PoseNames[which]}: {toWhat} / {NumPoses[which]}."); return; } if (PlayerPointer == IntPtr.Zero) return; var currentState = GetSeatingState(); currentState = TranslateState(currentState, WeaponDrawn); var pose = GetPose(which); if (currentState == which) { if (toWhat == GetCPoseActorState()) { if (pose != toWhat) { WritePose(which, toWhat); PluginLog.Debug("Overwrote {OldPose} with {NewPose} for {WhichPose:l}, currently in {CurrentState:l}.", pose, toWhat, PoseNames[which], PoseNames[currentState]); } } else { Task.Run(() => { var i = 0; do { PluginLog.Debug("Execute /cpose to get from {OldPose} to {NewPose} of {CurrentState:l}.", pose, toWhat, PoseNames[currentState]); _commandManager.Execute("/cpose"); Task.Delay(50); } while (toWhat != GetCPoseActorState() && i++ < 8); if (i > 8) PluginLog.Error("Could not change pose of {CurrentState:l}.", PoseNames[GetCPoseActorState()]); }); } } else if (pose != toWhat) { WritePose(which, toWhat); PluginLog.Debug("Overwrote {OldPose} with {NewPose} for {WhichPose:l}, currently in {CurrentState:l}.", pose, toWhat, PoseNames[which], PoseNames[currentState]); } }
// private static bool CurrentSongShouldBeIgnored() // { // if (OrchestrionPlugin.Configuration.SongReplacements.TryGetValue(CurrentSongId, out var potentialReplacement)) // { // if (PlayingSongId != 0 && potentialReplacement.ReplacementId == SongReplacement.NoChangeId) // return true; // } // // return false; // } public static void SetSong(ushort songId, int priority = 0) { if (priority < 0 || priority >= SceneCount) { throw new IndexOutOfRangeException(); } if (BGMAddressResolver.BGMSceneList != IntPtr.Zero) { unsafe { var bgms = (BGMScene *)BGMAddressResolver.BGMSceneList.ToPointer(); // sometimes we only have to set the first and it will set the other 2 // but particularly on stop/clear, the 2nd seems important as well bgms[priority].bgmReference = songId; bgms[priority].bgmId = songId; bgms[priority].previousBgmId = songId; if (songId == 0 && priority == 0) { bgms[priority].flags = SceneZeroFlags; } // these are probably not necessary, but clear them to be safe bgms[priority].timer = 0; bgms[priority].timerEnable = 0; PlayingSongId = songId; PlayingScene = priority; // unk5 is set to 0x100 by the game in some cases for priority 0 // but I wasn't able to see that it did anything if (!SongList.TryGetSong(songId, out var song)) { return; } // I hate my life if (song.DisableRestart) { bgms[priority].flags = SceneFlags.EnableDisableRestart; var disableRestart = AddDisableRestartId(&bgms[priority], songId); PluginLog.Debug($"AddDisableRestartId: {(ulong) disableRestart:X}"); bgms[priority].flags = SceneZeroFlags; // A lot. Task.Delay(500).ContinueWith(_ => { bgms[priority].flags = GetSceneFlagsNeededForBgm(song); }); } } } }
public Penumbra(DalamudPluginInterface pluginInterface) { FFXIVClientStructs.Resolver.Initialize(); Dalamud.Initialize(pluginInterface); GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage); Config = Configuration.Load(); MusicManager = new MusicManager(); MusicManager.DisableStreaming(); var gameUtils = Service <ResidentResources> .Set(); PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects); Service <MetaDefaults> .Set(); var modManager = Service <ModManager> .Set(); modManager.DiscoverMods(); ObjectReloader = new ObjectReloader(modManager, Config.WaitFrames); ResourceLoader = new ResourceLoader(this); Dalamud.Commands.AddHandler(CommandName, new CommandInfo(OnCommand) { HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", }); ResourceLoader.Init(); ResourceLoader.Enable(); gameUtils.ReloadPlayerResources(); SettingsInterface = new SettingsInterface(this); if (Config.EnableHttpApi) { CreateWebServer(); } if (!Config.EnablePlayerWatch || !Config.IsEnabled) { PlayerWatcher.Disable(); } PlayerWatcher.PlayerChanged += p => { PluginLog.Debug("Triggered Redraw of {Player}.", p.Name); ObjectReloader.RedrawObject(p, RedrawType.OnlyWithSettings); }; Api = new PenumbraApi(this); SubscribeItemLinks(); Ipc = new PenumbraIpc(pluginInterface, Api); }
public SeAddressBase(SigScanner sigScanner, string signature, int offset = 0) { Address = sigScanner.GetStaticAddressFromSig(signature); if (Address != IntPtr.Zero) { Address += offset; } var baseOffset = (ulong)Address.ToInt64() - (ulong)sigScanner.Module.BaseAddress.ToInt64(); PluginLog.Debug($"{GetType().Name} address 0x{Address.ToInt64():X16}, baseOffset 0x{baseOffset:X16}."); }
public Hook <T>?CreateHook(T detour, object callback) { if (Address != IntPtr.Zero) { var hook = new Hook <T>(Address, (T)detour, callback); hook.Enable(); PluginLog.Debug($"Hooked onto {GetType().Name} at address 0x{Address.ToInt64():X16}."); return(hook); } PluginLog.Error($"Trying to create Hook for {GetType().Name}, but no pointer available."); return(null); }
// Create a backup. Overwrites pre-existing backups. private void Create() { try { Delete(); ZipFile.CreateFromDirectory(_mod.ModPath.FullName, Name, CompressionLevel.Optimal, false); PluginLog.Debug("Created backup file {backupName} from {modDirectory}.", Name, _mod.ModPath.FullName); } catch (Exception e) { PluginLog.Error($"Could not backup mod {_mod.Name} to \"{Name}\":\n{e}"); } }
// Remove the given collection if it exists and is neither the empty nor the default-named collection. // If the removed collection was active, it also sets the corresponding collection to the appropriate default. // Also removes the collection from inheritances of all other collections. public bool RemoveCollection(int idx) { if (idx <= Empty.Index || idx >= _collections.Count) { PluginLog.Error("Can not remove the empty collection."); return(false); } if (idx == DefaultName.Index) { PluginLog.Error("Can not remove the default collection."); return(false); } if (idx == Current.Index) { SetCollection(DefaultName, Type.Current); } if (idx == Default.Index) { SetCollection(Empty, Type.Default); } foreach (var(characterName, _) in _characters.Where(c => c.Value.Index == idx).ToList()) { SetCollection(Empty, Type.Character, characterName); } var collection = _collections[idx]; collection.Delete(); _collections.RemoveAt(idx); foreach (var c in _collections) { var inheritedIdx = c._inheritance.IndexOf(collection); if (inheritedIdx >= 0) { c.RemoveInheritance(inheritedIdx); } if (c.Index > idx) { --c.Index; } } PluginLog.Debug("Removed collection {Name:l}.", collection.Name); CollectionChanged.Invoke(Type.Inactive, collection, null); return(true); }
public static void Init(SigScanner sig) { _baseAddress = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 85 C0 74 37 83 78 08 04", 2); _addRestartId = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 30 48 8B 41 20 48 8D 79 18"); _getSpecialMode = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 8B 41 10 33 DB"); PluginLog.Debug($"BGMAddressResolver init: baseaddress at {_baseAddress.ToInt64():X}"); var musicLoc = sig.ScanText("48 8B 8E ?? ?? ?? ?? 39 78 20 0F 94 C2 45 33 C0"); var musicOffset = Marshal.ReadInt32(musicLoc + 3); _musicManager = Marshal.ReadIntPtr(OrchestrionPlugin.Framework.Address.BaseAddress + musicOffset); PluginLog.Debug($"MusicManager found at {_musicManager.ToInt64():X}"); }
public void CalculateEffectiveFileList(DirectoryInfo modDir, bool withMetaManipulations, bool activeCollection) { PluginLog.Debug("Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name, withMetaManipulations, activeCollection); Cache ??= new ModCollectionCache(Name, modDir); UpdateSettings(false); Cache.CalculateEffectiveFileList(); if (withMetaManipulations) { Cache.UpdateMetaManipulations(); if (activeCollection) { Service <ResidentResources> .Get().ReloadPlayerResources(); } } }
// Delete a pre-existing backup. public void Delete() { if (!Exists) { return; } try { File.Delete(Name); PluginLog.Debug("Deleted backup file {backupName}.", Name); } catch (Exception e) { PluginLog.Error($"Could not delete file \"{Name}\":\n{e}"); } }
internal static void DisposeDevice() { try { CurrentInputDevice.EventReceived -= InputDevice_EventReceived; CurrentInputDevice.Reset(); } catch (Exception e) { PluginLog.Debug($"possible null inputDevice. {e}"); } finally { CurrentInputDevice?.Dispose(); CurrentInputDevice = null; } }
private static unsafe void DrawButtonPlayPause() { var PlayPauseIcon = IsPlaying ? FontAwesomeIcon.Pause.ToIconString() : FontAwesomeIcon.Play.ToIconString(); if (ImGui.Button(PlayPauseIcon)) { PluginLog.Debug($"PlayPause pressed. wasplaying: {IsPlaying}"); if (IsPlaying) { PlayerControl.Pause(); } else { PlayerControl.Play(); } } }
private IntPtr AddonSelectYesNoOnSetupDetour(IntPtr addon, uint a2, IntPtr dataPtr) { PluginLog.Debug($"AddonSelectYesNo.OnSetup"); var result = AddonSelectYesNoOnSetupHook.Original(addon, a2, dataPtr); try { var data = Marshal.PtrToStructure <AddonSelectYesNoOnSetupData>(dataPtr); var text = LastSeenDialogText = Marshal.PtrToStringAnsi(data.textPtr).Replace('\n', ' '); if (Configuration.Enabled) { foreach (var item in Configuration.TextEntries) { if (item.Enabled && !string.IsNullOrEmpty(item.Text)) { if ((item.IsRegex && (item.Regex?.IsMatch(text) ?? false)) || (!item.IsRegex && text.Contains(item.Text))) { unsafe { var addonObj = (AddonSelectYesno *)addon; var yesButton = addonObj->YesButton; if (yesButton != null && !yesButton->IsEnabled) { PluginLog.Debug($"AddonSelectYesNo: Enabling yes button"); yesButton->AtkComponentBase.OwnerNode->AtkResNode.Flags ^= 1 << 5; } } PluginLog.Debug($"AddonSelectYesNo: Selecting yes"); Click.SendClick("select_yes"); break; } } } } } catch (Exception ex) { PluginLog.Error(ex, "Don't crash the game"); } return(result); }
// Add a new collection of the given name. // If duplicate is not-null, the new collection will be a duplicate of it. // If the name of the collection would result in an already existing filename, skip it. // Returns true if the collection was successfully created and fires a Inactive event. // Also sets the current collection to the new collection afterwards. public bool AddCollection(string name, ModCollection?duplicate) { if (!CanAddCollection(name, out var fixedName)) { PluginLog.Warning($"The new collection {name} would lead to the same path {fixedName} as one that already exists."); return(false); } var newCollection = duplicate?.Duplicate(name) ?? CreateNewEmpty(name); newCollection.Index = _collections.Count; _collections.Add(newCollection); newCollection.Save(); PluginLog.Debug("Added collection {Name:l}.", newCollection.Name); CollectionChanged.Invoke(Type.Inactive, null, newCollection); SetCollection(newCollection.Index, Type.Current); return(true); }
public MusicManager( ) { var framework = Dalamud.Framework.Address.BaseAddress; // the wildcard is basically the framework offset we want (lol) // .text:000000000009051A 48 8B 8E 18 2A 00 00 mov rcx, [rsi+2A18h] // .text:0000000000090521 39 78 20 cmp [rax+20h], edi // .text:0000000000090524 0F 94 C2 setz dl // .text:0000000000090527 45 33 C0 xor r8d, r8d // .text:000000000009052A E8 41 1C 15 00 call musicInit var musicInitCallLocation = Dalamud.SigScanner.ScanText("48 8B 8E ?? ?? ?? ?? 39 78 20 0F 94 C2 45 33 C0"); var musicManagerOffset = *( int * )(musicInitCallLocation + 3); PluginLog.Debug("Found MusicInitCall location at 0x{Location:X16}. Framework offset for MusicManager is 0x{Offset:X8}", musicInitCallLocation.ToInt64(), musicManagerOffset); _musicManager = *( IntPtr * )(framework + musicManagerOffset); PluginLog.Debug("MusicManager found at 0x{Location:X16}", _musicManager); }
internal static async Task WaitSwitchInstrument() { var match = regex.Match(PlaylistManager.Filelist[PlaylistManager.CurrentPlaying].Item2); if (Plugin.config.autoSwitchInstrument && match.Success) { var wasplaying = Plugin.IsPlaying; Plugin.currentPlayback?.Stop(); var captured = match.Groups[1].Value.ToLowerInvariant(); Perform possibleInstrument = Plugin.InstrumentSheet.FirstOrDefault(i => i.Instrument.RawString.ToLowerInvariant() == captured); Perform possibleGMName = Plugin.InstrumentSheet.FirstOrDefault(i => i.Name.RawString.ToLowerInvariant().Contains(captured)); PluginLog.Debug($"{captured} {possibleInstrument} {possibleGMName} {(possibleInstrument ?? possibleGMName)?.Instrument} {(possibleInstrument ?? possibleGMName)?.Name}"); var key = possibleInstrument ?? possibleGMName; if (key != null) { if (key.RowId != 0) { await SwitchTo(key.RowId); } else { PluginLog.Debug("key.RowId == 0, not gonna switch instrument."); } } else { PluginLog.Error($"no instrument named {captured} found."); } if (wasplaying) { Plugin.currentPlayback?.Start(); } //PluginLog.Debug($"groups {match.Groups.Count}; captures {match.Captures.Count}"); //PluginLog.Debug("groups: " + string.Join("/", match.Groups.OfType<Group>().Select(i => $"[{i.Name}] {i.Value}"))); //PluginLog.Debug("captures: " + string.Join("/", match.Captures.OfType<Capture>().Select(i => i.Value))); } }
// Load a new mod and add it to the manager if successful. public void AddMod(DirectoryInfo modFolder) { if (_mods.Any(m => m.ModPath.Name == modFolder.Name)) { return; } var mod = LoadMod(modFolder); if (mod == null) { return; } mod.Index = _mods.Count; _mods.Add(mod); ModPathChanged.Invoke(ModPathChangeType.Added, mod, null, mod.ModPath); PluginLog.Debug("Added new mod {Name:l} from {Directory:l}.", mod.Name, modFolder.FullName); }
// Restore a mod from a pre-existing backup. Does not check if the mod contained in the backup is even similar. // Does an automatic reload after extraction. public void Restore() { try { if (Directory.Exists(_mod.ModPath.FullName)) { Directory.Delete(_mod.ModPath.FullName, true); PluginLog.Debug("Deleted mod folder {modFolder}.", _mod.ModPath.FullName); } ZipFile.ExtractToDirectory(Name, _mod.ModPath.FullName); PluginLog.Debug("Extracted backup file {backupName} to {modName}.", Name, _mod.ModPath.FullName); Penumbra.ModManager.ReloadMod(_mod.Index); } catch (Exception e) { PluginLog.Error($"Could not restore {_mod.Name} from backup \"{Name}\":\n{e}"); } }
private void DrawLogLoadedFilesBox() { ImGui.Checkbox(LabelLogLoadedFiles, ref _base._penumbra.ResourceLoader.LogAllFiles); ImGui.SameLine(); var regex = _base._penumbra.ResourceLoader.LogFileFilter?.ToString() ?? string.Empty; var tmp = regex; if (ImGui.InputTextWithHint("##LogFilter", "Matching this Regex...", ref tmp, 64) && tmp != regex) { try { var newRegex = tmp.Length > 0 ? new Regex(tmp, RegexOptions.Compiled) : null; _base._penumbra.ResourceLoader.LogFileFilter = newRegex; } catch (Exception e) { PluginLog.Debug("Could not create regex:\n{Exception}", e); } } }
public unsafe SafeNamePlateObject GetNamePlateObject(int index) { if (Pointer == IntPtr.Zero) { PluginLog.Debug($"[{GetType().Name}] AddonNamePlate was null"); return(null); } var npObjectArrayPtrPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32(); var npObjectArrayPtr = Marshal.ReadIntPtr(npObjectArrayPtrPtr); if (npObjectArrayPtr == IntPtr.Zero) { PluginLog.Debug($"[{GetType().Name}] NamePlateObjectArray was null"); return(null); } var npObjectPtr = npObjectArrayPtr + Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject)) * index; return(new SafeNamePlateObject(npObjectPtr, index)); }
public static Playback GetFilePlayback(this(MidiFile, string) fileTuple) { var file = fileTuple.Item1; MidiClockSettings clock = new MidiClockSettings { CreateTickGeneratorCallback = () => new HighPrecisionTickGenerator() }; try { CurrentTMap = file.GetTempoMap(); } catch (Exception e) { PluginLog.Debug("error when getting tmap, using default tmap instead."); CurrentTMap = TempoMap.Default; } Playback playback; try { CurrentTracks = file.GetTrackChunks() .Where(i => i.GetNotes().Any()) .Select(i => { var TrackName = string.Join(", ", i.Events.OfType <SequenceTrackNameEvent>().Select(j => j.Text.Replace("\0", string.Empty).Trim())); //var ProgramChangeEvent = string.Join(", ", i.Events.OfType<ProgramChangeEvent>().Select(j => j.ToString())); if (string.IsNullOrWhiteSpace(TrackName)) { TrackName = "Untitled"; } //var EventTypes = string.Join(", ", i.Events.GroupBy(j => j.EventType).Select(j => j.Key)); //var instrumentsName = string.Join(", ", i.Events.OfType<InstrumentNameEvent>().Select(j => j)); //try //{ var notes = i.GetNotes().ToList(); var notesCount = notes.Count; var notesHighest = notes.MaxElement(j => (int)j.NoteNumber).ToString(); var notesLowest = notes.MinElement(j => (int)j.NoteNumber).ToString(); //TrackName = "Note Track " + TrackName; var duration = TimeSpan.FromTicks(i.GetPlayback(CurrentTMap).GetDuration <MetricTimeSpan>().TotalMicroseconds * 10); //return (i, $"{TrackName} / {notesCount} notes / {notesLowest}-{notesHighest} / {(int)duration.TotalMinutes:00}:{duration.Seconds:00}.{duration.Milliseconds:000}"); return(i, $"{TrackName} / {notesCount} notes / {notesLowest}-{notesHighest}"); //} //catch (Exception e) //{ // var eventsCount = i.Events.Count; // var events = string.Join("\n", i.Events.Select(j => j.ToString().Replace("\0", string.Empty).Trim())); // var eventTypes = string.Join("", i.Events.GroupBy(j => j.EventType).Select(j => $"\n[{j.Key} {j.Count()}]")); // TrackName = "Control Track " + TrackName; // return (i, $"{TrackName} / {eventsCount} events{eventTypes}\n{file.GetDuration<MetricTimeSpan>()} / {i.GetPlayback(CurrentTMap).GetDuration<MetricTimeSpan>()}"); //} }).ToList(); List <TrackChunk> SelectedTracks = new List <TrackChunk>(); if (CurrentTracks.Count > 1) { for (int i = 0; i < CurrentTracks.Count; i++) { if (config.EnabledTracks[i]) { SelectedTracks.Add(CurrentTracks[i].Item1); } } } else { SelectedTracks = CurrentTracks.Select(i => i.Item1).ToList(); } playback = SelectedTracks.GetPlayback(CurrentTMap, BardPlayer, clock); } catch (Exception e) { PluginLog.Debug("error when parsing tracks, falling back to generated MidiEvent playback."); try { PluginLog.Debug($"file.Chunks.Count {file.Chunks.Count}"); var trackChunks = file.GetTrackChunks().ToList(); PluginLog.Debug($"file.GetTrackChunks.Count {trackChunks.Count()}"); PluginLog.Debug($"file.GetTrackChunks.First {trackChunks.First()}"); PluginLog.Debug($"file.GetTrackChunks.Events.Count {trackChunks.First().Events.Count()}"); PluginLog.Debug($"file.GetTrackChunks.Events.OfType<NoteEvent>.Count {trackChunks.First().Events.OfType<NoteEvent>().Count()}"); CurrentTracks = trackChunks.Select(i => { var notes = i.Events.OfType <NoteEvent>().GetNotes().ToList(); var notesCount = notes.Count; var notesHighest = notes.MaxElement(j => (int)j.NoteNumber).ToString(); var notesLowest = notes.MinElement(j => (int)j.NoteNumber).ToString(); var s = $"Reconstructed / {notesCount} notes / {notesLowest}-{notesHighest}"; return(new TrackChunk(i.Events.OfType <NoteEvent>()), s); }).ToList(); List <TrackChunk> SelectedTracks = new List <TrackChunk>(); for (int i = 0; i < CurrentTracks.Count; i++) { if (config.EnabledTracks[i]) { SelectedTracks.Add(CurrentTracks[i].Item1); } } playback = SelectedTracks.GetPlayback(CurrentTMap, BardPlayer, clock); } catch (Exception exception) { PluginLog.Error(e, "still errors? check your file"); throw; } } playback.InterruptNotesOnStop = true; playback.Speed = config.playSpeed; playback.Finished += Playback_Finished; return(playback); }