Decompiled source of DOOM for Crispberry v1.0.1
plugins/data/lib/ManagedDoom.Core.dll
Decompiled 18 hours 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.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using ManagedDoom.Audio; using ManagedDoom.UserInput; using ManagedDoom.Video; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ManagedDoom.Core")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ManagedDoom.Core")] [assembly: AssemblyTitle("ManagedDoom.Core")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ManagedDoom { public static class ApplicationInfo { public static readonly string Title = "Managed Doom v2.1a"; } public enum Bgm { NONE, E1M1, E1M2, E1M3, E1M4, E1M5, E1M6, E1M7, E1M8, E1M9, E2M1, E2M2, E2M3, E2M4, E2M5, E2M6, E2M7, E2M8, E2M9, E3M1, E3M2, E3M3, E3M4, E3M5, E3M6, E3M7, E3M8, E3M9, INTER, INTRO, BUNNY, VICTOR, INTROA, RUNNIN, STALKS, COUNTD, BETWEE, DOOM, THE_DA, SHAWN, DDTBLU, IN_CIT, DEAD, STLKS2, THEDA2, DOOM2, DDTBL2, RUNNI2, DEAD2, STLKS3, ROMERO, SHAWN2, MESSAG, COUNT2, DDTBL3, AMPIE, THEDA3, ADRIAN, MESSG2, ROMER2, TENSE, SHAWN3, OPENIN, EVIL, ULTIMA, READ_M, DM2TTL, DM2INT } public enum Sfx { NONE, PISTOL, SHOTGN, SGCOCK, DSHTGN, DBOPN, DBCLS, DBLOAD, PLASMA, BFG, SAWUP, SAWIDL, SAWFUL, SAWHIT, RLAUNC, RXPLOD, FIRSHT, FIRXPL, PSTART, PSTOP, DOROPN, DORCLS, STNMOV, SWTCHN, SWTCHX, PLPAIN, DMPAIN, POPAIN, VIPAIN, MNPAIN, PEPAIN, SLOP, ITEMUP, WPNUP, OOF, TELEPT, POSIT1, POSIT2, POSIT3, BGSIT1, BGSIT2, SGTSIT, CACSIT, BRSSIT, CYBSIT, SPISIT, BSPSIT, KNTSIT, VILSIT, MANSIT, PESIT, SKLATK, SGTATK, SKEPCH, VILATK, CLAW, SKESWG, PLDETH, PDIEHI, PODTH1, PODTH2, PODTH3, BGDTH1, BGDTH2, SGTDTH, CACDTH, SKLDTH, BRSDTH, CYBDTH, SPIDTH, BSPDTH, VILDTH, KNTDTH, PEDTH, SKEDTH, POSACT, BGACT, DMACT, BSPACT, BSPWLK, VILACT, NOWAY, BAREXP, PUNCH, HOOF, METAL, CHGUN, TINK, BDOPN, BDCLS, ITMBK, FLAME, FLAMST, GETPOW, BOSPIT, BOSCUB, BOSSIT, BOSPN, BOSDTH, MANATK, MANDTH, SSSIT, SSDTH, KEENPN, KEENDT, SKEACT, SKESIT, SKEATK, RADIO } public enum SfxType { Diffuse, Weapon, Voice, Footstep, Misc } public sealed class CommandLineArgs { public class Arg { private bool present; public bool Present => present; public Arg() { present = false; } public Arg(bool present) { this.present = present; } } public class Arg<T> { private bool present; private T value; public bool Present => present; public T Value => value; public Arg() { present = false; value = default(T); } public Arg(T value) { present = true; this.value = value; } } public readonly Arg<string> iwad; public readonly Arg<string[]> file; public readonly Arg<string[]> deh; public readonly Arg<Tuple<int, int>> warp; public readonly Arg<int> episode; public readonly Arg<int> skill; public readonly Arg deathmatch; public readonly Arg altdeath; public readonly Arg fast; public readonly Arg respawn; public readonly Arg nomonsters; public readonly Arg solonet; public readonly Arg<string> playdemo; public readonly Arg<string> timedemo; public readonly Arg<int> loadgame; public readonly Arg nomouse; public readonly Arg nosound; public readonly Arg nosfx; public readonly Arg nomusic; public readonly Arg nodeh; public CommandLineArgs(string[] args) { iwad = GetString(args, "-iwad"); file = Check_file(args); deh = Check_deh(args); warp = Check_warp(args); episode = GetInt(args, "-episode"); skill = GetInt(args, "-skill"); deathmatch = new Arg(args.Contains("-deathmatch")); altdeath = new Arg(args.Contains("-altdeath")); fast = new Arg(args.Contains("-fast")); respawn = new Arg(args.Contains("-respawn")); nomonsters = new Arg(args.Contains("-nomonsters")); solonet = new Arg(args.Contains("-solo-net")); playdemo = GetString(args, "-playdemo"); timedemo = GetString(args, "-timedemo"); loadgame = GetInt(args, "-loadgame"); nomouse = new Arg(args.Contains("-nomouse")); nosound = new Arg(args.Contains("-nosound")); nosfx = new Arg(args.Contains("-nosfx")); nomusic = new Arg(args.Contains("-nomusic")); nodeh = new Arg(args.Contains("-nodeh")); if (args.Length == 0 || !args.All((string arg) => arg.FirstOrDefault() != '-')) { return; } string text = null; List<string> list = new List<string>(); List<string> list2 = new List<string>(); foreach (string text2 in args) { string text3 = Path.GetExtension(text2).ToLower(); if (text3 == ".wad") { if (ConfigUtilities.IsIwad(text2)) { text = text2; } else { list.Add(text2); } } else if (text3 == ".deh") { list2.Add(text2); } } if (text != null) { iwad = new Arg<string>(text); } if (list.Count > 0) { file = new Arg<string[]>(list.ToArray()); } if (list2.Count > 0) { deh = new Arg<string[]>(list2.ToArray()); } } private static Arg<string[]> Check_file(string[] args) { string[] values = GetValues(args, "-file"); if (values.Length >= 1) { return new Arg<string[]>(values); } return new Arg<string[]>(); } private static Arg<string[]> Check_deh(string[] args) { string[] values = GetValues(args, "-deh"); if (values.Length >= 1) { return new Arg<string[]>(values); } return new Arg<string[]>(); } private static Arg<Tuple<int, int>> Check_warp(string[] args) { string[] values = GetValues(args, "-warp"); int result2; int result3; if (values.Length == 1) { if (int.TryParse(values[0], out var result)) { return new Arg<Tuple<int, int>>(Tuple.Create(1, result)); } } else if (values.Length == 2 && int.TryParse(values[0], out result2) && int.TryParse(values[1], out result3)) { return new Arg<Tuple<int, int>>(Tuple.Create(result2, result3)); } return new Arg<Tuple<int, int>>(); } private static Arg<string> GetString(string[] args, string name) { string[] values = GetValues(args, name); if (values.Length == 1) { return new Arg<string>(values[0]); } return new Arg<string>(); } private static Arg<int> GetInt(string[] args, string name) { string[] values = GetValues(args, name); if (values.Length == 1 && int.TryParse(values[0], out var result)) { return new Arg<int>(result); } return new Arg<int>(); } private static string[] GetValues(string[] args, string name) { return args.SkipWhile((string arg) => arg != name).Skip(1).TakeWhile((string arg) => arg[0] != '-') .ToArray(); } } public sealed class Config { public KeyBinding key_forward; public KeyBinding key_backward; public KeyBinding key_strafeleft; public KeyBinding key_straferight; public KeyBinding key_turnleft; public KeyBinding key_turnright; public KeyBinding key_fire; public KeyBinding key_use; public KeyBinding key_run; public KeyBinding key_strafe; public int mouse_sensitivity; public bool mouse_disableyaxis; public bool game_alwaysrun; public int video_screenwidth; public int video_screenheight; public bool video_fullscreen; public bool video_highresolution; public bool video_displaymessage; public int video_gamescreensize; public int video_gammacorrection; public int video_fpsscale; public int audio_soundvolume; public int audio_musicvolume; public bool audio_randompitch; public string audio_soundfont; public bool audio_musiceffect; private bool isRestoredFromFile; public bool IsRestoredFromFile => isRestoredFromFile; public Config() { key_forward = new KeyBinding(new DoomKey[2] { DoomKey.Up, DoomKey.W }); key_backward = new KeyBinding(new DoomKey[2] { DoomKey.Down, DoomKey.S }); key_strafeleft = new KeyBinding(new DoomKey[1]); key_straferight = new KeyBinding(new DoomKey[1] { DoomKey.D }); key_turnleft = new KeyBinding(new DoomKey[1] { DoomKey.Left }); key_turnright = new KeyBinding(new DoomKey[1] { DoomKey.Right }); key_fire = new KeyBinding(new DoomKey[2] { DoomKey.LControl, DoomKey.RControl }, new DoomMouseButton[1]); key_use = new KeyBinding(new DoomKey[1] { DoomKey.Space }, new DoomMouseButton[1] { DoomMouseButton.Mouse2 }); key_run = new KeyBinding(new DoomKey[2] { DoomKey.LShift, DoomKey.RShift }); key_strafe = new KeyBinding(new DoomKey[2] { DoomKey.LAlt, DoomKey.RAlt }); mouse_sensitivity = 8; mouse_disableyaxis = false; game_alwaysrun = true; video_screenwidth = 640; video_screenheight = 400; video_fullscreen = false; video_highresolution = true; video_gamescreensize = 7; video_displaymessage = true; video_gammacorrection = 2; video_fpsscale = 2; audio_soundvolume = 8; audio_musicvolume = 8; audio_randompitch = true; audio_soundfont = "TimGM6mb.sf2"; audio_musiceffect = true; isRestoredFromFile = false; } public Config(string path) : this() { try { Console.Write("Restore settings: "); Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string item in File.ReadLines(path)) { string[] array = item.Split('=', StringSplitOptions.RemoveEmptyEntries); if (array.Length == 2) { dictionary[array[0].Trim()] = array[1].Trim(); } } key_forward = GetKeyBinding(dictionary, "key_forward", key_forward); key_backward = GetKeyBinding(dictionary, "key_backward", key_backward); key_strafeleft = GetKeyBinding(dictionary, "key_strafeleft", key_strafeleft); key_straferight = GetKeyBinding(dictionary, "key_straferight", key_straferight); key_turnleft = GetKeyBinding(dictionary, "key_turnleft", key_turnleft); key_turnright = GetKeyBinding(dictionary, "key_turnright", key_turnright); key_fire = GetKeyBinding(dictionary, "key_fire", key_fire); key_use = GetKeyBinding(dictionary, "key_use", key_use); key_run = GetKeyBinding(dictionary, "key_run", key_run); key_strafe = GetKeyBinding(dictionary, "key_strafe", key_strafe); mouse_sensitivity = GetInt(dictionary, "mouse_sensitivity", mouse_sensitivity); mouse_disableyaxis = GetBool(dictionary, "mouse_disableyaxis", mouse_disableyaxis); game_alwaysrun = GetBool(dictionary, "game_alwaysrun", game_alwaysrun); video_screenwidth = GetInt(dictionary, "video_screenwidth", video_screenwidth); video_screenheight = GetInt(dictionary, "video_screenheight", video_screenheight); video_fullscreen = GetBool(dictionary, "video_fullscreen", video_fullscreen); video_highresolution = GetBool(dictionary, "video_highresolution", video_highresolution); video_displaymessage = GetBool(dictionary, "video_displaymessage", video_displaymessage); video_gamescreensize = GetInt(dictionary, "video_gamescreensize", video_gamescreensize); video_gammacorrection = GetInt(dictionary, "video_gammacorrection", video_gammacorrection); video_fpsscale = GetInt(dictionary, "video_fpsscale", video_fpsscale); audio_soundvolume = GetInt(dictionary, "audio_soundvolume", audio_soundvolume); audio_musicvolume = GetInt(dictionary, "audio_musicvolume", audio_musicvolume); audio_randompitch = GetBool(dictionary, "audio_randompitch", audio_randompitch); audio_soundfont = GetString(dictionary, "audio_soundfont", audio_soundfont); audio_musiceffect = GetBool(dictionary, "audio_musiceffect", audio_musiceffect); isRestoredFromFile = true; Console.WriteLine("OK"); } catch { Console.WriteLine("Failed"); } } public void Save(string path) { try { using StreamWriter streamWriter = new StreamWriter(path); streamWriter.WriteLine("key_forward = " + key_forward); streamWriter.WriteLine("key_backward = " + key_backward); streamWriter.WriteLine("key_strafeleft = " + key_strafeleft); streamWriter.WriteLine("key_straferight = " + key_straferight); streamWriter.WriteLine("key_turnleft = " + key_turnleft); streamWriter.WriteLine("key_turnright = " + key_turnright); streamWriter.WriteLine("key_fire = " + key_fire); streamWriter.WriteLine("key_use = " + key_use); streamWriter.WriteLine("key_run = " + key_run); streamWriter.WriteLine("key_strafe = " + key_strafe); streamWriter.WriteLine("mouse_sensitivity = " + mouse_sensitivity); streamWriter.WriteLine("mouse_disableyaxis = " + BoolToString(mouse_disableyaxis)); streamWriter.WriteLine("game_alwaysrun = " + BoolToString(game_alwaysrun)); streamWriter.WriteLine("video_screenwidth = " + video_screenwidth); streamWriter.WriteLine("video_screenheight = " + video_screenheight); streamWriter.WriteLine("video_fullscreen = " + BoolToString(video_fullscreen)); streamWriter.WriteLine("video_highresolution = " + BoolToString(video_highresolution)); streamWriter.WriteLine("video_displaymessage = " + BoolToString(video_displaymessage)); streamWriter.WriteLine("video_gamescreensize = " + video_gamescreensize); streamWriter.WriteLine("video_gammacorrection = " + video_gammacorrection); streamWriter.WriteLine("video_fpsscale = " + video_fpsscale); streamWriter.WriteLine("audio_soundvolume = " + audio_soundvolume); streamWriter.WriteLine("audio_musicvolume = " + audio_musicvolume); streamWriter.WriteLine("audio_randompitch = " + BoolToString(audio_randompitch)); streamWriter.WriteLine("audio_soundfont = " + audio_soundfont); streamWriter.WriteLine("audio_musiceffect = " + BoolToString(audio_musiceffect)); } catch { } } private static int GetInt(Dictionary<string, string> dic, string name, int defaultValue) { if (dic.TryGetValue(name, out var value) && int.TryParse(value, out var result)) { return result; } return defaultValue; } private static string GetString(Dictionary<string, string> dic, string name, string defaultValue) { if (dic.TryGetValue(name, out var value)) { return value; } return defaultValue; } private static bool GetBool(Dictionary<string, string> dic, string name, bool defaultValue) { if (dic.TryGetValue(name, out var value)) { if (value == "true") { return true; } if (value == "false") { return false; } } return defaultValue; } private static KeyBinding GetKeyBinding(Dictionary<string, string> dic, string name, KeyBinding defaultValue) { if (dic.TryGetValue(name, out var value)) { return KeyBinding.Parse(value); } return defaultValue; } private static string BoolToString(bool value) { return value ? "true" : "false"; } } public static class ConfigUtilities { private static readonly string[] iwadNames = new string[7] { "DOOM2.WAD", "PLUTONIA.WAD", "TNT.WAD", "DOOM.WAD", "DOOM1.WAD", "FREEDOOM2.WAD", "FREEDOOM1.WAD" }; public static Func<string> DataDirectoryOverride { get; set; } public static string GetProcessDirectory() { return Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); } public static string GetExeDirectory() { Func<string> dataDirectoryOverride = DataDirectoryOverride; if (dataDirectoryOverride != null) { string text = dataDirectoryOverride(); if (!string.IsNullOrEmpty(text)) { return text; } } return GetProcessDirectory(); } public static string GetConfigPath() { return Path.Combine(GetExeDirectory(), "managed-doom.cfg"); } public static string GetDefaultIwadPath() { string exeDirectory = GetExeDirectory(); string[] array = iwadNames; foreach (string path in array) { string text = Path.Combine(exeDirectory, path); if (File.Exists(text)) { return text; } } string currentDirectory = Directory.GetCurrentDirectory(); string[] array2 = iwadNames; foreach (string path2 in array2) { string text2 = Path.Combine(currentDirectory, path2); if (File.Exists(text2)) { return text2; } } throw new Exception("No IWAD was found!"); } public static bool IsIwad(string path) { string value = Path.GetFileName(path).ToUpper(); return iwadNames.Contains(value); } public static string[] GetWadPaths(CommandLineArgs args) { List<string> list = new List<string>(); if (args.iwad.Present) { list.Add(args.iwad.Value); } else { list.Add(GetDefaultIwadPath()); } if (args.file.Present) { string[] value = args.file.Value; foreach (string item in value) { list.Add(item); } } return list.ToArray(); } } public static class DoomDebug { public static int CombineHash(int a, int b) { return (3 * a) ^ b; } public static int GetMobjHash(Mobj mobj) { int a = 0; a = CombineHash(a, mobj.X.Data); a = CombineHash(a, mobj.Y.Data); a = CombineHash(a, mobj.Z.Data); a = CombineHash(a, (int)mobj.Angle.Data); a = CombineHash(a, (int)mobj.Sprite); a = CombineHash(a, mobj.Frame); a = CombineHash(a, mobj.FloorZ.Data); a = CombineHash(a, mobj.CeilingZ.Data); a = CombineHash(a, mobj.Radius.Data); a = CombineHash(a, mobj.Height.Data); a = CombineHash(a, mobj.MomX.Data); a = CombineHash(a, mobj.MomY.Data); a = CombineHash(a, mobj.MomZ.Data); a = CombineHash(a, mobj.Tics); a = CombineHash(a, (int)mobj.Flags); a = CombineHash(a, mobj.Health); a = CombineHash(a, (int)mobj.MoveDir); a = CombineHash(a, mobj.MoveCount); a = CombineHash(a, mobj.ReactionTime); return CombineHash(a, mobj.Threshold); } public static int GetMobjHash(World world) { int num = 0; foreach (Thinker thinker in world.Thinkers) { if (thinker is Mobj mobj) { num = CombineHash(num, GetMobjHash(mobj)); } } return num; } private static string GetMobjCsv(Mobj mobj) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(mobj.X.Data).Append(","); stringBuilder.Append(mobj.Y.Data).Append(","); stringBuilder.Append(mobj.Z.Data).Append(","); stringBuilder.Append((int)mobj.Angle.Data).Append(","); stringBuilder.Append((int)mobj.Sprite).Append(","); stringBuilder.Append(mobj.Frame).Append(","); stringBuilder.Append(mobj.FloorZ.Data).Append(","); stringBuilder.Append(mobj.CeilingZ.Data).Append(","); stringBuilder.Append(mobj.Radius.Data).Append(","); stringBuilder.Append(mobj.Height.Data).Append(","); stringBuilder.Append(mobj.MomX.Data).Append(","); stringBuilder.Append(mobj.MomY.Data).Append(","); stringBuilder.Append(mobj.MomZ.Data).Append(","); stringBuilder.Append(mobj.Tics).Append(","); stringBuilder.Append((int)mobj.Flags).Append(","); stringBuilder.Append(mobj.Health).Append(","); stringBuilder.Append((int)mobj.MoveDir).Append(","); stringBuilder.Append(mobj.MoveCount).Append(","); stringBuilder.Append(mobj.ReactionTime).Append(","); stringBuilder.Append(mobj.Threshold); return stringBuilder.ToString(); } public static void DumpMobjCsv(string path, World world) { using StreamWriter streamWriter = new StreamWriter(path); foreach (Thinker thinker in world.Thinkers) { if (thinker is Mobj mobj) { streamWriter.WriteLine(GetMobjCsv(mobj)); } } } public static int GetSectorHash(Sector sector) { int a = 0; a = CombineHash(a, sector.FloorHeight.Data); a = CombineHash(a, sector.CeilingHeight.Data); return CombineHash(a, sector.LightLevel); } public static int GetSectorHash(World world) { int num = 0; Sector[] sectors = world.Map.Sectors; foreach (Sector sector in sectors) { num = CombineHash(num, GetSectorHash(sector)); } return num; } } public static class DoomInterop { public static string ToString(byte[] data, int offset, int maxLength) { int num = 0; for (int i = 0; i < maxLength && data[offset + i] != 0; i++) { num++; } char[] array = new char[num]; for (int j = 0; j < array.Length; j++) { byte b = data[offset + j]; if (97 <= b && b <= 122) { b -= 32; } array[j] = (char)b; } return new string(array); } } public sealed class DoomRandom { private static readonly int[] table = new int[256] { 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66, 74, 21, 211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36, 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188, 52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224, 149, 104, 25, 178, 252, 182, 202, 182, 141, 197, 4, 81, 181, 242, 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175, 249, 0, 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235, 25, 92, 20, 145, 138, 77, 69, 166, 78, 176, 173, 212, 166, 113, 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, 171, 75, 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196, 135, 106, 63, 197, 195, 86, 96, 203, 113, 101, 170, 247, 181, 113, 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, 166, 103, 241, 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224, 145, 224, 81, 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95, 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14, 109, 226, 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36, 17, 46, 52, 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106, 197, 242, 98, 43, 39, 175, 254, 145, 190, 84, 118, 222, 187, 136, 120, 163, 236, 249 }; private int index; public DoomRandom() { index = 0; } public DoomRandom(int seed) { index = seed & 0xFF; } public int Next() { index = (index + 1) & 0xFF; return table[index]; } public void Clear() { index = 0; } } public sealed class DoomString { private static Dictionary<string, DoomString> valueTable = new Dictionary<string, DoomString>(); private static Dictionary<string, DoomString> nameTable = new Dictionary<string, DoomString>(); private string original; private string replaced; public char this[int index] => replaced[index]; public DoomString(string original) { this.original = original; replaced = original; if (!valueTable.ContainsKey(original)) { valueTable.Add(original, this); } } public DoomString(string name, string original) : this(original) { nameTable.Add(name, this); } public override string ToString() { return replaced; } public static implicit operator string(DoomString ds) { return ds.replaced; } public static void ReplaceByValue(string original, string replaced) { if (valueTable.TryGetValue(original, out var value)) { value.replaced = replaced; } } public static void ReplaceByName(string name, string value) { if (nameTable.TryGetValue(name, out var value2)) { value2.replaced = value; } } } public static class DeHackEd { private enum Block { None, Thing, Frame, Pointer, Sound, Ammo, Weapon, Cheat, Misc, Text, Sprite, BexStrings, BexPars } private static Tuple<Action<World, Player, PlayerSpriteDef>, Action<World, Mobj>>[] sourcePointerTable; public static void Initialize(CommandLineArgs args, Wad wad) { if (args.deh.Present) { ReadFiles(args.deh.Value); } if (!args.nodeh.Present) { ReadDeHackEdLump(wad); } } private static void ReadFiles(params string[] fileNames) { string text = null; try { DoomInfo.Strings.PRESSKEY.GetHashCode(); Console.Write("Load DeHackEd patches: "); foreach (string text2 in fileNames) { text = text2; ProcessLines(File.ReadLines(text2)); } Console.WriteLine("OK (" + string.Join(", ", fileNames.Select((string x) => Path.GetFileName(x))) + ")"); } catch (Exception innerException) { Console.WriteLine("Failed"); throw new Exception("Failed to apply DeHackEd patch: " + text, innerException); } } private static void ReadDeHackEdLump(Wad wad) { int lumpNumber = wad.GetLumpNumber("DEHACKED"); if (lumpNumber != -1) { DoomInfo.Strings.PRESSKEY.GetHashCode(); try { Console.Write("Load DeHackEd patch from WAD: "); ProcessLines(ReadLines(wad.ReadLump(lumpNumber))); Console.WriteLine("OK"); } catch (Exception innerException) { Console.WriteLine("Failed"); throw new Exception("Failed to apply DeHackEd patch!", innerException); } } } private static IEnumerable<string> ReadLines(byte[] data) { using MemoryStream ms = new MemoryStream(data); using StreamReader sr = new StreamReader(ms); for (string line = sr.ReadLine(); line != null; line = sr.ReadLine()) { yield return line; } } private static void ProcessLines(IEnumerable<string> lines) { if (sourcePointerTable == null) { sourcePointerTable = new Tuple<Action<World, Player, PlayerSpriteDef>, Action<World, Mobj>>[DoomInfo.States.Length]; for (int i = 0; i < sourcePointerTable.Length; i++) { Action<World, Player, PlayerSpriteDef> playerAction = DoomInfo.States[i].PlayerAction; Action<World, Mobj> mobjAction = DoomInfo.States[i].MobjAction; sourcePointerTable[i] = Tuple.Create(playerAction, mobjAction); } } int num = 0; List<string> list = new List<string>(); Block type = Block.None; int lineNumber = 0; foreach (string line in lines) { num++; if (line.Length <= 0 || line[0] != '#') { string[] split = line.Split(' '); Block blockType = GetBlockType(split); if (blockType == Block.None) { list.Add(line); continue; } ProcessBlock(type, list, lineNumber); list.Clear(); list.Add(line); type = blockType; lineNumber = num; } } ProcessBlock(type, list, lineNumber); } private static void ProcessBlock(Block type, List<string> data, int lineNumber) { try { switch (type) { case Block.Thing: ProcessThingBlock(data); break; case Block.Frame: ProcessFrameBlock(data); break; case Block.Pointer: ProcessPointerBlock(data); break; case Block.Sound: ProcessSoundBlock(data); break; case Block.Ammo: ProcessAmmoBlock(data); break; case Block.Weapon: ProcessWeaponBlock(data); break; case Block.Cheat: ProcessCheatBlock(data); break; case Block.Misc: ProcessMiscBlock(data); break; case Block.Text: ProcessTextBlock(data); break; case Block.Sprite: ProcessSpriteBlock(data); break; case Block.BexStrings: ProcessBexStringsBlock(data); break; case Block.BexPars: ProcessBexParsBlock(data); break; } } catch (Exception innerException) { throw new Exception("Failed to process block: " + type.ToString() + " (line " + lineNumber + ")", innerException); } } private static void ProcessThingBlock(List<string> data) { int num = int.Parse(data[0].Split(' ')[1]) - 1; MobjInfo mobjInfo = DoomInfo.MobjInfos[num]; Dictionary<string, string> keyValuePairs = GetKeyValuePairs(data); mobjInfo.DoomEdNum = GetInt(keyValuePairs, "ID #", mobjInfo.DoomEdNum); mobjInfo.SpawnState = (MobjState)GetInt(keyValuePairs, "Initial frame", (int)mobjInfo.SpawnState); mobjInfo.SpawnHealth = GetInt(keyValuePairs, "Hit points", mobjInfo.SpawnHealth); mobjInfo.SeeState = (MobjState)GetInt(keyValuePairs, "First moving frame", (int)mobjInfo.SeeState); mobjInfo.SeeSound = (Sfx)GetInt(keyValuePairs, "Alert sound", (int)mobjInfo.SeeSound); mobjInfo.ReactionTime = GetInt(keyValuePairs, "Reaction time", mobjInfo.ReactionTime); mobjInfo.AttackSound = (Sfx)GetInt(keyValuePairs, "Attack sound", (int)mobjInfo.AttackSound); mobjInfo.PainState = (MobjState)GetInt(keyValuePairs, "Injury frame", (int)mobjInfo.PainState); mobjInfo.PainChance = GetInt(keyValuePairs, "Pain chance", mobjInfo.PainChance); mobjInfo.PainSound = (Sfx)GetInt(keyValuePairs, "Pain sound", (int)mobjInfo.PainSound); mobjInfo.MeleeState = (MobjState)GetInt(keyValuePairs, "Close attack frame", (int)mobjInfo.MeleeState); mobjInfo.MissileState = (MobjState)GetInt(keyValuePairs, "Far attack frame", (int)mobjInfo.MissileState); mobjInfo.DeathState = (MobjState)GetInt(keyValuePairs, "Death frame", (int)mobjInfo.DeathState); mobjInfo.XdeathState = (MobjState)GetInt(keyValuePairs, "Exploding frame", (int)mobjInfo.XdeathState); mobjInfo.DeathSound = (Sfx)GetInt(keyValuePairs, "Death sound", (int)mobjInfo.DeathSound); mobjInfo.Speed = GetInt(keyValuePairs, "Speed", mobjInfo.Speed); mobjInfo.Radius = new Fixed(GetInt(keyValuePairs, "Width", mobjInfo.Radius.Data)); mobjInfo.Height = new Fixed(GetInt(keyValuePairs, "Height", mobjInfo.Height.Data)); mobjInfo.Mass = GetInt(keyValuePairs, "Mass", mobjInfo.Mass); mobjInfo.Damage = GetInt(keyValuePairs, "Missile damage", mobjInfo.Damage); mobjInfo.ActiveSound = (Sfx)GetInt(keyValuePairs, "Action sound", (int)mobjInfo.ActiveSound); mobjInfo.Flags = (MobjFlags)GetInt(keyValuePairs, "Bits", (int)mobjInfo.Flags); mobjInfo.Raisestate = (MobjState)GetInt(keyValuePairs, "Respawn frame", (int)mobjInfo.Raisestate); } private static void ProcessFrameBlock(List<string> data) { int num = int.Parse(data[0].Split(' ')[1]); MobjStateDef mobjStateDef = DoomInfo.States[num]; Dictionary<string, string> keyValuePairs = GetKeyValuePairs(data); mobjStateDef.Sprite = (Sprite)GetInt(keyValuePairs, "Sprite number", (int)mobjStateDef.Sprite); mobjStateDef.Frame = GetInt(keyValuePairs, "Sprite subnumber", mobjStateDef.Frame); mobjStateDef.Tics = GetInt(keyValuePairs, "Duration", mobjStateDef.Tics); mobjStateDef.Next = (MobjState)GetInt(keyValuePairs, "Next frame", (int)mobjStateDef.Next); mobjStateDef.Misc1 = GetInt(keyValuePairs, "Unknown 1", mobjStateDef.Misc1); mobjStateDef.Misc2 = GetInt(keyValuePairs, "Unknown 2", mobjStateDef.Misc2); } private static void ProcessPointerBlock(List<string> data) { Dictionary<string, string> keyValuePairs = GetKeyValuePairs(data); int num = data[0].IndexOf('(') + 1; int num2 = data[0].IndexOf(')'); int length = num2 - num; int num3 = int.Parse(data[0].Substring(num, length).Split(' ')[1]); int num4 = GetInt(keyValuePairs, "Codep Frame", -1); if (num4 != -1) { MobjStateDef mobjStateDef = DoomInfo.States[num3]; mobjStateDef.PlayerAction = sourcePointerTable[num4].Item1; mobjStateDef.MobjAction = sourcePointerTable[num4].Item2; } } private static void ProcessSoundBlock(List<string> data) { } private static void ProcessAmmoBlock(List<string> data) { int num = int.Parse(data[0].Split(' ')[1]); Dictionary<string, string> keyValuePairs = GetKeyValuePairs(data); int[] max = DoomInfo.AmmoInfos.Max; int[] clip = DoomInfo.AmmoInfos.Clip; max[num] = GetInt(keyValuePairs, "Max ammo", max[num]); clip[num] = GetInt(keyValuePairs, "Per ammo", clip[num]); } private static void ProcessWeaponBlock(List<string> data) { int num = int.Parse(data[0].Split(' ')[1]); WeaponInfo weaponInfo = DoomInfo.WeaponInfos[num]; Dictionary<string, string> keyValuePairs = GetKeyValuePairs(data); weaponInfo.Ammo = (AmmoType)GetInt(keyValuePairs, "Ammo type", (int)weaponInfo.Ammo); weaponInfo.UpState = (MobjState)GetInt(keyValuePairs, "Deselect frame", (int)weaponInfo.UpState); weaponInfo.DownState = (MobjState)GetInt(keyValuePairs, "Select frame", (int)weaponInfo.DownState); weaponInfo.ReadyState = (MobjState)GetInt(keyValuePairs, "Bobbing frame", (int)weaponInfo.ReadyState); weaponInfo.AttackState = (MobjState)GetInt(keyValuePairs, "Shooting frame", (int)weaponInfo.AttackState); weaponInfo.FlashState = (MobjState)GetInt(keyValuePairs, "Firing frame", (int)weaponInfo.FlashState); } private static void ProcessCheatBlock(List<string> data) { } private static void ProcessMiscBlock(List<string> data) { Dictionary<string, string> keyValuePairs = GetKeyValuePairs(data); DoomInfo.DeHackEdConst.InitialHealth = GetInt(keyValuePairs, "Initial Health", DoomInfo.DeHackEdConst.InitialHealth); DoomInfo.DeHackEdConst.InitialBullets = GetInt(keyValuePairs, "Initial Bullets", DoomInfo.DeHackEdConst.InitialBullets); DoomInfo.DeHackEdConst.MaxHealth = GetInt(keyValuePairs, "Max Health", DoomInfo.DeHackEdConst.MaxHealth); DoomInfo.DeHackEdConst.MaxArmor = GetInt(keyValuePairs, "Max Armor", DoomInfo.DeHackEdConst.MaxArmor); DoomInfo.DeHackEdConst.GreenArmorClass = GetInt(keyValuePairs, "Green Armor Class", DoomInfo.DeHackEdConst.GreenArmorClass); DoomInfo.DeHackEdConst.BlueArmorClass = GetInt(keyValuePairs, "Blue Armor Class", DoomInfo.DeHackEdConst.BlueArmorClass); DoomInfo.DeHackEdConst.MaxSoulsphere = GetInt(keyValuePairs, "Max Soulsphere", DoomInfo.DeHackEdConst.MaxSoulsphere); DoomInfo.DeHackEdConst.SoulsphereHealth = GetInt(keyValuePairs, "Soulsphere Health", DoomInfo.DeHackEdConst.SoulsphereHealth); DoomInfo.DeHackEdConst.MegasphereHealth = GetInt(keyValuePairs, "Megasphere Health", DoomInfo.DeHackEdConst.MegasphereHealth); DoomInfo.DeHackEdConst.GodModeHealth = GetInt(keyValuePairs, "God Mode Health", DoomInfo.DeHackEdConst.GodModeHealth); DoomInfo.DeHackEdConst.IdfaArmor = GetInt(keyValuePairs, "IDFA Armor", DoomInfo.DeHackEdConst.IdfaArmor); DoomInfo.DeHackEdConst.IdfaArmorClass = GetInt(keyValuePairs, "IDFA Armor Class", DoomInfo.DeHackEdConst.IdfaArmorClass); DoomInfo.DeHackEdConst.IdkfaArmor = GetInt(keyValuePairs, "IDKFA Armor", DoomInfo.DeHackEdConst.IdkfaArmor); DoomInfo.DeHackEdConst.IdkfaArmorClass = GetInt(keyValuePairs, "IDKFA Armor Class", DoomInfo.DeHackEdConst.IdkfaArmorClass); DoomInfo.DeHackEdConst.BfgCellsPerShot = GetInt(keyValuePairs, "BFG Cells/Shot", DoomInfo.DeHackEdConst.BfgCellsPerShot); DoomInfo.DeHackEdConst.MonstersInfight = GetInt(keyValuePairs, "Monsters Infight", 0) == 221; } private static void ProcessTextBlock(List<string> data) { string[] array = data[0].Split(' '); int num = int.Parse(array[1]); int num2 = int.Parse(array[2]); int num3 = 1; int num4 = 0; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < num; i++) { if (num4 == data[num3].Length) { stringBuilder.Append('\n'); num3++; num4 = 0; } else { stringBuilder.Append(data[num3][num4]); num4++; } } StringBuilder stringBuilder2 = new StringBuilder(); for (int j = 0; j < num2; j++) { if (num4 == data[num3].Length) { stringBuilder2.Append('\n'); num3++; num4 = 0; } else { stringBuilder2.Append(data[num3][num4]); num4++; } } DoomString.ReplaceByValue(stringBuilder.ToString(), stringBuilder2.ToString()); } private static void ProcessSpriteBlock(List<string> data) { } private static void ProcessBexStringsBlock(List<string> data) { string text = null; StringBuilder stringBuilder = null; foreach (string item in data.Skip(1)) { if (text == null) { int num = item.IndexOf('='); if (num != -1) { string text2 = item.Substring(0, num).Trim(); string text3 = item.Substring(num + 1).Trim().Replace("\\n", "\n"); if (text3.Last() != '\\') { DoomString.ReplaceByName(text2, text3); continue; } text = text2; stringBuilder = new StringBuilder(); stringBuilder.Append(text3, 0, text3.Length - 1); } } else { string text4 = item.Trim().Replace("\\n", "\n"); if (text4.Last() != '\\') { stringBuilder.Append(text4); DoomString.ReplaceByName(text, stringBuilder.ToString()); text = null; stringBuilder = null; } else { stringBuilder.Append(text4, 0, text4.Length - 1); } } } } private static void ProcessBexParsBlock(List<string> data) { foreach (string item in data.Skip(1)) { string[] array = item.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (array.Length < 3 || !(array[0] == "par")) { continue; } List<int> list = new List<int>(); foreach (string item2 in array.Skip(1)) { if (int.TryParse(item2, out var result)) { list.Add(result); continue; } break; } if (list.Count == 2 && list[0] <= DoomInfo.ParTimes.Doom2.Count) { DoomInfo.ParTimes.Doom2[list[0] - 1] = list[1]; } if (list.Count >= 3 && list[0] <= DoomInfo.ParTimes.Doom1.Count && list[1] <= DoomInfo.ParTimes.Doom1[list[0] - 1].Count) { DoomInfo.ParTimes.Doom1[list[0] - 1][list[1] - 1] = list[2]; } } } private static Block GetBlockType(string[] split) { if (IsThingBlockStart(split)) { return Block.Thing; } if (IsFrameBlockStart(split)) { return Block.Frame; } if (IsPointerBlockStart(split)) { return Block.Pointer; } if (IsSoundBlockStart(split)) { return Block.Sound; } if (IsAmmoBlockStart(split)) { return Block.Ammo; } if (IsWeaponBlockStart(split)) { return Block.Weapon; } if (IsCheatBlockStart(split)) { return Block.Cheat; } if (IsMiscBlockStart(split)) { return Block.Misc; } if (IsTextBlockStart(split)) { return Block.Text; } if (IsSpriteBlockStart(split)) { return Block.Sprite; } if (IsBexStringsBlockStart(split)) { return Block.BexStrings; } if (IsBexParsBlockStart(split)) { return Block.BexPars; } return Block.None; } private static bool IsThingBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Thing") { return false; } if (!IsNumber(split[1])) { return false; } return true; } private static bool IsFrameBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Frame") { return false; } if (!IsNumber(split[1])) { return false; } return true; } private static bool IsPointerBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Pointer") { return false; } return true; } private static bool IsSoundBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Sound") { return false; } if (!IsNumber(split[1])) { return false; } return true; } private static bool IsAmmoBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Ammo") { return false; } if (!IsNumber(split[1])) { return false; } return true; } private static bool IsWeaponBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Weapon") { return false; } if (!IsNumber(split[1])) { return false; } return true; } private static bool IsCheatBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Cheat") { return false; } if (split[1] != "0") { return false; } return true; } private static bool IsMiscBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Misc") { return false; } if (split[1] != "0") { return false; } return true; } private static bool IsTextBlockStart(string[] split) { if (split.Length < 3) { return false; } if (split[0] != "Text") { return false; } if (!IsNumber(split[1])) { return false; } if (!IsNumber(split[2])) { return false; } return true; } private static bool IsSpriteBlockStart(string[] split) { if (split.Length < 2) { return false; } if (split[0] != "Sprite") { return false; } if (!IsNumber(split[1])) { return false; } return true; } private static bool IsBexStringsBlockStart(string[] split) { if (split[0] == "[STRINGS]") { return true; } return false; } private static bool IsBexParsBlockStart(string[] split) { if (split[0] == "[PARS]") { return true; } return false; } private static bool IsNumber(string value) { foreach (char c in value) { if ('0' > c || c > '9') { return false; } } return true; } private static Dictionary<string, string> GetKeyValuePairs(List<string> data) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string datum in data) { string[] array = datum.Split('='); if (array.Length == 2) { dictionary[array[0].Trim()] = array[1].Trim(); } } return dictionary; } private static int GetInt(Dictionary<string, string> dic, string key, int defaultValue) { if (dic.TryGetValue(key, out var value) && int.TryParse(value, out var result)) { return result; } return defaultValue; } } public class Doom { private CommandLineArgs args; private Config config; private GameContent content; private IVideo video; private ISound sound; private IMusic music; private IUserInput userInput; private List<DoomEvent> events; private GameOptions options; private DoomMenu menu; private OpeningSequence opening; private DemoPlayback demoPlayback; private TicCmd[] cmds; private DoomGame game; private WipeEffect wipeEffect; private bool wiping; private DoomState currentState; private DoomState nextState; private bool needWipe; private bool sendPause; private bool quit; private string quitMessage; private bool mouseGrabbed; public DoomState State => currentState; public OpeningSequence Opening => opening; public DemoPlayback DemoPlayback => demoPlayback; public GameOptions Options => options; public DoomGame Game => game; public DoomMenu Menu => menu; public WipeEffect WipeEffect => wipeEffect; public bool Wiping => wiping; public string QuitMessage => quitMessage; public Doom(CommandLineArgs args, Config config, GameContent content, IVideo video, ISound sound, IMusic music, IUserInput userInput) { video = video ?? NullVideo.GetInstance(); sound = sound ?? NullSound.GetInstance(); music = music ?? NullMusic.GetInstance(); userInput = userInput ?? NullUserInput.GetInstance(); this.args = args; this.config = config; this.content = content; this.video = video; this.sound = sound; this.music = music; this.userInput = userInput; events = new List<DoomEvent>(); options = new GameOptions(args, content); options.Video = video; options.Sound = sound; options.Music = music; options.UserInput = userInput; menu = new DoomMenu(this); opening = new OpeningSequence(content, options); cmds = new TicCmd[Player.MaxPlayerCount]; for (int i = 0; i < Player.MaxPlayerCount; i++) { cmds[i] = new TicCmd(); } game = new DoomGame(content, options); wipeEffect = new WipeEffect(video.WipeBandCount, video.WipeHeight); wiping = false; currentState = DoomState.None; nextState = DoomState.Opening; needWipe = false; sendPause = false; quit = false; quitMessage = null; mouseGrabbed = false; CheckGameArgs(); } private void CheckGameArgs() { if (args.warp.Present) { nextState = DoomState.Game; options.Episode = args.warp.Value.Item1; options.Map = args.warp.Value.Item2; game.DeferedInitNew(); } else if (args.episode.Present) { nextState = DoomState.Game; options.Episode = args.episode.Value; options.Map = 1; game.DeferedInitNew(); } if (args.skill.Present) { options.Skill = (GameSkill)(args.skill.Value - 1); } if (args.deathmatch.Present) { options.Deathmatch = 1; } if (args.altdeath.Present) { options.Deathmatch = 2; } if (args.fast.Present) { options.FastMonsters = true; } if (args.respawn.Present) { options.RespawnMonsters = true; } if (args.nomonsters.Present) { options.NoMonsters = true; } if (args.loadgame.Present) { nextState = DoomState.Game; game.LoadGame(args.loadgame.Value); } if (args.playdemo.Present) { nextState = DoomState.DemoPlayback; demoPlayback = new DemoPlayback(args, content, options, args.playdemo.Value); } if (args.timedemo.Present) { nextState = DoomState.DemoPlayback; demoPlayback = new DemoPlayback(args, content, options, args.timedemo.Value); } } public void NewGame(GameSkill skill, int episode, int map) { game.DeferedInitNew(skill, episode, map); nextState = DoomState.Game; } public void EndGame() { nextState = DoomState.Opening; } private void DoEvents() { if (wiping) { return; } foreach (DoomEvent @event in events) { if (menu.DoEvent(@event) || (@event.Type == EventType.KeyDown && CheckFunctionKey(@event.Key))) { continue; } if (currentState == DoomState.Game) { if (@event.Key == DoomKey.Pause && @event.Type == EventType.KeyDown) { sendPause = true; } else if (!game.DoEvent(@event)) { } } else if (currentState == DoomState.DemoPlayback) { demoPlayback.DoEvent(@event); } } events.Clear(); } private bool CheckFunctionKey(DoomKey key) { switch (key) { case DoomKey.F1: menu.ShowHelpScreen(); return true; case DoomKey.F2: menu.ShowSaveScreen(); return true; case DoomKey.F3: menu.ShowLoadScreen(); return true; case DoomKey.F4: menu.ShowVolumeControl(); return true; case DoomKey.F6: menu.QuickSave(); return true; case DoomKey.F7: if (currentState == DoomState.Game) { menu.EndGame(); } else { options.Sound.StartSound(Sfx.OOF); } return true; case DoomKey.F8: video.DisplayMessage = !video.DisplayMessage; if (currentState == DoomState.Game && game.State == GameState.Level) { string message2 = ((!video.DisplayMessage) ? ((string)DoomInfo.Strings.MSGOFF) : ((string)DoomInfo.Strings.MSGON)); game.World.ConsolePlayer.SendMessage(message2); } menu.StartSound(Sfx.SWTCHN); return true; case DoomKey.F9: menu.QuickLoad(); return true; case DoomKey.F10: menu.Quit(); return true; case DoomKey.F11: { int gammaCorrectionLevel = video.GammaCorrectionLevel; gammaCorrectionLevel++; if (gammaCorrectionLevel > video.MaxGammaCorrectionLevel) { gammaCorrectionLevel = 0; } video.GammaCorrectionLevel = gammaCorrectionLevel; if (currentState == DoomState.Game && game.State == GameState.Level) { string message = ((gammaCorrectionLevel != 0) ? ("Gamma correction level " + gammaCorrectionLevel) : ((string)DoomInfo.Strings.GAMMALVL0)); game.World.ConsolePlayer.SendMessage(message); } return true; } case DoomKey.Quote: case DoomKey.Equal: case DoomKey.Add: if (currentState == DoomState.Game && game.State == GameState.Level && game.World.AutoMap.Visible) { return false; } video.WindowSize = Math.Min(video.WindowSize + 1, video.MaxWindowSize); sound.StartSound(Sfx.STNMOV); return true; case DoomKey.Semicolon: case DoomKey.Hyphen: case DoomKey.Subtract: if (currentState == DoomState.Game && game.State == GameState.Level && game.World.AutoMap.Visible) { return false; } video.WindowSize = Math.Max(video.WindowSize - 1, 0); sound.StartSound(Sfx.STNMOV); return true; default: return false; } } public UpdateResult Update() { DoEvents(); if (!wiping) { menu.Update(); if (nextState != currentState) { if (nextState != DoomState.Opening) { opening.Reset(); } if (nextState != DoomState.DemoPlayback) { demoPlayback = null; } currentState = nextState; } if (quit) { return UpdateResult.Completed; } if (needWipe) { needWipe = false; StartWipe(); } } if (!wiping) { switch (currentState) { case DoomState.Opening: if (opening.Update() == UpdateResult.NeedWipe) { StartWipe(); } break; case DoomState.DemoPlayback: switch (demoPlayback.Update()) { case UpdateResult.NeedWipe: StartWipe(); break; case UpdateResult.Completed: Quit("FPS: " + demoPlayback.Fps.ToString("0.0")); break; } break; case DoomState.Game: userInput.BuildTicCmd(cmds[options.ConsolePlayer]); if (sendPause) { sendPause = false; cmds[options.ConsolePlayer].Buttons |= (byte)(TicCmdButtons.Special | TicCmdButtons.Pause); } if (game.Update(cmds) == UpdateResult.NeedWipe) { StartWipe(); } break; default: throw new Exception("Invalid application state!"); } } if (wiping && wipeEffect.Update() == UpdateResult.Completed) { wiping = false; } sound.Update(); CheckMouseState(); return UpdateResult.None; } private void CheckMouseState() { bool flag = video.HasFocus() && (config.video_fullscreen || (currentState == DoomState.Game && !menu.Active)); if (mouseGrabbed) { if (!flag) { userInput.ReleaseMouse(); mouseGrabbed = false; } } else if (flag) { userInput.GrabMouse(); mouseGrabbed = true; } } private void StartWipe() { wipeEffect.Start(); video.InitializeWipe(); wiping = true; } public void PauseGame() { if (currentState == DoomState.Game && game.State == GameState.Level && !game.Paused && !sendPause) { sendPause = true; } } public void ResumeGame() { if (currentState == DoomState.Game && game.State == GameState.Level && game.Paused && !sendPause) { sendPause = true; } } public bool SaveGame(int slotNumber, string description) { if (currentState == DoomState.Game && game.State == GameState.Level) { game.SaveGame(slotNumber, description); return true; } return false; } public void LoadGame(int slotNumber) { game.LoadGame(slotNumber); nextState = DoomState.Game; } public void Quit() { quit = true; } public void Quit(string message) { quit = true; quitMessage = message; } public void PostEvent(DoomEvent e) { if (events.Count < 64) { events.Add(e); } } } public enum DoomState { None, Opening, DemoPlayback, Game } public sealed class DoomEvent { private EventType type; private DoomKey key; public EventType Type => type; public DoomKey Key => key; public DoomEvent(EventType type, DoomKey key) { this.type = type; this.key = key; } } public enum EventType { KeyDown, KeyUp, Mouse, Joystick } public sealed class Demo { private int p; private byte[] data; private GameOptions options; private int playerCount; public GameOptions Options => options; public Demo(byte[] data) { p = 0; if (data[p++] != 109) { throw new Exception("Demo is from a different game version!"); } this.data = data; options = new GameOptions(); options.Skill = (GameSkill)data[p++]; options.Episode = data[p++]; options.Map = data[p++]; options.Deathmatch = data[p++]; options.RespawnMonsters = data[p++] != 0; options.FastMonsters = data[p++] != 0; options.NoMonsters = data[p++] != 0; options.ConsolePlayer = data[p++]; options.Players[0].InGame = data[p++] != 0; options.Players[1].InGame = data[p++] != 0; options.Players[2].InGame = data[p++] != 0; options.Players[3].InGame = data[p++] != 0; options.DemoPlayback = true; playerCount = 0; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (options.Players[i].InGame) { playerCount++; } } if (playerCount >= 2) { options.NetGame = true; } } public Demo(string fileName) : this(File.ReadAllBytes(fileName)) { } public bool ReadCmd(TicCmd[] cmds) { if (p == data.Length) { return false; } if (data[p] == 128) { return false; } if (p + 4 * playerCount > data.Length) { return false; } Player[] players = options.Players; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (players[i].InGame) { TicCmd ticCmd = cmds[i]; ticCmd.ForwardMove = (sbyte)data[p++]; ticCmd.SideMove = (sbyte)data[p++]; ticCmd.AngleTurn = (short)(data[p++] << 8); ticCmd.Buttons = data[p++]; } } return true; } } public sealed class DoomGame { private enum GameAction { Nothing, LoadLevel, NewGame, LoadGame, SaveGame, Completed, Victory, WorldDone } private GameContent content; private GameOptions options; private GameAction gameAction; private GameState gameState; private int gameTic; private World world; private Intermission intermission; private Finale finale; private bool paused; private int loadGameSlotNumber; private int saveGameSlotNumber; private string saveGameDescription; public GameOptions Options => options; public GameState State => gameState; public int GameTic => gameTic; public World World => world; public Intermission Intermission => intermission; public Finale Finale => finale; public bool Paused => paused; public DoomGame(GameContent content, GameOptions options) { this.content = content; this.options = options; gameAction = GameAction.Nothing; gameTic = 0; } public void DeferedInitNew() { gameAction = GameAction.NewGame; } public void DeferedInitNew(GameSkill skill, int episode, int map) { options.Skill = skill; options.Episode = episode; options.Map = map; gameAction = GameAction.NewGame; } public void LoadGame(int slotNumber) { loadGameSlotNumber = slotNumber; gameAction = GameAction.LoadGame; } public void SaveGame(int slotNumber, string description) { saveGameSlotNumber = slotNumber; saveGameDescription = description; gameAction = GameAction.SaveGame; } public UpdateResult Update(TicCmd[] cmds) { Player[] players = options.Players; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (players[i].InGame && players[i].PlayerState == PlayerState.Reborn) { DoReborn(i); } } while (gameAction != GameAction.Nothing) { switch (gameAction) { case GameAction.LoadLevel: DoLoadLevel(); break; case GameAction.NewGame: DoNewGame(); break; case GameAction.LoadGame: DoLoadGame(); break; case GameAction.SaveGame: DoSaveGame(); break; case GameAction.Completed: DoCompleted(); break; case GameAction.Victory: DoFinale(); break; case GameAction.WorldDone: DoWorldDone(); break; } } for (int j = 0; j < Player.MaxPlayerCount; j++) { if (players[j].InGame) { TicCmd cmd = players[j].Cmd; cmd.CopyFrom(cmds[j]); if (cmd.ForwardMove > GameConst.TurboThreshold && (world.LevelTime & 0x1F) == 0 && ((world.LevelTime >> 5) & 3) == j) { Player player = players[options.ConsolePlayer]; player.SendMessage(players[j].Name + " is turbo!"); } } } for (int k = 0; k < Player.MaxPlayerCount; k++) { if (players[k].InGame && (players[k].Cmd.Buttons & TicCmdButtons.Special) != 0 && (players[k].Cmd.Buttons & TicCmdButtons.SpecialMask) == TicCmdButtons.Pause) { paused = !paused; if (paused) { options.Sound.Pause(); } else { options.Sound.Resume(); } } } UpdateResult updateResult = UpdateResult.None; switch (gameState) { case GameState.Level: if (!paused || world.FirstTicIsNotYetDone) { updateResult = world.Update(); if (updateResult == UpdateResult.Completed) { gameAction = GameAction.Completed; } } break; case GameState.Intermission: updateResult = intermission.Update(); if (updateResult != UpdateResult.Completed) { break; } gameAction = GameAction.WorldDone; if (world.SecretExit) { players[options.ConsolePlayer].DidSecret = true; } if (options.GameMode != GameMode.Commercial) { break; } switch (options.Map) { case 6: case 11: case 20: case 30: DoFinale(); updateResult = UpdateResult.NeedWipe; break; case 15: case 31: if (world.SecretExit) { DoFinale(); updateResult = UpdateResult.NeedWipe; } break; } break; case GameState.Finale: updateResult = finale.Update(); if (updateResult == UpdateResult.Completed) { gameAction = GameAction.WorldDone; } break; } gameTic++; if (updateResult == UpdateResult.NeedWipe) { return UpdateResult.NeedWipe; } return UpdateResult.None; } private void DoLoadLevel() { gameAction = GameAction.Nothing; gameState = GameState.Level; Player[] players = options.Players; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (players[i].InGame && players[i].PlayerState == PlayerState.Dead) { players[i].PlayerState = PlayerState.Reborn; } Array.Clear(players[i].Frags, 0, players[i].Frags.Length); } intermission = null; options.Sound.Reset(); world = new World(content, options, this); options.UserInput.Reset(); } private void DoNewGame() { gameAction = GameAction.Nothing; InitNew(options.Skill, options.Episode, options.Map); } private void DoLoadGame() { gameAction = GameAction.Nothing; string exeDirectory = ConfigUtilities.GetExeDirectory(); string path = Path.Combine(exeDirectory, "doomsav" + loadGameSlotNumber + ".dsg"); SaveAndLoad.Load(this, path); } private void DoSaveGame() { gameAction = GameAction.Nothing; string exeDirectory = ConfigUtilities.GetExeDirectory(); string path = Path.Combine(exeDirectory, "doomsav" + saveGameSlotNumber + ".dsg"); SaveAndLoad.Save(this, saveGameDescription, path); world.ConsolePlayer.SendMessage(DoomInfo.Strings.GGSAVED); } private void DoCompleted() { gameAction = GameAction.Nothing; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (options.Players[i].InGame) { options.Players[i].FinishLevel(); } } if (options.GameMode != GameMode.Commercial) { switch (options.Map) { case 8: gameAction = GameAction.Victory; return; case 9: { for (int j = 0; j < Player.MaxPlayerCount; j++) { options.Players[j].DidSecret = true; } break; } } } if (options.Map == 8 && options.GameMode != GameMode.Commercial) { gameAction = GameAction.Victory; return; } if (options.Map == 9 && options.GameMode != GameMode.Commercial) { for (int k = 0; k < Player.MaxPlayerCount; k++) { options.Players[k].DidSecret = true; } } IntermissionInfo intermissionInfo = options.IntermissionInfo; intermissionInfo.DidSecret = options.Players[options.ConsolePlayer].DidSecret; intermissionInfo.Episode = options.Episode - 1; intermissionInfo.LastLevel = options.Map - 1; if (options.GameMode == GameMode.Commercial) { if (world.SecretExit) { switch (options.Map) { case 15: intermissionInfo.NextLevel = 30; break; case 31: intermissionInfo.NextLevel = 31; break; } } else { int map = options.Map; int num = map; if ((uint)(num - 31) <= 1u) { intermissionInfo.NextLevel = 15; } else { intermissionInfo.NextLevel = options.Map; } } } else if (world.SecretExit) { intermissionInfo.NextLevel = 8; } else if (options.Map == 9) { switch (options.Episode) { case 1: intermissionInfo.NextLevel = 3; break; case 2: intermissionInfo.NextLevel = 5; break; case 3: intermissionInfo.NextLevel = 6; break; case 4: intermissionInfo.NextLevel = 2; break; } } else { intermissionInfo.NextLevel = options.Map; } intermissionInfo.MaxKillCount = world.TotalKills; intermissionInfo.MaxItemCount = world.TotalItems; intermissionInfo.MaxSecretCount = world.TotalSecrets; intermissionInfo.TotalFrags = 0; if (options.GameMode == GameMode.Commercial) { intermissionInfo.ParTime = 35 * DoomInfo.ParTimes.Doom2[options.Map - 1]; } else { intermissionInfo.ParTime = 35 * DoomInfo.ParTimes.Doom1[options.Episode - 1][options.Map - 1]; } Player[] players = options.Players; for (int l = 0; l < Player.MaxPlayerCount; l++) { intermissionInfo.Players[l].InGame = players[l].InGame; intermissionInfo.Players[l].KillCount = players[l].KillCount; intermissionInfo.Players[l].ItemCount = players[l].ItemCount; intermissionInfo.Players[l].SecretCount = players[l].SecretCount; intermissionInfo.Players[l].Time = world.LevelTime; Array.Copy(players[l].Frags, intermissionInfo.Players[l].Frags, Player.MaxPlayerCount); } gameState = GameState.Intermission; intermission = new Intermission(options, intermissionInfo); } private void DoWorldDone() { gameAction = GameAction.Nothing; gameState = GameState.Level; options.Map = options.IntermissionInfo.NextLevel + 1; DoLoadLevel(); } private void DoFinale() { gameAction = GameAction.Nothing; gameState = GameState.Finale; finale = new Finale(options); } public void InitNew(GameSkill skill, int episode, int map) { options.Skill = (GameSkill)Math.Clamp((int)skill, 0, 4); if (options.GameMode == GameMode.Retail) { options.Episode = Math.Clamp(episode, 1, 4); } else if (options.GameMode == GameMode.Shareware) { options.Episode = 1; } else { options.Episode = Math.Clamp(episode, 1, 4); } if (options.GameMode == GameMode.Commercial) { options.Map = Math.Clamp(map, 1, 32); } else { options.Map = Math.Clamp(map, 1, 9); } options.Random.Clear(); for (int i = 0; i < Player.MaxPlayerCount; i++) { options.Players[i].PlayerState = PlayerState.Reborn; } DoLoadLevel(); } public bool DoEvent(DoomEvent e) { if (gameState == GameState.Level) { return world.DoEvent(e); } if (gameState == GameState.Finale) { return finale.DoEvent(e); } return false; } private void DoReborn(int playerNumber) { if (!options.NetGame) { gameAction = GameAction.LoadLevel; return; } options.Players[playerNumber].Mobj.Player = null; ThingAllocation thingAllocation = world.ThingAllocation; if (options.Deathmatch != 0) { thingAllocation.DeathMatchSpawnPlayer(playerNumber); return; } if (thingAllocation.CheckSpot(playerNumber, thingAllocation.PlayerStarts[playerNumber])) { thingAllocation.SpawnPlayer(thingAllocation.PlayerStarts[playerNumber]); return; } for (int i = 0; i < Player.MaxPlayerCount; i++) { if (thingAllocation.CheckSpot(playerNumber, thingAllocation.PlayerStarts[i])) { thingAllocation.PlayerStarts[i].Type = playerNumber + 1; world.ThingAllocation.SpawnPlayer(thingAllocation.PlayerStarts[i]); thingAllocation.PlayerStarts[i].Type = i + 1; return; } } world.ThingAllocation.SpawnPlayer(thingAllocation.PlayerStarts[playerNumber]); } } public static class GameConst { public static readonly int TicRate = 35; public static readonly Fixed MaxThingRadius = Fixed.FromInt(32); public static readonly int TurboThreshold = 50; } public sealed class GameContent : IDisposable { private Wad wad; private Palette palette; private ColorMap colorMap; private ITextureLookup textures; private IFlatLookup flats; private ISpriteLookup sprites; private TextureAnimation animation; public Wad Wad => wad; public Palette Palette => palette; public ColorMap ColorMap => colorMap; public ITextureLookup Textures => textures; public IFlatLookup Flats => flats; public ISpriteLookup Sprites => sprites; public TextureAnimation Animation => animation; private GameContent() { } public GameContent(CommandLineArgs args) { wad = new Wad(ConfigUtilities.GetWadPaths(args)); DeHackEd.Initialize(args, wad); palette = new Palette(wad); colorMap = new ColorMap(wad); textures = new TextureLookup(wad); flats = new FlatLookup(wad); sprites = new SpriteLookup(wad); animation = new TextureAnimation(textures, flats); } public static GameContent CreateDummy(params string[] wadPaths) { GameContent gameContent = new GameContent(); gameContent.wad = new Wad(wadPaths); gameContent.palette = new Palette(gameContent.wad); gameContent.colorMap = new ColorMap(gameContent.wad); gameContent.textures = new DummyTextureLookup(gameContent.wad); gameContent.flats = new DummyFlatLookup(gameContent.wad); gameContent.sprites = new DummySpriteLookup(gameContent.wad); gameContent.animation = new TextureAnimation(gameContent.textures, gameContent.flats); return gameContent; } public void Dispose() { if (wad != null) { wad.Dispose(); wad = null; } } } public enum GameMode { Shareware, Registered, Commercial, Retail, Indetermined } public sealed class GameOptions { private GameVersion gameVersion; private GameMode gameMode; private MissionPack missionPack; private Player[] players; private int consolePlayer; private int episode; private int map; private GameSkill skill; private bool demoPlayback; private bool netGame; private int deathmatch; private bool fastMonsters; private bool respawnMonsters; private bool noMonsters; private IntermissionInfo intermissionInfo; private DoomRandom random; private IVideo video; private ISound sound; private IMusic music; private IUserInput userInput; public GameVersion GameVersion { get { return gameVersion; } set { gameVersion = value; } } public GameMode GameMode { get { return gameMode; } set { gameMode = value; } } public MissionPack MissionPack { get { return missionPack; } set { missionPack = value; } } public Player[] Players => players; public int ConsolePlayer { get { return consolePlayer; } set { consolePlayer = value; } } public int Episode { get { return episode; } set { episode = value; } } public int Map { get { return map; } set { map = value; } } public GameSkill Skill { get { return skill; } set { skill = value; } } public bool DemoPlayback { get { return demoPlayback; } set { demoPlayback = value; } } public bool NetGame { get { return netGame; } set { netGame = value; } } public int Deathmatch { get { return deathmatch; } set { deathmatch = value; } } public bool FastMonsters { get { return fastMonsters; } set { fastMonsters = value; } } public bool RespawnMonsters { get { return respawnMonsters; } set { respawnMonsters = value; } } public bool NoMonsters { get { return noMonsters; } set { noMonsters = value; } } public IntermissionInfo IntermissionInfo => intermissionInfo; public DoomRandom Random => random; public IVideo Video { get { return video; } set { video = value; } } public ISound Sound { get { return sound; } set { sound = value; } } public IMusic Music { get { return music; } set { music = value; } } public IUserInput UserInput { get { return userInput; } set { userInput = value; } } public GameOptions() { gameVersion = GameVersion.Version109; gameMode = GameMode.Commercial; missionPack = MissionPack.Doom2; players = new Player[Player.MaxPlayerCount]; for (int i = 0; i < Player.MaxPlayerCount; i++) { players[i] = new Player(i); } players[0].InGame = true; consolePlayer = 0; episode = 1; map = 1; skill = GameSkill.Medium; demoPlayback = false; netGame = false; deathmatch = 0; fastMonsters = false; respawnMonsters = false; noMonsters = false; intermissionInfo = new IntermissionInfo(); random = new DoomRandom(); video = NullVideo.GetInstance(); sound = NullSound.GetInstance(); music = NullMusic.GetInstance(); userInput = NullUserInput.GetInstance(); } public GameOptions(CommandLineArgs args, GameContent content) : this() { if (args.solonet.Present) { netGame = true; } gameVersion = content.Wad.GameVersion; gameMode = content.Wad.GameMode; missionPack = content.Wad.MissionPack; } } public enum GameSkill { Baby, Easy, Medium, Hard, Nightmare } public enum GameState { Level, Intermission, Finale } public enum GameVersion { Version109, Ultimate, Final, Final2 } public enum MissionPack { Doom2, Plutonia, Tnt } public sealed class Player { public static readonly int MaxPlayerCount = 4; public static readonly Fixed NormalViewHeight = Fixed.FromInt(41); private static readonly string[] defaultPlayerNames = new string[4] { "Green", "Indigo", "Brown", "Red" }; private int number; private string name; private bool inGame; private Mobj mobj; private PlayerState playerState; private TicCmd cmd; private Fixed viewZ; private Fixed viewHeight; private Fixed deltaViewHeight; private Fixed bob; private int health; private int armorPoints; private int armorType; private int[] powers; private bool[] cards; private bool backpack; private int[] frags; private WeaponType readyWeapon; private WeaponType pendingWeapon; private bool[] weaponOwned; private int[] ammo; private int[] maxAmmo; private bool attackDown; private bool useDown; private CheatFlags cheats; private int refire; private int killCount; private int itemCount; private int secretCount; private string message; private int messageTime; private int damageCount; private int bonusCount; private Mobj attacker; private int extraLight; private int fixedColorMap; private int colorMap; private PlayerSpriteDef[] playerSprites; private bool didSecret; private bool interpolate; private Fixed oldViewZ; private Angle oldAngle; public int Number => number; public string Name => name; public bool InGame { get { return inGame; } set { inGame = value; } } public Mobj Mobj { get { return mobj; } set { mobj = value; } } public PlayerState PlayerState { get { return playerState; } set { playerState = value; } } public TicCmd Cmd => cmd; public Fixed ViewZ { get { return viewZ; } set { viewZ = value; } } public Fixed ViewHeight { get { return viewHeight; } set { viewHeight = value; } } public Fixed DeltaViewHeight { get { return deltaViewHeight; } set { deltaViewHeight = value; } } public Fixed Bob { get { return bob; } set { bob = value; } } public int Health { get { return health; } set { health = value; } } public int ArmorPoints { get { return armorPoints; } set { armorPoints = value; } } public int ArmorType { get { return armorType; } set { armorType = value; } } public int[] Powers => powers; public bool[] Cards => cards; public bool Backpack { get { return backpack; } set { backpack = value; } } public int[] Frags => frags; public WeaponType ReadyWeapon { get { return readyWeapon; } set { readyWeapon = value; } } public WeaponType PendingWeapon { get { return pendingWeapon; } set { pendingWeapon = value; } } public bool[] WeaponOwned => weaponOwned; public int[] Ammo => ammo; public int[] MaxAmmo => maxAmmo; public bool AttackDown { get { return attackDown; } set { attackDown = value; } } public bool UseDown { get { return useDown; } set { useDown = value; } } public CheatFlags Cheats { get { return cheats; } set { cheats = value; } } public int Refire { get { return refire; } set { refire = value; } } public int KillCount { get { return killCount; } set { killCount = value; } } public int ItemCount { get { return itemCount; } set { itemCount = value; } } public int SecretCount { get { return secretCount; } set { secretCount = value; } } public string Message { get { return message; } set { message = value; } } public int MessageTime { get { return messageTime; } set { messageTime = value; } } public int DamageCount { get { return damageCount; } set { damageCount = value; } } public int BonusCount { get { return bonusCount; } set { bonusCount = value; } } public Mobj Attacker { get { return attacker; } set { attacker = value; } } public int ExtraLight { get { return extraLight; } set { extraLight = value; } } public int FixedColorMap { get { return fixedColorMap; } set { fixedColorMap = value; } } public int ColorMap { get { return colorMap; } set { colorMap = value; } } public PlayerSpriteDef[] PlayerSprites => playerSprites; public bool DidSecret { get { return didSecret; } set { didSecret = value; } } public Player(int number) { this.number = number; name = defaultPlayerNames[number]; cmd = new TicCmd(); powers = new int[6]; cards = new bool[6]; frags = new int[MaxPlayerCount]; weaponOwned = new bool[9]; ammo = new int[4]; maxAmmo = new int[4]; playerSprites = new PlayerSpriteDef[2]; for (int i = 0; i < playerSprites.Length; i++) { playerSprites[i] = new PlayerSpriteDef(); } } public void Clear() { mobj = null; playerState = PlayerState.Live; cmd.Clear(); viewZ = Fixed.Zero; viewHeight = Fixed.Zero; deltaViewHeight = Fixed.Zero; bob = Fixed.Zero; health = 0; armorPoints = 0; armorType = 0; Array.Clear(powers, 0, powers.Length); Array.Clear(cards, 0, cards.Length); backpack = false; Array.Clear(frags, 0, frags.Length); readyWeapon = WeaponType.Fist; pendingWeapon = WeaponType.Fist; Array.Clear(weaponOwned, 0, weaponOwned.Length); Array.Clear(ammo, 0, ammo.Length); Array.Clear(maxAmmo, 0, maxAmmo.Length); useDown = false; attackDown = false; cheats = (CheatFlags)0; refire = 0; killCount = 0; itemCount = 0; secretCount = 0; message = null; messageTime = 0; damageCount = 0; bonusCount = 0; attacker = null; extraLight = 0; fixedColorMap = 0; colorMap = 0; PlayerSpriteDef[] array = playerSprites; foreach (PlayerSpriteDef playerSpriteDef in array) { playerSpriteDef.Clear(); } didSecret = false; interpolate = false; oldViewZ = Fixed.Zero; oldAngle = Angle.Ang0; } public void Reborn() { mobj = null; playerState = PlayerState.Live; cmd.Clear(); viewZ = Fixed.Zero; viewHeight = Fixed.Zero; deltaViewHeight = Fixed.Zero; bob = Fixed.Zero; health = DoomInfo.DeHackEdConst.InitialHealth; armorPoints = 0; armorType = 0; Array.Clear(powers, 0, powers.Length); Array.Clear(cards, 0, cards.Length); backpack = false; readyWeapon = WeaponType.Pistol; pendingWeapon = WeaponType.Pistol; Array.Clear(weaponOwned, 0, weaponOwned.Length); Array.Clear(ammo, 0, ammo.Length); Array.Clear(maxAmmo, 0, maxAmmo.Length); weaponOwned[0] = true; weaponOwned[1] = true; ammo[0] = DoomInfo.DeHackEdConst.InitialBullets; for (int i = 0; i < 4; i++) { maxAmmo[i] = DoomInfo.AmmoInfos.Max[i]; } useDown = true; attackDown = true; cheats = (CheatFlags)0; refire = 0; message = null; messageTime = 0; damageCount = 0; bonusCount = 0; attacker = null; extraLight = 0; fixedColorMap = 0; colorMap = 0; PlayerSpriteDef[] array = playerSprites; foreach (PlayerSpriteDef playerSpriteDef in array) { playerSpriteDef.Clear(); } didSecret = false; interpolate = false; oldViewZ = Fixed.Zero; oldAngle = Angle.Ang0; } public void FinishLevel() { Array.Clear(powers, 0, powers.Length); Array.Clear(cards, 0, cards.Length); mobj.Flags &= ~MobjFlags.Shadow; extraLight = 0; fixedColorMap = 0; damageCount = 0; bonusCount = 0; } public void SendMessage(string message) { if ((object)this.message != (string)DoomInfo.Strings.MSGOFF || (object)message == (string)DoomInfo.Strings.MSGON) { this.message = message; messageTime = 4 * GameConst.TicRate; } } public void UpdateFrameInterpolationInfo() { interpolate = true; oldViewZ = viewZ; oldAngle = mobj.Angle; } public void DisableFrameInterpolationForOneFrame() { interpolate = false; } public Fixed GetInterpolatedViewZ(Fixed frameFrac) { if (interpolate && mobj.World.LevelTime > 1) { return oldViewZ + frameFrac * (viewZ - oldViewZ); } return viewZ; } public Angle GetInterpolatedAngle(Fixed frameFrac) { if (interpolate) { Angle angle = mobj.Angle - oldAngle; if (angle < Angle.Ang180) { return oldAngle + Angle.FromDegree(frameFrac.ToDouble() * angle.ToDegree()); } return oldAngle - Angle.FromDegree(frameFrac.ToDouble() * (360.0 - angle.ToDegree())); } return mobj.Angle; } } public enum PlayerState { Live, Dead, Reborn } public static class SaveAndLoad { private enum ThinkerClass { End, Mobj } private enum SpecialClass { Ceiling, Door, Floor, Plat, Flash, Strobe, Glow, EndSpecials } private class SaveGame { private byte[] data; private int ptr; public SaveGame(string description) { data = new byte[saveBufferSize]; ptr = 0; WriteDescription(description); WriteVersion(); } private void WriteDescription(string description) { for (int i = 0; i < description.Length; i++) { data[i] = (byte)description[i]; } ptr += DescriptionSize; } private void WriteVersion() { string text = "version 109"; for (int i = 0; i < text.Length; i++) { data[ptr + i] = (byte)text[i]; } ptr += versionSize; } public void Save(DoomGame game, string path) { GameOptions options = game.World.Options; data[ptr++] = (byte)options.Skill; data[ptr++] = (byte)options.Episode; data[ptr++] = (byte)options.Map; for (int i = 0; i < Player.MaxPlayerCount; i++) { data[ptr++] = (byte)(options.Players[i].InGame ? 1 : 0); } data[ptr++] = (byte)(game.World.LevelTime >> 16); data[ptr++] = (byte)(game.World.LevelTime >> 8); data[ptr++] = (byte)game.World.LevelTime; ArchivePlayers(game.World); ArchiveWorld(game.World); ArchiveThinkers(game.World); ArchiveSpecials(game.World); data[ptr++] = 29; using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write); fileStream.Write(data, 0, ptr); } private void PadPointer() { ptr += (4 - (ptr & 3)) & 3; } private void ArchivePlayers(World world) { Player[] players = world.Options.Players; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (players[i].InGame) { PadPointer(); ptr = ArchivePlayer(players[i], data, ptr); } } } private void ArchiveWorld(World world) { Sector[] sectors = world.Map.Sectors; for (int i = 0; i < sectors.Length; i++) { ptr = ArchiveSector(sectors[i], data, ptr); } LineDef[] lines = world.Map.Lines; for (int j = 0; j < lines.Length; j++) { ptr = ArchiveLine(lines[j], data, ptr); } } private void ArchiveThinkers(World world) { Thinkers thinkers = world.Thinkers; foreach (Thinker item in thinkers) { if (item is Mobj mobj) { data[ptr++] = 1; PadPointer(); WriteThinkerState(data, ptr + 8, mobj.ThinkerState); Write(data, ptr + 12, mobj.X.Data); Write(data, ptr + 16, mobj.Y.Data); Write(data, ptr + 20, mobj.Z.Data); Write(data, ptr + 32, mobj.Angle.Data); Write(data, ptr + 36, (int)mobj.Sprite); Write(data, ptr + 40, mobj.Frame); Write(data, ptr + 56, mobj.FloorZ.Data); Write(data, ptr + 60, mobj.CeilingZ.Data); Write(data, ptr + 64, mobj.Radius.Data); Write(data, ptr + 68, mobj.Height.Data); Write(data, ptr + 72, mobj.MomX.Data); Write(data, ptr + 76, mobj.MomY.Data); Write(data, ptr + 80, mobj.MomZ.Data); Write(data, ptr + 88, (int)mobj.Type); Write(data, ptr + 96, mobj.Tics); Write(data, ptr + 100, mobj.State.Number); Write(data, ptr + 104, (int)mobj.Flags); Write(data, ptr + 108, mobj.Health); Write(data, ptr + 112, (int)mobj.MoveDir); Write(data, ptr + 116, mobj.MoveCount); Write(data, ptr + 124, mobj.ReactionTime); Write(data, ptr + 128, mobj.Threshold); if (mobj.Player == null) { Write(data, ptr + 132, 0); } else { Write(data, ptr + 132, mobj.Player.Number + 1); } Write(data, ptr + 136, mobj.LastLook); if (mobj.SpawnPoint == null) { Write(data, ptr + 140, (short)0); Write(data, ptr + 142, (short)0); Write(data, ptr + 144, (short)0); Write(data, ptr + 146, (short)0); Write(data, ptr + 148, (short)0); } else { Write(data, ptr + 140, (short)mobj.SpawnPoint.X.ToIntFloor()); Write(data, ptr + 142, (short)mobj.SpawnPoint.Y.ToIntFloor()); Write(data, ptr + 144, (short)Math.Round(mobj.SpawnPoint.Angle.ToDegree())); Write(data, ptr + 146, (short)mobj.SpawnPoint.Type); Write(data, ptr + 148, (short)mobj.SpawnPoint.Flags); } ptr += 154; } } data[ptr++] = 0; } private void ArchiveSpecials(World world) { Thinkers thinkers = world.Thinkers; SectorAction sectorAction = world.SectorAction; foreach (Thinker item in thinkers) { if (item.ThinkerState == ThinkerState.InStasis) { CeilingMove ceilingMove = item as CeilingMove; if (sectorAction.CheckActiveCeiling(ceilingMove)) { data[ptr++] = 0; PadPointer(); WriteThinkerState(data, ptr + 8, ceilingMove.ThinkerState); Write(data, ptr + 12, (int)ceilingMove.Type); Write(data, ptr + 16, ceilingMove.Sector.Number); Write(data, ptr + 20, ceilingMove.BottomHeight.Data); Write(data, ptr + 24, ceilingMove.TopHeight.Data); Write(data, ptr + 28, ceilingMove.Speed.Data); Write(data, ptr + 32, ceilingMove.Crush ? 1 : 0); Write(data, ptr + 36, ceilingMove.Direction); Write(data, ptr + 40, ceilingMove.Tag); Write(data, ptr + 44, ceilingMove.OldDirection); ptr += 48; } } else if (item is CeilingMove ceilingMove2) { data[ptr++] = 0; PadPointer(); WriteThinkerState(data, ptr + 8, ceilingMove2.ThinkerState); Write(data, ptr + 12, (int)ceilingMove2.Type); Write(data, ptr + 16, ceilingMove2.Sector.Number); Write(data, ptr + 20, ceilingMove2.BottomHeight.Data); Write(data, ptr + 24, ceilingMove2.TopHeight.Data); Write(data, ptr + 28, ceilingMove2.Speed.Data); Write(data, ptr + 32, ceilingMove2.Crush ? 1 : 0); Write(data, ptr + 36, ceilingMove2.Direction); Write(data, ptr + 40, ceilingMove2.Tag); Write(data, ptr + 44, ceilingMove2.OldDirection); ptr += 48; } else if (item is VerticalDoor verticalDoor) { data[ptr++] = 1; PadPointer(); WriteThinkerState(data, ptr + 8, verticalDoor.ThinkerState); Write(data, ptr + 12, (int)verticalDoor.Type); Write(data, ptr + 16, verticalDoor.Sector.Number); Write(data, ptr + 20, verticalDoor.TopHeight.Data); Write(data, ptr + 24, verticalDoor.Speed.Data); Write(data, ptr + 28, verticalDoor.Direction); Write(data, ptr + 32, verticalDoor.TopWait); Write(data, ptr + 36, verticalDoor.TopCountDown); ptr += 40; } else if (item is FloorMove floorMove) { data[ptr++] = 2; PadPointer(); WriteThinkerState(data, ptr + 8, floorMove.ThinkerState); Write(data, ptr + 12, (int)floorMove.Type); Write(data, ptr + 16, floorMove.Crush ? 1 : 0); Write(data, ptr + 20, floorMove.Sector.Number); Write(data, ptr + 24, floorMove.Direction); Write(data, ptr + 28, (int)floorMove.NewSpecial); Write(data, ptr + 32, floorMove.Texture); Write(data, ptr + 36, floorMove.FloorDestHeight.Data); Write(data, ptr + 40, floorMove.Speed.Data); ptr += 44; } else if (item is Platform platform) { data[ptr++] = 3; PadPointer(); WriteThinkerState(data, ptr + 8, platform.ThinkerState); Write(data, ptr + 12, platform.Sector.Number); Write(data, ptr + 16, platform.Speed.Data); Write(data, ptr + 20, platform.Low.Data); Write(data, ptr + 24, platform.High.Data); Write(data, ptr + 28, platform.Wait); Write(data, ptr + 32, platform.Count); Write(data, ptr + 36, (int)platform.Status); Write(data, ptr + 40, (int)platform.OldStatus); Write(data, ptr + 44, platform.Crush ? 1 : 0); Write(data, ptr + 48, platform.Tag); Write(data, ptr + 52, (int)platform.Type); ptr += 56; } else if (item is LightFlash lightFlash) { data[ptr++] = 4; PadPointer(); WriteThinkerState(data, ptr + 8, lightFlash.ThinkerState); Write(data, ptr + 12, lightFlash.Sector.Number); Write(data, ptr + 16, lightFlash.Count); Write(data, ptr + 20, lightFlash.MaxLight); Write(data, ptr + 24, lightFlash.MinLight); Write(data, ptr + 28, lightFlash.MaxTime); Write(data, ptr + 32, lightFlash.MinTime); ptr += 36; } else if (item is StrobeFlash strobeFlash) { data[ptr++] = 5; PadPointer(); WriteThinkerState(data, ptr + 8, strobeFlash.ThinkerState); Write(data, ptr + 12, strobeFlash.Sector.Number); Write(data, ptr + 16, strobeFlash.Count); Write(data, ptr + 20, strobeFlash.MinLight); Write(data, ptr + 24, strobeFlash.MaxLight); Write(data, ptr + 28, strobeFlash.DarkTime); Write(data, ptr + 32, strobeFlash.BrightTime); ptr += 36; } else if (item is GlowingLight glowingLight) { data[ptr++] = 6; PadPointer(); WriteThinkerState(data, ptr + 8, glowingLight.ThinkerState); Write(data, ptr + 12, glowingLight.Sector.Number); Write(data, ptr + 16, glowingLight.MinLight); Write(data, ptr + 20, glowingLight.MaxLight); Write(data, ptr + 24, glowingLight.Direction); ptr += 28; } } data[ptr++] = 7; } private static int ArchivePlayer(Player player, byte[] data, int p) { Write(data, p + 4, (int)player.PlayerState); Write(data, p + 16, player.ViewZ.Data); Write(data, p + 20, player.ViewHeight.Data); Write(data, p + 24, player.DeltaViewHeight.Data); Write(data, p + 28, player.Bob.Data); Write(data, p + 32, player.Health); Write(data, p + 36, player.ArmorPoints); Write(data, p + 40, player.ArmorType); for (int i = 0; i < 6; i++) { Write(data, p + 44 + 4 * i, player.Powers[i]); } for (int j = 0; j < 6; j++) { Write(data, p + 68 + 4 * j, player.Cards[j] ? 1 : 0); } Write(data, p + 92, player.Backpack ? 1 : 0); for (int k = 0; k < Player.MaxPlayerCount; k++) { Write(data, p + 96 + 4 * k, player.Frags[k]); } Write(data, p + 112, (int)player.ReadyWeapon); Write(data, p + 116, (int)player.PendingWeapon); for (int l = 0; l < 9; l++) { Write(data, p + 120 + 4 * l, player.WeaponOwned[l] ? 1 : 0); } for (int m = 0; m < 4; m++) { Write(data, p + 156 + 4 * m, player.Ammo[m]); } for (int n = 0; n < 4; n++) { Write(data, p + 172 + 4 * n, player.MaxAmmo[n]); } Write(data, p + 188, player.AttackDown ? 1 : 0); Write(data, p + 192, player.UseDown ? 1 : 0); Write(data, p + 196, (int)player.Cheats); Write(data, p + 200, player.Refire); Write(data, p + 204, player.KillCount); Write(data, p + 208, player.ItemCount); Write(data, p + 212, player.SecretCount); Write(data, p + 220, player.DamageCount); Write(data, p + 224, player.BonusCount); Write(data, p + 232, player.ExtraLight); Write(data, p + 236, player.FixedColorMap); Write(data, p + 240, player.ColorMap); for (int num = 0; num < 2; num++) { if (player.PlayerSprites[num].State == null) { Write(data, p + 244 + 16 * num, 0); } else { Write(data, p + 244 + 16 * num, player.PlayerSprites[num].State.Number); } Write(data, p + 244 + 16 * num + 4, player.PlayerSprites[num].Tics); Write(data, p + 244 + 16 * num + 8, player.PlayerSprites[num].Sx.Data); Write(data, p + 244 + 16 * num + 12, player.PlayerSprites[num].Sy.Data); } Write(data, p + 276, player.DidSecret ? 1 : 0); return p + 280; } private static int ArchiveSector(Sector sector, byte[] data, int p) { Write(data, p, (short)sector.FloorHeight.ToIntFloor()); Write(data, p + 2, (short)sector.CeilingHeight.ToIntFloor()); Write(data, p + 4, (short)sector.FloorFlat); Write(data, p + 6, (short)sector.CeilingFlat); Write(data, p + 8, (short)sector.LightLevel); Write(data, p + 10, (short)sector.Special); Write(data, p + 12, (short)sector.Tag); return p + 14; } private static int ArchiveLine(LineDef line, byte[] data, int p) { Write(data, p, (short)line.Flags); Write(data, p + 2, (short)line.Special); Write(data, p + 4, line.Tag); p += 6; if (line.FrontSide != null) { SideDef frontSide = line.FrontSide; Write(data, p, (short)frontSide.TextureOffset.ToIntFloor()); Write(data, p + 2, (short)frontSide.RowOffset.ToIntFloor()); Write(data, p + 4, (short)frontSide.TopTexture); Write(data, p + 6, (short)frontSide.BottomTexture); Write(data, p + 8, (short)frontSide.MiddleTexture); p += 10; } if (line.BackSide != null) { SideDef backSide = line.BackSide; Write(data, p, (short)backSide.TextureOffset.ToIntFloor()); Write(data, p + 2, (short)backSide.RowOffset.ToIntFloor()); Write(data, p + 4, (short)backSide.TopTexture); Write(data, p + 6, (short)backSide.BottomTexture); Write(data, p + 8, (short)backSide.MiddleTexture); p += 10; } return p; } private static void Write(byte[] data, int p, int value) { data[p] = (byte)value; data[p + 1] = (byte)(value >> 8); data[p + 2] = (byte)(value >> 16); data[p + 3] = (byte)(value >> 24); } private static void Write(byte[] data, int p, uint value) { data[p] = (byte)value; data[p + 1] = (byte)(value >> 8); data[p + 2] = (byte)(value >> 16); data[p + 3] = (byte)(value >> 24); } private static void Write(byte[] data, int p, short value) { data[p] = (byte)value; data[p + 1] = (byte)(value >> 8); } private static void WriteThinkerState(byte[] data, int p, ThinkerState state) { if (state == ThinkerState.InStasis) { Write(data, p, 0); } else { Write(data, p, 1); } } } private class LoadGame { private byte[] data; private int ptr; public LoadGame(byte[] data) { this.data = data; ptr = 0; ReadDescription(); string text = ReadVersion(); if (text != "VERSION 109") { throw new Exception("Unsupported version!"); } } public void Load(DoomGame game) { GameOptions options = game.World.Options; options.Skill = (GameSkill)data[ptr++]; options.Episode = data[ptr++]; options.Map = data[ptr++]; for (int i = 0; i < Player.MaxPlayerCount; i++) { options.Players[i].InGame = data[ptr++] != 0; } game.InitNew(options.Skill, options.Episode, options.Map); byte b = data[ptr++]; byte b2 = data[ptr++]; byte b3 = data[ptr++]; int levelTime = (b << 16) + (b2 << 8) + b3; UnArchivePlayers(game.World); UnArchiveWorld(game.World); UnArchiveThinkers(game.World); UnArchiveSpecials(game.World); if (data[ptr] != 29) { throw new Exception("Bad savegame!"); } game.World.LevelTime = levelTime; options.Sound.SetListener(game.World.ConsolePlayer.Mobj); } private void PadPointer() { ptr += (4 - (ptr & 3)) & 3; } private string ReadDescription() { string result = DoomInterop.ToString(data, ptr, DescriptionSize); ptr += DescriptionSize; return result; } private string ReadVersion() { string result = DoomInterop.ToString(data, ptr, versionSize); ptr += versionSize; return result; } private void UnArchivePlayers(World world) { Player[] players = world.Options.Players; for (int i = 0; i < Player.MaxPlayerCount; i++) { if (players[i].InGame) { PadPointer(); ptr = UnArchivePlayer(players[i], data, ptr); } } } private void UnArchiveWorld(World world) { Sector[] sectors = world.Map.Sectors; for (int i = 0; i < sectors.Length; i++) { ptr = UnArchiveSector(sectors[i], data, ptr); } LineDef[] lines = world.Map.Lines; for (int j = 0; j < lines.Length; j++) { ptr = UnArchiveLine(lines[j], data, ptr); } } private void UnArchiveThinkers(World world) { Thinkers thinkers = world.Thinkers; ThingAllocation thingAllocation = world.ThingAllocation; foreach (Thinker item in thinkers) { if (item is Mobj mobj) { thingAllocation.RemoveMobj(mobj); } } thinkers.Reset(); while (true) { Mobj mobj2; switch ((ThinkerClass)data[ptr++]) { case ThinkerClass.End: return; case ThinkerClass.Mobj: { PadPointer(); mobj2 = new Mobj(world); mobj2.ThinkerState = ReadThinkerState(data, ptr + 8); mobj2.X = new Fixed(BitConverter.ToInt32(data, ptr + 12)); mobj2.Y = new Fixed(BitConverter.ToInt32(data, ptr + 16)); mobj2.Z = new Fixed(BitConverter.ToInt32(data, ptr + 20)); mobj2.Angle = new Angle(BitConverter.ToInt32(data, ptr + 32)); mobj2.Sprite = (Sprite)BitConverter.ToInt32(data, ptr + 36); mobj2.Frame = BitConverter.ToInt32(data, ptr + 40); mobj2.FloorZ = new Fixed(BitConverter.ToInt32(data, ptr + 56)); mobj2.CeilingZ = new Fixed(BitConverter.ToInt32(data, ptr + 60)); mobj2.Radius = new Fixed(BitConverter.ToInt32(data, ptr + 64)); mobj2.Height = new Fixed(BitConverter.ToInt32(data, ptr + 68)); mobj2.MomX = new Fixed(BitConverter.ToInt32(data, ptr + 72)); mobj2.MomY = new Fixed(BitConverter.ToInt32(data, ptr + 76)); mobj2.MomZ = new Fixed(BitConverter.ToInt32(data, ptr + 80)); mobj2.Type = (MobjType)BitConverter.ToInt32(data, ptr + 88); mobj2.Info = DoomInfo.MobjInfos[(int)mobj2.Type]; mobj2.Tics = BitConverter.ToInt32(data, ptr + 96); mobj2.State = DoomInfo.States[BitConverter.ToInt32(data, ptr + 100)]; mobj2.Flags = (MobjFlags)BitConverter.ToInt32(data, ptr + 104); mobj2.Health = BitConverter.ToInt32(data, ptr + 108); mobj2.MoveDir = (Direction)BitConverter.ToInt32(data, ptr + 112); mobj2.MoveCount = BitConverter.ToInt32(data, ptr + 116); mobj2.ReactionTime = BitConverter.ToInt32(data, ptr + 124); mobj2.Threshold = BitConverter.ToInt32(data, ptr + 128); int num = BitConverter.ToInt32(data, ptr + 132); if (num != 0) { mobj2.Player = world.Options.Players[num - 1]; mobj2.Player.Mobj = mobj2; } break; } default: throw new Exception("Unknown thinker class in savegame!"); } mobj2.LastLook = BitConverter.ToInt32(data, ptr + 136); mobj2.SpawnPoint = new MapThing(Fixed.FromInt(BitConverter.ToInt16(data, ptr + 140)), Fixed.FromInt(BitConverter.ToInt16(data, ptr + 142)), new Angle(Angle.Ang45.Data * (uint)(BitConverter.ToInt16(data, ptr + 144) / 45)), BitConverter.ToInt16(data, ptr + 146), (ThingFlags)BitConverter.ToInt16(data, ptr + 148)); ptr += 154; world.ThingMovement.SetThingPosition(mobj2); thinkers.Add(mobj2); } } private void UnArchiveSpecials(World world) { Thinkers thinkers = world.Thinkers; SectorAction sectorAction = world.SectorAction; while (true) { switch ((SpecialClass)data[ptr++]) { case SpecialClass.EndSpecials: return; case SpecialClass.Ceiling: { PadPointer(); CeilingMove ceilingMove = new CeilingMove(world); ceilingMove.ThinkerState = ReadThinkerState(data, ptr + 8); ceilingMove.Type = (CeilingMoveType)BitConverter.ToInt32(data, ptr + 12); ceilingMove.Sector = world.Map.Sectors[BitConverter.ToInt32(data, ptr + 16)]; ceilingMove.Sector.SpecialData = ceilingMove; ceilingMove.BottomHeight = new Fixed(BitConverter.ToInt32(data, ptr + 20)); ceilingMove.TopHeight = new Fixed(BitConverter.ToInt32(data, ptr + 24)); ceilingMove.Speed = new Fixed(BitConverter.ToInt32(data, ptr + 28)); ceilingMove.Crush = BitConverter.ToInt32(data, ptr + 32) != 0; ceilingMove.Direction = BitConverter.ToInt32(data, ptr + 36); ceilingMove.Tag = BitConverter.ToInt32(data, ptr + 40); ceilingMove.OldDirection = BitConverter.ToInt32(data, ptr + 44); ptr += 48; thinkers.Add(ceilingMove); sectorAction.AddActiveCeiling(ceilingMove); break; } case SpecialClass.Door: { PadPointer(); VerticalDoor verticalDoor = new VerticalDoor(world); verticalDoor.ThinkerState = ReadThinkerState(data, ptr + 8); verticalDoor.Type = (VerticalDoorType)BitConverter.ToInt32(data, ptr + 12); verticalDoor.Sector = world.Map.Sectors[BitConverter.ToInt32(data, ptr + 16)]; verticalDoor.Sector.SpecialData = verticalDoor; verticalDoor.TopHeight = new Fixed(BitConverter.ToInt32(data, ptr + 20)); verticalDoor.Speed = new Fixed(BitConverter.ToInt32(data, ptr + 24)); verticalDoor.Direction = BitConverter.ToInt32(data, ptr + 28); verticalDoor.TopWait = BitConverter.ToInt32(data, ptr + 32); verticalDoor.TopCountDown = BitConverter.ToInt32(data, ptr + 36); ptr += 40; thinkers.Add(verticalDoor); break; } case SpecialClass.Floor: { PadPointer(); FloorMove floorMove = new FloorMove(world); floorMove.ThinkerState = ReadThinkerState(data, ptr + 8); floorMove.Type = (FloorMoveType)BitConverter.ToInt32(data, ptr + 12); floorMove.Crush = BitConverter.ToInt32(data, ptr + 16) != 0; floorMove.Sector = world.Map.Sectors[BitConverter.ToInt32(data, ptr + 20)]; floorMove.Sector.SpecialData = floorMove;
plugins/data/lib/MeltySynth.dll
Decompiled 18 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("MeltySynthTest")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Nobuaki Tanaka")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("A SoundFont MIDI synthesizer for .NET")] [assembly: AssemblyFileVersion("2.4.1.0")] [assembly: AssemblyInformationalVersion("2.4.1")] [assembly: AssemblyProduct("MeltySynth")] [assembly: AssemblyTitle("MeltySynth")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/sinshu/meltysynth/")] [assembly: AssemblyVersion("2.4.1.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace MeltySynth { internal static class ArrayMath { public static void MultiplyAdd(float a, float[] x, float[] destination) { Span<Vector<float>> span = MemoryMarshal.Cast<float, Vector<float>>(x); Span<Vector<float>> span2 = MemoryMarshal.Cast<float, Vector<float>>(destination); int num = 0; for (int i = 0; i < span2.Length; i++) { span2[i] += a * span[i]; num += Vector<float>.Count; } for (int j = num; j < destination.Length; j++) { destination[j] += a * x[j]; } } public static void MultiplyAdd(float a, float step, float[] x, float[] destination) { for (int i = 0; i < destination.Length; i++) { destination[i] += a * x[i]; a += step; } } } public static class AudioRendererEx { public static void RenderInterleaved(this IAudioRenderer renderer, Span<float> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } if (destination.Length % 2 != 0) { throw new ArgumentException("The length of the destination buffer must be even.", "destination"); } int num = destination.Length / 2; int length = destination.Length; float[] array = ArrayPool<float>.Shared.Rent(length); try { Span<float> left = array.AsSpan(0, num); Span<float> right = array.AsSpan(num, num); renderer.Render(left, right); int num2 = 0; for (int i = 0; i < num; i++) { destination[num2++] = left[i]; destination[num2++] = right[i]; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderMono(this IAudioRenderer renderer, Span<float> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } int length = destination.Length; int minimumLength = 2 * destination.Length; float[] array = ArrayPool<float>.Shared.Rent(minimumLength); try { Span<float> left = array.AsSpan(0, length); Span<float> right = array.AsSpan(length, length); renderer.Render(left, right); for (int i = 0; i < length; i++) { destination[i] = (left[i] + right[i]) / 2f; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderInt16(this IAudioRenderer renderer, Span<short> left, Span<short> right) { if (renderer == null) { throw new ArgumentNullException("renderer"); } if (left.Length != right.Length) { throw new ArgumentException("The output buffers for the left and right must be the same length."); } int length = left.Length; int minimumLength = 2 * left.Length; float[] array = ArrayPool<float>.Shared.Rent(minimumLength); try { Span<float> left2 = array.AsSpan(0, length); Span<float> right2 = array.AsSpan(length, length); renderer.Render(left2, right2); for (int i = 0; i < length; i++) { float num = 32768f * left2[i]; if (num < -32768f) { num = -32768f; } else if (num > 32767f) { num = 32767f; } left[i] = (short)num; } for (int j = 0; j < length; j++) { float num2 = 32768f * right2[j]; if (num2 < -32768f) { num2 = -32768f; } else if (num2 > 32767f) { num2 = 32767f; } right[j] = (short)num2; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderInterleavedInt16(this IAudioRenderer renderer, Span<short> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } if (destination.Length % 2 != 0) { throw new ArgumentException("The length of the destination buffer must be even.", "destination"); } int num = destination.Length / 2; int length = destination.Length; float[] array = ArrayPool<float>.Shared.Rent(length); try { Span<float> left = array.AsSpan(0, num); Span<float> right = array.AsSpan(num, num); renderer.Render(left, right); int num2 = 0; for (int i = 0; i < num; i++) { int num3 = (int)(32768f * left[i]); if (num3 < -32768) { num3 = -32768; } else if (num3 > 32767) { num3 = 32767; } int num4 = (int)(32768f * right[i]); if (num4 < -32768) { num4 = -32768; } else if (num4 > 32767) { num4 = 32767; } destination[num2++] = (short)num3; destination[num2++] = (short)num4; } } finally { ArrayPool<float>.Shared.Return(array); } } public static void RenderMonoInt16(this IAudioRenderer renderer, Span<short> destination) { if (renderer == null) { throw new ArgumentNullException("renderer"); } int length = destination.Length; int minimumLength = 2 * destination.Length; float[] array = ArrayPool<float>.Shared.Rent(minimumLength); try { Span<float> left = array.AsSpan(0, length); Span<float> right = array.AsSpan(length, length); renderer.Render(left, right); for (int i = 0; i < length; i++) { int num = (int)(16384f * (left[i] + right[i])); if (num < -32768) { num = -32768; } else if (num > 32767) { num = 32767; } destination[i] = (short)num; } } finally { ArrayPool<float>.Shared.Return(array); } } } internal static class BinaryReaderEx { public static string ReadFourCC(this BinaryReader reader) { byte[] array = reader.ReadBytes(4); for (int i = 0; i < array.Length; i++) { byte b = array[i]; if (32 > b || b > 126) { array[i] = 63; } } return Encoding.ASCII.GetString(array, 0, array.Length); } public static string ReadFixedLengthString(this BinaryReader reader, int length) { byte[] array = reader.ReadBytes(length); int i; for (i = 0; i < array.Length && array[i] != 0; i++) { } return Encoding.ASCII.GetString(array, 0, i); } public static short ReadInt16BigEndian(this BinaryReader reader) { short num = reader.ReadInt16(); int num2 = 0xFF & num; int num3 = 0xFF & (num >> 8); return (short)((num2 << 8) | num3); } public static int ReadInt32BigEndian(this BinaryReader reader) { int num = reader.ReadInt32(); int num2 = 0xFF & num; int num3 = 0xFF & (num >> 8); int num4 = 0xFF & (num >> 16); int num5 = 0xFF & (num >> 24); return (num2 << 24) | (num3 << 16) | (num4 << 8) | num5; } public static int ReadIntVariableLength(this BinaryReader reader) { int num = 0; int num2 = 0; while (true) { byte b = reader.ReadByte(); num = (num << 7) | (b & 0x7F); if ((b & 0x80) == 0) { break; } num2++; if (num2 == 4) { throw new InvalidDataException("The length of the value must be equal to or less than 4."); } } return num; } } internal sealed class BiQuadFilter { private static readonly float resonancePeakOffset = 1f - 1f / MathF.Sqrt(2f); private readonly Synthesizer synthesizer; private bool active; private float a0; private float a1; private float a2; private float a3; private float a4; private float x1; private float x2; private float y1; private float y2; internal BiQuadFilter(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void ClearBuffer() { x1 = 0f; x2 = 0f; y1 = 0f; y2 = 0f; } public void SetLowPassFilter(float cutoffFrequency, float resonance) { if (cutoffFrequency < 0.499f * (float)synthesizer.SampleRate) { active = true; float num = resonance - resonancePeakOffset / (1f + 6f * (resonance - 1f)); float x = MathF.PI * 2f * cutoffFrequency / (float)synthesizer.SampleRate; float num2 = MathF.Cos(x); float num3 = MathF.Sin(x) / (2f * num); float b = (1f - num2) / 2f; float b2 = 1f - num2; float b3 = (1f - num2) / 2f; float num4 = 1f + num3; float num5 = -2f * num2; float num6 = 1f - num3; SetCoefficients(num4, num5, num6, b, b2, b3); } else { active = false; } } public void Process(float[] block) { if (active) { for (int i = 0; i < block.Length; i++) { float num = block[i]; float num2 = a0 * num + a1 * x1 + a2 * x2 - a3 * y1 - a4 * y2; x2 = x1; x1 = num; y2 = y1; y1 = num2; block[i] = num2; } } else { x2 = block[^2]; x1 = block[^1]; y2 = x2; y1 = x1; } } private void SetCoefficients(float a0, float a1, float a2, float b0, float b1, float b2) { this.a0 = b0 / a0; this.a1 = b1 / a0; this.a2 = b2 / a0; a3 = a1 / a0; a4 = a2 / a0; } } internal sealed class Channel { private readonly Synthesizer synthesizer; private readonly bool isPercussionChannel; private int bankNumber; private int patchNumber; private short modulation; private short volume; private short pan; private short expression; private bool holdPedal; private byte reverbSend; private byte chorusSend; private short rpn; private short pitchBendRange; private short coarseTune; private short fineTune; private float pitchBend; public bool IsPercussionChannel => isPercussionChannel; public int BankNumber => bankNumber; public int PatchNumber => patchNumber; public float Modulation => 0.003051944f * (float)modulation; public float Volume => 6.103888E-05f * (float)volume; public float Pan => 0.006103888f * (float)pan - 50f; public float Expression => 6.103888E-05f * (float)expression; public bool HoldPedal => holdPedal; public float ReverbSend => 1f / 127f * (float)(int)reverbSend; public float ChorusSend => 1f / 127f * (float)(int)chorusSend; public float PitchBendRange => (float)(pitchBendRange >> 7) + 0.01f * (float)(pitchBendRange & 0x7F); public float Tune => (float)coarseTune + 0.00012207031f * (float)(fineTune - 8192); public float PitchBend => PitchBendRange * pitchBend; internal Channel(Synthesizer synthesizer, bool isPercussionChannel) { this.synthesizer = synthesizer; this.isPercussionChannel = isPercussionChannel; Reset(); } public void Reset() { bankNumber = (isPercussionChannel ? 128 : 0); patchNumber = 0; modulation = 0; volume = 12800; pan = 8192; expression = 16256; holdPedal = false; reverbSend = 40; chorusSend = 0; rpn = -1; pitchBendRange = 256; coarseTune = 0; fineTune = 8192; pitchBend = 0f; } public void ResetAllControllers() { modulation = 0; expression = 16256; holdPedal = false; rpn = -1; pitchBend = 0f; } public void SetBank(int value) { bankNumber = value; if (isPercussionChannel) { bankNumber += 128; } } public void SetPatch(int value) { patchNumber = value; } public void SetModulationCoarse(int value) { modulation = (short)((modulation & 0x7F) | (value << 7)); } public void SetModulationFine(int value) { modulation = (short)((modulation & 0xFF80) | value); } public void SetVolumeCoarse(int value) { volume = (short)((volume & 0x7F) | (value << 7)); } public void SetVolumeFine(int value) { volume = (short)((volume & 0xFF80) | value); } public void SetPanCoarse(int value) { pan = (short)((pan & 0x7F) | (value << 7)); } public void SetPanFine(int value) { pan = (short)((pan & 0xFF80) | value); } public void SetExpressionCoarse(int value) { expression = (short)((expression & 0x7F) | (value << 7)); } public void SetExpressionFine(int value) { expression = (short)((expression & 0xFF80) | value); } public void SetHoldPedal(int value) { holdPedal = value >= 64; } public void SetReverbSend(int value) { reverbSend = (byte)value; } public void SetChorusSend(int value) { chorusSend = (byte)value; } public void SetRpnCoarse(int value) { rpn = (short)((rpn & 0x7F) | (value << 7)); } public void SetRpnFine(int value) { rpn = (short)((rpn & 0xFF80) | value); } public void DataEntryCoarse(int value) { switch (rpn) { case 0: pitchBendRange = (short)((pitchBendRange & 0x7F) | (value << 7)); break; case 1: fineTune = (short)((fineTune & 0x7F) | (value << 7)); break; case 2: coarseTune = (short)(value - 64); break; } } public void DataEntryFine(int value) { switch (rpn) { case 0: pitchBendRange = (short)((pitchBendRange & 0xFF80) | value); break; case 1: fineTune = (short)((fineTune & 0xFF80) | value); break; } } public void SetPitchBend(int value1, int value2) { pitchBend = 0.00012207031f * (float)((value1 | (value2 << 7)) - 8192); } } internal sealed class Chorus { private readonly float[] bufferL; private readonly float[] bufferR; private readonly float[] delayTable; private int bufferIndex; private int delayTableIndexL; private int delayTableIndexR; internal Chorus(int sampleRate, double delay, double depth, double frequency) { bufferL = new float[(int)((double)sampleRate * (delay + depth)) + 2]; bufferR = new float[(int)((double)sampleRate * (delay + depth)) + 2]; delayTable = new float[(int)Math.Round((double)sampleRate / frequency)]; for (int i = 0; i < delayTable.Length; i++) { double a = Math.PI * 2.0 * (double)i / (double)delayTable.Length; delayTable[i] = (float)((double)sampleRate * (delay + depth * Math.Sin(a))); } bufferIndex = 0; delayTableIndexL = 0; delayTableIndexR = delayTable.Length / 4; } public void Process(float[] inputLeft, float[] inputRight, float[] outputLeft, float[] outputRight) { for (int i = 0; i < outputLeft.Length; i++) { double num = (double)bufferIndex - (double)delayTable[delayTableIndexL]; if (num < 0.0) { num += (double)bufferL.Length; } int num2 = (int)num; int num3 = num2 + 1; if (num3 == bufferL.Length) { num3 = 0; } double num4 = bufferL[num2]; double num5 = bufferL[num3]; double num6 = num - (double)num2; outputLeft[i] = (float)(num4 + num6 * (num5 - num4)); delayTableIndexL++; if (delayTableIndexL == delayTable.Length) { delayTableIndexL = 0; } double num7 = (double)bufferIndex - (double)delayTable[delayTableIndexR]; if (num7 < 0.0) { num7 += (double)bufferR.Length; } int num8 = (int)num7; int num9 = num8 + 1; if (num9 == bufferR.Length) { num9 = 0; } double num10 = bufferR[num8]; double num11 = bufferR[num9]; double num12 = num7 - (double)num8; outputRight[i] = (float)(num10 + num12 * (num11 - num10)); delayTableIndexR++; if (delayTableIndexR == delayTable.Length) { delayTableIndexR = 0; } bufferL[bufferIndex] = inputLeft[i]; bufferR[bufferIndex] = inputRight[i]; bufferIndex++; if (bufferIndex == bufferL.Length) { bufferIndex = 0; } } } public void Mute() { Array.Clear(bufferL, 0, bufferL.Length); Array.Clear(bufferR, 0, bufferR.Length); } } internal struct Generator { private readonly GeneratorType type; private readonly ushort value; public GeneratorType Type => type; public ushort Value => value; private Generator(BinaryReader reader) { type = (GeneratorType)reader.ReadUInt16(); value = reader.ReadUInt16(); } internal static Generator[] ReadFromChunk(BinaryReader reader, int size) { if (size % 4 != 0) { throw new InvalidDataException("The generator list is invalid."); } Generator[] array = new Generator[size / 4 - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new Generator(reader); } new Generator(reader); return array; } } internal enum GeneratorType : ushort { StartAddressOffset, EndAddressOffset, StartLoopAddressOffset, EndLoopAddressOffset, StartAddressCoarseOffset, ModulationLfoToPitch, VibratoLfoToPitch, ModulationEnvelopeToPitch, InitialFilterCutoffFrequency, InitialFilterQ, ModulationLfoToFilterCutoffFrequency, ModulationEnvelopeToFilterCutoffFrequency, EndAddressCoarseOffset, ModulationLfoToVolume, Unused1, ChorusEffectsSend, ReverbEffectsSend, Pan, Unused2, Unused3, Unused4, DelayModulationLfo, FrequencyModulationLfo, DelayVibratoLfo, FrequencyVibratoLfo, DelayModulationEnvelope, AttackModulationEnvelope, HoldModulationEnvelope, DecayModulationEnvelope, SustainModulationEnvelope, ReleaseModulationEnvelope, KeyNumberToModulationEnvelopeHold, KeyNumberToModulationEnvelopeDecay, DelayVolumeEnvelope, AttackVolumeEnvelope, HoldVolumeEnvelope, DecayVolumeEnvelope, SustainVolumeEnvelope, ReleaseVolumeEnvelope, KeyNumberToVolumeEnvelopeHold, KeyNumberToVolumeEnvelopeDecay, Instrument, Reserved1, KeyRange, VelocityRange, StartLoopAddressCoarseOffset, KeyNumber, Velocity, InitialAttenuation, Reserved2, EndLoopAddressCoarseOffset, CoarseTune, FineTune, SampleID, SampleModes, Reserved3, ScaleTuning, ExclusiveClass, OverridingRootKey, Unused5, UnusedEnd } public interface IAudioRenderer { void Render(Span<float> left, Span<float> right); } public sealed class Instrument { internal static readonly Instrument Default = new Instrument(); private readonly string name; private readonly InstrumentRegion[] regions; public string Name => name; public IReadOnlyList<InstrumentRegion> Regions => regions; internal InstrumentRegion[] RegionArray => regions; private Instrument() { name = "Default"; regions = Array.Empty<InstrumentRegion>(); } private Instrument(InstrumentInfo info, Zone[] zones, SampleHeader[] samples) { name = info.Name; int num = info.ZoneEndIndex - info.ZoneStartIndex + 1; if (num <= 0) { throw new InvalidDataException("The instrument '" + info.Name + "' has no zone."); } Span<Zone> zones2 = zones.AsSpan(info.ZoneStartIndex, num); regions = InstrumentRegion.Create(this, zones2, samples); } internal static Instrument[] Create(InstrumentInfo[] infos, Zone[] zones, SampleHeader[] samples) { if (infos.Length <= 1) { throw new InvalidDataException("No valid instrument was found."); } Instrument[] array = new Instrument[infos.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new Instrument(infos[i], zones, samples); } return array; } public override string ToString() { return name; } } internal sealed class InstrumentInfo { private string name; private int zoneStartIndex; private int zoneEndIndex; public string Name => name; public int ZoneStartIndex => zoneStartIndex; public int ZoneEndIndex => zoneEndIndex; private InstrumentInfo(BinaryReader reader) { name = reader.ReadFixedLengthString(20); zoneStartIndex = reader.ReadUInt16(); } internal static InstrumentInfo[] ReadFromChunk(BinaryReader reader, int size) { if (size % 22 != 0) { throw new InvalidDataException("The instrument list is invalid."); } int num = size / 22; InstrumentInfo[] array = new InstrumentInfo[num]; for (int i = 0; i < num; i++) { array[i] = new InstrumentInfo(reader); } for (int j = 0; j < num - 1; j++) { array[j].zoneEndIndex = array[j + 1].zoneStartIndex - 1; } return array; } } public sealed class InstrumentRegion { internal static readonly InstrumentRegion Default = new InstrumentRegion(); private readonly short[] gs; private readonly SampleHeader sample; internal short this[GeneratorType generatortType] => gs[(uint)generatortType]; public SampleHeader Sample => sample; public int SampleStart => sample.Start + StartAddressOffset; public int SampleEnd => sample.End + EndAddressOffset; public int SampleStartLoop => sample.StartLoop + StartLoopAddressOffset; public int SampleEndLoop => sample.EndLoop + EndLoopAddressOffset; public int StartAddressOffset => 32768 * this[GeneratorType.StartAddressCoarseOffset] + this[GeneratorType.StartAddressOffset]; public int EndAddressOffset => 32768 * this[GeneratorType.EndAddressCoarseOffset] + this[GeneratorType.EndAddressOffset]; public int StartLoopAddressOffset => 32768 * this[GeneratorType.StartLoopAddressCoarseOffset] + this[GeneratorType.StartLoopAddressOffset]; public int EndLoopAddressOffset => 32768 * this[GeneratorType.EndLoopAddressCoarseOffset] + this[GeneratorType.EndLoopAddressOffset]; public int ModulationLfoToPitch => this[GeneratorType.ModulationLfoToPitch]; public int VibratoLfoToPitch => this[GeneratorType.VibratoLfoToPitch]; public int ModulationEnvelopeToPitch => this[GeneratorType.ModulationEnvelopeToPitch]; public float InitialFilterCutoffFrequency => SoundFontMath.CentsToHertz(this[GeneratorType.InitialFilterCutoffFrequency]); public float InitialFilterQ => 0.1f * (float)this[GeneratorType.InitialFilterQ]; public int ModulationLfoToFilterCutoffFrequency => this[GeneratorType.ModulationLfoToFilterCutoffFrequency]; public int ModulationEnvelopeToFilterCutoffFrequency => this[GeneratorType.ModulationEnvelopeToFilterCutoffFrequency]; public float ModulationLfoToVolume => 0.1f * (float)this[GeneratorType.ModulationLfoToVolume]; public float ChorusEffectsSend => 0.1f * (float)this[GeneratorType.ChorusEffectsSend]; public float ReverbEffectsSend => 0.1f * (float)this[GeneratorType.ReverbEffectsSend]; public float Pan => 0.1f * (float)this[GeneratorType.Pan]; public float DelayModulationLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationLfo]); public float FrequencyModulationLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyModulationLfo]); public float DelayVibratoLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVibratoLfo]); public float FrequencyVibratoLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyVibratoLfo]); public float DelayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationEnvelope]); public float AttackModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackModulationEnvelope]); public float HoldModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldModulationEnvelope]); public float DecayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayModulationEnvelope]); public float SustainModulationEnvelope => 0.1f * (float)this[GeneratorType.SustainModulationEnvelope]; public float ReleaseModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseModulationEnvelope]); public int KeyNumberToModulationEnvelopeHold => this[GeneratorType.KeyNumberToModulationEnvelopeHold]; public int KeyNumberToModulationEnvelopeDecay => this[GeneratorType.KeyNumberToModulationEnvelopeDecay]; public float DelayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVolumeEnvelope]); public float AttackVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackVolumeEnvelope]); public float HoldVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldVolumeEnvelope]); public float DecayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayVolumeEnvelope]); public float SustainVolumeEnvelope => 0.1f * (float)this[GeneratorType.SustainVolumeEnvelope]; public float ReleaseVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseVolumeEnvelope]); public int KeyNumberToVolumeEnvelopeHold => this[GeneratorType.KeyNumberToVolumeEnvelopeHold]; public int KeyNumberToVolumeEnvelopeDecay => this[GeneratorType.KeyNumberToVolumeEnvelopeDecay]; public int KeyRangeStart => this[GeneratorType.KeyRange] & 0xFF; public int KeyRangeEnd => (this[GeneratorType.KeyRange] >> 8) & 0xFF; public int VelocityRangeStart => this[GeneratorType.VelocityRange] & 0xFF; public int VelocityRangeEnd => (this[GeneratorType.VelocityRange] >> 8) & 0xFF; public float InitialAttenuation => 0.1f * (float)this[GeneratorType.InitialAttenuation]; public int CoarseTune => this[GeneratorType.CoarseTune]; public int FineTune => this[GeneratorType.FineTune] + sample.PitchCorrection; public LoopMode SampleModes { get { if (this[GeneratorType.SampleModes] == 2) { return LoopMode.NoLoop; } return (LoopMode)this[GeneratorType.SampleModes]; } } public int ScaleTuning => this[GeneratorType.ScaleTuning]; public int ExclusiveClass => this[GeneratorType.ExclusiveClass]; public int RootKey { get { if (this[GeneratorType.OverridingRootKey] == -1) { return sample.OriginalPitch; } return this[GeneratorType.OverridingRootKey]; } } private InstrumentRegion() { gs = new short[61]; gs[8] = 13500; gs[21] = -12000; gs[23] = -12000; gs[25] = -12000; gs[26] = -12000; gs[27] = -12000; gs[28] = -12000; gs[30] = -12000; gs[33] = -12000; gs[34] = -12000; gs[35] = -12000; gs[36] = -12000; gs[38] = -12000; gs[43] = 32512; gs[44] = 32512; gs[46] = -1; gs[47] = -1; gs[56] = 100; gs[58] = -1; sample = SampleHeader.Default; } private InstrumentRegion(Instrument instrument, Zone global, Zone local, SampleHeader[] samples) : this() { foreach (Generator generator in global.Generators) { SetParameter(generator); } foreach (Generator generator2 in local.Generators) { SetParameter(generator2); } short num = gs[53]; if (0 > num || num >= samples.Length) { throw new InvalidDataException($"The instrument '{instrument.Name}' contains an invalid sample ID '{num}'."); } sample = samples[num]; } internal static InstrumentRegion[] Create(Instrument instrument, Span<Zone> zones, SampleHeader[] samples) { if (zones[0].Generators.Count == 0 || zones[0].Generators.Last().Type != GeneratorType.SampleID) { Zone global = zones[0]; InstrumentRegion[] array = new InstrumentRegion[zones.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new InstrumentRegion(instrument, global, zones[i + 1], samples); } return array; } InstrumentRegion[] array2 = new InstrumentRegion[zones.Length]; for (int j = 0; j < array2.Length; j++) { array2[j] = new InstrumentRegion(instrument, Zone.Empty, zones[j], samples); } return array2; } private void SetParameter(Generator generator) { int type = (int)generator.Type; if (0 <= type && type < gs.Length) { gs[type] = (short)generator.Value; } } public bool Contains(int key, int velocity) { bool num = KeyRangeStart <= key && key <= KeyRangeEnd; bool flag = VelocityRangeStart <= velocity && velocity <= VelocityRangeEnd; return num && flag; } public override string ToString() { return $"{sample.Name} (Key: {KeyRangeStart}-{KeyRangeEnd}, Velocity: {VelocityRangeStart}-{VelocityRangeEnd})"; } } internal sealed class Lfo { private readonly Synthesizer synthesizer; private bool active; private double delay; private double period; private int processedSampleCount; private float value; public float Value => value; internal Lfo(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void Start(float delay, float frequency) { if ((double)frequency > 0.001) { active = true; this.delay = delay; period = 1.0 / (double)frequency; processedSampleCount = 0; value = 0f; } else { active = false; value = 0f; } } public void Process() { if (!active) { return; } processedSampleCount += synthesizer.BlockSize; double num = (double)processedSampleCount / (double)synthesizer.SampleRate; if (num < delay) { value = 0f; return; } double num2 = (num - delay) % period / period; if (num2 < 0.25) { value = (float)(4.0 * num2); } else if (num2 < 0.75) { value = (float)(4.0 * (0.5 - num2)); } else { value = (float)(4.0 * (num2 - 1.0)); } } } public enum LoopMode { NoLoop = 0, Continuous = 1, LoopUntilNoteOff = 3 } public sealed class MidiFile { internal struct Message { private byte channel; private byte command; private byte data1; private byte data2; public MessageType Type => channel switch { 252 => MessageType.TempoChange, 253 => MessageType.LoopStart, 254 => MessageType.LoopEnd, byte.MaxValue => MessageType.EndOfTrack, _ => MessageType.Normal, }; public byte Channel => channel; public byte Command => command; public byte Data1 => data1; public byte Data2 => data2; public double Tempo => 60000000.0 / (double)((command << 16) | (data1 << 8) | data2); private Message(byte channel, byte command, byte data1, byte data2) { this.channel = channel; this.command = command; this.data1 = data1; this.data2 = data2; } public static Message Common(byte status, byte data1) { byte num = (byte)(status & 0xF); byte b = (byte)(status & 0xF0); byte b2 = 0; return new Message(num, b, data1, b2); } public static Message Common(byte status, byte data1, byte data2, MidiFileLoopType loopType) { byte b = (byte)(status & 0xF); byte b2 = (byte)(status & 0xF0); if (b2 == 176) { switch (loopType) { case MidiFileLoopType.RpgMaker: if (data1 == 111) { return LoopStart(); } break; case MidiFileLoopType.IncredibleMachine: switch (data1) { case 110: return LoopStart(); case 111: return LoopEnd(); } break; case MidiFileLoopType.FinalFantasy: switch (data1) { case 116: return LoopStart(); case 117: return LoopEnd(); } break; } } return new Message(b, b2, data1, data2); } public static Message TempoChange(int tempo) { byte b = (byte)(tempo >> 16); byte b2 = (byte)(tempo >> 8); byte b3 = (byte)tempo; return new Message(252, b, b2, b3); } public static Message LoopStart() { return new Message(253, 0, 0, 0); } public static Message LoopEnd() { return new Message(254, 0, 0, 0); } public static Message EndOfTrack() { return new Message(byte.MaxValue, 0, 0, 0); } public override string ToString() { return channel switch { 252 => "Tempo: " + Tempo, 253 => "LoopStart", 254 => "LoopEnd", byte.MaxValue => "EndOfTrack", _ => "CH" + channel + ": " + command.ToString("X2") + ", " + data1.ToString("X2") + ", " + data2.ToString("X2"), }; } } internal enum MessageType { Normal = 0, TempoChange = 252, LoopStart = 253, LoopEnd = 254, EndOfTrack = 255 } private Message[] messages; private TimeSpan[] times; public TimeSpan Length => times.Last(); internal Message[] Messages => messages; internal TimeSpan[] Times => times; public MidiFile(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } Load(stream, 0, MidiFileLoopType.None); } public MidiFile(Stream stream, int loopPoint) { if (stream == null) { throw new ArgumentNullException("stream"); } if (loopPoint < 0) { throw new ArgumentException("The loop point must be a non-negative value.", "loopPoint"); } Load(stream, loopPoint, MidiFileLoopType.None); } public MidiFile(Stream stream, MidiFileLoopType loopType) { if (stream == null) { throw new ArgumentNullException("stream"); } Load(stream, 0, loopType); } public MidiFile(string path) { if (path == null) { throw new ArgumentNullException("path"); } using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read); Load(stream, 0, MidiFileLoopType.None); } public MidiFile(string path, int loopPoint) { if (path == null) { throw new ArgumentNullException("path"); } if (loopPoint < 0) { throw new ArgumentException("The loop point must be a non-negative value.", "loopPoint"); } using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read); Load(stream, loopPoint, MidiFileLoopType.None); } public MidiFile(string path, MidiFileLoopType loopType) { if (path == null) { throw new ArgumentNullException("path"); } using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read); Load(stream, 0, loopType); } internal static TimeSpan GetTimeSpanFromSeconds(double value) { return new TimeSpan((long)(10000000.0 * value)); } private void Load(Stream stream, int loopPoint, MidiFileLoopType loopType) { using BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, leaveOpen: true); string text = reader.ReadFourCC(); if (text != "MThd") { throw new InvalidDataException("The chunk type must be 'MThd', but was '" + text + "'."); } if (reader.ReadInt32BigEndian() != 6) { throw new InvalidDataException("The MThd chunk has invalid data."); } short num = reader.ReadInt16BigEndian(); if (num != 0 && num != 1) { throw new NotSupportedException($"The format {num} is not supported."); } short num2 = reader.ReadInt16BigEndian(); short resolution = reader.ReadInt16BigEndian(); List<Message>[] array = new List<Message>[num2]; List<int>[] array2 = new List<int>[num2]; for (int i = 0; i < num2; i++) { int num3 = i; List<int>[] array3 = array2; int num4 = i; (List<Message>, List<int>) tuple = ReadTrack(reader, loopType); array[num3] = tuple.Item1; array3[num4] = tuple.Item2; } if (loopPoint != 0) { List<int> list = array2[0]; List<Message> list2 = array[0]; if (loopPoint <= list.Last()) { for (int j = 0; j < list.Count; j++) { if (list[j] >= loopPoint) { list.Insert(j, loopPoint); list2.Insert(j, Message.LoopStart()); break; } } } else { list.Add(loopPoint); list2.Add(Message.LoopStart()); } } (messages, times) = MergeTracks(array, array2, resolution); } private static (List<Message>, List<int>) ReadTrack(BinaryReader reader, MidiFileLoopType loopType) { string text = reader.ReadFourCC(); if (text != "MTrk") { throw new InvalidDataException("The chunk type must be 'MTrk', but was '" + text + "'."); } long num = reader.ReadInt32BigEndian(); num += reader.BaseStream.Position; List<Message> list = new List<Message>(); List<int> list2 = new List<int>(); int num2 = 0; byte b = 0; while (true) { int num3 = reader.ReadIntVariableLength(); byte b2 = reader.ReadByte(); try { num2 = checked(num2 + num3); } catch (OverflowException) { throw new NotSupportedException("Long MIDI file is not supported."); } if ((b2 & 0x80) == 0) { int num4 = b & 0xF0; if (num4 == 192 || num4 == 208) { list.Add(Message.Common(b, b2)); list2.Add(num2); } else { byte data = reader.ReadByte(); list.Add(Message.Common(b, b2, data, loopType)); list2.Add(num2); } continue; } switch (b2) { case 240: DiscardData(reader); break; case 247: DiscardData(reader); break; case byte.MaxValue: switch (reader.ReadByte()) { case 47: reader.ReadByte(); list.Add(Message.EndOfTrack()); list2.Add(num2); if (reader.BaseStream.Position < num) { reader.BaseStream.Position = num; } return (list, list2); case 81: list.Add(Message.TempoChange(ReadTempo(reader))); list2.Add(num2); break; default: DiscardData(reader); break; } break; default: { int num5 = b2 & 0xF0; if (num5 == 192 || num5 == 208) { byte data2 = reader.ReadByte(); list.Add(Message.Common(b2, data2)); list2.Add(num2); } else { byte data3 = reader.ReadByte(); byte data4 = reader.ReadByte(); list.Add(Message.Common(b2, data3, data4, loopType)); list2.Add(num2); } break; } } b = b2; } } private static (Message[], TimeSpan[]) MergeTracks(List<Message>[] messageLists, List<int>[] tickLists, int resolution) { List<Message> list = new List<Message>(); List<TimeSpan> list2 = new List<TimeSpan>(); int[] array = new int[messageLists.Length]; int num = 0; TimeSpan zero = TimeSpan.Zero; double num2 = 120.0; while (true) { int num3 = int.MaxValue; int num4 = -1; for (int i = 0; i < tickLists.Length; i++) { if (array[i] < tickLists[i].Count) { int num5 = tickLists[i][array[i]]; if (num5 < num3) { num3 = num5; num4 = i; } } } if (num4 == -1) { break; } int num6 = tickLists[num4][array[num4]] - num; TimeSpan timeSpanFromSeconds = GetTimeSpanFromSeconds(60.0 / ((double)resolution * num2) * (double)num6); num += num6; zero += timeSpanFromSeconds; Message item = messageLists[num4][array[num4]]; if (item.Type == MessageType.TempoChange) { num2 = item.Tempo; } else { list.Add(item); list2.Add(zero); } array[num4]++; } return (list.ToArray(), list2.ToArray()); } private static int ReadTempo(BinaryReader reader) { if (reader.ReadIntVariableLength() != 3) { throw new InvalidDataException("Failed to read the tempo value."); } byte num = reader.ReadByte(); byte b = reader.ReadByte(); byte b2 = reader.ReadByte(); return (num << 16) | (b << 8) | b2; } private static void DiscardData(BinaryReader reader) { int num = reader.ReadIntVariableLength(); reader.BaseStream.Position += num; } } public enum MidiFileLoopType { None, RpgMaker, IncredibleMachine, FinalFantasy } public sealed class MidiFileSequencer : IAudioRenderer { public delegate void MessageHook(Synthesizer synthesizer, int channel, int command, int data1, int data2); private readonly Synthesizer synthesizer; private float speed; private MidiFile? midiFile; private bool loop; private int blockWrote; private TimeSpan currentTime; private int msgIndex; private int loopIndex; private MessageHook? onSendMessage; public Synthesizer Synthesizer => synthesizer; public TimeSpan Position => currentTime; public bool EndOfSequence { get { if (midiFile == null) { return true; } return msgIndex == midiFile.Messages.Length; } } public float Speed { get { return speed; } set { if (value > 0f) { speed = value; return; } throw new ArgumentOutOfRangeException("The playback speed must be a positive value."); } } public MessageHook? OnSendMessage { get { return onSendMessage; } set { onSendMessage = value; } } public MidiFileSequencer(Synthesizer synthesizer) { if (synthesizer == null) { throw new ArgumentNullException("synthesizer"); } this.synthesizer = synthesizer; speed = 1f; } public void Play(MidiFile midiFile, bool loop) { if (midiFile == null) { throw new ArgumentNullException("midiFile"); } this.midiFile = midiFile; this.loop = loop; blockWrote = synthesizer.BlockSize; currentTime = TimeSpan.Zero; msgIndex = 0; loopIndex = 0; synthesizer.Reset(); } public void Stop() { midiFile = null; synthesizer.Reset(); } public void Render(Span<float> left, Span<float> right) { if (left.Length != right.Length) { throw new ArgumentException("The output buffers for the left and right must be the same length."); } int num; for (int i = 0; i < left.Length; i += num) { if (blockWrote == synthesizer.BlockSize) { ProcessEvents(); blockWrote = 0; currentTime += MidiFile.GetTimeSpanFromSeconds((double)speed * (double)synthesizer.BlockSize / (double)synthesizer.SampleRate); } int val = synthesizer.BlockSize - blockWrote; int val2 = left.Length - i; num = Math.Min(val, val2); synthesizer.Render(left.Slice(i, num), right.Slice(i, num)); blockWrote += num; } } private void ProcessEvents() { if (midiFile == null) { return; } while (msgIndex < midiFile.Messages.Length) { TimeSpan timeSpan = midiFile.Times[msgIndex]; MidiFile.Message message = midiFile.Messages[msgIndex]; if (!(timeSpan <= currentTime)) { break; } if (message.Type == MidiFile.MessageType.Normal) { if (onSendMessage == null) { synthesizer.ProcessMidiMessage(message.Channel, message.Command, message.Data1, message.Data2); } else { onSendMessage(synthesizer, message.Channel, message.Command, message.Data1, message.Data2); } } else if (loop) { if (message.Type == MidiFile.MessageType.LoopStart) { loopIndex = msgIndex; } else if (message.Type == MidiFile.MessageType.LoopEnd) { currentTime = midiFile.Times[loopIndex]; msgIndex = loopIndex; synthesizer.NoteOffAll(immediate: false); } } msgIndex++; } if (msgIndex == midiFile.Messages.Length && loop) { currentTime = midiFile.Times[loopIndex]; msgIndex = loopIndex; synthesizer.NoteOffAll(immediate: false); } } } internal sealed class ModulationEnvelope { private enum Stage { Delay, Attack, Hold, Decay, Release } private readonly Synthesizer synthesizer; private double attackSlope; private double decaySlope; private double releaseSlope; private double attackStartTime; private double holdStartTime; private double decayStartTime; private double decayEndTime; private double releaseEndTime; private float sustainLevel; private float releaseLevel; private int processedSampleCount; private Stage stage; private float value; public float Value => value; internal ModulationEnvelope(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void Start(float delay, float attack, float hold, float decay, float sustain, float release) { attackSlope = 1f / attack; decaySlope = 1f / decay; releaseSlope = 1f / release; attackStartTime = delay; holdStartTime = attackStartTime + (double)attack; decayStartTime = holdStartTime + (double)hold; decayEndTime = decayStartTime + (double)decay; releaseEndTime = release; sustainLevel = Math.Clamp(sustain, 0f, 1f); releaseLevel = 0f; processedSampleCount = 0; stage = Stage.Delay; value = 0f; Process(0); } public void Release() { stage = Stage.Release; releaseEndTime += (double)processedSampleCount / (double)synthesizer.SampleRate; releaseLevel = value; } public bool Process() { return Process(synthesizer.BlockSize); } private bool Process(int sampleCount) { processedSampleCount += sampleCount; double num = (double)processedSampleCount / (double)synthesizer.SampleRate; while (stage <= Stage.Hold && !(num < stage switch { Stage.Delay => attackStartTime, Stage.Attack => holdStartTime, Stage.Hold => decayStartTime, _ => throw new InvalidOperationException("Invalid envelope stage."), })) { stage++; } switch (stage) { case Stage.Delay: value = 0f; return true; case Stage.Attack: value = (float)(attackSlope * (num - attackStartTime)); return true; case Stage.Hold: value = 1f; return true; case Stage.Decay: value = Math.Max((float)(decaySlope * (decayEndTime - num)), sustainLevel); return value > SoundFontMath.NonAudible; case Stage.Release: value = Math.Max((float)((double)releaseLevel * releaseSlope * (releaseEndTime - num)), 0f); return value > SoundFontMath.NonAudible; default: throw new InvalidOperationException("Invalid envelope stage."); } } } internal static class Modulator { internal static void DiscardData(BinaryReader reader, int size) { if (size % 10 != 0) { throw new InvalidDataException("The modulator list is invalid."); } reader.BaseStream.Position += size; } } internal sealed class Oscillator { private const int fracBits = 24; private const long fracUnit = 16777216L; private const float fpToSample = 1.8189894E-12f; private readonly Synthesizer synthesizer; private short[]? data; private LoopMode loopMode; private int sampleRate; private int start; private int end; private int startLoop; private int endLoop; private int rootKey; private float tune; private float pitchChangeScale; private float sampleRateRatio; private bool looping; private long position_fp; internal Oscillator(Synthesizer synthesizer) { this.synthesizer = synthesizer; } public void Start(short[] data, LoopMode loopMode, int sampleRate, int start, int end, int startLoop, int endLoop, int rootKey, int coarseTune, int fineTune, int scaleTuning) { this.data = data; this.loopMode = loopMode; this.sampleRate = sampleRate; this.start = start; this.end = end; this.startLoop = startLoop; this.endLoop = endLoop; this.rootKey = rootKey; tune = (float)coarseTune + 0.01f * (float)fineTune; pitchChangeScale = 0.01f * (float)scaleTuning; sampleRateRatio = (float)sampleRate / (float)synthesizer.SampleRate; if (loopMode == LoopMode.NoLoop) { looping = false; } else { looping = true; } position_fp = (long)start << 24; } public void Release() { if (loopMode == LoopMode.LoopUntilNoteOff) { looping = false; } } public bool Process(float[] block, float pitch) { float num = pitchChangeScale * (pitch - (float)rootKey) + tune; float num2 = sampleRateRatio * MathF.Pow(2f, num / 12f); return FillBlock(block, num2); } internal bool FillBlock(float[] block, double pitchRatio) { long pitchRatio_fp = (long)(16777216.0 * pitchRatio); if (looping) { return FillBlock_Continuous(block, pitchRatio_fp); } return FillBlock_NoLoop(block, pitchRatio_fp); } private bool FillBlock_NoLoop(float[] block, long pitchRatio_fp) { for (int i = 0; i < block.Length; i++) { long num = position_fp >> 24; if (num >= end) { if (i > 0) { Array.Clear(block, i, block.Length - i); return true; } return false; } short num2 = data[num]; short num3 = data[num + 1]; long num4 = position_fp & 0xFFFFFF; block[i] = 1.8189894E-12f * (float)(((long)num2 << 24) + num4 * (num3 - num2)); position_fp += pitchRatio_fp; } return true; } private bool FillBlock_Continuous(float[] block, long pitchRatio_fp) { long num = (long)endLoop << 24; long num2 = endLoop - startLoop; long num3 = num2 << 24; for (int i = 0; i < block.Length; i++) { if (position_fp >= num) { position_fp -= num3; } long num4 = position_fp >> 24; long num5 = num4 + 1; if (num5 >= endLoop) { num5 -= num2; } short num6 = data[num4]; short num7 = data[num5]; long num8 = position_fp & 0xFFFFFF; block[i] = 1.8189894E-12f * (float)(((long)num6 << 24) + num8 * (num7 - num6)); position_fp += pitchRatio_fp; } return true; } } public sealed class Preset { internal static readonly Preset Default = new Preset(); private readonly string name; private readonly int patchNumber; private readonly int bankNumber; private readonly int library; private readonly int genre; private readonly int morphology; private readonly PresetRegion[] regions; public string Name => name; public int PatchNumber => patchNumber; public int BankNumber => bankNumber; public IReadOnlyList<PresetRegion> Regions => regions; internal PresetRegion[] RegionArray => regions; private Preset() { name = "Default"; regions = Array.Empty<PresetRegion>(); } private Preset(PresetInfo info, Zone[] zones, Instrument[] instruments) { name = info.Name; patchNumber = info.PatchNumber; bankNumber = info.BankNumber; library = info.Library; genre = info.Genre; morphology = info.Morphology; int num = info.ZoneEndIndex - info.ZoneStartIndex + 1; if (num <= 0) { throw new InvalidDataException("The preset '" + info.Name + "' has no zone."); } Span<Zone> zones2 = zones.AsSpan(info.ZoneStartIndex, num); regions = PresetRegion.Create(this, zones2, instruments); } internal static Preset[] Create(PresetInfo[] infos, Zone[] zones, Instrument[] instruments) { if (infos.Length <= 1) { throw new InvalidDataException("No valid preset was found."); } Preset[] array = new Preset[infos.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new Preset(infos[i], zones, instruments); } return array; } public override string ToString() { return name; } } internal sealed class PresetInfo { private string name; private int patchNumber; private int bankNumber; private int zoneStartIndex; private int zoneEndIndex; private int library; private int genre; private int morphology; public string Name => name; public int PatchNumber => patchNumber; public int BankNumber => bankNumber; public int ZoneStartIndex => zoneStartIndex; public int ZoneEndIndex => zoneEndIndex; public int Library => library; public int Genre => genre; public int Morphology => morphology; private PresetInfo(BinaryReader reader) { name = reader.ReadFixedLengthString(20); patchNumber = reader.ReadUInt16(); bankNumber = reader.ReadUInt16(); zoneStartIndex = reader.ReadUInt16(); library = reader.ReadInt32(); genre = reader.ReadInt32(); morphology = reader.ReadInt32(); } internal static PresetInfo[] ReadFromChunk(BinaryReader reader, int size) { if (size % 38 != 0) { throw new InvalidDataException("The preset list is invalid."); } int num = size / 38; PresetInfo[] array = new PresetInfo[num]; for (int i = 0; i < num; i++) { array[i] = new PresetInfo(reader); } for (int j = 0; j < num - 1; j++) { array[j].zoneEndIndex = array[j + 1].zoneStartIndex - 1; } return array; } } public sealed class PresetRegion { internal static readonly PresetRegion Default = new PresetRegion(); private readonly short[] gs; private readonly Instrument instrument; internal short this[GeneratorType generatortType] => gs[(uint)generatortType]; public Instrument Instrument => instrument; public int ModulationLfoToPitch => this[GeneratorType.ModulationLfoToPitch]; public int VibratoLfoToPitch => this[GeneratorType.VibratoLfoToPitch]; public int ModulationEnvelopeToPitch => this[GeneratorType.ModulationEnvelopeToPitch]; public float InitialFilterCutoffFrequency => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.InitialFilterCutoffFrequency]); public float InitialFilterQ => 0.1f * (float)this[GeneratorType.InitialFilterQ]; public int ModulationLfoToFilterCutoffFrequency => this[GeneratorType.ModulationLfoToFilterCutoffFrequency]; public int ModulationEnvelopeToFilterCutoffFrequency => this[GeneratorType.ModulationEnvelopeToFilterCutoffFrequency]; public float ModulationLfoToVolume => 0.1f * (float)this[GeneratorType.ModulationLfoToVolume]; public float ChorusEffectsSend => 0.1f * (float)this[GeneratorType.ChorusEffectsSend]; public float ReverbEffectsSend => 0.1f * (float)this[GeneratorType.ReverbEffectsSend]; public float Pan => 0.1f * (float)this[GeneratorType.Pan]; public float DelayModulationLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayModulationLfo]); public float FrequencyModulationLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.FrequencyModulationLfo]); public float DelayVibratoLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayVibratoLfo]); public float FrequencyVibratoLfo => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.FrequencyVibratoLfo]); public float DelayModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayModulationEnvelope]); public float AttackModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.AttackModulationEnvelope]); public float HoldModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.HoldModulationEnvelope]); public float DecayModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DecayModulationEnvelope]); public float SustainModulationEnvelope => 0.1f * (float)this[GeneratorType.SustainModulationEnvelope]; public float ReleaseModulationEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.ReleaseModulationEnvelope]); public int KeyNumberToModulationEnvelopeHold => this[GeneratorType.KeyNumberToModulationEnvelopeHold]; public int KeyNumberToModulationEnvelopeDecay => this[GeneratorType.KeyNumberToModulationEnvelopeDecay]; public float DelayVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DelayVolumeEnvelope]); public float AttackVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.AttackVolumeEnvelope]); public float HoldVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.HoldVolumeEnvelope]); public float DecayVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.DecayVolumeEnvelope]); public float SustainVolumeEnvelope => 0.1f * (float)this[GeneratorType.SustainVolumeEnvelope]; public float ReleaseVolumeEnvelope => SoundFontMath.CentsToMultiplyingFactor(this[GeneratorType.ReleaseVolumeEnvelope]); public int KeyNumberToVolumeEnvelopeHold => this[GeneratorType.KeyNumberToVolumeEnvelopeHold]; public int KeyNumberToVolumeEnvelopeDecay => this[GeneratorType.KeyNumberToVolumeEnvelopeDecay]; public int KeyRangeStart => this[GeneratorType.KeyRange] & 0xFF; public int KeyRangeEnd => (this[GeneratorType.KeyRange] >> 8) & 0xFF; public int VelocityRangeStart => this[GeneratorType.VelocityRange] & 0xFF; public int VelocityRangeEnd => (this[GeneratorType.VelocityRange] >> 8) & 0xFF; public float InitialAttenuation => 0.1f * (float)this[GeneratorType.InitialAttenuation]; public int CoarseTune => this[GeneratorType.CoarseTune]; public int FineTune => this[GeneratorType.FineTune]; public int ScaleTuning => this[GeneratorType.ScaleTuning]; private PresetRegion() { gs = new short[61]; gs[43] = 32512; gs[44] = 32512; instrument = MeltySynth.Instrument.Default; } private PresetRegion(Preset preset, Zone global, Zone local, Instrument[] instruments) : this() { foreach (Generator generator in global.Generators) { SetParameter(generator); } foreach (Generator generator2 in local.Generators) { SetParameter(generator2); } short num = gs[41]; if (0 > num || num >= instruments.Length) { throw new InvalidDataException($"The preset '{preset.Name}' contains an invalid instrument ID '{num}'."); } instrument = instruments[num]; } internal static PresetRegion[] Create(Preset preset, Span<Zone> zones, Instrument[] instruments) { if (zones[0].Generators.Count == 0 || zones[0].Generators.Last().Type != GeneratorType.Instrument) { Zone global = zones[0]; PresetRegion[] array = new PresetRegion[zones.Length - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new PresetRegion(preset, global, zones[i + 1], instruments); } return array; } PresetRegion[] array2 = new PresetRegion[zones.Length]; for (int j = 0; j < array2.Length; j++) { array2[j] = new PresetRegion(preset, Zone.Empty, zones[j], instruments); } return array2; } private void SetParameter(Generator generator) { int type = (int)generator.Type; if (0 <= type && type < gs.Length) { gs[type] = (short)generator.Value; } } public bool Contains(int key, int velocity) { bool num = KeyRangeStart <= key && key <= KeyRangeEnd; bool flag = VelocityRangeStart <= velocity && velocity <= VelocityRangeEnd; return num && flag; } public override string ToString() { return $"{instrument.Name} (Key: {KeyRangeStart}-{KeyRangeEnd}, Velocity: {VelocityRangeStart}-{VelocityRangeEnd})"; } } internal static class RegionEx { public static void Start(this Oscillator oscillator, short[] data, InstrumentRegion region) { oscillator.Start(data, new RegionPair(PresetRegion.Default, region)); } public static void Start(this Oscillator oscillator, short[] data, RegionPair region) { int sampleRate = region.Instrument.Sample.SampleRate; LoopMode sampleModes = region.SampleModes; int sampleStart = region.SampleStart; int sampleEnd = region.SampleEnd; int sampleStartLoop = region.SampleStartLoop; int sampleEndLoop = region.SampleEndLoop; int rootKey = region.RootKey; int coarseTune = region.CoarseTune; int fineTune = region.FineTune; int scaleTuning = region.ScaleTuning; oscillator.Start(data, sampleModes, sampleRate, sampleStart, sampleEnd, sampleStartLoop, sampleEndLoop, rootKey, coarseTune, fineTune, scaleTuning); } public static void Start(this VolumeEnvelope envelope, InstrumentRegion region, int key, int velocity) { envelope.Start(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void Start(this VolumeEnvelope envelope, RegionPair region, int key, int velocity) { float delayVolumeEnvelope = region.DelayVolumeEnvelope; float attackVolumeEnvelope = region.AttackVolumeEnvelope; float hold = region.HoldVolumeEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToVolumeEnvelopeHold, key); float decay = region.DecayVolumeEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToVolumeEnvelopeDecay, key); float sustain = SoundFontMath.DecibelsToLinear(0f - region.SustainVolumeEnvelope); float release = Math.Max(region.ReleaseVolumeEnvelope, 0.01f); envelope.Start(delayVolumeEnvelope, attackVolumeEnvelope, hold, decay, sustain, release); } public static void Start(this ModulationEnvelope envelope, InstrumentRegion region, int key, int velocity) { envelope.Start(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void Start(this ModulationEnvelope envelope, RegionPair region, int key, int velocity) { float delayModulationEnvelope = region.DelayModulationEnvelope; float attack = region.AttackModulationEnvelope * ((float)(145 - velocity) / 144f); float hold = region.HoldModulationEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToModulationEnvelopeHold, key); float decay = region.DecayModulationEnvelope * SoundFontMath.KeyNumberToMultiplyingFactor(region.KeyNumberToModulationEnvelopeDecay, key); float sustain = 1f - region.SustainModulationEnvelope / 100f; float releaseModulationEnvelope = region.ReleaseModulationEnvelope; envelope.Start(delayModulationEnvelope, attack, hold, decay, sustain, releaseModulationEnvelope); } public static void StartVibrato(this Lfo lfo, InstrumentRegion region, int key, int velocity) { lfo.StartVibrato(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void StartVibrato(this Lfo lfo, RegionPair region, int key, int velocity) { lfo.Start(region.DelayVibratoLfo, region.FrequencyVibratoLfo); } public static void StartModulation(this Lfo lfo, InstrumentRegion region, int key, int velocity) { lfo.StartModulation(new RegionPair(PresetRegion.Default, region), key, velocity); } public static void StartModulation(this Lfo lfo, RegionPair region, int key, int velocity) { lfo.Start(region.DelayModulationLfo, region.FrequencyModulationLfo); } } internal struct RegionPair { private readonly PresetRegion preset; private readonly InstrumentRegion instrument; private int this[GeneratorType generatortType] => instrument[generatortType] + preset[generatortType]; public PresetRegion Preset => preset; public InstrumentRegion Instrument => instrument; public int SampleStart => instrument.SampleStart; public int SampleEnd => instrument.SampleEnd; public int SampleStartLoop => instrument.SampleStartLoop; public int SampleEndLoop => instrument.SampleEndLoop; public int StartAddressOffset => instrument.StartAddressOffset; public int EndAddressOffset => instrument.EndAddressOffset; public int StartLoopAddressOffset => instrument.StartLoopAddressOffset; public int EndLoopAddressOffset => instrument.EndLoopAddressOffset; public int ModulationLfoToPitch => this[GeneratorType.ModulationLfoToPitch]; public int VibratoLfoToPitch => this[GeneratorType.VibratoLfoToPitch]; public int ModulationEnvelopeToPitch => this[GeneratorType.ModulationEnvelopeToPitch]; public float InitialFilterCutoffFrequency => SoundFontMath.CentsToHertz(this[GeneratorType.InitialFilterCutoffFrequency]); public float InitialFilterQ => 0.1f * (float)this[GeneratorType.InitialFilterQ]; public int ModulationLfoToFilterCutoffFrequency => this[GeneratorType.ModulationLfoToFilterCutoffFrequency]; public int ModulationEnvelopeToFilterCutoffFrequency => this[GeneratorType.ModulationEnvelopeToFilterCutoffFrequency]; public float ModulationLfoToVolume => 0.1f * (float)this[GeneratorType.ModulationLfoToVolume]; public float ChorusEffectsSend => 0.1f * (float)this[GeneratorType.ChorusEffectsSend]; public float ReverbEffectsSend => 0.1f * (float)this[GeneratorType.ReverbEffectsSend]; public float Pan => 0.1f * (float)this[GeneratorType.Pan]; public float DelayModulationLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationLfo]); public float FrequencyModulationLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyModulationLfo]); public float DelayVibratoLfo => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVibratoLfo]); public float FrequencyVibratoLfo => SoundFontMath.CentsToHertz(this[GeneratorType.FrequencyVibratoLfo]); public float DelayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayModulationEnvelope]); public float AttackModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackModulationEnvelope]); public float HoldModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldModulationEnvelope]); public float DecayModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayModulationEnvelope]); public float SustainModulationEnvelope => 0.1f * (float)this[GeneratorType.SustainModulationEnvelope]; public float ReleaseModulationEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseModulationEnvelope]); public int KeyNumberToModulationEnvelopeHold => this[GeneratorType.KeyNumberToModulationEnvelopeHold]; public int KeyNumberToModulationEnvelopeDecay => this[GeneratorType.KeyNumberToModulationEnvelopeDecay]; public float DelayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DelayVolumeEnvelope]); public float AttackVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.AttackVolumeEnvelope]); public float HoldVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.HoldVolumeEnvelope]); public float DecayVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.DecayVolumeEnvelope]); public float SustainVolumeEnvelope => 0.1f * (float)this[GeneratorType.SustainVolumeEnvelope]; public float ReleaseVolumeEnvelope => SoundFontMath.TimecentsToSeconds(this[GeneratorType.ReleaseVolumeEnvelope]); public int KeyNumberToVolumeEnvelopeHold => this[GeneratorType.KeyNumberToVolumeEnvelopeHold]; public int KeyNumberToVolumeEnvelopeDecay => this[GeneratorType.KeyNumberToVolumeEnvelopeDecay]; public float InitialAttenuation => 0.1f * (float)this[GeneratorType.InitialAttenuation]; public int CoarseTune => this[GeneratorType.CoarseTune]; public int FineTune => this[GeneratorType.FineTune] + instrument.Sample.PitchCorrection; public LoopMode SampleModes => instrument.SampleModes; public int ScaleTuning => this[GeneratorType.ScaleTuning]; public int ExclusiveClass => instrument.ExclusiveClass; public int RootKey => instrument.RootKey; internal RegionPair(PresetRegion preset, InstrumentRegion instrument) { this.preset = preset; this.instrument = instrument; } } internal sealed class Reverb { internal sealed class CombFilter { private readonly float[] buffer; private int bufferIndex; private float filterStore; private float feedback; private float damp1; private float damp2; public float Feedback { get { return feedback; } set { feedback = value; } } public float Damp { get { return damp1; } set { damp1 = value; damp2 = 1f - value; } } public CombFilter(int bufferSize) { buffer = new float[bufferSize]; bufferIndex = 0; filterStore = 0f; feedback = 0f; damp1 = 0f; damp2 = 0f; } public void Mute() { Array.Clear(buffer, 0, buffer.Length); filterStore = 0f; } public void Process(float[] inputBlock, float[] outputBlock) { int num; for (int i = 0; i < outputBlock.Length; i += num) { if (bufferIndex == buffer.Length) { bufferIndex = 0; } int val = buffer.Length - bufferIndex; int val2 = outputBlock.Length - i; num = Math.Min(val, val2); for (int j = 0; j < num; j++) { int num2 = i + j; int num3 = bufferIndex + j; float num4 = inputBlock[num2]; float num5 = buffer[num3]; if (MathF.Abs(num5) < 1E-06f) { num5 = 0f; } filterStore = num5 * damp2 + filterStore * damp1; if (MathF.Abs(filterStore) < 1E-06f) { filterStore = 0f; } buffer[num3] = num4 + filterStore * feedback; outputBlock[num2] += num5; } bufferIndex += num; } } } internal sealed class AllPassFilter { private readonly float[] buffer; private int bufferIndex; private float feedback; public float Feedback { get { return feedback; } set { feedback = value; } } public AllPassFilter(int bufferSize) { buffer = new float[bufferSize]; bufferIndex = 0; feedback = 0f; } public void Mute() { Array.Clear(buffer, 0, buffer.Length); } public void Process(float[] block) { int num; for (int i = 0; i < block.Length; i += num) { if (bufferIndex == buffer.Length) { bufferIndex = 0; } int val = buffer.Length - bufferIndex; int val2 = block.Length - i; num = Math.Min(val, val2); for (int j = 0; j < num; j++) { int num2 = i + j; int num3 = bufferIndex + j; float num4 = block[num2]; float num5 = buffer[num3]; if (MathF.Abs(num5) < 1E-06f) { num5 = 0f; } block[num2] = num5 - num4; buffer[num3] = num4 + num5 * feedback; } bufferIndex += num; } } } private const float fixedGain = 0.015f; private const float scaleWet = 3f; private const float scaleDamp = 0.4f; private const float scaleRoom = 0.28f; private const float offsetRoom = 0.7f; private const float initialRoom = 0.5f; private const float initialDamp = 0.5f; private const float initialWet = 1f / 3f; private const float initialWidth = 1f; private const int stereoSpread = 23; private const int cfTuningL1 = 1116; private const int cfTuningR1 = 1139; private const int cfTuningL2 = 1188; private const int cfTuningR2 = 1211; private const int cfTuningL3 = 1277; private const int cfTuningR3 = 1300; private const int cfTuningL4 = 1356; private const int cfTuningR4 = 1379; private const int cfTuningL5 = 1422; private const int cfTuningR5 = 1445; private const int cfTuningL6 = 1491; private const int cfTuningR6 = 1514; private const int cfTuningL7 = 1557; private const int cfTuningR7 = 1580; private const int cfTuningL8 = 1617; private const int cfTuningR8 = 1640; private const int apfTuningL1 = 556; private const int apfTuningR1 = 579; private const int apfTuningL2 = 441; private const int apfTuningR2 = 464; private const int apfTuningL3 = 341; private const int apfTuningR3 = 364; private const int apfTuningL4 = 225; private const int apfTuningR4 = 248; private readonly CombFilter[] cfsL; private readonly CombFilter[] cfsR; private readonly AllPassFilter[] apfsL; private readonly AllPassFilter[] apfsR; private float gain; private float roomSize; private float roomSize1; private float damp; private float damp1; private float wet; private float wet1; private float wet2; private float width; public float InputGain => gain; public float RoomSize { get { return (roomSize - 0.7f) / 0.28f; } set { roomSize = value * 0.28f + 0.7f; Update(); } } public float Damp { get { return damp / 0.4f; } set { damp = value * 0.4f; Update(); } } public float Wet { get { return wet / 3f; } set { wet = value * 3f; Update(); } } public float Width { get { return width; } set { width = value; Update(); } } internal Reverb(int sampleRate) { cfsL = new CombFilter[8] { new CombFilter(ScaleTuning(sampleRate, 1116)), new CombFilter(ScaleTuning(sampleRate, 1188)), new CombFilter(ScaleTuning(sampleRate, 1277)), new CombFilter(ScaleTuning(sampleRate, 1356)), new CombFilter(ScaleTuning(sampleRate, 1422)), new CombFilter(ScaleTuning(sampleRate, 1491)), new CombFilter(ScaleTuning(sampleRate, 1557)), new CombFilter(ScaleTuning(sampleRate, 1617)) }; cfsR = new CombFilter[8] { new CombFilter(ScaleTuning(sampleRate, 1139)), new CombFilter(ScaleTuning(sampleRate, 1211)), new CombFilter(ScaleTuning(sampleRate, 1300)), new CombFilter(ScaleTuning(sampleRate, 1379)), new CombFilter(ScaleTuning(sampleRate, 1445)), new CombFilter(ScaleTuning(sampleRate, 1514)), new CombFilter(ScaleTuning(sampleRate, 1580)), new CombFilter(ScaleTuning(sampleRate, 1640)) }; apfsL = new AllPassFilter[4] { new AllPassFilter(ScaleTuning(sampleRate, 556)), new AllPassFilter(ScaleTuning(sampleRate, 441)), new AllPassFilter(ScaleTuning(sampleRate, 341)), new AllPassFilter(ScaleTuning(sampleRate, 225)) }; apfsR = new AllPassFilter[4] { new AllPassFilter(ScaleTuning(sampleRate, 579)), new AllPassFilter(ScaleTuning(sampleRate, 464)), new AllPassFilter(ScaleTuning(sampleRate, 364)), new AllPassFilter(ScaleTuning(sampleRate, 248)) }; AllPassFilter[] array = apfsL; for (int i = 0; i < array.Length; i++) { array[i].Feedback = 0.5f; } array = apfsR; for (int i = 0; i < array.Length; i++) { array[i].Feedback = 0.5f; } Wet = 1f / 3f; RoomSize = 0.5f; Damp = 0.5f; Width = 1f; } private int ScaleTuning(int sampleRate, int tuning) { return (int)Math.Round((double)sampleRate / 44100.0 * (double)tuning); } public void Process(float[] input, float[] outputLeft, float[] outputRight) { Array.Clear(outputLeft, 0, outputLeft.Length); Array.Clear(outputRight, 0, outputRight.Length); CombFilter[] array = cfsL; for (int i = 0; i < array.Length; i++) { array[i].Process(input, outputLeft); } AllPassFilter[] array2 = apfsL; for (int i = 0; i < array2.Length; i++) { array2[i].Process(outputLeft); } array = cfsR; for (int i = 0; i < array.Length; i++) { array[i].Process(input, outputRight); } array2 = apfsR; for (int i = 0; i < array2.Length; i++) { array2[i].Process(outputRight); } if ((double)(1f - wet1) > 0.001 || (double)wet2 > 0.001) { for (int j = 0; j < input.Length; j++) { float num = outputLeft[j]; float num2 = outputRight[j]; outputLeft[j] = num * wet1 + num2 * wet2; outputRight[j] = num2 * wet1 + num * wet2; } } } public void Mute() { CombFilter[] array = cfsL; for (int i = 0; i < array.Length; i++) { array[i].Mute(); } array = cfsR; for (int i = 0; i < array.Length; i++) { array[i].Mute(); } AllPassFilter[] array2 = apfsL; for (int i = 0; i < array2.Length; i++) { array2[i].Mute(); } array2 = apfsR; for (int i = 0; i < array2.Length; i++) { array2[i].Mute(); } } private void Update() { wet1 = wet * (width / 2f + 0.5f); wet2 = wet * ((1f - width) / 2f); roomSize1 = roomSize; damp1 = damp; gain = 0.015f; CombFilter[] array = cfsL; foreach (CombFilter obj in array) { obj.Feedback = roomSize1; obj.Damp = damp1; } array = cfsR; foreach (CombFilter obj2 in array) { obj2.Feedback = roomSize1; obj2.Damp = damp1; } } } public sealed class SampleHeader { internal static readonly SampleHeader Default = new SampleHeader(); private readonly string name; private readonly int start; private readonly int end; private readonly int startLoop; private readonly int endLoop; private readonly int sampleRate; private readonly byte originalPitch; private readonly sbyte pitchCorrection; private readonly ushort link; private readonly SampleType type; public string Name => name; public int Start => start; public int End => end; public int StartLoop => startLoop; public int EndLoop => endLoop; public int SampleRate => sampleRate; public byte OriginalPitch => originalPitch; public sbyte PitchCorrection => pitchCorrection; private SampleHeader() { name = "Default"; } private SampleHeader(BinaryReader reader) { name = reader.ReadFixedLengthString(20); start = reader.ReadInt32(); end = reader.ReadInt32(); startLoop = reader.ReadInt32(); endLoop = reader.ReadInt32(); sampleRate = reader.ReadInt32(); originalPitch = reader.ReadByte(); pitchCorrection = reader.ReadSByte(); link = reader.ReadUInt16(); type = (SampleType)reader.ReadUInt16(); } internal static SampleHeader[] ReadFromChunk(BinaryReader reader, int size) { if (size % 46 != 0) { throw new InvalidDataException("The sample header list is invalid."); } SampleHeader[] array = new SampleHeader[size / 46 - 1]; for (int i = 0; i < array.Length; i++) { array[i] = new SampleHeader(reader); } new SampleHeader(reader); return array; } public override string ToString() { return name; } } internal enum SampleType { Mono = 1, Right = 2, Left = 4, Linked = 8, RomMono = 32769, RomRight = 32770, RomLeft = 32772, RomLinked = 32776 } public sealed class SoundFont { private SoundFontInfo info; private int bitsPerSample; private short[] waveData; private SampleHeader[] sampleHeaders; private Preset[] presets; private Instrument[] instruments; public SoundFontInfo Info => info; public int BitsPerSample => bitsPerSample; public ReadOnlySpan<short> WaveData => waveData; public IReadOnlyList<SampleHeader> SampleHeaders => sampleHeaders; public IReadOnlyList<Preset> Presets => presets; public IReadOnlyList<Instrument> Instruments => instruments; internal short[] WaveDataArray => waveData; internal SampleHeader[] SampleHeaderArray => sampleHeaders; internal Preset[] PresetArray => presets; internal Instrument[] InstrumentArray => instruments; public SoundFont(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } Load(stream); } public SoundFont(string path) { if (path == null) { throw new ArgumentNullException("path"); } using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read); Load(stream); } private void Load(Stream stream) { using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.ASCII, leaveOpen: true)) { if (binaryReader.ReadFourCC() != "RIFF") { throw new InvalidDataException("The RIFF chunk was not found."); } binaryReader.ReadInt32(); string text = binaryReader.ReadFourCC(); if (text != "sfbk") { throw new InvalidDataException("The type of the RIFF chunk must be 'sfbk', but was '" + text + "'."); } info = new SoundFontInfo(binaryReader); SoundFontSampleData soundFontSampleData = new SoundFontSampleData(binaryReader); bitsPerSample = soundFontSampleData.BitsPerSample; waveData = soundFontSampleData.Samples; SoundFontParameters soundFontParameters = new SoundFontParameters(binaryReader); sampleHeaders = soundFontParameters.SampleHeaders; presets = soundFontParameters.Presets; instruments = soundFontParameters.Instruments; } CheckSamples(); CheckRegions(); } public override string ToString() { return info.BankName; } private void CheckSamples() { int num = waveData.Length - 4; SampleHeader[] array = sampleHeaders; foreach (SampleHeader sampleHeader in array) { if (0 > sampleHeader.Start || sampleHeader.Start >= num) { throw new InvalidDataException("The start position of the sample '" + sampleHeader.Name + "' is out of range."); } if (0 > sampleHeader.StartLoop || sampleHeader.StartLoop >= num) { throw new InvalidDataException("The loop start position of the sample '" + sampleHeader.Name + "' is out of range."); } if (0 >= sampleHeader.End || sampleHeader.End > num) { throw new InvalidDataException("The end position of the sample '" + sampleHeader.Name + "' is out of range."); } if (0 > sampleHeader.EndLoop || sampleHeader.EndLoop > num) { throw new InvalidDataException("The loop end position of the sample '" + sampleHeader.Name + "' is out of range."); } } } private void CheckRegions() { int num = waveData.Length - 4; Instrument[] array = instruments; foreach (Instrument instrument in array) { InstrumentRegion[] regionArray = instrument.RegionArray; foreach (InstrumentRegion instrumentRegion in regionArray) { if (0 > instrumentRegion.SampleStart || instrumentRegion.SampleStart >= num) { throw new InvalidDataException("The start position of the sample '" + instrumentRegion.Sample.Name + "' in the instrument '" + instrument.Name + "' is out of range."); } if (0 > instrumentRegion.SampleStartLoop || instrumentRegion.SampleStartLoop >= num) { throw new InvalidDataException("The loop start position of the sample '" + instrumentRegion.Sample.Name + "' in the instrument '" + instrument.Name + "' is out of range."); } if (0 >= instrumentRegion.SampleEnd || instrumentRegion.SampleEnd > num) { throw new InvalidDataException("The end position of the sample '" + instrumentRegion.Sample.Name + "' in the instrument '" + instrument.Name + "' is out of range."); } if (0 > instrumentRegion.SampleEndLoop || instrumentRegion.SampleEndLoop > num) { throw new InvalidDataException("The loop end position of the sample '" + instrumentRegion.Sample.Name + "' in the instrument '" + instrument.Name + "' is out of range."); } LoopMode sampleModes = instrumentRegion.SampleModes; if ((uint)sampleModes > 1u && sampleModes != LoopMode.LoopUntilNoteOff) { throw new InvalidDataException("The sample '" + instrumentRegion.Sample.Name + "' in the instrument '" + instrument.Name + "' has an invalid loop mode."); } } } } } public sealed class SoundFontInfo { private readonly SoundFontVersion version; private readonly string targetSoundEngine = string.Empty; private readonly string bankName = string.Empty; private readonly string romName = string.Empty; private readonly SoundFontVersion romVersion; private readonly string creationDate = string.Empty; private readonly string author = string.Empty; private readonly string targetProduct = string.Empty; private readonly string copyright = string.Empty; private readonly string comments = string.Empty; private readonly string tools = string.Empty; public SoundFontVersion Version => version; public string TargetSoundEngine => targetSoundEngine; public string BankName => bankName; public string RomName => romName; public SoundFontVersion RomVersion => romVersion; public string CeationDate => creationDate; public string Author => author; public string TargetProduct => targetProduct; public string Copyright => copyright; public string Comments => comments; public string Tools => tools; internal SoundFontInfo(BinaryReader reader) { if (reader.ReadFourCC() != "LIST") { throw new InvalidDataException("The LIST chunk was not found."); } long num = reader.ReadInt32(); num += reader.BaseStream.Position; string text = reader.ReadFourCC(); if (text != "INFO") { throw new InvalidDataException("The type of the LIST chunk must be 'INFO', but was '" + text + "'."); } while (reader.BaseStream.Position < num) { string text2 = reader.ReadFourCC(); int length = reader.ReadInt32(); switch (text2) { case "ifil": version = new SoundFontVersion(reader.ReadInt16(), reader.ReadInt16()); break; case "isng": targetSoundEngine = reader.ReadFixedLengthString(length); break; case "INAM": bankName = reader.ReadFixedLengthString(length); break; case "irom": romName = reader.ReadFixedLengthString(length); break; case "iver": romVersion = new SoundFontVersion(reader.ReadInt16(), reader.ReadInt16()); break; case "ICRD": creationDate = reader.ReadFixedLengthString(length); break; case "IENG": author = reader.ReadFixedLengthString(length); break; case "IPRD": targetProduct = reader.ReadFixedLengthString(length); break; case "ICOP": copyright = reader.ReadFixedLengthString(length); break; case "ICMT": comments = reader.ReadFixedLengthString(length); break; case "ISFT": tools = reader.ReadFixedLengthString(length); break; default: throw new InvalidDataException("The INFO list contains an unknown ID '" + text2 + "'."); } } } public override string ToString() { return bankName; } } internal static class SoundFontMath { public const float HalfPi = MathF.PI / 2f; public static readonly float NonAudible = 0.001f; private static readonly double logNonAudible = Math.Log(0.001); public static float TimecentsToSeconds(float x) { return MathF.Pow(2f, 0.00083333335f * x); } public static float CentsToHertz(float x) { return 8.176f * MathF.Pow(2f, 0.00083333335f * x); } public static float CentsToMultiplyingFactor(float x) { return MathF.Pow(2f, 0.00083333335f * x); } public static float DecibelsToLinear(float x) { return MathF.Pow(10f, 0.05f * x); } public static float LinearToDecibels(float x) { return 20f * MathF.Log10(x); } public static float KeyNumberToMultiplyingFactor(int cents, int key) { return TimecentsToSeconds(cents * (60 - key)); } public static double ExpCutoff(double x) { if (x < logNonAudible) { return 0.0; } return Math.Exp(x); } } internal sealed class SoundFontParameters { private readonly SampleHeader[] sampleHeaders; private readonly Preset[] presets; private readonly Instrument[] instruments; public SampleHeader[] SampleHeaders => sampleHeaders; public Preset[] Presets => presets; public Instrument[] Instruments => instruments; internal SoundFontParameters(BinaryReader reader) { if (reader.ReadFourCC() != "LIST") { throw new InvalidDataException("The LIST chunk was not found."); } long num = reader.ReadInt32(); num += reader.BaseStream.Position; string text = reader.ReadFourCC(); if (text != "pdta") { throw new InvalidDataException("The type of the LIST chunk must be 'pdta', but was '" + text + "'."); } PresetInfo[] array = null; ZoneInfo[] array2 = null; Generator[] array3 = null; InstrumentInfo[] array4 = null; ZoneInfo[] array5 = null; Generator[] array6 = null; while (reader.BaseStream.Position < num) { string text2 = reader.ReadFourCC(); int size = reader.ReadInt32(); switch (text2) { case "phdr": array = PresetInfo.ReadFromChunk(reader, size); break; case "pbag": array2 = ZoneInfo.ReadFromChunk(reader, size); break; case "pmod": Modulator.DiscardData(reader, size); break; case "pgen": array3 = Generator.ReadFromChunk(reader, size); break; case "inst": array4 = InstrumentInfo.ReadFromChunk(reader, size); break; case "ibag": array5 = ZoneInfo.ReadFromChunk(reader, size); break; case "imod": Modulator.DiscardData(reader, size); break; case "igen": array6 = Generator.ReadFromChunk(reader, size); break; case "shdr": sampleHeaders = SampleHeader.ReadFromChunk(reader, size); break; default: throw new InvalidDataException("The INFO list contains an unknown ID '" + text2 + "'."); } } if (array == null) { throw new InvalidDataException("The PHDR sub-chunk was not found."); } if (array2 == null) { throw new InvalidDataException("The PBAG sub-chunk was not found."); } if (array3 == null) { throw new InvalidDataException("The PGEN sub-chunk was not found."); } if (array4 == null) { throw new InvalidDataException("The INST sub-chunk was not found."); } if (array5 == null) { throw new InvalidDataException("The IBAG sub-chunk was not found."); } if (array6 == null) { throw new InvalidDataException("The IGEN sub-chunk was not found."); } if (sampleHeaders == null) { throw new InvalidDataException("The SHDR sub-chunk was not found."); } Zone[] zones = Zone.Create(array5, array6); instruments = Instrument.Create(array4, zones, sampleHeaders); Zone[] zones2 = Zone.Create(array2, array3); presets = Preset.Create(array, zones2, instruments); } } internal sealed class SoundFontSampleData { private readonly int bitsPerSample; private readonly short[] samples; public int BitsPerSample => bitsPerSample; public short[] Samples => samples; internal SoundFontSampleData(BinaryReader reader) { if (reader.ReadFourCC() != "LIST") { throw new InvalidDataException("The LIST chunk was not found."); } long num = reader.ReadInt32(); num += reader.BaseStream.Position; string text = reader.ReadFourCC(); if (text != "sdta") { throw new InvalidDataException("The type of the LIST chunk must be 'sdta', but was '" + text + "'."); } while (reader.BaseStream.Position < num) { string text2 = reader.ReadFourCC(); int num2 = reader.ReadInt32(); if (!(text2 == "smpl")) { if (!(text2 == "sm24")) { throw new InvalidDataException("The INFO list contains an unknown ID '" + text2 + "'."); } reader.BaseStream.Position += num2; } else { bitsPerSample = 16; samples = new short[num2 / 2]; reader.Read(MemoryMarshal.Cast<short, byte>(samples)); } } if (samples == null) { throw new InvalidDataException("No valid sample data was found."); } if (Encoding.ASCII.GetString(MemoryMarshal.Cast<short, byte>(samples).Slice(0, 4)) == "OggS") { throw new NotSupportedException("SoundFont3 is not yet supported."); } if (!BitConverter.IsLittleEndian) { throw new NotSupportedException("Big endian architectures are not yet supported."); } } } public struct SoundFontVersion { private readonly short major; private readonly short minor; public short Major => major; public short Minor => minor; internal SoundFontVersion(short major, short minor) { this.major = major; this.minor = minor; } public override string ToString() { return $"{major}.{minor}"; } } public sealed class Synthesizer : IAudioRenderer { private static readonly int channelCount = 16; private static readonly int percussionChannel = 9; private readonly SoundFont soundFont; private readonly int sampleRate; private readonly int blockSize; private readonly int maximumPolyphony; private readonly bool enableReverbAndChorus; private readonly int minimumVoiceDuration; private readonly Dictionary<int, Preset> presetLookup; private readonly Preset defaultPreset; private readonly Channel[] channels; private readonly VoiceCollection voices; private readonly float[] blockLeft; private readonly float[] blockRight; private readonly float inverseBlockSize; private int blockRead; private float masterVolume; private Reverb? reverb; private float[]? reverbInput; private float[]? reverbOutputLeft; private float[]? reverbOutputRight; private Chorus? chorus; private float[]? chorusInputLeft; private float[]? chorusInputRight; private float[]? chorusOutputLeft; private float[]? chorusOutputRight; public int BlockSize => blockSize; public int MaximumPolyphony => maximumPolyphony; public int ChannelCount => channelCount; public int PercussionChannel => percussionChannel; public SoundFont SoundFont => soundFont; public int SampleRate => sampleRate; public int ActiveVoiceCount => voices.ActiveVoiceCount; public float MasterVolume { get { return masterVolume; } set { masterVolume = value; } } internal int MinimumVoiceDuration => minimumVoiceDuration; internal Channel[] Channels => channels; public Synthesizer(string soundFontPath, int sampleRate) : this(new SoundFont(soundFontPath), new SynthesizerSettings(sampleRate)) { } public Synthesizer(SoundFont soundFont, int sampleRate) : this(soundFont, new SynthesizerSettings(sampleRate)) { } public Synthesizer(string soundFontPath, SynthesizerSettings settings) : this(new SoundFont(soundFontPath), settings) { } public Synthesizer(SoundFont soundFont, SynthesizerSettings settings) { if (soundFont == null) { throw new ArgumentNullException("soundFont"); } if (settings == null) { throw new ArgumentNullException("settings"); } this.soundFont = soundFont; sampleRate = settings.SampleRate; blockSize = settings.BlockSize; maximumPolyphony = settings.MaximumPolyphony; enableReverbAndChorus = settings.EnableReverbAndChorus; minimumVoiceDuration = sampleRate / 500; presetLookup = new Dictionary<int, Preset>(); int num = int.MaxValue; Preset[] presetArray = soundFont.PresetArray; foreach (Preset preset in presetArray) { int num2 = (preset.BankNumber << 16) | preset.PatchNumber; presetLookup.TryAdd(num2, preset); if (num2 < num) { defaultPreset = preset; num = num2; } } channels = new Channel[channelCount]; for (int j = 0; j < channels.Length; j++) { channels[j] = new Channel(this, j == percussionChannel); } voices = new VoiceCollection(this, maximumPolyphony); blockLeft = new float[blockSize]; blockRight = new float[blockSize]; inverseBlockSize = 1f / (float)blockSize; blockRead = blockSize; masterVolume = 0.5f; if (enableReverbAndChorus) { reverb = new Reverb(sampleRate); reverbInput = new float[blockSize]; reverbOutputLeft = new float[blockSize]; reverbOutputRight = new float[blockSize]; chorus = new Chorus(sampleRate, 0.002, 0.0019, 0.4); chorusInputLeft = new float[blockSize]; chorusInputRight = new float[blockSize]; chorusOutputLeft = new float[blockSize]; chorusOutputRight = new float[blockSize]; } } public void ProcessMidiMessage(int channel, int command, int data1, int data2) { if (0 > channel || channel >= channels.Length) { return; } Channel channel2 = channels[channel]; switch (command) { case 128: NoteOff(channel, data1); break; case 144: NoteOn(channel, data1, data2); break; case 176: switch (data1) { case 0: channel2.SetBank(data2); break; case 1: channel2.SetModulationCoarse(data2); break; case 33: channel2.SetModulationFine(data2); break; case 6: channel2.DataEntryCoarse(data2); break; case 38: channel2.DataEntryFine(data2); break; case 7: channel2.SetVolumeCoarse(data2); break; case 39: channel2.SetVolumeFine(data2); break; case 10: channel2.SetPanCoarse(data2); break; case 42: channel2.SetPanFine(data2); break; case 11: channel2.SetExpressionCoarse(data2); break; case 43: channel2.SetExpressionFine(data2); break; case 64: channel2.SetHoldPedal(data2); break; case 91: channel2.SetReverbSend(data2); break; case 93: channel2.SetChorusSend(data2); break; case 101: channel2.SetRpnCoarse(data2); break; case 100: channel2.SetRpnFine(data2); break; case 120: NoteOffAll(channel, immediate: true); break; case 121: ResetAllControllers(channel); break; case 123: NoteOffAll(channel, immediate: false); break; } break; case 192: channel2.SetPatch(data1); break; case 224: channel2.SetPitchBend(data1, data2); break; } } public void NoteOff(int channel, int key) { if (0 > channel || channel >= channels.Length) { return; } foreach (Voice voice in voices) { if (voice.Channel == channel && voice.Key == key) { voice.End(); } } } public void NoteOn(int channel, int key, int velocity) { if (velocity == 0) { NoteOff(channel, key); } else { if (0 > channel || channel >= channels.Length) { return; } Channel channel2 = channels[channel]; int key2 = (channel2.BankNumber << 16) | channel2.PatchNumber; if (!presetLookup.TryGetValue(key2, out Preset value)) { int key3 = ((channel2.BankNumber < 128) ? channel2.PatchNumber : 8388608); if (!presetLookup.TryGetValue(key3, out value)) { value = defaultPreset; } } PresetRegion[] regionArray = value.RegionArray; foreach (PresetRegion presetRegion in regionArray) { if (!presetRegion.Contains(key, velocity)) { continue; } InstrumentRegion[] regionArray2 = presetRegion.Instrument.RegionArray; foreach (InstrumentRegion instrumentRegion in regionArray2) { if (instrumentRegion.Contains(key, velocity)) { RegionPair region = new RegionPair(presetRegion, instrumentRegion); voices.RequestNew(instrumentRegion, channel)?.Start(region, channel, key, velocity); } } } } } public void NoteOffAll(bool immediate) { if (immediate) { voices.Clear(); return; } foreach (Voice voice in voices) { voice.End(); } } public void NoteOffAll(int channel, bool immediate) { if (immediate) { foreach (Voice voice in voices) { if (voice.Channel == channel) { voice.Kill(); } } return; } foreach (Voice voice2 in voices) { if (voice2.Channel == channel) { voice2.End(); } } } public void ResetAllControllers() { Channel[] array = channels; for (int i = 0; i < array.Length; i++) { array[i].ResetAllControllers(); } } public void ResetAllControllers(int channel) { if (0 <= channel && channel < channels.Length) { channels[channel].ResetAllControllers(); } } public void Reset() { voices.Clear(); Channel[] array = channels; for (int i = 0; i < array.Length; i++) { array[i].Reset(); } if (enableReverbAndChorus) { reverb.Mute(); chorus.Mute(); } blockRead = blockSize; } public void Render(Span<float> left, Span<float> right) { if (left.Length != right.Length) { throw new ArgumentException("The output buffers for the left and right must be the same length."); } int num; for (int i = 0; i < left.Length; i += num) { if (blockRead == blockSize) { RenderBlock(); blockRead = 0; } int val = blockSize - blockRead; int val2 = left.Length - i; num = Math.Min(val, val2); blockLeft.AsSpan(blockRead, num).CopyTo(left.Slice(i, num)); blockRight.AsSpan(blockRead, num).CopyTo(right.Slice(i, num)); blockRead += num; } } private void RenderBlock() { voices.Process(); Array.Clear(blockLeft, 0, blockLeft.Length); Array.Clear(blockRight, 0, blockRight.Length); foreach (Voice voice in voices) { float previousGain = masterVolume * voice.PreviousMixGainLeft; float currentGain = masterVolume * voice.CurrentMixGainLeft; WriteBlock(previousGain, currentGain, voice.Block, blockLeft); float previousGain2 = masterVolume * voice.PreviousMixGainRight; float currentGain2 = masterVolume * voice.CurrentMixGainRight; WriteBlock(previousGain2, currentGain2, voice.Block, blockRight); } if (!enableReverbAndChorus) { return; } Array.Clear(chorusInputLeft, 0, chorusInputLeft.Length); Array.Clear(chorusInputRight, 0, chorusInputRight.Length); foreach (Voice voice2 in voices) { float previousGain3 = voice2.PreviousChorusSend * voice2.PreviousMixGainLeft; float currentGain3 = voice2.CurrentChorusSend * voice2.CurrentMixGainLeft; WriteBlock(previousGain3, currentGain3, voice2.Block, chorusInputLeft); float previousGain4 = voice2.PreviousChorusSend * voice2.PreviousMixGainRight; float currentGain4 = voice2.CurrentChorusSend * voice2.CurrentMixGainRight; WriteBlock(previousGain4, currentGain4, voice2.Block, chorusInputRight); } chorus.Process(chorusInputLeft, chorusInputRight, chorusOutputLeft, chorusOutputRight); ArrayMath.MultiplyAdd(masterVolume, chorusOutputLeft, blockLeft); ArrayMath.MultiplyAdd(masterVolume, chorusOutputRight, blockRight); Array.Clear(reverbInput, 0, reverbInput.Length); foreach (Voice voice3 in voices) { float previousGain5 = reverb.InputGain * voice3.PreviousReverbSend * (voice3.PreviousMixGainLeft + voice3.PreviousMixGainRight); float currentGain5 = reverb.InputGain * voice3.CurrentReverbSend * (voice3.CurrentMixGainLeft + voice3.CurrentMixGainRight); WriteBlock(previousGain5, currentGain5, voice3.Block, reverbInput); } reverb.Process(reverbInput, reverbOutputLeft, reverbOutputRight); ArrayMath.MultiplyAdd(masterVolume, reverbOutputLeft, blockLeft); ArrayMath.MultiplyAdd(masterVolume, reverbOutputRight, blockRight); } private void WriteBlock(float previousGain, float currentGain, float[] source, float[] destination) { if (!(Math.Max(previousGain, currentGain) < SoundFontMath.NonAudible)) { if ((double)MathF.Abs(currentGain - previousGain) < 0.001) { ArrayMath.M
plugins/DoomForCrispberry.dll
Decompiled 18 hours 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.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using DoomForCrispberry.Engine; using DoomForCrispberry.Interop; using DoomForCrispberry.Music; using ManagedDoom; using ManagedDoom.Audio; using ManagedDoom.UserInput; using ManagedDoom.Video; using MeltySynth; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("DoomForCrispberry")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+4cb4b83a9fbd86a7fb558182bb03b9aa0d0b53c4")] [assembly: AssemblyProduct("DoomForCrispberry")] [assembly: AssemblyTitle("Doom for Crispberry")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DoomForCrispberry { public sealed class DoomApp { private const string AppId = "onlystar.doom"; private const int RenderWidth = 640; private const int RenderHeight = 400; private const int RowEngine = 0; private const int RowGameData = 1; private const int RowInput = 2; private const int RowMusic = 3; private const int RowSfx = 4; private const float RowFloorSeconds = 0.32f; private const float FatalHoldSeconds = 1.1f; private static readonly string[] RowNames = new string[5] { "Engine", "Game data", "Input", "Music", "Sound effects" }; private readonly DoomScreen screen = new DoomScreen(); private readonly MessageScreen message = new MessageScreen(); private readonly PreflightScreen preflight = new PreflightScreen(); private object runnerRef; private object host; private bool running; private bool suspended; private RectTransform content; private int openGeneration; private bool failed; private static DoomApp active; private static string musicFailure; private bool IsFocused { get { if (!running || runnerRef == null) { return false; } return PiPhoneInterop.IsAppFocused("onlystar.doom"); } } public void Register() { object app = PiPhoneInterop.CreateApp(); PiPhoneInterop.SetField(app, "Id", "onlystar.doom"); PiPhoneInterop.SetField(app, "DisplayName", "DOOM"); PiPhoneInterop.SetField(app, "IconGlyph", "extension"); PiPhoneInterop.SetField(app, "Category", "Games"); PiPhoneInterop.SetField(app, "Description", "The 1993 classic, running on your phone. Requires your own WAD file."); PiPhoneInterop.SetField(app, "Landscape", true); PiPhoneInterop.SetField(app, "Fullscreen", true); PiPhoneInterop.SetField(app, "Immersive", true); PiPhoneInterop.SetField(app, "CoversWallpaper", true); PiPhoneInterop.SetField(app, "ApplyPhoneFonts", false); PiPhoneInterop.SetField(app, "AllowPlayThrough", false); TrySetIcon(app); PiPhoneInterop.SetOnOpen(app, OnOpen); PiPhoneInterop.SetField(app, "OnClose", new Action(OnClose)); PiPhoneInterop.RegisterApp(app); } private static void TrySetIcon(object app) { string text = Plugin.ResolveIconPath(); if (string.IsNullOrEmpty(text)) { Plugin.Log.LogInfo((object)"No icon.png found; falling back to glyph icon."); return; } try { if (PiPhoneInterop.SetIconFile(app, text)) { Plugin.Log.LogInfo((object)("App icon loaded from " + text)); } else { Plugin.Log.LogWarning((object)("PiPhone rejected icon " + text + "; falling back to glyph.")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Icon load failed (" + ex.Message + "); falling back to glyph.")); } } private void OnOpen(object hostObj) { host = hostObj; openGeneration++; try { ref RectTransform reference = ref content; object obj = PiPhoneInterop.HostProperty(host, "Content"); reference = (RectTransform)((obj is RectTransform) ? obj : null); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not read host Content: " + ex)); return; } SetTitle("DOOM"); if (suspended && runnerRef != null) { Resume(); return; } MusicStatus.Clear(); musicFailure = null; StartPreflight(Plugin.ResolveWadPath()); } private void Resume() { suspended = false; DoomRunner doomRunner = (DoomRunner)runnerRef; screen.Build(content, doomRunner.RenderWidth, doomRunner.RenderHeight); screen.Poll(); try { doomRunner.Render(); screen.Blit(doomRunner.Buffer); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Rendering the resumed frame failed: " + ex.Message)); } doomRunner.Resume(); EnableKeys(); running = true; active = this; StartLoop(); } private void ShowNoGameData() { preflight.Destroy(); message.Show(content, "No game data", "DOOM needs a WAD file to run.\n\nDo one of the following:\n1. Reinstall \"DOOM for Crispberry\" from Thunderstore.\n2. Get FREEDOOM1.WAD from freedoom.github.io.\n3. Get DOOM.WAD from your Steam copy, or search for \"Ultimate Doom WAD\".\n\nOnce you have DOOM.WAD or FREEDOOM1.WAD, place it in the mod's data folder:", Plugin.ModDirectory, new MessageAction[1] { new MessageAction("Open Data Folder", delegate { OpenFolder(Plugin.ModDirectory); }) }); } private static void OpenFolder(string path) { try { Directory.CreateDirectory(path); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not create " + path + " (" + ex.Message + ").")); } Application.OpenURL("file://" + path.Replace('\\', '/')); } private void EnableKeys() { SetKeys(value: true); } private void DisableKeys() { SetKeys(value: false); } private void SetKeys(bool value) { if (runnerRef != null) { ApplyKeys(runnerRef, value); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void ApplyKeys(object runner, bool value) { ((DoomRunner)runner).KeysEnabled = value; } private void StartLoop() { try { PiPhoneInterop.HostInvoke(host, "StartHostCoroutine", Loop()); } catch (Exception ex) { Plugin.Log.LogError((object)("StartHostCoroutine failed: " + ex)); running = false; } } private void StartPreflight(string wad) { failed = false; preflight.Show(content, "Launching Doom...", RowNames); int generation = openGeneration; if (!Plugin.EnginePresent) { StartCoroutine(NoEngine(generation), "NoEngine"); } else { StartCoroutine(Preflight(wad, generation), "Preflight"); } } private void StartCoroutine(IEnumerator routine, string name) { try { PiPhoneInterop.HostInvoke(host, "StartHostCoroutine", routine); } catch (Exception ex) { Plugin.Log.LogError((object)("StartHostCoroutine failed for " + name + ": " + ex)); preflight.Destroy(); running = false; } } private IEnumerator NoEngine(int generation) { yield return FailRow(0); if (!PreflightAborted(generation)) { ShowNoGameEngine(); } } private bool PreflightAborted(int generation) { if (generation == openGeneration && host != null) { return (Object)(object)content == (Object)null; } return true; } private IEnumerator Preflight(string wad, int generation) { yield return PassRow(0, null); if (PreflightAborted(generation)) { yield break; } if (string.IsNullOrEmpty(wad)) { yield return FailRow(1); if (!PreflightAborted(generation)) { ShowNoGameData(); Plugin.Log.LogWarning((object)("No WAD found; configured path: " + Plugin.WadPath.Value)); } } else { yield return RunEngine(wad, generation); } } private IEnumerator RunEngine(string wad, int generation) { Plugin.EnsureDoomDataDirectory(); Plugin.EnsureSavesDirectory(); DoomRunner pending = new DoomRunner(); pending.Begin(wad, 640, 400); pending.KeysEnabled = true; for (int phase = 0; phase < 5; phase++) { float realtimeSinceStartup = Time.realtimeSinceStartup; Exception failure = null; try { pending.RunPhase(); } catch (Exception ex) { failure = ex; } if (PreflightAborted(generation)) { DisposePartial(pending); yield break; } if (failure != null) { yield return FailRow(1); DisposePartial(pending); if (!PreflightAborted(generation)) { FailToCorruptWad(wad, failure); } yield break; } CommitPhase(phase, pending); float num = Time.realtimeSinceStartup - realtimeSinceStartup; if (num < 0.32f) { yield return (object)new WaitForSecondsRealtime(0.32f - num); } if (PreflightAborted(generation)) { DisposePartial(pending); yield break; } } pending.Finish(); runnerRef = pending; if (musicFailure != null) { yield return (object)new WaitForSecondsRealtime(1.1f); if (PreflightAborted(generation)) { DisposeSession(); } else { ShowMusicFailure(wad); } yield break; } if (!failed) { yield return (object)new WaitForSecondsRealtime(0.25f); if (PreflightAborted(generation)) { DisposeSession(); } else { EnterGame(wad); } yield break; } preflight.ShowAction(new MessageAction("CONTINUE ANYWAY", delegate { if (!PreflightAborted(generation)) { EnterGame(wad); } })); } private IEnumerator PassRow(int row, string label) { if (label != null) { preflight.SetLabel(row, label); } preflight.Set(row, PreflightState.Ok); yield return (object)new WaitForSecondsRealtime(0.32f); } private IEnumerator FailRow(int row) { preflight.Set(row, PreflightState.Fail); yield return (object)new WaitForSecondsRealtime(1.1f); } private void CommitPhase(int phase, object pendingRef) { DoomRunner doomRunner = (DoomRunner)pendingRef; switch (phase) { case 1: preflight.SetLabel(1, "Game data: " + doomRunner.VersionLabel()); preflight.Set(1, PreflightState.Ok); break; case 2: CommitSfx(doomRunner); break; case 3: CommitMusic(); break; case 4: CommitInput(doomRunner); break; case 0: break; } } private void CommitSfx(object pendingRef) { if (((DoomRunner)pendingRef).SfxCounts(out var loaded, out var total) && loaded == 0) { failed = true; preflight.Set(4, PreflightState.Fail); } else { preflight.SetLabel(4, "Sound effects: " + loaded + "/" + total); preflight.Set(4, PreflightState.Ok); } } private void CommitMusic() { string unavailable = MusicStatus.Unavailable; if (unavailable != null) { failed = true; musicFailure = unavailable; preflight.Set(3, PreflightState.Fail); } else { preflight.Set(3, PreflightState.Ok); } } private static string MusicMissingClause() { bool flag = string.IsNullOrEmpty(Plugin.ResolveSoundFontPath()); bool flag2 = !Plugin.SynthPresent; if (flag && flag2) { return "of which both TimGM6mb.sf2 (soundfont) and MeltySynth.dll (synth) are missing"; } if (flag) { return "of which TimGM6mb.sf2 (soundfont) is missing"; } if (flag2) { return "of which MeltySynth.dll (synth) is missing"; } return null; } private void ShowMusicFailure(string wad) { preflight.Destroy(); string text = MusicMissingClause(); string body = ((text != null) ? ("DOOM needs a soundfont and a synth to play music, " + text + ".\n\nI suggest reinstalling the DOOM for Crispberry mod.\n\nIf you want to play anyway, continue below.") : "DOOM needs a soundfont and a synth to play music, and they could not be initialized.\n\nIf you want to play anyway, continue below."); message.Show(content, "Music could not be initialized", body, (text != null) ? null : musicFailure, null, new MessageAction[1] { new MessageAction("Continue Without Music", delegate { EnterGame(wad); }) }); Plugin.Log.LogWarning((object)("Music unavailable: " + musicFailure)); } private void ShowNoGameEngine() { preflight.Destroy(); message.Show(content, "No game engine", "ManagedDoom was not found.\n\nReinstall the DOOM for Crispberry mod, or provide it manually in the mod's data/lib folder:", Plugin.LibDirectory, new MessageAction[1] { new MessageAction("Open Lib Folder", delegate { OpenFolder(Plugin.LibDirectory); }) }); Plugin.Log.LogError((object)("ManagedDoom.Core.dll not found in " + Plugin.LibDirectory)); } private void CommitInput(object pendingRef) { DoomRunner doomRunner = (DoomRunner)pendingRef; if (PiPhoneInterop.IsAppFocused("onlystar.doom") && doomRunner.KeysEnabled) { preflight.Set(2, PreflightState.Ok); return; } failed = true; preflight.Set(2, PreflightState.Fail); } private void EnterGame(string wad) { preflight.Destroy(); message.Destroy(); screen.Build(content, ((DoomRunner)runnerRef).RenderWidth, ((DoomRunner)runnerRef).RenderHeight); EnableKeys(); running = true; active = this; StartLoop(); Plugin.Log.LogInfo((object)("DOOM started from " + wad)); } private void FailToCorruptWad(string wad, Exception e) { preflight.Destroy(); message.Show(content, "Could not load DOOM", "The WAD file could not be read:\n" + wad, e.Message, "The file may be corrupt, incomplete, or not a DOOM IWAD.\n\nI suggest reinstalling DOOM for Crispberry.", null); Plugin.Log.LogError((object)("DoomSession failed: " + e)); runnerRef = null; DisableKeys(); } private void DisposePartial(object pendingRef) { DoomRunner doomRunner = (DoomRunner)pendingRef; preflight.Destroy(); DisableKeys(); try { doomRunner.Dispose(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Disposing a partial DOOM session failed: " + ex.Message)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void DisposeRunner(object runner) { ((DoomRunner)runner).Dispose(); } private void ReleaseRunner() { if (runnerRef == null) { return; } object runner = runnerRef; runnerRef = null; try { DisposeRunner(runner); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Disposing the DOOM runner failed: " + ex.Message)); } } private void DisposeSession() { preflight.Destroy(); DisableKeys(); ReleaseRunner(); } private IEnumerator Loop() { while (running && runnerRef != null) { bool isFocused = IsFocused; ((DoomRunner)runnerRef).KeysEnabled = isFocused; ((DoomRunner)runnerRef).SampleKeys(); if (isFocused) { ((DoomRunner)runnerRef).PumpMenuKeys(); } bool flag; try { flag = ((DoomRunner)runnerRef).Advance(Time.unscaledDeltaTime); } catch (Exception ex) { Plugin.Log.LogError((object)("DOOM tick failed: " + ex)); FailToMessage("DOOM stopped", ex); break; } if (((DoomRunner)runnerRef).Completed) { running = false; suspended = false; active = null; DisableKeys(); ReleaseRunner(); screen.Destroy(); break; } if (flag) { try { screen.Poll(); ((DoomRunner)runnerRef).Render(); screen.Blit(((DoomRunner)runnerRef).Buffer); } catch (Exception ex2) { Plugin.Log.LogError((object)("DOOM render failed: " + ex2)); FailToMessage("DOOM stopped", ex2); break; } } yield return null; } } private void FailToMessage(string title, Exception e) { running = false; suspended = false; active = null; DisableKeys(); screen.Destroy(); ReleaseRunner(); message.Show(content, title, e.Message + "\n\nLeave the app and open it again to restart. The BepInEx log has the full details."); } private void OnClose() { if (running && runnerRef != null) { Suspend(); } else { Teardown(); } } private void Suspend() { DoomRunner doomRunner = (DoomRunner)runnerRef; try { doomRunner.Pause(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Muting DOOM on suspend failed: " + ex.Message)); } suspended = true; running = false; active = null; DisableKeys(); openGeneration++; preflight.Destroy(); message.Destroy(); screen.Destroy(); host = null; content = null; } private void Teardown() { suspended = false; running = false; active = null; DisableKeys(); openGeneration++; preflight.Destroy(); message.Destroy(); screen.Destroy(); ReleaseRunner(); host = null; content = null; } private void SetTitle(string title) { try { PiPhoneInterop.HostInvoke(host, "SetTitle", title); } catch (Exception ex) { Plugin.Log.LogDebug((object)("SetTitle failed: " + ex.Message)); } } } public sealed class DoomRunner : IDisposable { private const int DefaultSoundVolume = 8; private const int DefaultMusicVolume = 6; private readonly UnityKeyState keys = new UnityKeyState(); private DoomSession.Builder builder; private DoomSession session; public int PhaseCount => 5; public bool Completed { get { if (session != null) { return session.Completed; } return false; } } public int RenderWidth => session.Video.RenderWidth; public int RenderHeight => session.Video.RenderHeight; public bool KeysEnabled { get { return keys.Enabled; } set { keys.Enabled = value; } } public byte[] Buffer => session.Video.Buffer; public void SampleKeys() { keys.Sample(); } public void Begin(string wadPath, int width, int height) { builder = DoomSession.Build(wadPath, keys, width, height, (GameContent content) => (ISound)(object)new UnitySound(content, 8), CreateMusic); } public void RunPhase() { builder.RunNext(); } public void Finish() { session = builder.Session; } public string VersionLabel() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected I4, but got Unknown GameContent content = builder.Session.Content; if (content == null || content.Wad == null) { return "UNKNOWN"; } Wad wad = content.Wad; IReadOnlyList<string> names = wad.Names; string text = ((names != null && names.Count > 0) ? names[0] : null); GameMode gameMode = wad.GameMode; switch ((int)gameMode) { case 3: if (!(text == "freedoom1")) { return "RETAIL"; } return "FREEDOOM"; case 1: if (!(text == "freedoom1")) { return "REGISTERED"; } return "FREEDOOM"; case 0: if (!(text == "freedoom1")) { return "SHAREWARE"; } return "FREEDOOM"; case 2: return "DOOM II"; default: return "UNKNOWN"; } } public bool SfxCounts(out int loaded, out int total) { if (!(builder.Session.Sound is UnitySound unitySound)) { loaded = 0; total = 0; return false; } loaded = unitySound.LoadedCount; total = unitySound.TotalCount; return true; } public bool Advance(double deltaSeconds) { return session.Advance(deltaSeconds); } public void Pause() { ISound sound = session.Sound; if (sound != null) { sound.Pause(); } IMusic music = session.Music; if (music != null) { music.Pause(); } } public void Resume() { ISound sound = session.Sound; if (sound != null) { sound.Resume(); } IMusic music = session.Music; if (music != null) { music.Resume(); } } public void Render() { session.Render(); } public void PumpMenuKeys() { //IL_0011: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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) KeyValuePair<KeyCode, DoomKey>[] bindings = UnityKeyState.Bindings; for (int i = 0; i < bindings.Length; i++) { KeyCode key = bindings[i].Key; if (Input.GetKeyDown(key)) { session.PostKeyDown(bindings[i].Value); } else if (Input.GetKeyUp(key)) { session.PostKeyUp(bindings[i].Value); } } } private static IMusic CreateMusic(GameContent content) { if (!Plugin.SynthPresent) { MusicStatus.Unavailable = "Music off: MeltySynth.dll not found."; Plugin.Log.LogWarning((object)"No MeltySynth.dll found; music disabled."); return null; } string text = Plugin.ResolveSoundFontPath(); if (string.IsNullOrEmpty(text)) { MusicStatus.Unavailable = "Music off: no SoundFont found."; Plugin.Log.LogWarning((object)"No .sf2 SoundFont found; music disabled."); return null; } try { return NewUnityMusic(content, text); } catch (Exception ex) { MusicStatus.Unavailable = "Music off: " + ex.Message; Plugin.Log.LogWarning((object)("Music init failed (" + ex.Message + "); continuing without music.")); return null; } } [MethodImpl(MethodImplOptions.NoInlining)] private static IMusic NewUnityMusic(GameContent content, string soundFontPath) { return (IMusic)(object)new UnityMusic(content, soundFontPath, 6); } public void Dispose() { if (builder != null) { builder.Session.Dispose(); } builder = null; session = null; } } public static class MusicStatus { public static string Unavailable { get; set; } public static void Clear() { Unavailable = null; } } [BepInPlugin("onlystar.DoomForCrispberry", "Doom for Crispberry", "1.0.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "onlystar.DoomForCrispberry"; private const int SaveSlotCount = 6; private DoomApp app; private static string savesDirectory; public const string EngineAssembly = "ManagedDoom.Core.dll"; public const string SynthAssembly = "MeltySynth.dll"; private static bool dataDirectoryRedirected; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static ConfigEntry<string> WadPath { get; private set; } public static ConfigEntry<string> SoundFontPath { get; private set; } public static string PluginDirectory => Path.GetDirectoryName(((BaseUnityPlugin)Instance).Info.Location); public static string ModDirectory => Path.Combine(PluginDirectory, "data"); public static string LibDirectory => Path.Combine(ModDirectory, "lib"); public static bool EnginePresent => File.Exists(Path.Combine(LibDirectory, "ManagedDoom.Core.dll")); public static bool SynthPresent => File.Exists(Path.Combine(LibDirectory, "MeltySynth.dll")); private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; string text = Path.Combine(ModDirectory, "DOOM.WAD"); WadPath = ((BaseUnityPlugin)this).Config.Bind<string>("General", "WadPath", text, "Full path to a DOOM IWAD. Leave empty to auto-discover one in the plugin folder, which prefers a commercial WAD over the bundled Freedoom."); SoundFontPath = ((BaseUnityPlugin)this).Config.Bind<string>("Audio", "SoundFontPath", string.Empty, "Optional path to a .sf2 SoundFont for music. Leave empty to use the bundled TimGM6mb.sf2."); if (!PiPhoneInterop.Resolve()) { Log.LogError((object)("Crispberry PiPhone not found - DOOM app not registered. (" + PiPhoneInterop.FailureReason + ")")); return; } try { app = new DoomApp(); app.Register(); Log.LogInfo((object)"Registered DOOM app with Crispberry PiPhone."); } catch (Exception ex) { Log.LogError((object)("Failed to register DOOM app: " + ex)); } } public static void EnsureDoomDataDirectory() { if (!dataDirectoryRedirected) { dataDirectoryRedirected = true; RedirectDoomDataDirectory(); } } private static void RedirectDoomDataDirectory() { string text; try { text = ConfigUtilities.GetProcessDirectory(); } catch (Exception ex) { text = null; Log.LogWarning((object)("Could not resolve the process directory (" + ex.Message + ").")); } savesDirectory = Path.Combine(ModDirectory, "saves"); ConfigUtilities.DataDirectoryOverride = () => savesDirectory; Log.LogInfo((object)("DOOM save data redirected to " + savesDirectory + " (engine default was " + (text ?? "unresolved") + ").")); MigrateStraySaves(text); } private static void MigrateStraySaves(string processDirectory) { if (string.IsNullOrEmpty(processDirectory)) { return; } try { if (!Directory.Exists(processDirectory)) { return; } for (int i = 0; i < 6; i++) { string text = "doomsav" + i + ".dsg"; string text2 = Path.Combine(processDirectory, text); if (File.Exists(text2)) { Directory.CreateDirectory(savesDirectory); string text3 = Path.Combine(savesDirectory, text); if (File.Exists(text3)) { Log.LogWarning((object)("Leaving " + text2 + " in place; " + text + " already exists in the mod folder.")); } else { File.Move(text2, text3); Log.LogInfo((object)("Moved stray DOOM save " + text + " into " + savesDirectory + ".")); } } } } catch (Exception ex) { Log.LogWarning((object)("Could not migrate stray DOOM saves (" + ex.Message + "); leaving them untouched.")); } } public static string EnsureSavesDirectory() { try { Directory.CreateDirectory(savesDirectory); } catch (Exception ex) { Log.LogWarning((object)("Could not create the DOOM saves folder (" + ex.Message + "); saving may fail.")); } return savesDirectory; } public static string ResolveIconPath() { string text = Path.Combine(ModDirectory, "app_icon.png"); if (!File.Exists(text)) { return null; } return text; } public static string ResolveSoundFontPath() { string value = SoundFontPath.Value; if (!string.IsNullOrEmpty(value) && File.Exists(value)) { return value; } string modDirectory = ModDirectory; string text = Path.Combine(modDirectory, "TimGM6mb.sf2"); if (File.Exists(text)) { return text; } if (Directory.Exists(modDirectory)) { string[] files = Directory.GetFiles(modDirectory, "*.sf2", SearchOption.TopDirectoryOnly); if (files.Length != 0) { return files[0]; } } return null; } public static string ResolveWadPath() { string value = WadPath.Value; if (!string.IsNullOrEmpty(value) && File.Exists(value)) { return value; } string modDirectory = ModDirectory; if (Directory.Exists(modDirectory)) { string[] array = new string[7] { "DOOM2.WAD", "DOOM.WAD", "DOOM1.WAD", "FREEDOOM2.WAD", "FREEDOOM1.WAD", "TNT.WAD", "PLUTONIA.WAD" }; foreach (string path in array) { string text = Path.Combine(modDirectory, path); if (File.Exists(text)) { return text; } } string[] files = Directory.GetFiles(modDirectory, "*.wad", SearchOption.TopDirectoryOnly); if (files.Length != 0) { return files[0]; } } return null; } } public sealed class DoomScreen { private Texture2D texture; private RawImage image; private RectTransform holder; private RectTransform inner; private LayoutElement layout; private RectTransform parent; private int srcWidth; private int srcHeight; private Vector2 lastParentSize = new Vector2(-1f, -1f); public void Build(RectTransform parentRect, int renderWidth, int renderHeight) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) parent = parentRect; srcWidth = renderWidth; srcHeight = renderHeight; GameObject val = new GameObject("DoomScreen"); holder = val.AddComponent<RectTransform>(); ((Transform)holder).SetParent((Transform)(object)parent, false); layout = val.AddComponent<LayoutElement>(); layout.flexibleWidth = 1f; layout.flexibleHeight = 1f; GameObject val2 = new GameObject("Surface"); inner = val2.AddComponent<RectTransform>(); ((Transform)inner).SetParent((Transform)(object)holder, false); image = val2.AddComponent<RawImage>(); texture = new Texture2D(renderHeight, renderWidth, (TextureFormat)4, false) { filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1 }; image.texture = (Texture)(object)texture; ((Graphic)image).color = Color.white; ((Graphic)image).raycastTarget = false; inner.anchorMin = new Vector2(0.5f, 0.5f); inner.anchorMax = new Vector2(0.5f, 0.5f); inner.pivot = new Vector2(0.5f, 0.5f); inner.anchoredPosition = Vector2.zero; ((Transform)inner).localRotation = Quaternion.Euler(0f, 0f, -90f); ((Transform)inner).localScale = Vector3.one; Fit(); } public void Fit() { //IL_0023: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: 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_0128: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)holder == (Object)null || (Object)(object)parent == (Object)null) { return; } Rect rect = parent.rect; float width = ((Rect)(ref rect)).width; rect = parent.rect; float height = ((Rect)(ref rect)).height; if (width <= 1f || height <= 1f) { Canvas componentInParent = ((Component)parent).GetComponentInParent<Canvas>(); RectTransform val = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).GetComponent<RectTransform>() : null); if ((Object)(object)val != (Object)null) { rect = val.rect; width = ((Rect)(ref rect)).width; rect = val.rect; height = ((Rect)(ref rect)).height; } } if (!(width <= 1f) && !(height <= 1f)) { float num = (float)srcWidth / (float)srcHeight; float num2 = width; float num3 = num2 / num; if (num3 > height) { num3 = height; num2 = num3 * num; } layout.preferredWidth = num2; layout.preferredHeight = num3; layout.minWidth = num2; layout.minHeight = num3; inner.sizeDelta = new Vector2(num3, num2); lastParentSize = new Vector2(width, height); } } public void Poll() { //IL_0023: 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) if (!((Object)(object)holder == (Object)null) && !((Object)(object)parent == (Object)null)) { Rect rect = parent.rect; if (Mathf.Abs(((Rect)(ref rect)).width - lastParentSize.x) > 0.5f || Mathf.Abs(((Rect)(ref rect)).height - lastParentSize.y) > 0.5f) { Fit(); } } } public void Blit(byte[] buffer) { if (!((Object)(object)texture == (Object)null)) { texture.LoadRawTextureData(buffer); texture.Apply(false); } } public void Destroy() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)holder != (Object)null) { Object.Destroy((Object)(object)((Component)holder).gameObject); holder = null; } if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); texture = null; } inner = null; image = null; layout = null; parent = null; lastParentSize = new Vector2(-1f, -1f); } } public sealed class MessageScreen { private static readonly Color Background = new Color(0.05f, 0.03f, 0.03f, 1f); private static readonly Color TitleColor = new Color(0.85f, 0.15f, 0.1f, 1f); private static readonly Color BodyColor = new Color(0.87f, 0.85f, 0.82f, 1f); private static readonly Color ButtonColor = new Color(0.16f, 0.16f, 0.18f, 1f); private static readonly Color ButtonText = new Color(0.95f, 0.94f, 0.92f, 1f); private static readonly Color DetailColor = new Color(0.93f, 0.62f, 0.2f, 1f); private RectTransform holder; private RectTransform column; private Sprite pixel; public void Show(RectTransform parent, string title, string body) { Show(parent, title, body, null, null); } public void Show(RectTransform parent, string title, string body, IEnumerable<MessageAction> actions) { Show(parent, title, body, null, actions); } public void Show(RectTransform parent, string title, string body, string detail, IEnumerable<MessageAction> actions) { Show(parent, title, body, detail, null, actions); } public void Show(RectTransform parent, string title, string body, string detail, string footer, IEnumerable<MessageAction> actions) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) Destroy(); if ((Object)(object)parent == (Object)null) { return; } GameObject val = new GameObject("DoomMessage"); holder = val.AddComponent<RectTransform>(); ((Transform)holder).SetParent((Transform)(object)parent, false); holder.anchorMin = Vector2.zero; holder.anchorMax = Vector2.one; holder.offsetMin = Vector2.zero; holder.offsetMax = Vector2.zero; Image obj = val.AddComponent<Image>(); ((Graphic)obj).color = Background; ((Graphic)obj).raycastTarget = true; LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.flexibleWidth = 1f; obj2.flexibleHeight = 1f; obj2.ignoreLayout = false; GameObject val2 = new GameObject("Column"); column = val2.AddComponent<RectTransform>(); ((Transform)column).SetParent((Transform)(object)holder, false); column.anchorMin = new Vector2(0f, 0.5f); column.anchorMax = new Vector2(1f, 0.5f); column.pivot = new Vector2(0.5f, 0.5f); column.anchoredPosition = Vector2.zero; column.offsetMin = new Vector2(28f, column.offsetMin.y); column.offsetMax = new Vector2(-28f, column.offsetMax.y); VerticalLayoutGroup obj3 = val2.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj3).spacing = 10f; ((LayoutGroup)obj3).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)obj3).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj3).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandHeight = false; ContentSizeFitter obj4 = val2.AddComponent<ContentSizeFitter>(); obj4.horizontalFit = (FitMode)0; obj4.verticalFit = (FitMode)2; AddLabel(column, title, 26, (FontStyle)1, TitleColor); AddLabel(column, body, 15, (FontStyle)0, BodyColor); if (!string.IsNullOrEmpty(detail)) { AddLabel(column, detail, 15, (FontStyle)2, DetailColor); } if (!string.IsNullOrEmpty(footer)) { AddLabel(column, footer, 15, (FontStyle)0, BodyColor); } if (actions == null) { return; } foreach (MessageAction action in actions) { AddButton(column, action); } } private static void AddLabel(RectTransform parent, string value, int size, FontStyle style, Color color) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown GameObject val = new GameObject("Label"); ((Transform)val.AddComponent<RectTransform>()).SetParent((Transform)(object)parent, false); MakeText(val, value, size, style, color); val.AddComponent<LayoutElement>(); } private void AddButton(RectTransform parent, MessageAction action) { //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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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) //IL_0078: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown GameObject val = new GameObject("Button"); RectTransform val2 = val.AddComponent<RectTransform>(); ((Transform)val2).SetParent((Transform)(object)parent, false); Image val3 = val.AddComponent<Image>(); val3.sprite = Pixel(); ((Graphic)val3).color = ButtonColor; Button obj = val.AddComponent<Button>(); ((Selectable)obj).targetGraphic = (Graphic)(object)val3; Action handler = action.OnClick; string label = action.Label; ((UnityEvent)obj.onClick).AddListener((UnityAction)delegate { try { handler(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Message action '" + label + "' failed: " + ex.Message)); } }); LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.preferredHeight = 34f; obj2.minHeight = 34f; GameObject val4 = new GameObject("Text"); RectTransform obj3 = val4.AddComponent<RectTransform>(); ((Transform)obj3).SetParent((Transform)(object)val2, false); obj3.anchorMin = Vector2.zero; obj3.anchorMax = Vector2.one; obj3.offsetMin = Vector2.zero; obj3.offsetMax = Vector2.zero; MakeText(val4, label, 16, (FontStyle)1, ButtonText); } private static Text MakeText(GameObject go, string value, int size, FontStyle style, Color color) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) Text obj = go.AddComponent<Text>(); obj.text = value; obj.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); obj.fontSize = size; obj.fontStyle = style; ((Graphic)obj).color = color; obj.alignment = (TextAnchor)4; obj.horizontalOverflow = (HorizontalWrapMode)0; obj.verticalOverflow = (VerticalWrapMode)1; ((Graphic)obj).raycastTarget = false; return obj; } private Sprite Pixel() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pixel == (Object)null) { Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, Color.white); val.Apply(); pixel = Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f)); } return pixel; } public void Destroy() { if ((Object)(object)holder != (Object)null) { Object.Destroy((Object)(object)((Component)holder).gameObject); holder = null; } if ((Object)(object)pixel != (Object)null) { Object.Destroy((Object)(object)pixel.texture); Object.Destroy((Object)(object)pixel); pixel = null; } column = null; } } public sealed class MessageAction { public string Label { get; } public Action OnClick { get; } public MessageAction(string label, Action onClick) { Label = label; OnClick = onClick; } } public enum PreflightState { Pending, Ok, Fail } public sealed class PreflightScreen { private sealed class Row { public Text Marker; public Text Label; public string Name; } private static readonly Color Background = new Color(0.05f, 0.03f, 0.03f, 1f); private static readonly Color TitleColor = new Color(0.85f, 0.15f, 0.1f, 1f); private static readonly Color PendingColor = new Color(0.45f, 0.43f, 0.41f, 1f); private static readonly Color OkColor = new Color(0.35f, 0.78f, 0.35f, 1f); private static readonly Color FailColor = new Color(0.85f, 0.15f, 0.1f, 1f); private static readonly Color ButtonColor = new Color(0.16f, 0.16f, 0.18f, 1f); private static readonly Color ButtonText = new Color(0.95f, 0.94f, 0.92f, 1f); private const float MarkerWidth = 46f; private const int RowFontSize = 16; private RectTransform holder; private RectTransform column; private RectTransform actions; private Sprite pixel; private Row[] rows; public bool Visible => (Object)(object)holder != (Object)null; public void Show(RectTransform parent, string title, string[] rowNames) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_003d: 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_005d: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) Destroy(); if (!((Object)(object)parent == (Object)null) && rowNames != null) { GameObject val = new GameObject("DoomPreflight"); holder = val.AddComponent<RectTransform>(); ((Transform)holder).SetParent((Transform)(object)parent, false); holder.anchorMin = Vector2.zero; holder.anchorMax = Vector2.one; holder.offsetMin = Vector2.zero; holder.offsetMax = Vector2.zero; Image obj = val.AddComponent<Image>(); ((Graphic)obj).color = Background; ((Graphic)obj).raycastTarget = true; LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.flexibleWidth = 1f; obj2.flexibleHeight = 1f; obj2.ignoreLayout = false; GameObject val2 = new GameObject("Column"); column = val2.AddComponent<RectTransform>(); ((Transform)column).SetParent((Transform)(object)holder, false); column.anchorMin = new Vector2(0f, 0.5f); column.anchorMax = new Vector2(1f, 0.5f); column.pivot = new Vector2(0.5f, 0.5f); column.anchoredPosition = Vector2.zero; column.offsetMin = new Vector2(34f, column.offsetMin.y); column.offsetMax = new Vector2(-34f, column.offsetMax.y); VerticalLayoutGroup obj3 = val2.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj3).spacing = 8f; ((LayoutGroup)obj3).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)obj3).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj3).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandHeight = false; ContentSizeFitter obj4 = val2.AddComponent<ContentSizeFitter>(); obj4.horizontalFit = (FitMode)0; obj4.verticalFit = (FitMode)2; AddTitle(title); rows = new Row[rowNames.Length]; for (int i = 0; i < rowNames.Length; i++) { rows[i] = AddRow(rowNames[i]); } AddActionsHolder(); } } public void Set(int index, PreflightState state) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) if (rows == null || index < 0 || index >= rows.Length) { return; } Row row = rows[index]; if (row != null && !((Object)(object)row.Marker == (Object)null)) { switch (state) { case PreflightState.Ok: row.Marker.text = "[OK]"; ((Graphic)row.Marker).color = OkColor; ((Graphic)row.Label).color = OkColor; break; case PreflightState.Fail: row.Marker.text = "[X]"; ((Graphic)row.Marker).color = FailColor; ((Graphic)row.Label).color = FailColor; break; default: row.Marker.text = "[ ]"; ((Graphic)row.Marker).color = PendingColor; ((Graphic)row.Label).color = PendingColor; break; } } } public void SetLabel(int index, string label) { if (rows != null && index >= 0 && index < rows.Length) { Row row = rows[index]; if (row != null && !((Object)(object)row.Label == (Object)null)) { row.Name = label; row.Label.text = label; } } } private void AddTitle(string title) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown GameObject val = new GameObject("Title"); ((Transform)val.AddComponent<RectTransform>()).SetParent((Transform)(object)column, false); MakeText(val, title, 24, (FontStyle)1, TitleColor).alignment = (TextAnchor)3; LayoutElement obj = val.AddComponent<LayoutElement>(); obj.minHeight = 34f; obj.preferredHeight = 34f; } private Row AddRow(string name) { //IL_0011: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0099: 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: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) Row obj = new Row { Name = name }; GameObject val = new GameObject("Row"); RectTransform val2 = val.AddComponent<RectTransform>(); ((Transform)val2).SetParent((Transform)(object)column, false); HorizontalLayoutGroup obj2 = val.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj2).spacing = 10f; ((LayoutGroup)obj2).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false; LayoutElement obj3 = val.AddComponent<LayoutElement>(); obj3.minHeight = 22f; obj3.preferredHeight = 22f; GameObject val3 = new GameObject("Marker"); ((Transform)val3.AddComponent<RectTransform>()).SetParent((Transform)(object)val2, false); obj.Marker = MakeText(val3, "[ ]", 16, (FontStyle)1, PendingColor); obj.Marker.alignment = (TextAnchor)3; obj.Marker.horizontalOverflow = (HorizontalWrapMode)1; LayoutElement obj4 = val3.AddComponent<LayoutElement>(); obj4.minWidth = 46f; obj4.preferredWidth = 46f; obj4.flexibleWidth = 0f; GameObject val4 = new GameObject("Label"); ((Transform)val4.AddComponent<RectTransform>()).SetParent((Transform)(object)val2, false); obj.Label = MakeText(val4, name, 16, (FontStyle)0, PendingColor); obj.Label.alignment = (TextAnchor)3; obj.Label.horizontalOverflow = (HorizontalWrapMode)1; val4.AddComponent<LayoutElement>().flexibleWidth = 1f; return obj; } private void AddActionsHolder() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown GameObject val = new GameObject("Actions"); actions = val.AddComponent<RectTransform>(); ((Transform)actions).SetParent((Transform)(object)column, false); VerticalLayoutGroup obj = val.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f; ((LayoutGroup)obj).padding = new RectOffset(0, 0, 10, 0); ((LayoutGroup)obj).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; val.AddComponent<LayoutElement>(); } public void ShowAction(MessageAction action) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: 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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown if ((Object)(object)actions == (Object)null || action == null) { return; } GameObject val = new GameObject("Button"); RectTransform val2 = val.AddComponent<RectTransform>(); ((Transform)val2).SetParent((Transform)(object)actions, false); Image val3 = val.AddComponent<Image>(); val3.sprite = Pixel(); ((Graphic)val3).color = ButtonColor; Button obj = val.AddComponent<Button>(); ((Selectable)obj).targetGraphic = (Graphic)(object)val3; Action handler = action.OnClick; string label = action.Label; ((UnityEvent)obj.onClick).AddListener((UnityAction)delegate { try { handler(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Preflight action '" + label + "' failed: " + ex.Message)); } }); LayoutElement obj2 = val.AddComponent<LayoutElement>(); obj2.preferredHeight = 34f; obj2.minHeight = 34f; GameObject val4 = new GameObject("Text"); RectTransform obj3 = val4.AddComponent<RectTransform>(); ((Transform)obj3).SetParent((Transform)(object)val2, false); obj3.anchorMin = Vector2.zero; obj3.anchorMax = Vector2.one; obj3.offsetMin = Vector2.zero; obj3.offsetMax = Vector2.zero; MakeText(val4, label, 16, (FontStyle)1, ButtonText); } private static Text MakeText(GameObject go, string value, int size, FontStyle style, Color color) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) Text obj = go.AddComponent<Text>(); obj.text = value; obj.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); obj.fontSize = size; obj.fontStyle = style; ((Graphic)obj).color = color; obj.alignment = (TextAnchor)4; obj.horizontalOverflow = (HorizontalWrapMode)0; obj.verticalOverflow = (VerticalWrapMode)1; ((Graphic)obj).raycastTarget = false; return obj; } private Sprite Pixel() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pixel == (Object)null) { Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, Color.white); val.Apply(); pixel = Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f)); } return pixel; } public void Destroy() { if ((Object)(object)holder != (Object)null) { Object.Destroy((Object)(object)((Component)holder).gameObject); holder = null; } if ((Object)(object)pixel != (Object)null) { Object.Destroy((Object)(object)pixel.texture); Object.Destroy((Object)(object)pixel); pixel = null; } column = null; actions = null; rows = null; } } public sealed class UnityKeyState : IKeyState { private static readonly Dictionary<DoomKey, KeyCode> Map = BuildMap(); private static readonly KeyValuePair<KeyCode, DoomKey>[] Reverse = BuildReverse(); public bool Enabled { get; set; } public static KeyValuePair<KeyCode, DoomKey>[] Bindings => Reverse; public void Sample() { } public bool IsPressed(DoomKey key) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { return false; } if (Map.TryGetValue(key, out var value)) { return Input.GetKey(value); } return false; } private static KeyValuePair<KeyCode, DoomKey>[] BuildReverse() { //IL_0028: 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) List<KeyValuePair<KeyCode, DoomKey>> list = new List<KeyValuePair<KeyCode, DoomKey>>(Map.Count); foreach (KeyValuePair<DoomKey, KeyCode> item in Map) { list.Add(new KeyValuePair<KeyCode, DoomKey>(item.Value, item.Key)); } return list.ToArray(); } private static Dictionary<DoomKey, KeyCode> BuildMap() { return new Dictionary<DoomKey, KeyCode> { { (DoomKey)0, (KeyCode)97 }, { (DoomKey)1, (KeyCode)98 }, { (DoomKey)2, (KeyCode)99 }, { (DoomKey)3, (KeyCode)100 }, { (DoomKey)4, (KeyCode)101 }, { (DoomKey)5, (KeyCode)102 }, { (DoomKey)6, (KeyCode)103 }, { (DoomKey)7, (KeyCode)104 }, { (DoomKey)8, (KeyCode)105 }, { (DoomKey)9, (KeyCode)106 }, { (DoomKey)10, (KeyCode)107 }, { (DoomKey)11, (KeyCode)108 }, { (DoomKey)12, (KeyCode)109 }, { (DoomKey)13, (KeyCode)110 }, { (DoomKey)14, (KeyCode)111 }, { (DoomKey)15, (KeyCode)112 }, { (DoomKey)16, (KeyCode)113 }, { (DoomKey)17, (KeyCode)114 }, { (DoomKey)18, (KeyCode)115 }, { (DoomKey)19, (KeyCode)116 }, { (DoomKey)20, (KeyCode)117 }, { (DoomKey)21, (KeyCode)118 }, { (DoomKey)22, (KeyCode)119 }, { (DoomKey)23, (KeyCode)120 }, { (DoomKey)24, (KeyCode)121 }, { (DoomKey)25, (KeyCode)122 }, { (DoomKey)26, (KeyCode)48 }, { (DoomKey)27, (KeyCode)49 }, { (DoomKey)28, (KeyCode)50 }, { (DoomKey)29, (KeyCode)51 }, { (DoomKey)30, (KeyCode)52 }, { (DoomKey)31, (KeyCode)53 }, { (DoomKey)32, (KeyCode)54 }, { (DoomKey)33, (KeyCode)55 }, { (DoomKey)34, (KeyCode)56 }, { (DoomKey)35, (KeyCode)57 }, { (DoomKey)75, (KeyCode)256 }, { (DoomKey)76, (KeyCode)257 }, { (DoomKey)77, (KeyCode)258 }, { (DoomKey)78, (KeyCode)259 }, { (DoomKey)79, (KeyCode)260 }, { (DoomKey)80, (KeyCode)261 }, { (DoomKey)81, (KeyCode)262 }, { (DoomKey)82, (KeyCode)263 }, { (DoomKey)83, (KeyCode)264 }, { (DoomKey)84, (KeyCode)265 }, { (DoomKey)85, (KeyCode)282 }, { (DoomKey)86, (KeyCode)283 }, { (DoomKey)87, (KeyCode)284 }, { (DoomKey)88, (KeyCode)285 }, { (DoomKey)89, (KeyCode)286 }, { (DoomKey)90, (KeyCode)287 }, { (DoomKey)91, (KeyCode)288 }, { (DoomKey)92, (KeyCode)289 }, { (DoomKey)93, (KeyCode)290 }, { (DoomKey)94, (KeyCode)291 }, { (DoomKey)95, (KeyCode)292 }, { (DoomKey)96, (KeyCode)293 }, { (DoomKey)73, (KeyCode)273 }, { (DoomKey)74, (KeyCode)274 }, { (DoomKey)71, (KeyCode)276 }, { (DoomKey)72, (KeyCode)275 }, { (DoomKey)36, (KeyCode)279 }, { (DoomKey)58, (KeyCode)13 }, { (DoomKey)60, (KeyCode)9 }, { (DoomKey)59, (KeyCode)8 }, { (DoomKey)57, (KeyCode)32 }, { (DoomKey)100, (KeyCode)19 }, { (DoomKey)38, (KeyCode)304 }, { (DoomKey)42, (KeyCode)303 }, { (DoomKey)37, (KeyCode)306 }, { (DoomKey)41, (KeyCode)305 }, { (DoomKey)39, (KeyCode)308 }, { (DoomKey)43, (KeyCode)307 }, { (DoomKey)49, (KeyCode)44 }, { (DoomKey)50, (KeyCode)46 }, { (DoomKey)52, (KeyCode)47 }, { (DoomKey)53, (KeyCode)92 }, { (DoomKey)48, (KeyCode)59 }, { (DoomKey)55, (KeyCode)61 }, { (DoomKey)68, (KeyCode)45 }, { (DoomKey)67, (KeyCode)270 }, { (DoomKey)69, (KeyCode)268 }, { (DoomKey)70, (KeyCode)267 }, { (DoomKey)46, (KeyCode)91 }, { (DoomKey)47, (KeyCode)93 }, { (DoomKey)65, (KeyCode)277 }, { (DoomKey)66, (KeyCode)127 }, { (DoomKey)64, (KeyCode)278 }, { (DoomKey)61, (KeyCode)280 }, { (DoomKey)62, (KeyCode)281 } }; } } public sealed class UnityMusic : IMusic, IDisposable { private const int SampleRate = 44100; private const int ChannelCountOut = 2; private const int BufferBlocks = 8; private readonly Wad wad; private readonly Synthesizer synthesizer; private readonly object gate = new object(); private GameObject root; private AudioSource source; private AudioClip clip; private IDecoder decoder; private Bgm current; private int volume; private Exception failure; private bool failureLogged; private bool disposed; private float[] blockLeft; private float[] blockRight; private int blockPos; private int blockLen; public int MaxVolume => 15; public int Volume { get { return volume; } set { volume = Mathf.Clamp(value, 0, MaxVolume); if ((Object)(object)source != (Object)null) { source.volume = (float)volume / (float)MaxVolume; } } } public UnityMusic(GameContent content, string soundFontPath, int initialVolume) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown wad = content.Wad; volume = Mathf.Clamp(initialVolume, 0, MaxVolume); if (string.IsNullOrEmpty(soundFontPath) || !File.Exists(soundFontPath)) { throw new FileNotFoundException("SoundFont not found: " + soundFontPath, soundFontPath); } SynthesizerSettings val = new SynthesizerSettings(44100); val.EnableReverbAndChorus = false; val.BlockSize = MusDecoder.BlockLength; synthesizer = new Synthesizer(soundFontPath, val); blockLen = MusDecoder.BlockLength; blockLeft = new float[blockLen]; blockRight = new float[blockLen]; blockPos = blockLen; root = new GameObject("DoomMusic"); Object.DontDestroyOnLoad((Object)(object)root); source = root.AddComponent<AudioSource>(); source.playOnAwake = false; source.loop = true; source.spatialBlend = 0f; source.bypassEffects = true; source.bypassReverbZones = true; source.volume = (float)volume / (float)MaxVolume; clip = AudioClip.Create("DoomMusic", 352800, 2, 44100, true, new PCMReaderCallback(OnAudioRead)); source.clip = clip; Plugin.Log.LogInfo((object)("DOOM music: synthesizer ready (" + Path.GetFileName(soundFontPath) + ").")); } private void OnAudioRead(float[] buffer) { int num = buffer.Length / 2; lock (gate) { if (disposed || decoder == null) { Array.Clear(buffer, 0, buffer.Length); return; } int num2; for (int i = 0; i < num; i += num2) { if (blockPos >= blockLen) { try { decoder.RenderWaveform(synthesizer, blockLeft, blockRight); } catch (Exception ex) { decoder = null; failure = ex; Array.Clear(buffer, i * 2, (num - i) * 2); break; } blockPos = 0; } num2 = Math.Min(blockLen - blockPos, num - i); for (int j = 0; j < num2; j++) { int num3 = (i + j) * 2; buffer[num3] = blockLeft[blockPos + j]; buffer[num3 + 1] = blockRight[blockPos + j]; } blockPos += num2; } } } public void ReportFailure() { Exception ex = null; lock (gate) { if (failure != null && !failureLogged) { failureLogged = true; ex = failure; } } if (ex != null) { Plugin.Log.LogError((object)("DOOM music stopped - renderer threw: " + ex)); } } public void StartMusic(Bgm bgm, bool loop) { //IL_000e: 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_0019: 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) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) ReportFailure(); if (disposed || bgm == current) { return; } current = bgm; if ((int)bgm == 0) { lock (gate) { this.decoder = null; } if ((Object)(object)source != (Object)null && source.isPlaying) { source.Stop(); } return; } try { string text = "D_" + ((object)DoomInfo.BgmNames[bgm]).ToString().ToUpper(); int lumpNumber = wad.GetLumpNumber(text); if (lumpNumber < 0) { Plugin.Log.LogWarning((object)("music lump missing: " + text)); return; } IDecoder decoder = CreateDecoder(wad.ReadLump(lumpNumber), loop); if (decoder == null) { Plugin.Log.LogWarning((object)("unrecognized music format in " + text)); return; } lock (gate) { synthesizer.Reset(); this.decoder = decoder; blockPos = blockLen; } if ((Object)(object)source != (Object)null && !source.isPlaying) { source.Play(); } } catch (Exception ex) { Plugin.Log.LogError((object)("music start failed: " + ex.Message)); } } public void Pause() { if (!disposed && !((Object)(object)source == (Object)null) && source.isPlaying) { source.Pause(); } } public void Resume() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!disposed && !((Object)(object)source == (Object)null) && (int)current != 0) { source.UnPause(); } } private static IDecoder CreateDecoder(byte[] data, bool loop) { if (data == null || data.Length < 4) { return null; } if (Matches(data, MusDecoder.MusHeader)) { return new MusDecoder(data, loop); } if (Matches(data, MidiDecoder.MidiHeader)) { return new MidiDecoder(data, loop); } return null; } private static bool Matches(byte[] data, byte[] header) { if (data.Length < header.Length) { return false; } for (int i = 0; i < header.Length; i++) { if (data[i] != header[i]) { return false; } } return true; } public void Dispose() { lock (gate) { if (disposed) { return; } disposed = true; decoder = null; } if ((Object)(object)source != (Object)null) { source.Stop(); source = null; } lock (gate) { if ((Object)(object)clip != (Object)null) { Object.Destroy((Object)(object)clip); clip = null; } } if ((Object)(object)root != (Object)null) { Object.Destroy((Object)(object)root); root = null; } } } public sealed class UnitySound : ISound, IDisposable { private sealed class ChannelInfo { public Sfx Reserved; public Sfx Playing; public float Priority; public Mobj Source; public SfxType Type; public int Volume; public Fixed LastX; public Fixed LastY; public void Clear() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) Reserved = (Sfx)0; Playing = (Sfx)0; Priority = 0f; Source = null; Type = (SfxType)0; Volume = 0; LastX = Fixed.Zero; LastY = Fixed.Zero; } } private const int ChannelCount = 8; private const float ClipDist = 1200f; private const float CloseDist = 160f; private const float Attenuator = 1040f; private static readonly float FastDecay = (float)Math.Pow(0.5, 1.0 / 7.0); private static readonly float SlowDecay = (float)Math.Pow(0.5, 1.0 / 35.0); private readonly AudioClip[] buffers; private readonly float[] amplitudes; private readonly AudioSource[] sources = (AudioSource[])(object)new AudioSource[8]; private readonly ChannelInfo[] infos = new ChannelInfo[8]; private AudioSource uiSource; private Sfx uiReserved; private Mobj listener; private GameObject root; private readonly DoomRandom random = new DoomRandom(); private int volume; private float lastUpdate; public int LoadedCount { get; private set; } public int TotalCount { get; private set; } public int MaxVolume => 15; public int Volume { get { return volume; } set { volume = Mathf.Clamp(value, 0, MaxVolume); } } private float MasterDecay => (float)volume / (float)MaxVolume; public UnitySound(GameContent content, int initialVolume) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown volume = Mathf.Clamp(initialVolume, 0, MaxVolume); int num = DoomInfo.SfxNames.Length; buffers = (AudioClip[])(object)new AudioClip[num]; amplitudes = new float[num]; root = new GameObject("DoomAudio"); Object.DontDestroyOnLoad((Object)(object)root); for (int i = 0; i < 8; i++) { sources[i] = CreateSource("Channel" + i); infos[i] = new ChannelInfo(); infos[i].Clear(); } uiSource = CreateSource("UI"); int num2 = 0; for (int j = 0; j < num; j++) { string text = "DS" + ((object)DoomInfo.SfxNames[j]).ToString().ToUpper(); try { int lumpNumber = content.Wad.GetLumpNumber(text); if (lumpNumber >= 0) { float amplitude; AudioClip val = Decode(content.Wad.ReadLump(lumpNumber), text, out amplitude); if ((Object)(object)val != (Object)null) { buffers[j] = val; amplitudes[j] = amplitude; num2++; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("sfx " + text + " failed: " + ex.Message)); } } LoadedCount = num2; TotalCount = num; Plugin.Log.LogInfo((object)("DOOM audio: loaded " + num2 + "/" + num + " sound effects.")); } private AudioSource CreateSource(string name) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(root.transform, false); AudioSource obj = val.AddComponent<AudioSource>(); obj.playOnAwake = false; obj.loop = false; obj.spatialBlend = 0f; obj.bypassEffects = true; obj.bypassReverbZones = true; return obj; } private static AudioClip Decode(byte[] data, string name, out float amplitude) { amplitude = 0f; if (data == null || data.Length < 8) { return null; } int num = BitConverter.ToUInt16(data, 2); int num2 = BitConverter.ToInt32(data, 4); int num3 = 8; if (ContainsDmxPadding(data, num2)) { num3 += 16; num2 -= 32; } if (num2 <= 0 || num <= 0 || num3 + num2 > data.Length) { return null; } float[] array = new float[num2]; int num4 = 0; int num5 = Math.Min(num / 5, num2); for (int i = 0; i < num2; i++) { int num6 = data[num3 + i] - 128; array[i] = (float)num6 / 128f; if (i < num5) { int num7 = ((num6 < 0) ? (-num6) : num6); if (num7 > num4) { num4 = num7; } } } amplitude = (float)num4 / 128f; AudioClip obj = AudioClip.Create(name, num2, 1, num, false); obj.SetData(array, 0); return obj; } private static bool ContainsDmxPadding(byte[] data, int sampleCount) { if (sampleCount < 32 || 8 + sampleCount > data.Length) { return false; } byte b = data[8]; for (int i = 1; i < 16; i++) { if (data[8 + i] != b) { return false; } } byte b2 = data[8 + sampleCount - 1]; for (int j = 1; j < 16; j++) { if (data[8 + sampleCount - j - 1] != b2) { return false; } } return true; } public void SetListener(Mobj value) { listener = value; } public void Update() { //IL_002e: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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_00db: Unknown result type (might be due to invalid IL or missing references) if (Time.unscaledTime - lastUpdate < 0.01f) { return; } for (int i = 0; i < infos.Length; i++) { ChannelInfo channelInfo = infos[i]; AudioSource val = sources[i]; if ((int)channelInfo.Playing != 0) { if (val.isPlaying) { channelInfo.Priority *= (((int)channelInfo.Type == 0) ? SlowDecay : FastDecay); SetParam(val, channelInfo); } else { channelInfo.Playing = (Sfx)0; if ((int)channelInfo.Reserved == 0) { channelInfo.Source = null; } } } if ((int)channelInfo.Reserved != 0) { if ((int)channelInfo.Playing != 0) { val.Stop(); } val.clip = buffers[channelInfo.Reserved]; SetParam(val, channelInfo); val.pitch = GetPitch(channelInfo.Type, channelInfo.Reserved); val.Play(); channelInfo.Playing = channelInfo.Reserved; channelInfo.Reserved = (Sfx)0; } } if ((int)uiReserved != 0) { if (uiSource.isPlaying) { uiSource.Stop(); } uiSource.panStereo = 0f; uiSource.pitch = 1f; uiSource.volume = MasterDecay; uiSource.clip = buffers[uiReserved]; uiSource.Play(); uiReserved = (Sfx)0; } lastUpdate = Time.unscaledTime; } public void StartSound(Sfx sfx) { //IL_0006: 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) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)buffers[sfx] == (Object)null)) { uiReserved = sfx; } } public void StartSound(Mobj mobj, Sfx sfx, SfxType type) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) StartSound(mobj, sfx, type, 100); } public void StartSound(Mobj mobj, Sfx sfx, SfxType type, int vol) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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_00a6: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)buffers[sfx] == (Object)null || mobj == null) { return; } float num; if ((int)type == 0 || listener == null) { num = vol; } else { Fixed val = mobj.X - listener.X; float num2 = ((Fixed)(ref val)).ToFloat(); val = mobj.Y - listener.Y; float num3 = ((Fixed)(ref val)).ToFloat(); num = amplitudes[sfx] * GetDistanceDecay(Mathf.Sqrt(num2 * num2 + num3 * num3)) * (float)vol; } for (int i = 0; i < infos.Length; i++) { if (infos[i].Source == mobj && infos[i].Type == type) { infos[i].Reserved = sfx; infos[i].Priority = num; infos[i].Volume = vol; return; } } for (int j = 0; j < infos.Length; j++) { if ((int)infos[j].Reserved == 0 && (int)infos[j].Playing == 0) { infos[j].Reserved = sfx; infos[j].Priority = num; infos[j].Source = mobj; infos[j].Type = type; infos[j].Volume = vol; return; } } int num4 = 0; float num5 = float.MaxValue; for (int k = 0; k < infos.Length; k++) { if (infos[k].Priority < num5) { num5 = infos[k].Priority; num4 = k; } } if (num >= num5) { infos[num4].Reserved = sfx; infos[num4].Priority = num; infos[num4].Source = mobj; infos[num4].Type = type; infos[num4].Volume = vol; } } public void StopSound(Mobj mobj) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_004d: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < infos.Length; i++) { if (infos[i].Source == mobj) { infos[i].LastX = infos[i].Source.X; infos[i].LastY = infos[i].Source.Y; infos[i].Source = null; infos[i].Volume /= 5; } } } public void Reset() { random.Clear(); for (int i = 0; i < infos.Length; i++) { sources[i].Stop(); infos[i].Clear(); } listener = null; } public void Pause() { for (int i = 0; i < sources.Length; i++) { if (sources[i].isPlaying) { sources[i].Pause(); } } if ((Object)(object)uiSource != (Object)null && uiSource.isPlaying) { uiSource.Pause(); } } public void Resume() { for (int i = 0; i < sources.Length; i++) { sources[i].UnPause(); } if ((Object)(object)uiSource != (Object)null) { uiSource.UnPause(); } } private void SetParam(AudioSource src, ChannelInfo info) { //IL_0001: 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) //IL_0059: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) if ((int)info.Type == 0 || listener == null) { src.panStereo = 0f; src.volume = 0.01f * MasterDecay * (float)info.Volume; return; } Fixed val; Fixed val2; if (info.Source == null) { val = info.LastX; val2 = info.LastY; } else { val = info.Source.X; val2 = info.Source.Y; } Fixed val3 = val - listener.X; float num = ((Fixed)(ref val3)).ToFloat(); val3 = val2 - listener.Y; float num2 = ((Fixed)(ref val3)).ToFloat(); if (Mathf.Abs(num) < 16f && Mathf.Abs(num2) < 16f) { src.panStereo = 0f; src.volume = 0.01f * MasterDecay * (float)info.Volume; return; } float dist = Mathf.Sqrt(num * num + num2 * num2); float num3 = Mathf.Atan2(num2, num); Angle angle = listener.Angle; float num4 = num3 - (float)((Angle)(ref angle)).ToRadian(); src.panStereo = Mathf.Clamp(0f - Mathf.Sin(num4), -1f, 1f); src.volume = 0.01f * MasterDecay * GetDistanceDecay(dist) * (float)info.Volume; } private static float GetDistanceDecay(float dist) { if (dist < 160f) { return 1f; } return Mathf.Max((1200f - Mathf.Min(dist, 1200f)) / 1040f, 0f); } private float GetPitch(SfxType type, Sfx sfx) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if ((int)sfx == 32 || (int)sfx == 87 || (int)sfx == 108) { return 1f; } if ((int)type == 2) { return 1f + 0.075f * (float)(random.Next() - 128) / 128f; } return 1f + 0.025f * (float)(random.Next() - 128) / 128f; } public void Dispose() { if ((Object)(object)root != (Object)null) { Object.Destroy((Object)(object)root); root = null; } } } } namespace DoomForCrispberry.Music { internal interface IDecoder { void RenderWaveform(Synthesizer synthesizer, Span<float> left, Span<float> right); } internal sealed class MusDecoder : IDecoder { private class MusEvent { public int Type; public int Channel; public int Data1; public int Data2; } private enum ReadResult { Ongoing, EndOfGroup, EndOfFile } public static readonly int SampleRate = 44100; public static readonly int BlockLength = SampleRate / 140; public static readonly byte[] MusHeader = new byte[4] { 77, 85, 83, 26 }; private byte[] data; private bool loop; private int scoreLength; private int scoreStart; private int channelCount; private int channelCount2; private int instrumentCount; private int[] instruments; private MusEvent[] events; private int eventCount; private int[] lastVolume; private int p; private int delay; private int blockWrote; public MusDecoder(byte[] data, bool loop) { CheckHeader(data); this.data = data; this.loop = loop; scoreLength = BitConverter.ToUInt16(data, 4); scoreStart = BitConverter.ToUInt16(data, 6); channelCount = BitConverter.ToUInt16(data, 8); channelCount2 = BitConverter.ToUInt16(data, 10); instrumentCount = BitConverter.ToUInt16(data, 12); instruments = new int[instrumentCount]; for (int i = 0; i < instruments.Length; i++) { instruments[i] = BitConverter.ToUInt16(data, 16 + 2 * i); } events = new MusEvent[128]; for (int j = 0; j < events.Length; j++) { events[j] = new MusEvent(); } eventCount = 0; lastVolume = new int[16]; Reset(); blockWrote = BlockLength; } private static void CheckHeader(byte[] data) { for (int i = 0; i < MusHeader.Length; i++) { if (data[i] != MusHeader[i]) { throw new Exception("Invalid format!"); } } } public void RenderWaveform(Synthesizer synthesizer, Span<float> left, Span<float> right) { int num; for (int i = 0; i < left.Length; i += num) { if (blockWrote == synthesizer.BlockSize) { ProcessMidiEvents(synthesizer); blockWrote = 0; } int val = synthesizer.BlockSize - blockWrote; int val2 = left.Length - i; num = Math.Min(val, val2); synthesizer.Render(left.Slice(i, num), right.Slice(i, num)); blockWrote += num; } } private void ProcessMidiEvents(Synthesizer synthesizer) { if (delay > 0) { delay--; } if (delay != 0) { return; } delay = ReadSingleEventGroup(); SendEvents(synthesizer); if (delay == -1) { synthesizer.NoteOffAll(false); if (loop) { Reset(); } } } private void Reset() { for (int i = 0; i < lastVolume.Length; i++) { lastVolume[i] = 0; } p = scoreStart; delay = 0; } private int ReadSingleEventGroup() { eventCount = 0; while (true) { switch (ReadSingleEvent()) { case ReadResult.EndOfFile: return -1; case ReadResult.EndOfGroup: { int num = 0; byte b; do { b = data[p++]; num = num * 128 + (b & 0x7F); } while ((b & 0x80) != 0); return num; } } } } private ReadResult ReadSingleEvent() { int num = this.data[p] & 0xF; if (num == 15) { num = 9; } else if (num >= 9) { num++; } int num2 = (this.data[p] & 0x70) >> 4; bool flag = this.data[p] >> 7 != 0; p++; MusEvent musEvent = events[eventCount]; eventCount++; switch (num2) { case 0: { musEvent.Type = 0; musEvent.Channel = num; byte data6 = this.data[p++]; musEvent.Data1 = data6; musEvent.Data2 = 0; break; } case 1: { musEvent.Type = 1; musEvent.Channel = num; byte num4 = this.data[p++]; int data5 = num4 & 0x7F; int num5 = (((num4 & 0x80) != 0) ? this.data[p++] : (-1)); musEvent.Data1 = data5; if (num5 == -1) { musEvent.Data2 = lastVolume[num]; break; } musEvent.Data2 = num5; lastVolume[num] = num5; break; } case 2: { musEvent.Type = 2; musEvent.Channel = num; int num3 = (this.data[p++] << 7) / 2; int data4 = num3 & 0x7F; num3 >>= 7; musEvent.Data1 = data4; musEvent.Data2 = num3; break; } case 3: { musEvent.Type = 3; musEvent.Channel = num; byte data3 = this.data[p++]; musEvent.Data1 = data3; musEvent.Data2 = 0; break; } case 4: { musEvent.Type = 4; musEvent.Channel = num; byte data = this.data[p++]; byte data2 = this.data[p++]; musEvent.Data1 = data; musEvent.Data2 = data2; break; } case 6: return ReadResult.EndOfFile; default: throw new Exception("Unknown event type!"); } if (flag) { return ReadResult.EndOfGroup; } return ReadResult.Ongoing; } private void SendEvents(Synthesizer synthesizer) { for (int i = 0; i < eventCount; i++) { MusEvent musEvent = events[i]; switch (musEvent.Type) { case 0: synthesizer.NoteOff(musEvent.Channel, musEvent.Data1); break; case 1: synthesizer.NoteOn(musEvent.Channel, musEvent.Data1, musEvent.Data2); break; case 2: synthesizer.ProcessMidiMessage(musEvent.Channel, 224, musEvent.Data1, musEvent.Data2); break; case 3: switch (musEvent.Data1) { case 11: synthesizer.NoteOffAll(musEvent.Channel, false); break; case 14: synthesizer.ResetAllControllers(musEvent.Channel); break; } break; case 4: switch (musEvent.Data1) { case 0: synthesizer.ProcessMidiMessage(musEvent.Channel, 192, musEvent.Data2, 0); break; case 1: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 0, musEvent.Data2); break; case 2: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 1, musEvent.Data2); break; case 3: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 7, musEvent.Data2); break; case 4: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 10, musEvent.Data2); break; case 5: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 11, musEvent.Data2); break; case 6: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 91, musEvent.Data2); break; case 7: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 93, musEvent.Data2); break; case 8: synthesizer.ProcessMidiMessage(musEvent.Channel, 176, 64, musEvent.Data2); break; } break; } } } } internal sealed class MidiDecoder : IDecoder { public static readonly byte[] MidiHeader = new byte[4] { 77, 84, 104, 100 }; private MidiFile midi; private MidiFileSequencer sequencer; private bool loop; public MidiDecoder(byte[] data, bool loop) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown midi = new MidiFile((Stream)new MemoryStream(data)); this.loop = loop; } public void RenderWaveform(Synthesizer synthesizer, Span<float> left, Span<float> right) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (sequencer == null) { sequencer = new MidiFileSequencer(synthesizer); sequencer.Play(midi, loop); } sequencer.Render(left, right); } } } namespace DoomForCrispberry.Interop { public static class PiPhoneInterop { public const string PluginGuid = "tony4twentys.Crispberry_PiPhone"; private const string RootNamespace = "Crispberry_PiPhone"; private static bool resolved; private static Assembly assembly; private static PropertyInfo isOpenProperty; private static PropertyInfo currentAppIdProperty; private static bool focusResolved; public static Type ApiType { get; private set; } public static Type AppType { get; private set; } public static Type HostType { get; private set; } public static bool Available => Resolve(); public static string FailureReason { get; private set; } public static bool Resolve() { if (resolved) { return assembly != null; } resolved = true; assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetType("Crispberry_PiPhone.PiPhoneApi", throwOnError: false) != null); if (assembly == null) { FailureReason = "Crispberry PiPhone assembly not loaded."; return false; } ApiType = assembly.GetType("Crispberry_PiPhone.PiPhoneApi", throwOnError: false); AppType = assembly.GetType("Crispberry_PiPhone.PiPhoneApp", throwOnError: false); HostType = assembly.GetType("Crispberry_PiPhone.IPiPhoneHost", throwOnError: false); if (ApiType == null || AppType == null || HostType == null) { FailureReason = "PiPhone types missing (API changed?): PiPhoneApi=" + (ApiType != null) + " PiPhoneApp=" + (AppType != null) + " IPiPhoneHost=" + (HostType != null); assembly = null; return false; } return true; } public static object CreateApp() { return Activator.CreateInstance(AppType); } public static void SetField(object app, string name, object value) { FieldInfo? field = AppType.GetField(name, BindingFlags.Instance | BindingFlags.Public); if (field == null) { throw new MissingFieldException(AppType.FullName, name); } field.SetValue(app, value); } public static void SetOnOpen(object app, Action<object> handler) { Delegate value = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(HostType), handler.Target, handler.Method); SetField(app, "OnOpen", value); } public static bool SetIconFile(object app, string path) { MethodInfo? method = AppType.GetMethod("SetIconFile", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(string) }, null); if (method == null) { throw new MissingMethodException(AppType.FullName, "SetIconFile"); } object obj = method.Invoke(app, new object[1] { path }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } public static void RegisterApp(object app) { MethodInfo? method = ApiType.GetMethod("RegisterApp", BindingFlags.Static | BindingFlags.Public); if (method == null) { throw new MissingMethodException(ApiType.FullName, "RegisterApp"); } method.Invoke(null, new object[1] { app }); } public static bool IsAppFocused(string appId) { if (!Resolve()) { return false; } if (!focusResolved) { focusResolved = true; isOpenProperty = ApiType.GetProperty("IsOpen", BindingFlags.Static | BindingFlags.Public); currentAppIdProperty = ApiType.GetProperty("CurrentAppId", BindingFlags.Static | BindingFlags.Public); } if (isOpenProperty == null || currentAppIdProperty == null) { return false; } object value = isOpenProperty.GetValue(null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return currentAppIdProperty.GetValue(null) as string == appId; } return false; } public static object HostProperty(object host, string name) { return HostType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public)?.GetValue(host); } public static object HostInvoke(object host, string name, params object[] args) { Type[] types = args.Select((object a) => a?.GetType() ?? typeof(object)).ToArray(); MethodInfo? method = HostType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); if (method == null) { throw new MissingMethodException(HostType.FullName, name); } return method.Invoke(host, args); } } } namespace DoomForCrispberry.Engine { public enum DoomPhase { Resolve, Engine, Sound, Music, Finalize } public sealed class DoomSession : IDisposable { public sealed class Builder { private static readonly DoomPhase[] Order = new DoomPhase[5] { DoomPhase.Resolve, DoomPhase.Engine, DoomPhase.Sound, DoomPhase.Music, DoomPhase.