From 1e9564a337134aed40eb3c2a77851af3129544cb Mon Sep 17 00:00:00 2001 From: Kabuto Date: Mon, 28 Sep 2015 01:30:58 +0200 Subject: [PATCH 01/24] C64 core: tape loading added, lots of bugfixes and improvements --- BizHawk.Client.EmuHawk/MainForm.cs | 4 +- BizHawk.Emulation.Common/Database/Database.cs | 1 + .../BizHawk.Emulation.Cores.csproj | 3 +- .../CPUs/MOS 6502X/Execute.cs | 5 + .../Computers/Commodore64/C64.IDebuggable.cs | 113 +- .../Commodore64/C64.IDisassemblable.cs | 36 + .../Computers/Commodore64/C64.Motherboard.cs | 1 + .../Computers/Commodore64/C64.cs | 179 +- .../CassettePort/CassettePortDevice.cs | 43 +- .../Commodore64/CassettePort/Tape.cs | 93 + .../Computers/Commodore64/MOS/CassettePort.cs | 30 - .../Computers/Commodore64/MOS/MOS6510.cs | 9 +- .../Computers/Commodore64/MOS/MOS6526.cs | 1578 +++++++++-------- .../Computers/Commodore64/MOS/MOS6567.cs | 4 +- .../Computers/Commodore64/MOS/Vic.cs | 5 +- 15 files changed, 1174 insertions(+), 930 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index a289f4d627..80b635e87d 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -1965,7 +1965,7 @@ namespace BizHawk.Client.EmuHawk if (VersionInfo.DeveloperBuild) { return FormatFilter( - "Rom Files", "*.nes;*.fds;*unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.psf;*.minipsf;*.nsf;%ARCH%", + "Rom Files", "*.nes;*.fds;*unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.psf;*.minipsf;*.nsf;%ARCH%", "Music Files", "*.psf;*.minipsf;*.sid;*.nsf", "Disc Images", "*.cue;*.ccd;*.m3u", "NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%", @@ -1986,7 +1986,7 @@ namespace BizHawk.Client.EmuHawk "PlayStation", "*.cue;*.ccd;*.m3u", "PSX Executables (experimental)", "*.exe", "PSF Playstation Sound File", "*.psf;*.minipsf", - "Commodore 64 (experimental)", "*.prg; *.d64, *.g64; *.crt;%ARCH%", + "Commodore 64 (experimental)", "*.prg; *.d64, *.g64; *.crt; *.tap;%ARCH%", "SID Commodore 64 Music File", "*.sid;%ARCH%", "Nintendo 64", "*.z64;*.v64;*.n64", "WonderSwan", "*.ws;*.wsc;%ARCH%", diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index 16d6b65e85..41c4f8eaf2 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -315,6 +315,7 @@ namespace BizHawk.Emulation.Common case ".T64": case ".G64": case ".CRT": + case ".TAP": game.System = "C64"; break; diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 0876ed3bcb..5446a07a1c 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -151,6 +151,7 @@ C64.cs + C64.cs @@ -176,11 +177,11 @@ + - diff --git a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs index 30e0bec3fd..0c5e94c799 100644 --- a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs @@ -2931,5 +2931,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (!rdy_freeze) mi++; } //ExecuteOne + + public bool AtInstructionStart() + { + return Microcode[opcode][mi] >= Uop.End; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs index f01f915c14..6618958233 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs @@ -51,15 +51,110 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } } - public IMemoryCallbackSystem MemoryCallbacks - { - [FeatureNotImplemented] - get { throw new NotImplementedException(); } - } + public bool CanStep(StepType type) + { + switch (type) + { + case StepType.Into: + case StepType.Over: + case StepType.Out: + return true; + default: + return false; + } + } - [FeatureNotImplemented] - public void Step(StepType type) { throw new NotImplementedException(); } - public bool CanStep(StepType type) { return false; } - } + public void Step(StepType type) + { + switch (type) + { + case StepType.Into: + StepInto(); + break; + case StepType.Out: + StepOut(); + break; + case StepType.Over: + StepOver(); + break; + } + } + + private void StepInto() + { + while (board.cpu.AtInstructionStart()) + { + DoCycle(); + } + while (!board.cpu.AtInstructionStart()) + { + DoCycle(); + } + } + + private void StepOver() + { + var instruction = board.cpu.Peek(board.cpu.PC); + + if (instruction == JSR) + { + var destination = board.cpu.PC + JSRSize; + while (board.cpu.PC != destination) + { + StepInto(); + } + } + else + { + StepInto(); + } + } + + private void StepOut() + { + var instr = board.cpu.Peek(board.cpu.PC); + + JSRCount = instr == JSR ? 1 : 0; + + var bailOutFrame = Frame + 1; + + while (true) + { + StepInto(); + instr = board.cpu.Peek(board.cpu.PC); + if (instr == JSR) + { + JSRCount++; + } + else if ((instr == RTS || instr == RTI) && JSRCount <= 0) + { + StepInto(); + JSRCount = 0; + break; + } + else if (instr == RTS || instr == RTI) + { + JSRCount--; + } + else //Emergency Bailout Logic + { + if (Frame == bailOutFrame) + { + break; + } + } + } + } + + private int JSRCount = 0; + + private const byte JSR = 0x20; + private const byte RTI = 0x40; + private const byte RTS = 0x60; + + private const byte JSRSize = 3; + + public IMemoryCallbackSystem MemoryCallbacks { get; private set; } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs new file mode 100644 index 0000000000..a24503805c --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64 +{ + public partial class C64 : IDisassemblable + { + public string Cpu + { + get + { + return "6510"; + } + set + { + } + } + + public string PCRegisterName + { + get { return "PC"; } + } + + public IEnumerable AvailableCpus + { + get { yield return "6510"; } + } + + public string Disassemble(MemoryDomain m, uint addr, out int length) + { + return Components.M6502.MOS6502X.Disassemble((ushort)addr, out length, (a) => m.PeekByte(a)); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs index 99633d578a..e36539f7b7 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs @@ -102,6 +102,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 sid.HardReset(); vic.HardReset(); userPort.HardReset(); + cassPort.HardReset(); // because of how mapping works, the cpu needs to be hard reset twice cpu.HardReset(); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index 3d0028a017..81ab539f64 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -4,6 +4,7 @@ using System.IO; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Computers.Commodore64.MOS; +using System.Windows.Forms; namespace BizHawk.Emulation.Cores.Computers.Commodore64 { @@ -21,8 +22,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 isReleased: false )] [ServiceNotApplicable(typeof(IRegionable), typeof(ISettable<,>))] - sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable - { + sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable + { // framework public C64(CoreComm comm, GameInfo game, byte[] rom, string romextension) { @@ -33,13 +34,37 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 inputFileInfo.Data = rom; inputFileInfo.Extension = romextension; CoreComm = comm; - Init(Region.PAL); + Nullable region = queryUserForRegion(); + if (region == null) + { + throw new Exception("Can't construct new C64 because you didn't choose anything"); + } + Init(region.Value); cyclesPerFrame = board.vic.CyclesPerFrame; SetupMemoryDomains(); - HardReset(); + MemoryCallbacks = new MemoryCallbackSystem(); + HardReset(); (ServiceProvider as BasicServiceProvider).Register(board.vic); - } + } + + + private Nullable queryUserForRegion() + { + Form prompt = new Form() { Width = 160, Height = 120, FormBorderStyle = FormBorderStyle.FixedDialog, Text = "Region selector", StartPosition = FormStartPosition.CenterScreen }; + Label textLabel = new Label() { Left = 10, Top = 10, Width = 260, Text = "Please choose a region:" }; + RadioButton palButton = new RadioButton() { Left = 10, Top = 30, Width = 70, Text = "PAL", Checked = true }; + RadioButton ntscButton = new RadioButton() { Left = 80, Top = 30, Width = 70, Text = "NTSC" }; + Button confirmation = new Button() { Text = "Ok", Left = 40, Width = 80, Top = 60, DialogResult = DialogResult.OK }; + confirmation.Click += (sender, e) => { prompt.Close(); }; + prompt.Controls.Add(textLabel); + prompt.Controls.Add(palButton); + prompt.Controls.Add(ntscButton); + prompt.Controls.Add(confirmation); + prompt.AcceptButton = confirmation; + + return prompt.ShowDialog() == DialogResult.OK ? palButton.Checked ? new Nullable(Region.PAL) : ntscButton.Checked ? new Nullable(Region.NTSC) : null : null; + } // internal variables private int _frame = 0; @@ -63,7 +88,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 _frame = 0; _lagcount = 0; _islag = false; - } + frameCycles = 0; + } // audio/video public void EndAsyncSound() { } //TODO @@ -101,79 +127,69 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } } - // process frame - public void FrameAdvance(bool render, bool rendersound) + int frameCycles; + + private void DoCycle() + { + if (frameCycles == 0) { + board.inputRead = false; + board.PollInput(); + board.cpu.LagCycles = 0; + } + + //disk.Execute(); + board.Execute(); + frameCycles++; + + // load PRG file if needed + if (loadPrg) + { + // check to see if cpu PC is at the BASIC warm start vector + if (board.cpu.PC == ((board.ram.Peek(0x0303) << 8) | board.ram.Peek(0x0302))) + { + //board.ram.Poke(0x0302, 0xAE); + //board.ram.Poke(0x0303, 0xA7); + ////board.ram.Poke(0x0302, board.ram.Peek(0x0308)); + ////board.ram.Poke(0x0303, board.ram.Peek(0x0309)); + + //if (inputFileInfo.Data.Length >= 6) + //{ + // board.ram.Poke(0x0039, inputFileInfo.Data[4]); + // board.ram.Poke(0x003A, inputFileInfo.Data[5]); + //} + PRG.Load(board.pla, inputFileInfo.Data); + loadPrg = false; + } + } + + if (frameCycles == cyclesPerFrame) + { + board.Flush(); + _islag = !board.inputRead; + + if (_islag) + _lagcount++; + frameCycles -= cyclesPerFrame; + _frame++; + + //Console.WriteLine("CPUPC: " + C64Util.ToHex(board.cpu.PC, 4) + " 1541PC: " + C64Util.ToHex(disk.PC, 4)); + + int test = board.cpu.LagCycles; + DriveLightOn = DriveLED; + } + } + + // process frame + public void FrameAdvance(bool render, bool rendersound) { - board.inputRead = false; - board.PollInput(); - board.cpu.LagCycles = 0; + do + { + DoCycle(); + } + while (frameCycles != 0); + } - for (int count = 0; count < cyclesPerFrame; count++) - { - //disk.Execute(); - board.Execute(); - -#if false - if (board.cpu.PC == 0xE16F && (board.cpu.ReadPort() & 0x7) == 7) - { - // HUGE kernal hack to load files - // the only purpose for this is to be able to run the Lorenz - // test suite! - - int fileNameLength = board.ram.Peek(0xB7); - int fileNameOffset = board.ram.Peek(0xBB) | ((int)board.ram.Peek(0xBC) << 8); - byte[] fileNameRaw = new byte[fileNameLength]; - for (int i = 0; i < fileNameLength; i++) - { - fileNameRaw[i] = board.ram.Peek(fileNameOffset + i); - } - var enc = System.Text.Encoding.ASCII; - string fileName = enc.GetString(fileNameRaw); - string filePath = Path.Combine(@"E:\Programming\Visual Studio 2013\Vice\testprogs\general\Lorenz-2.15\src\", fileName + ".prg"); - if (File.Exists(filePath)) - { - PRG.Load(board.pla, File.ReadAllBytes(filePath)); - } - board.cpu.PC = 0xE1B5; - } -#endif - - // load PRG file if needed - if (loadPrg) - { - // check to see if cpu PC is at the BASIC warm start vector - if (board.cpu.PC == ((board.ram.Peek(0x0303) << 8) | board.ram.Peek(0x0302))) - { - //board.ram.Poke(0x0302, 0xAE); - //board.ram.Poke(0x0303, 0xA7); - ////board.ram.Poke(0x0302, board.ram.Peek(0x0308)); - ////board.ram.Poke(0x0303, board.ram.Peek(0x0309)); - - //if (inputFileInfo.Data.Length >= 6) - //{ - // board.ram.Poke(0x0039, inputFileInfo.Data[4]); - // board.ram.Poke(0x003A, inputFileInfo.Data[5]); - //} - PRG.Load(board.pla, inputFileInfo.Data); - loadPrg = false; - } - } - } - - board.Flush(); - _islag = !board.inputRead; - - if (_islag) - _lagcount++; - _frame++; - - //Console.WriteLine("CPUPC: " + C64Util.ToHex(board.cpu.PC, 4) + " 1541PC: " + C64Util.ToHex(disk.PC, 4)); - - int test = board.cpu.LagCycles; - DriveLightOn = DriveLED; - } - - private void HandleFirmwareError(string file) + private void HandleFirmwareError(string file) { System.Windows.Forms.MessageBox.Show("the C64 core is referencing a firmware file which could not be found. Please make sure it's in your configured C64 firmwares folder. The referenced filename is: " + file); throw new FileNotFoundException(); @@ -212,6 +228,13 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 { board.cartPort.Connect(cart); } + break; + case @".TAP": + CassettePort.Tape tape = CassettePort.Tape.Load(inputFileInfo.Data); + if (tape != null) + { + board.cassPort.Connect(tape); + } break; case @".PRG": if (inputFileInfo.Data.Length > 2) @@ -238,5 +261,5 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 board.HardReset(); //disk.HardReset(); } - } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs index d8f567713c..970b326e37 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs @@ -9,26 +9,33 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort { public class CassettePortDevice { - public Func ReadDataOutput; - public Func ReadMotor; + public Func ReadDataOutput; + public Func ReadMotor; + Commodore64.CassettePort.Tape tape; - public void HardReset() - { - } + public void HardReset() + { + if (tape != null) tape.rewind(); + } - virtual public bool ReadDataInputBuffer() - { - return true; - } + virtual public bool ReadDataInputBuffer() + { + return tape != null && !ReadMotor() ? tape.read() : true; + } - virtual public bool ReadSenseBuffer() - { - return true; - } + virtual public bool ReadSenseBuffer() + { + return tape == null; // Just assume that "play" is constantly pressed as long as a tape is inserted + } - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - } + public void SyncState(Serializer ser) + { + SaveState.SyncObject(ser, this); + } + + internal void Connect(Tape tape) + { + this.tape = tape; + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs new file mode 100644 index 0000000000..9b0c786f9d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +using BizHawk.Common; + + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort +{ + /** + * This class represents a tape. Only TAP-style tapes are supported for now. + */ + class Tape + { + private byte[] tapeData; + private byte version; + private uint pos, cycle, start, end; + + public Tape(byte version, byte[] tapeData, uint start, uint end) + { + this.version = version; + this.tapeData = tapeData; + this.start = start; + this.end = end; + rewind(); + } + + // Rewinds the tape back to start + public void rewind() + { + pos = start; + cycle = 0; + } + + // Reads from tape, this will tell the caller if the flag pin should be raised + public bool read() + { + if (cycle == 0) + { + Console.WriteLine("Tape @ " + pos.ToString()); + if (pos >= end) + { + return true; + } + else + { + cycle = ((uint)tapeData[pos++])*8; + if (cycle == 0) + { + if (version == 0) + { + cycle = 256 * 8; // unspecified overflow condition + } + else + { + cycle = BitConverter.ToUInt32(tapeData, (int)pos-1)>>8; + pos += 3; + if (cycle == 0) + { + throw new Exception("Bad tape data"); + } + } + } + } + } + + // Send a single negative pulse at the end of a cycle + return --cycle != 0; + } + + // Try to construct a tape file from file data. Returns null if not a tape file, throws exceptions for bad tape files. + // (Note that some error conditions aren't caught right here.) + static public Tape Load(byte[] tapeFile) + { + Tape result = null; + + if (System.Text.Encoding.ASCII.GetString(tapeFile, 0, 12) == "C64-TAPE-RAW") + { + byte version = tapeFile[12]; + if (version > 1) throw new Exception("This tape has an unsupported version"); + uint size = BitConverter.ToUInt32(tapeFile, 16); + if (size + 20 != tapeFile.Length) + { + throw new Exception("Tape file header specifies a length that doesn't match the file size"); + } + result = new Tape(version, tapeFile, 20, (uint)tapeFile.Length); + } + return result; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs deleted file mode 100644 index 26ddeb1b49..0000000000 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/CassettePort.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using BizHawk.Common; - -namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS -{ - public class CassettePort - { - public Func ReadDataOutput; - public Func ReadMotor; - - public void HardReset() - { - } - - virtual public bool ReadDataInputBuffer() - { - return true; - } - - virtual public bool ReadSenseBuffer() - { - return true; - } - - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs index 557312b0a9..6149c4cf42 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs @@ -103,9 +103,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS } } - // ------------------------------------ + internal bool AtInstructionStart() + { + return cpu.AtInstructionStart(); + } - public ushort PC + // ------------------------------------ + + public ushort PC { get { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs index 97a8ab2185..341fd8a728 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs @@ -3,791 +3,795 @@ using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { - // MOS technology 6526 "CIA" - // - // emulation notes: - // * CS, R/W and RS# pins are not emulated. (not needed) - // * A low RES pin is emulated via HardReset(). - - sealed public class MOS6526 - { - // ------------------------------------ - - enum InMode - { - Phase2, - CNT, - TimerAUnderflow, - TimerAUnderflowCNT - } - - enum OutMode - { - Pulse, - Toggle - } - - enum RunMode - { - Continuous, - Oneshot - } - - enum SPMode - { - Input, - Output - } - - static byte[] PBOnBit = new byte[] { 0x40, 0x80 }; - static byte[] PBOnMask = new byte[] { 0xBF, 0x7F }; - - // ------------------------------------ - - public Func ReadCNT; - public Func ReadFlag; - public Func ReadSP; - - // ------------------------------------ - - bool alarmSelect; - Region chipRegion; - bool cntPos; - bool enableIntAlarm; - bool enableIntFlag; - bool enableIntSP; - bool[] enableIntTimer; - bool intAlarm; - bool intFlag; - bool intSP; - bool[] intTimer; - bool pinCnt; - bool pinCntLast; - bool pinPC; - bool pinSP; - byte sr; - int[] timerDelay; - InMode[] timerInMode; - OutMode[] timerOutMode; - bool[] timerPortEnable; - bool[] timerPulse; - RunMode[] timerRunMode; - SPMode timerSPMode; - byte[] tod; - byte[] todAlarm; - bool todAlarmPM; - int todCounter; - int todCounterLatch; - bool todIn; - bool todPM; - - // ------------------------------------ - - public MOS6526(Region region) - { - chipRegion = region; - enableIntTimer = new bool[2]; - intTimer = new bool[2]; - timerDelay = new int[2]; - timerInMode = new InMode[2]; - timerOutMode = new OutMode[2]; - timerPortEnable = new bool[2]; - timerPulse = new bool[2]; - timerRunMode = new RunMode[2]; - tod = new byte[4]; - todAlarm = new byte[4]; - SetTodIn(chipRegion); - - portA = new LatchedPort(); - portB = new LatchedPort(); - timer = new int[2]; - timerLatch = new int[2]; - timerOn = new bool[2]; - underflow = new bool[2]; - - pinSP = true; - } - - // ------------------------------------ - - public void ExecutePhase1() - { - // unsure if the timer actually operates in ph1 - pinIRQ = !( - (intTimer[0] && enableIntTimer[0]) || - (intTimer[1] && enableIntTimer[1]) || - (intAlarm && enableIntAlarm) || - (intSP && enableIntSP) || - (intFlag && enableIntFlag) - ); - } - - public void ExecutePhase2() - { - { - bool sumCnt = ReadCNT(); - cntPos |= (!pinCntLast && sumCnt); - pinCntLast = sumCnt; - - pinPC = true; - TODRun(); - - if (timerPulse[0]) - { - portA.Latch &= PBOnMask[0]; - } - if (timerPulse[1]) - { - portB.Latch &= PBOnMask[1]; - } - - if (timerDelay[0] == 0) - TimerRun(0); - else - timerDelay[0]--; - - if (timerDelay[1] == 0) - TimerRun(1); - else - timerDelay[1]--; - - intAlarm |= ( - tod[0] == todAlarm[0] && - tod[1] == todAlarm[1] && - tod[2] == todAlarm[2] && - tod[3] == todAlarm[3] && - todPM == todAlarmPM); - - cntPos = false; - underflow[0] = false; - underflow[1] = false; - } - } - - public void HardReset() - { - HardResetInternal(); - alarmSelect = false; - cntPos = false; - enableIntAlarm = false; - enableIntFlag = false; - enableIntSP = false; - enableIntTimer[0] = false; - enableIntTimer[1] = false; - intAlarm = false; - intFlag = false; - intSP = false; - intTimer[0] = false; - intTimer[1] = false; - sr = 0; - timerDelay[0] = 0; - timerDelay[1] = 0; - timerInMode[0] = InMode.Phase2; - timerInMode[1] = InMode.Phase2; - timerOn[0] = false; - timerOn[1] = false; - timerOutMode[0] = OutMode.Pulse; - timerOutMode[1] = OutMode.Pulse; - timerPortEnable[0] = false; - timerPortEnable[1] = false; - timerPulse[0] = false; - timerPulse[1] = false; - timerRunMode[0] = RunMode.Continuous; - timerRunMode[1] = RunMode.Continuous; - timerSPMode = SPMode.Input; - tod[0] = 0; - tod[1] = 0; - tod[2] = 0; - tod[3] = 0x12; - todAlarm[0] = 0; - todAlarm[1] = 0; - todAlarm[2] = 0; - todAlarm[3] = 0; - todCounter = todCounterLatch; - todIn = (chipRegion == Region.PAL); - todPM = false; - - pinCnt = false; - pinPC = true; - } - - private void SetTodIn(Region region) - { - switch (region) - { - case Region.NTSC: - todCounterLatch = 14318181 / 140; - todIn = false; - break; - case Region.PAL: - todCounterLatch = 17734472 / 180; - todIn = true; - break; - } - } - - // ------------------------------------ - - private byte BCDAdd(byte i, byte j, out bool overflow) - { - - { - int lo; - int hi; - int result; - - lo = (i & 0x0F) + (j & 0x0F); - hi = (i & 0x70) + (j & 0x70); - if (lo > 0x09) - { - hi += 0x10; - lo += 0x06; - } - if (hi > 0x50) - { - hi += 0xA0; - } - overflow = hi >= 0x60; - result = (hi & 0x70) + (lo & 0x0F); - return (byte)(result & 0xFF); - } - } - - private void TimerRun(int index) - { - - { - if (timerOn[index]) - { - int t = timer[index]; - bool u = false; - - { - switch (timerInMode[index]) - { - case InMode.CNT: - // CNT positive - if (cntPos) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - case InMode.Phase2: - // every clock - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - break; - case InMode.TimerAUnderflow: - // every underflow[0] - if (underflow[0]) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - case InMode.TimerAUnderflowCNT: - // every underflow[0] while CNT high - if (underflow[0] && pinCnt) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - } - - // underflow? - if (u) - { - timerDelay[index] = 1; - t = timerLatch[index]; - if (timerRunMode[index] == RunMode.Oneshot) - timerOn[index] = false; - - if (timerPortEnable[index]) - { - // force port B bit to output - portB.Direction |= PBOnBit[index]; - switch (timerOutMode[index]) - { - case OutMode.Pulse: - timerPulse[index] = true; - portB.Latch |= PBOnBit[index]; - break; - case OutMode.Toggle: - portB.Latch ^= PBOnBit[index]; - break; - } - } - } - - underflow[index] = u; - timer[index] = t; - } - } - } - } - - private void TODRun() - { - - { - bool todV; - - if (todCounter == 0) - { - todCounter = todCounterLatch; - tod[0] = BCDAdd(tod[0], 1, out todV); - if (tod[0] >= 10) - { - tod[0] = 0; - tod[1] = BCDAdd(tod[1], 1, out todV); - if (todV) - { - tod[1] = 0; - tod[2] = BCDAdd(tod[2], 1, out todV); - if (todV) - { - tod[2] = 0; - tod[3] = BCDAdd(tod[3], 1, out todV); - if (tod[3] > 12) - { - tod[3] = 1; - } - else if (tod[3] == 12) - { - todPM = !todPM; - } - } - } - } - } - todCounter--; - } - } - - // ------------------------------------ - - public byte Peek(long addr) - { - return ReadRegister((int)addr & 0xF); - } - - public void Poke(long addr, byte val) - { - WriteRegister((int)(addr & 0xF), val); - } - - public byte Peek(int addr) - { - return ReadRegister((int)addr & 0xF); - } - - public void Poke(int addr, byte val) - { - WriteRegister((int)(addr & 0xF), val); - } - - public byte Read(int addr) - { - return Read(addr, 0xFF); - } - - public byte Read(int addr, byte mask) - { - addr &= 0xF; - byte val; - - switch (addr) - { - case 0x01: - val = ReadRegister(addr); - pinPC = false; - break; - case 0x0D: - val = ReadRegister(addr); - intTimer[0] = false; - intTimer[1] = false; - intAlarm = false; - intSP = false; - intFlag = false; - pinIRQ = true; - break; - default: - val = ReadRegister(addr); - break; - } - - val &= mask; - return val; - } - - public bool ReadCNTBuffer() - { - return pinCnt; - } - - public bool ReadPCBuffer() - { - return pinPC; - } - - private byte ReadRegister(int addr) - { - byte val = 0x00; //unused pin value - int timerVal; - - switch (addr) - { - case 0x0: - val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask); - break; - case 0x1: - val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask); - break; - case 0x2: - val = portA.Direction; - break; - case 0x3: - val = portB.Direction; - break; - case 0x4: - timerVal = ReadTimerValue(0); - val = (byte)(timerVal & 0xFF); - break; - case 0x5: - timerVal = ReadTimerValue(0); - val = (byte)(timerVal >> 8); - break; - case 0x6: - timerVal = ReadTimerValue(1); - val = (byte)(timerVal & 0xFF); - break; - case 0x7: - timerVal = ReadTimerValue(1); - val = (byte)(timerVal >> 8); - break; - case 0x8: - val = tod[0]; - break; - case 0x9: - val = tod[1]; - break; - case 0xA: - val = tod[2]; - break; - case 0xB: - val = tod[3]; - break; - case 0xC: - val = sr; - break; - case 0xD: - val = (byte)( - (intTimer[0] ? 0x01 : 0x00) | - (intTimer[1] ? 0x02 : 0x00) | - (intAlarm ? 0x04 : 0x00) | - (intSP ? 0x08 : 0x00) | - (intFlag ? 0x10 : 0x00) | - (!pinIRQ ? 0x80 : 0x00) - ); - break; - case 0xE: - val = (byte)( - (timerOn[0] ? 0x01 : 0x00) | - (timerPortEnable[0] ? 0x02 : 0x00) | - (todIn ? 0x80 : 0x00)); - if (timerOutMode[0] == OutMode.Toggle) - val |= 0x04; - if (timerRunMode[0] == RunMode.Oneshot) - val |= 0x08; - if (timerInMode[0] == InMode.CNT) - val |= 0x20; - if (timerSPMode == SPMode.Output) - val |= 0x40; - break; - case 0xF: - val = (byte)( - (timerOn[1] ? 0x01 : 0x00) | - (timerPortEnable[1] ? 0x02 : 0x00) | - (alarmSelect ? 0x80 : 0x00)); - if (timerOutMode[1] == OutMode.Toggle) - val |= 0x04; - if (timerRunMode[1] == RunMode.Oneshot) - val |= 0x08; - switch (timerInMode[1]) - { - case InMode.CNT: - val |= 0x20; - break; - case InMode.TimerAUnderflow: - val |= 0x40; - break; - case InMode.TimerAUnderflowCNT: - val |= 0x60; - break; - } - break; - } - - return val; - } - - public bool ReadSPBuffer() - { - return pinSP; - } - - private int ReadTimerValue(int index) - { - if (timerOn[index]) - { - if (timer[index] == 0) - return timerLatch[index]; - else - return timer[index]; - } - else - { - return timer[index]; - } - } - - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - - public void Write(int addr, byte val) - { - Write(addr, val, 0xFF); - } - - public void Write(int addr, byte val, byte mask) - { - addr &= 0xF; - val &= mask; - val |= (byte)(ReadRegister(addr) & ~mask); - - switch (addr) - { - case 0x1: - WriteRegister(addr, val); - pinPC = false; - break; - case 0x5: - WriteRegister(addr, val); - if (!timerOn[0]) - timer[0] = timerLatch[0]; - break; - case 0x7: - WriteRegister(addr, val); - if (!timerOn[1]) - timer[1] = timerLatch[1]; - break; - case 0xE: - WriteRegister(addr, val); - if ((val & 0x10) != 0) - timer[0] = timerLatch[0]; - break; - case 0xF: - WriteRegister(addr, val); - if ((val & 0x10) != 0) - timer[1] = timerLatch[1]; - break; - default: - WriteRegister(addr, val); - break; - } - } - - public void WriteRegister(int addr, byte val) - { - bool intReg; - - switch (addr) - { - case 0x0: - portA.Latch = val; - break; - case 0x1: - portB.Latch = val; - break; - case 0x2: - portA.Direction = val; - break; - case 0x3: - portB.Direction = val; - break; - case 0x4: - timerLatch[0] &= 0xFF00; - timerLatch[0] |= val; - break; - case 0x5: - timerLatch[0] &= 0x00FF; - timerLatch[0] |= val << 8; - break; - case 0x6: - timerLatch[1] &= 0xFF00; - timerLatch[1] |= val; - break; - case 0x7: - timerLatch[1] &= 0x00FF; - timerLatch[1] |= val << 8; - break; - case 0x8: - if (alarmSelect) - todAlarm[0] = (byte)(val & 0xF); - else - tod[0] = (byte)(val & 0xF); - break; - case 0x9: - if (alarmSelect) - todAlarm[1] = (byte)(val & 0x7F); - else - tod[1] = (byte)(val & 0x7F); - break; - case 0xA: - if (alarmSelect) - todAlarm[2] = (byte)(val & 0x7F); - else - tod[2] = (byte)(val & 0x7F); - break; - case 0xB: - if (alarmSelect) - { - todAlarm[3] = (byte)(val & 0x1F); - todAlarmPM = ((val & 0x80) != 0); - } - else - { - tod[3] = (byte)(val & 0x1F); - todPM = ((val & 0x80) != 0); - } - break; - case 0xC: - sr = val; - break; - case 0xD: - intReg = ((val & 0x80) != 0); - if ((val & 0x01) != 0) - enableIntTimer[0] = intReg; - if ((val & 0x02) != 0) - enableIntTimer[1] = intReg; - if ((val & 0x04) != 0) - enableIntAlarm = intReg; - if ((val & 0x08) != 0) - enableIntSP = intReg; - if ((val & 0x10) != 0) - enableIntFlag = intReg; - break; - case 0xE: - if ((val & 0x01) != 0 && !timerOn[0]) - timerDelay[0] = 2; - timerOn[0] = ((val & 0x01) != 0); - timerPortEnable[0] = ((val & 0x02) != 0); - timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; - timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; - timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2; - timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input; - todIn = ((val & 0x80) != 0); - break; - case 0xF: - if ((val & 0x01) != 0 && !timerOn[1]) - timerDelay[1] = 2; - timerOn[1] = ((val & 0x01) != 0); - timerPortEnable[1] = ((val & 0x02) != 0); - timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; - timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; - switch (val & 0x60) - { - case 0x00: timerInMode[1] = InMode.Phase2; break; - case 0x20: timerInMode[1] = InMode.CNT; break; - case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break; - case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break; - } - alarmSelect = ((val & 0x80) != 0); - break; - } - } - - // ------------------------------------ - - public byte PortAMask = 0xFF; - public byte PortBMask = 0xFF; - - bool pinIRQ; - LatchedPort portA; - LatchedPort portB; - int[] timer; - int[] timerLatch; - bool[] timerOn; - bool[] underflow; - - public Func ReadPortA = (() => { return 0xFF; }); - public Func ReadPortB = (() => { return 0xFF; }); - - void HardResetInternal() - { - timer[0] = 0xFFFF; - timer[1] = 0xFFFF; - timerLatch[0] = timer[0]; - timerLatch[1] = timer[1]; - pinIRQ = true; - } - - public byte PortAData - { - get - { - return portA.ReadOutput(); - } - } - - public byte PortADirection - { - get - { - return portA.Direction; - } - } - - public byte PortALatch - { - get - { - return portA.Latch; - } - } - - public byte PortBData - { - get - { - return portB.ReadOutput(); - } - } - - public byte PortBDirection - { - get - { - return portB.Direction; - } - } - - public byte PortBLatch - { - get - { - return portB.Latch; - } - } - - public bool ReadIRQBuffer() { return pinIRQ; } - } + // MOS technology 6526 "CIA" + // + // emulation notes: + // * CS, R/W and RS# pins are not emulated. (not needed) + // * A low RES pin is emulated via HardReset(). + + sealed public class MOS6526 + { + // ------------------------------------ + + enum InMode + { + Phase2, + CNT, + TimerAUnderflow, + TimerAUnderflowCNT + } + + enum OutMode + { + Pulse, + Toggle + } + + enum RunMode + { + Continuous, + Oneshot + } + + enum SPMode + { + Input, + Output + } + + static byte[] PBOnBit = new byte[] { 0x40, 0x80 }; + static byte[] PBOnMask = new byte[] { 0xBF, 0x7F }; + + // ------------------------------------ + + public Func ReadCNT; + public Func ReadFlag; + public Func ReadSP; + + // ------------------------------------ + + bool alarmSelect; + Region chipRegion; + bool cntPos; + bool enableIntAlarm; + bool enableIntFlag; + bool enableIntSP; + bool[] enableIntTimer; + bool intAlarm; + bool intFlag; + bool intSP; + bool[] intTimer; + bool pinCnt; + bool pinCntLast; + bool pinPC; + bool pinSP; + byte sr; + int[] timerDelay; + InMode[] timerInMode; + OutMode[] timerOutMode; + bool[] timerPortEnable; + bool[] timerPulse; + RunMode[] timerRunMode; + SPMode timerSPMode; + byte[] tod; + byte[] todAlarm; + bool todAlarmPM; + int todCounter; + int todCounterLatch; + bool todIn; + bool todPM; + bool oldFlag; + // ------------------------------------ + + public MOS6526(Region region) + { + chipRegion = region; + enableIntTimer = new bool[2]; + intTimer = new bool[2]; + timerDelay = new int[2]; + timerInMode = new InMode[2]; + timerOutMode = new OutMode[2]; + timerPortEnable = new bool[2]; + timerPulse = new bool[2]; + timerRunMode = new RunMode[2]; + tod = new byte[4]; + todAlarm = new byte[4]; + SetTodIn(chipRegion); + + portA = new LatchedPort(); + portB = new LatchedPort(); + timer = new int[2]; + timerLatch = new int[2]; + timerOn = new bool[2]; + underflow = new bool[2]; + + pinSP = true; + } + + // ------------------------------------ + + public void ExecutePhase1() + { + // unsure if the timer actually operates in ph1 + pinIRQ = !( + (intTimer[0] && enableIntTimer[0]) || + (intTimer[1] && enableIntTimer[1]) || + (intAlarm && enableIntAlarm) || + (intSP && enableIntSP) || + (intFlag && enableIntFlag) + ); + } + + public void ExecutePhase2() + { + { + bool sumCnt = ReadCNT(); + cntPos |= (!pinCntLast && sumCnt); + pinCntLast = sumCnt; + + pinPC = true; + TODRun(); + + if (timerPulse[0]) + { + portA.Latch &= PBOnMask[0]; + } + if (timerPulse[1]) + { + portB.Latch &= PBOnMask[1]; + } + + if (timerDelay[0] == 0) + TimerRun(0); + else + timerDelay[0]--; + + if (timerDelay[1] == 0) + TimerRun(1); + else + timerDelay[1]--; + + intAlarm |= ( + tod[0] == todAlarm[0] && + tod[1] == todAlarm[1] && + tod[2] == todAlarm[2] && + tod[3] == todAlarm[3] && + todPM == todAlarmPM); + + cntPos = false; + underflow[0] = false; + underflow[1] = false; + + bool newFlag = ReadFlag(); + intFlag |= oldFlag && !newFlag; + oldFlag = newFlag; + } + } + + public void HardReset() + { + HardResetInternal(); + alarmSelect = false; + cntPos = false; + enableIntAlarm = false; + enableIntFlag = false; + enableIntSP = false; + enableIntTimer[0] = false; + enableIntTimer[1] = false; + intAlarm = false; + intFlag = false; + intSP = false; + intTimer[0] = false; + intTimer[1] = false; + sr = 0; + timerDelay[0] = 0; + timerDelay[1] = 0; + timerInMode[0] = InMode.Phase2; + timerInMode[1] = InMode.Phase2; + timerOn[0] = false; + timerOn[1] = false; + timerOutMode[0] = OutMode.Pulse; + timerOutMode[1] = OutMode.Pulse; + timerPortEnable[0] = false; + timerPortEnable[1] = false; + timerPulse[0] = false; + timerPulse[1] = false; + timerRunMode[0] = RunMode.Continuous; + timerRunMode[1] = RunMode.Continuous; + timerSPMode = SPMode.Input; + tod[0] = 0; + tod[1] = 0; + tod[2] = 0; + tod[3] = 0x12; + todAlarm[0] = 0; + todAlarm[1] = 0; + todAlarm[2] = 0; + todAlarm[3] = 0; + todCounter = todCounterLatch; + todIn = (chipRegion == Region.PAL); + todPM = false; + + pinCnt = false; + pinPC = true; + } + + private void SetTodIn(Region region) + { + switch (region) + { + case Region.NTSC: + todCounterLatch = 14318181 / 140; + todIn = false; + break; + case Region.PAL: + todCounterLatch = 17734472 / 180; + todIn = true; + break; + } + } + + // ------------------------------------ + + private byte BCDAdd(byte i, byte j, out bool overflow) + { + + { + int lo; + int hi; + int result; + + lo = (i & 0x0F) + (j & 0x0F); + hi = (i & 0x70) + (j & 0x70); + if (lo > 0x09) + { + hi += 0x10; + lo += 0x06; + } + if (hi > 0x50) + { + hi += 0xA0; + } + overflow = hi >= 0x60; + result = (hi & 0x70) + (lo & 0x0F); + return (byte)(result & 0xFF); + } + } + + private void TimerRun(int index) + { + + { + if (timerOn[index]) + { + int t = timer[index]; + bool u = false; + + { + switch (timerInMode[index]) + { + case InMode.CNT: + // CNT positive + if (cntPos) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + case InMode.Phase2: + // every clock + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + break; + case InMode.TimerAUnderflow: + // every underflow[0] + if (underflow[0]) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + case InMode.TimerAUnderflowCNT: + // every underflow[0] while CNT high + if (underflow[0] && pinCnt) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + } + + // underflow? + if (u) + { + timerDelay[index] = 1; + t = timerLatch[index]; + if (timerRunMode[index] == RunMode.Oneshot) + timerOn[index] = false; + + if (timerPortEnable[index]) + { + // force port B bit to output + portB.Direction |= PBOnBit[index]; + switch (timerOutMode[index]) + { + case OutMode.Pulse: + timerPulse[index] = true; + portB.Latch |= PBOnBit[index]; + break; + case OutMode.Toggle: + portB.Latch ^= PBOnBit[index]; + break; + } + } + } + + underflow[index] = u; + timer[index] = t; + } + } + } + } + + private void TODRun() + { + + { + bool todV; + + if (todCounter == 0) + { + todCounter = todCounterLatch; + tod[0] = BCDAdd(tod[0], 1, out todV); + if (tod[0] >= 10) + { + tod[0] = 0; + tod[1] = BCDAdd(tod[1], 1, out todV); + if (todV) + { + tod[1] = 0; + tod[2] = BCDAdd(tod[2], 1, out todV); + if (todV) + { + tod[2] = 0; + tod[3] = BCDAdd(tod[3], 1, out todV); + if (tod[3] > 12) + { + tod[3] = 1; + } + else if (tod[3] == 12) + { + todPM = !todPM; + } + } + } + } + } + todCounter--; + } + } + + // ------------------------------------ + + public byte Peek(long addr) + { + return ReadRegister((int)addr & 0xF); + } + + public void Poke(long addr, byte val) + { + WriteRegister((int)(addr & 0xF), val); + } + + public byte Peek(int addr) + { + return ReadRegister((int)addr & 0xF); + } + + public void Poke(int addr, byte val) + { + WriteRegister((int)(addr & 0xF), val); + } + + public byte Read(int addr) + { + return Read(addr, 0xFF); + } + + public byte Read(int addr, byte mask) + { + addr &= 0xF; + byte val; + + switch (addr) + { + case 0x01: + val = ReadRegister(addr); + pinPC = false; + break; + case 0x0D: + val = ReadRegister(addr); + intTimer[0] = false; + intTimer[1] = false; + intAlarm = false; + intSP = false; + intFlag = false; + pinIRQ = true; + break; + default: + val = ReadRegister(addr); + break; + } + + val &= mask; + return val; + } + + public bool ReadCNTBuffer() + { + return pinCnt; + } + + public bool ReadPCBuffer() + { + return pinPC; + } + + private byte ReadRegister(int addr) + { + byte val = 0x00; //unused pin value + int timerVal; + + switch (addr) + { + case 0x0: + val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask); + break; + case 0x1: + val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask); + break; + case 0x2: + val = portA.Direction; + break; + case 0x3: + val = portB.Direction; + break; + case 0x4: + timerVal = ReadTimerValue(0); + val = (byte)(timerVal & 0xFF); + break; + case 0x5: + timerVal = ReadTimerValue(0); + val = (byte)(timerVal >> 8); + break; + case 0x6: + timerVal = ReadTimerValue(1); + val = (byte)(timerVal & 0xFF); + break; + case 0x7: + timerVal = ReadTimerValue(1); + val = (byte)(timerVal >> 8); + break; + case 0x8: + val = tod[0]; + break; + case 0x9: + val = tod[1]; + break; + case 0xA: + val = tod[2]; + break; + case 0xB: + val = tod[3]; + break; + case 0xC: + val = sr; + break; + case 0xD: + val = (byte)( + (intTimer[0] ? 0x01 : 0x00) | + (intTimer[1] ? 0x02 : 0x00) | + (intAlarm ? 0x04 : 0x00) | + (intSP ? 0x08 : 0x00) | + (intFlag ? 0x10 : 0x00) | + (!pinIRQ ? 0x80 : 0x00) + ); + break; + case 0xE: + val = (byte)( + (timerOn[0] ? 0x01 : 0x00) | + (timerPortEnable[0] ? 0x02 : 0x00) | + (todIn ? 0x80 : 0x00)); + if (timerOutMode[0] == OutMode.Toggle) + val |= 0x04; + if (timerRunMode[0] == RunMode.Oneshot) + val |= 0x08; + if (timerInMode[0] == InMode.CNT) + val |= 0x20; + if (timerSPMode == SPMode.Output) + val |= 0x40; + break; + case 0xF: + val = (byte)( + (timerOn[1] ? 0x01 : 0x00) | + (timerPortEnable[1] ? 0x02 : 0x00) | + (alarmSelect ? 0x80 : 0x00)); + if (timerOutMode[1] == OutMode.Toggle) + val |= 0x04; + if (timerRunMode[1] == RunMode.Oneshot) + val |= 0x08; + switch (timerInMode[1]) + { + case InMode.CNT: + val |= 0x20; + break; + case InMode.TimerAUnderflow: + val |= 0x40; + break; + case InMode.TimerAUnderflowCNT: + val |= 0x60; + break; + } + break; + } + + return val; + } + + public bool ReadSPBuffer() + { + return pinSP; + } + + private int ReadTimerValue(int index) + { + if (timerOn[index]) + { + if (timer[index] == 0) + return timerLatch[index]; + else + return timer[index]; + } + else + { + return timer[index]; + } + } + + public void SyncState(Serializer ser) + { + SaveState.SyncObject(ser, this); + } + + public void Write(int addr, byte val) + { + Write(addr, val, 0xFF); + } + + public void Write(int addr, byte val, byte mask) + { + addr &= 0xF; + val &= mask; + val |= (byte)(ReadRegister(addr) & ~mask); + + switch (addr) + { + case 0x1: + WriteRegister(addr, val); + pinPC = false; + break; + case 0x5: + WriteRegister(addr, val); + if (!timerOn[0]) + timer[0] = timerLatch[0]; + break; + case 0x7: + WriteRegister(addr, val); + if (!timerOn[1]) + timer[1] = timerLatch[1]; + break; + case 0xE: + WriteRegister(addr, val); + if ((val & 0x10) != 0) + timer[0] = timerLatch[0]; + break; + case 0xF: + WriteRegister(addr, val); + if ((val & 0x10) != 0) + timer[1] = timerLatch[1]; + break; + default: + WriteRegister(addr, val); + break; + } + } + + public void WriteRegister(int addr, byte val) + { + bool intReg; + + switch (addr) + { + case 0x0: + portA.Latch = val; + break; + case 0x1: + portB.Latch = val; + break; + case 0x2: + portA.Direction = val; + break; + case 0x3: + portB.Direction = val; + break; + case 0x4: + timerLatch[0] &= 0xFF00; + timerLatch[0] |= val; + break; + case 0x5: + timerLatch[0] &= 0x00FF; + timerLatch[0] |= val << 8; + break; + case 0x6: + timerLatch[1] &= 0xFF00; + timerLatch[1] |= val; + break; + case 0x7: + timerLatch[1] &= 0x00FF; + timerLatch[1] |= val << 8; + break; + case 0x8: + if (alarmSelect) + todAlarm[0] = (byte)(val & 0xF); + else + tod[0] = (byte)(val & 0xF); + break; + case 0x9: + if (alarmSelect) + todAlarm[1] = (byte)(val & 0x7F); + else + tod[1] = (byte)(val & 0x7F); + break; + case 0xA: + if (alarmSelect) + todAlarm[2] = (byte)(val & 0x7F); + else + tod[2] = (byte)(val & 0x7F); + break; + case 0xB: + if (alarmSelect) + { + todAlarm[3] = (byte)(val & 0x1F); + todAlarmPM = ((val & 0x80) != 0); + } + else + { + tod[3] = (byte)(val & 0x1F); + todPM = ((val & 0x80) != 0); + } + break; + case 0xC: + sr = val; + break; + case 0xD: + intReg = ((val & 0x80) != 0); + if ((val & 0x01) != 0) + enableIntTimer[0] = intReg; + if ((val & 0x02) != 0) + enableIntTimer[1] = intReg; + if ((val & 0x04) != 0) + enableIntAlarm = intReg; + if ((val & 0x08) != 0) + enableIntSP = intReg; + if ((val & 0x10) != 0) + enableIntFlag = intReg; + break; + case 0xE: + if ((val & 0x01) != 0 && !timerOn[0]) + timerDelay[0] = 2; + timerOn[0] = ((val & 0x01) != 0); + timerPortEnable[0] = ((val & 0x02) != 0); + timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; + timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; + timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2; + timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input; + todIn = ((val & 0x80) != 0); + break; + case 0xF: + if ((val & 0x01) != 0 && !timerOn[1]) + timerDelay[1] = 2; + timerOn[1] = ((val & 0x01) != 0); + timerPortEnable[1] = ((val & 0x02) != 0); + timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; + timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; + switch (val & 0x60) + { + case 0x00: timerInMode[1] = InMode.Phase2; break; + case 0x20: timerInMode[1] = InMode.CNT; break; + case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break; + case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break; + } + alarmSelect = ((val & 0x80) != 0); + break; + } + } + + // ------------------------------------ + + public byte PortAMask = 0xFF; + public byte PortBMask = 0xFF; + + bool pinIRQ; + LatchedPort portA; + LatchedPort portB; + int[] timer; + int[] timerLatch; + bool[] timerOn; + bool[] underflow; + + public Func ReadPortA = (() => { return 0xFF; }); + public Func ReadPortB = (() => { return 0xFF; }); + + void HardResetInternal() + { + timer[0] = 0xFFFF; + timer[1] = 0xFFFF; + timerLatch[0] = timer[0]; + timerLatch[1] = timer[1]; + pinIRQ = true; + } + + public byte PortAData + { + get + { + return portA.ReadOutput(); + } + } + + public byte PortADirection + { + get + { + return portA.Direction; + } + } + + public byte PortALatch + { + get + { + return portA.Latch; + } + } + + public byte PortBData + { + get + { + return portB.ReadOutput(); + } + } + + public byte PortBDirection + { + get + { + return portB.Direction; + } + } + + public byte PortBLatch + { + get + { + return portB.Latch; + } + } + + public bool ReadIRQBuffer() { return pinIRQ; } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs index 939e8211f8..b68dc7af8f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs @@ -11,8 +11,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS static int vblankstart = 0x00D % lines; static int vblankend = 0x018 % lines; static int hblankoffset = 20; - static int hblankstart = (0x18C + hblankoffset) % scanwidth; - static int hblankend = (0x1F0 + hblankoffset) % scanwidth; + static int hblankstart = (0x18C + hblankoffset) % scanwidth - 8; // -8 because the VIC repeats internal pixel cycles around 0x18C + static int hblankend = (0x1F0 + hblankoffset) % scanwidth - 8; static int[] timing = Vic.TimingBuilder_XRaster(0x19C, 0x200, scanwidth, 0x18C, 8); static int[] fetch = Vic.TimingBuilder_Fetch(timing, 0x174); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs index ba587c6360..f58517536b 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs @@ -74,7 +74,10 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS lastRasterLine = rasterLine; } - // display enable compare + // display enable compare + if (rasterLine == 0) + badlineEnable = false; + if (rasterLine == 0x030) badlineEnable |= displayEnable; From f7c15bfd0ff5cac3156bf03d5ffa35c86e45696e Mon Sep 17 00:00:00 2001 From: Kabuto Date: Mon, 28 Sep 2015 20:53:19 +0200 Subject: [PATCH 02/24] Fixed indentation and TODOs --- BizHawk.Emulation.Common/Database/Database.cs | 2 +- .../CPUs/MOS 6502X/Execute.cs | 8 +- .../Computers/Commodore64/C64.IDebuggable.cs | 186 +- .../Commodore64/C64.IDisassemblable.cs | 44 +- .../Computers/Commodore64/C64.Motherboard.cs | 8 +- .../Computers/Commodore64/C64.cs | 209 ++- .../CassettePort/CassettePortDevice.cs | 48 +- .../Commodore64/CassettePort/Tape.cs | 150 +- .../Computers/Commodore64/MOS/MOS6510.cs | 18 +- .../Computers/Commodore64/MOS/MOS6526-2.cs | 6 +- .../Computers/Commodore64/MOS/MOS6526.cs | 1582 ++++++++--------- .../Computers/Commodore64/MOS/MOS6581.cs | 2 +- .../Computers/Commodore64/MOS/Sid.cs | 6 +- .../Computers/Commodore64/MOS/Vic.Parse.cs | 4 +- .../Computers/Commodore64/MOS/Vic.cs | 6 +- 15 files changed, 1136 insertions(+), 1143 deletions(-) diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index 41c4f8eaf2..ee30a95d48 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -315,7 +315,7 @@ namespace BizHawk.Emulation.Common case ".T64": case ".G64": case ".CRT": - case ".TAP": + case ".TAP": game.System = "C64"; break; diff --git a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs index 0c5e94c799..a79d0f8881 100644 --- a/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs @@ -2932,9 +2932,9 @@ namespace BizHawk.Emulation.Cores.Components.M6502 mi++; } //ExecuteOne - public bool AtInstructionStart() - { - return Microcode[opcode][mi] >= Uop.End; - } + public bool AtInstructionStart() + { + return Microcode[opcode][mi] >= Uop.End; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs index 6618958233..8e29874b02 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDebuggable.cs @@ -51,110 +51,110 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } } - public bool CanStep(StepType type) - { - switch (type) - { - case StepType.Into: - case StepType.Over: - case StepType.Out: - return true; - default: - return false; - } - } + public bool CanStep(StepType type) + { + switch (type) + { + case StepType.Into: + case StepType.Over: + case StepType.Out: + return true; + default: + return false; + } + } - public void Step(StepType type) - { - switch (type) - { - case StepType.Into: - StepInto(); - break; - case StepType.Out: - StepOut(); - break; - case StepType.Over: - StepOver(); - break; - } - } + public void Step(StepType type) + { + switch (type) + { + case StepType.Into: + StepInto(); + break; + case StepType.Out: + StepOut(); + break; + case StepType.Over: + StepOver(); + break; + } + } - private void StepInto() - { - while (board.cpu.AtInstructionStart()) - { - DoCycle(); - } - while (!board.cpu.AtInstructionStart()) - { - DoCycle(); - } - } + private void StepInto() + { + while (board.cpu.AtInstructionStart()) + { + DoCycle(); + } + while (!board.cpu.AtInstructionStart()) + { + DoCycle(); + } + } - private void StepOver() - { - var instruction = board.cpu.Peek(board.cpu.PC); + private void StepOver() + { + var instruction = board.cpu.Peek(board.cpu.PC); - if (instruction == JSR) - { - var destination = board.cpu.PC + JSRSize; - while (board.cpu.PC != destination) - { - StepInto(); - } - } - else - { - StepInto(); - } - } + if (instruction == JSR) + { + var destination = board.cpu.PC + JSRSize; + while (board.cpu.PC != destination) + { + StepInto(); + } + } + else + { + StepInto(); + } + } - private void StepOut() - { - var instr = board.cpu.Peek(board.cpu.PC); + private void StepOut() + { + var instr = board.cpu.Peek(board.cpu.PC); - JSRCount = instr == JSR ? 1 : 0; + JSRCount = instr == JSR ? 1 : 0; - var bailOutFrame = Frame + 1; + var bailOutFrame = Frame + 1; - while (true) - { - StepInto(); - instr = board.cpu.Peek(board.cpu.PC); - if (instr == JSR) - { - JSRCount++; - } - else if ((instr == RTS || instr == RTI) && JSRCount <= 0) - { - StepInto(); - JSRCount = 0; - break; - } - else if (instr == RTS || instr == RTI) - { - JSRCount--; - } - else //Emergency Bailout Logic - { - if (Frame == bailOutFrame) - { - break; - } - } - } - } + while (true) + { + StepInto(); + instr = board.cpu.Peek(board.cpu.PC); + if (instr == JSR) + { + JSRCount++; + } + else if ((instr == RTS || instr == RTI) && JSRCount <= 0) + { + StepInto(); + JSRCount = 0; + break; + } + else if (instr == RTS || instr == RTI) + { + JSRCount--; + } + else //Emergency Bailout Logic + { + if (Frame == bailOutFrame) + { + break; + } + } + } + } - private int JSRCount = 0; + private int JSRCount = 0; - private const byte JSR = 0x20; - private const byte RTI = 0x40; - private const byte RTS = 0x60; + private const byte JSR = 0x20; + private const byte RTI = 0x40; + private const byte RTS = 0x60; - private const byte JSRSize = 3; + private const byte JSRSize = 3; - public IMemoryCallbackSystem MemoryCallbacks { get; private set; } - } + public IMemoryCallbackSystem MemoryCallbacks { get; private set; } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs index a24503805c..593b624e2f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs @@ -5,32 +5,26 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64 { - public partial class C64 : IDisassemblable - { - public string Cpu - { - get - { - return "6510"; - } - set - { - } - } + public partial class C64 : IDisassemblable + { + public string Cpu + { + get { return "6510"; } + } - public string PCRegisterName - { - get { return "PC"; } - } + public string PCRegisterName + { + get { return "PC"; } + } - public IEnumerable AvailableCpus - { - get { yield return "6510"; } - } + public IEnumerable AvailableCpus + { + get { yield return "6510"; } + } - public string Disassemble(MemoryDomain m, uint addr, out int length) - { - return Components.M6502.MOS6502X.Disassemble((ushort)addr, out length, (a) => m.PeekByte(a)); - } - } + public string Disassemble(MemoryDomain m, uint addr, out int length) + { + return Components.M6502.MOS6502X.Disassemble((ushort)addr, out length, (a) => m.PeekByte(a)); + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs index e36539f7b7..9336a2b329 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs @@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 private C64 _c64; - public Motherboard(C64 c64, Region initRegion) + public Motherboard(C64 c64, DisplayType initRegion) { // note: roms need to be added on their own externally _c64 = c64; @@ -59,8 +59,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 sid = MOS6581.Create(44100, initRegion); switch (initRegion) { - case Region.NTSC: vic = MOS6567.Create(); break; - case Region.PAL: vic = MOS6569.Create(); break; + case DisplayType.NTSC: vic = MOS6567.Create(); break; + case DisplayType.PAL: vic = MOS6569.Create(); break; } userPort = new UserPortDevice(); } @@ -102,7 +102,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 sid.HardReset(); vic.HardReset(); userPort.HardReset(); - cassPort.HardReset(); + cassPort.HardReset(); // because of how mapping works, the cpu needs to be hard reset twice cpu.HardReset(); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index 81ab539f64..926e0f3eed 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -8,22 +8,15 @@ using System.Windows.Forms; namespace BizHawk.Emulation.Cores.Computers.Commodore64 { - // TODO: use the EMulation.Common Region enum - public enum Region - { - NTSC, - PAL - } - [CoreAttributes( "C64Hawk", "SaxxonPIke", isPorted: false, isReleased: false )] - [ServiceNotApplicable(typeof(IRegionable), typeof(ISettable<,>))] - sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable - { + [ServiceNotApplicable(typeof(ISettable<,>))] + sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable, IRegionable + { // framework public C64(CoreComm comm, GameInfo game, byte[] rom, string romextension) { @@ -34,37 +27,37 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 inputFileInfo.Data = rom; inputFileInfo.Extension = romextension; CoreComm = comm; - Nullable region = queryUserForRegion(); - if (region == null) - { - throw new Exception("Can't construct new C64 because you didn't choose anything"); - } - Init(region.Value); + Region = queryUserForRegion(); + Init(Region); cyclesPerFrame = board.vic.CyclesPerFrame; SetupMemoryDomains(); - MemoryCallbacks = new MemoryCallbackSystem(); - HardReset(); + MemoryCallbacks = new MemoryCallbackSystem(); + HardReset(); (ServiceProvider as BasicServiceProvider).Register(board.vic); - } + } - private Nullable queryUserForRegion() - { - Form prompt = new Form() { Width = 160, Height = 120, FormBorderStyle = FormBorderStyle.FixedDialog, Text = "Region selector", StartPosition = FormStartPosition.CenterScreen }; - Label textLabel = new Label() { Left = 10, Top = 10, Width = 260, Text = "Please choose a region:" }; - RadioButton palButton = new RadioButton() { Left = 10, Top = 30, Width = 70, Text = "PAL", Checked = true }; - RadioButton ntscButton = new RadioButton() { Left = 80, Top = 30, Width = 70, Text = "NTSC" }; - Button confirmation = new Button() { Text = "Ok", Left = 40, Width = 80, Top = 60, DialogResult = DialogResult.OK }; - confirmation.Click += (sender, e) => { prompt.Close(); }; - prompt.Controls.Add(textLabel); - prompt.Controls.Add(palButton); - prompt.Controls.Add(ntscButton); - prompt.Controls.Add(confirmation); - prompt.AcceptButton = confirmation; + private DisplayType queryUserForRegion() + { + Form prompt = new Form() { Width = 160, Height = 120, FormBorderStyle = FormBorderStyle.FixedDialog, Text = "Region selector", StartPosition = FormStartPosition.CenterScreen }; + Label textLabel = new Label() { Left = 10, Top = 10, Width = 260, Text = "Please choose a region:" }; + RadioButton palButton = new RadioButton() { Left = 10, Top = 30, Width = 70, Text = "PAL", Checked = true }; + RadioButton ntscButton = new RadioButton() { Left = 80, Top = 30, Width = 70, Text = "NTSC" }; + Button confirmation = new Button() { Text = "Ok", Left = 40, Width = 80, Top = 60, DialogResult = DialogResult.OK }; + confirmation.Click += (sender, e) => { prompt.Close(); }; + prompt.Controls.Add(textLabel); + prompt.Controls.Add(palButton); + prompt.Controls.Add(ntscButton); + prompt.Controls.Add(confirmation); + prompt.AcceptButton = confirmation; - return prompt.ShowDialog() == DialogResult.OK ? palButton.Checked ? new Nullable(Region.PAL) : ntscButton.Checked ? new Nullable(Region.NTSC) : null : null; - } + if (prompt.ShowDialog() != DialogResult.OK || !palButton.Checked && !ntscButton.Checked) + { + throw new Exception("Can't construct new C64 because you didn't choose anything"); + } + return palButton.Checked ? DisplayType.PAL : DisplayType.NTSC; + } // internal variables private int _frame = 0; @@ -88,8 +81,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 _frame = 0; _lagcount = 0; _islag = false; - frameCycles = 0; - } + frameCycles = 0; + } // audio/video public void EndAsyncSound() { } //TODO @@ -118,6 +111,12 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 public IEmulatorServiceProvider ServiceProvider { get; private set; } + public DisplayType Region + { + get; + private set; + } + public void Dispose() { if (board.sid != null) @@ -127,69 +126,69 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } } - int frameCycles; + int frameCycles; - private void DoCycle() - { - if (frameCycles == 0) { - board.inputRead = false; - board.PollInput(); - board.cpu.LagCycles = 0; - } - - //disk.Execute(); - board.Execute(); - frameCycles++; - - // load PRG file if needed - if (loadPrg) - { - // check to see if cpu PC is at the BASIC warm start vector - if (board.cpu.PC == ((board.ram.Peek(0x0303) << 8) | board.ram.Peek(0x0302))) - { - //board.ram.Poke(0x0302, 0xAE); - //board.ram.Poke(0x0303, 0xA7); - ////board.ram.Poke(0x0302, board.ram.Peek(0x0308)); - ////board.ram.Poke(0x0303, board.ram.Peek(0x0309)); - - //if (inputFileInfo.Data.Length >= 6) - //{ - // board.ram.Poke(0x0039, inputFileInfo.Data[4]); - // board.ram.Poke(0x003A, inputFileInfo.Data[5]); - //} - PRG.Load(board.pla, inputFileInfo.Data); - loadPrg = false; - } - } - - if (frameCycles == cyclesPerFrame) - { - board.Flush(); - _islag = !board.inputRead; - - if (_islag) - _lagcount++; - frameCycles -= cyclesPerFrame; - _frame++; - - //Console.WriteLine("CPUPC: " + C64Util.ToHex(board.cpu.PC, 4) + " 1541PC: " + C64Util.ToHex(disk.PC, 4)); - - int test = board.cpu.LagCycles; - DriveLightOn = DriveLED; - } - } - - // process frame - public void FrameAdvance(bool render, bool rendersound) + // process frame + public void FrameAdvance(bool render, bool rendersound) { - do - { - DoCycle(); - } - while (frameCycles != 0); - } + do + { + DoCycle(); + } + while (frameCycles != 0); + } - private void HandleFirmwareError(string file) + private void DoCycle() + { + if (frameCycles == 0) { + board.inputRead = false; + board.PollInput(); + board.cpu.LagCycles = 0; + } + + //disk.Execute(); + board.Execute(); + frameCycles++; + + // load PRG file if needed + if (loadPrg) + { + // check to see if cpu PC is at the BASIC warm start vector + if (board.cpu.PC == ((board.ram.Peek(0x0303) << 8) | board.ram.Peek(0x0302))) + { + //board.ram.Poke(0x0302, 0xAE); + //board.ram.Poke(0x0303, 0xA7); + ////board.ram.Poke(0x0302, board.ram.Peek(0x0308)); + ////board.ram.Poke(0x0303, board.ram.Peek(0x0309)); + + //if (inputFileInfo.Data.Length >= 6) + //{ + // board.ram.Poke(0x0039, inputFileInfo.Data[4]); + // board.ram.Poke(0x003A, inputFileInfo.Data[5]); + //} + PRG.Load(board.pla, inputFileInfo.Data); + loadPrg = false; + } + } + + if (frameCycles == cyclesPerFrame) + { + board.Flush(); + _islag = !board.inputRead; + + if (_islag) + _lagcount++; + frameCycles -= cyclesPerFrame; + _frame++; + + //Console.WriteLine("CPUPC: " + C64Util.ToHex(board.cpu.PC, 4) + " 1541PC: " + C64Util.ToHex(disk.PC, 4)); + + int test = board.cpu.LagCycles; + DriveLightOn = DriveLED; + } + } + + private void HandleFirmwareError(string file) { System.Windows.Forms.MessageBox.Show("the C64 core is referencing a firmware file which could not be found. Please make sure it's in your configured C64 firmwares folder. The referenced filename is: " + file); throw new FileNotFoundException(); @@ -206,7 +205,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 return result; } - private void Init(Region initRegion) + private void Init(DisplayType initRegion) { board = new Motherboard(this, initRegion); InitRoms(); @@ -228,13 +227,13 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 { board.cartPort.Connect(cart); } - break; - case @".TAP": - CassettePort.Tape tape = CassettePort.Tape.Load(inputFileInfo.Data); - if (tape != null) - { - board.cassPort.Connect(tape); - } + break; + case @".TAP": + CassettePort.Tape tape = CassettePort.Tape.Load(inputFileInfo.Data); + if (tape != null) + { + board.cassPort.Connect(tape); + } break; case @".PRG": if (inputFileInfo.Data.Length > 2) @@ -261,5 +260,5 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 board.HardReset(); //disk.HardReset(); } - } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs index 970b326e37..44aa71e893 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/CassettePortDevice.cs @@ -9,33 +9,33 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort { public class CassettePortDevice { - public Func ReadDataOutput; - public Func ReadMotor; - Commodore64.CassettePort.Tape tape; + public Func ReadDataOutput; + public Func ReadMotor; + Commodore64.CassettePort.Tape tape; - public void HardReset() - { - if (tape != null) tape.rewind(); - } + public void HardReset() + { + if (tape != null) tape.rewind(); + } - virtual public bool ReadDataInputBuffer() - { - return tape != null && !ReadMotor() ? tape.read() : true; - } + virtual public bool ReadDataInputBuffer() + { + return tape != null && !ReadMotor() ? tape.read() : true; + } - virtual public bool ReadSenseBuffer() - { - return tape == null; // Just assume that "play" is constantly pressed as long as a tape is inserted - } + virtual public bool ReadSenseBuffer() + { + return tape == null; // Just assume that "play" is constantly pressed as long as a tape is inserted + } - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } + public void SyncState(Serializer ser) + { + SaveState.SyncObject(ser, this); + } - internal void Connect(Tape tape) - { - this.tape = tape; - } - } + internal void Connect(Tape tape) + { + this.tape = tape; + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs index 9b0c786f9d..8bf85bae65 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs @@ -9,85 +9,85 @@ using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort { - /** - * This class represents a tape. Only TAP-style tapes are supported for now. - */ - class Tape - { - private byte[] tapeData; - private byte version; - private uint pos, cycle, start, end; + /** + * This class represents a tape. Only TAP-style tapes are supported for now. + */ + class Tape + { + private byte[] tapeData; + private byte version; + private uint pos, cycle, start, end; - public Tape(byte version, byte[] tapeData, uint start, uint end) - { - this.version = version; - this.tapeData = tapeData; - this.start = start; - this.end = end; - rewind(); - } + public Tape(byte version, byte[] tapeData, uint start, uint end) + { + this.version = version; + this.tapeData = tapeData; + this.start = start; + this.end = end; + rewind(); + } - // Rewinds the tape back to start - public void rewind() - { - pos = start; - cycle = 0; - } + // Rewinds the tape back to start + public void rewind() + { + pos = start; + cycle = 0; + } - // Reads from tape, this will tell the caller if the flag pin should be raised - public bool read() - { - if (cycle == 0) - { - Console.WriteLine("Tape @ " + pos.ToString()); - if (pos >= end) - { - return true; - } - else - { - cycle = ((uint)tapeData[pos++])*8; - if (cycle == 0) - { - if (version == 0) - { - cycle = 256 * 8; // unspecified overflow condition - } - else - { - cycle = BitConverter.ToUInt32(tapeData, (int)pos-1)>>8; - pos += 3; - if (cycle == 0) - { - throw new Exception("Bad tape data"); - } - } - } - } - } + // Reads from tape, this will tell the caller if the flag pin should be raised + public bool read() + { + if (cycle == 0) + { + Console.WriteLine("Tape @ " + pos.ToString()); + if (pos >= end) + { + return true; + } + else + { + cycle = ((uint)tapeData[pos++])*8; + if (cycle == 0) + { + if (version == 0) + { + cycle = 256 * 8; // unspecified overflow condition + } + else + { + cycle = BitConverter.ToUInt32(tapeData, (int)pos-1)>>8; + pos += 3; + if (cycle == 0) + { + throw new Exception("Bad tape data"); + } + } + } + } + } - // Send a single negative pulse at the end of a cycle - return --cycle != 0; - } + // Send a single negative pulse at the end of a cycle + return --cycle != 0; + } - // Try to construct a tape file from file data. Returns null if not a tape file, throws exceptions for bad tape files. - // (Note that some error conditions aren't caught right here.) - static public Tape Load(byte[] tapeFile) - { - Tape result = null; + // Try to construct a tape file from file data. Returns null if not a tape file, throws exceptions for bad tape files. + // (Note that some error conditions aren't caught right here.) + static public Tape Load(byte[] tapeFile) + { + Tape result = null; - if (System.Text.Encoding.ASCII.GetString(tapeFile, 0, 12) == "C64-TAPE-RAW") - { - byte version = tapeFile[12]; - if (version > 1) throw new Exception("This tape has an unsupported version"); - uint size = BitConverter.ToUInt32(tapeFile, 16); - if (size + 20 != tapeFile.Length) - { - throw new Exception("Tape file header specifies a length that doesn't match the file size"); - } - result = new Tape(version, tapeFile, 20, (uint)tapeFile.Length); - } - return result; - } - } + if (System.Text.Encoding.ASCII.GetString(tapeFile, 0, 12) == "C64-TAPE-RAW") + { + byte version = tapeFile[12]; + if (version > 1) throw new Exception("This tape has an unsupported version"); + uint size = BitConverter.ToUInt32(tapeFile, 16); + if (size + 20 != tapeFile.Length) + { + throw new Exception("Tape file header specifies a length that doesn't match the file size"); + } + result = new Tape(version, tapeFile, 20, (uint)tapeFile.Length); + } + return result; + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs index 6149c4cf42..51cb927531 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6510.cs @@ -99,18 +99,18 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS } set { - lagCycles = value; - } - } + lagCycles = value; + } + } - internal bool AtInstructionStart() - { - return cpu.AtInstructionStart(); - } + internal bool AtInstructionStart() + { + return cpu.AtInstructionStart(); + } - // ------------------------------------ + // ------------------------------------ - public ushort PC + public ushort PC { get { diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526-2.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526-2.cs index bc0d060fb1..30a3bdfd9d 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526-2.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526-2.cs @@ -233,7 +233,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS LatchedPort portA; LatchedPort portB; - public MOS6526_2(Region region) + public MOS6526_2(Common.DisplayType region) { a = new CiaTimer(serialPortA, underFlowA); b = new CiaTimer(serialPortB, underFlowB); @@ -241,10 +241,10 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS portB = new LatchedPort(); switch (region) { - case Region.NTSC: + case Common.DisplayType.NTSC: tod_period = 14318181 / 140; break; - case Region.PAL: + case Common.DisplayType.PAL: tod_period = 17734472 / 180; break; } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs index 341fd8a728..54df3fd29d 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs @@ -3,795 +3,795 @@ using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { - // MOS technology 6526 "CIA" - // - // emulation notes: - // * CS, R/W and RS# pins are not emulated. (not needed) - // * A low RES pin is emulated via HardReset(). - - sealed public class MOS6526 - { - // ------------------------------------ - - enum InMode - { - Phase2, - CNT, - TimerAUnderflow, - TimerAUnderflowCNT - } - - enum OutMode - { - Pulse, - Toggle - } - - enum RunMode - { - Continuous, - Oneshot - } - - enum SPMode - { - Input, - Output - } - - static byte[] PBOnBit = new byte[] { 0x40, 0x80 }; - static byte[] PBOnMask = new byte[] { 0xBF, 0x7F }; - - // ------------------------------------ - - public Func ReadCNT; - public Func ReadFlag; - public Func ReadSP; - - // ------------------------------------ - - bool alarmSelect; - Region chipRegion; - bool cntPos; - bool enableIntAlarm; - bool enableIntFlag; - bool enableIntSP; - bool[] enableIntTimer; - bool intAlarm; - bool intFlag; - bool intSP; - bool[] intTimer; - bool pinCnt; - bool pinCntLast; - bool pinPC; - bool pinSP; - byte sr; - int[] timerDelay; - InMode[] timerInMode; - OutMode[] timerOutMode; - bool[] timerPortEnable; - bool[] timerPulse; - RunMode[] timerRunMode; - SPMode timerSPMode; - byte[] tod; - byte[] todAlarm; - bool todAlarmPM; - int todCounter; - int todCounterLatch; - bool todIn; - bool todPM; - bool oldFlag; - // ------------------------------------ - - public MOS6526(Region region) - { - chipRegion = region; - enableIntTimer = new bool[2]; - intTimer = new bool[2]; - timerDelay = new int[2]; - timerInMode = new InMode[2]; - timerOutMode = new OutMode[2]; - timerPortEnable = new bool[2]; - timerPulse = new bool[2]; - timerRunMode = new RunMode[2]; - tod = new byte[4]; - todAlarm = new byte[4]; - SetTodIn(chipRegion); - - portA = new LatchedPort(); - portB = new LatchedPort(); - timer = new int[2]; - timerLatch = new int[2]; - timerOn = new bool[2]; - underflow = new bool[2]; - - pinSP = true; - } - - // ------------------------------------ - - public void ExecutePhase1() - { - // unsure if the timer actually operates in ph1 - pinIRQ = !( - (intTimer[0] && enableIntTimer[0]) || - (intTimer[1] && enableIntTimer[1]) || - (intAlarm && enableIntAlarm) || - (intSP && enableIntSP) || - (intFlag && enableIntFlag) - ); - } - - public void ExecutePhase2() - { - { - bool sumCnt = ReadCNT(); - cntPos |= (!pinCntLast && sumCnt); - pinCntLast = sumCnt; - - pinPC = true; - TODRun(); - - if (timerPulse[0]) - { - portA.Latch &= PBOnMask[0]; - } - if (timerPulse[1]) - { - portB.Latch &= PBOnMask[1]; - } - - if (timerDelay[0] == 0) - TimerRun(0); - else - timerDelay[0]--; - - if (timerDelay[1] == 0) - TimerRun(1); - else - timerDelay[1]--; - - intAlarm |= ( - tod[0] == todAlarm[0] && - tod[1] == todAlarm[1] && - tod[2] == todAlarm[2] && - tod[3] == todAlarm[3] && - todPM == todAlarmPM); - - cntPos = false; - underflow[0] = false; - underflow[1] = false; - - bool newFlag = ReadFlag(); - intFlag |= oldFlag && !newFlag; - oldFlag = newFlag; - } - } - - public void HardReset() - { - HardResetInternal(); - alarmSelect = false; - cntPos = false; - enableIntAlarm = false; - enableIntFlag = false; - enableIntSP = false; - enableIntTimer[0] = false; - enableIntTimer[1] = false; - intAlarm = false; - intFlag = false; - intSP = false; - intTimer[0] = false; - intTimer[1] = false; - sr = 0; - timerDelay[0] = 0; - timerDelay[1] = 0; - timerInMode[0] = InMode.Phase2; - timerInMode[1] = InMode.Phase2; - timerOn[0] = false; - timerOn[1] = false; - timerOutMode[0] = OutMode.Pulse; - timerOutMode[1] = OutMode.Pulse; - timerPortEnable[0] = false; - timerPortEnable[1] = false; - timerPulse[0] = false; - timerPulse[1] = false; - timerRunMode[0] = RunMode.Continuous; - timerRunMode[1] = RunMode.Continuous; - timerSPMode = SPMode.Input; - tod[0] = 0; - tod[1] = 0; - tod[2] = 0; - tod[3] = 0x12; - todAlarm[0] = 0; - todAlarm[1] = 0; - todAlarm[2] = 0; - todAlarm[3] = 0; - todCounter = todCounterLatch; - todIn = (chipRegion == Region.PAL); - todPM = false; - - pinCnt = false; - pinPC = true; - } - - private void SetTodIn(Region region) - { - switch (region) - { - case Region.NTSC: - todCounterLatch = 14318181 / 140; - todIn = false; - break; - case Region.PAL: - todCounterLatch = 17734472 / 180; - todIn = true; - break; - } - } - - // ------------------------------------ - - private byte BCDAdd(byte i, byte j, out bool overflow) - { - - { - int lo; - int hi; - int result; - - lo = (i & 0x0F) + (j & 0x0F); - hi = (i & 0x70) + (j & 0x70); - if (lo > 0x09) - { - hi += 0x10; - lo += 0x06; - } - if (hi > 0x50) - { - hi += 0xA0; - } - overflow = hi >= 0x60; - result = (hi & 0x70) + (lo & 0x0F); - return (byte)(result & 0xFF); - } - } - - private void TimerRun(int index) - { - - { - if (timerOn[index]) - { - int t = timer[index]; - bool u = false; - - { - switch (timerInMode[index]) - { - case InMode.CNT: - // CNT positive - if (cntPos) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - case InMode.Phase2: - // every clock - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - break; - case InMode.TimerAUnderflow: - // every underflow[0] - if (underflow[0]) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - case InMode.TimerAUnderflowCNT: - // every underflow[0] while CNT high - if (underflow[0] && pinCnt) - { - t--; - u = (t == 0); - intTimer[index] |= (t == 0); - } - break; - } - - // underflow? - if (u) - { - timerDelay[index] = 1; - t = timerLatch[index]; - if (timerRunMode[index] == RunMode.Oneshot) - timerOn[index] = false; - - if (timerPortEnable[index]) - { - // force port B bit to output - portB.Direction |= PBOnBit[index]; - switch (timerOutMode[index]) - { - case OutMode.Pulse: - timerPulse[index] = true; - portB.Latch |= PBOnBit[index]; - break; - case OutMode.Toggle: - portB.Latch ^= PBOnBit[index]; - break; - } - } - } - - underflow[index] = u; - timer[index] = t; - } - } - } - } - - private void TODRun() - { - - { - bool todV; - - if (todCounter == 0) - { - todCounter = todCounterLatch; - tod[0] = BCDAdd(tod[0], 1, out todV); - if (tod[0] >= 10) - { - tod[0] = 0; - tod[1] = BCDAdd(tod[1], 1, out todV); - if (todV) - { - tod[1] = 0; - tod[2] = BCDAdd(tod[2], 1, out todV); - if (todV) - { - tod[2] = 0; - tod[3] = BCDAdd(tod[3], 1, out todV); - if (tod[3] > 12) - { - tod[3] = 1; - } - else if (tod[3] == 12) - { - todPM = !todPM; - } - } - } - } - } - todCounter--; - } - } - - // ------------------------------------ - - public byte Peek(long addr) - { - return ReadRegister((int)addr & 0xF); - } - - public void Poke(long addr, byte val) - { - WriteRegister((int)(addr & 0xF), val); - } - - public byte Peek(int addr) - { - return ReadRegister((int)addr & 0xF); - } - - public void Poke(int addr, byte val) - { - WriteRegister((int)(addr & 0xF), val); - } - - public byte Read(int addr) - { - return Read(addr, 0xFF); - } - - public byte Read(int addr, byte mask) - { - addr &= 0xF; - byte val; - - switch (addr) - { - case 0x01: - val = ReadRegister(addr); - pinPC = false; - break; - case 0x0D: - val = ReadRegister(addr); - intTimer[0] = false; - intTimer[1] = false; - intAlarm = false; - intSP = false; - intFlag = false; - pinIRQ = true; - break; - default: - val = ReadRegister(addr); - break; - } - - val &= mask; - return val; - } - - public bool ReadCNTBuffer() - { - return pinCnt; - } - - public bool ReadPCBuffer() - { - return pinPC; - } - - private byte ReadRegister(int addr) - { - byte val = 0x00; //unused pin value - int timerVal; - - switch (addr) - { - case 0x0: - val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask); - break; - case 0x1: - val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask); - break; - case 0x2: - val = portA.Direction; - break; - case 0x3: - val = portB.Direction; - break; - case 0x4: - timerVal = ReadTimerValue(0); - val = (byte)(timerVal & 0xFF); - break; - case 0x5: - timerVal = ReadTimerValue(0); - val = (byte)(timerVal >> 8); - break; - case 0x6: - timerVal = ReadTimerValue(1); - val = (byte)(timerVal & 0xFF); - break; - case 0x7: - timerVal = ReadTimerValue(1); - val = (byte)(timerVal >> 8); - break; - case 0x8: - val = tod[0]; - break; - case 0x9: - val = tod[1]; - break; - case 0xA: - val = tod[2]; - break; - case 0xB: - val = tod[3]; - break; - case 0xC: - val = sr; - break; - case 0xD: - val = (byte)( - (intTimer[0] ? 0x01 : 0x00) | - (intTimer[1] ? 0x02 : 0x00) | - (intAlarm ? 0x04 : 0x00) | - (intSP ? 0x08 : 0x00) | - (intFlag ? 0x10 : 0x00) | - (!pinIRQ ? 0x80 : 0x00) - ); - break; - case 0xE: - val = (byte)( - (timerOn[0] ? 0x01 : 0x00) | - (timerPortEnable[0] ? 0x02 : 0x00) | - (todIn ? 0x80 : 0x00)); - if (timerOutMode[0] == OutMode.Toggle) - val |= 0x04; - if (timerRunMode[0] == RunMode.Oneshot) - val |= 0x08; - if (timerInMode[0] == InMode.CNT) - val |= 0x20; - if (timerSPMode == SPMode.Output) - val |= 0x40; - break; - case 0xF: - val = (byte)( - (timerOn[1] ? 0x01 : 0x00) | - (timerPortEnable[1] ? 0x02 : 0x00) | - (alarmSelect ? 0x80 : 0x00)); - if (timerOutMode[1] == OutMode.Toggle) - val |= 0x04; - if (timerRunMode[1] == RunMode.Oneshot) - val |= 0x08; - switch (timerInMode[1]) - { - case InMode.CNT: - val |= 0x20; - break; - case InMode.TimerAUnderflow: - val |= 0x40; - break; - case InMode.TimerAUnderflowCNT: - val |= 0x60; - break; - } - break; - } - - return val; - } - - public bool ReadSPBuffer() - { - return pinSP; - } - - private int ReadTimerValue(int index) - { - if (timerOn[index]) - { - if (timer[index] == 0) - return timerLatch[index]; - else - return timer[index]; - } - else - { - return timer[index]; - } - } - - public void SyncState(Serializer ser) - { - SaveState.SyncObject(ser, this); - } - - public void Write(int addr, byte val) - { - Write(addr, val, 0xFF); - } - - public void Write(int addr, byte val, byte mask) - { - addr &= 0xF; - val &= mask; - val |= (byte)(ReadRegister(addr) & ~mask); - - switch (addr) - { - case 0x1: - WriteRegister(addr, val); - pinPC = false; - break; - case 0x5: - WriteRegister(addr, val); - if (!timerOn[0]) - timer[0] = timerLatch[0]; - break; - case 0x7: - WriteRegister(addr, val); - if (!timerOn[1]) - timer[1] = timerLatch[1]; - break; - case 0xE: - WriteRegister(addr, val); - if ((val & 0x10) != 0) - timer[0] = timerLatch[0]; - break; - case 0xF: - WriteRegister(addr, val); - if ((val & 0x10) != 0) - timer[1] = timerLatch[1]; - break; - default: - WriteRegister(addr, val); - break; - } - } - - public void WriteRegister(int addr, byte val) - { - bool intReg; - - switch (addr) - { - case 0x0: - portA.Latch = val; - break; - case 0x1: - portB.Latch = val; - break; - case 0x2: - portA.Direction = val; - break; - case 0x3: - portB.Direction = val; - break; - case 0x4: - timerLatch[0] &= 0xFF00; - timerLatch[0] |= val; - break; - case 0x5: - timerLatch[0] &= 0x00FF; - timerLatch[0] |= val << 8; - break; - case 0x6: - timerLatch[1] &= 0xFF00; - timerLatch[1] |= val; - break; - case 0x7: - timerLatch[1] &= 0x00FF; - timerLatch[1] |= val << 8; - break; - case 0x8: - if (alarmSelect) - todAlarm[0] = (byte)(val & 0xF); - else - tod[0] = (byte)(val & 0xF); - break; - case 0x9: - if (alarmSelect) - todAlarm[1] = (byte)(val & 0x7F); - else - tod[1] = (byte)(val & 0x7F); - break; - case 0xA: - if (alarmSelect) - todAlarm[2] = (byte)(val & 0x7F); - else - tod[2] = (byte)(val & 0x7F); - break; - case 0xB: - if (alarmSelect) - { - todAlarm[3] = (byte)(val & 0x1F); - todAlarmPM = ((val & 0x80) != 0); - } - else - { - tod[3] = (byte)(val & 0x1F); - todPM = ((val & 0x80) != 0); - } - break; - case 0xC: - sr = val; - break; - case 0xD: - intReg = ((val & 0x80) != 0); - if ((val & 0x01) != 0) - enableIntTimer[0] = intReg; - if ((val & 0x02) != 0) - enableIntTimer[1] = intReg; - if ((val & 0x04) != 0) - enableIntAlarm = intReg; - if ((val & 0x08) != 0) - enableIntSP = intReg; - if ((val & 0x10) != 0) - enableIntFlag = intReg; - break; - case 0xE: - if ((val & 0x01) != 0 && !timerOn[0]) - timerDelay[0] = 2; - timerOn[0] = ((val & 0x01) != 0); - timerPortEnable[0] = ((val & 0x02) != 0); - timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; - timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; - timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2; - timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input; - todIn = ((val & 0x80) != 0); - break; - case 0xF: - if ((val & 0x01) != 0 && !timerOn[1]) - timerDelay[1] = 2; - timerOn[1] = ((val & 0x01) != 0); - timerPortEnable[1] = ((val & 0x02) != 0); - timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; - timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; - switch (val & 0x60) - { - case 0x00: timerInMode[1] = InMode.Phase2; break; - case 0x20: timerInMode[1] = InMode.CNT; break; - case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break; - case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break; - } - alarmSelect = ((val & 0x80) != 0); - break; - } - } - - // ------------------------------------ - - public byte PortAMask = 0xFF; - public byte PortBMask = 0xFF; - - bool pinIRQ; - LatchedPort portA; - LatchedPort portB; - int[] timer; - int[] timerLatch; - bool[] timerOn; - bool[] underflow; - - public Func ReadPortA = (() => { return 0xFF; }); - public Func ReadPortB = (() => { return 0xFF; }); - - void HardResetInternal() - { - timer[0] = 0xFFFF; - timer[1] = 0xFFFF; - timerLatch[0] = timer[0]; - timerLatch[1] = timer[1]; - pinIRQ = true; - } - - public byte PortAData - { - get - { - return portA.ReadOutput(); - } - } - - public byte PortADirection - { - get - { - return portA.Direction; - } - } - - public byte PortALatch - { - get - { - return portA.Latch; - } - } - - public byte PortBData - { - get - { - return portB.ReadOutput(); - } - } - - public byte PortBDirection - { - get - { - return portB.Direction; - } - } - - public byte PortBLatch - { - get - { - return portB.Latch; - } - } - - public bool ReadIRQBuffer() { return pinIRQ; } - } + // MOS technology 6526 "CIA" + // + // emulation notes: + // * CS, R/W and RS# pins are not emulated. (not needed) + // * A low RES pin is emulated via HardReset(). + + sealed public class MOS6526 + { + // ------------------------------------ + + enum InMode + { + Phase2, + CNT, + TimerAUnderflow, + TimerAUnderflowCNT + } + + enum OutMode + { + Pulse, + Toggle + } + + enum RunMode + { + Continuous, + Oneshot + } + + enum SPMode + { + Input, + Output + } + + static byte[] PBOnBit = new byte[] { 0x40, 0x80 }; + static byte[] PBOnMask = new byte[] { 0xBF, 0x7F }; + + // ------------------------------------ + + public Func ReadCNT; + public Func ReadFlag; + public Func ReadSP; + + // ------------------------------------ + + bool alarmSelect; + Common.DisplayType chipRegion; + bool cntPos; + bool enableIntAlarm; + bool enableIntFlag; + bool enableIntSP; + bool[] enableIntTimer; + bool intAlarm; + bool intFlag; + bool intSP; + bool[] intTimer; + bool pinCnt; + bool pinCntLast; + bool pinPC; + bool pinSP; + byte sr; + int[] timerDelay; + InMode[] timerInMode; + OutMode[] timerOutMode; + bool[] timerPortEnable; + bool[] timerPulse; + RunMode[] timerRunMode; + SPMode timerSPMode; + byte[] tod; + byte[] todAlarm; + bool todAlarmPM; + int todCounter; + int todCounterLatch; + bool todIn; + bool todPM; + bool oldFlag; + // ------------------------------------ + + public MOS6526(Common.DisplayType region) + { + chipRegion = region; + enableIntTimer = new bool[2]; + intTimer = new bool[2]; + timerDelay = new int[2]; + timerInMode = new InMode[2]; + timerOutMode = new OutMode[2]; + timerPortEnable = new bool[2]; + timerPulse = new bool[2]; + timerRunMode = new RunMode[2]; + tod = new byte[4]; + todAlarm = new byte[4]; + SetTodIn(chipRegion); + + portA = new LatchedPort(); + portB = new LatchedPort(); + timer = new int[2]; + timerLatch = new int[2]; + timerOn = new bool[2]; + underflow = new bool[2]; + + pinSP = true; + } + + // ------------------------------------ + + public void ExecutePhase1() + { + // unsure if the timer actually operates in ph1 + pinIRQ = !( + (intTimer[0] && enableIntTimer[0]) || + (intTimer[1] && enableIntTimer[1]) || + (intAlarm && enableIntAlarm) || + (intSP && enableIntSP) || + (intFlag && enableIntFlag) + ); + } + + public void ExecutePhase2() + { + { + bool sumCnt = ReadCNT(); + cntPos |= (!pinCntLast && sumCnt); + pinCntLast = sumCnt; + + pinPC = true; + TODRun(); + + if (timerPulse[0]) + { + portA.Latch &= PBOnMask[0]; + } + if (timerPulse[1]) + { + portB.Latch &= PBOnMask[1]; + } + + if (timerDelay[0] == 0) + TimerRun(0); + else + timerDelay[0]--; + + if (timerDelay[1] == 0) + TimerRun(1); + else + timerDelay[1]--; + + intAlarm |= ( + tod[0] == todAlarm[0] && + tod[1] == todAlarm[1] && + tod[2] == todAlarm[2] && + tod[3] == todAlarm[3] && + todPM == todAlarmPM); + + cntPos = false; + underflow[0] = false; + underflow[1] = false; + + bool newFlag = ReadFlag(); + intFlag |= oldFlag && !newFlag; + oldFlag = newFlag; + } + } + + public void HardReset() + { + HardResetInternal(); + alarmSelect = false; + cntPos = false; + enableIntAlarm = false; + enableIntFlag = false; + enableIntSP = false; + enableIntTimer[0] = false; + enableIntTimer[1] = false; + intAlarm = false; + intFlag = false; + intSP = false; + intTimer[0] = false; + intTimer[1] = false; + sr = 0; + timerDelay[0] = 0; + timerDelay[1] = 0; + timerInMode[0] = InMode.Phase2; + timerInMode[1] = InMode.Phase2; + timerOn[0] = false; + timerOn[1] = false; + timerOutMode[0] = OutMode.Pulse; + timerOutMode[1] = OutMode.Pulse; + timerPortEnable[0] = false; + timerPortEnable[1] = false; + timerPulse[0] = false; + timerPulse[1] = false; + timerRunMode[0] = RunMode.Continuous; + timerRunMode[1] = RunMode.Continuous; + timerSPMode = SPMode.Input; + tod[0] = 0; + tod[1] = 0; + tod[2] = 0; + tod[3] = 0x12; + todAlarm[0] = 0; + todAlarm[1] = 0; + todAlarm[2] = 0; + todAlarm[3] = 0; + todCounter = todCounterLatch; + todIn = (chipRegion == Common.DisplayType.PAL); + todPM = false; + + pinCnt = false; + pinPC = true; + } + + private void SetTodIn(Common.DisplayType region) + { + switch (region) + { + case Common.DisplayType.NTSC: + todCounterLatch = 14318181 / 140; + todIn = false; + break; + case Common.DisplayType.PAL: + todCounterLatch = 17734472 / 180; + todIn = true; + break; + } + } + + // ------------------------------------ + + private byte BCDAdd(byte i, byte j, out bool overflow) + { + + { + int lo; + int hi; + int result; + + lo = (i & 0x0F) + (j & 0x0F); + hi = (i & 0x70) + (j & 0x70); + if (lo > 0x09) + { + hi += 0x10; + lo += 0x06; + } + if (hi > 0x50) + { + hi += 0xA0; + } + overflow = hi >= 0x60; + result = (hi & 0x70) + (lo & 0x0F); + return (byte)(result & 0xFF); + } + } + + private void TimerRun(int index) + { + + { + if (timerOn[index]) + { + int t = timer[index]; + bool u = false; + + { + switch (timerInMode[index]) + { + case InMode.CNT: + // CNT positive + if (cntPos) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + case InMode.Phase2: + // every clock + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + break; + case InMode.TimerAUnderflow: + // every underflow[0] + if (underflow[0]) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + case InMode.TimerAUnderflowCNT: + // every underflow[0] while CNT high + if (underflow[0] && pinCnt) + { + t--; + u = (t == 0); + intTimer[index] |= (t == 0); + } + break; + } + + // underflow? + if (u) + { + timerDelay[index] = 1; + t = timerLatch[index]; + if (timerRunMode[index] == RunMode.Oneshot) + timerOn[index] = false; + + if (timerPortEnable[index]) + { + // force port B bit to output + portB.Direction |= PBOnBit[index]; + switch (timerOutMode[index]) + { + case OutMode.Pulse: + timerPulse[index] = true; + portB.Latch |= PBOnBit[index]; + break; + case OutMode.Toggle: + portB.Latch ^= PBOnBit[index]; + break; + } + } + } + + underflow[index] = u; + timer[index] = t; + } + } + } + } + + private void TODRun() + { + + { + bool todV; + + if (todCounter == 0) + { + todCounter = todCounterLatch; + tod[0] = BCDAdd(tod[0], 1, out todV); + if (tod[0] >= 10) + { + tod[0] = 0; + tod[1] = BCDAdd(tod[1], 1, out todV); + if (todV) + { + tod[1] = 0; + tod[2] = BCDAdd(tod[2], 1, out todV); + if (todV) + { + tod[2] = 0; + tod[3] = BCDAdd(tod[3], 1, out todV); + if (tod[3] > 12) + { + tod[3] = 1; + } + else if (tod[3] == 12) + { + todPM = !todPM; + } + } + } + } + } + todCounter--; + } + } + + // ------------------------------------ + + public byte Peek(long addr) + { + return ReadRegister((int)addr & 0xF); + } + + public void Poke(long addr, byte val) + { + WriteRegister((int)(addr & 0xF), val); + } + + public byte Peek(int addr) + { + return ReadRegister((int)addr & 0xF); + } + + public void Poke(int addr, byte val) + { + WriteRegister((int)(addr & 0xF), val); + } + + public byte Read(int addr) + { + return Read(addr, 0xFF); + } + + public byte Read(int addr, byte mask) + { + addr &= 0xF; + byte val; + + switch (addr) + { + case 0x01: + val = ReadRegister(addr); + pinPC = false; + break; + case 0x0D: + val = ReadRegister(addr); + intTimer[0] = false; + intTimer[1] = false; + intAlarm = false; + intSP = false; + intFlag = false; + pinIRQ = true; + break; + default: + val = ReadRegister(addr); + break; + } + + val &= mask; + return val; + } + + public bool ReadCNTBuffer() + { + return pinCnt; + } + + public bool ReadPCBuffer() + { + return pinPC; + } + + private byte ReadRegister(int addr) + { + byte val = 0x00; //unused pin value + int timerVal; + + switch (addr) + { + case 0x0: + val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask); + break; + case 0x1: + val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask); + break; + case 0x2: + val = portA.Direction; + break; + case 0x3: + val = portB.Direction; + break; + case 0x4: + timerVal = ReadTimerValue(0); + val = (byte)(timerVal & 0xFF); + break; + case 0x5: + timerVal = ReadTimerValue(0); + val = (byte)(timerVal >> 8); + break; + case 0x6: + timerVal = ReadTimerValue(1); + val = (byte)(timerVal & 0xFF); + break; + case 0x7: + timerVal = ReadTimerValue(1); + val = (byte)(timerVal >> 8); + break; + case 0x8: + val = tod[0]; + break; + case 0x9: + val = tod[1]; + break; + case 0xA: + val = tod[2]; + break; + case 0xB: + val = tod[3]; + break; + case 0xC: + val = sr; + break; + case 0xD: + val = (byte)( + (intTimer[0] ? 0x01 : 0x00) | + (intTimer[1] ? 0x02 : 0x00) | + (intAlarm ? 0x04 : 0x00) | + (intSP ? 0x08 : 0x00) | + (intFlag ? 0x10 : 0x00) | + (!pinIRQ ? 0x80 : 0x00) + ); + break; + case 0xE: + val = (byte)( + (timerOn[0] ? 0x01 : 0x00) | + (timerPortEnable[0] ? 0x02 : 0x00) | + (todIn ? 0x80 : 0x00)); + if (timerOutMode[0] == OutMode.Toggle) + val |= 0x04; + if (timerRunMode[0] == RunMode.Oneshot) + val |= 0x08; + if (timerInMode[0] == InMode.CNT) + val |= 0x20; + if (timerSPMode == SPMode.Output) + val |= 0x40; + break; + case 0xF: + val = (byte)( + (timerOn[1] ? 0x01 : 0x00) | + (timerPortEnable[1] ? 0x02 : 0x00) | + (alarmSelect ? 0x80 : 0x00)); + if (timerOutMode[1] == OutMode.Toggle) + val |= 0x04; + if (timerRunMode[1] == RunMode.Oneshot) + val |= 0x08; + switch (timerInMode[1]) + { + case InMode.CNT: + val |= 0x20; + break; + case InMode.TimerAUnderflow: + val |= 0x40; + break; + case InMode.TimerAUnderflowCNT: + val |= 0x60; + break; + } + break; + } + + return val; + } + + public bool ReadSPBuffer() + { + return pinSP; + } + + private int ReadTimerValue(int index) + { + if (timerOn[index]) + { + if (timer[index] == 0) + return timerLatch[index]; + else + return timer[index]; + } + else + { + return timer[index]; + } + } + + public void SyncState(Serializer ser) + { + SaveState.SyncObject(ser, this); + } + + public void Write(int addr, byte val) + { + Write(addr, val, 0xFF); + } + + public void Write(int addr, byte val, byte mask) + { + addr &= 0xF; + val &= mask; + val |= (byte)(ReadRegister(addr) & ~mask); + + switch (addr) + { + case 0x1: + WriteRegister(addr, val); + pinPC = false; + break; + case 0x5: + WriteRegister(addr, val); + if (!timerOn[0]) + timer[0] = timerLatch[0]; + break; + case 0x7: + WriteRegister(addr, val); + if (!timerOn[1]) + timer[1] = timerLatch[1]; + break; + case 0xE: + WriteRegister(addr, val); + if ((val & 0x10) != 0) + timer[0] = timerLatch[0]; + break; + case 0xF: + WriteRegister(addr, val); + if ((val & 0x10) != 0) + timer[1] = timerLatch[1]; + break; + default: + WriteRegister(addr, val); + break; + } + } + + public void WriteRegister(int addr, byte val) + { + bool intReg; + + switch (addr) + { + case 0x0: + portA.Latch = val; + break; + case 0x1: + portB.Latch = val; + break; + case 0x2: + portA.Direction = val; + break; + case 0x3: + portB.Direction = val; + break; + case 0x4: + timerLatch[0] &= 0xFF00; + timerLatch[0] |= val; + break; + case 0x5: + timerLatch[0] &= 0x00FF; + timerLatch[0] |= val << 8; + break; + case 0x6: + timerLatch[1] &= 0xFF00; + timerLatch[1] |= val; + break; + case 0x7: + timerLatch[1] &= 0x00FF; + timerLatch[1] |= val << 8; + break; + case 0x8: + if (alarmSelect) + todAlarm[0] = (byte)(val & 0xF); + else + tod[0] = (byte)(val & 0xF); + break; + case 0x9: + if (alarmSelect) + todAlarm[1] = (byte)(val & 0x7F); + else + tod[1] = (byte)(val & 0x7F); + break; + case 0xA: + if (alarmSelect) + todAlarm[2] = (byte)(val & 0x7F); + else + tod[2] = (byte)(val & 0x7F); + break; + case 0xB: + if (alarmSelect) + { + todAlarm[3] = (byte)(val & 0x1F); + todAlarmPM = ((val & 0x80) != 0); + } + else + { + tod[3] = (byte)(val & 0x1F); + todPM = ((val & 0x80) != 0); + } + break; + case 0xC: + sr = val; + break; + case 0xD: + intReg = ((val & 0x80) != 0); + if ((val & 0x01) != 0) + enableIntTimer[0] = intReg; + if ((val & 0x02) != 0) + enableIntTimer[1] = intReg; + if ((val & 0x04) != 0) + enableIntAlarm = intReg; + if ((val & 0x08) != 0) + enableIntSP = intReg; + if ((val & 0x10) != 0) + enableIntFlag = intReg; + break; + case 0xE: + if ((val & 0x01) != 0 && !timerOn[0]) + timerDelay[0] = 2; + timerOn[0] = ((val & 0x01) != 0); + timerPortEnable[0] = ((val & 0x02) != 0); + timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; + timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; + timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2; + timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input; + todIn = ((val & 0x80) != 0); + break; + case 0xF: + if ((val & 0x01) != 0 && !timerOn[1]) + timerDelay[1] = 2; + timerOn[1] = ((val & 0x01) != 0); + timerPortEnable[1] = ((val & 0x02) != 0); + timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse; + timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous; + switch (val & 0x60) + { + case 0x00: timerInMode[1] = InMode.Phase2; break; + case 0x20: timerInMode[1] = InMode.CNT; break; + case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break; + case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break; + } + alarmSelect = ((val & 0x80) != 0); + break; + } + } + + // ------------------------------------ + + public byte PortAMask = 0xFF; + public byte PortBMask = 0xFF; + + bool pinIRQ; + LatchedPort portA; + LatchedPort portB; + int[] timer; + int[] timerLatch; + bool[] timerOn; + bool[] underflow; + + public Func ReadPortA = (() => { return 0xFF; }); + public Func ReadPortB = (() => { return 0xFF; }); + + void HardResetInternal() + { + timer[0] = 0xFFFF; + timer[1] = 0xFFFF; + timerLatch[0] = timer[0]; + timerLatch[1] = timer[1]; + pinIRQ = true; + } + + public byte PortAData + { + get + { + return portA.ReadOutput(); + } + } + + public byte PortADirection + { + get + { + return portA.Direction; + } + } + + public byte PortALatch + { + get + { + return portA.Latch; + } + } + + public byte PortBData + { + get + { + return portB.ReadOutput(); + } + } + + public byte PortBDirection + { + get + { + return portB.Direction; + } + } + + public byte PortBLatch + { + get + { + return portB.Latch; + } + } + + public bool ReadIRQBuffer() { return pinIRQ; } + } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs index 9485937697..dc7e2e70dc 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs @@ -4119,7 +4119,7 @@ } }; - static public Sid Create(int newSampleRate, Region newRegion) + static public Sid Create(int newSampleRate, Common.DisplayType newRegion) { return new Sid(waveTable, newSampleRate, newRegion); } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs index d74e8fc828..9a9f51fd47 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs @@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS public Func ReadPotX; public Func ReadPotY; - public Sid(int[][] newWaveformTable, int newSampleRate, Region newRegion) + public Sid(int[][] newWaveformTable, int newSampleRate, Common.DisplayType newRegion) { uint cyclesPerSec = 0; uint cyclesNum; @@ -51,8 +51,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS switch (newRegion) { - case Region.NTSC: cyclesNum = 14318181; cyclesDen = 14; break; - case Region.PAL: cyclesNum = 17734472; cyclesDen = 18; break; + case Common.DisplayType.NTSC: cyclesNum = 14318181; cyclesDen = 14; break; + case Common.DisplayType.PAL: cyclesNum = 17734472; cyclesDen = 18; break; default: return; } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Parse.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Parse.cs index f510f09159..ac5d441678 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Parse.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.Parse.cs @@ -145,8 +145,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS } else if (parseba == 0x1000) { - pinBA = !badline; - } + pinBA = !badline; + } else { parsecycleBAsprite0 = (parseba & 0x000F); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs index f58517536b..c1bbf6a699 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Vic.cs @@ -74,9 +74,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS lastRasterLine = rasterLine; } - // display enable compare - if (rasterLine == 0) - badlineEnable = false; + // display enable compare + if (rasterLine == 0) + badlineEnable = false; if (rasterLine == 0x030) badlineEnable |= displayEnable; From 004c8294fb076fc9217245ba17df580affa81ab3 Mon Sep 17 00:00:00 2001 From: Kabuto Date: Mon, 28 Sep 2015 23:52:23 +0200 Subject: [PATCH 03/24] c64 core uses ISettable now and supports 2 more video standards --- BizHawk.Client.Common/RomLoader.cs | 2 +- .../movie/PlatformFrameRates.cs | 11 +++ .../BizHawk.Emulation.Cores.csproj | 5 +- .../Commodore64/C64.IDisassemblable.cs | 2 +- .../Computers/Commodore64/C64.ISettable.cs | 75 +++++++++++++++++ .../Computers/Commodore64/C64.Motherboard.cs | 37 +++++++-- .../Computers/Commodore64/C64.cs | 16 ++-- .../Commodore64/CassettePort/Tape.cs | 1 - .../Computers/Commodore64/MOS/MOS6526.cs | 41 ++++------ .../Computers/Commodore64/MOS/MOS6567R56A.cs | 45 ++++++++++ .../MOS/{MOS6567.cs => MOS6567R8.cs} | 82 +++++++++---------- .../Computers/Commodore64/MOS/MOS6572.cs | 44 ++++++++++ .../Computers/Commodore64/MOS/MOS6581.cs | 4 +- .../Computers/Commodore64/MOS/Sid.cs | 14 +--- 14 files changed, 279 insertions(+), 100 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R56A.cs rename BizHawk.Emulation.Cores/Computers/Commodore64/MOS/{MOS6567.cs => MOS6567R8.cs} (93%) create mode 100644 BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6572.cs diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 096632fec1..9827265f2d 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -672,7 +672,7 @@ namespace BizHawk.Client.Common nextEmulator = new Atari7800(nextComm, game, rom.RomData, gamedbpath); break; case "C64": - var c64 = new C64(nextComm, game, rom.RomData, rom.Extension); + var c64 = new C64(nextComm, game, rom.RomData, rom.Extension, GetCoreSettings(), GetCoreSyncSettings()); nextEmulator = c64; break; case "GBA": diff --git a/BizHawk.Client.Common/movie/PlatformFrameRates.cs b/BizHawk.Client.Common/movie/PlatformFrameRates.cs index b093fc2e27..726c5ad73b 100644 --- a/BizHawk.Client.Common/movie/PlatformFrameRates.cs +++ b/BizHawk.Client.Common/movie/PlatformFrameRates.cs @@ -8,6 +8,12 @@ namespace BizHawk.Client.Common // these are political numbers, designed to be in accord with tasvideos.org tradition. theyre not necessarily mathematical factualities (although they may be in some cases) // it would be nice if we could turn this into a rational expression natively, and also, to write some comments about the derivation and ideal valees (since this seems to be where theyre all collected) // are we collecting them anywhere else? for avi-writing code perhaps? + + // just some constants, according to specs + private static readonly double PAL_CARRIER = 15625 * 283.75 + 25; // 4.43361875 MHz + private static readonly double NTSC_CARRIER = 4500000 * 227.5 / 286; // 3.579545454... MHz + private static readonly double PAL_N_CARRIER = 15625 * 229.25 + 25; // 3.58205625 MHz + private static readonly Dictionary _rates = new Dictionary { { "NES", 60.098813897440515532 }, // discussion here: http://forums.nesdev.com/viewtopic.php?t=492 ; a rational expression would be (19687500 / 11) / ((341*262-0.529780.5)/3) -> (118125000 / 1965513) -> 60.098813897440515529533511098629 (so our chosen number is very close) @@ -48,6 +54,11 @@ namespace BizHawk.Client.Common {"PSX", 44100.0*768*11/7/263/3413}, //59.292862562 {"PSX_PAL", 44100.0*768*11/7/314/3406}, //49.7645593576 + {"C64_PAL", PAL_CARRIER*2/9/312/63}, + {"C64_NTSC", NTSC_CARRIER*2/7/263/65}, + {"C64_NTSC_OLD", NTSC_CARRIER*2/7/262/64}, + {"C64_DREAN", PAL_N_CARRIER*2/7/312/65}, + //according to ryphecha, using //clocks[2] = { 53.693182e06, 53.203425e06 }; //ntsc console, pal console //lpf[2][2] = { { 263, 262.5 }, { 314, 312.5 } }; //ntsc,pal; noninterlaced, interlaced diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 5446a07a1c..412c1f1a21 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -161,6 +161,7 @@ C64.cs + C64.cs @@ -185,6 +186,7 @@ + @@ -194,7 +196,8 @@ - + + diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs index 593b624e2f..9afa0b3220 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IDisassemblable.cs @@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 { public string Cpu { - get { return "6510"; } + get { return "6510"; } set { } } public string PCRegisterName diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs new file mode 100644 index 0000000000..a19d5ebc69 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs @@ -0,0 +1,75 @@ +using BizHawk.Emulation.Common; + +using Newtonsoft.Json; + +using System; +using System.ComponentModel; +using System.Drawing; + + +namespace BizHawk.Emulation.Cores.Computers.Commodore64 +{ + public partial class C64 : ISettable + { + public C64Settings GetSettings() + { + return Settings.Clone(); + } + + public C64SyncSettings GetSyncSettings() + { + return SyncSettings.Clone(); + } + + public bool PutSettings(C64Settings o) + { + Settings = o; + return false; + } + + public bool PutSyncSettings(C64SyncSettings o) + { + SyncSettings = o; + return false; + } + + internal C64Settings Settings { get; private set; } + internal C64SyncSettings SyncSettings { get; private set; } + + public class C64Settings + { + public C64Settings Clone() + { + return (C64Settings)MemberwiseClone(); + } + + public C64Settings() + { + BizHawk.Common.SettingsUtil.SetDefaultValues(this); + } + } + + public class C64SyncSettings + { + [DisplayName("VIC type")] + [Description("Set the type of video chip to use")] + [DefaultValue(VicType.PAL)] + public VicType vicType { get; set; } + + public C64SyncSettings Clone() + { + return (C64SyncSettings)MemberwiseClone(); + } + + public C64SyncSettings() + { + BizHawk.Common.SettingsUtil.SetDefaultValues(this); + } + } + + public enum VicType + { + PAL, NTSC, NTSC_OLD, DREAN + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs index 9336a2b329..7fc07731ba 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs @@ -42,25 +42,48 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 private C64 _c64; - public Motherboard(C64 c64, DisplayType initRegion) + public Motherboard(C64 c64, C64.VicType initRegion) { // note: roms need to be added on their own externally _c64 = c64; - + int clockNum, clockDen, mainsFrq; + switch (initRegion) + { + case C64.VicType.PAL: + clockNum = 17734475; + clockDen = 18; + mainsFrq = 50; + break; + case C64.VicType.NTSC: + case C64.VicType.NTSC_OLD: + clockNum = 11250000; + clockDen = 11; + mainsFrq = 60; + break; + case C64.VicType.DREAN: + clockNum = 14328225; + clockDen = 14; + mainsFrq = 50; + break; + default: + throw new System.Exception(); + } cartPort = new CartridgePort(); cassPort = new CassettePortDevice(); - cia0 = new MOS6526(initRegion); - cia1 = new MOS6526(initRegion); + cia0 = new MOS6526(clockNum, clockDen*mainsFrq); + cia1 = new MOS6526(clockNum, clockDen*mainsFrq); colorRam = new Chip2114(); cpu = new MOS6510(); pla = new MOSPLA(); ram = new Chip4864(); serPort = new SerialPort(); - sid = MOS6581.Create(44100, initRegion); + sid = MOS6581.Create(44100, clockNum, clockDen); switch (initRegion) { - case DisplayType.NTSC: vic = MOS6567.Create(); break; - case DisplayType.PAL: vic = MOS6569.Create(); break; + case C64.VicType.NTSC: vic = MOS6567R8.Create(); break; + case C64.VicType.PAL: vic = MOS6569.Create(); break; + case C64.VicType.NTSC_OLD: vic = MOS6567R56A.Create(); break; + case C64.VicType.DREAN: vic = MOS6572.Create(); break; } userPort = new UserPortDevice(); } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index 926e0f3eed..cfbfac420e 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -15,11 +15,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 isReleased: false )] [ServiceNotApplicable(typeof(ISettable<,>))] - sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable, IRegionable + sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable, IRegionable, ISettable { // framework - public C64(CoreComm comm, GameInfo game, byte[] rom, string romextension) + public C64(CoreComm comm, GameInfo game, byte[] rom, string romextension, object Settings, object SyncSettings) { + PutSyncSettings((C64SyncSettings)SyncSettings ?? new C64SyncSettings()); + PutSettings((C64Settings)Settings ?? new C64Settings()); + ServiceProvider = new BasicServiceProvider(this); InputCallbacks = new InputCallbackSystem(); @@ -27,8 +30,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 inputFileInfo.Data = rom; inputFileInfo.Extension = romextension; CoreComm = comm; - Region = queryUserForRegion(); - Init(Region); + Init(this.SyncSettings.vicType); cyclesPerFrame = board.vic.CyclesPerFrame; SetupMemoryDomains(); MemoryCallbacks = new MemoryCallbackSystem(); @@ -38,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 } - private DisplayType queryUserForRegion() + /*private DisplayType queryUserForRegion() { Form prompt = new Form() { Width = 160, Height = 120, FormBorderStyle = FormBorderStyle.FixedDialog, Text = "Region selector", StartPosition = FormStartPosition.CenterScreen }; Label textLabel = new Label() { Left = 10, Top = 10, Width = 260, Text = "Please choose a region:" }; @@ -57,7 +59,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 throw new Exception("Can't construct new C64 because you didn't choose anything"); } return palButton.Checked ? DisplayType.PAL : DisplayType.NTSC; - } + }*/ // internal variables private int _frame = 0; @@ -205,7 +207,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 return result; } - private void Init(DisplayType initRegion) + private void Init(VicType initRegion) { board = new Motherboard(this, initRegion); InitRoms(); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs index 8bf85bae65..262f999fd2 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs @@ -39,7 +39,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort { if (cycle == 0) { - Console.WriteLine("Tape @ " + pos.ToString()); if (pos >= end) { return true; diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs index 54df3fd29d..cec480ae07 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526.cs @@ -51,7 +51,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS // ------------------------------------ bool alarmSelect; - Common.DisplayType chipRegion; bool cntPos; bool enableIntAlarm; bool enableIntFlag; @@ -77,16 +76,22 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS byte[] todAlarm; bool todAlarmPM; int todCounter; - int todCounterLatch; bool todIn; bool todPM; bool oldFlag; // ------------------------------------ - public MOS6526(Common.DisplayType region) + int todStepsNum; + int todStepsDen; + + // todStepsNum/todStepsDen is the number of clock cycles it takes the external clock source to advance one cycle + // (50 or 60 Hz depending on AC frequency in use). + // By default the CIA assumes 60 Hz and will thus count incorrectly when fed with 50 Hz. + public MOS6526(int todStepsNum, int todStepsDen) { - chipRegion = region; - enableIntTimer = new bool[2]; + this.todStepsNum = todStepsNum; + this.todStepsDen = todStepsDen; + enableIntTimer = new bool[2]; intTimer = new bool[2]; timerDelay = new int[2]; timerInMode = new InMode[2]; @@ -96,7 +101,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS timerRunMode = new RunMode[2]; tod = new byte[4]; todAlarm = new byte[4]; - SetTodIn(chipRegion); portA = new LatchedPort(); portB = new LatchedPort(); @@ -207,29 +211,14 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS todAlarm[1] = 0; todAlarm[2] = 0; todAlarm[3] = 0; - todCounter = todCounterLatch; - todIn = (chipRegion == Common.DisplayType.PAL); + todCounter = 0; + todIn = false; todPM = false; pinCnt = false; pinPC = true; } - private void SetTodIn(Common.DisplayType region) - { - switch (region) - { - case Common.DisplayType.NTSC: - todCounterLatch = 14318181 / 140; - todIn = false; - break; - case Common.DisplayType.PAL: - todCounterLatch = 17734472 / 180; - todIn = true; - break; - } - } - // ------------------------------------ private byte BCDAdd(byte i, byte j, out bool overflow) @@ -342,9 +331,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { bool todV; - if (todCounter == 0) + if (todCounter <= 0) { - todCounter = todCounterLatch; + todCounter += todStepsNum*(todIn ? 6 : 5); tod[0] = BCDAdd(tod[0], 1, out todV); if (tod[0] >= 10) { @@ -370,7 +359,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS } } } - todCounter--; + todCounter -= todStepsDen; } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R56A.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R56A.cs new file mode 100644 index 0000000000..49570065e4 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R56A.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS +{ + // vic ntsc old + // TODO is everything right? it's mostly a copy from the other NTSC chip with tweaks wherever it was neccessary to fix something + static public class MOS6567R56A + { + static int cycles = 64; + static int scanwidth = cycles * 8; + static int lines = 262; + static int vblankstart = 0x00D % lines; + static int vblankend = 0x018 % lines; + static int hblankoffset = 20; + static int hblankstart = (0x18C + hblankoffset) % scanwidth; + static int hblankend = (0x1F0 + hblankoffset) % scanwidth; + + static int[] timing = Vic.TimingBuilder_XRaster(0x19C, 0x200, scanwidth, -1, -1); + static int[] fetch = Vic.TimingBuilder_Fetch(timing, 0x174); + static int[] ba = Vic.TimingBuilder_BA(fetch); + static int[] act = Vic.TimingBuilder_Act(timing, 0x004, 0x14C, hblankstart, hblankend); + + static int[][] pipeline = new int[][] + { + timing, + fetch, + ba, + act + }; + + static public Vic Create() + { + return new Vic( + cycles, lines, + pipeline, + 14318181 / 14, + hblankstart, hblankend, + vblankstart, vblankend + ); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R8.cs similarity index 93% rename from BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs rename to BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R8.cs index b68dc7af8f..f6470b0d6a 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6567R8.cs @@ -1,41 +1,41 @@ -using System.Drawing; - -namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS -{ - // vic ntsc - static public class MOS6567 - { - static int cycles = 65; - static int scanwidth = cycles * 8; - static int lines = 263; - static int vblankstart = 0x00D % lines; - static int vblankend = 0x018 % lines; - static int hblankoffset = 20; - static int hblankstart = (0x18C + hblankoffset) % scanwidth - 8; // -8 because the VIC repeats internal pixel cycles around 0x18C - static int hblankend = (0x1F0 + hblankoffset) % scanwidth - 8; - - static int[] timing = Vic.TimingBuilder_XRaster(0x19C, 0x200, scanwidth, 0x18C, 8); - static int[] fetch = Vic.TimingBuilder_Fetch(timing, 0x174); - static int[] ba = Vic.TimingBuilder_BA(fetch); - static int[] act = Vic.TimingBuilder_Act(timing, 0x004, 0x14C, hblankstart, hblankend); - - static int[][] pipeline = new int[][] - { - timing, - fetch, - ba, - act - }; - - static public Vic Create() - { - return new Vic( - cycles, lines, - pipeline, - 14318181 / 14, - hblankstart, hblankend, - vblankstart, vblankend - ); - } - } -} +using System.Drawing; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS +{ + // vic ntsc + static public class MOS6567R8 + { + static int cycles = 65; + static int scanwidth = cycles * 8; + static int lines = 263; + static int vblankstart = 0x00D % lines; + static int vblankend = 0x018 % lines; + static int hblankoffset = 20; + static int hblankstart = (0x18C + hblankoffset) % scanwidth - 8; // -8 because the VIC repeats internal pixel cycles around 0x18C + static int hblankend = (0x1F0 + hblankoffset) % scanwidth - 8; + + static int[] timing = Vic.TimingBuilder_XRaster(0x19C, 0x200, scanwidth, 0x18C, 8); + static int[] fetch = Vic.TimingBuilder_Fetch(timing, 0x174); + static int[] ba = Vic.TimingBuilder_BA(fetch); + static int[] act = Vic.TimingBuilder_Act(timing, 0x004, 0x14C, hblankstart, hblankend); + + static int[][] pipeline = new int[][] + { + timing, + fetch, + ba, + act + }; + + static public Vic Create() + { + return new Vic( + cycles, lines, + pipeline, + 14318181 / 14, + hblankstart, hblankend, + vblankstart, vblankend + ); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6572.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6572.cs new file mode 100644 index 0000000000..ad11e5737c --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6572.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS +{ + // pal n / drean - TODO correct? + class MOS6572 + { + static int cycles = 65; + static int scanwidth = cycles * 8; + static int lines = 312; + static int vblankstart = 0x12C % lines; + static int vblankend = 0x00F % lines; + static int hblankoffset = 20; + static int hblankstart = (0x18C + hblankoffset) % scanwidth - 8; // -8 because the VIC repeats internal pixel cycles around 0x18C + static int hblankend = (0x1F0 + hblankoffset) % scanwidth - 8; + + static int[] timing = Vic.TimingBuilder_XRaster(0x19C, 0x200, scanwidth, 0x18C, 8); + static int[] fetch = Vic.TimingBuilder_Fetch(timing, 0x174); + static int[] ba = Vic.TimingBuilder_BA(fetch); + static int[] act = Vic.TimingBuilder_Act(timing, 0x004, 0x14C, hblankstart, hblankend); + + static int[][] pipeline = new int[][] + { + timing, + fetch, + ba, + act + }; + + static public Vic Create() + { + return new Vic( + cycles, lines, + pipeline, + 14328225 / 14, + hblankstart, hblankend, + vblankstart, vblankend + ); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs index dc7e2e70dc..d19800e88c 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6581.cs @@ -4119,9 +4119,9 @@ } }; - static public Sid Create(int newSampleRate, Common.DisplayType newRegion) + static public Sid Create(int newSampleRate, int clockFrqNum, int clockFrqDen) { - return new Sid(waveTable, newSampleRate, newRegion); + return new Sid(waveTable, (uint)newSampleRate, (uint)clockFrqNum, (uint)clockFrqDen); } } } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs index 9a9f51fd47..873dcfacb5 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs @@ -42,20 +42,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS public Func ReadPotX; public Func ReadPotY; - public Sid(int[][] newWaveformTable, int newSampleRate, Common.DisplayType newRegion) + public Sid(int[][] newWaveformTable, uint sampleRate, uint cyclesNum, uint cyclesDen) { - uint cyclesPerSec = 0; - uint cyclesNum; - uint cyclesDen; - uint sampleRate = 44100; - - switch (newRegion) - { - case Common.DisplayType.NTSC: cyclesNum = 14318181; cyclesDen = 14; break; - case Common.DisplayType.PAL: cyclesNum = 17734472; cyclesDen = 18; break; - default: return; - } - waveformTable = newWaveformTable; envelopes = new Envelope[3]; From f09634c43e337b5d35eb4f27d5c23be038a2c874 Mon Sep 17 00:00:00 2001 From: Kabuto Date: Tue, 29 Sep 2015 21:36:15 +0200 Subject: [PATCH 04/24] Tape didn't sync properly -> fixed --- .../Computers/Commodore64/CassettePort/Tape.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs index 262f999fd2..2c04d1bf09 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/CassettePort/Tape.cs @@ -14,9 +14,10 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort */ class Tape { - private byte[] tapeData; - private byte version; - private uint pos, cycle, start, end; + private readonly byte[] tapeData; + private readonly byte version; + private uint pos, cycle; + private readonly uint start, end; public Tape(byte version, byte[] tapeData, uint start, uint end) { @@ -88,5 +89,13 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.CassettePort } return result; } + + public void SyncState(Serializer ser) + { + ser.BeginSection("tape"); + ser.Sync("pos", ref pos); + ser.Sync("cycle", ref cycle); + ser.EndSection(); + } } } From 5cfc1dae2a7439c37b74ba12ee14d2874f5c519c Mon Sep 17 00:00:00 2001 From: zeromus Date: Fri, 2 Oct 2015 00:52:56 -0500 Subject: [PATCH 05/24] update psx core to 0.9.38.7 --- output/dll/octoshock.dll | Bin 924160 -> 927232 bytes psx/octoshock/docs/upstreaminfo.txt | 10 +- psx/octoshock/psx/cpu.cpp | 3519 ++++++++++++++------------- psx/octoshock/psx/cpu.h | 525 ++-- psx/octoshock/psx/dis.cpp | 23 +- psx/octoshock/psx/gte.cpp | 16 - psx/octoshock/psx/psx.cpp | 1 - psx/octoshock/psx/timer.cpp | 865 +++---- 8 files changed, 2539 insertions(+), 2420 deletions(-) diff --git a/output/dll/octoshock.dll b/output/dll/octoshock.dll index e8a0a325dc222798ca5df30eab5a82b32776fbb0..935bf7a9ba5149886a46604f953f3d47384760cf 100644 GIT binary patch delta 133196 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 delta 129590 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 P+ 0.9.38.6 -[OK] psx/gpu & gpu_sprite : Fixed GPU emulation timing bugs that caused graphical glitches in "Mr. Driller G". \ No newline at end of file +[OK] psx/gpu & gpu_sprite : Fixed GPU emulation timing bugs that caused graphical glitches in "Mr. Driller G". +0.9.38.5 -> 0.9.38.7 +[OK] psx/cpu : Revisions to exception handling +[OK] psx/cpu : Many revisions and cleanups to branch and exception handling in opcode implementations +[OK] psx/dis : Just some basic disassembly changes +[OK] psx/gte : Cleanup +[OK] psx/psx : Cleanup +[OK] psx/timer : Major functional changes +[NO] psx/timer : Added loadstate sanity checks \ No newline at end of file diff --git a/psx/octoshock/psx/cpu.cpp b/psx/octoshock/psx/cpu.cpp index c7c20ec732..4be2a6699e 100644 --- a/psx/octoshock/psx/cpu.cpp +++ b/psx/octoshock/psx/cpu.cpp @@ -20,6 +20,12 @@ #include "cpu.h" #include "math_ops.h" +#if 0 +#define EXP_ILL_CHECK(n) {n;} +#else +#define EXP_ILL_CHECK(n) {} +#endif + //not very organized, is it void* g_ShockTraceCallbackOpaque = NULL; ShockCallback_Trace g_ShockTraceCallback = NULL; @@ -360,43 +366,54 @@ INLINE void PS_CPU::WriteMemory(pscpu_timestamp_t ×tamp, uint32 address, ui } } -uint32 PS_CPU::Exception(uint32 code, uint32 PC, const uint32 NPM) -{ - const bool InBDSlot = !(NPM & 0x3); - uint32 handler = 0x80000080; - - assert(code < 16); - - if(code != EXCEPTION_INT && code != EXCEPTION_BP && code != EXCEPTION_SYSCALL) - { - PSX_DBG(PSX_DBG_WARNING, "Exception: %08x @ PC=0x%08x(IBDS=%d) -- IPCache=0x%02x -- IPEND=0x%02x -- SR=0x%08x ; IRQC_Status=0x%04x -- IRQC_Mask=0x%04x\n", code, PC, InBDSlot, IPCache, (CP0.CAUSE >> 8) & 0xFF, CP0.SR, - IRQ_GetRegister(IRQ_GSREG_STATUS, NULL, 0), IRQ_GetRegister(IRQ_GSREG_MASK, NULL, 0)); - } - - if(CP0.SR & (1 << 22)) // BEV - handler = 0xBFC00180; - - CP0.EPC = PC; - if(InBDSlot) - CP0.EPC -= 4; - - if(ADDBT) - ADDBT(PC, handler, true); - - // "Push" IEc and KUc(so that the new IEc and KUc are 0) - CP0.SR = (CP0.SR & ~0x3F) | ((CP0.SR << 2) & 0x3F); - - // Setup cause register - CP0.CAUSE &= 0x0000FF00; - CP0.CAUSE |= code << 2; - - // If EPC was adjusted -= 4 because we were in a branch delay slot, set the bit. - if(InBDSlot) - CP0.CAUSE |= 0x80000000; - - RecalcIPCache(); - - return(handler); + +uint32 NO_INLINE PS_CPU::Exception(uint32 code, uint32 PC, const uint32 NP, const uint32 NPM, const uint32 instr) +{ + const bool AfterBranchInstr = !(NPM & 0x1); + const bool BranchTaken = !(NPM & 0x3); + uint32 handler = 0x80000080; + + assert(code < 16); + + if(code != EXCEPTION_INT && code != EXCEPTION_BP && code != EXCEPTION_SYSCALL) + { + static const char* exmne[16] = + { + "INT", "MOD", "TLBL", "TLBS", "ADEL", "ADES", "IBE", "DBE", "SYSCALL", "BP", "RI", "COPU", "OV", NULL, NULL, NULL + }; + + PSX_DBG(PSX_DBG_WARNING, "[CPU] Exception %s(0x%02x) @ PC=0x%08x(NP=0x%08x, NPM=0x%08x), Instr=0x%08x, IPCache=0x%02x, CAUSE=0x%08x, SR=0x%08x, IRQC_Status=0x%04x, IRQC_Mask=0x%04x\n", + exmne[code], code, PC, NP, NPM, instr, IPCache, CP0.CAUSE, CP0.SR, IRQ_GetRegister(IRQ_GSREG_STATUS, NULL, 0), IRQ_GetRegister(IRQ_GSREG_MASK, NULL, 0)); + } + + if(CP0.SR & (1 << 22)) // BEV + handler = 0xBFC00180; + + CP0.EPC = PC; + if(AfterBranchInstr) + { + CP0.EPC -= 4; + CP0.TAR = (PC & (NPM | 3)) + NP; + } + + if(ADDBT) + ADDBT(PC, handler, true); + + // "Push" IEc and KUc(so that the new IEc and KUc are 0) + CP0.SR = (CP0.SR & ~0x3F) | ((CP0.SR << 2) & 0x3F); + + // Setup cause register + CP0.CAUSE &= 0x0000FF00; + CP0.CAUSE |= code << 2; + + // If EPC was adjusted -= 4 because we are after a branch instruction, set bit 31. + CP0.CAUSE |= AfterBranchInstr << 31; + CP0.CAUSE |= BranchTaken << 30; + CP0.CAUSE |= (instr << 2) & (0x3 << 28); // CE + + RecalcIPCache(); + + return(handler); } #define BACKING_TO_ACTIVE \ @@ -413,9 +430,12 @@ uint32 PS_CPU::Exception(uint32 code, uint32 PC, const uint32 NPM) BACKED_LDWhich = LDWhich; \ BACKED_LDValue = LDValue; -#define GPR_DEPRES_BEGIN { uint8 back = ReadAbsorb[0]; -#define GPR_DEP(n) { unsigned tn = (n); ReadAbsorb[tn] = 0; } -#define GPR_RES(n) { unsigned tn = (n); ReadAbsorb[tn] = 0; } +// +// Should come before DO_LDS() so the EXP_ILL_CHECK() emulator debugging macro in GPR_DEP() will work properly. +// +#define GPR_DEPRES_BEGIN { uint8 back = ReadAbsorb[0]; +#define GPR_DEP(n) { unsigned tn = (n); ReadAbsorb[tn] = 0; EXP_ILL_CHECK(if(LDWhich > 0 && LDWhich < 0x20 && LDWhich == tn) { PSX_DBG(PSX_DBG_WARNING, "[CPU] Instruction at PC=0x%08x in load delay slot has dependency on load target register(0x%02x): SR=0x%08x\n", PC, LDWhich, CP0.SR); }) } +#define GPR_RES(n) { unsigned tn = (n); ReadAbsorb[tn] = 0; } #define GPR_DEPRES_END ReadAbsorb[0] = back; } template @@ -481,7 +501,8 @@ pscpu_timestamp_t PS_CPU::RunReal(pscpu_timestamp_t timestamp_in) { // This will block interrupt processing, but since we're going more for keeping broken homebrew/hacks from working // than super-duper-accurate pipeline emulation, it shouldn't be a problem. - new_PC = Exception(EXCEPTION_ADEL, PC, new_PC_mask); + CP0.BADVA = PC; + new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, new_PC_mask, 0); new_PC_mask = 0; goto OpDone; } @@ -576,37 +597,54 @@ pscpu_timestamp_t PS_CPU::RunReal(pscpu_timestamp_t timestamp_in) #endif #define DO_LDS() { GPR[LDWhich] = LDValue; ReadAbsorb[LDWhich] = LDAbsorb; ReadFudge = LDWhich; ReadAbsorbWhich |= LDWhich & 0x1F; LDWhich = 0x20; } - #define BEGIN_OPF(name, arg_op, arg_funct) { op_##name: /*assert( ((arg_op) ? (0x40 | (arg_op)) : (arg_funct)) == opf); */ + #define BEGIN_OPF(name) { op_##name: #define END_OPF goto OpDone; } - #define DO_BRANCH(offset, mask) \ - { \ - if(ILHMode) \ - { \ - uint32 old_PC = PC; \ - PC = (PC & new_PC_mask) + new_PC; \ - if(old_PC == ((PC & (mask)) + (offset))) \ - { \ - if(MDFN_densb(&FastMap[PC >> FAST_MAP_SHIFT][PC]) == 0) \ - { \ - if(next_event_ts > timestamp) /* Necessary since next_event_ts might be set to something like "0" to force a call to the event handler. */ \ - { \ - timestamp = next_event_ts; \ - } \ - } \ - } \ - } \ - else \ - PC = (PC & new_PC_mask) + new_PC; \ - new_PC = (offset); \ - new_PC_mask = (mask) & ~3; \ - /* Lower bits of new_PC_mask being clear signifies being in a branch delay slot. (overloaded behavior for performance) */ \ - \ - if(DebugMode && ADDBT) \ - { \ - ADDBT(PC, (PC & new_PC_mask) + new_PC, false); \ - } \ - goto SkipNPCStuff; \ + +#define DO_BRANCH(arg_cond, arg_offset, arg_mask, arg_dolink, arg_linkreg)\ + { \ + const bool cond = (arg_cond); \ + const uint32 offset = (arg_offset); \ + const uint32 mask = (arg_mask); \ + const uint32 old_PC = PC; \ + \ + EXP_ILL_CHECK(if(!(new_PC_mask & 0x03)) { PSX_DBG(PSX_DBG_WARNING, "[CPU] Branch instruction at PC=0x%08x in branch delay slot: SR=0x%08x\n", PC, CP0.SR);}) \ + \ + PC = (PC & new_PC_mask) + new_PC; \ + \ + /* Clear lower bit to signify being after a branch instruction (overloaded behavior for performance). */ \ + new_PC_mask = ~1U; \ + new_PC = 4; \ + \ + if(arg_dolink) \ + GPR[(arg_linkreg)] = PC + 4; \ + \ + if(cond) \ + { \ + if(ILHMode) \ + { \ + if(old_PC == ((PC & mask) + offset)) \ + { \ + if(MDFN_densb(&FastMap[PC >> FAST_MAP_SHIFT][PC]) == 0) \ + { \ + if(next_event_ts > timestamp) /* Necessary since next_event_ts might be set to something like "0" to force a call to the event handler. */ \ + { \ + timestamp = next_event_ts; \ + } \ + } \ + } \ + } \ + \ + /* Lower bits of new_PC_mask being clear signifies being in a branch delay slot. (overloaded behavior for performance) */ \ + new_PC = offset; \ + new_PC_mask = mask & ~3; \ + \ + if(DebugMode && ADDBT) \ + { \ + ADDBT(PC, (PC & new_PC_mask) + new_PC, false); \ + } \ + } \ + goto SkipNPCStuff; \ } #define ITYPE uint32 rs MDFN_NOWARN_UNUSED = (instr >> 21) & 0x1F; uint32 rt MDFN_NOWARN_UNUSED = (instr >> 16) & 0x1F; uint32 immediate = (int32)(int16)(instr & 0xFFFF); /*printf(" rs=%02x(%08x), rt=%02x(%08x), immediate=(%08x) ", rs, GPR[rs], rt, GPR[rt], immediate);*/ @@ -626,1642 +664,1713 @@ pscpu_timestamp_t PS_CPU::RunReal(pscpu_timestamp_t timestamp_in) #define CGE(l) case __COUNTER__ - CGESB: goto l; #define CGEND } } #endif - - CGBEGIN - CGE(op_SLL) CGE(op_ILL) CGE(op_SRL) CGE(op_SRA) CGE(op_SLLV) CGE(op_ILL) CGE(op_SRLV) CGE(op_SRAV) - CGE(op_JR) CGE(op_JALR) CGE(op_ILL) CGE(op_ILL) CGE(op_SYSCALL) CGE(op_BREAK) CGE(op_ILL) CGE(op_ILL) - CGE(op_MFHI) CGE(op_MTHI) CGE(op_MFLO) CGE(op_MTLO) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_MULT) CGE(op_MULTU) CGE(op_DIV) CGE(op_DIVU) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_ADD) CGE(op_ADDU) CGE(op_SUB) CGE(op_SUBU) CGE(op_AND) CGE(op_OR) CGE(op_XOR) CGE(op_NOR) - CGE(op_ILL) CGE(op_ILL) CGE(op_SLT) CGE(op_SLTU) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - - CGE(op_ILL) CGE(op_BCOND) CGE(op_J) CGE(op_JAL) CGE(op_BEQ) CGE(op_BNE) CGE(op_BLEZ) CGE(op_BGTZ) - CGE(op_ADDI) CGE(op_ADDIU) CGE(op_SLTI) CGE(op_SLTIU) CGE(op_ANDI) CGE(op_ORI) CGE(op_XORI) CGE(op_LUI) - CGE(op_COP0) CGE(op_COP1) CGE(op_COP2) CGE(op_COP3) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_LB) CGE(op_LH) CGE(op_LWL) CGE(op_LW) CGE(op_LBU) CGE(op_LHU) CGE(op_LWR) CGE(op_ILL) - CGE(op_SB) CGE(op_SH) CGE(op_SWL) CGE(op_SW) CGE(op_ILL) CGE(op_ILL) CGE(op_SWR) CGE(op_ILL) - CGE(op_LWC0) CGE(op_LWC1) CGE(op_LWC2) CGE(op_LWC3) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - CGE(op_SWC0) CGE(op_SWC1) CGE(op_SWC2) CGE(op_SWC3) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) - - // Interrupt portion of this table is constructed so that an interrupt won't be taken when the PC is pointing to a GTE instruction, - // to avoid problems caused by pipeline vs coprocessor nuances that aren't emulated. - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - - CGE(op_ILL) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_COP2) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) - CGEND - - { - BEGIN_OPF(ILL, 0, 0); - PSX_WARNING("[CPU] Unknown instruction @%08x = %08x, op=%02x, funct=%02x", PC, instr, instr >> 26, (instr & 0x3F)); - DO_LDS(); - new_PC = Exception(EXCEPTION_RI, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // ADD - Add Word - // - BEGIN_OPF(ADD, 0, 0x20); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] + GPR[rt]; - bool ep = ((~(GPR[rs] ^ GPR[rt])) & (GPR[rs] ^ result)) & 0x80000000; - - DO_LDS(); - - if(MDFN_UNLIKELY(ep)) - { - new_PC = Exception(EXCEPTION_OV, PC, new_PC_mask); - new_PC_mask = 0; - } - else - GPR[rd] = result; - - END_OPF; - - // - // ADDI - Add Immediate Word - // - BEGIN_OPF(ADDI, 0x08, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = GPR[rs] + immediate; - bool ep = ((~(GPR[rs] ^ immediate)) & (GPR[rs] ^ result)) & 0x80000000; - - DO_LDS(); - - if(MDFN_UNLIKELY(ep)) - { - new_PC = Exception(EXCEPTION_OV, PC, new_PC_mask); - new_PC_mask = 0; - } - else - GPR[rt] = result; - - END_OPF; - - // - // ADDIU - Add Immediate Unsigned Word - // - BEGIN_OPF(ADDIU, 0x09, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = GPR[rs] + immediate; - - DO_LDS(); - - GPR[rt] = result; - - END_OPF; - - // - // ADDU - Add Unsigned Word - // - BEGIN_OPF(ADDU, 0, 0x21); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] + GPR[rt]; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - // - // AND - And - // - BEGIN_OPF(AND, 0, 0x24); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] & GPR[rt]; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - // - // ANDI - And Immediate - // - BEGIN_OPF(ANDI, 0x0C, 0); - ITYPE_ZE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = GPR[rs] & immediate; - - DO_LDS(); - - GPR[rt] = result; - - END_OPF; - - // - // BEQ - Branch on Equal - // - BEGIN_OPF(BEQ, 0x04, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - bool result = (GPR[rs] == GPR[rt]); - - DO_LDS(); - - if(result) - { - DO_BRANCH((immediate << 2), ~0U); - } - END_OPF; - - // Bah, why does MIPS encoding have to be funky like this. :( - // Handles BGEZ, BGEZAL, BLTZ, BLTZAL - BEGIN_OPF(BCOND, 0x01, 0); - const uint32 tv = GPR[(instr >> 21) & 0x1F]; - uint32 riv = (instr >> 16) & 0x1F; - uint32 immediate = (int32)(int16)(instr & 0xFFFF); - bool result = (int32)(tv ^ (riv << 31)) < 0; - - GPR_DEPRES_BEGIN - GPR_DEP((instr >> 21) & 0x1F); - - if(riv & 0x10) - GPR_RES(31); - - GPR_DEPRES_END - - - DO_LDS(); - - if(riv & 0x10) // Unconditional link reg setting. - GPR[31] = PC + 8; - - if(result) - { - DO_BRANCH((immediate << 2), ~0U); - } - - END_OPF; - - - // - // BGTZ - Branch on Greater than Zero - // - BEGIN_OPF(BGTZ, 0x07, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - bool result = (int32)GPR[rs] > 0; - - DO_LDS(); - - if(result) - { - DO_BRANCH((immediate << 2), ~0U); - } - END_OPF; - - // - // BLEZ - Branch on Less Than or Equal to Zero - // - BEGIN_OPF(BLEZ, 0x06, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - bool result = (int32)GPR[rs] <= 0; - - DO_LDS(); - - if(result) - { - DO_BRANCH((immediate << 2), ~0U); - } - - END_OPF; - - // - // BNE - Branch on Not Equal - // - BEGIN_OPF(BNE, 0x05, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - bool result = GPR[rs] != GPR[rt]; - - DO_LDS(); - - if(result) - { - DO_BRANCH((immediate << 2), ~0U); - } - - END_OPF; - - // - // BREAK - Breakpoint - // - BEGIN_OPF(BREAK, 0, 0x0D); - PSX_WARNING("[CPU] BREAK BREAK BREAK BREAK DAAANCE -- PC=0x%08x", PC); - - DO_LDS(); - new_PC = Exception(EXCEPTION_BP, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // Cop "instructions": CFCz(no CP0), COPz, CTCz(no CP0), LWCz(no CP0), MFCz, MTCz, SWCz(no CP0) - // - // COP0 instructions - BEGIN_OPF(COP0, 0x10, 0); - uint32 sub_op = (instr >> 21) & 0x1F; - - if(sub_op & 0x10) - sub_op = 0x10 + (instr & 0x3F); - - //printf("COP0 thing: %02x\n", sub_op); - switch(sub_op) - { - default: - DO_LDS(); - break; - - case 0x00: // MFC0 - Move from Coprocessor - { - uint32 rt = (instr >> 16) & 0x1F; - uint32 rd = (instr >> 11) & 0x1F; - - //printf("MFC0: rt=%d <- rd=%d(%08x)\n", rt, rd, CP0.Regs[rd]); - DO_LDS(); - - LDAbsorb = 0; - LDWhich = rt; - LDValue = CP0.Regs[rd]; - } - break; - - case 0x04: // MTC0 - Move to Coprocessor - { - uint32 rt = (instr >> 16) & 0x1F; - uint32 rd = (instr >> 11) & 0x1F; - uint32 val = GPR[rt]; - - if(rd != CP0REG_PRID && rd != CP0REG_CAUSE && rd != CP0REG_SR && val) - { - PSX_WARNING("[CPU] Unimplemented MTC0: rt=%d(%08x) -> rd=%d", rt, GPR[rt], rd); - } - - switch(rd) - { - case CP0REG_BPC: - CP0.BPC = val; - break; - - case CP0REG_BDA: - CP0.BDA = val; - break; - - case CP0REG_TAR: - CP0.TAR = val; - break; - - case CP0REG_DCIC: - CP0.DCIC = val & 0xFF80003F; - break; - - case CP0REG_BDAM: - CP0.BDAM = val; - break; - - case CP0REG_BPCM: - CP0.BPCM = val; - break; - - case CP0REG_CAUSE: - CP0.CAUSE &= ~(0x3 << 8); - CP0.CAUSE |= val & (0x3 << 8); - RecalcIPCache(); - break; - - case CP0REG_SR: - if((CP0.SR ^ val) & 0x10000) - PSX_DBG(PSX_DBG_SPARSE, "[CPU] IsC %u->%u\n", (bool)(CP0.SR & (1U << 16)), (bool)(val & (1U << 16))); - - CP0.SR = val & ~( (0x3 << 26) | (0x3 << 23) | (0x3 << 6)); - RecalcIPCache(); - break; - } - } - DO_LDS(); - break; - - case (0x10 + 0x10): // RFE - // "Pop" - DO_LDS(); - CP0.SR = (CP0.SR & ~0x0F) | ((CP0.SR >> 2) & 0x0F); - RecalcIPCache(); - break; - } - END_OPF; - - // - // COP1 - // - BEGIN_OPF(COP1, 0x11, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // COP2 - // - BEGIN_OPF(COP2, 0x12, 0); - uint32 sub_op = (instr >> 21) & 0x1F; - - switch(sub_op) - { - default: - DO_LDS(); - break; - - case 0x00: // MFC2 - Move from Coprocessor - { - uint32 rt = (instr >> 16) & 0x1F; - uint32 rd = (instr >> 11) & 0x1F; - - DO_LDS(); - - if(timestamp < gte_ts_done) - { - LDAbsorb = gte_ts_done - timestamp; - timestamp = gte_ts_done; - } - else - LDAbsorb = 0; - - LDWhich = rt; - LDValue = GTE_ReadDR(rd); - } - break; - - case 0x04: // MTC2 - Move to Coprocessor - { - uint32 rt = (instr >> 16) & 0x1F; - uint32 rd = (instr >> 11) & 0x1F; - uint32 val = GPR[rt]; - - if(timestamp < gte_ts_done) - timestamp = gte_ts_done; - - //printf("GTE WriteDR: %d %d\n", rd, val); - GTE_WriteDR(rd, val); - DO_LDS(); - } - break; - - case 0x02: // CFC2 - { - uint32 rt = (instr >> 16) & 0x1F; - uint32 rd = (instr >> 11) & 0x1F; - - DO_LDS(); - - if(timestamp < gte_ts_done) - { - LDAbsorb = gte_ts_done - timestamp; - timestamp = gte_ts_done; - } - else - LDAbsorb = 0; - - LDWhich = rt; - LDValue = GTE_ReadCR(rd); - - //printf("GTE ReadCR: %d %d\n", rd, GPR[rt]); - } - break; - - case 0x06: // CTC2 - { - uint32 rt = (instr >> 16) & 0x1F; - uint32 rd = (instr >> 11) & 0x1F; - uint32 val = GPR[rt]; - - //printf("GTE WriteCR: %d %d\n", rd, val); - - if(timestamp < gte_ts_done) - timestamp = gte_ts_done; - - GTE_WriteCR(rd, val); - DO_LDS(); - } - break; - - case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: - case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: - //printf("%08x\n", PC); - if(timestamp < gte_ts_done) - timestamp = gte_ts_done; - gte_ts_done = timestamp + GTE_Instruction(instr); - DO_LDS(); - break; - } - END_OPF; - - // - // COP3 - // - BEGIN_OPF(COP3, 0x13, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // LWC0 - // - BEGIN_OPF(LWC0, 0x30, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // LWC1 - // - BEGIN_OPF(LWC1, 0x31, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // LWC2 - // - BEGIN_OPF(LWC2, 0x32, 0); - ITYPE; - uint32 address = GPR[rs] + immediate; - - DO_LDS(); - - if(MDFN_UNLIKELY(address & 3)) - { - new_PC = Exception(EXCEPTION_ADEL, PC, new_PC_mask); - new_PC_mask = 0; - } - else - { - if(timestamp < gte_ts_done) - timestamp = gte_ts_done; - - GTE_WriteDR(rt, ReadMemory(timestamp, address, false, true)); - } - // GTE stuff here - END_OPF; - - // - // LWC3 - // - BEGIN_OPF(LWC3, 0x33, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - - // - // SWC0 - // - BEGIN_OPF(SWC0, 0x38, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // SWC1 - // - BEGIN_OPF(SWC1, 0x39, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_COPU, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - // - // SWC2 - // - BEGIN_OPF(SWC2, 0x3A, 0); - ITYPE; - uint32 address = GPR[rs] + immediate; - - if(MDFN_UNLIKELY(address & 0x3)) - { - new_PC = Exception(EXCEPTION_ADES, PC, new_PC_mask); - new_PC_mask = 0; - } - else - { - if(timestamp < gte_ts_done) - timestamp = gte_ts_done; - - WriteMemory(timestamp, address, GTE_ReadDR(rt)); - } - DO_LDS(); - END_OPF; - - // - // SWC3 - /// - BEGIN_OPF(SWC3, 0x3B, 0); - DO_LDS(); - new_PC = Exception(EXCEPTION_RI, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - - // - // DIV - Divide Word - // - BEGIN_OPF(DIV, 0, 0x1A); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - if(!GPR[rt]) - { - if(GPR[rs] & 0x80000000) - LO = 1; - else - LO = 0xFFFFFFFF; - - HI = GPR[rs]; - } - else if(GPR[rs] == 0x80000000 && GPR[rt] == 0xFFFFFFFF) - { - LO = 0x80000000; - HI = 0; - } - else - { - LO = (int32)GPR[rs] / (int32)GPR[rt]; - HI = (int32)GPR[rs] % (int32)GPR[rt]; - } - muldiv_ts_done = timestamp + 37; - - DO_LDS(); - - END_OPF; - - - // - // DIVU - Divide Unsigned Word - // - BEGIN_OPF(DIVU, 0, 0x1B); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - if(!GPR[rt]) - { - LO = 0xFFFFFFFF; - HI = GPR[rs]; - } - else - { - LO = GPR[rs] / GPR[rt]; - HI = GPR[rs] % GPR[rt]; - } - muldiv_ts_done = timestamp + 37; - - DO_LDS(); - END_OPF; - - // - // J - Jump - // - BEGIN_OPF(J, 0x02, 0); - JTYPE; - - DO_LDS(); - - DO_BRANCH(target << 2, 0xF0000000); - END_OPF; - - // - // JAL - Jump and Link - // - BEGIN_OPF(JAL, 0x03, 0); - JTYPE; - - //GPR_DEPRES_BEGIN - GPR_RES(31); - //GPR_DEPRES_END - - DO_LDS(); - - GPR[31] = PC + 8; - - DO_BRANCH(target << 2, 0xF0000000); - END_OPF; - - // - // JALR - Jump and Link Register - // - BEGIN_OPF(JALR, 0, 0x09); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 tmp = GPR[rs]; - - DO_LDS(); - - GPR[rd] = PC + 8; - - DO_BRANCH(tmp, 0); - - END_OPF; - - // - // JR - Jump Register - // - BEGIN_OPF(JR, 0, 0x08); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 bt = GPR[rs]; - - DO_LDS(); - - DO_BRANCH(bt, 0); - - END_OPF; - - // - // LUI - Load Upper Immediate - // - BEGIN_OPF(LUI, 0x0F, 0); - ITYPE_ZE; // Actually, probably would be sign-extending...if we were emulating a 64-bit MIPS chip :b - - GPR_DEPRES_BEGIN - GPR_RES(rt); - GPR_DEPRES_END - - DO_LDS(); - - GPR[rt] = immediate << 16; - - END_OPF; - - // - // MFHI - Move from HI - // - BEGIN_OPF(MFHI, 0, 0x10); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_RES(rd); - GPR_DEPRES_END - - DO_LDS(); - - if(timestamp < muldiv_ts_done) - { - if(timestamp == muldiv_ts_done - 1) - muldiv_ts_done--; - else - { - do - { - if(ReadAbsorb[ReadAbsorbWhich]) - ReadAbsorb[ReadAbsorbWhich]--; - timestamp++; - } while(timestamp < muldiv_ts_done); - } - } - - GPR[rd] = HI; - - END_OPF; - - - // - // MFLO - Move from LO - // - BEGIN_OPF(MFLO, 0, 0x12); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_RES(rd); - GPR_DEPRES_END - - DO_LDS(); - - if(timestamp < muldiv_ts_done) - { - if(timestamp == muldiv_ts_done - 1) - muldiv_ts_done--; - else - { - do - { - if(ReadAbsorb[ReadAbsorbWhich]) - ReadAbsorb[ReadAbsorbWhich]--; - timestamp++; - } while(timestamp < muldiv_ts_done); - } - } - - GPR[rd] = LO; - - END_OPF; - - - // - // MTHI - Move to HI - // - BEGIN_OPF(MTHI, 0, 0x11); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - HI = GPR[rs]; - - DO_LDS(); - - END_OPF; - - // - // MTLO - Move to LO - // - BEGIN_OPF(MTLO, 0, 0x13); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - LO = GPR[rs]; - - DO_LDS(); - - END_OPF; - - - // - // MULT - Multiply Word - // - BEGIN_OPF(MULT, 0, 0x18); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint64 result; - - result = (int64)(int32)GPR[rs] * (int32)GPR[rt]; - muldiv_ts_done = timestamp + MULT_Tab24[MDFN_lzcount32((GPR[rs] ^ ((int32)GPR[rs] >> 31)) | 0x400)]; - DO_LDS(); - - LO = result; - HI = result >> 32; - - END_OPF; - - // - // MULTU - Multiply Unsigned Word - // - BEGIN_OPF(MULTU, 0, 0x19); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint64 result; - - result = (uint64)GPR[rs] * GPR[rt]; - muldiv_ts_done = timestamp + MULT_Tab24[MDFN_lzcount32(GPR[rs] | 0x400)]; - DO_LDS(); - - LO = result; - HI = result >> 32; - - END_OPF; - - - // - // NOR - NOR - // - BEGIN_OPF(NOR, 0, 0x27); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = ~(GPR[rs] | GPR[rt]); - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - // - // OR - OR - // - BEGIN_OPF(OR, 0, 0x25); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] | GPR[rt]; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // ORI - OR Immediate - // - BEGIN_OPF(ORI, 0x0D, 0); - ITYPE_ZE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = GPR[rs] | immediate; - - DO_LDS(); - - GPR[rt] = result; - - END_OPF; - - - // - // SLL - Shift Word Left Logical - // - BEGIN_OPF(SLL, 0, 0x00); // SLL - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rt] << shamt; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SLLV - Shift Word Left Logical Variable - // - BEGIN_OPF(SLLV, 0, 0x04); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rt] << (GPR[rs] & 0x1F); - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - // - // SLT - Set on Less Than - // - BEGIN_OPF(SLT, 0, 0x2A); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = (bool)((int32)GPR[rs] < (int32)GPR[rt]); - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SLTI - Set on Less Than Immediate - // - BEGIN_OPF(SLTI, 0x0A, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = (bool)((int32)GPR[rs] < (int32)immediate); - - DO_LDS(); - - GPR[rt] = result; - - END_OPF; - - - // - // SLTIU - Set on Less Than Immediate, Unsigned - // - BEGIN_OPF(SLTIU, 0x0B, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = (bool)(GPR[rs] < (uint32)immediate); - - DO_LDS(); - - GPR[rt] = result; - - END_OPF; - - - // - // SLTU - Set on Less Than, Unsigned - // - BEGIN_OPF(SLTU, 0, 0x2B); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = (bool)(GPR[rs] < GPR[rt]); - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SRA - Shift Word Right Arithmetic - // - BEGIN_OPF(SRA, 0, 0x03); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = ((int32)GPR[rt]) >> shamt; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SRAV - Shift Word Right Arithmetic Variable - // - BEGIN_OPF(SRAV, 0, 0x07); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = ((int32)GPR[rt]) >> (GPR[rs] & 0x1F); - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SRL - Shift Word Right Logical - // - BEGIN_OPF(SRL, 0, 0x02); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rt] >> shamt; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - // - // SRLV - Shift Word Right Logical Variable - // - BEGIN_OPF(SRLV, 0, 0x06); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rt] >> (GPR[rs] & 0x1F); - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SUB - Subtract Word - // - BEGIN_OPF(SUB, 0, 0x22); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] - GPR[rt]; - bool ep = (((GPR[rs] ^ GPR[rt])) & (GPR[rs] ^ result)) & 0x80000000; - - DO_LDS(); - - if(MDFN_UNLIKELY(ep)) - { - new_PC = Exception(EXCEPTION_OV, PC, new_PC_mask); - new_PC_mask = 0; - } - else - GPR[rd] = result; - - END_OPF; - - - // - // SUBU - Subtract Unsigned Word - // - BEGIN_OPF(SUBU, 0, 0x23); // SUBU - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] - GPR[rt]; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - - // - // SYSCALL - // - BEGIN_OPF(SYSCALL, 0, 0x0C); - DO_LDS(); - - new_PC = Exception(EXCEPTION_SYSCALL, PC, new_PC_mask); - new_PC_mask = 0; - END_OPF; - - - // - // XOR - // - BEGIN_OPF(XOR, 0, 0x26); - RTYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_RES(rd); - GPR_DEPRES_END - - uint32 result = GPR[rs] ^ GPR[rt]; - - DO_LDS(); - - GPR[rd] = result; - - END_OPF; - - // - // XORI - Exclusive OR Immediate - // - BEGIN_OPF(XORI, 0x0E, 0); - ITYPE_ZE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_RES(rt); - GPR_DEPRES_END - - uint32 result = GPR[rs] ^ immediate; - - DO_LDS(); - - GPR[rt] = result; - END_OPF; - - // - // Memory access instructions(besides the coprocessor ones) follow: - // - - // - // LB - Load Byte - // - BEGIN_OPF(LB, 0x20, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - DO_LDS(); - - LDWhich = rt; - LDValue = (int32)ReadMemory(timestamp, address); - END_OPF; - - // - // LBU - Load Byte Unsigned - // - BEGIN_OPF(LBU, 0x24, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - DO_LDS(); - - LDWhich = rt; - LDValue = ReadMemory(timestamp, address); - END_OPF; - - // - // LH - Load Halfword - // - BEGIN_OPF(LH, 0x21, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - DO_LDS(); - - if(MDFN_UNLIKELY(address & 1)) - { - new_PC = Exception(EXCEPTION_ADEL, PC, new_PC_mask); - new_PC_mask = 0; - } - else - { - LDWhich = rt; - LDValue = (int32)ReadMemory(timestamp, address); - } - END_OPF; - - // - // LHU - Load Halfword Unsigned - // - BEGIN_OPF(LHU, 0x25, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - DO_LDS(); - - if(MDFN_UNLIKELY(address & 1)) - { - new_PC = Exception(EXCEPTION_ADEL, PC, new_PC_mask); - new_PC_mask = 0; - } - else - { - LDWhich = rt; - LDValue = ReadMemory(timestamp, address); - } - END_OPF; - - - // - // LW - Load Word - // - BEGIN_OPF(LW, 0x23, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - DO_LDS(); - - if(MDFN_UNLIKELY(address & 3)) - { - new_PC = Exception(EXCEPTION_ADEL, PC, new_PC_mask); - new_PC_mask = 0; - } - else - { - LDWhich = rt; - LDValue = ReadMemory(timestamp, address); - } - END_OPF; - - // - // SB - Store Byte - // - BEGIN_OPF(SB, 0x28, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - WriteMemory(timestamp, address, GPR[rt]); - - DO_LDS(); - END_OPF; - - // - // SH - Store Halfword - // - BEGIN_OPF(SH, 0x29, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - if(MDFN_UNLIKELY(address & 0x1)) - { - new_PC = Exception(EXCEPTION_ADES, PC, new_PC_mask); - new_PC_mask = 0; - } - else - WriteMemory(timestamp, address, GPR[rt]); - - DO_LDS(); - END_OPF; - - // - // SW - Store Word - // - BEGIN_OPF(SW, 0x2B, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - if(MDFN_UNLIKELY(address & 0x3)) - { - new_PC = Exception(EXCEPTION_ADES, PC, new_PC_mask); - new_PC_mask = 0; - } - else - WriteMemory(timestamp, address, GPR[rt]); - - DO_LDS(); - END_OPF; - - // LWL and LWR load delay slot tomfoolery appears to apply even to MFC0! (and probably MFCn and CFCn as well, though they weren't explicitly tested) - - // - // LWL - Load Word Left - // - BEGIN_OPF(LWL, 0x22, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - //GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - uint32 v = GPR[rt]; - - if(LDWhich == rt) - { - v = LDValue; - ReadFudge = 0; - } - else - { - DO_LDS(); - } - - LDWhich = rt; - switch(address & 0x3) - { - case 0: LDValue = (v & ~(0xFF << 24)) | (ReadMemory(timestamp, address & ~3) << 24); - break; - - case 1: LDValue = (v & ~(0xFFFF << 16)) | (ReadMemory(timestamp, address & ~3) << 16); - break; - - case 2: LDValue = (v & ~(0xFFFFFF << 8)) | (ReadMemory(timestamp, address & ~3, true) << 8); - break; - - case 3: LDValue = (v & ~(0xFFFFFFFF << 0)) | (ReadMemory(timestamp, address & ~3) << 0); - break; - } - END_OPF; - - // - // SWL - Store Word Left - // - BEGIN_OPF(SWL, 0x2A, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - switch(address & 0x3) - { - case 0: WriteMemory(timestamp, address & ~3, GPR[rt] >> 24); - break; - - case 1: WriteMemory(timestamp, address & ~3, GPR[rt] >> 16); - break; - - case 2: WriteMemory(timestamp, address & ~3, GPR[rt] >> 8, true); - break; - - case 3: WriteMemory(timestamp, address & ~3, GPR[rt] >> 0); - break; - } - DO_LDS(); - - END_OPF; - - // - // LWR - Load Word Right - // - BEGIN_OPF(LWR, 0x26, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - //GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - uint32 v = GPR[rt]; - - if(LDWhich == rt) - { - v = LDValue; - ReadFudge = 0; - } - else - { - DO_LDS(); - } - - LDWhich = rt; - switch(address & 0x3) - { - case 0: LDValue = (v & ~(0xFFFFFFFF)) | ReadMemory(timestamp, address); - break; - - case 1: LDValue = (v & ~(0xFFFFFF)) | ReadMemory(timestamp, address, true); - break; - - case 2: LDValue = (v & ~(0xFFFF)) | ReadMemory(timestamp, address); - break; - - case 3: LDValue = (v & ~(0xFF)) | ReadMemory(timestamp, address); - break; - } - END_OPF; - - // - // SWR - Store Word Right - // - BEGIN_OPF(SWR, 0x2E, 0); - ITYPE; - - GPR_DEPRES_BEGIN - GPR_DEP(rs); - GPR_DEP(rt); - GPR_DEPRES_END - - uint32 address = GPR[rs] + immediate; - - switch(address & 0x3) - { - case 0: WriteMemory(timestamp, address, GPR[rt]); - break; - - case 1: WriteMemory(timestamp, address, GPR[rt], true); - break; - - case 2: WriteMemory(timestamp, address, GPR[rt]); - break; - - case 3: WriteMemory(timestamp, address, GPR[rt]); - break; - } - - DO_LDS(); - - END_OPF; - - // - // Mednafen special instruction - // - BEGIN_OPF(INTERRUPT, 0x3F, 0); - if(Halted) - { - goto SkipNPCStuff; - } - else - { - DO_LDS(); - - new_PC = Exception(EXCEPTION_INT, PC, new_PC_mask); - new_PC_mask = 0; - } - END_OPF; - } - - OpDone: ; - - PC = (PC & new_PC_mask) + new_PC; - new_PC_mask = ~0U; - new_PC = 4; - - SkipNPCStuff: ; - - //printf("\n"); - } - } while(MDFN_LIKELY(PSX_EventHandler(timestamp))); - - if(gte_ts_done > 0) - gte_ts_done -= timestamp; - - if(muldiv_ts_done > 0) - muldiv_ts_done -= timestamp; - + + CGBEGIN + CGE(op_SLL) CGE(op_ILL) CGE(op_SRL) CGE(op_SRA) CGE(op_SLLV) CGE(op_ILL) CGE(op_SRLV) CGE(op_SRAV) + CGE(op_JR) CGE(op_JALR) CGE(op_ILL) CGE(op_ILL) CGE(op_SYSCALL) CGE(op_BREAK) CGE(op_ILL) CGE(op_ILL) + CGE(op_MFHI) CGE(op_MTHI) CGE(op_MFLO) CGE(op_MTLO) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_MULT) CGE(op_MULTU) CGE(op_DIV) CGE(op_DIVU) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_ADD) CGE(op_ADDU) CGE(op_SUB) CGE(op_SUBU) CGE(op_AND) CGE(op_OR) CGE(op_XOR) CGE(op_NOR) + CGE(op_ILL) CGE(op_ILL) CGE(op_SLT) CGE(op_SLTU) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + + CGE(op_ILL) CGE(op_BCOND) CGE(op_J) CGE(op_JAL) CGE(op_BEQ) CGE(op_BNE) CGE(op_BLEZ) CGE(op_BGTZ) + CGE(op_ADDI) CGE(op_ADDIU) CGE(op_SLTI) CGE(op_SLTIU) CGE(op_ANDI) CGE(op_ORI) CGE(op_XORI) CGE(op_LUI) + CGE(op_COP0) CGE(op_COP13) CGE(op_COP2) CGE(op_COP13) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_LB) CGE(op_LH) CGE(op_LWL) CGE(op_LW) CGE(op_LBU) CGE(op_LHU) CGE(op_LWR) CGE(op_ILL) + CGE(op_SB) CGE(op_SH) CGE(op_SWL) CGE(op_SW) CGE(op_ILL) CGE(op_ILL) CGE(op_SWR) CGE(op_ILL) + CGE(op_LWC013) CGE(op_LWC013) CGE(op_LWC2) CGE(op_LWC013) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + CGE(op_SWC013) CGE(op_SWC013) CGE(op_SWC2) CGE(op_SWC013) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) CGE(op_ILL) + + // Interrupt portion of this table is constructed so that an interrupt won't be taken when the PC is pointing to a GTE instruction, + // to avoid problems caused by pipeline vs coprocessor nuances that aren't emulated. + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + + CGE(op_ILL) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_COP2) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) CGE(op_INTERRUPT) + CGEND + + { + BEGIN_OPF(ILL); + PSX_WARNING("[CPU] Unknown instruction @%08x = %08x, op=%02x, funct=%02x", PC, instr, instr >> 26, (instr & 0x3F)); + DO_LDS(); + new_PC = Exception(EXCEPTION_RI, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + END_OPF; + + // + // ADD - Add Word + // + BEGIN_OPF(ADD); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] + GPR[rt]; + bool ep = ((~(GPR[rs] ^ GPR[rt])) & (GPR[rs] ^ result)) & 0x80000000; + + DO_LDS(); + + if(MDFN_UNLIKELY(ep)) + { + new_PC = Exception(EXCEPTION_OV, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + GPR[rd] = result; + + END_OPF; + + // + // ADDI - Add Immediate Word + // + BEGIN_OPF(ADDI); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = GPR[rs] + immediate; + bool ep = ((~(GPR[rs] ^ immediate)) & (GPR[rs] ^ result)) & 0x80000000; + + DO_LDS(); + + if(MDFN_UNLIKELY(ep)) + { + new_PC = Exception(EXCEPTION_OV, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + GPR[rt] = result; + + END_OPF; + + // + // ADDIU - Add Immediate Unsigned Word + // + BEGIN_OPF(ADDIU); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = GPR[rs] + immediate; + + DO_LDS(); + + GPR[rt] = result; + + END_OPF; + + // + // ADDU - Add Unsigned Word + // + BEGIN_OPF(ADDU); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] + GPR[rt]; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + // + // AND - And + // + BEGIN_OPF(AND); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] & GPR[rt]; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + // + // ANDI - And Immediate + // + BEGIN_OPF(ANDI); + ITYPE_ZE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = GPR[rs] & immediate; + + DO_LDS(); + + GPR[rt] = result; + + END_OPF; + + // + // BEQ - Branch on Equal + // + BEGIN_OPF(BEQ); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + const bool result = (GPR[rs] == GPR[rt]); + + DO_LDS(); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + END_OPF; + + // Bah, why does MIPS encoding have to be funky like this. :( + // Handles BGEZ, BGEZAL, BLTZ, BLTZAL + BEGIN_OPF(BCOND); + const uint32 tv = GPR[(instr >> 21) & 0x1F]; + const uint32 riv = (instr >> 16) & 0x1F; + const uint32 immediate = (int32)(int16)(instr & 0xFFFF); + const bool result = (int32)(tv ^ (riv << 31)) < 0; + const uint32 link = ((riv & 0x1E) == 0x10) ? 31 : 0; + + GPR_DEPRES_BEGIN + GPR_DEP((instr >> 21) & 0x1F); + GPR_RES(link); + GPR_DEPRES_END + + DO_LDS(); + + DO_BRANCH(result, (immediate << 2), ~0U, true, link); + END_OPF; + + + // + // BGTZ - Branch on Greater than Zero + // + BEGIN_OPF(BGTZ); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + const bool result = (int32)GPR[rs] > 0; + + DO_LDS(); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + END_OPF; + + // + // BLEZ - Branch on Less Than or Equal to Zero + // + BEGIN_OPF(BLEZ); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + const bool result = (int32)GPR[rs] <= 0; + + DO_LDS(); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + END_OPF; + + // + // BNE - Branch on Not Equal + // + BEGIN_OPF(BNE); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + const bool result = GPR[rs] != GPR[rt]; + + DO_LDS(); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + END_OPF; + + // + // BREAK - Breakpoint + // + BEGIN_OPF(BREAK); + DO_LDS(); + new_PC = Exception(EXCEPTION_BP, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + END_OPF; + + // Cop "instructions": CFCz(no CP0), COPz, CTCz(no CP0), LWCz(no CP0), MFCz, MTCz, SWCz(no CP0) + // + // COP0 instructions + // + BEGIN_OPF(COP0); + const uint32 sub_op = (instr >> 21) & 0x1F; + const uint32 rt = (instr >> 16) & 0x1F; + const uint32 rd = (instr >> 11) & 0x1F; + const uint32 val = GPR[rt]; + + switch(sub_op) + { + default: + DO_LDS(); + break; + + case 0x02: + case 0x06: + DO_LDS(); + new_PC = Exception(EXCEPTION_RI, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + break; + + case 0x00: // MFC0 - Move from Coprocessor + switch(rd) + { + case 0x00: + case 0x01: + case 0x02: + case 0x04: + case 0x0A: + DO_LDS(); + new_PC = Exception(EXCEPTION_RI, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + break; + + case 0x03: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + LDAbsorb = 0; + LDWhich = rt; + LDValue = CP0.Regs[rd]; + break; + + default: + // Tested to be rather NOPish + DO_LDS(); + PSX_DBG(PSX_DBG_WARNING, "[CPU] MFC0 from unmapped CP0 register %u.\n", rd); + break; + } + break; + + case 0x04: // MTC0 - Move to Coprocessor + DO_LDS(); + switch(rd) + { + case 0x00: + case 0x01: + case 0x02: + case 0x04: + case 0x0A: + new_PC = Exception(EXCEPTION_RI, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + break; + + case CP0REG_BPC: + CP0.BPC = val; + break; + + case CP0REG_BDA: + CP0.BDA = val; + break; + + case CP0REG_DCIC: + if(val) + { + PSX_DBG(PSX_DBG_WARNING, "[CPU] Non-zero write to DCIC: 0x%08x\n", val); + } + CP0.DCIC = val & 0xFF80003F; + break; + + case CP0REG_BDAM: + CP0.BDAM = val; + break; + + case CP0REG_BPCM: + CP0.BPCM = val; + break; + + case CP0REG_CAUSE: + CP0.CAUSE &= ~(0x3 << 8); + CP0.CAUSE |= val & (0x3 << 8); + RecalcIPCache(); + break; + + case CP0REG_SR: + if((CP0.SR ^ val) & 0x10000) + PSX_DBG(PSX_DBG_SPARSE, "[CPU] IsC %u->%u\n", (bool)(CP0.SR & (1U << 16)), (bool)(val & (1U << 16))); + + CP0.SR = val & ~( (0x3 << 26) | (0x3 << 23) | (0x3 << 6)); + RecalcIPCache(); + break; + } + break; + + case 0x08: // BC + case 0x0C: + DO_LDS(); + { + const uint32 immediate = (int32)(int16)(instr & 0xFFFF); + const bool result = (false == (bool)(instr & (1U << 16))); + + PSX_DBG(PSX_DBG_WARNING, "[CPU] BC0x instruction(0x%08x) @ PC=0x%08x\n", instr, PC); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + } + break; + + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: + DO_LDS(); + { + const uint32 cp0_op = instr & 0x1F; // Not 0x3F + + if(MDFN_LIKELY(cp0_op == 0x10)) // RFE + { + // "Pop" + CP0.SR = (CP0.SR & ~0x0F) | ((CP0.SR >> 2) & 0x0F); + RecalcIPCache(); + } + else if(cp0_op == 0x01 || cp0_op == 0x02 || cp0_op == 0x06 || cp0_op == 0x08) // TLBR, TLBWI, TLBWR, TLBP + { + new_PC = Exception(EXCEPTION_RI, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + } + break; + } + END_OPF; + + // + // COP2 + // + BEGIN_OPF(COP2); + const uint32 sub_op = (instr >> 21) & 0x1F; + const uint32 rt = (instr >> 16) & 0x1F; + const uint32 rd = (instr >> 11) & 0x1F; + const uint32 val = GPR[rt]; + + if(MDFN_UNLIKELY(!(CP0.SR & (1U << (28 + 2))))) + { + DO_LDS(); + new_PC = Exception(EXCEPTION_COPU, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else switch(sub_op) + { + default: + DO_LDS(); + break; + + case 0x00: // MFC2 - Move from Coprocessor + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + if(timestamp < gte_ts_done) + { + LDAbsorb = gte_ts_done - timestamp; + timestamp = gte_ts_done; + } + else + LDAbsorb = 0; + + LDWhich = rt; + LDValue = GTE_ReadDR(rd); + break; + + case 0x04: // MTC2 - Move to Coprocessor + DO_LDS(); + + if(timestamp < gte_ts_done) + timestamp = gte_ts_done; + + GTE_WriteDR(rd, val); + break; + + case 0x02: // CFC2 + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + if(timestamp < gte_ts_done) + { + LDAbsorb = gte_ts_done - timestamp; + timestamp = gte_ts_done; + } + else + LDAbsorb = 0; + + LDWhich = rt; + LDValue = GTE_ReadCR(rd); + break; + + case 0x06: // CTC2 + DO_LDS(); + + if(timestamp < gte_ts_done) + timestamp = gte_ts_done; + + GTE_WriteCR(rd, val); + break; + + case 0x08: + case 0x0C: + DO_LDS(); + { + const uint32 immediate = (int32)(int16)(instr & 0xFFFF); + const bool result = (false == (bool)(instr & (1U << 16))); + + PSX_DBG(PSX_DBG_WARNING, "[CPU] BC2x instruction(0x%08x) @ PC=0x%08x\n", instr, PC); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + } + break; + + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: + DO_LDS(); + + if(timestamp < gte_ts_done) + timestamp = gte_ts_done; + gte_ts_done = timestamp + GTE_Instruction(instr); + break; + } + END_OPF; + + // + // COP1, COP3 + // + BEGIN_OPF(COP13); + DO_LDS(); + + if(!(CP0.SR & (1U << (28 + ((instr >> 26) & 0x3))))) + { + new_PC = Exception(EXCEPTION_COPU, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + const uint32 sub_op = (instr >> 21) & 0x1F; + + PSX_DBG(PSX_DBG_WARNING, "[CPU] COP%u instruction(0x%08x) @ PC=0x%08x\n", (instr >> 26) & 0x3, instr, PC); + + if(sub_op == 0x08 || sub_op == 0x0C) + { + const uint32 immediate = (int32)(int16)(instr & 0xFFFF); + const bool result = (false == (bool)(instr & (1U << 16))); + + DO_BRANCH(result, (immediate << 2), ~0U, false, 0); + } + } + END_OPF; + + // + // LWC0, LWC1, LWC3 + // + BEGIN_OPF(LWC013); + ITYPE; + const uint32 address = GPR[rs] + immediate; + + DO_LDS(); + + if(!(CP0.SR & (1U << (28 + ((instr >> 26) & 0x3))))) + { + new_PC = Exception(EXCEPTION_COPU, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(MDFN_UNLIKELY(address & 3)) + { + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + PSX_DBG(PSX_DBG_WARNING, "[CPU] LWC%u instruction(0x%08x) @ PC=0x%08x\n", (instr >> 26) & 0x3, instr, PC); + + ReadMemory(timestamp, address, false, true); + } + } + END_OPF; + + // + // LWC2 + // + BEGIN_OPF(LWC2); + ITYPE; + const uint32 address = GPR[rs] + immediate; + + DO_LDS(); + + if(MDFN_UNLIKELY(address & 3)) + { + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(timestamp < gte_ts_done) + timestamp = gte_ts_done; + + GTE_WriteDR(rt, ReadMemory(timestamp, address, false, true)); + } + // GTE stuff here + END_OPF; + + // + // SWC0, SWC1, SCW3 + // + BEGIN_OPF(SWC013); + ITYPE; + const uint32 address = GPR[rs] + immediate; + + DO_LDS(); + + if(!(CP0.SR & (1U << (28 + ((instr >> 26) & 0x3))))) + { + new_PC = Exception(EXCEPTION_COPU, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(MDFN_UNLIKELY(address & 0x3)) + { + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADES, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + PSX_DBG(PSX_DBG_WARNING, "[CPU] SWC%u instruction(0x%08x) @ PC=0x%08x\n", (instr >> 26) & 0x3, instr, PC); + //WriteMemory(timestamp, address, SOMETHING); + } + } + END_OPF; + + // + // SWC2 + // + BEGIN_OPF(SWC2); + ITYPE; + const uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(address & 0x3)) + { + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADES, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(timestamp < gte_ts_done) + timestamp = gte_ts_done; + + WriteMemory(timestamp, address, GTE_ReadDR(rt)); + } + DO_LDS(); + END_OPF; + + // + // DIV - Divide Word + // + BEGIN_OPF(DIV); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + if(!GPR[rt]) + { + if(GPR[rs] & 0x80000000) + LO = 1; + else + LO = 0xFFFFFFFF; + + HI = GPR[rs]; + } + else if(GPR[rs] == 0x80000000 && GPR[rt] == 0xFFFFFFFF) + { + LO = 0x80000000; + HI = 0; + } + else + { + LO = (int32)GPR[rs] / (int32)GPR[rt]; + HI = (int32)GPR[rs] % (int32)GPR[rt]; + } + muldiv_ts_done = timestamp + 37; + + DO_LDS(); + + END_OPF; + + + // + // DIVU - Divide Unsigned Word + // + BEGIN_OPF(DIVU); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + if(!GPR[rt]) + { + LO = 0xFFFFFFFF; + HI = GPR[rs]; + } + else + { + LO = GPR[rs] / GPR[rt]; + HI = GPR[rs] % GPR[rt]; + } + muldiv_ts_done = timestamp + 37; + + DO_LDS(); + END_OPF; + + // + // J - Jump + // + BEGIN_OPF(J); + JTYPE; + + DO_LDS(); + + DO_BRANCH(true, target << 2, 0xF0000000, false, 0); + END_OPF; + + // + // JAL - Jump and Link + // + BEGIN_OPF(JAL); + JTYPE; + + //GPR_DEPRES_BEGIN + GPR_RES(31); + //GPR_DEPRES_END + + DO_LDS(); + + DO_BRANCH(true, target << 2, 0xF0000000, true, 31); + END_OPF; + + // + // JALR - Jump and Link Register + // + BEGIN_OPF(JALR); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 tmp = GPR[rs]; + + DO_LDS(); + + DO_BRANCH(true, tmp, 0, true, rd); + END_OPF; + + // + // JR - Jump Register + // + BEGIN_OPF(JR); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 bt = GPR[rs]; + + DO_LDS(); + + DO_BRANCH(true, bt, 0, false, 0); + END_OPF; + + // + // LUI - Load Upper Immediate + // + BEGIN_OPF(LUI); + ITYPE_ZE; // Actually, probably would be sign-extending...if we were emulating a 64-bit MIPS chip :b + + GPR_DEPRES_BEGIN + GPR_RES(rt); + GPR_DEPRES_END + + DO_LDS(); + + GPR[rt] = immediate << 16; + + END_OPF; + + // + // MFHI - Move from HI + // + BEGIN_OPF(MFHI); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_RES(rd); + GPR_DEPRES_END + + DO_LDS(); + + if(timestamp < muldiv_ts_done) + { + if(timestamp == muldiv_ts_done - 1) + muldiv_ts_done--; + else + { + do + { + if(ReadAbsorb[ReadAbsorbWhich]) + ReadAbsorb[ReadAbsorbWhich]--; + timestamp++; + } while(timestamp < muldiv_ts_done); + } + } + + GPR[rd] = HI; + + END_OPF; + + + // + // MFLO - Move from LO + // + BEGIN_OPF(MFLO); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_RES(rd); + GPR_DEPRES_END + + DO_LDS(); + + if(timestamp < muldiv_ts_done) + { + if(timestamp == muldiv_ts_done - 1) + muldiv_ts_done--; + else + { + do + { + if(ReadAbsorb[ReadAbsorbWhich]) + ReadAbsorb[ReadAbsorbWhich]--; + timestamp++; + } while(timestamp < muldiv_ts_done); + } + } + + GPR[rd] = LO; + + END_OPF; + + + // + // MTHI - Move to HI + // + BEGIN_OPF(MTHI); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + HI = GPR[rs]; + + DO_LDS(); + + END_OPF; + + // + // MTLO - Move to LO + // + BEGIN_OPF(MTLO); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + LO = GPR[rs]; + + DO_LDS(); + + END_OPF; + + + // + // MULT - Multiply Word + // + BEGIN_OPF(MULT); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint64 result; + + result = (int64)(int32)GPR[rs] * (int32)GPR[rt]; + muldiv_ts_done = timestamp + MULT_Tab24[MDFN_lzcount32((GPR[rs] ^ ((int32)GPR[rs] >> 31)) | 0x400)]; + DO_LDS(); + + LO = result; + HI = result >> 32; + + END_OPF; + + // + // MULTU - Multiply Unsigned Word + // + BEGIN_OPF(MULTU); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint64 result; + + result = (uint64)GPR[rs] * GPR[rt]; + muldiv_ts_done = timestamp + MULT_Tab24[MDFN_lzcount32(GPR[rs] | 0x400)]; + DO_LDS(); + + LO = result; + HI = result >> 32; + + END_OPF; + + + // + // NOR - NOR + // + BEGIN_OPF(NOR); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = ~(GPR[rs] | GPR[rt]); + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + // + // OR - OR + // + BEGIN_OPF(OR); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] | GPR[rt]; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // ORI - OR Immediate + // + BEGIN_OPF(ORI); + ITYPE_ZE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = GPR[rs] | immediate; + + DO_LDS(); + + GPR[rt] = result; + + END_OPF; + + + // + // SLL - Shift Word Left Logical + // + BEGIN_OPF(SLL); // SLL + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rt] << shamt; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SLLV - Shift Word Left Logical Variable + // + BEGIN_OPF(SLLV); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rt] << (GPR[rs] & 0x1F); + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + // + // SLT - Set on Less Than + // + BEGIN_OPF(SLT); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = (bool)((int32)GPR[rs] < (int32)GPR[rt]); + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SLTI - Set on Less Than Immediate + // + BEGIN_OPF(SLTI); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = (bool)((int32)GPR[rs] < (int32)immediate); + + DO_LDS(); + + GPR[rt] = result; + + END_OPF; + + + // + // SLTIU - Set on Less Than Immediate, Unsigned + // + BEGIN_OPF(SLTIU); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = (bool)(GPR[rs] < (uint32)immediate); + + DO_LDS(); + + GPR[rt] = result; + + END_OPF; + + + // + // SLTU - Set on Less Than, Unsigned + // + BEGIN_OPF(SLTU); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = (bool)(GPR[rs] < GPR[rt]); + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SRA - Shift Word Right Arithmetic + // + BEGIN_OPF(SRA); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = ((int32)GPR[rt]) >> shamt; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SRAV - Shift Word Right Arithmetic Variable + // + BEGIN_OPF(SRAV); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = ((int32)GPR[rt]) >> (GPR[rs] & 0x1F); + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SRL - Shift Word Right Logical + // + BEGIN_OPF(SRL); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rt] >> shamt; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + // + // SRLV - Shift Word Right Logical Variable + // + BEGIN_OPF(SRLV); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rt] >> (GPR[rs] & 0x1F); + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SUB - Subtract Word + // + BEGIN_OPF(SUB); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] - GPR[rt]; + bool ep = (((GPR[rs] ^ GPR[rt])) & (GPR[rs] ^ result)) & 0x80000000; + + DO_LDS(); + + if(MDFN_UNLIKELY(ep)) + { + new_PC = Exception(EXCEPTION_OV, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + GPR[rd] = result; + + END_OPF; + + + // + // SUBU - Subtract Unsigned Word + // + BEGIN_OPF(SUBU); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] - GPR[rt]; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + + // + // SYSCALL + // + BEGIN_OPF(SYSCALL); + DO_LDS(); + + new_PC = Exception(EXCEPTION_SYSCALL, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + END_OPF; + + + // + // XOR + // + BEGIN_OPF(XOR); + RTYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_RES(rd); + GPR_DEPRES_END + + uint32 result = GPR[rs] ^ GPR[rt]; + + DO_LDS(); + + GPR[rd] = result; + + END_OPF; + + // + // XORI - Exclusive OR Immediate + // + BEGIN_OPF(XORI); + ITYPE_ZE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_RES(rt); + GPR_DEPRES_END + + uint32 result = GPR[rs] ^ immediate; + + DO_LDS(); + + GPR[rt] = result; + END_OPF; + + // + // Memory access instructions(besides the coprocessor ones) follow: + // + + // + // LB - Load Byte + // + BEGIN_OPF(LB); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + LDWhich = rt; + LDValue = (int32)ReadMemory(timestamp, address); + END_OPF; + + // + // LBU - Load Byte Unsigned + // + BEGIN_OPF(LBU); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + LDWhich = rt; + LDValue = ReadMemory(timestamp, address); + END_OPF; + + // + // LH - Load Halfword + // + BEGIN_OPF(LH); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(address & 1)) + { + DO_LDS(); + + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + LDWhich = rt; + LDValue = (int32)ReadMemory(timestamp, address); + } + END_OPF; + + // + // LHU - Load Halfword Unsigned + // + BEGIN_OPF(LHU); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(address & 1)) + { + DO_LDS(); + + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + LDWhich = rt; + LDValue = ReadMemory(timestamp, address); + } + END_OPF; + + + // + // LW - Load Word + // + BEGIN_OPF(LW); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(address & 3)) + { + DO_LDS(); + + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADEL, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + { + if(MDFN_UNLIKELY(LDWhich == rt)) + LDWhich = 0; + + DO_LDS(); + + LDWhich = rt; + LDValue = ReadMemory(timestamp, address); + } + END_OPF; + + // + // SB - Store Byte + // + BEGIN_OPF(SB); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + WriteMemory(timestamp, address, GPR[rt]); + + DO_LDS(); + END_OPF; + + // + // SH - Store Halfword + // + BEGIN_OPF(SH); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(address & 0x1)) + { + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADES, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + WriteMemory(timestamp, address, GPR[rt]); + + DO_LDS(); + END_OPF; + + // + // SW - Store Word + // + BEGIN_OPF(SW); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + if(MDFN_UNLIKELY(address & 0x3)) + { + CP0.BADVA = address; + new_PC = Exception(EXCEPTION_ADES, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + else + WriteMemory(timestamp, address, GPR[rt]); + + DO_LDS(); + END_OPF; + + // LWL and LWR load delay slot tomfoolery appears to apply even to MFC0! (and probably MFCn and CFCn as well, though they weren't explicitly tested) + + // + // LWL - Load Word Left + // + BEGIN_OPF(LWL); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + //GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + uint32 v = GPR[rt]; + + if(LDWhich == rt) + { + v = LDValue; + ReadFudge = 0; + } + else + { + DO_LDS(); + } + + LDWhich = rt; + switch(address & 0x3) + { + case 0: LDValue = (v & ~(0xFF << 24)) | (ReadMemory(timestamp, address & ~3) << 24); + break; + + case 1: LDValue = (v & ~(0xFFFF << 16)) | (ReadMemory(timestamp, address & ~3) << 16); + break; + + case 2: LDValue = (v & ~(0xFFFFFF << 8)) | (ReadMemory(timestamp, address & ~3, true) << 8); + break; + + case 3: LDValue = (v & ~(0xFFFFFFFF << 0)) | (ReadMemory(timestamp, address & ~3) << 0); + break; + } + END_OPF; + + // + // SWL - Store Word Left + // + BEGIN_OPF(SWL); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + switch(address & 0x3) + { + case 0: WriteMemory(timestamp, address & ~3, GPR[rt] >> 24); + break; + + case 1: WriteMemory(timestamp, address & ~3, GPR[rt] >> 16); + break; + + case 2: WriteMemory(timestamp, address & ~3, GPR[rt] >> 8, true); + break; + + case 3: WriteMemory(timestamp, address & ~3, GPR[rt] >> 0); + break; + } + DO_LDS(); + + END_OPF; + + // + // LWR - Load Word Right + // + BEGIN_OPF(LWR); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + //GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + uint32 v = GPR[rt]; + + if(LDWhich == rt) + { + v = LDValue; + ReadFudge = 0; + } + else + { + DO_LDS(); + } + + LDWhich = rt; + switch(address & 0x3) + { + case 0: LDValue = (v & ~(0xFFFFFFFF)) | ReadMemory(timestamp, address); + break; + + case 1: LDValue = (v & ~(0xFFFFFF)) | ReadMemory(timestamp, address, true); + break; + + case 2: LDValue = (v & ~(0xFFFF)) | ReadMemory(timestamp, address); + break; + + case 3: LDValue = (v & ~(0xFF)) | ReadMemory(timestamp, address); + break; + } + END_OPF; + + // + // SWR - Store Word Right + // + BEGIN_OPF(SWR); + ITYPE; + + GPR_DEPRES_BEGIN + GPR_DEP(rs); + GPR_DEP(rt); + GPR_DEPRES_END + + uint32 address = GPR[rs] + immediate; + + switch(address & 0x3) + { + case 0: WriteMemory(timestamp, address, GPR[rt]); + break; + + case 1: WriteMemory(timestamp, address, GPR[rt], true); + break; + + case 2: WriteMemory(timestamp, address, GPR[rt]); + break; + + case 3: WriteMemory(timestamp, address, GPR[rt]); + break; + } + + DO_LDS(); + + END_OPF; + + // + // Mednafen special instruction + // + BEGIN_OPF(INTERRUPT); + if(Halted) + { + goto SkipNPCStuff; + } + else + { + DO_LDS(); + + new_PC = Exception(EXCEPTION_INT, PC, new_PC, new_PC_mask, instr); + new_PC_mask = 0; + } + END_OPF; + } + + OpDone: ; + + PC = (PC & new_PC_mask) + new_PC; + new_PC_mask = ~0U; + new_PC = 4; + + SkipNPCStuff: ; + + //printf("\n"); + } + } while(MDFN_LIKELY(PSX_EventHandler(timestamp))); + + if(gte_ts_done > 0) + gte_ts_done -= timestamp; + + if(muldiv_ts_done > 0) + muldiv_ts_done -= timestamp; + ACTIVE_TO_BACKING; - return(timestamp); } diff --git a/psx/octoshock/psx/cpu.h b/psx/octoshock/psx/cpu.h index e6bfe3b5a1..619f8c27f2 100644 --- a/psx/octoshock/psx/cpu.h +++ b/psx/octoshock/psx/cpu.h @@ -1,263 +1,262 @@ -#ifndef __MDFN_PSX_CPU_H -#define __MDFN_PSX_CPU_H - -/* - Load delay notes: - - // Takes 1 less - ".set noreorder\n\t" - ".set nomacro\n\t" - "lw %0, 0(%2)\n\t" - "nop\n\t" - "nop\n\t" - "or %0, %1, %1\n\t" - - // cycle than this: - ".set noreorder\n\t" - ".set nomacro\n\t" - "lw %0, 0(%2)\n\t" - "nop\n\t" - "or %0, %1, %1\n\t" - "nop\n\t" - - - // Both of these - ".set noreorder\n\t" - ".set nomacro\n\t" - "lw %0, 0(%2)\n\t" - "nop\n\t" - "nop\n\t" - "or %1, %0, %0\n\t" - - // take same...(which is kind of odd). - ".set noreorder\n\t" - ".set nomacro\n\t" - "lw %0, 0(%2)\n\t" - "nop\n\t" - "or %1, %0, %0\n\t" - "nop\n\t" -*/ - -#include "gte.h" - -namespace MDFN_IEN_PSX -{ - -#define PS_CPU_EMULATE_ICACHE 1 - -class PS_CPU -{ - public: - - PS_CPU() MDFN_COLD; - ~PS_CPU() MDFN_COLD; - - templatevoid SyncState(EW::NewState *ns); - - // FAST_MAP_* enums are in BYTES(8-bit), not in 32-bit units("words" in MIPS context), but the sizes - // will always be multiples of 4. - enum { FAST_MAP_SHIFT = 16 }; - enum { FAST_MAP_PSIZE = 1 << FAST_MAP_SHIFT }; - - void SetFastMap(void *region_mem, uint32 region_address, uint32 region_size); - - INLINE void SetEventNT(const pscpu_timestamp_t next_event_ts_arg) - { - next_event_ts = next_event_ts_arg; - } - - pscpu_timestamp_t Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode); - - void Power(void) MDFN_COLD; - - // which ranges 0-5, inclusive - void AssertIRQ(unsigned which, bool asserted); - - void SetHalt(bool status); - - // TODO eventually: factor BIU address decoding directly in the CPU core somehow without hurting speed. - void SetBIU(uint32 val); - uint32 GetBIU(void); - - private: - - uint32 GPR[32 + 1]; // GPR[32] Used as dummy in load delay simulation(indexing past the end of real GPR) - - uint32 LO; - uint32 HI; - - - uint32 BACKED_PC; - uint32 BACKED_new_PC; - uint32 BACKED_new_PC_mask; - - uint32 IPCache; - void RecalcIPCache(void); - bool Halted; - - uint32 BACKED_LDWhich; - uint32 BACKED_LDValue; - uint32 LDAbsorb; - - pscpu_timestamp_t next_event_ts; - pscpu_timestamp_t gte_ts_done; - pscpu_timestamp_t muldiv_ts_done; - - uint32 BIU; - - struct __ICache - { - uint32 TV; - uint32 Data; - }; - - union - { - __ICache ICache[1024]; - uint32 ICache_Bulk[2048]; - }; - - enum - { - CP0REG_BPC = 3, // PC breakpoint address. - CP0REG_BDA = 5, // Data load/store breakpoint address. - CP0REG_TAR = 6, // Target address(???) - CP0REG_DCIC = 7, // Cache control - CP0REG_BDAM = 9, // Data load/store address mask. - CP0REG_BPCM = 11, // PC breakpoint address mask. - CP0REG_SR = 12, - CP0REG_CAUSE = 13, - CP0REG_EPC = 14, - CP0REG_PRID = 15, // Product ID - CP0REG_ERREG = 16 - }; - - struct - { - union - { - uint32 Regs[32]; - struct - { - uint32 Unused00; - uint32 Unused01; - uint32 Unused02; - uint32 BPC; // RW - uint32 Unused04; - uint32 BDA; // RW - uint32 TAR; - uint32 DCIC; // RW - uint32 Unused08; - uint32 BDAM; // R/W - uint32 Unused0A; - uint32 BPCM; // R/W - uint32 SR; // R/W - uint32 CAUSE; // R/W(partial) - uint32 EPC; // R - uint32 PRID; // R - uint32 ERREG; // ?(may not exist, test) - }; - }; - } CP0; - -#if 1 - //uint32 WrAbsorb; - //uint8 WrAbsorbShift; - - // On read: - //WrAbsorb = 0; - //WrAbsorbShift = 0; - - // On write: - //WrAbsorb >>= (WrAbsorbShift >> 2) & 8; - //WrAbsorbShift -= (WrAbsorbShift >> 2) & 8; - - //WrAbsorb |= (timestamp - pre_write_timestamp) << WrAbsorbShift; - //WrAbsorbShift += 8; -#endif - - uint8 ReadAbsorb[0x20 + 1]; - uint8 ReadAbsorbWhich; - uint8 ReadFudge; - - //uint32 WriteAbsorb; - //uint8 WriteAbsorbCount; - //uint8 WriteAbsorbMonkey; - uint8 MULT_Tab24[24]; - - MultiAccessSizeMem<1024, false> ScratchRAM; - - //PS_GTE GTE; - - uint8 *FastMap[1 << (32 - FAST_MAP_SHIFT)]; - uint8 DummyPage[FAST_MAP_PSIZE]; - - enum - { - EXCEPTION_INT = 0, - EXCEPTION_MOD = 1, - EXCEPTION_TLBL = 2, - EXCEPTION_TLBS = 3, - EXCEPTION_ADEL = 4, // Address error on load - EXCEPTION_ADES = 5, // Address error on store - EXCEPTION_IBE = 6, // Instruction bus error - EXCEPTION_DBE = 7, // Data bus error - EXCEPTION_SYSCALL = 8, // System call - EXCEPTION_BP = 9, // Breakpoint - EXCEPTION_RI = 10, // Reserved instruction - EXCEPTION_COPU = 11, // Coprocessor unusable - EXCEPTION_OV = 12 // Arithmetic overflow - }; - - uint32 Exception(uint32 code, uint32 PC, const uint32 NPM) MDFN_WARN_UNUSED_RESULT; - - template pscpu_timestamp_t RunReal(pscpu_timestamp_t timestamp_in) NO_INLINE; - - template T PeekMemory(uint32 address) MDFN_COLD; - template void PokeMemory(uint32 address, T value) MDFN_COLD; - template T ReadMemory(pscpu_timestamp_t ×tamp, uint32 address, bool DS24 = false, bool LWC_timing = false); - template void WriteMemory(pscpu_timestamp_t ×tamp, uint32 address, uint32 value, bool DS24 = false); - - - // - // Mednafen debugger stuff follows: - // - public: - void SetCPUHook(void (*cpuh)(const pscpu_timestamp_t timestamp, uint32 pc), void (*addbt)(uint32 from, uint32 to, bool exception)); - void CheckBreakpoints(void (*callback)(bool write, uint32 address, unsigned int len), uint32 instr); - void* debug_GetScratchRAMPtr() { return ScratchRAM.data8; } - void* debug_GetGPRPtr() { return GPR; } - - enum - { - GSREG_GPR = 0, - GSREG_PC = 32, - GSREG_PC_NEXT, - GSREG_IN_BD_SLOT, - GSREG_LO, - GSREG_HI, - GSREG_SR, - GSREG_CAUSE, - GSREG_EPC, - }; - - uint32 GetRegister(unsigned int which, char *special, const uint32 special_len); - void SetRegister(unsigned int which, uint32 value); - bool PeekCheckICache(uint32 PC, uint32 *iw); - - uint8 PeekMem8(uint32 A); - uint16 PeekMem16(uint32 A); - uint32 PeekMem32(uint32 A); - - void PokeMem8(uint32 A, uint8 V); - void PokeMem16(uint32 A, uint16 V); - void PokeMem32(uint32 A, uint32 V); - - private: - void (*CPUHook)(const pscpu_timestamp_t timestamp, uint32 pc); - void (*ADDBT)(uint32 from, uint32 to, bool exception); -}; - -} - -#endif +#ifndef __MDFN_PSX_CPU_H +#define __MDFN_PSX_CPU_H + +/* +Load delay notes: + +// Takes 1 less +".set noreorder\n\t" +".set nomacro\n\t" +"lw %0, 0(%2)\n\t" +"nop\n\t" +"nop\n\t" +"or %0, %1, %1\n\t" + +// cycle than this: +".set noreorder\n\t" +".set nomacro\n\t" +"lw %0, 0(%2)\n\t" +"nop\n\t" +"or %0, %1, %1\n\t" +"nop\n\t" + + +// Both of these +".set noreorder\n\t" +".set nomacro\n\t" +"lw %0, 0(%2)\n\t" +"nop\n\t" +"nop\n\t" +"or %1, %0, %0\n\t" + +// take same...(which is kind of odd). +".set noreorder\n\t" +".set nomacro\n\t" +"lw %0, 0(%2)\n\t" +"nop\n\t" +"or %1, %0, %0\n\t" +"nop\n\t" +*/ + +#include "gte.h" + +namespace MDFN_IEN_PSX +{ + +#define PS_CPU_EMULATE_ICACHE 1 + + class PS_CPU + { + public: + + PS_CPU() MDFN_COLD; + ~PS_CPU() MDFN_COLD; + + templatevoid SyncState(EW::NewState *ns); + + // FAST_MAP_* enums are in BYTES(8-bit), not in 32-bit units("words" in MIPS context), but the sizes + // will always be multiples of 4. + enum { FAST_MAP_SHIFT = 16 }; + enum { FAST_MAP_PSIZE = 1 << FAST_MAP_SHIFT }; + + void SetFastMap(void *region_mem, uint32 region_address, uint32 region_size); + + INLINE void SetEventNT(const pscpu_timestamp_t next_event_ts_arg) + { + next_event_ts = next_event_ts_arg; + } + + pscpu_timestamp_t Run(pscpu_timestamp_t timestamp_in, bool BIOSPrintMode, bool ILHMode); + + void Power(void) MDFN_COLD; + + // which ranges 0-5, inclusive + void AssertIRQ(unsigned which, bool asserted); + + void SetHalt(bool status); + + // TODO eventually: factor BIU address decoding directly in the CPU core somehow without hurting speed. + void SetBIU(uint32 val); + uint32 GetBIU(void); + + private: + + uint32 GPR[32 + 1]; // GPR[32] Used as dummy in load delay simulation(indexing past the end of real GPR) + + uint32 LO; + uint32 HI; + + + uint32 BACKED_PC; + uint32 BACKED_new_PC; + uint32 BACKED_new_PC_mask; + + uint32 IPCache; + void RecalcIPCache(void); + bool Halted; + + uint32 BACKED_LDWhich; + uint32 BACKED_LDValue; + uint32 LDAbsorb; + + pscpu_timestamp_t next_event_ts; + pscpu_timestamp_t gte_ts_done; + pscpu_timestamp_t muldiv_ts_done; + + uint32 BIU; + + struct __ICache + { + uint32 TV; + uint32 Data; + }; + + union + { + __ICache ICache[1024]; + uint32 ICache_Bulk[2048]; + }; + + enum + { + CP0REG_BPC = 3, // PC breakpoint address. + CP0REG_BDA = 5, // Data load/store breakpoint address. + CP0REG_TAR = 6, // Target address(???) + CP0REG_DCIC = 7, // Cache control + CP0REG_BADVA = 8, + CP0REG_BDAM = 9, // Data load/store address mask. + CP0REG_BPCM = 11, // PC breakpoint address mask. + CP0REG_SR = 12, + CP0REG_CAUSE = 13, + CP0REG_EPC = 14, + CP0REG_PRID = 15 // Product ID + }; + + struct + { + union + { + uint32 Regs[32]; + struct + { + uint32 Unused00; + uint32 Unused01; + uint32 Unused02; + uint32 BPC; // RW + uint32 Unused04; + uint32 BDA; // RW + uint32 TAR; // R + uint32 DCIC; // RW + uint32 BADVA; // R + uint32 BDAM; // R/W + uint32 Unused0A; + uint32 BPCM; // R/W + uint32 SR; // R/W + uint32 CAUSE; // R/W(partial) + uint32 EPC; // R + uint32 PRID; // R + }; + }; + } CP0; + +#if 1 + //uint32 WrAbsorb; + //uint8 WrAbsorbShift; + + // On read: + //WrAbsorb = 0; + //WrAbsorbShift = 0; + + // On write: + //WrAbsorb >>= (WrAbsorbShift >> 2) & 8; + //WrAbsorbShift -= (WrAbsorbShift >> 2) & 8; + + //WrAbsorb |= (timestamp - pre_write_timestamp) << WrAbsorbShift; + //WrAbsorbShift += 8; +#endif + + uint8 ReadAbsorb[0x20 + 1]; + uint8 ReadAbsorbWhich; + uint8 ReadFudge; + + //uint32 WriteAbsorb; + //uint8 WriteAbsorbCount; + //uint8 WriteAbsorbMonkey; + uint8 MULT_Tab24[24]; + + MultiAccessSizeMem<1024, false> ScratchRAM; + + //PS_GTE GTE; + + uint8 *FastMap[1 << (32 - FAST_MAP_SHIFT)]; + uint8 DummyPage[FAST_MAP_PSIZE]; + + enum + { + EXCEPTION_INT = 0, + EXCEPTION_MOD = 1, + EXCEPTION_TLBL = 2, + EXCEPTION_TLBS = 3, + EXCEPTION_ADEL = 4, // Address error on load + EXCEPTION_ADES = 5, // Address error on store + EXCEPTION_IBE = 6, // Instruction bus error + EXCEPTION_DBE = 7, // Data bus error + EXCEPTION_SYSCALL = 8, // System call + EXCEPTION_BP = 9, // Breakpoint + EXCEPTION_RI = 10, // Reserved instruction + EXCEPTION_COPU = 11, // Coprocessor unusable + EXCEPTION_OV = 12 // Arithmetic overflow + }; + + uint32 Exception(uint32 code, uint32 PC, const uint32 NP, const uint32 NPM, const uint32 instr) MDFN_WARN_UNUSED_RESULT; + + template pscpu_timestamp_t RunReal(pscpu_timestamp_t timestamp_in) NO_INLINE; + + template T PeekMemory(uint32 address) MDFN_COLD; + template void PokeMemory(uint32 address, T value) MDFN_COLD; + template T ReadMemory(pscpu_timestamp_t ×tamp, uint32 address, bool DS24 = false, bool LWC_timing = false); + template void WriteMemory(pscpu_timestamp_t ×tamp, uint32 address, uint32 value, bool DS24 = false); + + + // + // Mednafen debugger stuff follows: + // + public: + void SetCPUHook(void(*cpuh)(const pscpu_timestamp_t timestamp, uint32 pc), void(*addbt)(uint32 from, uint32 to, bool exception)); + void CheckBreakpoints(void(*callback)(bool write, uint32 address, unsigned int len), uint32 instr); + void* debug_GetScratchRAMPtr() { return ScratchRAM.data8; } + void* debug_GetGPRPtr() { return GPR; } + + enum + { + GSREG_GPR = 0, + GSREG_PC = 32, + GSREG_PC_NEXT, + GSREG_IN_BD_SLOT, + GSREG_LO, + GSREG_HI, + GSREG_SR, + GSREG_CAUSE, + GSREG_EPC, + }; + + uint32 GetRegister(unsigned int which, char *special, const uint32 special_len); + void SetRegister(unsigned int which, uint32 value); + bool PeekCheckICache(uint32 PC, uint32 *iw); + + uint8 PeekMem8(uint32 A); + uint16 PeekMem16(uint32 A); + uint32 PeekMem32(uint32 A); + + void PokeMem8(uint32 A, uint8 V); + void PokeMem16(uint32 A, uint16 V); + void PokeMem32(uint32 A, uint32 V); + + private: + void(*CPUHook)(const pscpu_timestamp_t timestamp, uint32 pc); + void(*ADDBT)(uint32 from, uint32 to, bool exception); + }; + +} + +#endif diff --git a/psx/octoshock/psx/dis.cpp b/psx/octoshock/psx/dis.cpp index 8ecb217d8c..9ebe5fc15a 100644 --- a/psx/octoshock/psx/dis.cpp +++ b/psx/octoshock/psx/dis.cpp @@ -128,13 +128,15 @@ struct OpEntry #define MK_OP(mnemonic, format, op, func, extra_mask) { MASK_OP | (op ? 0 : MASK_FUNC) | extra_mask, ((unsigned)op << 26) | func, mnemonic, format } -#define MK_OP_REGIMM(mnemonic, regop) { MASK_OP | MASK_RT, (0x01U << 26) | (regop << 16), mnemonic, "s, p" } +#define MK_OP_REGIMM(mnemonic, regop_mask, regop) { MASK_OP | (regop_mask << 16), (0x01U << 26) | (regop << 16), mnemonic, "s, p" } #define MK_COPZ(z) { MASK_OP | (0x1U << 25), (0x1U << 25) | ((0x10U | z) << 26), "cop" #z, "F" } #define MK_COP0_FUNC(mnemonic, func) { MASK_OP | (0x1U << 25) | MASK_FUNC, (0x10U << 26) | (0x1U << 25) | func, mnemonic, "" } #define MK_COPZ_XFER(z, mnemonic, format, xf) { MASK_OP | (0x1FU << 21), ((0x10U | z) << 26) | (xf << 21), mnemonic, format } +#define MK_COPZ_BCzx(z, x) { MASK_OP | (0x1BU << 21) | (0x01 << 16), ((0x10U | z) << 26) | (0x08 << 21) | (x << 16), (x ? "bc" #z "t" : "bc" #z "f"), "p" } +#define MK_COPZ_BC(z) MK_COPZ_BCzx(z, 0), MK_COPZ_BCzx(z, 1) #define MK_GTE(mnemonic, format, func) { MASK_OP | (0x1U << 25) | MASK_FUNC, (0x1U << 25) | (0x12U << 26) | func, mnemonic, format } @@ -180,10 +182,12 @@ static OpEntry ops[] = MK_OP("slt", "d, s, t", 0, 42, 0), MK_OP("sltu", "d, s, t", 0, 43, 0), - MK_OP_REGIMM("bgez", 0x01), - MK_OP_REGIMM("bgezal", 0x11), - MK_OP_REGIMM("bltz", 0x00), - MK_OP_REGIMM("bltzal", 0x10), + // keep *al before the non-linking versions, due to mask setup. + MK_OP_REGIMM("bgezal", 0x1F, 0x11), + MK_OP_REGIMM("bltzal", 0x1F, 0x10), + + MK_OP_REGIMM("bgez", 0x01, 0x01), + MK_OP_REGIMM("bltz", 0x00, 0x00), MK_OP("j", "P", 2, 0, 0), @@ -225,6 +229,11 @@ static OpEntry ops[] = MK_COPZ_XFER(2, "ctc2", "t, G", 0x06), MK_COPZ_XFER(3, "ctc3", "t, ?", 0x06), + MK_COPZ_BC(0), + MK_COPZ_BC(1), + MK_COPZ_BC(2), + MK_COPZ_BC(3), + // COP0 stuff here MK_COP0_FUNC("rfe", 0x10), @@ -316,8 +325,8 @@ EW_EXPORT s32 shock_Util_DisassembleMIPS(u32 PC, u32 instr, void* outbuf, s32 bu static const char *cop0_names[32] = { - "CPR0", "CPR1", "CPR2", "BPC", "CPR4", "BDA", "TAR", "DCIC", "CPR8", "BDAM", "CPR10", "BPCM", "SR", "CAUSE", "EPC", "PRID", - "ERREG", "CPR17", "CPR18", "CPR19", "CPR20", "CPR21", "CPR22", "CPR23", "CPR24", "CPR25", "CPR26", "CPR27", "CPR28", "CPR29", "CPR30", "CPR31" + "CPR0", "CPR1", "CPR2", "BPC", "CPR4", "BDA", "TAR", "DCIC", "BADVA", "BDAM", "CPR10", "BPCM", "SR", "CAUSE", "EPC", "PRID", + "CPR16", "CPR17", "CPR18", "CPR19", "CPR20", "CPR21", "CPR22", "CPR23", "CPR24", "CPR25", "CPR26", "CPR27", "CPR28", "CPR29", "CPR30", "CPR31" }; static const char *gte_cr_names[32] = diff --git a/psx/octoshock/psx/gte.cpp b/psx/octoshock/psx/gte.cpp index 2f87f94078..0f38aefae8 100644 --- a/psx/octoshock/psx/gte.cpp +++ b/psx/octoshock/psx/gte.cpp @@ -796,22 +796,6 @@ static INLINE int32 Lm_G(unsigned int which, int32 value) // limit to 4096, not 4095 static INLINE int32 Lm_H(int32 value) { -#if 0 - if(FLAGS & (1 << 15)) - { - value = 0; - FLAGS |= 1 << 12; - return value; - } - - if(FLAGS & (1 << 16)) - { - value = 4096; - FLAGS |= 1 << 12; - return value; - } -#endif - if(value < 0) { value = 0; diff --git a/psx/octoshock/psx/psx.cpp b/psx/octoshock/psx/psx.cpp index 7a30774eb9..eca13166b7 100644 --- a/psx/octoshock/psx/psx.cpp +++ b/psx/octoshock/psx/psx.cpp @@ -272,7 +272,6 @@ static void RebaseTS(const pscpu_timestamp_t timestamp) void PSX_SetEventNT(const int type, const pscpu_timestamp_t next_timestamp) { - assert(type > PSX_EVENT__SYNFIRST && type < PSX_EVENT__SYNLAST); event_list_entry *e = &events[type]; if(next_timestamp < e->event_time) diff --git a/psx/octoshock/psx/timer.cpp b/psx/octoshock/psx/timer.cpp index 88a94ed2e4..573561ed02 100644 --- a/psx/octoshock/psx/timer.cpp +++ b/psx/octoshock/psx/timer.cpp @@ -18,433 +18,441 @@ #include #include "psx.h" #include "timer.h" - -/* - Notes(some of it may be incomplete or wrong in subtle ways) - - Control bits: - Lower 3 bits of mode, for timer1(when mode is | 0x100): - 0x1 = don't count while in vblank(except that the first count while in vblank does go through) - 0x3 = vblank going inactive triggers timer reset, then some interesting behavior where counting again is delayed... - 0x5 = vblank going inactive triggers timer reset, and only count within vblank. - 0x7 = Wait until vblank goes active then inactive, then start counting? - For timer2: - 0x1 = timer stopped(TODO: confirm on real system) - - Target mode enabled 0x008 - IRQ enable 0x010 - --?Affects 0x400 status flag?-- 0x020 - IRQ evaluation auto-reset 0x040 - --unknown-- 0x080 - Clock selection 0x100 - Divide by 8(timer 2 only?) 0x200 - - Counter: - Reset to 0 on writes to the mode/status register. - - Status flags: - Unknown flag 0x0400 - Compare flag 0x0800 - Cleared on mode/status read. - Set when: //ever Counter == 0(proooobably, need to investigate lower 3 bits in relation to this). - - - Overflow/Carry flag 0x1000 - Cleared on mode/status read. - Set when counter overflows from 0xFFFF->0. - - Hidden flags: - IRQ done - Cleared on writes to the mode/status register, on writes to the count register, and apparently automatically when the counter - increments if (Mode & 0x40) [Note: If target mode is enabled, and target is 0, IRQ done flag won't be automatically reset] - - There seems to be a brief period(edge condition?) where, if count to target is enabled, you can (sometimes?) read the target value in the count - register before it's reset to 0. I doubt any games rely on this, but who knows. Maybe a PSX equivalent of the PC Engine "Battle Royale"? ;) - - When the counter == 0, the compare flag is set. An IRQ will be generated if (Mode & 0x10), and the hidden IRQ done flag will be set. -*/ - -/* - Dec. 26, 2011 Note - Due to problems I've had with my GPU timing test program, timer2 appears to be unreliable(clocks are skipped?) when target mode is enabled and the full - 33MHz clock is used(rather than 33MHz / 8). TODO: Investigate further and confirm(or not). - - Jan. 15, 2013 Note: - Counters using GPU clock sources(hretrace,dot clock) reportedly will with a low probability return wrong count values on an actual PS1, so keep this in mind - when writing test programs(IE keep reading the count value until two consecutive reads return the same value). -*/ - -/* - FIXME: Clock appropriately(and update events) when using SetRegister() via the debugger. - - TODO: If we ever return randomish values to "simulate" open bus, remember to change the return type and such of the TIMER_Read() function to full 32-bit too. -*/ - -namespace MDFN_IEN_PSX -{ - -struct Timer -{ - uint32 Mode; - int32 Counter; // Only 16-bit, but 32-bit here for detecting counting past target. - int32 Target; - - int32 Div8Counter; - - bool IRQDone; - int32 DoZeCounting; -}; - -static bool vblank; -static bool hretrace; -static Timer Timers[3]; -static pscpu_timestamp_t lastts; - -static int32 CalcNextEvent(int32 next_event) -{ - for(int i = 0; i < 3; i++) - { - int32 target; - int32 count_delta; - - if((i == 0 || i == 1) && (Timers[i].Mode & 0x100)) // If clocked by GPU, abort for this timer(will result in poor granularity for pixel-clock-derived timer IRQs, but whatever). - continue; - - if(!(Timers[i].Mode & 0x10)) // If IRQ is disabled, abort for this timer. - continue; - - if((Timers[i].Mode & 0x8) && (Timers[i].Counter == 0) && (Timers[i].Target == 0) && !Timers[i].IRQDone) - { - next_event = 1; - continue; - } - - target = ((Timers[i].Mode & 0x8) && (Timers[i].Counter < Timers[i].Target)) ? Timers[i].Target : 0x10000; - - count_delta = target - Timers[i].Counter; - if(count_delta <= 0) - { - PSX_DBG(PSX_DBG_ERROR, "timer %d count_delta <= 0!!! %d %d\n", i, target, Timers[i].Counter); - continue; - } - - { - int32 tmp_clocks; - - if(Timers[i].DoZeCounting <= 0) - continue; - - if((i == 0x2) && (Timers[i].Mode & 0x1)) - continue; - - if((i == 0x2) && (Timers[i].Mode & 0x200)) - { - assert(Timers[i].Div8Counter >= 0 && Timers[i].Div8Counter < 8); - tmp_clocks = ((count_delta - 1) * 8) + (8 - Timers[i].Div8Counter); - } - else - tmp_clocks = count_delta; - - assert(tmp_clocks > 0); - - if(next_event > tmp_clocks) - next_event = tmp_clocks; - } - } - - return(next_event); -} - -static void ClockTimer(int i, uint32 clocks) -{ - int32 before = Timers[i].Counter; - int32 target = 0x10000; - bool zero_tm = false; - - if(Timers[i].DoZeCounting <= 0) - clocks = 0; - - if(i == 0x2) - { - uint32 d8_clocks; - - Timers[i].Div8Counter += clocks; - d8_clocks = Timers[i].Div8Counter >> 3; - Timers[i].Div8Counter -= d8_clocks << 3; - - if(Timers[i].Mode & 0x200) // Divide by 8, at least for timer 0x2 - clocks = d8_clocks; - - if(Timers[i].Mode & 1) - clocks = 0; - } - - if(Timers[i].Mode & 0x008) - target = Timers[i].Target; - - if(target == 0 && Timers[i].Counter == 0) - zero_tm = true; - else - Timers[i].Counter += clocks; - - if(clocks && (Timers[i].Mode & 0x40)) - Timers[i].IRQDone = false; - - if((before < target && Timers[i].Counter >= target) || zero_tm || Timers[i].Counter > 0xFFFF) - { -#if 1 - if(Timers[i].Mode & 0x10) - { - if((Timers[i].Counter - target) > 3) - PSX_WARNING("Timer %d IRQ trigger error: %d", i, Timers[i].Counter - target); - } - -#endif - - - Timers[i].Mode |= 0x0800; - - if(Timers[i].Counter > 0xFFFF) - { - Timers[i].Counter -= 0x10000; - - if(target == 0x10000) - Timers[i].Mode |= 0x1000; - - if(!target) - Timers[i].Counter = 0; - } - - if(target) - Timers[i].Counter -= (Timers[i].Counter / target) * target; - - if((Timers[i].Mode & 0x10) && !Timers[i].IRQDone) - { - Timers[i].IRQDone = true; - - IRQ_Assert(IRQ_TIMER_0 + i, true); - IRQ_Assert(IRQ_TIMER_0 + i, false); - } - - if(Timers[i].Counter && (Timers[i].Mode & 0x40)) - Timers[i].IRQDone = false; - } - -} - -void TIMER_SetVBlank(bool status) -{ - switch(Timers[1].Mode & 0x7) - { - case 0x1: - Timers[1].DoZeCounting = !status; - break; - - case 0x3: - if(vblank && !status) - Timers[1].Counter = 0; - break; - - case 0x5: - Timers[1].DoZeCounting = status; - if(vblank && !status) - Timers[1].Counter = 0; - break; - - case 0x7: - if(Timers[1].DoZeCounting == -1) - { - if(!vblank && status) - Timers[1].DoZeCounting = 0; - } - else if(Timers[1].DoZeCounting == 0) - { - if(vblank && !status) - Timers[1].DoZeCounting = 1; - } - break; - } - vblank = status; -} - -void TIMER_SetHRetrace(bool status) -{ - if(hretrace && !status) - { - if((Timers[0].Mode & 0x7) == 0x3) - Timers[0].Counter = 0; - } - - hretrace = status; -} - -void TIMER_AddDotClocks(uint32 count) -{ - if(Timers[0].Mode & 0x100) - ClockTimer(0, count); -} - -void TIMER_ClockHRetrace(void) -{ - if(Timers[1].Mode & 0x100) - ClockTimer(1, 1); -} - -pscpu_timestamp_t TIMER_Update(const pscpu_timestamp_t timestamp) -{ - int32 cpu_clocks = timestamp - lastts; - - for(int i = 0; i < 3; i++) - { - uint32 timer_clocks = cpu_clocks; - - if(Timers[i].Mode & 0x100) - continue; - - ClockTimer(i, timer_clocks); - } - - lastts = timestamp; - - return(timestamp + CalcNextEvent(1024)); -} - -static void CalcCountingStart(unsigned which) -{ - Timers[which].DoZeCounting = true; - - switch(which) - { - case 1: - switch(Timers[which].Mode & 0x07) - { - case 0x1: - Timers[which].DoZeCounting = !vblank; - break; - - case 0x5: - Timers[which].DoZeCounting = vblank; - break; - - case 0x7: - Timers[which].DoZeCounting = -1; - break; - } - break; - - - } -} - -void TIMER_Write(const pscpu_timestamp_t timestamp, uint32 A, uint16 V) -{ - TIMER_Update(timestamp); - - int which = (A >> 4) & 0x3; - - V <<= (A & 3) * 8; - - PSX_DBGINFO("[TIMER] Write: %08x %04x\n", A, V); - - if(which >= 3) - return; - - // TODO: See if the "Timers[which].Counter" part of the IRQ if() statements below is what a real PSX does. - switch(A & 0xC) - { - case 0x0: Timers[which].IRQDone = false; -#if 1 - if(Timers[which].Counter && (V & 0xFFFF) == 0) - { - Timers[which].Mode |= 0x0800; - if((Timers[which].Mode & 0x10) && !Timers[which].IRQDone) - { - Timers[which].IRQDone = true; - IRQ_Assert(IRQ_TIMER_0 + which, true); - IRQ_Assert(IRQ_TIMER_0 + which, false); - } - } -#endif - Timers[which].Counter = V & 0xFFFF; - break; - - case 0x4: Timers[which].Mode = (V & 0x3FF) | (Timers[which].Mode & 0x1C00); - Timers[which].IRQDone = false; -#if 1 - if(Timers[which].Counter) - { - Timers[which].Mode |= 0x0800; - if((Timers[which].Mode & 0x10) && !Timers[which].IRQDone) - { - Timers[which].IRQDone = true; - IRQ_Assert(IRQ_TIMER_0 + which, true); - IRQ_Assert(IRQ_TIMER_0 + which, false); - } - } - Timers[which].Counter = 0; -#endif - CalcCountingStart(which); // Call after setting .Mode - break; - - case 0x8: Timers[which].Target = V & 0xFFFF; - break; - - case 0xC: // Open bus - break; - } - - // TIMER_Update(timestamp); - - PSX_SetEventNT(PSX_EVENT_TIMER, timestamp + CalcNextEvent(1024)); -} - -uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A) -{ - uint16 ret = 0; - int which = (A >> 4) & 0x3; - - if(which >= 3) - { - PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A); - - return(ret >> ((A & 3) * 8)); - } - - TIMER_Update(timestamp); - - switch(A & 0xC) - { - case 0x0: ret = Timers[which].Counter; - break; - - case 0x4: ret = Timers[which].Mode; - Timers[which].Mode &= ~0x1800; - break; - - case 0x8: ret = Timers[which].Target; - break; - - case 0xC: PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A); - break; - } - - return(ret >> ((A & 3) * 8)); -} - - -void TIMER_ResetTS(void) -{ - lastts = 0; -} - - -void TIMER_Power(void) -{ - lastts = 0; - - hretrace = false; - vblank = false; - memset(Timers, 0, sizeof(Timers)); + +/* + Notes(some of it may be incomplete or wrong in subtle ways) + + Control bits: + Lower 3 bits of mode, for timer1(when mode is | 0x100): + 0x1 = don't count while in vblank(except that the first count while in vblank does go through) + 0x3 = vblank going inactive triggers timer reset, then some interesting behavior where counting again is delayed... + 0x5 = vblank going inactive triggers timer reset, and only count within vblank. + 0x7 = Wait until vblank goes active then inactive, then start counting? + For timer2: + 0x1 = timer stopped(TODO: confirm on real system) + + Target match counter reset enable 0x008 + Target match IRQ enable 0x010 + Overflow IRQ enable 0x020 + IRQ evaluation auto-reset 0x040 + --unknown-- 0x080 + Clock selection 0x100 + Divide by 8(timer 2 only?) 0x200 + + Counter: + Reset to 0 on writes to the mode/status register. + + Status flags: + Current IRQ line status? 0x0400 + Compare flag 0x0800 + Cleared on mode/status read. + Set repeatedly while counter == target. + + Overflow/Carry flag 0x1000 + Cleared on mode/status read. + Set when counter overflows from 0xFFFF->0. + + Hidden flags: + IRQ done + Cleared on writes to the mode/status register, on writes to the count register, and apparently automatically when the counter + increments if (Mode & 0x40) [Note: If target mode is enabled, and target is 0, IRQ done flag won't be automatically reset] + + There seems to be a brief period(edge condition?) where, if target match reset mode is enabled, you can (sometimes?) read the target value in the count + register before it's reset to 0. Currently not emulated; I doubt any games rely on this, but who knows. Maybe a PSX equivalent + of the PC Engine "Battle Royale"? ;) + + A timer is somewhat unreliable when target match reset mode is enabled and the 33MHz clock is used. Average 2.4 counts seem to be + skipped for that timer every target match reset, but oddly subtracting only 2 from the desired target match value seems to effectively + negate the loss...wonder if my test program is faulty in some way. Currently not emulated. + + Counters using GPU clock sources(hretrace,dot clock) reportedly will with a low probability return wrong count values on an actual PS1, + so keep this in mind when writing test programs(IE keep reading the count value until two consecutive reads return the same value). + Currently not emulated. +*/ + +/* + FIXME: Clock appropriately(and update events) when using SetRegister() via the debugger. + + TODO: If we ever return randomish values to "simulate" open bus, remember to change the return type and such of the TIMER_Read() function to full 32-bit too. +*/ + +namespace MDFN_IEN_PSX +{ + +struct Timer +{ + uint32 Mode; + uint32 Counter; // Only 16-bit, but 32-bit here for detecting counting past target. + uint32 Target; + + uint32 Div8Counter; + + bool IRQDone; + int32 DoZeCounting; +}; + +static bool vblank; +static bool hretrace; +static Timer Timers[3]; +static pscpu_timestamp_t lastts; + +static uint32 CalcNextEvent(void) +{ + uint32 next_event = 1024; // + + for(unsigned i = 0; i < 3; i++) + { + if(!(Timers[i].Mode & 0x30)) // If IRQ is disabled, abort for this timer(don't look at IRQDone for this test, or things will break since its resetting is deferred!). + continue; + + if((Timers[i].Mode & 0x8) && (Timers[i].Counter == 0) && (Timers[i].Target == 0) && !Timers[i].IRQDone) + { + next_event = 1; + continue; + } + + // + // + if((i == 0 || i == 1) && (Timers[i].Mode & 0x100)) // If clocked by GPU, abort for this timer(will result in poor granularity for pixel-clock-derived timer IRQs, but whatever). + continue; + + if(Timers[i].DoZeCounting <= 0) + continue; + + if((i == 0x2) && (Timers[i].Mode & 0x1)) + continue; + + // + // + // + const uint32 target = ((Timers[i].Mode & 0x18) && (Timers[i].Counter < Timers[i].Target)) ? Timers[i].Target : 0x10000; + const uint32 count_delta = target - Timers[i].Counter; + uint32 tmp_clocks; + + if((i == 0x2) && (Timers[i].Mode & 0x200)) + tmp_clocks = (count_delta * 8) - Timers[i].Div8Counter; + else + tmp_clocks = count_delta; + + if(next_event > tmp_clocks) + next_event = tmp_clocks; + } + + return(next_event); +} + +static bool TimerMatch(unsigned i) +{ + bool irq_exact = false; + + Timers[i].Mode |= 0x0800; + + if(Timers[i].Mode & 0x008) + Timers[i].Counter %= std::max(1, Timers[i].Target); + + if((Timers[i].Mode & 0x10) && !Timers[i].IRQDone) + { + if(Timers[i].Counter == 0 || Timers[i].Counter == Timers[i].Target) + irq_exact = true; + +#if 1 + { + const uint16 lateness = (Timers[i].Mode & 0x008) ? Timers[i].Counter : (Timers[i].Counter - Timers[i].Target); + + if(lateness > ((i == 1 && (Timers[i].Mode & 0x100)) ? 0 : 3)) + PSX_DBG(PSX_DBG_WARNING, "[TIMER] Timer %d match IRQ trigger late: %u\n", i, lateness); + } +#endif + + Timers[i].IRQDone = true; + IRQ_Assert(IRQ_TIMER_0 + i, true); + IRQ_Assert(IRQ_TIMER_0 + i, false); + } + + return irq_exact; +} + +static bool TimerOverflow(unsigned i) +{ + bool irq_exact = false; + + Timers[i].Mode |= 0x1000; + Timers[i].Counter &= 0xFFFF; + + if((Timers[i].Mode & 0x20) && !Timers[i].IRQDone) + { + if(Timers[i].Counter == 0) + irq_exact = true; + +#if 1 + if(Timers[i].Counter > ((i == 1 && (Timers[i].Mode & 0x100)) ? 0 : 3)) + PSX_DBG(PSX_DBG_WARNING, "[TIMER] Timer %d overflow IRQ trigger late: %u\n", i, Timers[i].Counter); +#endif + + Timers[i].IRQDone = true; + IRQ_Assert(IRQ_TIMER_0 + i, true); + IRQ_Assert(IRQ_TIMER_0 + i, false); + } + + return irq_exact; +} + +static void ClockTimer(int i, uint32 clocks) +{ + if(Timers[i].DoZeCounting <= 0) + clocks = 0; + + if(i == 0x2) + { + uint32 d8_clocks; + + Timers[i].Div8Counter += clocks; + d8_clocks = Timers[i].Div8Counter >> 3; + Timers[i].Div8Counter &= 0x7; + + if(Timers[i].Mode & 0x200) // Divide by 8, at least for timer 0x2 + clocks = d8_clocks; + + if(Timers[i].Mode & 1) + clocks = 0; + } + + if((Timers[i].Mode & 0x008) && Timers[i].Target == 0 && Timers[i].Counter == 0) + TimerMatch(i); + else if(clocks) + { + uint32 before = Timers[i].Counter; + + Timers[i].Counter += clocks; + + if(Timers[i].Mode & 0x40) + Timers[i].IRQDone = false; + + bool irq_exact = false; + + // + // Target match handling + // + if((before < Timers[i].Target && Timers[i].Counter >= Timers[i].Target) || (Timers[i].Counter >= Timers[i].Target + 0x10000)) + irq_exact |= TimerMatch(i); + + // + // Overflow handling + // + if(Timers[i].Counter >= 0x10000) + irq_exact |= TimerOverflow(i); + + // + if((Timers[i].Mode & 0x40) && !irq_exact) + Timers[i].IRQDone = false; + } +} + +void TIMER_SetVBlank(bool status) +{ + switch(Timers[1].Mode & 0x7) + { + case 0x1: + Timers[1].DoZeCounting = !status; + break; + + case 0x3: + if(vblank && !status) + { + Timers[1].Counter = 0; + if(Timers[1].Counter == Timers[1].Target) + TimerMatch(1); + } + break; + + case 0x5: + Timers[1].DoZeCounting = status; + if(vblank && !status) + { + Timers[1].Counter = 0; + if(Timers[1].Counter == Timers[1].Target) + TimerMatch(1); + } + break; + + case 0x7: + if(Timers[1].DoZeCounting == -1) + { + if(!vblank && status) + Timers[1].DoZeCounting = 0; + } + else if(Timers[1].DoZeCounting == 0) + { + if(vblank && !status) + Timers[1].DoZeCounting = 1; + } + break; + } + vblank = status; +} + +void TIMER_SetHRetrace(bool status) +{ + if(hretrace && !status) + { + if((Timers[0].Mode & 0x7) == 0x3) + { + Timers[0].Counter = 0; + + if(Timers[0].Counter == Timers[0].Target) + TimerMatch(0); + } + } + + hretrace = status; +} + +void TIMER_AddDotClocks(uint32 count) +{ + if(Timers[0].Mode & 0x100) + ClockTimer(0, count); +} + +void TIMER_ClockHRetrace(void) +{ + if(Timers[1].Mode & 0x100) + ClockTimer(1, 1); +} + +pscpu_timestamp_t TIMER_Update(const pscpu_timestamp_t timestamp) +{ + int32 cpu_clocks = timestamp - lastts; + + for(int i = 0; i < 3; i++) + { + uint32 timer_clocks = cpu_clocks; + + if(Timers[i].Mode & 0x100) + continue; + + ClockTimer(i, timer_clocks); + } + + lastts = timestamp; + + return(timestamp + CalcNextEvent()); +} + +static void CalcCountingStart(unsigned which) +{ + Timers[which].DoZeCounting = true; + + switch(which) + { + case 1: + switch(Timers[which].Mode & 0x07) + { + case 0x1: + Timers[which].DoZeCounting = !vblank; + break; + + case 0x5: + Timers[which].DoZeCounting = vblank; + break; + + case 0x7: + Timers[which].DoZeCounting = -1; + break; + } + break; + + + } +} + +void TIMER_Write(const pscpu_timestamp_t timestamp, uint32 A, uint16 V) +{ + TIMER_Update(timestamp); + + int which = (A >> 4) & 0x3; + + V <<= (A & 3) * 8; + + PSX_DBGINFO("[TIMER] Write: %08x %04x\n", A, V); + + if(which >= 3) + return; + + switch(A & 0xC) + { + case 0x0: Timers[which].IRQDone = false; + Timers[which].Counter = V & 0xFFFF; + break; + + case 0x4: Timers[which].Mode = (V & 0x3FF) | (Timers[which].Mode & 0x1C00); + Timers[which].IRQDone = false; + Timers[which].Counter = 0; + + CalcCountingStart(which); // Call after setting .Mode + break; + + case 0x8: Timers[which].Target = V & 0xFFFF; + break; + + case 0xC: // Open bus + break; + } + + if(Timers[which].Counter == Timers[which].Target) + TimerMatch(which); + + PSX_SetEventNT(PSX_EVENT_TIMER, timestamp + CalcNextEvent()); +} + +uint16 TIMER_Read(const pscpu_timestamp_t timestamp, uint32 A) +{ + uint16 ret = 0; + int which = (A >> 4) & 0x3; + + if(which >= 3) + { + PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A); + + return(ret >> ((A & 3) * 8)); + } + + TIMER_Update(timestamp); + + switch(A & 0xC) + { + case 0x0: ret = Timers[which].Counter; + break; + + case 0x4: ret = Timers[which].Mode; + Timers[which].Mode &= ~0x1000; + if(Timers[which].Counter != Timers[which].Target) + Timers[which].Mode &= ~0x0800; + break; + + case 0x8: ret = Timers[which].Target; + break; + + case 0xC: PSX_WARNING("[TIMER] Open Bus Read: 0x%08x", A); + break; + } + + return(ret >> ((A & 3) * 8)); +} + + +void TIMER_ResetTS(void) +{ + lastts = 0; +} + + +void TIMER_Power(void) +{ + lastts = 0; + + hretrace = false; + vblank = false; + memset(Timers, 0, sizeof(Timers)); } void TIMER_SyncState(bool isReader, EW::NewState *ns) @@ -496,6 +504,9 @@ void TIMER_SetRegister(unsigned int which, uint32 value) break; } + if (Timers[tw].Counter == Timers[tw].Target) + TimerMatch(tw); + } From d1fa71812084edce0451877370703aa9baca42b6 Mon Sep 17 00:00:00 2001 From: Suuper Date: Sat, 3 Oct 2015 09:36:07 -0500 Subject: [PATCH 06/24] Put my AutofireStickyXORAdapter back and fixed the bug it had. --- .../inputAdapters/InputAdapters.cs | 543 +++++++++--------- BizHawk.Client.EmuHawk/MainForm.cs | 2 +- .../tools/TAStudio/TAStudio.ListView.cs | 8 +- 3 files changed, 270 insertions(+), 283 deletions(-) diff --git a/BizHawk.Client.Common/inputAdapters/InputAdapters.cs b/BizHawk.Client.Common/inputAdapters/InputAdapters.cs index 70bf6ebdb3..46a31fb849 100644 --- a/BizHawk.Client.Common/inputAdapters/InputAdapters.cs +++ b/BizHawk.Client.Common/inputAdapters/InputAdapters.cs @@ -383,238 +383,139 @@ namespace BizHawk.Client.Common private List _justPressed = new List(); } - /// SuuperW: I'm leaving the old class in case I accidentally screwed something up - /// adelikat: You did, the autofire feature this was controlling, putting it back, fix your class - public class AutoFireStickyXorAdapter : IController, ISticky - { - public int On { get; set; } - public int Off { get; set; } - public WorkingDictionary buttonStarts = new WorkingDictionary(); - public WorkingDictionary lagStarts = new WorkingDictionary(); // TODO: need a data structure not misc dictionaries - - private readonly HashSet _stickySet = new HashSet(); - - public IController Source { get; set; } - - public void SetOnOffPatternFromConfig() - { - On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn; - Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff; - } - - public AutoFireStickyXorAdapter() - { - //On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn; - //Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff; - On = 1; - Off = 1; - } - - public bool IsPressed(string button) - { - return this[button]; - } - - public bool this[string button] - { - get - { - var source = Source[button]; - - if (_stickySet.Contains(button)) - { - var lagcount = 0; - if (Global.Emulator.CanPollInput() && Global.Config.AutofireLagFrames) - { - lagcount = Global.Emulator.AsInputPollable().LagCount; - } - - var a = ((Global.Emulator.Frame - lagcount) - (buttonStarts[button] - lagStarts[button])) % (On + Off); - if (a < On) - { - return source ^= true; - } - else - { - return source ^= false; - } - } - - return source; - } - - set - { - throw new InvalidOperationException(); - } - } - - public ControllerDefinition Type { get { return Source.Type; } set { throw new InvalidOperationException(); } } - public bool Locked { get; set; } // Pretty much a hack, - - // dumb passthrough for floats, because autofire doesn't care about them - public float GetFloat(string name) - { - return Source.GetFloat(name); - } - - public void SetSticky(string button, bool isSticky) - { - if (isSticky) - { - _stickySet.Add(button); - buttonStarts.Add(button, Global.Emulator.Frame); - - if (Global.Emulator.CanPollInput()) - { - lagStarts.Add(button, Global.Emulator.AsInputPollable().LagCount); - } - else - { - lagStarts.Add(button, 0); - } - } - else - { - _stickySet.Remove(button); - buttonStarts.Remove(button); - lagStarts.Remove(button); - } - } - - public bool IsSticky(string button) - { - return this._stickySet.Contains(button); - } - - public HashSet CurrentStickies - { - get - { - return this._stickySet; - } - } - - public void ClearStickies() - { - _stickySet.Clear(); - buttonStarts.Clear(); - lagStarts.Clear(); - } - - public void MassToggleStickyState(List buttons) - { - foreach (var button in buttons.Where(button => !_justPressed.Contains(button))) - { - if (_stickySet.Contains(button)) - { - _stickySet.Remove(button); - } - else - { - _stickySet.Add(button); - } - } - - _justPressed = buttons; - } - - /// - /// Determines if a sticky is current mashing the button itself, - /// If sticky is not set then false, if set, it returns true if the Source is not pressed, else false - /// - public bool StickyIsInEffect(string button) - { - if (Source.IsPressed(button)) - { - return false; - } - - return (IsPressed(button)); // Shortcut logic since we know the Source isn't pressed, Ispressed can only return true if the autofire sticky is in effect for this frame - } - - private List _justPressed = new List(); - } - - // commenting this out, it breaks the autofire hotkey + ///// SuuperW: I'm leaving the old class in case I accidentally screwed something up //public class AutoFireStickyXorAdapter : IController, ISticky //{ - // // TODO: Change the AutoHold adapter to be one of these, with an 'Off' value of 0? - // // Probably would have slightly lower performance, but it seems weird to have such a similar class that is only used once. - // private int On; - // private int Off; + // public int On { get; set; } + // public int Off { get; set; } + // public WorkingDictionary buttonStarts = new WorkingDictionary(); + // public WorkingDictionary lagStarts = new WorkingDictionary(); // TODO: need a data structure not misc dictionaries + + // private readonly HashSet _stickySet = new HashSet(); + + // public IController Source { get; set; } + // public void SetOnOffPatternFromConfig() // { // On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn; // Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff; // } - // private WorkingDictionary _boolPatterns = new WorkingDictionary(); - // private WorkingDictionary _floatPatterns = new WorkingDictionary(); - // public AutoFireStickyXorAdapter() // { - // On = 1; Off = 1; + // //On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn; + // //Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff; + // On = 1; + // Off = 1; // } - // public IController Source { get; set; } - - // public ControllerDefinition Type - // { - // get { return Source.Type; } - // } - - // public bool Locked { get; set; } // Pretty much a hack, - // public bool IsPressed(string button) // { // return this[button]; // } - // public void SetFloat(string name, float? value, AutoPatternFloat pattern = null) - // { - // if (value.HasValue) - // { - // if (pattern == null) - // pattern = new AutoPatternFloat(value.Value, On, 0, Off); - // _floatPatterns[name] = pattern; - // } - // else - // { - // _floatPatterns.Remove(name); - // } - // } - - // public float GetFloat(string name) - // { - // if (_floatPatterns.ContainsKey(name)) - // return _floatPatterns[name].PeekNextValue(); - - // if (Source == null) - // return 0; - - // return Source.GetFloat(name); - // } - - // public void ClearStickyFloats() - // { - // _floatPatterns.Clear(); - // } - // public bool this[string button] // { // get // { // var source = Source[button]; - // bool patternValue = false; - // if (_boolPatterns.ContainsKey(button)) - // { // I can't figure a way to determine right here if it should Peek or Get. - // patternValue = _boolPatterns[button].PeekNextValue(); + + // if (_stickySet.Contains(button)) + // { + // var lagcount = 0; + // if (Global.Emulator.CanPollInput() && Global.Config.AutofireLagFrames) + // { + // lagcount = Global.Emulator.AsInputPollable().LagCount; + // } + + // var a = ((Global.Emulator.Frame - lagcount) - (buttonStarts[button] - lagStarts[button])) % (On + Off); + // if (a < On) + // { + // return source ^= true; + // } + // else + // { + // return source ^= false; + // } // } - // source ^= patternValue; // return source; // } + + // set + // { + // throw new InvalidOperationException(); + // } + // } + + // public ControllerDefinition Type { get { return Source.Type; } set { throw new InvalidOperationException(); } } + // public bool Locked { get; set; } // Pretty much a hack, + + // // dumb passthrough for floats, because autofire doesn't care about them + // public float GetFloat(string name) + // { + // return Source.GetFloat(name); + // } + + // public void SetSticky(string button, bool isSticky) + // { + // if (isSticky) + // { + // _stickySet.Add(button); + // buttonStarts.Add(button, Global.Emulator.Frame); + + // if (Global.Emulator.CanPollInput()) + // { + // lagStarts.Add(button, Global.Emulator.AsInputPollable().LagCount); + // } + // else + // { + // lagStarts.Add(button, 0); + // } + // } + // else + // { + // _stickySet.Remove(button); + // buttonStarts.Remove(button); + // lagStarts.Remove(button); + // } + // } + + // public bool IsSticky(string button) + // { + // return this._stickySet.Contains(button); + // } + + // public HashSet CurrentStickies + // { + // get + // { + // return this._stickySet; + // } + // } + + // public void ClearStickies() + // { + // _stickySet.Clear(); + // buttonStarts.Clear(); + // lagStarts.Clear(); + // } + + // public void MassToggleStickyState(List buttons) + // { + // foreach (var button in buttons.Where(button => !_justPressed.Contains(button))) + // { + // if (_stickySet.Contains(button)) + // { + // _stickySet.Remove(button); + // } + // else + // { + // _stickySet.Add(button); + // } + // } + + // _justPressed = buttons; // } // /// @@ -623,84 +524,174 @@ namespace BizHawk.Client.Common // /// // public bool StickyIsInEffect(string button) // { - // if (IsSticky(button)) + // if (Source.IsPressed(button)) // { - // return !Source.IsPressed(button); + // return false; // } - // return false; + // return (IsPressed(button)); // Shortcut logic since we know the Source isn't pressed, Ispressed can only return true if the autofire sticky is in effect for this frame // } - // public void SetSticky(string button, bool isSticky, AutoPatternBool pattern = null) - // { - // if (isSticky) - // { - // if (pattern == null) - // pattern = new AutoPatternBool(On, Off); - // _boolPatterns[button] = pattern; - // } - // else - // { - // _boolPatterns.Remove(button); - // } - // } - - // public void Unset(string button) - // { - // _boolPatterns.Remove(button); - // _floatPatterns.Remove(button); - // } - - // public bool IsSticky(string button) - // { - // return _boolPatterns.ContainsKey(button) || _floatPatterns.ContainsKey(button); - // } - - // public HashSet CurrentStickies - // { - // get - // { - // return new HashSet(_boolPatterns.Keys); - // } - // } - - // public void ClearStickies() - // { - // _boolPatterns.Clear(); - // _floatPatterns.Clear(); - // } - - // public void IncrementLoops(bool lagged) - // { - // for (int i = 0; i < _boolPatterns.Count; i++) - // _boolPatterns.ElementAt(i).Value.GetNextValue(lagged); - // for (int i = 0; i < _floatPatterns.Count; i++) - // _floatPatterns.ElementAt(i).Value.GetNextValue(lagged); - // } - - // // SuuperW: What does this even do? I set a breakpoint inside the loop and it wasn't reached. - // private WorkingDictionary _toggledButtons = new WorkingDictionary(); // private List _justPressed = new List(); - // public void MassToggleStickyState(List buttons) - // { - // foreach (var button in buttons.Where(button => !_justPressed.Contains(button))) - // { - // if (_boolPatterns.ContainsKey(button)) - // { - // _toggledButtons[button] = _boolPatterns[button]; - // SetSticky(button, false); - // } - // else - // { - // _boolPatterns[button] = _toggledButtons[button]; - // _toggledButtons.Remove(button); - // } - // } - - // _justPressed = buttons; - // } //} + // commenting this out, it breaks the autofire hotkey + public class AutoFireStickyXorAdapter : IController, ISticky + { + // TODO: Change the AutoHold adapter to be one of these, with an 'Off' value of 0? + // Probably would have slightly lower performance, but it seems weird to have such a similar class that is only used once. + private int On; + private int Off; + public void SetOnOffPatternFromConfig() + { + On = Global.Config.AutofireOn < 1 ? 0 : Global.Config.AutofireOn; + Off = Global.Config.AutofireOff < 1 ? 0 : Global.Config.AutofireOff; + } + + private WorkingDictionary _boolPatterns = new WorkingDictionary(); + private WorkingDictionary _floatPatterns = new WorkingDictionary(); + + public AutoFireStickyXorAdapter() + { + On = 1; Off = 1; + } + + public IController Source { get; set; } + + public ControllerDefinition Type + { + get { return Source.Type; } + } + + public bool Locked { get; set; } // Pretty much a hack, + + public bool IsPressed(string button) + { + return this[button]; + } + + public void SetFloat(string name, float? value, AutoPatternFloat pattern = null) + { + if (value.HasValue) + { + if (pattern == null) + pattern = new AutoPatternFloat(value.Value, On, 0, Off); + _floatPatterns[name] = pattern; + } + else + { + _floatPatterns.Remove(name); + } + } + + public float GetFloat(string name) + { + if (_floatPatterns.ContainsKey(name)) + return _floatPatterns[name].PeekNextValue(); + + if (Source == null) + return 0; + + return Source.GetFloat(name); + } + + public void ClearStickyFloats() + { + _floatPatterns.Clear(); + } + + public bool this[string button] + { + get + { + var source = Source[button]; + bool patternValue = false; + if (_boolPatterns.ContainsKey(button)) + { // I can't figure a way to determine right here if it should Peek or Get. + patternValue = _boolPatterns[button].PeekNextValue(); + } + source ^= patternValue; + + return source; + } + } + + /// + /// Determines if a sticky is current mashing the button itself, + /// If sticky is not set then false, if set, it returns true if the Source is not pressed, else false + /// + public bool StickyIsInEffect(string button) + { + if (IsSticky(button)) + { + return !Source.IsPressed(button); + } + + return false; + } + + public void SetSticky(string button, bool isSticky, AutoPatternBool pattern = null) + { + if (isSticky) + { + if (pattern == null) + pattern = new AutoPatternBool(On, Off); + _boolPatterns[button] = pattern; + } + else + { + _boolPatterns.Remove(button); + } + } + + public void Unset(string button) + { + _boolPatterns.Remove(button); + _floatPatterns.Remove(button); + } + + public bool IsSticky(string button) + { + return _boolPatterns.ContainsKey(button) || _floatPatterns.ContainsKey(button); + } + + public HashSet CurrentStickies + { + get + { + return new HashSet(_boolPatterns.Keys); + } + } + + public void ClearStickies() + { + _boolPatterns.Clear(); + _floatPatterns.Clear(); + } + + public void IncrementLoops(bool lagged) + { + for (int i = 0; i < _boolPatterns.Count; i++) + _boolPatterns.ElementAt(i).Value.GetNextValue(lagged); + for (int i = 0; i < _floatPatterns.Count; i++) + _floatPatterns.ElementAt(i).Value.GetNextValue(lagged); + } + + private List _justPressed = new List(); + public void MassToggleStickyState(List buttons) + { + foreach (var button in buttons.Where(button => !_justPressed.Contains(button))) + { + if (_boolPatterns.ContainsKey(button)) + SetSticky(button, false); + else + SetSticky(button, true); + } + + _justPressed = buttons; + } + } + /// /// Just copies source to sink, or returns whatever a NullController would if it is disconnected. useful for immovable hardpoints. /// diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index a289f4d627..a25d511693 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -2956,7 +2956,7 @@ namespace BizHawk.Client.EmuHawk { Global.AutoFireController.IncrementStarts(); } - //Global.AutofireStickyXORAdapter.IncrementLoops(IsLagFrame); + Global.AutofireStickyXORAdapter.IncrementLoops(IsLagFrame); PressFrameAdvance = false; diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index f1a1eb98a4..81118050ef 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -315,9 +315,7 @@ namespace BizHawk.Client.EmuHawk else index += controllerType.BoolButtons.Count - 1; AutoPatternBool p = BoolPatterns[index]; - // adelikat: I broke it - //Global.AutofireStickyXORAdapter.SetSticky(button, isOn.Value, p); - Global.StickyXORAdapter.SetSticky(button, true); + Global.AutofireStickyXORAdapter.SetSticky(button, isOn.Value, p); } else { @@ -328,9 +326,7 @@ namespace BizHawk.Client.EmuHawk float? value = null; if (isOn.Value) value = 0f; AutoPatternFloat p = FloatPatterns[index]; - // adelikat: I broke it - //Global.AutofireStickyXORAdapter.SetFloat(button, value, p); - Global.StickyXORAdapter.SetFloat(button, value); + Global.AutofireStickyXORAdapter.SetFloat(button, value, p); } } From c9838d668a8fd55eeb306f73de7f785d83ad9fa6 Mon Sep 17 00:00:00 2001 From: zeromus Date: Sat, 3 Oct 2015 17:27:52 -0500 Subject: [PATCH 07/24] psx - hook up new controller configuration, to support 0-2 pads and 0-2 memcards. Not tested very well yet. no multitap this release. --- .../BizHawk.Client.EmuHawk.csproj | 9 - BizHawk.Client.EmuHawk/MainForm.Designer.cs | 2 +- BizHawk.Client.EmuHawk/MainForm.Events.cs | 2 +- .../config/NES/NesControllerSettings.cs | 2 - .../PSX/PSXControllerConfig.Designer.cs | 96 -- .../config/PSX/PSXControllerConfig.cs | 98 --- .../config/PSX/PSXControllerConfig.resx | 624 ------------- .../PSX/PSXControllerConfigNew.Designer.cs | 833 +++++++++--------- .../config/PSX/PSXControllerConfigNew.cs | 84 +- .../config/PSX/PSXControllerConfigNew.resx | 238 ++--- .../tools/VirtualPads/schema/PSXSchema.cs | 19 +- .../BizHawk.Emulation.Cores.csproj | 2 +- .../Consoles/Sony/PSX/Octoshock.cs | 284 +++--- ...ControlConfig.cs => OctoshockFIOConfig.cs} | 53 +- output/dll/octoshock.dll | Bin 927232 -> 927232 bytes psx/octoshock/bizhawk/octoshock.sln | 2 +- psx/octoshock/psx/frontio.cpp | 4 - psx/octoshock/psx/frontio.h | 2 + psx/octoshock/psx/input/memcard.cpp | 2 + psx/octoshock/psx/psx.cpp | 13 +- 20 files changed, 813 insertions(+), 1556 deletions(-) delete mode 100644 BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.Designer.cs delete mode 100644 BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.cs delete mode 100644 BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.resx rename BizHawk.Emulation.Cores/Consoles/Sony/PSX/{OctoshockControlConfig.cs => OctoshockFIOConfig.cs} (54%) diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 5e3798dc2c..ad085d840b 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -412,12 +412,6 @@ ProfileConfig.cs - - Form - - - PSXControllerConfig.cs - Form @@ -1271,9 +1265,6 @@ ProfileConfig.cs - - PSXControllerConfig.cs - PSXControllerConfigNew.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index c1002ff667..4cbf0f2b2e 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -2553,7 +2553,7 @@ this.PSXControllerSettingsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.GameController; this.PSXControllerSettingsMenuItem.Name = "PSXControllerSettingsMenuItem"; this.PSXControllerSettingsMenuItem.Size = new System.Drawing.Size(172, 22); - this.PSXControllerSettingsMenuItem.Text = "Controller Settings"; + this.PSXControllerSettingsMenuItem.Text = "FrontIO Settings"; this.PSXControllerSettingsMenuItem.Click += new System.EventHandler(this.PSXControllerSettingsMenuItem_Click); // // PSXOptionsMenuItem diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 2d68d2746c..39adb03adf 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -1746,7 +1746,7 @@ namespace BizHawk.Client.EmuHawk private void PSXControllerSettingsMenuItem_Click(object sender, EventArgs e) { - new PSXControllerConfig().ShowDialog(); + new PSXControllerConfigNew().ShowDialog(); } #endregion diff --git a/BizHawk.Client.EmuHawk/config/NES/NesControllerSettings.cs b/BizHawk.Client.EmuHawk/config/NES/NesControllerSettings.cs index c6d641bbeb..94e7d82c6b 100644 --- a/BizHawk.Client.EmuHawk/config/NES/NesControllerSettings.cs +++ b/BizHawk.Client.EmuHawk/config/NES/NesControllerSettings.cs @@ -58,8 +58,6 @@ namespace BizHawk.Client.EmuHawk SyncSettings.Controls = ctrls; - SyncSettings.Controls = ctrls; - if (changed) { GlobalWin.MainForm.PutCoreSyncSettings(SyncSettings); diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.Designer.cs deleted file mode 100644 index f55c730785..0000000000 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.Designer.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace BizHawk.Client.EmuHawk -{ - partial class PSXControllerConfig - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PSXControllerConfig)); - this.CancelBtn = new System.Windows.Forms.Button(); - this.OkBtn = new System.Windows.Forms.Button(); - this.btnTest = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // CancelBtn - // - this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(294, 227); - this.CancelBtn.Name = "CancelBtn"; - this.CancelBtn.Size = new System.Drawing.Size(60, 23); - this.CancelBtn.TabIndex = 5; - this.CancelBtn.Text = "&Cancel"; - this.CancelBtn.UseVisualStyleBackColor = true; - this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); - // - // OkBtn - // - this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(228, 227); - this.OkBtn.Name = "OkBtn"; - this.OkBtn.Size = new System.Drawing.Size(60, 23); - this.OkBtn.TabIndex = 4; - this.OkBtn.Text = "&Ok"; - this.OkBtn.UseVisualStyleBackColor = true; - this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); - // - // btnTest - // - this.btnTest.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.btnTest.Location = new System.Drawing.Point(12, 227); - this.btnTest.Name = "btnTest"; - this.btnTest.Size = new System.Drawing.Size(60, 23); - this.btnTest.TabIndex = 6; - this.btnTest.Text = "Test"; - this.btnTest.UseVisualStyleBackColor = true; - this.btnTest.Click += new System.EventHandler(this.btnTest_Click); - // - // PSXControllerConfig - // - this.AcceptButton = this.OkBtn; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(366, 262); - this.Controls.Add(this.btnTest); - this.Controls.Add(this.CancelBtn); - this.Controls.Add(this.OkBtn); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.Name = "PSXControllerConfig"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Controller Settings"; - this.Load += new System.EventHandler(this.PSXControllerConfig_Load); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.Button CancelBtn; - private System.Windows.Forms.Button OkBtn; - private System.Windows.Forms.Button btnTest; - } -} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.cs b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.cs deleted file mode 100644 index edafbf1e66..0000000000 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Windows.Forms; - -using BizHawk.Common; -using BizHawk.Emulation.Cores.Sony.PSX; -using BizHawk.Client.Common; -using BizHawk.Client.EmuHawk.WinFormExtensions; -using BizHawk.Common.ReflectionExtensions; - -namespace BizHawk.Client.EmuHawk -{ - public partial class PSXControllerConfig : Form - { - public PSXControllerConfig() - { - InitializeComponent(); - } - - private void PSXControllerConfig_Load(object sender, EventArgs e) - { - var psxSettings = ((Octoshock)Global.Emulator).GetSyncSettings(); - for (int i = 0; i < psxSettings.Controllers.Length; i++) - { - Controls.Add(new Label - { - Text = "Controller " + (i + 1), - Location = new Point(15, 19 + (i * 25)), - Width = 85 - }); - Controls.Add(new CheckBox - { - Text = "Connected", - Name = "Controller" + i, - Location = new Point(105, 15 + (i * 25)), - Checked = psxSettings.Controllers[i].IsConnected, - Width = 90 - }); - - var dropdown = new ComboBox - { - Name = "Controller" + i, - DropDownStyle = ComboBoxStyle.DropDownList, - Location = new Point(200, 15 + (i * 25)) - }; - - dropdown.PopulateFromEnum(psxSettings.Controllers[i].Type); - - Controls.Add(dropdown); - } - } - - private void OkBtn_Click(object sender, EventArgs e) - { - var psxSettings = ((Octoshock)Global.Emulator).GetSyncSettings(); - - Controls - .OfType() - .OrderBy(c => c.Name) - .ToList() - .ForEach(c => - { - var index = int.Parse(c.Name.Replace("Controller", "")); - psxSettings.Controllers[index].IsConnected = c.Checked; - }); - - Controls - .OfType() - .OrderBy(c => c.Name) - .ToList() - .ForEach(c => - { - var index = int.Parse(c.Name.Replace("Controller", "")); - psxSettings.Controllers[index].Type = c.SelectedItem.ToString().GetEnumFromDescription(); - }); - - GlobalWin.MainForm.PutCoreSyncSettings(psxSettings); - DialogResult = DialogResult.OK; - Close(); - } - - private void CancelBtn_Click(object sender, EventArgs e) - { - DialogResult = DialogResult.Cancel; - Close(); - } - - private void btnTest_Click(object sender, EventArgs e) - { - new PSXControllerConfigNew().ShowDialog(); - } - } -} diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.resx b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.resx deleted file mode 100644 index 91f294b89b..0000000000 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfig.resx +++ /dev/null @@ -1,624 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA - BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ - AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm - AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA - AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP// - /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA - AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw - AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA - AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ - AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA - AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE - AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3 - dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH - x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI - cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI - h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA - AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH - eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA - AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// - AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A - H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// - AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA - AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ - AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// - /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC - AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ - AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA - AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA - AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA - AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI - h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA - yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA - AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// - ////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB - /AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA////////////////////////// - //8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA - AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA - d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI - yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH - d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/ - /wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP// - /wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA - AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI - iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA - AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP// - AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8 - PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF - RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU - VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP - UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ - WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s - awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr - agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4 - dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf - TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5 - +gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC - ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh - oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP - kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj - jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk - owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1 - swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr - 9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - 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 - AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme - VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// - AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A - H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// - AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA - AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ - AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// - /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE - AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc - XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55 - eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg - YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51 - dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz - dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz - dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM - 5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO - jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A - gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud - iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc - mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer - qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv - rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2 - tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV - 3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa - mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc - tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA - AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882 - AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP - z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb - orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR - l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH - ///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA - AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA - AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2 - dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15 - eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+ - fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek - VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P - jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK - iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ - mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi - oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8 - ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf - 8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA - AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy - NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA - PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM - mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ - hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv - YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP// - /wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA - BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA - AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw - cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K - igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS - kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay - sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS - 0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA - AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb - Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5 - AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA - AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS - U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP - T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY - V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw - cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - 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 - AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4 - uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA - AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6 - ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8 - vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA - ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT - kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck - pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6 - OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk - ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br - auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0 - c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg - n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA - AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA - vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg - nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA - AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO - zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv - rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH - RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF - RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+ - vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB - vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX - V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i - pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/ - vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv - L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z - sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9 - uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e - nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6 - t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV - lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6 - u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC - gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej - hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y - sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T - k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn - JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC - QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK - StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/ - QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+ - PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L - S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ - SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ - Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2 - NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km - Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// - /////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af//// - AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA - B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA - AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA - AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB// - AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP// - /////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA - AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q - av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw - cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1 - dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4 - ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+ - Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA - AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA - AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc - HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A - f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z - sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui - of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP - z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB - v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8 - vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ - x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O - Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK - yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz - dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc - 9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI - zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw - t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il - o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX - V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc - XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6 - OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD - ///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4 - D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA - ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv - 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 - - - \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs index 36740d6774..2460ef350f 100644 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs @@ -28,413 +28,429 @@ /// private void InitializeComponent() { - this.cbMultitap_1 = new System.Windows.Forms.CheckBox(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.lbl_p_1_4 = new System.Windows.Forms.Label(); - this.lbl_p_1_3 = new System.Windows.Forms.Label(); - this.lbl_p_1_2 = new System.Windows.Forms.Label(); - this.lbl_p_1_1 = new System.Windows.Forms.Label(); - this.lbl_1_4 = new System.Windows.Forms.Label(); - this.lbl_1_3 = new System.Windows.Forms.Label(); - this.lbl_1_2 = new System.Windows.Forms.Label(); - this.lbl_1_1 = new System.Windows.Forms.Label(); - this.combo_1_4 = new System.Windows.Forms.ComboBox(); - this.combo_1_3 = new System.Windows.Forms.ComboBox(); - this.combo_1_2 = new System.Windows.Forms.ComboBox(); - this.combo_1_1 = new System.Windows.Forms.ComboBox(); - this.cbMemcard_1 = new System.Windows.Forms.CheckBox(); - this.btnOK = new System.Windows.Forms.Button(); - this.btnCancel = new System.Windows.Forms.Button(); - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.lbl_p_2_4 = new System.Windows.Forms.Label(); - this.lbl_p_2_3 = new System.Windows.Forms.Label(); - this.lbl_p_2_2 = new System.Windows.Forms.Label(); - this.lbl_p_2_1 = new System.Windows.Forms.Label(); - this.lbl_2_4 = new System.Windows.Forms.Label(); - this.lbl_2_3 = new System.Windows.Forms.Label(); - this.lbl_2_2 = new System.Windows.Forms.Label(); - this.lbl_2_1 = new System.Windows.Forms.Label(); - this.combo_2_4 = new System.Windows.Forms.ComboBox(); - this.combo_2_3 = new System.Windows.Forms.ComboBox(); - this.combo_2_2 = new System.Windows.Forms.ComboBox(); - this.combo_2_1 = new System.Windows.Forms.ComboBox(); - this.cbMemcard_2 = new System.Windows.Forms.CheckBox(); - this.cbMultitap_2 = new System.Windows.Forms.CheckBox(); - this.groupBox1.SuspendLayout(); - this.groupBox2.SuspendLayout(); - this.SuspendLayout(); - // - // cbMultitap_1 - // - this.cbMultitap_1.AutoSize = true; - this.cbMultitap_1.Location = new System.Drawing.Point(18, 43); - this.cbMultitap_1.Name = "cbMultitap_1"; - this.cbMultitap_1.Size = new System.Drawing.Size(63, 17); - this.cbMultitap_1.TabIndex = 0; - this.cbMultitap_1.Text = "Multitap"; - this.cbMultitap_1.UseVisualStyleBackColor = true; - this.cbMultitap_1.CheckedChanged += new System.EventHandler(this.cb_changed); - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.lbl_p_1_4); - this.groupBox1.Controls.Add(this.lbl_p_1_3); - this.groupBox1.Controls.Add(this.lbl_p_1_2); - this.groupBox1.Controls.Add(this.lbl_p_1_1); - this.groupBox1.Controls.Add(this.lbl_1_4); - this.groupBox1.Controls.Add(this.lbl_1_3); - this.groupBox1.Controls.Add(this.lbl_1_2); - this.groupBox1.Controls.Add(this.lbl_1_1); - this.groupBox1.Controls.Add(this.combo_1_4); - this.groupBox1.Controls.Add(this.combo_1_3); - this.groupBox1.Controls.Add(this.combo_1_2); - this.groupBox1.Controls.Add(this.combo_1_1); - this.groupBox1.Controls.Add(this.cbMemcard_1); - this.groupBox1.Controls.Add(this.cbMultitap_1); - this.groupBox1.Location = new System.Drawing.Point(12, 12); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(273, 136); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Port 1"; - // - // lbl_p_1_4 - // - this.lbl_p_1_4.AutoSize = true; - this.lbl_p_1_4.Location = new System.Drawing.Point(241, 105); - this.lbl_p_1_4.Name = "lbl_p_1_4"; - this.lbl_p_1_4.Size = new System.Drawing.Size(20, 13); - this.lbl_p_1_4.TabIndex = 12; - this.lbl_p_1_4.Text = "P1"; - this.lbl_p_1_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_p_1_3 - // - this.lbl_p_1_3.AutoSize = true; - this.lbl_p_1_3.Location = new System.Drawing.Point(241, 78); - this.lbl_p_1_3.Name = "lbl_p_1_3"; - this.lbl_p_1_3.Size = new System.Drawing.Size(20, 13); - this.lbl_p_1_3.TabIndex = 11; - this.lbl_p_1_3.Text = "P1"; - this.lbl_p_1_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_p_1_2 - // - this.lbl_p_1_2.AutoSize = true; - this.lbl_p_1_2.Location = new System.Drawing.Point(241, 50); - this.lbl_p_1_2.Name = "lbl_p_1_2"; - this.lbl_p_1_2.Size = new System.Drawing.Size(20, 13); - this.lbl_p_1_2.TabIndex = 10; - this.lbl_p_1_2.Text = "P1"; - this.lbl_p_1_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_p_1_1 - // - this.lbl_p_1_1.AutoSize = true; - this.lbl_p_1_1.Location = new System.Drawing.Point(241, 24); - this.lbl_p_1_1.Name = "lbl_p_1_1"; - this.lbl_p_1_1.Size = new System.Drawing.Size(20, 13); - this.lbl_p_1_1.TabIndex = 9; - this.lbl_p_1_1.Text = "P1"; - this.lbl_p_1_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_1_4 - // - this.lbl_1_4.AutoSize = true; - this.lbl_1_4.Location = new System.Drawing.Point(94, 105); - this.lbl_1_4.Name = "lbl_1_4"; - this.lbl_1_4.Size = new System.Drawing.Size(15, 13); - this.lbl_1_4.TabIndex = 8; - this.lbl_1_4.Text = "D"; - this.lbl_1_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_1_3 - // - this.lbl_1_3.AutoSize = true; - this.lbl_1_3.Location = new System.Drawing.Point(94, 78); - this.lbl_1_3.Name = "lbl_1_3"; - this.lbl_1_3.Size = new System.Drawing.Size(14, 13); - this.lbl_1_3.TabIndex = 7; - this.lbl_1_3.Text = "C"; - this.lbl_1_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_1_2 - // - this.lbl_1_2.AutoSize = true; - this.lbl_1_2.Location = new System.Drawing.Point(94, 51); - this.lbl_1_2.Name = "lbl_1_2"; - this.lbl_1_2.Size = new System.Drawing.Size(14, 13); - this.lbl_1_2.TabIndex = 6; - this.lbl_1_2.Text = "B"; - this.lbl_1_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_1_1 - // - this.lbl_1_1.AutoSize = true; - this.lbl_1_1.Location = new System.Drawing.Point(94, 24); - this.lbl_1_1.Name = "lbl_1_1"; - this.lbl_1_1.Size = new System.Drawing.Size(14, 13); - this.lbl_1_1.TabIndex = 2; - this.lbl_1_1.Text = "A"; - this.lbl_1_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // combo_1_4 - // - this.combo_1_4.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_1_4.FormattingEnabled = true; - this.combo_1_4.Location = new System.Drawing.Point(114, 102); - this.combo_1_4.Name = "combo_1_4"; - this.combo_1_4.Size = new System.Drawing.Size(121, 21); - this.combo_1_4.TabIndex = 5; - this.combo_1_4.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // combo_1_3 - // - this.combo_1_3.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_1_3.FormattingEnabled = true; - this.combo_1_3.Location = new System.Drawing.Point(114, 75); - this.combo_1_3.Name = "combo_1_3"; - this.combo_1_3.Size = new System.Drawing.Size(121, 21); - this.combo_1_3.TabIndex = 4; - this.combo_1_3.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // combo_1_2 - // - this.combo_1_2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_1_2.FormattingEnabled = true; - this.combo_1_2.Location = new System.Drawing.Point(114, 48); - this.combo_1_2.Name = "combo_1_2"; - this.combo_1_2.Size = new System.Drawing.Size(121, 21); - this.combo_1_2.TabIndex = 3; - this.combo_1_2.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // combo_1_1 - // - this.combo_1_1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_1_1.FormattingEnabled = true; - this.combo_1_1.Location = new System.Drawing.Point(114, 21); - this.combo_1_1.Name = "combo_1_1"; - this.combo_1_1.Size = new System.Drawing.Size(121, 21); - this.combo_1_1.TabIndex = 2; - this.combo_1_1.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // cbMemcard_1 - // - this.cbMemcard_1.AutoSize = true; - this.cbMemcard_1.Location = new System.Drawing.Point(18, 21); - this.cbMemcard_1.Name = "cbMemcard_1"; - this.cbMemcard_1.Size = new System.Drawing.Size(70, 17); - this.cbMemcard_1.TabIndex = 1; - this.cbMemcard_1.Text = "Memcard"; - this.cbMemcard_1.UseVisualStyleBackColor = true; - this.cbMemcard_1.CheckedChanged += new System.EventHandler(this.cb_changed); - // - // btnOK - // - this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; - this.btnOK.Location = new System.Drawing.Point(408, 163); - this.btnOK.Name = "btnOK"; - this.btnOK.Size = new System.Drawing.Size(75, 23); - this.btnOK.TabIndex = 2; - this.btnOK.Text = "OK"; - this.btnOK.UseVisualStyleBackColor = true; - // - // btnCancel - // - this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnCancel.Location = new System.Drawing.Point(489, 163); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(75, 23); - this.btnCancel.TabIndex = 3; - this.btnCancel.Text = "Cancel"; - this.btnCancel.UseVisualStyleBackColor = true; - // - // groupBox2 - // - this.groupBox2.Controls.Add(this.lbl_p_2_4); - this.groupBox2.Controls.Add(this.lbl_p_2_3); - this.groupBox2.Controls.Add(this.lbl_p_2_2); - this.groupBox2.Controls.Add(this.lbl_p_2_1); - this.groupBox2.Controls.Add(this.lbl_2_4); - this.groupBox2.Controls.Add(this.lbl_2_3); - this.groupBox2.Controls.Add(this.lbl_2_2); - this.groupBox2.Controls.Add(this.lbl_2_1); - this.groupBox2.Controls.Add(this.combo_2_4); - this.groupBox2.Controls.Add(this.combo_2_3); - this.groupBox2.Controls.Add(this.combo_2_2); - this.groupBox2.Controls.Add(this.combo_2_1); - this.groupBox2.Controls.Add(this.cbMemcard_2); - this.groupBox2.Controls.Add(this.cbMultitap_2); - this.groupBox2.Location = new System.Drawing.Point(291, 12); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(273, 136); - this.groupBox2.TabIndex = 13; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Port 2"; - // - // lbl_p_2_4 - // - this.lbl_p_2_4.AutoSize = true; - this.lbl_p_2_4.Location = new System.Drawing.Point(241, 105); - this.lbl_p_2_4.Name = "lbl_p_2_4"; - this.lbl_p_2_4.Size = new System.Drawing.Size(20, 13); - this.lbl_p_2_4.TabIndex = 12; - this.lbl_p_2_4.Text = "P1"; - this.lbl_p_2_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_p_2_3 - // - this.lbl_p_2_3.AutoSize = true; - this.lbl_p_2_3.Location = new System.Drawing.Point(241, 78); - this.lbl_p_2_3.Name = "lbl_p_2_3"; - this.lbl_p_2_3.Size = new System.Drawing.Size(20, 13); - this.lbl_p_2_3.TabIndex = 11; - this.lbl_p_2_3.Text = "P1"; - this.lbl_p_2_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_p_2_2 - // - this.lbl_p_2_2.AutoSize = true; - this.lbl_p_2_2.Location = new System.Drawing.Point(241, 50); - this.lbl_p_2_2.Name = "lbl_p_2_2"; - this.lbl_p_2_2.Size = new System.Drawing.Size(20, 13); - this.lbl_p_2_2.TabIndex = 10; - this.lbl_p_2_2.Text = "P1"; - this.lbl_p_2_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_p_2_1 - // - this.lbl_p_2_1.AutoSize = true; - this.lbl_p_2_1.Location = new System.Drawing.Point(241, 24); - this.lbl_p_2_1.Name = "lbl_p_2_1"; - this.lbl_p_2_1.Size = new System.Drawing.Size(20, 13); - this.lbl_p_2_1.TabIndex = 9; - this.lbl_p_2_1.Text = "P1"; - this.lbl_p_2_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_2_4 - // - this.lbl_2_4.AutoSize = true; - this.lbl_2_4.Location = new System.Drawing.Point(94, 105); - this.lbl_2_4.Name = "lbl_2_4"; - this.lbl_2_4.Size = new System.Drawing.Size(15, 13); - this.lbl_2_4.TabIndex = 8; - this.lbl_2_4.Text = "D"; - this.lbl_2_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_2_3 - // - this.lbl_2_3.AutoSize = true; - this.lbl_2_3.Location = new System.Drawing.Point(94, 78); - this.lbl_2_3.Name = "lbl_2_3"; - this.lbl_2_3.Size = new System.Drawing.Size(14, 13); - this.lbl_2_3.TabIndex = 7; - this.lbl_2_3.Text = "C"; - this.lbl_2_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_2_2 - // - this.lbl_2_2.AutoSize = true; - this.lbl_2_2.Location = new System.Drawing.Point(94, 51); - this.lbl_2_2.Name = "lbl_2_2"; - this.lbl_2_2.Size = new System.Drawing.Size(14, 13); - this.lbl_2_2.TabIndex = 6; - this.lbl_2_2.Text = "B"; - this.lbl_2_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbl_2_1 - // - this.lbl_2_1.AutoSize = true; - this.lbl_2_1.Location = new System.Drawing.Point(94, 24); - this.lbl_2_1.Name = "lbl_2_1"; - this.lbl_2_1.Size = new System.Drawing.Size(14, 13); - this.lbl_2_1.TabIndex = 2; - this.lbl_2_1.Text = "A"; - this.lbl_2_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // combo_2_4 - // - this.combo_2_4.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_2_4.FormattingEnabled = true; - this.combo_2_4.Location = new System.Drawing.Point(114, 102); - this.combo_2_4.Name = "combo_2_4"; - this.combo_2_4.Size = new System.Drawing.Size(121, 21); - this.combo_2_4.TabIndex = 5; - this.combo_2_4.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // combo_2_3 - // - this.combo_2_3.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_2_3.FormattingEnabled = true; - this.combo_2_3.Location = new System.Drawing.Point(114, 75); - this.combo_2_3.Name = "combo_2_3"; - this.combo_2_3.Size = new System.Drawing.Size(121, 21); - this.combo_2_3.TabIndex = 4; - this.combo_2_3.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // combo_2_2 - // - this.combo_2_2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_2_2.FormattingEnabled = true; - this.combo_2_2.Location = new System.Drawing.Point(114, 48); - this.combo_2_2.Name = "combo_2_2"; - this.combo_2_2.Size = new System.Drawing.Size(121, 21); - this.combo_2_2.TabIndex = 3; - this.combo_2_2.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // combo_2_1 - // - this.combo_2_1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.combo_2_1.FormattingEnabled = true; - this.combo_2_1.Location = new System.Drawing.Point(114, 21); - this.combo_2_1.Name = "combo_2_1"; - this.combo_2_1.Size = new System.Drawing.Size(121, 21); - this.combo_2_1.TabIndex = 2; - this.combo_2_1.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); - // - // cbMemcard_2 - // - this.cbMemcard_2.AutoSize = true; - this.cbMemcard_2.Location = new System.Drawing.Point(18, 21); - this.cbMemcard_2.Name = "cbMemcard_2"; - this.cbMemcard_2.Size = new System.Drawing.Size(70, 17); - this.cbMemcard_2.TabIndex = 1; - this.cbMemcard_2.Text = "Memcard"; - this.cbMemcard_2.UseVisualStyleBackColor = true; - this.cbMemcard_2.CheckedChanged += new System.EventHandler(this.cb_changed); - // - // cbMultitap_2 - // - this.cbMultitap_2.AutoSize = true; - this.cbMultitap_2.Location = new System.Drawing.Point(18, 43); - this.cbMultitap_2.Name = "cbMultitap_2"; - this.cbMultitap_2.Size = new System.Drawing.Size(63, 17); - this.cbMultitap_2.TabIndex = 0; - this.cbMultitap_2.Text = "Multitap"; - this.cbMultitap_2.UseVisualStyleBackColor = true; - this.cbMultitap_2.CheckedChanged += new System.EventHandler(this.cb_changed); - // - // PSXControllerConfigNew - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(586, 201); - this.Controls.Add(this.groupBox2); - this.Controls.Add(this.btnCancel); - this.Controls.Add(this.btnOK); - this.Controls.Add(this.groupBox1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "PSXControllerConfigNew"; - this.Text = "PSX FrontIO Configuration"; - this.Load += new System.EventHandler(this.PSXControllerConfigNew_Load); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.groupBox2.ResumeLayout(false); - this.groupBox2.PerformLayout(); - this.ResumeLayout(false); - + this.cbMultitap_1 = new System.Windows.Forms.CheckBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.lbl_p_1_4 = new System.Windows.Forms.Label(); + this.lbl_p_1_3 = new System.Windows.Forms.Label(); + this.lbl_p_1_2 = new System.Windows.Forms.Label(); + this.lbl_p_1_1 = new System.Windows.Forms.Label(); + this.lbl_1_4 = new System.Windows.Forms.Label(); + this.lbl_1_3 = new System.Windows.Forms.Label(); + this.lbl_1_2 = new System.Windows.Forms.Label(); + this.lbl_1_1 = new System.Windows.Forms.Label(); + this.combo_1_4 = new System.Windows.Forms.ComboBox(); + this.combo_1_3 = new System.Windows.Forms.ComboBox(); + this.combo_1_2 = new System.Windows.Forms.ComboBox(); + this.combo_1_1 = new System.Windows.Forms.ComboBox(); + this.cbMemcard_1 = new System.Windows.Forms.CheckBox(); + this.btnOK = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.lbl_p_2_4 = new System.Windows.Forms.Label(); + this.lbl_p_2_3 = new System.Windows.Forms.Label(); + this.lbl_p_2_2 = new System.Windows.Forms.Label(); + this.lbl_p_2_1 = new System.Windows.Forms.Label(); + this.lbl_2_4 = new System.Windows.Forms.Label(); + this.lbl_2_3 = new System.Windows.Forms.Label(); + this.lbl_2_2 = new System.Windows.Forms.Label(); + this.lbl_2_1 = new System.Windows.Forms.Label(); + this.combo_2_4 = new System.Windows.Forms.ComboBox(); + this.combo_2_3 = new System.Windows.Forms.ComboBox(); + this.combo_2_2 = new System.Windows.Forms.ComboBox(); + this.combo_2_1 = new System.Windows.Forms.ComboBox(); + this.cbMemcard_2 = new System.Windows.Forms.CheckBox(); + this.cbMultitap_2 = new System.Windows.Forms.CheckBox(); + this.label1 = new System.Windows.Forms.Label(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // cbMultitap_1 + // + this.cbMultitap_1.AutoSize = true; + this.cbMultitap_1.Enabled = false; + this.cbMultitap_1.Location = new System.Drawing.Point(18, 43); + this.cbMultitap_1.Name = "cbMultitap_1"; + this.cbMultitap_1.Size = new System.Drawing.Size(63, 17); + this.cbMultitap_1.TabIndex = 0; + this.cbMultitap_1.Text = "Multitap"; + this.cbMultitap_1.UseVisualStyleBackColor = true; + this.cbMultitap_1.CheckedChanged += new System.EventHandler(this.cb_changed); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.lbl_p_1_4); + this.groupBox1.Controls.Add(this.lbl_p_1_3); + this.groupBox1.Controls.Add(this.lbl_p_1_2); + this.groupBox1.Controls.Add(this.lbl_p_1_1); + this.groupBox1.Controls.Add(this.lbl_1_4); + this.groupBox1.Controls.Add(this.lbl_1_3); + this.groupBox1.Controls.Add(this.lbl_1_2); + this.groupBox1.Controls.Add(this.lbl_1_1); + this.groupBox1.Controls.Add(this.combo_1_4); + this.groupBox1.Controls.Add(this.combo_1_3); + this.groupBox1.Controls.Add(this.combo_1_2); + this.groupBox1.Controls.Add(this.combo_1_1); + this.groupBox1.Controls.Add(this.cbMemcard_1); + this.groupBox1.Controls.Add(this.cbMultitap_1); + this.groupBox1.Location = new System.Drawing.Point(12, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(273, 136); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Port 1"; + // + // lbl_p_1_4 + // + this.lbl_p_1_4.AutoSize = true; + this.lbl_p_1_4.Location = new System.Drawing.Point(241, 105); + this.lbl_p_1_4.Name = "lbl_p_1_4"; + this.lbl_p_1_4.Size = new System.Drawing.Size(20, 13); + this.lbl_p_1_4.TabIndex = 12; + this.lbl_p_1_4.Text = "P1"; + this.lbl_p_1_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_p_1_3 + // + this.lbl_p_1_3.AutoSize = true; + this.lbl_p_1_3.Location = new System.Drawing.Point(241, 78); + this.lbl_p_1_3.Name = "lbl_p_1_3"; + this.lbl_p_1_3.Size = new System.Drawing.Size(20, 13); + this.lbl_p_1_3.TabIndex = 11; + this.lbl_p_1_3.Text = "P1"; + this.lbl_p_1_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_p_1_2 + // + this.lbl_p_1_2.AutoSize = true; + this.lbl_p_1_2.Location = new System.Drawing.Point(241, 50); + this.lbl_p_1_2.Name = "lbl_p_1_2"; + this.lbl_p_1_2.Size = new System.Drawing.Size(20, 13); + this.lbl_p_1_2.TabIndex = 10; + this.lbl_p_1_2.Text = "P1"; + this.lbl_p_1_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_p_1_1 + // + this.lbl_p_1_1.AutoSize = true; + this.lbl_p_1_1.Location = new System.Drawing.Point(241, 24); + this.lbl_p_1_1.Name = "lbl_p_1_1"; + this.lbl_p_1_1.Size = new System.Drawing.Size(20, 13); + this.lbl_p_1_1.TabIndex = 9; + this.lbl_p_1_1.Text = "P1"; + this.lbl_p_1_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_1_4 + // + this.lbl_1_4.AutoSize = true; + this.lbl_1_4.Location = new System.Drawing.Point(94, 105); + this.lbl_1_4.Name = "lbl_1_4"; + this.lbl_1_4.Size = new System.Drawing.Size(15, 13); + this.lbl_1_4.TabIndex = 8; + this.lbl_1_4.Text = "D"; + this.lbl_1_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_1_3 + // + this.lbl_1_3.AutoSize = true; + this.lbl_1_3.Location = new System.Drawing.Point(94, 78); + this.lbl_1_3.Name = "lbl_1_3"; + this.lbl_1_3.Size = new System.Drawing.Size(14, 13); + this.lbl_1_3.TabIndex = 7; + this.lbl_1_3.Text = "C"; + this.lbl_1_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_1_2 + // + this.lbl_1_2.AutoSize = true; + this.lbl_1_2.Location = new System.Drawing.Point(94, 51); + this.lbl_1_2.Name = "lbl_1_2"; + this.lbl_1_2.Size = new System.Drawing.Size(14, 13); + this.lbl_1_2.TabIndex = 6; + this.lbl_1_2.Text = "B"; + this.lbl_1_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_1_1 + // + this.lbl_1_1.AutoSize = true; + this.lbl_1_1.Location = new System.Drawing.Point(94, 24); + this.lbl_1_1.Name = "lbl_1_1"; + this.lbl_1_1.Size = new System.Drawing.Size(14, 13); + this.lbl_1_1.TabIndex = 2; + this.lbl_1_1.Text = "A"; + this.lbl_1_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // combo_1_4 + // + this.combo_1_4.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_1_4.FormattingEnabled = true; + this.combo_1_4.Location = new System.Drawing.Point(114, 102); + this.combo_1_4.Name = "combo_1_4"; + this.combo_1_4.Size = new System.Drawing.Size(121, 21); + this.combo_1_4.TabIndex = 5; + this.combo_1_4.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // combo_1_3 + // + this.combo_1_3.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_1_3.FormattingEnabled = true; + this.combo_1_3.Location = new System.Drawing.Point(114, 75); + this.combo_1_3.Name = "combo_1_3"; + this.combo_1_3.Size = new System.Drawing.Size(121, 21); + this.combo_1_3.TabIndex = 4; + this.combo_1_3.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // combo_1_2 + // + this.combo_1_2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_1_2.FormattingEnabled = true; + this.combo_1_2.Location = new System.Drawing.Point(114, 48); + this.combo_1_2.Name = "combo_1_2"; + this.combo_1_2.Size = new System.Drawing.Size(121, 21); + this.combo_1_2.TabIndex = 3; + this.combo_1_2.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // combo_1_1 + // + this.combo_1_1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_1_1.FormattingEnabled = true; + this.combo_1_1.Location = new System.Drawing.Point(114, 21); + this.combo_1_1.Name = "combo_1_1"; + this.combo_1_1.Size = new System.Drawing.Size(121, 21); + this.combo_1_1.TabIndex = 2; + this.combo_1_1.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // cbMemcard_1 + // + this.cbMemcard_1.AutoSize = true; + this.cbMemcard_1.Location = new System.Drawing.Point(18, 21); + this.cbMemcard_1.Name = "cbMemcard_1"; + this.cbMemcard_1.Size = new System.Drawing.Size(70, 17); + this.cbMemcard_1.TabIndex = 1; + this.cbMemcard_1.Text = "Memcard"; + this.cbMemcard_1.UseVisualStyleBackColor = true; + this.cbMemcard_1.CheckedChanged += new System.EventHandler(this.cb_changed); + // + // btnOK + // + this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOK.Location = new System.Drawing.Point(408, 163); + this.btnOK.Name = "btnOK"; + this.btnOK.Size = new System.Drawing.Size(75, 23); + this.btnOK.TabIndex = 2; + this.btnOK.Text = "OK"; + this.btnOK.UseVisualStyleBackColor = true; + this.btnOK.Click += new System.EventHandler(this.btnOK_Click); + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(489, 163); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 3; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.lbl_p_2_4); + this.groupBox2.Controls.Add(this.lbl_p_2_3); + this.groupBox2.Controls.Add(this.lbl_p_2_2); + this.groupBox2.Controls.Add(this.lbl_p_2_1); + this.groupBox2.Controls.Add(this.lbl_2_4); + this.groupBox2.Controls.Add(this.lbl_2_3); + this.groupBox2.Controls.Add(this.lbl_2_2); + this.groupBox2.Controls.Add(this.lbl_2_1); + this.groupBox2.Controls.Add(this.combo_2_4); + this.groupBox2.Controls.Add(this.combo_2_3); + this.groupBox2.Controls.Add(this.combo_2_2); + this.groupBox2.Controls.Add(this.combo_2_1); + this.groupBox2.Controls.Add(this.cbMemcard_2); + this.groupBox2.Controls.Add(this.cbMultitap_2); + this.groupBox2.Location = new System.Drawing.Point(291, 12); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(273, 136); + this.groupBox2.TabIndex = 13; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Port 2"; + // + // lbl_p_2_4 + // + this.lbl_p_2_4.AutoSize = true; + this.lbl_p_2_4.Location = new System.Drawing.Point(241, 105); + this.lbl_p_2_4.Name = "lbl_p_2_4"; + this.lbl_p_2_4.Size = new System.Drawing.Size(20, 13); + this.lbl_p_2_4.TabIndex = 12; + this.lbl_p_2_4.Text = "P1"; + this.lbl_p_2_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_p_2_3 + // + this.lbl_p_2_3.AutoSize = true; + this.lbl_p_2_3.Location = new System.Drawing.Point(241, 78); + this.lbl_p_2_3.Name = "lbl_p_2_3"; + this.lbl_p_2_3.Size = new System.Drawing.Size(20, 13); + this.lbl_p_2_3.TabIndex = 11; + this.lbl_p_2_3.Text = "P1"; + this.lbl_p_2_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_p_2_2 + // + this.lbl_p_2_2.AutoSize = true; + this.lbl_p_2_2.Location = new System.Drawing.Point(241, 50); + this.lbl_p_2_2.Name = "lbl_p_2_2"; + this.lbl_p_2_2.Size = new System.Drawing.Size(20, 13); + this.lbl_p_2_2.TabIndex = 10; + this.lbl_p_2_2.Text = "P1"; + this.lbl_p_2_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_p_2_1 + // + this.lbl_p_2_1.AutoSize = true; + this.lbl_p_2_1.Location = new System.Drawing.Point(241, 24); + this.lbl_p_2_1.Name = "lbl_p_2_1"; + this.lbl_p_2_1.Size = new System.Drawing.Size(20, 13); + this.lbl_p_2_1.TabIndex = 9; + this.lbl_p_2_1.Text = "P1"; + this.lbl_p_2_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_2_4 + // + this.lbl_2_4.AutoSize = true; + this.lbl_2_4.Location = new System.Drawing.Point(94, 105); + this.lbl_2_4.Name = "lbl_2_4"; + this.lbl_2_4.Size = new System.Drawing.Size(15, 13); + this.lbl_2_4.TabIndex = 8; + this.lbl_2_4.Text = "D"; + this.lbl_2_4.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_2_3 + // + this.lbl_2_3.AutoSize = true; + this.lbl_2_3.Location = new System.Drawing.Point(94, 78); + this.lbl_2_3.Name = "lbl_2_3"; + this.lbl_2_3.Size = new System.Drawing.Size(14, 13); + this.lbl_2_3.TabIndex = 7; + this.lbl_2_3.Text = "C"; + this.lbl_2_3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_2_2 + // + this.lbl_2_2.AutoSize = true; + this.lbl_2_2.Location = new System.Drawing.Point(94, 51); + this.lbl_2_2.Name = "lbl_2_2"; + this.lbl_2_2.Size = new System.Drawing.Size(14, 13); + this.lbl_2_2.TabIndex = 6; + this.lbl_2_2.Text = "B"; + this.lbl_2_2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbl_2_1 + // + this.lbl_2_1.AutoSize = true; + this.lbl_2_1.Location = new System.Drawing.Point(94, 24); + this.lbl_2_1.Name = "lbl_2_1"; + this.lbl_2_1.Size = new System.Drawing.Size(14, 13); + this.lbl_2_1.TabIndex = 2; + this.lbl_2_1.Text = "A"; + this.lbl_2_1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // combo_2_4 + // + this.combo_2_4.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_2_4.FormattingEnabled = true; + this.combo_2_4.Location = new System.Drawing.Point(114, 102); + this.combo_2_4.Name = "combo_2_4"; + this.combo_2_4.Size = new System.Drawing.Size(121, 21); + this.combo_2_4.TabIndex = 5; + this.combo_2_4.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // combo_2_3 + // + this.combo_2_3.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_2_3.FormattingEnabled = true; + this.combo_2_3.Location = new System.Drawing.Point(114, 75); + this.combo_2_3.Name = "combo_2_3"; + this.combo_2_3.Size = new System.Drawing.Size(121, 21); + this.combo_2_3.TabIndex = 4; + this.combo_2_3.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // combo_2_2 + // + this.combo_2_2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_2_2.FormattingEnabled = true; + this.combo_2_2.Location = new System.Drawing.Point(114, 48); + this.combo_2_2.Name = "combo_2_2"; + this.combo_2_2.Size = new System.Drawing.Size(121, 21); + this.combo_2_2.TabIndex = 3; + this.combo_2_2.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // combo_2_1 + // + this.combo_2_1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.combo_2_1.FormattingEnabled = true; + this.combo_2_1.Location = new System.Drawing.Point(114, 21); + this.combo_2_1.Name = "combo_2_1"; + this.combo_2_1.Size = new System.Drawing.Size(121, 21); + this.combo_2_1.TabIndex = 2; + this.combo_2_1.SelectedIndexChanged += new System.EventHandler(this.combo_SelectedIndexChanged); + // + // cbMemcard_2 + // + this.cbMemcard_2.AutoSize = true; + this.cbMemcard_2.Location = new System.Drawing.Point(18, 21); + this.cbMemcard_2.Name = "cbMemcard_2"; + this.cbMemcard_2.Size = new System.Drawing.Size(70, 17); + this.cbMemcard_2.TabIndex = 1; + this.cbMemcard_2.Text = "Memcard"; + this.cbMemcard_2.UseVisualStyleBackColor = true; + this.cbMemcard_2.CheckedChanged += new System.EventHandler(this.cb_changed); + // + // cbMultitap_2 + // + this.cbMultitap_2.AutoSize = true; + this.cbMultitap_2.Enabled = false; + this.cbMultitap_2.Location = new System.Drawing.Point(18, 43); + this.cbMultitap_2.Name = "cbMultitap_2"; + this.cbMultitap_2.Size = new System.Drawing.Size(63, 17); + this.cbMultitap_2.TabIndex = 0; + this.cbMultitap_2.Text = "Multitap"; + this.cbMultitap_2.UseVisualStyleBackColor = true; + this.cbMultitap_2.CheckedChanged += new System.EventHandler(this.cb_changed); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(13, 172); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(177, 13); + this.label1.TabIndex = 14; + this.label1.Text = "Sorry, multitap not supported just yet"; + // + // PSXControllerConfigNew + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(586, 201); + this.Controls.Add(this.label1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOK); + this.Controls.Add(this.groupBox1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "PSXControllerConfigNew"; + this.Text = "PSX FrontIO Configuration"; + this.Load += new System.EventHandler(this.PSXControllerConfigNew_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + } #endregion @@ -470,6 +486,7 @@ private System.Windows.Forms.ComboBox combo_2_2; private System.Windows.Forms.ComboBox combo_2_1; private System.Windows.Forms.CheckBox cbMemcard_2; - private System.Windows.Forms.CheckBox cbMultitap_2; + private System.Windows.Forms.CheckBox cbMultitap_2; + private System.Windows.Forms.Label label1; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.cs b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.cs index 2f7758a097..0c4ab7d700 100644 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.cs +++ b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.cs @@ -34,15 +34,56 @@ namespace BizHawk.Client.EmuHawk combo.SelectedIndex = 0; } + var psxSettings = ((Octoshock)Global.Emulator).GetSyncSettings(); + GuiFromUserConfig(psxSettings.FIOConfig); + RefreshLabels(); + } + + void GuiFromUserConfig(OctoshockFIOConfigUser user) + { + cbMemcard_1.Checked = user.Memcards[0]; + cbMemcard_2.Checked = user.Memcards[1]; + cbMultitap_1.Checked = user.Multitaps[0]; + cbMultitap_2.Checked = user.Multitaps[1]; + + var combos = new[] { combo_1_1, combo_1_2, combo_1_3, combo_1_4, combo_2_1, combo_2_2, combo_2_3, combo_2_4 }; + for (int i = 0; i < 8; i++) + { + var combo = combos[i]; + if (user.Devices8[i] == OctoshockDll.ePeripheralType.None) combo.SelectedIndex = 0; + if (user.Devices8[i] == OctoshockDll.ePeripheralType.DualAnalog) combo.SelectedIndex = 1; + if (user.Devices8[i] == OctoshockDll.ePeripheralType.DualShock) combo.SelectedIndex = 2; + } + } + + OctoshockFIOConfigUser UserConfigFromGui() + { + OctoshockFIOConfigUser uc = new OctoshockFIOConfigUser(); + + uc.Memcards[0] = cbMemcard_1.Checked; + uc.Memcards[1] = cbMemcard_2.Checked; + + uc.Multitaps[0] = cbMultitap_1.Checked; + uc.Multitaps[1] = cbMultitap_2.Checked; + + var combos = new[] { combo_1_1, combo_1_2, combo_1_3, combo_1_4, combo_2_1, combo_2_2, combo_2_3, combo_2_4 }; + for (int i = 0; i < 8; i++) + { + var combo = combos[i]; + if (combo.SelectedIndex == 0) uc.Devices8[i] = OctoshockDll.ePeripheralType.None; + if (combo.SelectedIndex == 1) uc.Devices8[i] = OctoshockDll.ePeripheralType.DualAnalog; + if (combo.SelectedIndex == 2) uc.Devices8[i] = OctoshockDll.ePeripheralType.DualShock; + } + + return uc; } void RefreshLabels() - { - bool multitap_1 = cbMultitap_1.Checked; - bool multitap_2 = cbMultitap_2.Checked; + { + var uc = UserConfigFromGui(); - bool b1 = multitap_1; + bool b1 = uc.Multitaps[0]; lbl_1_1.Visible = b1; lbl_1_2.Visible = b1; lbl_1_3.Visible = b1; @@ -52,9 +93,9 @@ namespace BizHawk.Client.EmuHawk combo_1_4.Enabled = b1; lbl_p_1_2.Visible = b1; lbl_p_1_3.Visible = b1; - lbl_p_1_4.Visible = b1; - - bool b2 = multitap_2; + lbl_p_1_4.Visible = b1; + + bool b2 = uc.Multitaps[1]; lbl_2_1.Visible = b2; lbl_2_2.Visible = b2; lbl_2_3.Visible = b2; @@ -66,21 +107,7 @@ namespace BizHawk.Client.EmuHawk lbl_p_2_3.Visible = b2; lbl_p_2_4.Visible = b2; - OctoshockControlUserConfig uc = new OctoshockControlUserConfig(); - - uc.Multitaps[0] = multitap_1; - uc.Multitaps[1] = multitap_2; - - var combos = new[] { combo_1_1, combo_1_2, combo_1_3, combo_1_4, combo_2_1, combo_2_2, combo_2_3, combo_2_4}; - for (int i = 0; i < 8; i++) - { - var combo = combos[i]; - if (combo.SelectedIndex == 0) uc.Devices8[i] = OctoshockDll.ePeripheralType.None; - if (combo.SelectedIndex == 1) uc.Devices8[i] = OctoshockDll.ePeripheralType.DualAnalog; - if (combo.SelectedIndex == 2) uc.Devices8[i] = OctoshockDll.ePeripheralType.DualShock; - } - - var LC = uc.ToLogicalConfig(); + var LC = uc.ToLogical(); var p_labels = new[] { lbl_p_1_1,lbl_p_1_2,lbl_p_1_3,lbl_p_1_4,lbl_p_2_1,lbl_p_2_2,lbl_p_2_3,lbl_p_2_4}; for (int i = 0; i < 8; i++) @@ -94,7 +121,6 @@ namespace BizHawk.Client.EmuHawk lbl.Visible = true; } } - } private void cb_changed(object sender, EventArgs e) @@ -105,6 +131,18 @@ namespace BizHawk.Client.EmuHawk private void combo_SelectedIndexChanged(object sender, EventArgs e) { RefreshLabels(); + } + + private void btnOK_Click(object sender, EventArgs e) + { + var psxSettings = ((Octoshock)Global.Emulator).GetSyncSettings(); + + psxSettings.FIOConfig = UserConfigFromGui(); + GlobalWin.MainForm.PutCoreSyncSettings(psxSettings); + + DialogResult = DialogResult.OK; + + Close(); } } } diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx index 1af7de150c..29dcb1b3a3 100644 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx +++ b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/PSXSchema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/PSXSchema.cs index e2fc87c47c..71082ec872 100644 --- a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/PSXSchema.cs +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/PSXSchema.cs @@ -14,19 +14,14 @@ namespace BizHawk.Client.EmuHawk var psx = ((Octoshock)Global.Emulator); var settings = (Octoshock.SyncSettings)psx.GetSyncSettings(); - for (int i = 0; i < settings.Controllers.Length; i++) + var fioConfig = settings.FIOConfig.ToLogical(); + for (int i = 0; i < 2; i++) { - if (settings.Controllers[i].IsConnected) - { - if (settings.Controllers[i].Type == Octoshock.ControllerSetting.ControllerType.Gamepad) - { - yield return GamePadController(i + 1); - } - else - { - yield return DualShockController(i + 1); - } - } + int pnum = i + 1; + if (fioConfig.DevicesPlayer[i] == OctoshockDll.ePeripheralType.DualAnalog || fioConfig.DevicesPlayer[i] == OctoshockDll.ePeripheralType.DualShock) + yield return DualShockController(pnum); + if (fioConfig.DevicesPlayer[i] == OctoshockDll.ePeripheralType.Pad) + yield return GamePadController(pnum); } yield return ConsoleButtons(psx); diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 0876ed3bcb..32e208be7d 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -797,8 +797,8 @@ Code - + diff --git a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs index 2552eac888..ca16d2ce99 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs @@ -39,47 +39,49 @@ namespace BizHawk.Emulation.Cores.Sony.PSX private void SetControllerButtons() { ControllerDefinition = new ControllerDefinition(); - ControllerDefinition.Name = _SyncSettings.Controllers.All(c => c.Type == ControllerSetting.ControllerType.Gamepad) - ? "PSX Gamepad Controller" - : "PSX DualShock Controller"; // Meh, more nuanced logic doesn't really work with a simple property + ControllerDefinition.Name = "PSX DualShock Controller"; // <-- for compatibility + //ControllerDefinition.Name = "PSX FrontIO"; // TODO - later rename to this, I guess, so it's less misleading. don't want to wreck keybindings yet. ControllerDefinition.BoolButtons.Clear(); ControllerDefinition.FloatControls.Clear(); - for (int i = 0; i < _SyncSettings.Controllers.Length; i++) + var cfg = _SyncSettings.FIOConfig.ToLogical(); + + for (int i = 0; i < cfg.NumPlayers; i++) { - if (_SyncSettings.Controllers[i].IsConnected) - { + int pnum = i + 1; ControllerDefinition.BoolButtons.AddRange(new[] { - "P" + (i + 1) + " Up", - "P" + (i + 1) + " Down", - "P" + (i + 1) + " Left", - "P" + (i + 1) + " Right", - "P" + (i + 1) + " Select", - "P" + (i + 1) + " Start", - "P" + (i + 1) + " Square", - "P" + (i + 1) + " Triangle", - "P" + (i + 1) + " Circle", - "P" + (i + 1) + " Cross", - "P" + (i + 1) + " L1", - "P" + (i + 1) + " R1", - "P" + (i + 1) + " L2", - "P" + (i + 1) + " R2", + "P" + pnum + " Up", + "P" + pnum + " Down", + "P" + pnum + " Left", + "P" + pnum + " Right", + "P" + pnum + " Select", + "P" + pnum + " Start", + "P" + pnum + " Square", + "P" + pnum + " Triangle", + "P" + pnum + " Circle", + "P" + pnum + " Cross", + "P" + pnum + " L1", + "P" + pnum + " R1", + "P" + pnum + " L2", + "P" + pnum + " R2", }); - if (_SyncSettings.Controllers[i].Type != ControllerSetting.ControllerType.Gamepad) + var type = cfg.DevicesPlayer[i]; + + if (type == OctoshockDll.ePeripheralType.DualShock || type == OctoshockDll.ePeripheralType.DualAnalog) { - ControllerDefinition.BoolButtons.Add("P" + (i + 1) + " L3"); - ControllerDefinition.BoolButtons.Add("P" + (i + 1) + " R3"); - ControllerDefinition.BoolButtons.Add("P" + (i + 1) + " MODE"); + ControllerDefinition.BoolButtons.Add("P" + pnum + " L3"); + ControllerDefinition.BoolButtons.Add("P" + pnum + " R3"); + ControllerDefinition.BoolButtons.Add("P" + pnum + " MODE"); ControllerDefinition.FloatControls.AddRange(new[] { - "P" + (i + 1) + " LStick X", - "P" + (i + 1) + " LStick Y", - "P" + (i + 1) + " RStick X", - "P" + (i + 1) + " RStick Y" + "P" + pnum + " LStick X", + "P" + pnum + " LStick Y", + "P" + pnum + " RStick X", + "P" + pnum + " RStick Y" }); ControllerDefinition.FloatRanges.Add(new[] { 0.0f, 128.0f, 255.0f }); @@ -88,7 +90,6 @@ namespace BizHawk.Emulation.Cores.Sony.PSX ControllerDefinition.FloatRanges.Add(new[] { 255.0f, 128.0f, 0.0f }); } } - } ControllerDefinition.BoolButtons.AddRange(new[] { @@ -374,21 +375,18 @@ namespace BizHawk.Emulation.Cores.Sony.PSX //setup the controller based on sync settings SetControllerButtons(); - var lookup = new Dictionary { - { ControllerSetting.ControllerType.Gamepad, OctoshockDll.ePeripheralType.Pad }, - { ControllerSetting.ControllerType.DualAnalog, OctoshockDll.ePeripheralType.DualAnalog }, - { ControllerSetting.ControllerType.DualShock, OctoshockDll.ePeripheralType.DualShock }, + var fioCfg = _SyncSettings.FIOConfig; + if(fioCfg.Devices8[0] != OctoshockDll.ePeripheralType.None) + OctoshockDll.shock_Peripheral_Connect(psx, 0x01, fioCfg.Devices8[0]); + if (fioCfg.Devices8[4] != OctoshockDll.ePeripheralType.None) + OctoshockDll.shock_Peripheral_Connect(psx, 0x02, fioCfg.Devices8[4]); + + var memcardTransaction = new OctoshockDll.ShockMemcardTransaction() + { + transaction = OctoshockDll.eShockMemcardTransaction.Connect }; - - if (_SyncSettings.Controllers[0].IsConnected) - { - OctoshockDll.shock_Peripheral_Connect(psx, 0x01, lookup[_SyncSettings.Controllers[0].Type]); - } - - if (_SyncSettings.Controllers[1].IsConnected) - { - OctoshockDll.shock_Peripheral_Connect(psx, 0x02, lookup[_SyncSettings.Controllers[1].Type]); - } + if (fioCfg.Memcards[0]) OctoshockDll.shock_Peripheral_MemcardTransact(psx, 0x01, ref memcardTransaction); + if (fioCfg.Memcards[1]) OctoshockDll.shock_Peripheral_MemcardTransact(psx, 0x02, ref memcardTransaction); //do this after framebuffers and peripherals and whatever crap are setup. kind of lame, but thats how it is for now StudySaveBufferSize(); @@ -423,65 +421,48 @@ namespace BizHawk.Emulation.Cores.Sony.PSX void SetInput() { - uint buttons = 0; + var fioCfg = _SyncSettings.FIOConfig.ToLogical(); - if (_SyncSettings.Controllers[0].IsConnected) + int portNum = 0x01; + foreach (int slot in new[] { 0, 4 }) { - //dualshock style - if (Controller["P1 Select"]) buttons |= 1; - if (Controller["P1 L3"]) buttons |= 2; - if (Controller["P1 R3"]) buttons |= 4; - if (Controller["P1 Start"]) buttons |= 8; - if (Controller["P1 Up"]) buttons |= 16; - if (Controller["P1 Right"]) buttons |= 32; - if (Controller["P1 Down"]) buttons |= 64; - if (Controller["P1 Left"]) buttons |= 128; - if (Controller["P1 L2"]) buttons |= 256; - if (Controller["P1 R2"]) buttons |= 512; - if (Controller["P1 L1"]) buttons |= 1024; - if (Controller["P1 R1"]) buttons |= 2048; - if (Controller["P1 Triangle"]) buttons |= 4096; - if (Controller["P1 Circle"]) buttons |= 8192; - if (Controller["P1 Cross"]) buttons |= 16384; - if (Controller["P1 Square"]) buttons |= 32768; - if (Controller["P1 MODE"]) buttons |= 65536; + //no input to set + if (fioCfg.Devices8[slot] == OctoshockDll.ePeripheralType.None) + continue; - byte left_x = (byte)Controller.GetFloat("P1 LStick X"); - byte left_y = (byte)Controller.GetFloat("P1 LStick Y"); - byte right_x = (byte)Controller.GetFloat("P1 RStick X"); - byte right_y = (byte)Controller.GetFloat("P1 RStick Y"); + uint buttons = 0; + string pstring = "P" + fioCfg.PlayerAssignments[slot] + " "; - OctoshockDll.shock_Peripheral_SetPadInput(psx, 0x01, buttons, left_x, left_y, right_x, right_y); - } + if (Controller[pstring + "Select"]) buttons |= 1; + if (Controller[pstring + "Start"]) buttons |= 8; + if (Controller[pstring + "Up"]) buttons |= 16; + if (Controller[pstring + "Right"]) buttons |= 32; + if (Controller[pstring + "Down"]) buttons |= 64; + if (Controller[pstring + "Left"]) buttons |= 128; + if (Controller[pstring + "L2"]) buttons |= 256; + if (Controller[pstring + "R2"]) buttons |= 512; + if (Controller[pstring + "L1"]) buttons |= 1024; + if (Controller[pstring + "R1"]) buttons |= 2048; + if (Controller[pstring + "Triangle"]) buttons |= 4096; + if (Controller[pstring + "Circle"]) buttons |= 8192; + if (Controller[pstring + "Cross"]) buttons |= 16384; + if (Controller[pstring + "Square"]) buttons |= 32768; - if (_SyncSettings.Controllers[1].IsConnected) - { - //dualshock style - buttons = 0; - if (Controller["P2 Select"]) buttons |= 1; - if (Controller["P2 L3"]) buttons |= 2; - if (Controller["P2 R3"]) buttons |= 4; - if (Controller["P2 Start"]) buttons |= 8; - if (Controller["P2 Up"]) buttons |= 16; - if (Controller["P2 Right"]) buttons |= 32; - if (Controller["P2 Down"]) buttons |= 64; - if (Controller["P2 Left"]) buttons |= 128; - if (Controller["P2 L2"]) buttons |= 256; - if (Controller["P2 R2"]) buttons |= 512; - if (Controller["P2 L1"]) buttons |= 1024; - if (Controller["P2 R1"]) buttons |= 2048; - if (Controller["P2 Triangle"]) buttons |= 4096; - if (Controller["P2 Circle"]) buttons |= 8192; - if (Controller["P2 Cross"]) buttons |= 16384; - if (Controller["P2 Square"]) buttons |= 32768; - if (Controller["P2 MODE"]) buttons |= 65536; + byte left_x = 0, left_y = 0, right_x = 0, right_y = 0; + if (fioCfg.Devices8[slot] == OctoshockDll.ePeripheralType.DualShock || fioCfg.Devices8[slot] == OctoshockDll.ePeripheralType.DualAnalog) + { + if (Controller[pstring + "L3"]) buttons |= 2; + if (Controller[pstring + "R3"]) buttons |= 4; + if (Controller[pstring + "MODE"]) buttons |= 65536; - byte left_x = (byte)Controller.GetFloat("P2 LStick X"); - byte left_y = (byte)Controller.GetFloat("P2 LStick Y"); - byte right_x = (byte)Controller.GetFloat("P2 RStick X"); - byte right_y = (byte)Controller.GetFloat("P2 RStick Y"); + left_x = (byte)Controller.GetFloat(pstring + "LStick X"); + left_y = (byte)Controller.GetFloat(pstring + "LStick Y"); + right_x = (byte)Controller.GetFloat(pstring + "RStick X"); + right_y = (byte)Controller.GetFloat(pstring + "RStick Y"); + } - OctoshockDll.shock_Peripheral_SetPadInput(psx, 0x02, buttons, left_x, left_y, right_x, right_y); + OctoshockDll.shock_Peripheral_SetPadInput(psx, portNum, buttons, left_x, left_y, right_x, right_y); + portNum <<= 1; } } @@ -858,25 +839,42 @@ namespace BizHawk.Emulation.Cores.Sony.PSX public byte[] CloneSaveRam() { - var buf = new byte[128 * 1024]; - fixed (byte* pbuf = buf) + var cfg = _SyncSettings.FIOConfig.ToLogical(); + int nMemcards = cfg.NumMemcards; + var buf = new byte[128 * 1024 * nMemcards]; + for (int i = 0, idx = 0, addr=0x01; i < 2; i++, addr<<=1) { - var transaction = new OctoshockDll.ShockMemcardTransaction(); - transaction.buffer128k = pbuf; - transaction.transaction = OctoshockDll.eShockMemcardTransaction.Read; - OctoshockDll.shock_Peripheral_MemcardTransact(psx, 0x01, ref transaction); + if (cfg.Memcards[i]) + { + fixed (byte* pbuf = buf) + { + var transaction = new OctoshockDll.ShockMemcardTransaction(); + transaction.buffer128k = pbuf + idx * 128 * 1024; + transaction.transaction = OctoshockDll.eShockMemcardTransaction.Read; + OctoshockDll.shock_Peripheral_MemcardTransact(psx, addr, ref transaction); + idx++; + } + } } return buf; } public void StoreSaveRam(byte[] data) { - fixed (byte* pbuf = data) + var cfg = _SyncSettings.FIOConfig.ToLogical(); + for (int i = 0, idx = 0, addr = 0x01; i < 2; i++, addr <<= 1) { - var transaction = new OctoshockDll.ShockMemcardTransaction(); - transaction.buffer128k = pbuf; - transaction.transaction = OctoshockDll.eShockMemcardTransaction.Write; - OctoshockDll.shock_Peripheral_MemcardTransact(psx, 0x01, ref transaction); + if (cfg.Memcards[i]) + { + fixed (byte* pbuf = data) + { + var transaction = new OctoshockDll.ShockMemcardTransaction(); + transaction.buffer128k = pbuf + idx * 128 * 1024; + transaction.transaction = OctoshockDll.eShockMemcardTransaction.Write; + OctoshockDll.shock_Peripheral_MemcardTransact(psx, addr, ref transaction); + idx++; + } + } } } @@ -884,9 +882,20 @@ namespace BizHawk.Emulation.Cores.Sony.PSX { get { - var transaction = new OctoshockDll.ShockMemcardTransaction(); - transaction.transaction = OctoshockDll.eShockMemcardTransaction.CheckDirty; - return OctoshockDll.shock_Peripheral_MemcardTransact(psx, 0x01, ref transaction) == OctoshockDll.SHOCK_TRUE; + var cfg = _SyncSettings.FIOConfig.ToLogical(); + for (int i = 0, addr = 0x01; i < 2; i++, addr <<= 1) + { + if (cfg.Memcards[i]) + { + var transaction = new OctoshockDll.ShockMemcardTransaction(); + transaction.transaction = OctoshockDll.eShockMemcardTransaction.CheckDirty; + OctoshockDll.shock_Peripheral_MemcardTransact(psx, addr, ref transaction); + if (OctoshockDll.shock_Peripheral_MemcardTransact(psx, addr, ref transaction) == OctoshockDll.SHOCK_TRUE) + return true; + } + } + + return false; } } @@ -1055,49 +1064,23 @@ namespace BizHawk.Emulation.Cores.Sony.PSX { public SyncSettings Clone() { - var ret = (SyncSettings)MemberwiseClone(); - ret.Controllers = Controllers.Select(x => x.Clone()).ToArray(); - - return ret; + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)); } public bool EnableLEC; - public ControllerSetting[] Controllers = + public SyncSettings() { - new ControllerSetting - { - IsConnected = true, - Type = ControllerSetting.ControllerType.DualShock - }, - new ControllerSetting - { - IsConnected = false, - Type = ControllerSetting.ControllerType.DualShock - } - }; - } - - public class ControllerSetting - { - public ControllerSetting Clone() - { - return (ControllerSetting)this.MemberwiseClone(); + //initialize with historical default settings + var user = new OctoshockFIOConfigUser(); + user.Memcards[0] = user.Memcards[1] = true; + user.Multitaps[0] = user.Multitaps[0] = false; + user.Devices8[0] = OctoshockDll.ePeripheralType.DualShock; + user.Devices8[4] = OctoshockDll.ePeripheralType.DualShock; + FIOConfig = user; } - public bool IsConnected { get; set; } - public ControllerType Type { get; set; } - - public enum ControllerType - { - Gamepad, - - [Description("Dual Analog")] - DualAnalog, - - [Description("Dual Shock")] - DualShock - } + public OctoshockFIOConfigUser FIOConfig; } public enum eHorizontalClipping @@ -1200,12 +1183,15 @@ namespace BizHawk.Emulation.Cores.Sony.PSX public bool PutSyncSettings(SyncSettings o) { - //check for reboot-required options (well, none right now) - bool reboot = false; + //currently LEC and pad settings changes both require reboot + bool reboot = true; + + //we could do it this way roughly if we need to + //if(JsonConvert.SerializeObject(o.FIOConfig) != JsonConvert.SerializeObject(_SyncSettings.FIOConfig) + _SyncSettings = o; - - //TODO - store settings into core? or we can just keep doing it before frameadvance + return reboot; } diff --git a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/OctoshockControlConfig.cs b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/OctoshockFIOConfig.cs similarity index 54% rename from BizHawk.Emulation.Cores/Consoles/Sony/PSX/OctoshockControlConfig.cs rename to BizHawk.Emulation.Cores/Consoles/Sony/PSX/OctoshockFIOConfig.cs index 6cbf28bf3f..c4e0845434 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/OctoshockControlConfig.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/OctoshockFIOConfig.cs @@ -3,28 +3,57 @@ using System.Collections.Generic; namespace BizHawk.Emulation.Cores.Sony.PSX { - public class OctoshockControlUserConfig + /// + /// Represents a user's view of what equipment is plugged into the PSX FIO + /// + public class OctoshockFIOConfigUser { public bool[] Multitaps = new bool[2]; + public bool[] Memcards = new bool[2]; public OctoshockDll.ePeripheralType[] Devices8 = new OctoshockDll.ePeripheralType[8]; - public OctoshockControlLogicalConfig ToLogicalConfig() + public OctoshockFIOConfigLogical ToLogical() { - var lc = new OctoshockControlLogicalConfig(); + var lc = new OctoshockFIOConfigLogical(); lc.PopulateFrom(this); return lc; } } - public class OctoshockControlLogicalConfig + /// + /// Represents a baked-down view of what's plugged into the PSX FIO. + /// But really, users are interested in it too (its what produces the player number assignments) + /// + public class OctoshockFIOConfigLogical { - public int[] PlayerAssignments = new int[8]; public bool[] Multitaps; + public bool[] Memcards; public OctoshockDll.ePeripheralType[] Devices8; - internal void PopulateFrom(OctoshockControlUserConfig userConfig) + /// + /// Total number of players defined + /// + public int NumPlayers; + + /// + /// The player number on each of the input slots + /// + public int[] PlayerAssignments = new int[8]; + + /// + /// The device type associated with each player + /// + public OctoshockDll.ePeripheralType[] DevicesPlayer = new OctoshockDll.ePeripheralType[8]; + + /// + /// Total number of connected memcards + /// + public int NumMemcards { get { return (Memcards[0] ? 1 : 0) + (Memcards[1] ? 1 : 0); } } + + internal void PopulateFrom(OctoshockFIOConfigUser userConfig) { Multitaps = (bool[])userConfig.Multitaps.Clone(); + Memcards = (bool[])userConfig.Memcards.Clone(); Devices8 = (OctoshockDll.ePeripheralType[])userConfig.Devices8.Clone(); int id = 1; @@ -38,6 +67,18 @@ namespace BizHawk.Emulation.Cores.Sony.PSX if (userConfig.Devices8[5] == OctoshockDll.ePeripheralType.None || !userConfig.Multitaps[1]) PlayerAssignments[5] = -1; else PlayerAssignments[5] = id++; if (userConfig.Devices8[6] == OctoshockDll.ePeripheralType.None || !userConfig.Multitaps[1]) PlayerAssignments[6] = -1; else PlayerAssignments[6] = id++; if (userConfig.Devices8[7] == OctoshockDll.ePeripheralType.None || !userConfig.Multitaps[1]) PlayerAssignments[7] = -1; else PlayerAssignments[7] = id++; + + NumPlayers = id - 1; + + for (int i = 0; i < 8; i++) + { + int pnum = i+1; + for (int j = 0; j < 8; j++) + { + if(PlayerAssignments[j] == pnum) + DevicesPlayer[i] = userConfig.Devices8[j]; + } + } } } diff --git a/output/dll/octoshock.dll b/output/dll/octoshock.dll index 935bf7a9ba5149886a46604f953f3d47384760cf..1dadf2281d34d8a760d02397a315bcf1574ea4ea 100644 GIT binary patch delta 48094 zcmaHU33yD``~RFflgZ?siA*FSkwsQSf{-9;i;|$Wi4am-)HWhWE3p#<5hFzh$6jk| zYti7KTG52qrK0vFNV&ukwUkQD|2^l<#7z4Cee*nL?!BM)J@0$Y`=0Zj4q>o1GPH6zs2;q7*F+aMS8~DXyl$wb5CT zwlu&SrJ1gwmO}6tQ%#|s5FKjzM3{)K=Is@xAY?R!sk)#!rgT-H&n6RAWR3KMLvgKnx;Cq)f2ja7R&`m=NQVWwQQSJMb~{^|>?NGdpM|7VC{q-#|q zgPvWVGF?#z3dvJV@6}CQGdM9`N)fTbkOpMpJ5KB8F2&0Dp=Gms>ZGk-g8F>ZSjWH! zU6nA4D#Mb`vCfbWKPf_r*6`#i@au1R{#h+1B_@;j#v>-1wQb2-#L|)gZng}g54Qw7EBqy1^ZoX1@kZ&q)(cP5ZB0~tu&ns_{ ziO?p~nCNN3?65p3`mX4k<)>AoC=453s1&{R(*FAVN`hVsSKpf+bUY}G`^mJS(=cIp z-8_d_wTNctX?ykaMLDM7sox2@zfIb)%>zzVip+k4e$yzVA9`bjw2l32WH6ocPD#aTZCfR4+!mzjY*fxe~z$jJn z4DMf=oG*H22HS$U^Y<~v2z%@x!(!~QBMj>Vm_gwWj zoFIc$UXYC@gVnqsgBzf-Hg1qX*$yr4JBoO_tikM5Dd zYW|VIY7UY?i-#Z(Rk_GQ@CUheK9ccjPLe^{OH~e$!D?QTL93hgUOMTX&LS7+B`u%1pYDPRyXb_yO2 zGlH00)qK5SpQ+ugh=6?-@&Opa5)4P+3dWRlmSGD=3Qdb|8Hb$(JpM$d;4TWO{ykPk zEd(fcn0XwN+_FlM-f#&R9&Kk1>a0ysDGLlY0nkhT+_f^ha%R`8Ql;Kd2DI`_#;F9( zC!Dj`G~>2sVg;l0waR$QDVwXwaON@;Q-HI9l~V&(dTB}(X8_kG@}lXcn96TA6+EYy z{O&Xn{HL0F-Kp*EKh;?$p-Qv)(QE{T?iB2w+dWuoj+*B`#Wed)ZM4C(>dy8$8yHu} zd*Is2s@5sEfrV>>X<(@*FlCfdp>=5H6fRTnYJRi zHiNB18L<^8BewEnWH@Lr@U9!pilQ4_aQ5Inz-(rZMJNpb zGAORu$gH=5(bZ*HJ_M&=Q^a5H9zb)q&{`LLBz^wZIM-qSDs(d-ErPTTRBbd}`zu!C zsX$Dr!c_0&<62&3g>D3-pCMg@^a#>ZQ^>2a=(Nf7YFyCI*=F;`Sr2>af*cmW@5q4W zbJ<~DzRU7_mjn6wc$jYlzb@+i`+Vs0J$1QpU5ta>_2!JXMpxsf;pSpv>)gcdz1j7d z1+gED{ld)`jXhNhXM8ZucpLaOH?EV>Ro_qMtg-NFZd^>@9k_Ni_6|3-eX|I?%sc(2 zvw-qVHQ%*CCr!QHMWRda8*bYD&Y@0Zn#ExnSdjE# zDX?TSmM12!f12X2Ho|o`cKz1W_n($t18mr(VHAj!;Dkep>87nf#kSN5hk?q*i9?#{ zrq}3XYVH-nW%4N|TZa8Af|2H7Bn88979A&s_5413pG2o-5 zXu}f*XsWWr7%CZ{s{-@}?TjiQ(ri@sGx!6LXx3|t+Mi))Frvg1g;}o!ii?%RE({H3 zn8ttb65z1#Lo0jh!5~@9d@^EdKN+z#pp4jBP)2M`C?mEul#%)tEApz#kgXMEgqd-) zIVj|#8D-qoj)16hyMZB1d(H0AAFU^I+L}*BZ0*-uWi_DbU$bYZ#f}zZS!~+iaV2D3=t zI%piqA%%5NCcBLFMuD|QG=v`d>pmAKxXZjaCm3dXXrzd6GSeG%6O;|f8E-UHnALzb z_CYfdQji~f(Y(4*(BN|Cy@DwXU-IjvM@P(NXnK#zSa=>tw zvo7+Y@uacH_)95d`a9|u3-s1lU|h$buy9v)Kz0X%KTf?IJT>x4d&!x3lCb-uCSRS&gmI{QooeVZp+C&D@qj_g zuX5*i0e-gP;H%i~EVHLAU+#Q8!-DOxJ`8JMgE<-^;Hn>skB(V|n%O7%bFl`0Xq9{+ zNH#P;{X-d3qEXHI_@GMQGkU*EWm-m>to{rVW5XGC$sRK>tkfP$W!MvjRaLlmRvH7}+B2pzOq*#}>d6fAx5uV4EZiQOMdHJd zTl`!`>SRw^z_9N2*dm4vx5t(+Y!<_;hrkh2+4@2R;RnaN|qSc^E zwyvn1n`I4G6=BH3CS1XY2E_lyD+|ur+Q)z;gViFI3|0$SGFUBU$sh!+R5@zNK}$xf zMJ*Yu7Pe%tTHKPsYJp1z<;X=|gd_K`kHJc&RSQ-!C`T(2(FnO`ehg4Dty+YV!D=B& z2CKy=8H6BZ8>1L#4N_UwASH9kQA%&=78m5rlB{ZRN@lGVr~tH$RJnO9OtCn00U{M~ zYmA09WFg9^Okg1@gNGFl7bp;*=G%3shE6j#Q&9R>`vEw1g;fw+V8~w1y=s(Jn4o!C9<(MqB#36|@UeR?sd^SwXu%Wd$vfiV1~KC8H3l zSXjuNzYG)_&+v^l@Xt!7-WtBF`R(GD6|@UrR?sejSwTw(BY#C8w{UCNvJ!3MmZfO9 z^DS|UG;NA%d+X%*WlmJVjRxt#WwUuQx*2s{-?9RwTGW$F96ul$kzempnrz14^7>Yv9zuK@Ak&WAdxpuC14d@Nt*A4%W=U`qOd=qNCa-gV$0ia}!iomEv` zy#wVF@h~hLTJ0baB~X}u*hSav)cIkc9i&Z|~f{v2VUzk2UMW*@~R3<{T}N2_ySZfX&E!fX>z?7 zE=HRU{LZZc`y7_4%2p|1R$0od@=WNugc&3XdHi6b)NyfD4%s-DnQ^`*`pe8XxmM%+ zYLf+wb5%CZHD;WJH2J#4I5(JavMt8BB^&3K#W=Si%OSG+4rDo%MK}D&9Q~J#)a*I* z-d&V~P$f;hj}{{#HIx2ZhQ3DVCSCM^0o|ulUx@)n=&nZ$2%kdRmIENHokY()VJF&& zbkH+)GCiF>ea?8F(CIJG0VFj4l19BmKO$k?cv==An2~i_pXUvjryL3mPQ|Oh+;;QwoegXFG7p zAR6V!DFZCpSJ{XbIZJ=5XlDULC$b{Q5?nYr!Ah>l$nmtSriJXz$vtV52UpOYsfMx_ z*(mVhEL~}oH)rW=DZ^zO7C+9?$*LrPDfyI^1z3~>adLYa6~ZO7rDgRjlm?vg35{yV zDbW`7D(+g@x{WwXbF1tKCOeXqL9SmjPL8mWn=^7_TGre`Zpq1EG^!O>5Xx*0^=oFM zpbclKN2A(tmS9U6I@qvu;4A@FC7qa(y0om5MM)ed`_QN^TtXdM*40Ak#woRFR1Z$6 zWl=xgMz)@_)U?V@V6t_zEWsk7uiyrox^#J>;DW>gZB>8!y07q#eBWPiMtO99e@5Ia z+7pXD7oZ(F(aWC;HISI4v8O&92-FFV^vyt^3d7V?J(#g2IM}n*P6oD)DpD_5I3~Wvoy1pn8h>v$mRUs10#APY_}N%EIMkr4A23H=HPX)oxzuNWb; zg2bc)A^ACR$^gQWXY^VI6WH~sMc|1^AaK?b@@SH9Ub&}SLk|xXqEU?m<;SsP^y3v% zLB@thW-}SQy^Q1=pw^>iL9L;FvsnG^)1=v;mep?# ztKVHRI*Zk>l(w47iu35UwSMzi{ce-4^I83FQLk)P4X2wVc>$~64HBBe>UW*K%VC7j zYb3_V>UWhmEn@Y%!s^!wxty~pf7i>_x_-^-`UiRRwd~5p+?6FOma^JiB&U|jwflzG zu9%dXZ&wN9k&twX>edM3knpI8ZdfIJfoe?s zjJFFub(nI3R;*)Gr{flCoxUHbs|slG21X72jmB(ZCx`MaY~qNIY+a91|E)kp*XZK! zK&Nu_5e=EW6V;{u+k`PZ_-_|j@IOR-cM6LT`jzh3B`k&De}K0AQTQ5y|2}$dH&BEc zd33;DVF?8Pz4Yl%Kw`mvpOA}$!9P-b04V4zT}zp?HalsfLrmI_^zdOO&1W0!afC@* z^c}tR3!{9XDMuM4bu;}pp9zCu=r<;;=LYIs$b`W#bestbUq{;(u>!1LL(iOK1<q@Aa$A2DfdrqD*^Od6Er36thGk#>8=q%BIPx1KW!l;j1Y zjQf(l|BDF&o4jJex{ss2Ze9=aEoPIAVFLGOIl?Rl9PjB+m zr^@3fp1##7e+To&(=#=d`<3V{neCxGE~vZFF0~n}8d>Oeb|@lKkq@ zZm~*Vm9os!iIfkA!g|t(&y;tOkmyO>x+|Bfge|V5;b5@F_;}?#BzU;egaln4GfbqR})I(V%S}}!dkZ8#?uHl|l z!z_!2Ib6dHtA=c@;Ra2};TkT<8ZOa@uepXx)NKjZaNeTf9IaRe6y=I@j`UKp@*JWY zS1LOT%Hc&;MQbdI)^SDoRz;h*qI{aLg)2HFD>_6YwsS>?sM}7iD9@tkCt9(aSLG*K zvX@bY@3Lw*V9{`pYuIYl@C(ev2V?E-= znrKz@#G>dKS2W(L=p|P)o+iBJiVU(M1C98HD>6{GO0MV&i=v_XD^#pu4W%W5irH#_ zRe_^QZdgt#*0B0m71RJ&@aRJmbigi@^^g_xpb@p0oWvf~tu~Vr+}WZaj#hYc4RN%@ zmurZzY6!Gw2<94ESv7=m4XtQG7}wBD*3gVbL~sqws9Q6xA>5)Nj8?P&igHC*m90Ke zv4$05Rn*p^s6AKYXI0dREApcWaa>VtSy62o(TyvrP2GBMMXp@Yaas}2E25(%2@D@D zg8Y8ao%i$yob|!axqKy;f1IWbVeB4CnlO~HmsL8luyCA4jO5}gshfd|f8$7Yjsd(P zm5YB(OU7~W&mGB_ubB7=7V#NeyyQs2rU0Bam5Y~X!gMbFE{M-$;%5V{OuVb6?w_+> zc6Tl_M(_>LGM{P5=31`Pk{qt(5@^X~TE4buS;DoP11-y#mgQW_IhsJYmJ^_5HPf<| z;fW`x+j@ov=Y#l-O#CJ;{wOWk!o?o~@!OdA?H2Jnx%i(z{B918!ZGaSD2QoTuTluxvp}CK`e`W@k9lK*d5hFKHa&ia#f*SWaFQz zYl4~Dj|G4*~IdVqtZ)8 zST&9o>sY?^e?vYY)nL)}k40A{*R_1316PF{n5v;nl}^Q!I68om&uIgV15@%jQv$MX zV+W8G-bYrIK&2XtyefgIf-%^MiW{w<`==T_+T2;j*AbbZZTZEpDf;&-tWZ4`_hs)F21%! zd^d~u9$Y*;EzZUF=HhiUp$`}DB#U>VK}lS^6LlNF#Vcj;0=++kix+6gP#`G3tW=Z1 zfewXq(@2Ya1ISnIeXS;bp#Xm|mMeHo6UH<47thq>b`ys}m@WpTa}Cd^+eAibDyhl( z7C?DO?@#3_9@3KOT*Y0hirE$wSzN_+tBM6&#dVsnkgF)Ms3@U9i@Ayt>b8`tIA>9D zmfl~^Rh*?IgsV7VRk7BhVm()J)T&}LS8#ZZfiWO^T|S${~TB}z4G zQJ-5?Xw-6t5Y?PKPyQ>@X)z2zjoeL;LDfQQ9$@xC;nGZ*gx;y(jCs5=+$LEUBGf4f%yJRe3C`{04`nt@k5ySWG-Hy314vW?;XhP5ls9jF8)1r8_mVP zb|C9hnfS3>{3}{Ao{N9xKn71>;?phSCvx!*9f;plCVmSAy&!%M6Tg>>-%Atn)Xqq7+(WSAL!EqY6m1_>>{tI`h$?Mle{{}jyuS!L+rSnygJN|+sLb*+3|bw z>Igf2M_>J-UM8SBG$&sTYrI~YXr15GunIg%w-=~)A))5CwBK>{dL(?cj{bcDPS8cV z;-tD25>~FJzn@aKLqh*m)b$KI@mxvc&#FJ;E5>u`UQn*(^yztZPgr*@qiu`TvteI~ z9{pXN3A+zVX-tV4p4kE55(mDfF_$gC6%H6_%ry&eodXMK%uNe$iv#m$%pD6*%7HmF z=AH$(&w)%D^QQ%Pz=7#BMzR2pI536Alv{u&9GFOBo>_qB9GF03URZ#?IFLqTUa2Dx z`kQ|FTHOS0KhTYD;3$w(dhu^{V}wSL-~U#>BCp@6Tcef)y@T_R_i*k=n*IaleJkPo z3SC;sGJj8x{mU{>8bSa1z%q9xFFpWME#%l5)m|Rp_=>~|a0*-2f+O%#x>pHDA$thD zr*Z`Te&ntSm>e8|sXlR4!+CQJoI~j}ECZCziDe*3^rFZzK`?|n6l+J}^hSKRAKUE6D_0+-L5oRaTdeUdL976>`-<^7SI$l6R=4bS8|LUDpmI8+kz+jp71AwD9P0{b3oUF4M`3OR{WH?>9c&FYrlrjtKfp;iEp5q9 z8q(5M{GW(bBg3B$$@A=O=-*v;#l!r==bFi61SEK?Ct6zCkDrLNG>M;RXz2icqNb&T_=$>^ z4&f&PEgcFc${d9%`|dDDNE%2^C3nu^}A3nj!vy zUXv~}#ZS;-k~LFoiRO@>XNn0zye4FdLxhwk^irny)S*t;ZKP1Xb^9yw zRAN8r_Mh;uaS!%bfHdp@xiL@lM=wa_Jh6^Y?*Z|gFZ%oJyT#ZKv4^CCKjwHu z`^*>T2tu1#BH<%ULHHqEBHdNX?62C`= z+-+p-V~}9b+CT?o$cICj@+0?0Hp88PeDYOJ)W0H1vn3vaJQHlw~ag z=UO2Jyx#z&SYU+=29`}@So9zho-99aWlLqK&Jb*6gU7kvTT=`w?@Lu4jfcgTpXH)O=t88Tw)3mLI>g^bvG0uV!Hl_$6(WF}ib$cU{QWW?4B zGGgll8L{<&jM%z>BTB=fS>&Zrj1hchlje)W&ek_1V&DylTXex9v5#Pdk`txS8&vvQ z%t4vSv*7_94|rj+vya|oZVz}Qf<0v8k=xnVFd0blV`(ztZ&<<*{|sI~X=bQo2!9Y=MxqSbIrar=ZqPBrhNq>$lOg;8 zM3&3oKZnFG72O6#*ulA-I~k&@35zv!sfNTE^bE17uilVcO->&}+8mO^wpQXSwnpNBt&IQx6ETbBc5Y)l zFTaz}ImYnOTs(`dwK!mFEDpql*cgi=wzlE`GZlURjrfa3sC%9qUL!UY=FTBc*NAno z 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

Ll;|2 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

8YusU(EufnVy6rfPv|pbW?& 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�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 #include @@ -1197,8 +1199,15 @@ struct { //TODO - once we get flexible here, do some extra condition checks.. whether memcards exist, etc. much like devices. switch(transaction->transaction) { - case eShockMemcardTransaction_Connect: return SHOCK_ERROR; //not supported yet - case eShockMemcardTransaction_Disconnect: return SHOCK_ERROR; //not supported yet + case eShockMemcardTransaction_Connect: + //cant connect when a memcard is already connected + if(!strcmp(FIO->MCPorts[portnum]->GetName(),"InputDevice_Memcard")) + return SHOCK_NOCANDO; + delete FIO->MCPorts[portnum]; //delete dummy + FIO->MCPorts[portnum] = Device_Memcard_Create(); + + case eShockMemcardTransaction_Disconnect: + return SHOCK_ERROR; //not supported yet case eShockMemcardTransaction_Write: FIO->MCPorts[portnum]->WriteNV((uint8*)transaction->buffer128k,0,128*1024); From 7b3461798f04bc2a85dea2b75abb69c849bde818 Mon Sep 17 00:00:00 2001 From: zeromus Date: Sat, 3 Oct 2015 17:44:22 -0500 Subject: [PATCH 08/24] changelog update --- Dist/changelog.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dist/changelog.txt b/Dist/changelog.txt index 9aff7cb33e..78bad25545 100644 --- a/Dist/changelog.txt +++ b/Dist/changelog.txt @@ -13,6 +13,7 @@ **Sequentially number screenshots taken in the same second **Finetune hotkey config dialog **Leniency fixes to cue loading +**Make on-screen watches position editable **Fix #461 - Repair autofire patterns **Fix #477 - Support key input over IPC for automation **Fix #490 - Ampersands in hex editor flakeout @@ -38,6 +39,7 @@ **Add movie.startsfromsavestate() and movie.startsfromsaveram(), movie.getheader(), movie.getcomments(), and movie.getsubtitles() **Add memorysavestate library **Fix bizstring.split() +**Fix crashes removing callbacks **Fix #469 - forms.newform() : add an onclosed callback optional parameter **Fix #463 - memory.readbyterange off-by-one **Fix #498 - fix quicknes lua rendering being offset incorrectly sometimes @@ -49,8 +51,9 @@ **Issue #481 - keep scroll bar position when resetting the current rom as opposed to changing to a new one *PSXHawk -**Update to mednafen 0.9.38.6 -**Fix loading of some .psf filess +**Update to mednafen 0.9.38.7 +**Support 0-2 pads/shocks and 0-2 memcards +**Fix loading of some .psf files **Add overscan clipping and deinterlacer options **Fix resolution management and PAR stuff for some PAL modes **Support .xml disc bundling tool as alternative to .m3u From 915abb9504bd1d08afa96c3856c63306a269c7e8 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sat, 3 Oct 2015 19:20:18 -0400 Subject: [PATCH 09/24] Stuff --- .../movie/tasproj/StateManagerState.cs | 15 +++++++++ .../movie/tasproj/TasStateManager.cs | 32 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs b/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs index c4012ac9d7..c1a6801f47 100644 --- a/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs +++ b/BizHawk.Client.Common/movie/tasproj/StateManagerState.cs @@ -1,4 +1,5 @@ using System; +using System.IO; namespace BizHawk.Client.Common { @@ -15,6 +16,20 @@ namespace BizHawk.Client.Common public int Frame { get; set; } + public void Write(BinaryWriter w) + { + w.Write(Frame); + w.Write(_state.Length); + w.Write(_state); + } + + public static StateManagerState Read(BinaryReader r, TasStateManager m) + { + int frame = r.ReadInt32(); + byte[] data = r.ReadBytes(r.ReadInt32()); + return new StateManagerState(m, data, frame); + } + public byte[] State { get diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 1858794af2..e40785fc73 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -520,6 +520,19 @@ namespace BizHawk.Client.Common bw.Write(kvp.Value.Length); bw.Write(kvp.Value.State); } + + bw.Write(currentBranch); + bw.Write(BranchStates.Count); + foreach (var s in BranchStates) + { + bw.Write(s.Key); + bw.Write(s.Value.Count); + foreach (var t in s.Value) + { + bw.Write(t.Key); + t.Value.Write(bw); + } + } } private List ExcludeStates() @@ -574,6 +587,25 @@ namespace BizHawk.Client.Common //Used += len; } //} + + currentBranch = br.ReadInt32(); + int c = br.ReadInt32(); + BranchStates = new SortedList>(c); + while (c > 0) + { + int key = br.ReadInt32(); + int c2 = br.ReadInt32(); + var list = new SortedList(c2); + while (c2 > 0) + { + int key2 = br.ReadInt32(); + var state = StateManagerState.Read(br, this); + list.Add(key2, state); + c2--; + } + BranchStates.Add(key, list); + c--; + } } public KeyValuePair GetStateClosestToFrame(int frame) From 33f1f8904e54e7683294b8e266eeb3ada05786d8 Mon Sep 17 00:00:00 2001 From: adelikat Date: Sat, 3 Oct 2015 20:30:10 -0400 Subject: [PATCH 10/24] meh --- BizHawk.Client.EmuHawk/MainForm.Designer.cs | 10 +- .../PSX/PSXControllerConfigNew.Designer.cs | 4 +- .../config/PSX/PSXControllerConfigNew.resx | 504 ++++++++++++++++++ 3 files changed, 512 insertions(+), 6 deletions(-) diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index 4cbf0f2b2e..cdff502530 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -2552,28 +2552,28 @@ // this.PSXControllerSettingsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.GameController; this.PSXControllerSettingsMenuItem.Name = "PSXControllerSettingsMenuItem"; - this.PSXControllerSettingsMenuItem.Size = new System.Drawing.Size(172, 22); - this.PSXControllerSettingsMenuItem.Text = "FrontIO Settings"; + this.PSXControllerSettingsMenuItem.Size = new System.Drawing.Size(234, 22); + this.PSXControllerSettingsMenuItem.Text = "Controller / Memcard Settings"; this.PSXControllerSettingsMenuItem.Click += new System.EventHandler(this.PSXControllerSettingsMenuItem_Click); // // PSXOptionsMenuItem // this.PSXOptionsMenuItem.Name = "PSXOptionsMenuItem"; - this.PSXOptionsMenuItem.Size = new System.Drawing.Size(172, 22); + this.PSXOptionsMenuItem.Size = new System.Drawing.Size(234, 22); this.PSXOptionsMenuItem.Text = "&Options"; this.PSXOptionsMenuItem.Click += new System.EventHandler(this.PSXOptionsMenuItem_Click); // // PSXDiscControlsMenuItem // this.PSXDiscControlsMenuItem.Name = "PSXDiscControlsMenuItem"; - this.PSXDiscControlsMenuItem.Size = new System.Drawing.Size(172, 22); + this.PSXDiscControlsMenuItem.Size = new System.Drawing.Size(234, 22); this.PSXDiscControlsMenuItem.Text = "&Disc Controls"; this.PSXDiscControlsMenuItem.Click += new System.EventHandler(this.PSXDiscControlsMenuItem_Click); // // PSXHashDiscsToolStripMenuItem // this.PSXHashDiscsToolStripMenuItem.Name = "PSXHashDiscsToolStripMenuItem"; - this.PSXHashDiscsToolStripMenuItem.Size = new System.Drawing.Size(172, 22); + this.PSXHashDiscsToolStripMenuItem.Size = new System.Drawing.Size(234, 22); this.PSXHashDiscsToolStripMenuItem.Text = "&Hash Discs"; this.PSXHashDiscsToolStripMenuItem.Click += new System.EventHandler(this.PSXHashDiscsToolStripMenuItem_Click); // diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs index 2460ef350f..2082194199 100644 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.Designer.cs @@ -28,6 +28,7 @@ /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PSXControllerConfigNew)); this.cbMultitap_1 = new System.Windows.Forms.CheckBox(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.lbl_p_1_4 = new System.Windows.Forms.Label(); @@ -439,10 +440,11 @@ this.Controls.Add(this.btnOK); this.Controls.Add(this.groupBox1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "PSXControllerConfigNew"; - this.Text = "PSX FrontIO Configuration"; + this.Text = "Controller / Memcard Configuration"; this.Load += new System.EventHandler(this.PSXControllerConfigNew_Load); this.groupBox1.ResumeLayout(false); this.groupBox1.PerformLayout(); diff --git a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx index 29dcb1b3a3..91f294b89b 100644 --- a/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx +++ b/BizHawk.Client.EmuHawk/config/PSX/PSXControllerConfigNew.resx @@ -117,4 +117,508 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA + BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ + AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm + AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA + AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP// + /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA + AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw + AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA + AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ + AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA + AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE + AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3 + dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH + x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI + cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI + h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA + AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH + eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA + AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA + AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA + AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA + AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI + h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA + yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA + AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + ////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB + /AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA////////////////////////// + //8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA + d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI + yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH + d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/ + /wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP// + /wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA + AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI + iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA + AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP// + AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8 + PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF + RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU + VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP + UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ + WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s + awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr + agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4 + dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf + TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5 + +gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC + ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh + oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP + kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj + jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk + owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1 + swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr + 9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme + VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE + AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc + XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55 + eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg + YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51 + dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz + dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz + dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM + 5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO + jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A + gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud + iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc + mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer + qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv + rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2 + tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV + 3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa + mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc + tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA + AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882 + AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP + z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb + orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR + l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH + ///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA + AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA + AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2 + dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15 + eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+ + fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek + VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P + jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK + iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ + mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi + oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8 + ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf + 8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA + AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy + NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA + PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM + mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ + hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv + YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP// + /wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA + BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA + AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw + cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K + igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS + kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay + sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS + 0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA + AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb + Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5 + AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA + AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS + U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP + T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY + V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw + cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4 + uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA + AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6 + ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8 + vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA + ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT + kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck + pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6 + OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk + ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br + auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0 + c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg + n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA + AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA + vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg + nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA + AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO + zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv + rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH + RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF + RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+ + vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB + vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX + V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i + pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/ + vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv + L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z + sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9 + uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e + nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6 + t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV + lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6 + u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC + gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej + hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y + sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T + k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn + JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC + QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK + StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/ + QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+ + PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L + S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ + SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ + Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2 + NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km + Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af//// + AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA + B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA + AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA + AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB// + AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP// + /////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA + AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q + av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw + cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1 + dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4 + ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+ + Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA + AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA + AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc + HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A + f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z + sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui + of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP + z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB + v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8 + vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ + x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O + Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK + yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz + dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc + 9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI + zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw + t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il + o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX + V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc + XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6 + OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD + ///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4 + D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA + ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv + 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 + + \ No newline at end of file From e5de3bdaa7b82d79c9ebe1451d0e787bf0a03d4d Mon Sep 17 00:00:00 2001 From: zeromus Date: Sat, 3 Oct 2015 20:26:30 -0500 Subject: [PATCH 11/24] make aboutboxes more informational while simultaneously removing svn revision --- BizHawk.Client.EmuHawk/AboutBox.Designer.cs | 89 ++++++++++++++++----- BizHawk.Client.EmuHawk/AboutBox.cs | 7 +- BizHawk.Client.EmuHawk/BizBox.Designer.cs | 51 +++++++++--- BizHawk.Client.EmuHawk/BizBox.cs | 16 +++- Version/VersionInfo.cs | 2 +- 5 files changed, 130 insertions(+), 35 deletions(-) diff --git a/BizHawk.Client.EmuHawk/AboutBox.Designer.cs b/BizHawk.Client.EmuHawk/AboutBox.Designer.cs index 6ce6402235..702733471c 100644 --- a/BizHawk.Client.EmuHawk/AboutBox.Designer.cs +++ b/BizHawk.Client.EmuHawk/AboutBox.Designer.cs @@ -35,7 +35,6 @@ this.timer1 = new System.Windows.Forms.Timer(this.components); this.label3 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label(); - this.HR = new BizHawk.Client.EmuHawk.HorizontalLine(); this.label5 = new System.Windows.Forms.Label(); this.mom2 = new System.Windows.Forms.PictureBox(); this.pictureBox2 = new System.Windows.Forms.PictureBox(); @@ -43,9 +42,14 @@ this.pictureBox4 = new System.Windows.Forms.PictureBox(); this.pictureBox3 = new System.Windows.Forms.PictureBox(); this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.pictureBox5 = new BizHawk.Client.EmuHawk.MyViewportPanel(); this.CloseBtn = new System.Windows.Forms.Button(); this.btnBizBox = new System.Windows.Forms.Button(); + this.tbBranch = new System.Windows.Forms.TextBox(); + this.tbCommit = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.pictureBox5 = new BizHawk.Client.EmuHawk.MyViewportPanel(); + this.HR = new BizHawk.Client.EmuHawk.HorizontalLine(); ((System.ComponentModel.ISupportInitialize)(this.mom2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.mom1)).BeginInit(); @@ -100,15 +104,6 @@ this.label4.TabIndex = 5; this.label4.Text = "(LEVAR BURTON\r\nCAMEO)"; // - // HR - // - this.HR.Font = new System.Drawing.Font("Microsoft Sans Serif", 26.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.HR.Location = new System.Drawing.Point(349, 213); - this.HR.Name = "HR"; - this.HR.Size = new System.Drawing.Size(158, 2); - this.HR.TabIndex = 4; - this.HR.Text = "COPYRITE 2001"; - // // label5 // this.label5.AutoSize = true; @@ -178,17 +173,9 @@ this.pictureBox1.TabIndex = 0; this.pictureBox1.TabStop = false; // - // pictureBox5 - // - this.pictureBox5.Enabled = false; - this.pictureBox5.Location = new System.Drawing.Point(71, 223); - this.pictureBox5.Name = "pictureBox5"; - this.pictureBox5.Size = new System.Drawing.Size(376, 48); - this.pictureBox5.TabIndex = 15; - this.pictureBox5.TabStop = false; - // // CloseBtn // + this.CloseBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.CloseBtn.Location = new System.Drawing.Point(424, 462); this.CloseBtn.Name = "CloseBtn"; this.CloseBtn.Size = new System.Drawing.Size(75, 23); @@ -200,6 +187,7 @@ // // btnBizBox // + this.btnBizBox.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnBizBox.Location = new System.Drawing.Point(-4, -3); this.btnBizBox.Name = "btnBizBox"; this.btnBizBox.Size = new System.Drawing.Size(75, 23); @@ -208,12 +196,69 @@ this.btnBizBox.UseVisualStyleBackColor = true; this.btnBizBox.Click += new System.EventHandler(this.btnBizBox_Click); // + // tbBranch + // + this.tbBranch.Location = new System.Drawing.Point(49, 476); + this.tbBranch.Name = "tbBranch"; + this.tbBranch.ReadOnly = true; + this.tbBranch.Size = new System.Drawing.Size(100, 20); + this.tbBranch.TabIndex = 20; + // + // tbCommit + // + this.tbCommit.Location = new System.Drawing.Point(203, 476); + this.tbCommit.Name = "tbCommit"; + this.tbCommit.ReadOnly = true; + this.tbCommit.Size = new System.Drawing.Size(100, 20); + this.tbCommit.TabIndex = 20; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(2, 479); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(44, 13); + this.label6.TabIndex = 21; + this.label6.Text = "Branch:"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(155, 479); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(44, 13); + this.label7.TabIndex = 22; + this.label7.Text = "Commit:"; + // + // pictureBox5 + // + this.pictureBox5.Enabled = false; + this.pictureBox5.Location = new System.Drawing.Point(71, 223); + this.pictureBox5.Name = "pictureBox5"; + this.pictureBox5.Size = new System.Drawing.Size(376, 48); + this.pictureBox5.TabIndex = 15; + this.pictureBox5.TabStop = false; + // + // HR + // + this.HR.Font = new System.Drawing.Font("Microsoft Sans Serif", 26.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.HR.Location = new System.Drawing.Point(349, 213); + this.HR.Name = "HR"; + this.HR.Size = new System.Drawing.Size(158, 2); + this.HR.TabIndex = 4; + this.HR.Text = "COPYRITE 2001"; + // // AboutBox // this.AcceptButton = this.CloseBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.CloseBtn; this.ClientSize = new System.Drawing.Size(519, 497); + this.Controls.Add(this.label7); + this.Controls.Add(this.label6); + this.Controls.Add(this.tbCommit); + this.Controls.Add(this.tbBranch); this.Controls.Add(this.btnBizBox); this.Controls.Add(this.CloseBtn); this.Controls.Add(this.pictureBox5); @@ -268,5 +313,9 @@ private System.Windows.Forms.PictureBox pictureBox1; private System.Windows.Forms.Button CloseBtn; private System.Windows.Forms.Button btnBizBox; + private System.Windows.Forms.TextBox tbBranch; + private System.Windows.Forms.TextBox tbCommit; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/AboutBox.cs b/BizHawk.Client.EmuHawk/AboutBox.cs index 0aec2ff897..1c6b3be5de 100644 --- a/BizHawk.Client.EmuHawk/AboutBox.cs +++ b/BizHawk.Client.EmuHawk/AboutBox.cs @@ -37,6 +37,9 @@ namespace BizHawk.Client.EmuHawk pictureBox2.BringToFront(); pictureBox1.BringToFront(); pictureBox5.Visible = false; + + tbBranch.Text = SubWCRev.GIT_BRANCH; + tbCommit.Text = SubWCRev.GIT_SHORTHASH; } protected override void OnClosed(EventArgs e) @@ -160,9 +163,9 @@ namespace BizHawk.Client.EmuHawk private void AboutBox_Load(object sender, EventArgs e) { #if DEBUG - Text = "BizHawk Developer Build (DEBUG MODE) GIT " + SubWCRev.GIT_BRANCH + "-" + SubWCRev.SVN_REV + "#" + SubWCRev.GIT_SHORTHASH; + Text = "BizHawk Developer Build (DEBUG MODE) GIT " + SubWCRev.GIT_BRANCH + "#" + SubWCRev.GIT_SHORTHASH; #else - Text = "BizHawk Developer Build (RELEASE MODE) GIT " + SubWCRev.GIT_BRANCH + "-"+SubWCRev.SVN_REV + "#" + SubWCRev.GIT_SHORTHASH; + Text = "BizHawk Developer Build (RELEASE MODE) GIT " + SubWCRev.GIT_BRANCH + "#" + SubWCRev.GIT_SHORTHASH; #endif if (DateTime.Now.Month == 12) if (DateTime.Now.Day > 17 && DateTime.Now.Day <= 25) diff --git a/BizHawk.Client.EmuHawk/BizBox.Designer.cs b/BizHawk.Client.EmuHawk/BizBox.Designer.cs index 7484442a31..2cc054603d 100644 --- a/BizHawk.Client.EmuHawk/BizBox.Designer.cs +++ b/BizHawk.Client.EmuHawk/BizBox.Designer.cs @@ -36,7 +36,6 @@ this.label2 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label(); - this.VersionLabel = new System.Windows.Forms.Label(); this.label5 = new System.Windows.Forms.Label(); this.label6 = new System.Windows.Forms.Label(); this.label7 = new System.Windows.Forms.Label(); @@ -44,6 +43,9 @@ this.label37 = new System.Windows.Forms.Label(); this.CoreInfoPanel = new System.Windows.Forms.Panel(); this.textBox1 = new System.Windows.Forms.TextBox(); + this.VersionLabel = new System.Windows.Forms.Label(); + this.btnCopyHash = new System.Windows.Forms.Button(); + this.linkLabel2 = new System.Windows.Forms.LinkLabel(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.SuspendLayout(); // @@ -119,14 +121,6 @@ this.label4.TabIndex = 6; this.label4.Text = "A multi-Platform Emulator"; // - // VersionLabel - // - this.VersionLabel.AutoSize = true; - this.VersionLabel.Location = new System.Drawing.Point(198, 52); - this.VersionLabel.Name = "VersionLabel"; - this.VersionLabel.Size = new System.Drawing.Size(0, 13); - this.VersionLabel.TabIndex = 7; - // // label5 // this.label5.AutoSize = true; @@ -196,6 +190,39 @@ this.textBox1.TabIndex = 16; this.textBox1.Text = "jabo_direct3d8_patched.dll is distributed with the special permission of the auth" + "or."; + // + // VersionLabel + // + this.VersionLabel.AutoSize = true; + this.VersionLabel.Location = new System.Drawing.Point(198, 52); + this.VersionLabel.Name = "VersionLabel"; + this.VersionLabel.Size = new System.Drawing.Size(0, 13); + this.VersionLabel.TabIndex = 7; + // + // btnCopyHash + // + this.btnCopyHash.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.btnCopyHash.AutoSize = true; + this.btnCopyHash.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.btnCopyHash.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.Duplicate; + this.btnCopyHash.Location = new System.Drawing.Point(12, 505); + this.btnCopyHash.Name = "btnCopyHash"; + this.btnCopyHash.Size = new System.Drawing.Size(22, 22); + this.btnCopyHash.TabIndex = 18; + this.btnCopyHash.UseVisualStyleBackColor = true; + this.btnCopyHash.Click += new System.EventHandler(this.btnCopyHash_Click); + // + // linkLabel2 + // + this.linkLabel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.linkLabel2.AutoSize = true; + this.linkLabel2.Location = new System.Drawing.Point(40, 509); + this.linkLabel2.Name = "linkLabel2"; + this.linkLabel2.Size = new System.Drawing.Size(100, 13); + this.linkLabel2.TabIndex = 19; + this.linkLabel2.TabStop = true; + this.linkLabel2.Text = "Commit #XXXXXXX"; + this.linkLabel2.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel2_LinkClicked); // // BizBox // @@ -204,6 +231,8 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.OK; this.ClientSize = new System.Drawing.Size(448, 536); + this.Controls.Add(this.linkLabel2); + this.Controls.Add(this.btnCopyHash); this.Controls.Add(this.textBox1); this.Controls.Add(this.CoreInfoPanel); this.Controls.Add(this.label37); @@ -240,7 +269,6 @@ private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label4; - private System.Windows.Forms.Label VersionLabel; private System.Windows.Forms.Label label5; private System.Windows.Forms.Label label6; private System.Windows.Forms.Label label7; @@ -248,5 +276,8 @@ private System.Windows.Forms.Label label37; private System.Windows.Forms.Panel CoreInfoPanel; private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label VersionLabel; + private System.Windows.Forms.Button btnCopyHash; + private System.Windows.Forms.LinkLabel linkLabel2; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/BizBox.cs b/BizHawk.Client.EmuHawk/BizBox.cs index cbef7d3edc..d32b3a65ed 100644 --- a/BizHawk.Client.EmuHawk/BizBox.cs +++ b/BizHawk.Client.EmuHawk/BizBox.cs @@ -28,11 +28,11 @@ namespace BizHawk.Client.EmuHawk { if (VersionInfo.DeveloperBuild) { - Text = " BizHawk (GIT " + SubWCRev.GIT_BRANCH + "-" + SubWCRev.SVN_REV + "#" + SubWCRev.GIT_SHORTHASH + ")"; + Text = " BizHawk (GIT " + SubWCRev.GIT_BRANCH + "#" + SubWCRev.GIT_SHORTHASH + ")"; } else { - Text = "Version " + VersionInfo.MAINVERSION + " (GIT " + SubWCRev.GIT_BRANCH + "-" + SubWCRev.SVN_REV + "#" + SubWCRev.GIT_SHORTHASH + ")"; + Text = "Version " + VersionInfo.MAINVERSION + " (GIT " + SubWCRev.GIT_BRANCH + "#" + SubWCRev.GIT_SHORTHASH + ")"; } VersionLabel.Text = "Version " + VersionInfo.MAINVERSION + " " + VersionInfo.RELEASEDATE; @@ -55,6 +55,18 @@ namespace BizHawk.Client.EmuHawk }); } + + linkLabel2.Text = "Commit # " + SubWCRev.GIT_SHORTHASH; + } + + private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/TASVideos/BizHawk/commit/" + SubWCRev.GIT_SHORTHASH); + } + + private void btnCopyHash_Click(object sender, EventArgs e) + { + System.Windows.Forms.Clipboard.SetText(SubWCRev.GIT_SHORTHASH); } } } diff --git a/Version/VersionInfo.cs b/Version/VersionInfo.cs index e08f42ee47..59a2dfdef9 100644 --- a/Version/VersionInfo.cs +++ b/Version/VersionInfo.cs @@ -7,6 +7,6 @@ static class VersionInfo public static string GetEmuVersion() { - return DeveloperBuild ? "SVN " + SubWCRev.SVN_REV : ("Version " + MAINVERSION); + return DeveloperBuild ? ("GIT " + SubWCRev.GIT_BRANCH + "#" + SubWCRev.GIT_SHORTHASH) : ("Version " + MAINVERSION); } } From 4625bdce0fbf2379452e7de98d197abf68bf07ea Mon Sep 17 00:00:00 2001 From: feos Date: Sun, 4 Oct 2015 13:39:14 +0300 Subject: [PATCH 12/24] tastudio: a bunch of fixes. - update branches per RefreshDialog() - clear selection per right click if it's beyond movie length - fix crash when load branch is called with null selection (can't stably reproduce, but it happens) - assign guid to branches from the right place. this required setting some statics, don't know if it was right, but it works. --- BizHawk.Client.Common/movie/tasproj/TasBranch.cs | 8 ++++++++ BizHawk.Client.Common/movie/tasproj/TasMovie.cs | 10 ++-------- .../movie/tasproj/TasStateManager.cs | 2 +- .../tools/TAStudio/BookmarksBranchesBox.cs | 7 +++---- .../tools/TAStudio/TAStudio.ListView.cs | 14 ++++++++++++-- BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs | 3 +++ 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs index 47e416a9e4..fcb03c296b 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs @@ -9,6 +9,14 @@ namespace BizHawk.Client.Common { public class TasBranch { + public TasBranch() + { + do + { + UniqueIdentifier = Guid.NewGuid(); + } while (TasMovie.BranchIndexByHash(UniqueIdentifier.GetHashCode()) != -1); + } + public int Frame { get; set; } public byte[] CoreData { get; set; } public List InputLog { get; set; } diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 960723ccd6..b50eec82e2 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -22,7 +22,7 @@ namespace BizHawk.Client.Common private readonly Dictionary InputStateCache = new Dictionary(); public readonly List VerificationLog = new List(); // For movies that do not begin with power-on, this is the input required to get into the initial state - private readonly TasBranchCollection Branches = new TasBranchCollection(); + public static readonly TasBranchCollection Branches = new TasBranchCollection(); private BackgroundWorker _progressReportWorker = null; public void NewBGWorker(BackgroundWorker newWorker) @@ -85,7 +85,7 @@ namespace BizHawk.Client.Common public TasBranch GetBranch(int index) { return Branches[index]; } public int BranchHashByIndex(int index) { return Branches[index].UniqueIdentifier.GetHashCode(); } - public int BranchIndexByHash(int hash) + public static int BranchIndexByHash(int hash) { TasBranch branch = Branches.Where(b => b.UniqueIdentifier.GetHashCode() == hash).SingleOrDefault(); if (branch == null) @@ -524,12 +524,6 @@ namespace BizHawk.Client.Common public void AddBranch(TasBranch branch) { - // before adding, make sure guid hash is unique too, we can't afford branch id clashes - do - { - branch.UniqueIdentifier = Guid.NewGuid(); - } while (BranchIndexByHash(branch.UniqueIdentifier.GetHashCode()) != -1); - Branches.Add(branch); TasStateManager.AddBranch(); Changes = true; diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index e40785fc73..d0a804b4cf 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -710,7 +710,7 @@ namespace BizHawk.Client.Common SortedList stateList = BranchStates[frame]; for (int i = 0; i < _movie.BranchCount; i++) { - if (i == _movie.BranchIndexByHash(branchHash)) + if (i == TasMovie.BranchIndexByHash(branchHash)) continue; if (stateList != null && stateList.ContainsKey(i) && stateList[i] == stateToMatch) diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index 98394e1793..ada9c4147a 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -141,12 +141,11 @@ namespace BizHawk.Client.EmuHawk private void LoadSelectedBranch() { - int index = BranchView.SelectedRows.First(); - //if (CurrentBranch == index) // if the current branch was edited, we should allow loading it. some day there might be a proper check - // return; - if (SelectedBranch != null) { + int index = BranchView.SelectedRows.First(); + //if (CurrentBranch == index) // if the current branch was edited, we should allow loading it. some day there might be a proper check + // return; CurrentBranch = index; LoadBranch(SelectedBranch); BranchView.Refresh(); diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 81118050ef..81d249fda8 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -271,7 +271,6 @@ namespace BizHawk.Client.EmuHawk { CurrentTasMovie.Markers.Add(TasView.LastSelectedIndex.Value, ""); RefreshDialog(); - } else if (columnName != CursorColumnName) // TODO: what about float? { @@ -541,7 +540,18 @@ namespace BizHawk.Client.EmuHawk { if (e.Button == MouseButtons.Right && !TasView.IsPointingAtColumnHeader && !_supressContextMenu) { - RightClickMenu.Show(TasView, e.X, e.Y); + if (Global.MovieSession.Movie.FrameCount < TasView.SelectedRows.Max()) + { + // trying to be smart here + // if a loaded branch log is shorter than selection, keep selection until you attempt to call context menu + // you might need it when you load again the branch where this frame exists + TasView.DeselectAll(); + RefreshTasView(); + } + else + { + RightClickMenu.Show(TasView, e.X, e.Y); + } } else if (e.Button == MouseButtons.Left) { diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 85ced34cad..2995d1cc8e 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -594,6 +594,9 @@ namespace BizHawk.Client.EmuHawk if (MarkerControl != null) MarkerControl.UpdateValues(); + if (BookMarkControl != null) + BookMarkControl.UpdateValues(); + if (undoForm != null && !undoForm.IsDisposed) undoForm.UpdateValues(); } From 9f67d8e59b66be55e34a528ed6a05aaefcf41bfb Mon Sep 17 00:00:00 2001 From: adelikat Date: Sun, 4 Oct 2015 10:43:36 -0400 Subject: [PATCH 13/24] TasBranchCollection - encapsulate unique identifier logic into TasBranchCollection --- .../movie/tasproj/TasBranch.cs | 22 ++++++++++++------- .../movie/tasproj/TasMovie.cs | 4 ++-- .../movie/tasproj/TasStateManager.cs | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs index fcb03c296b..f5a42191e6 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using Newtonsoft.Json; using BizHawk.Bizware.BizwareGL; @@ -9,14 +10,6 @@ namespace BizHawk.Client.Common { public class TasBranch { - public TasBranch() - { - do - { - UniqueIdentifier = Guid.NewGuid(); - } while (TasMovie.BranchIndexByHash(UniqueIdentifier.GetHashCode()) != -1); - } - public int Frame { get; set; } public byte[] CoreData { get; set; } public List InputLog { get; set; } @@ -30,6 +23,19 @@ namespace BizHawk.Client.Common public class TasBranchCollection : List { + public new void Add(TasBranch item) + { + var currentHashes = this.Select(b => b.UniqueIdentifier.GetHashCode()).ToList(); + + // TODO: loop until this is unique + if (currentHashes.Contains(item.UniqueIdentifier.GetHashCode())) + { + item.UniqueIdentifier = Guid.NewGuid(); + } + + base.Add(item); + } + public void Save(BinaryStateSaver bs) { var nheader = new IndexedStateLump(BinaryStateLump.BranchHeader); diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index b50eec82e2..a57ab51cd4 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -22,7 +22,7 @@ namespace BizHawk.Client.Common private readonly Dictionary InputStateCache = new Dictionary(); public readonly List VerificationLog = new List(); // For movies that do not begin with power-on, this is the input required to get into the initial state - public static readonly TasBranchCollection Branches = new TasBranchCollection(); + public readonly TasBranchCollection Branches = new TasBranchCollection(); private BackgroundWorker _progressReportWorker = null; public void NewBGWorker(BackgroundWorker newWorker) @@ -85,7 +85,7 @@ namespace BizHawk.Client.Common public TasBranch GetBranch(int index) { return Branches[index]; } public int BranchHashByIndex(int index) { return Branches[index].UniqueIdentifier.GetHashCode(); } - public static int BranchIndexByHash(int hash) + public int BranchIndexByHash(int hash) { TasBranch branch = Branches.Where(b => b.UniqueIdentifier.GetHashCode() == hash).SingleOrDefault(); if (branch == null) diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index d0a804b4cf..e40785fc73 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -710,7 +710,7 @@ namespace BizHawk.Client.Common SortedList stateList = BranchStates[frame]; for (int i = 0; i < _movie.BranchCount; i++) { - if (i == TasMovie.BranchIndexByHash(branchHash)) + if (i == _movie.BranchIndexByHash(branchHash)) continue; if (stateList != null && stateList.ContainsKey(i) && stateList[i] == stateToMatch) From 2a8578c74f0dfab359f8b462782b249555f5f833 Mon Sep 17 00:00:00 2001 From: feos Date: Sun, 4 Oct 2015 18:00:04 +0300 Subject: [PATCH 14/24] tastudio: keep the same identifier for updated branch. ban context menu when there's no selection. attempts to fix mysterious crashes in TasStateManager. --- BizHawk.Client.Common/movie/tasproj/TasMovie.cs | 1 + BizHawk.Client.Common/movie/tasproj/TasStateManager.cs | 4 ++-- BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index a57ab51cd4..85b22baa90 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -539,6 +539,7 @@ namespace BizHawk.Client.Common public void UpdateBranch(TasBranch old, TasBranch newBranch) { int index = Branches.IndexOf(old); + newBranch.UniqueIdentifier = old.UniqueIdentifier; Branches[index] = newBranch; TasStateManager.UpdateBranch(index); Changes = true; diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index e40785fc73..1ad2ce7a0a 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -265,7 +265,7 @@ namespace BizHawk.Client.Common { if (BranchStates.Any()) { - var kvp = BranchStates.ElementAt(1); + var kvp = BranchStates.Count() > 1 ? BranchStates.ElementAt(1) : BranchStates.ElementAt(0); shouldRemove.X = kvp.Key; shouldRemove.Y = kvp.Value.Keys[0]; } @@ -356,7 +356,7 @@ namespace BizHawk.Client.Common { if (branch == -1) accessed.Remove(States[frame]); - else + else if (accessed.Contains(BranchStates[frame][branch])) accessed.Remove(BranchStates[frame][branch]); StateManagerState state; diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs index 81d249fda8..aef3c4b631 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.ListView.cs @@ -538,7 +538,7 @@ namespace BizHawk.Client.EmuHawk private void TasView_MouseUp(object sender, MouseEventArgs e) { - if (e.Button == MouseButtons.Right && !TasView.IsPointingAtColumnHeader && !_supressContextMenu) + if (e.Button == MouseButtons.Right && !TasView.IsPointingAtColumnHeader && !_supressContextMenu && TasView.SelectedRows.Any()) { if (Global.MovieSession.Movie.FrameCount < TasView.SelectedRows.Max()) { From 89d919e6a9139fed2d38769f1daab81ec67b7a2d Mon Sep 17 00:00:00 2001 From: feos Date: Sun, 4 Oct 2015 21:10:40 +0300 Subject: [PATCH 15/24] tastudio: loop guid dup check. don't remove marker 0. update when going to frame 0. don't check hasDuplicate against current states if branch is not current (what was it for at all?). releases Used per branch removal. --- BizHawk.Client.Common/movie/tasproj/TasBranch.cs | 7 ++----- BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs | 2 ++ BizHawk.Client.Common/movie/tasproj/TasStateManager.cs | 4 ++-- .../tools/TAStudio/TAStudio.Navigation.cs | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs index f5a42191e6..80f0d720dd 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs @@ -27,11 +27,8 @@ namespace BizHawk.Client.Common { var currentHashes = this.Select(b => b.UniqueIdentifier.GetHashCode()).ToList(); - // TODO: loop until this is unique - if (currentHashes.Contains(item.UniqueIdentifier.GetHashCode())) - { - item.UniqueIdentifier = Guid.NewGuid(); - } + do item.UniqueIdentifier = Guid.NewGuid(); + while (currentHashes.Contains(item.UniqueIdentifier.GetHashCode())); base.Add(item); } diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs b/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs index 1ecce08ea7..41fca12525 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovieMarker.cs @@ -218,6 +218,8 @@ namespace BizHawk.Client.Common { if (this[i].Frame >= startFrame) { + if (i == 0) + continue; _movie.ChangeLog.AddMarkerChange(null, this[i].Frame, this[i].Message); RemoveAt(i); deletedCount++; diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 1ad2ce7a0a..9811f9d2e4 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -698,8 +698,8 @@ namespace BizHawk.Client.Common if (!BranchStates[frame].ContainsKey(branchHash)) return -2; stateToMatch = BranchStates[frame][branchHash]; - if (States.ContainsKey(frame) && States[frame] == stateToMatch) - return -1; + //if (States.ContainsKey(frame) && States[frame] == stateToMatch) + // return -1; } // there's no state for that frame at all diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs index 88899f8814..ddeb65f5ce 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Navigation.cs @@ -39,7 +39,7 @@ namespace BizHawk.Client.EmuHawk MaybeFollowCursor(); - return; + //return; seriously? well, maybe it's for some insane speedup, but it skipped updating when putting playback to frame zero. } else // Emulate to a future frame { From 87dd32eeaccca680b2349311b7eda702d8a5a487 Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 5 Oct 2015 00:24:41 +0300 Subject: [PATCH 16/24] tastudio: found another bomb set by picking branch states by index. two actually. --- BizHawk.Client.Common/movie/tasproj/TasStateManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 9811f9d2e4..8b13eb284a 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -289,7 +289,7 @@ namespace BizHawk.Client.Common return _movie.Markers.IsMarker(States[frame].Frame + 1); else { - if (_movie.GetBranch(branch).Markers == null) + if (_movie.GetBranch(_movie.BranchIndexByHash(branch)).Markers == null) return _movie.Markers.IsMarker(States[frame].Frame + 1); else return _movie.GetBranch(branch).Markers.Any(m => m.Frame + 1 == frame); @@ -727,7 +727,10 @@ namespace BizHawk.Client.Common if (!States.ContainsValue(s)) { if (BranchStates.ContainsKey(s.Frame)) - ret.Y = BranchStates[s.Frame].Values.IndexOf(s); + { + int index = BranchStates[s.Frame].Values.IndexOf(s); + ret.Y = BranchStates[s.Frame].Keys.ElementAt(index); + } if (ret.Y == -1) return new Point(-1, -2); } From 31e476a3cd8d4c8f876e1330a3f53de16e3ec4c1 Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 5 Oct 2015 19:08:21 +0300 Subject: [PATCH 17/24] tastudio: load projects without branch state info. fix some other crashes, kill selection when loading a file. --- .../movie/tasproj/TasMovie.cs | 17 +++++++++++-- .../movie/tasproj/TasStateManager.cs | 3 +++ .../tools/TAStudio/BookmarksBranchesBox.cs | 25 +++++++++++-------- .../tools/TAStudio/TAStudio.cs | 6 ++++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index 85b22baa90..a5a99e3894 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -82,8 +82,21 @@ namespace BizHawk.Client.Common public bool BindMarkersToInput { get; set; } public bool UseInputCache { get; set; } public int BranchCount { get { return Branches.Count; } } - public TasBranch GetBranch(int index) { return Branches[index]; } - public int BranchHashByIndex(int index) { return Branches[index].UniqueIdentifier.GetHashCode(); } + public TasBranch GetBranch(int index) + { + if (index >= Branches.Count) + return null; // are we allowed? + else + return Branches[index]; + } + + public int BranchHashByIndex(int index) + { + if (index >= Branches.Count) + return -1; + else + return Branches[index].UniqueIdentifier.GetHashCode(); + } public int BranchIndexByHash(int hash) { diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 8b13eb284a..86e572ff12 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -588,6 +588,9 @@ namespace BizHawk.Client.Common } //} + if (br.PeekChar() == -1) // at least don't crash when loading an old project + return; + currentBranch = br.ReadInt32(); int c = br.ReadInt32(); BranchStates = new SortedList>(c); diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index ada9c4147a..f7692ea626 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -98,19 +98,22 @@ namespace BizHawk.Client.EmuHawk private void QueryItemBkColor(int index, InputRoll.RollColumn column, ref Color color) { - var record = Tastudio.CurrentTasMovie[GetBranch(index).Frame]; - - if (index == CurrentBranch) - color = TAStudio.CurrentFrame_InputLog; // SystemColors.HotTrack; - else if (record.Lagged.HasValue) + TasBranch branch = GetBranch(index); + if (branch != null) { - if (record.Lagged.Value) + var record = Tastudio.CurrentTasMovie[branch.Frame]; + if (index == CurrentBranch) + color = TAStudio.CurrentFrame_InputLog; // SystemColors.HotTrack; + else if (record.Lagged.HasValue) { - color = TAStudio.LagZone_InputLog; - } - else - { - color = TAStudio.GreenZone_InputLog; + if (record.Lagged.Value) + { + color = TAStudio.LagZone_InputLog; + } + else + { + color = TAStudio.GreenZone_InputLog; + } } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 2995d1cc8e..444af2c4fc 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -432,8 +432,12 @@ namespace BizHawk.Client.EmuHawk if (!HandleMovieLoadStuff(newMovie)) return false; + + // clear all selections + TasView.DeselectAll(); + BookMarkControl.Restart(); + MarkerControl.Restart(); - BookMarkControl.UpdateValues(); RefreshDialog(); return true; } From e0c2e43e48777af5e46b3a6443002bd9dc80c991 Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 5 Oct 2015 22:11:45 +0300 Subject: [PATCH 18/24] tastudio: add an option for (not) using branch states in tasproj. clueless about what should go in its description. fixed new guid being assigned to branches loaded from the project. --- .../movie/tasproj/TasBranch.cs | 9 +- .../movie/tasproj/TasStateManager.cs | 97 ++++++++++--------- .../movie/tasproj/TasStateManagerSettings.cs | 17 ++++ .../TAStudio/GreenzoneSettings.Designer.cs | 38 +++++--- .../tools/TAStudio/GreenzoneSettings.cs | 6 ++ 5 files changed, 107 insertions(+), 60 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs index 80f0d720dd..b290831ff5 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs @@ -25,10 +25,13 @@ namespace BizHawk.Client.Common { public new void Add(TasBranch item) { - var currentHashes = this.Select(b => b.UniqueIdentifier.GetHashCode()).ToList(); + if (item.UniqueIdentifier == Guid.Empty) + { + var currentHashes = this.Select(b => b.UniqueIdentifier.GetHashCode()).ToList(); - do item.UniqueIdentifier = Guid.NewGuid(); - while (currentHashes.Contains(item.UniqueIdentifier.GetHashCode())); + do item.UniqueIdentifier = Guid.NewGuid(); + while (currentHashes.Contains(item.UniqueIdentifier.GetHashCode())); + } base.Add(item); } diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index 86e572ff12..a3fb105333 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -503,38 +503,6 @@ namespace BizHawk.Client.Common MaybeRemoveStates(); } - // TODO: save/load BranchStates - public void Save(BinaryWriter bw) - { - List noSave = ExcludeStates(); - - bw.Write(States.Count - noSave.Count); - for (int i = 0; i < States.Count; i++) - { - if (noSave.Contains(i)) - continue; - - StateAccessed(States.ElementAt(i).Key); - KeyValuePair kvp = States.ElementAt(i); - bw.Write(kvp.Key); - bw.Write(kvp.Value.Length); - bw.Write(kvp.Value.State); - } - - bw.Write(currentBranch); - bw.Write(BranchStates.Count); - foreach (var s in BranchStates) - { - bw.Write(s.Key); - bw.Write(s.Value.Count); - foreach (var t in s.Value) - { - bw.Write(t.Key); - t.Value.Write(bw); - } - } - } - private List ExcludeStates() { List ret = new List(); @@ -569,6 +537,41 @@ namespace BizHawk.Client.Common return ret; } + public void Save(BinaryWriter bw) + { + List noSave = ExcludeStates(); + + bw.Write(States.Count - noSave.Count); + for (int i = 0; i < States.Count; i++) + { + if (noSave.Contains(i)) + continue; + + StateAccessed(States.ElementAt(i).Key); + KeyValuePair kvp = States.ElementAt(i); + bw.Write(kvp.Key); + bw.Write(kvp.Value.Length); + bw.Write(kvp.Value.State); + } + + bw.Write(currentBranch); + + if (Settings.BranchStatesInTasproj) + { + bw.Write(BranchStates.Count); + foreach (var s in BranchStates) + { + bw.Write(s.Key); + bw.Write(s.Value.Count); + foreach (var t in s.Value) + { + bw.Write(t.Key); + t.Value.Write(bw); + } + } + } + } + public void Load(BinaryReader br) { States.Clear(); @@ -592,22 +595,26 @@ namespace BizHawk.Client.Common return; currentBranch = br.ReadInt32(); - int c = br.ReadInt32(); - BranchStates = new SortedList>(c); - while (c > 0) + + if (Settings.BranchStatesInTasproj) { - int key = br.ReadInt32(); - int c2 = br.ReadInt32(); - var list = new SortedList(c2); - while (c2 > 0) + int c = br.ReadInt32(); + BranchStates = new SortedList>(c); + while (c > 0) { - int key2 = br.ReadInt32(); - var state = StateManagerState.Read(br, this); - list.Add(key2, state); - c2--; + int key = br.ReadInt32(); + int c2 = br.ReadInt32(); + var list = new SortedList(c2); + while (c2 > 0) + { + int key2 = br.ReadInt32(); + var state = StateManagerState.Read(br, this); + list.Add(key2, state); + c2--; + } + BranchStates.Add(key, list); + c--; } - BranchStates.Add(key, list); - c--; } } diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs index 2ccd8ba8fa..04c9a6a135 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs @@ -13,6 +13,7 @@ namespace BizHawk.Client.Common DiskSaveCapacitymb = 512; Capacitymb = 512; DiskCapacitymb = 512; + BranchStatesInTasproj = false; } public TasStateManagerSettings(TasStateManagerSettings settings) @@ -20,6 +21,7 @@ namespace BizHawk.Client.Common DiskSaveCapacitymb = settings.DiskSaveCapacitymb; Capacitymb = settings.Capacitymb; DiskCapacitymb = settings.DiskCapacitymb; + BranchStatesInTasproj = settings.BranchStatesInTasproj; } ///

@@ -50,6 +52,13 @@ namespace BizHawk.Client.Common [Description("The size limit of the state history buffer on the disk. When this limit is reached it will start removing previous savestates")] public int DiskCapacitymb { get; set; } + /// + /// Put branch states to .tasproj + /// + [DisplayName("Put branch states to .tasproj")] + [Description("Put branch states to .tasproj")] + public bool BranchStatesInTasproj { get; set; } + /// /// The total state capacity in bytes. /// @@ -77,6 +86,7 @@ namespace BizHawk.Client.Common sb.AppendLine(DiskSaveCapacitymb.ToString()); sb.AppendLine(Capacitymb.ToString()); sb.AppendLine(DiskCapacitymb.ToString()); + sb.AppendLine(BranchStatesInTasproj.ToString()); return sb.ToString(); } @@ -88,6 +98,7 @@ namespace BizHawk.Client.Common string[] lines = settings.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); Capacitymb = int.Parse(lines[1]); int refCapacity; + if (!int.TryParse(lines[0], out refCapacity)) { if (bool.Parse(lines[0])) @@ -97,10 +108,16 @@ namespace BizHawk.Client.Common } else DiskSaveCapacitymb = refCapacity; + if (lines.Length > 2) DiskCapacitymb = int.Parse(lines[2]); else DiskCapacitymb = 512; + + if (lines.Length > 3) + BranchStatesInTasproj = bool.Parse(lines[3]); + else + BranchStatesInTasproj = false; } } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs index 8488b81e49..2cdd800120 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs @@ -53,6 +53,7 @@ namespace BizHawk.Client.EmuHawk this.label8 = new System.Windows.Forms.Label(); this.label9 = new System.Windows.Forms.Label(); this.NumSaveStatesLabel = new System.Windows.Forms.Label(); + this.BranchStatesInTasproj = new System.Windows.Forms.CheckBox(); ((System.ComponentModel.ISupportInitialize)(this.MemCapacityNumeric)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.DiskCapacityNumeric)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.SaveCapacityNumeric)).BeginInit(); @@ -62,7 +63,7 @@ namespace BizHawk.Client.EmuHawk // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(216, 113); + this.CancelBtn.Location = new System.Drawing.Point(216, 143); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(60, 23); this.CancelBtn.TabIndex = 0; @@ -73,7 +74,7 @@ namespace BizHawk.Client.EmuHawk // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(150, 113); + this.OkBtn.Location = new System.Drawing.Point(150, 143); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(60, 23); this.OkBtn.TabIndex = 1; @@ -116,7 +117,7 @@ namespace BizHawk.Client.EmuHawk // label2 // this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(12, 9); + this.label2.Location = new System.Drawing.Point(9, 9); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(88, 13); this.label2.TabIndex = 5; @@ -125,7 +126,7 @@ namespace BizHawk.Client.EmuHawk // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(126, 9); + this.label3.Location = new System.Drawing.Point(147, 9); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(79, 13); this.label3.TabIndex = 6; @@ -134,7 +135,7 @@ namespace BizHawk.Client.EmuHawk // SavestateSizeLabel // this.SavestateSizeLabel.AutoSize = true; - this.SavestateSizeLabel.Location = new System.Drawing.Point(208, 9); + this.SavestateSizeLabel.Location = new System.Drawing.Point(229, 9); this.SavestateSizeLabel.Name = "SavestateSizeLabel"; this.SavestateSizeLabel.Size = new System.Drawing.Size(25, 13); this.SavestateSizeLabel.TabIndex = 7; @@ -188,7 +189,7 @@ namespace BizHawk.Client.EmuHawk // label6 // this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(12, 49); + this.label6.Location = new System.Drawing.Point(9, 49); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(72, 13); this.label6.TabIndex = 5; @@ -196,7 +197,7 @@ namespace BizHawk.Client.EmuHawk // // SaveCapacityNumeric // - this.SaveCapacityNumeric.Location = new System.Drawing.Point(158, 66); + this.SaveCapacityNumeric.Location = new System.Drawing.Point(150, 66); this.SaveCapacityNumeric.Maximum = new decimal(new int[] { 65536, 0, @@ -215,7 +216,7 @@ namespace BizHawk.Client.EmuHawk // label7 // this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(213, 69); + this.label7.Location = new System.Drawing.Point(205, 69); this.label7.Name = "label7"; this.label7.Size = new System.Drawing.Size(21, 13); this.label7.TabIndex = 4; @@ -224,7 +225,7 @@ namespace BizHawk.Client.EmuHawk // label8 // this.label8.AutoSize = true; - this.label8.Location = new System.Drawing.Point(158, 49); + this.label8.Location = new System.Drawing.Point(147, 49); this.label8.Name = "label8"; this.label8.Size = new System.Drawing.Size(112, 13); this.label8.TabIndex = 5; @@ -233,7 +234,7 @@ namespace BizHawk.Client.EmuHawk // label9 // this.label9.AutoSize = true; - this.label9.Location = new System.Drawing.Point(155, 89); + this.label9.Location = new System.Drawing.Point(147, 89); this.label9.Name = "label9"; this.label9.Size = new System.Drawing.Size(84, 13); this.label9.TabIndex = 8; @@ -242,19 +243,31 @@ namespace BizHawk.Client.EmuHawk // NumSaveStatesLabel // this.NumSaveStatesLabel.AutoSize = true; - this.NumSaveStatesLabel.Location = new System.Drawing.Point(242, 89); + this.NumSaveStatesLabel.Location = new System.Drawing.Point(234, 89); this.NumSaveStatesLabel.Name = "NumSaveStatesLabel"; this.NumSaveStatesLabel.Size = new System.Drawing.Size(25, 13); this.NumSaveStatesLabel.TabIndex = 9; this.NumSaveStatesLabel.Text = "1kb"; // + // BranchStatesInTasproj + // + this.BranchStatesInTasproj.AutoSize = true; + this.BranchStatesInTasproj.Location = new System.Drawing.Point(12, 118); + this.BranchStatesInTasproj.Name = "BranchStatesInTasproj"; + this.BranchStatesInTasproj.Size = new System.Drawing.Size(158, 17); + this.BranchStatesInTasproj.TabIndex = 10; + this.BranchStatesInTasproj.Text = "Put branch states to .tasproj"; + this.BranchStatesInTasproj.UseVisualStyleBackColor = true; + this.BranchStatesInTasproj.CheckedChanged += new System.EventHandler(this.BranchStatesInTasproj_CheckedChanged); + // // StateHistorySettingsForm // this.AcceptButton = this.OkBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(288, 148); + this.ClientSize = new System.Drawing.Size(288, 178); + this.Controls.Add(this.BranchStatesInTasproj); this.Controls.Add(this.NumSaveStatesLabel); this.Controls.Add(this.NumStatesLabel); this.Controls.Add(this.label9); @@ -303,5 +316,6 @@ namespace BizHawk.Client.EmuHawk private Label label8; private Label label9; private Label NumSaveStatesLabel; + private CheckBox BranchStatesInTasproj; } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs index a9293a0f9c..5e25225437 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs @@ -44,6 +44,7 @@ namespace BizHawk.Client.EmuHawk SavestateSizeLabel.Text = Math.Round(_stateSizeMb, 2).ToString() + " mb"; CapacityNumeric_ValueChanged(null, null); SaveCapacityNumeric_ValueChanged(null, null); + BranchStatesInTasproj.Checked = Settings.BranchStatesInTasproj; } private int MaxStatesInCapacity @@ -79,5 +80,10 @@ namespace BizHawk.Client.EmuHawk { NumSaveStatesLabel.Text = ((int)Math.Floor(SaveCapacityNumeric.Value / _stateSizeMb)).ToString(); } + + private void BranchStatesInTasproj_CheckedChanged(object sender, EventArgs e) + { + Settings.BranchStatesInTasproj = BranchStatesInTasproj.Checked; + } } } From ef5369a443456b5ec0e5b842ce24be360b675ad2 Mon Sep 17 00:00:00 2001 From: feos Date: Tue, 6 Oct 2015 21:51:03 +0300 Subject: [PATCH 19/24] tastudio: "Erase branch states before all the rest" option. fixed branch info loading. --- .../movie/tasproj/TasStateManager.cs | 55 +++++++++++-------- .../movie/tasproj/TasStateManagerSettings.cs | 15 +++++ .../TAStudio/GreenzoneSettings.Designer.cs | 24 ++++++-- .../tools/TAStudio/GreenzoneSettings.cs | 6 ++ 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs index a3fb105333..062b27ec4d 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManager.cs @@ -221,11 +221,20 @@ namespace BizHawk.Client.Common /// private Point StateToRemove() { - int markerSkips = maxStates / 2; - // X is frame, Y is branch Point shouldRemove = new Point(-1, -1); + + if (BranchStates.Any() && Settings.EraseBranchStatesFirst) + { + var kvp = BranchStates.Count() > 1 ? BranchStates.ElementAt(1) : BranchStates.ElementAt(0); + shouldRemove.X = kvp.Key; + shouldRemove.Y = kvp.Value.Keys[0]; + + return shouldRemove; + } + int i = 0; + int markerSkips = maxStates / 2; // lowPrioritySates (e.g. states with only lag frames between them) do { @@ -263,7 +272,7 @@ namespace BizHawk.Client.Common if (shouldRemove.X < 1) // only found marker states above { - if (BranchStates.Any()) + if (BranchStates.Any() && !Settings.EraseBranchStatesFirst) { var kvp = BranchStates.Count() > 1 ? BranchStates.ElementAt(1) : BranchStates.ElementAt(0); shouldRemove.X = kvp.Key; @@ -356,7 +365,7 @@ namespace BizHawk.Client.Common { if (branch == -1) accessed.Remove(States[frame]); - else if (accessed.Contains(BranchStates[frame][branch])) + else if (accessed.Contains(BranchStates[frame][branch]) && !Settings.EraseBranchStatesFirst) accessed.Remove(BranchStates[frame][branch]); StateManagerState state; @@ -591,31 +600,31 @@ namespace BizHawk.Client.Common } //} - if (br.PeekChar() == -1) // at least don't crash when loading an old project - return; - - currentBranch = br.ReadInt32(); - - if (Settings.BranchStatesInTasproj) + try { - int c = br.ReadInt32(); - BranchStates = new SortedList>(c); - while (c > 0) + currentBranch = br.ReadInt32(); + if (Settings.BranchStatesInTasproj) { - int key = br.ReadInt32(); - int c2 = br.ReadInt32(); - var list = new SortedList(c2); - while (c2 > 0) + int c = br.ReadInt32(); + BranchStates = new SortedList>(c); + while (c > 0) { - int key2 = br.ReadInt32(); - var state = StateManagerState.Read(br, this); - list.Add(key2, state); - c2--; + int key = br.ReadInt32(); + int c2 = br.ReadInt32(); + var list = new SortedList(c2); + while (c2 > 0) + { + int key2 = br.ReadInt32(); + var state = StateManagerState.Read(br, this); + list.Add(key2, state); + c2--; + } + BranchStates.Add(key, list); + c--; } - BranchStates.Add(key, list); - c--; } } + catch (EndOfStreamException) { } } public KeyValuePair GetStateClosestToFrame(int frame) diff --git a/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs b/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs index 04c9a6a135..a575f21e5d 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasStateManagerSettings.cs @@ -14,6 +14,7 @@ namespace BizHawk.Client.Common Capacitymb = 512; DiskCapacitymb = 512; BranchStatesInTasproj = false; + EraseBranchStatesFirst = true; } public TasStateManagerSettings(TasStateManagerSettings settings) @@ -22,6 +23,7 @@ namespace BizHawk.Client.Common Capacitymb = settings.Capacitymb; DiskCapacitymb = settings.DiskCapacitymb; BranchStatesInTasproj = settings.BranchStatesInTasproj; + EraseBranchStatesFirst = settings.EraseBranchStatesFirst; } /// @@ -59,6 +61,13 @@ namespace BizHawk.Client.Common [Description("Put branch states to .tasproj")] public bool BranchStatesInTasproj { get; set; } + /// + /// Erase branch states before greenzone states when capacity is met + /// + [DisplayName("Erase branch states first")] + [Description("Erase branch states before greenzone states when capacity is met")] + public bool EraseBranchStatesFirst { get; set; } + /// /// The total state capacity in bytes. /// @@ -87,6 +96,7 @@ namespace BizHawk.Client.Common sb.AppendLine(Capacitymb.ToString()); sb.AppendLine(DiskCapacitymb.ToString()); sb.AppendLine(BranchStatesInTasproj.ToString()); + sb.AppendLine(EraseBranchStatesFirst.ToString()); return sb.ToString(); } @@ -118,6 +128,11 @@ namespace BizHawk.Client.Common BranchStatesInTasproj = bool.Parse(lines[3]); else BranchStatesInTasproj = false; + + if (lines.Length > 4) + EraseBranchStatesFirst = bool.Parse(lines[4]); + else + EraseBranchStatesFirst = true; } } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs index 2cdd800120..54fa987917 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.Designer.cs @@ -54,6 +54,7 @@ namespace BizHawk.Client.EmuHawk this.label9 = new System.Windows.Forms.Label(); this.NumSaveStatesLabel = new System.Windows.Forms.Label(); this.BranchStatesInTasproj = new System.Windows.Forms.CheckBox(); + this.EraseBranchStatesFirst = new System.Windows.Forms.CheckBox(); ((System.ComponentModel.ISupportInitialize)(this.MemCapacityNumeric)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.DiskCapacityNumeric)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.SaveCapacityNumeric)).BeginInit(); @@ -63,7 +64,7 @@ namespace BizHawk.Client.EmuHawk // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(216, 143); + this.CancelBtn.Location = new System.Drawing.Point(216, 163); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(60, 23); this.CancelBtn.TabIndex = 0; @@ -74,7 +75,7 @@ namespace BizHawk.Client.EmuHawk // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(150, 143); + this.OkBtn.Location = new System.Drawing.Point(150, 163); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(60, 23); this.OkBtn.TabIndex = 1; @@ -252,7 +253,7 @@ namespace BizHawk.Client.EmuHawk // BranchStatesInTasproj // this.BranchStatesInTasproj.AutoSize = true; - this.BranchStatesInTasproj.Location = new System.Drawing.Point(12, 118); + this.BranchStatesInTasproj.Location = new System.Drawing.Point(12, 115); this.BranchStatesInTasproj.Name = "BranchStatesInTasproj"; this.BranchStatesInTasproj.Size = new System.Drawing.Size(158, 17); this.BranchStatesInTasproj.TabIndex = 10; @@ -260,13 +261,27 @@ namespace BizHawk.Client.EmuHawk this.BranchStatesInTasproj.UseVisualStyleBackColor = true; this.BranchStatesInTasproj.CheckedChanged += new System.EventHandler(this.BranchStatesInTasproj_CheckedChanged); // + // EraseBranchStatesFirst + // + this.EraseBranchStatesFirst.AutoSize = true; + this.EraseBranchStatesFirst.Checked = true; + this.EraseBranchStatesFirst.CheckState = System.Windows.Forms.CheckState.Checked; + this.EraseBranchStatesFirst.Location = new System.Drawing.Point(12, 140); + this.EraseBranchStatesFirst.Name = "EraseBranchStatesFirst"; + this.EraseBranchStatesFirst.Size = new System.Drawing.Size(139, 17); + this.EraseBranchStatesFirst.TabIndex = 11; + this.EraseBranchStatesFirst.Text = "Erase branch states first"; + this.EraseBranchStatesFirst.UseVisualStyleBackColor = true; + this.EraseBranchStatesFirst.CheckedChanged += new System.EventHandler(this.EraseBranchStatesFIrst_CheckedChanged); + // // StateHistorySettingsForm // this.AcceptButton = this.OkBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(288, 178); + this.ClientSize = new System.Drawing.Size(288, 198); + this.Controls.Add(this.EraseBranchStatesFirst); this.Controls.Add(this.BranchStatesInTasproj); this.Controls.Add(this.NumSaveStatesLabel); this.Controls.Add(this.NumStatesLabel); @@ -317,5 +332,6 @@ namespace BizHawk.Client.EmuHawk private Label label9; private Label NumSaveStatesLabel; private CheckBox BranchStatesInTasproj; + private CheckBox EraseBranchStatesFirst; } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs index 5e25225437..e60cc15c7c 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/GreenzoneSettings.cs @@ -45,6 +45,7 @@ namespace BizHawk.Client.EmuHawk CapacityNumeric_ValueChanged(null, null); SaveCapacityNumeric_ValueChanged(null, null); BranchStatesInTasproj.Checked = Settings.BranchStatesInTasproj; + EraseBranchStatesFirst.Checked = Settings.EraseBranchStatesFirst; } private int MaxStatesInCapacity @@ -85,5 +86,10 @@ namespace BizHawk.Client.EmuHawk { Settings.BranchStatesInTasproj = BranchStatesInTasproj.Checked; } + + private void EraseBranchStatesFIrst_CheckedChanged(object sender, EventArgs e) + { + Settings.EraseBranchStatesFirst = EraseBranchStatesFirst.Checked; + } } } From daed8bb3cff0bf51e66d0a5c2e9e5bdb7812bdce Mon Sep 17 00:00:00 2001 From: feos Date: Tue, 6 Oct 2015 21:55:57 +0300 Subject: [PATCH 20/24] let mGBA core go. --- BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs index e07f6c7f45..50eff0e60f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs @@ -10,7 +10,7 @@ using System.ComponentModel; namespace BizHawk.Emulation.Cores.Nintendo.GBA { - [CoreAttributes("mGBA", "endrift", true, false, "NOT DONE", "NOT DONE", false)] + [CoreAttributes("mGBA", "endrift", true, true, "NOT DONE", "NOT DONE", false)] [ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))] public class MGBAHawk : IEmulator, IVideoProvider, ISyncSoundProvider, IGBAGPUViewable, ISaveRam, IStatable, IInputPollable, ISettable { From 443862effda16bf10d6529d0ecf4d7139e9b3d2a Mon Sep 17 00:00:00 2001 From: adelikat Date: Thu, 8 Oct 2015 20:36:20 -0400 Subject: [PATCH 21/24] mgba - version info and url --- BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs index 50eff0e60f..cec1c68c4e 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs @@ -10,7 +10,7 @@ using System.ComponentModel; namespace BizHawk.Emulation.Cores.Nintendo.GBA { - [CoreAttributes("mGBA", "endrift", true, true, "NOT DONE", "NOT DONE", false)] + [CoreAttributes("mGBA", "endrift", true, true, "0.2.0", "https://mgba.io/", false)] [ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))] public class MGBAHawk : IEmulator, IVideoProvider, ISyncSoundProvider, IGBAGPUViewable, ISaveRam, IStatable, IInputPollable, ISettable { From a44cf5a067e76ceb26a7a93c6205dae488ef8b2d Mon Sep 17 00:00:00 2001 From: adelikat Date: Sat, 10 Oct 2015 10:09:30 -0400 Subject: [PATCH 22/24] Update version info for the 1.11.2 release --- Version/VersionInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Version/VersionInfo.cs b/Version/VersionInfo.cs index 59a2dfdef9..50bd4eb4e5 100644 --- a/Version/VersionInfo.cs +++ b/Version/VersionInfo.cs @@ -1,7 +1,7 @@ static class VersionInfo { - public const string MAINVERSION = "1.10.0"; // Use numbers only or the new version notification won't work - public static readonly string RELEASEDATE = "June 15, 2015"; + public const string MAINVERSION = "1.11.2"; // Use numbers only or the new version notification won't work + public static readonly string RELEASEDATE = "October 9, 2015"; public static readonly bool DeveloperBuild = true; public static readonly string HomePage = "http://tasvideos.org/BizHawk.html"; From c57519c54f7b525f2e44688bb6379b446702523c Mon Sep 17 00:00:00 2001 From: adelikat Date: Sat, 10 Oct 2015 12:20:59 -0400 Subject: [PATCH 23/24] C64 - add a c64 menu with a settings menu and dialog --- BizHawk.Client.EmuHawk/MainForm.Designer.cs | 20 +++++++++++++++++++ BizHawk.Client.EmuHawk/MainForm.Events.cs | 9 +++++++++ BizHawk.Client.EmuHawk/MainForm.cs | 4 ++++ .../config/GenericCoreConfig.Designer.cs | 3 +++ .../Computers/Commodore64/C64.ISettable.cs | 12 ++++++----- .../Computers/Commodore64/C64.cs | 2 +- 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index cdff502530..b348c2cc33 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -330,6 +330,8 @@ this.ForumsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.FeaturesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.AboutMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.C64SubMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.C64SettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainStatusBar = new StatusStripEx(); this.DumpStatusButton = new System.Windows.Forms.ToolStripDropDownButton(); this.EmuStatus = new System.Windows.Forms.ToolStripStatusLabel(); @@ -433,6 +435,7 @@ this.GenesisSubMenu, this.wonderSwanToolStripMenuItem, this.AppleSubMenu, + this.C64SubMenu, this.HelpSubMenu}); this.MainformMenu.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow; this.MainformMenu.Location = new System.Drawing.Point(0, 0); @@ -2926,6 +2929,21 @@ this.AboutMenuItem.Text = "&About"; this.AboutMenuItem.Click += new System.EventHandler(this.AboutMenuItem_Click); // + // C64SubMenu + // + this.C64SubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.C64SettingsMenuItem}); + this.C64SubMenu.Name = "C64SubMenu"; + this.C64SubMenu.Size = new System.Drawing.Size(39, 19); + this.C64SubMenu.Text = "&C64"; + // + // C64SettingsMenuItem + // + this.C64SettingsMenuItem.Name = "C64SettingsMenuItem"; + this.C64SettingsMenuItem.Size = new System.Drawing.Size(152, 22); + this.C64SettingsMenuItem.Text = "&Settings..."; + this.C64SettingsMenuItem.Click += new System.EventHandler(this.C64SettingsMenuItem_Click); + // // MainStatusBar // this.MainStatusBar.ClickThrough = true; @@ -3973,6 +3991,8 @@ private System.Windows.Forms.ToolStripMenuItem Speed400MenuItem; private System.Windows.Forms.ToolStripMenuItem BasicBotMenuItem; private System.Windows.Forms.ToolStripMenuItem DisplayMessagesMenuItem; + private System.Windows.Forms.ToolStripMenuItem C64SubMenu; + private System.Windows.Forms.ToolStripMenuItem C64SettingsMenuItem; } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 39adb03adf..60cd748d54 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -2063,6 +2063,15 @@ namespace BizHawk.Client.EmuHawk #endregion + #region C64 + + private void C64SettingsMenuItem_Click(object sender, EventArgs e) + { + GenericCoreConfig.DoDialog(this, "C64 Settings"); + } + + #endregion + #region Help private void OnlineHelpMenuItem_Click(object sender, EventArgs e) diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 6d63a1b68b..19af808367 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -1595,6 +1595,7 @@ namespace BizHawk.Client.EmuHawk GenesisSubMenu.Visible = false; wonderSwanToolStripMenuItem.Visible = false; AppleSubMenu.Visible = false; + C64SubMenu.Visible = false; switch (system) { @@ -1675,6 +1676,9 @@ namespace BizHawk.Client.EmuHawk case "AppleII": AppleSubMenu.Visible = true; break; + case "C64": + C64SubMenu.Visible = true; + break; } } diff --git a/BizHawk.Client.EmuHawk/config/GenericCoreConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/GenericCoreConfig.Designer.cs index 359cc23605..0232504ff4 100644 --- a/BizHawk.Client.EmuHawk/config/GenericCoreConfig.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/GenericCoreConfig.Designer.cs @@ -67,6 +67,7 @@ // // propertyGrid1 // + this.propertyGrid1.CategoryForeColor = System.Drawing.SystemColors.InactiveCaptionText; this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill; this.propertyGrid1.Location = new System.Drawing.Point(3, 3); this.propertyGrid1.Name = "propertyGrid1"; @@ -88,6 +89,7 @@ // // propertyGrid2 // + this.propertyGrid2.CategoryForeColor = System.Drawing.SystemColors.InactiveCaptionText; this.propertyGrid2.Dock = System.Windows.Forms.DockStyle.Fill; this.propertyGrid2.Location = new System.Drawing.Point(3, 3); this.propertyGrid2.Name = "propertyGrid2"; @@ -143,6 +145,7 @@ this.Controls.Add(this.tabControl1); this.Name = "GenericCoreConfig"; this.ShowIcon = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "GenericCoreConfig"; this.Load += new System.EventHandler(this.GenericCoreConfig_Load); this.tabControl1.ResumeLayout(false); diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs index a19d5ebc69..b6e382a294 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.ISettable.cs @@ -9,11 +9,13 @@ using System.Drawing; namespace BizHawk.Emulation.Cores.Computers.Commodore64 { - public partial class C64 : ISettable + // adelikat: changing settings to default object untl there are actually settings, as the ui depends on it to know if there are any settings avaialable + public partial class C64 : ISettable { - public C64Settings GetSettings() + public object /*C64Settings*/ GetSettings() { - return Settings.Clone(); + //return Settings.Clone(); + return null; } public C64SyncSettings GetSyncSettings() @@ -21,9 +23,9 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 return SyncSettings.Clone(); } - public bool PutSettings(C64Settings o) + public bool PutSettings(object /*C64Settings*/ o) { - Settings = o; + //Settings = o; return false; } diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index cfbfac420e..2294bb131f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -15,7 +15,7 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 isReleased: false )] [ServiceNotApplicable(typeof(ISettable<,>))] - sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable, IRegionable, ISettable + sealed public partial class C64 : IEmulator, IStatable, IInputPollable, IDriveLight, IDebuggable, IDisassemblable, IRegionable, ISettable { // framework public C64(CoreComm comm, GameInfo game, byte[] rom, string romextension, object Settings, object SyncSettings) From 1ee56ea4ef40e75fe6de6128282f49bc44e000c6 Mon Sep 17 00:00:00 2001 From: adelikat Date: Sun, 11 Oct 2015 09:53:32 -0400 Subject: [PATCH 24/24] Ram Search - fix save menu item adding to ram watch recent files instead of ram search --- BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs b/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs index 7ee58c663b..d1bfdfe875 100644 --- a/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs +++ b/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs @@ -1024,7 +1024,7 @@ namespace BizHawk.Client.EmuHawk if (result) { MessageLabel.Text = Path.GetFileName(_currentFileName) + " saved"; - Global.Config.RecentWatches.Add(watches.CurrentFileName); + Settings.RecentSearches.Add(watches.CurrentFileName); } } }