├── .gitignore ├── Assets └── famicon.ico ├── CS64.Core ├── Audio │ ├── BlipBuffer.cs │ └── SID.cs ├── CPU │ ├── AddressingModes.cs │ ├── AudioBuffer.cs │ ├── CpuExecution.cs │ ├── InitInstructionSet.cs │ ├── MC6502InstructionSet.cs │ ├── MC6502State.cs │ └── OpCodeDelegate.cs ├── CS64.Core.csproj ├── Fonts │ └── Modeseven.ttf ├── Interface │ ├── AudioProvider.cs │ ├── DisplayMode.cs │ ├── IMainInterface.cs │ ├── Input │ │ ├── AnsiKeyboard.cs │ │ ├── C64Keyboard.cs │ │ ├── Controller.cs │ │ ├── InputEvent.cs │ │ ├── InputEventArgs.cs │ │ ├── InputEventType.cs │ │ ├── InputKeyEnum.cs │ │ ├── InputProvider.cs │ │ ├── KeyBinding.cs │ │ └── Keyboard.cs │ ├── KeyboardMatrix.cs │ ├── Main.Keyboard.cs │ ├── Main.cs │ ├── Playback.cs │ ├── RowCol.cs │ └── VideoProvider.cs ├── SDL2_ttf.dll ├── UnsupportedMapperException.cs ├── Utility │ └── BinaryWriterExtensions.cs ├── Video │ ├── CIA.cs │ ├── StateEnum.cs │ └── VICII.cs ├── blip_buf.dll ├── libfreetype-6.dll └── zlib1.dll ├── CS64.UI ├── CS64.UI.csproj ├── Configuration.cs ├── ControlExtensions.cs ├── FormExtensions.cs ├── KeyCodeMappings.cs ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── MapWaitForm.Designer.cs ├── MapWaitForm.cs ├── MapWaitForm.resx ├── MappingForm.Designer.cs ├── MappingForm.cs ├── MappingForm.resx └── famicon.ico ├── CS64.sln ├── CS64 ├── CS64.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── publish.cmd └── rom │ ├── basic.bin │ ├── characters.bin │ └── kernal.bin ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #Ignore thumbnails created by Windows 3 | Thumbs.db 4 | #Ignore files built by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | .vs/ 31 | #Nuget packages folder 32 | packages/ 33 | -------------------------------------------------------------------------------- /Assets/famicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/Assets/famicon.ico -------------------------------------------------------------------------------- /CS64.Core/Audio/BlipBuffer.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | // ReSharper disable StyleCop.SA1300 7 | // ReSharper disable InconsistentNaming 8 | namespace BizHawk.Emulation.Common 9 | { 10 | /// 11 | /// wrapper around blargg's unmanaged blip_buf 12 | /// 13 | public sealed class BlipBuffer : IDisposable 14 | { 15 | // this is transitional only. if the band-limited synthesis idea works out, i'll 16 | // make a managed MIT implementation 17 | private static class BlipBufDll 18 | { 19 | /** Creates new buffer that can hold at most sample_count samples. Sets rates 20 | so that there are blip_max_ratio clocks per sample. Returns pointer to new 21 | buffer, or NULL if insufficient memory. */ 22 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 23 | public static extern IntPtr blip_new(int sample_count); 24 | 25 | /** Sets approximate input clock rate and output sample rate. For every 26 | clock_rate input clocks, approximately sample_rate samples are generated. */ 27 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 28 | public static extern void blip_set_rates(IntPtr context, double clock_rate, double sample_rate); 29 | 30 | /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, 31 | clock_rate must not be greater than sample_rate*blip_max_ratio. */ 32 | public const int BlipMaxRatio = 1 << 20; 33 | 34 | /** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ 35 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 36 | public static extern void blip_clear(IntPtr context); 37 | 38 | /** Adds positive/negative delta into buffer at specified clock time. */ 39 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 40 | public static extern void blip_add_delta(IntPtr context, uint clock_time, int delta); 41 | 42 | /** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ 43 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 44 | public static extern void blip_add_delta_fast(IntPtr context, uint clock_time, int delta); 45 | 46 | /** Length of time frame, in clocks, needed to make sample_count additional 47 | samples available. */ 48 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 49 | public static extern int blip_clocks_needed(IntPtr context, int sample_count); 50 | 51 | /** Maximum number of samples that can be generated from one time frame. */ 52 | public const int BlipMaxFrame = 4000; 53 | 54 | /** Makes input clocks before clock_duration available for reading as output 55 | samples. Also begins new time frame at clock_duration, so that clock time 0 in 56 | the new time frame specifies the same clock as clock_duration in the old time 57 | frame specified. Deltas can have been added slightly past clock_duration (up to 58 | however many clocks there are in two output samples). */ 59 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 60 | public static extern void blip_end_frame(IntPtr context, uint clock_duration); 61 | 62 | /** Number of buffered samples available for reading. */ 63 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 64 | public static extern int blip_samples_avail(IntPtr context); 65 | 66 | /** Reads and removes at most 'count' samples and writes them to 'out'. If 67 | 'stereo' is true, writes output to every other element of 'out', allowing easy 68 | interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed 69 | samples. Returns number of samples actually read. */ 70 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 71 | public static extern int blip_read_samples(IntPtr context, short[] @out, int count, int stereo); 72 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 73 | public static extern int blip_read_samples(IntPtr context, IntPtr @out, int count, int stereo); 74 | 75 | /** Frees buffer. No effect if NULL is passed. */ 76 | [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] 77 | public static extern void blip_delete(IntPtr context); 78 | } 79 | 80 | private IntPtr _context; 81 | 82 | /// unmanaged call failed 83 | public BlipBuffer(int sampleCount) 84 | { 85 | _context = BlipBufDll.blip_new(sampleCount); 86 | if (_context == IntPtr.Zero) 87 | { 88 | throw new Exception("blip_new returned NULL!"); 89 | } 90 | } 91 | 92 | ~BlipBuffer() 93 | { 94 | Dispose(); 95 | } 96 | 97 | public void Dispose() 98 | { 99 | if (_context != IntPtr.Zero) 100 | { 101 | BlipBufDll.blip_delete(_context); 102 | _context = IntPtr.Zero; 103 | GC.SuppressFinalize(this); 104 | } 105 | } 106 | 107 | public void SetRates(double clockRate, double sampleRate) 108 | { 109 | BlipBufDll.blip_set_rates(_context, clockRate, sampleRate); 110 | } 111 | 112 | public const int MaxRatio = BlipBufDll.BlipMaxRatio; 113 | 114 | public void Clear() 115 | { 116 | BlipBufDll.blip_clear(_context); 117 | } 118 | 119 | public void AddDelta(uint clockTime, int delta) 120 | { 121 | BlipBufDll.blip_add_delta(_context, clockTime, delta); 122 | } 123 | 124 | public void AddDeltaFast(uint clockTime, int delta) 125 | { 126 | BlipBufDll.blip_add_delta_fast(_context, clockTime, delta); 127 | } 128 | 129 | public int ClocksNeeded(int sampleCount) 130 | { 131 | return BlipBufDll.blip_clocks_needed(_context, sampleCount); 132 | } 133 | 134 | public const int MaxFrame = BlipBufDll.BlipMaxFrame; 135 | 136 | public void EndFrame(uint clockDuration) 137 | { 138 | BlipBufDll.blip_end_frame(_context, clockDuration); 139 | } 140 | 141 | public int SamplesAvailable() 142 | { 143 | return BlipBufDll.blip_samples_avail(_context); 144 | } 145 | 146 | /// can't hold samples (or twice that if is ) 147 | public int ReadSamples(short[] output, int count, bool stereo) 148 | { 149 | if (output.Length < count * (stereo ? 2 : 1)) 150 | { 151 | throw new ArgumentOutOfRangeException(); 152 | } 153 | 154 | return BlipBufDll.blip_read_samples(_context, output, count, stereo ? 1 : 0); 155 | } 156 | 157 | /// can't hold 2 * samples 158 | public int ReadSamplesLeft(short[] output, int count) 159 | { 160 | if (output.Length < count * 2) 161 | { 162 | throw new ArgumentOutOfRangeException(); 163 | } 164 | 165 | return BlipBufDll.blip_read_samples(_context, output, count, 1); 166 | } 167 | 168 | /// can't hold 2 * samples 169 | public int ReadSamplesRight(short[] output, int count) 170 | { 171 | if (output.Length < count * 2) 172 | { 173 | throw new ArgumentOutOfRangeException(); 174 | } 175 | 176 | unsafe 177 | { 178 | fixed (short* s = &output[1]) 179 | return BlipBufDll.blip_read_samples(_context, new IntPtr(s), count, 1); 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /CS64.Core/Audio/SID.cs: -------------------------------------------------------------------------------- 1 | using CS64.Core.CPU; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace CS64.Core.Audio 7 | { 8 | public class SID 9 | { 10 | public uint sampleclock; 11 | private MC6502State mC6502State; 12 | 13 | public SID(MC6502State mC6502State) 14 | { 15 | this.mC6502State = mC6502State; 16 | } 17 | 18 | public int EmitSample() 19 | { 20 | return 0; 21 | } 22 | 23 | public uint Read(uint address) 24 | { 25 | return 0; 26 | } 27 | 28 | public void Write(uint address, uint value) 29 | { 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CS64.Core/CPU/AddressingModes.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace CS64.Core.CPU 4 | { 5 | public partial class MC6502State 6 | { 7 | 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | private void AddrModeImmediate() 10 | { 11 | EffectiveAddr = PC + 1; 12 | } 13 | 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | private void AddrModeZeroPage() 16 | { 17 | EffectiveAddr = BusRead(PC + 1); 18 | } 19 | 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | private void AddrModeZeroPageX() 22 | { 23 | EffectiveAddr = (BusRead(PC + 1) + X) % 0x100; 24 | } 25 | 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | private void AddrModeZeroPageY() 28 | { 29 | EffectiveAddr = (BusRead(PC + 1) + Y) % 0x100; 30 | } 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | private void AddrModeAbsolute() 34 | { 35 | EffectiveAddr = BusRead(PC + 1) + BusRead(PC + 2) * 0x100; 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | private void AddrModeAbsoluteX() 40 | { 41 | var basemem = BusRead(PC + 1) + BusRead(PC + 2) * 0x100; 42 | var basememPage = (basemem >> 8) & 0xff; 43 | EffectiveAddr = basemem + X; 44 | var effAddPage = (EffectiveAddr >> 8) & 0xff; 45 | 46 | //var x = PC >> 8; 47 | //var y = EffectiveAddr >> 8; 48 | 49 | if (basememPage != effAddPage) 50 | { 51 | PageBoundsCrossed = true; 52 | } 53 | } 54 | 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | private void AddrModeAbsoluteY() 57 | { 58 | var basemem = BusRead(PC + 1) + BusRead(PC + 2) * 0x100; 59 | var basememPage = (basemem >> 8) & 0xFF; 60 | EffectiveAddr = (basemem + Y) & 0xFFFF; 61 | var effAddPage = (EffectiveAddr >> 8) & 0xFF; 62 | 63 | //var x = PC >> 8; 64 | //var y = EffectiveAddr >> 8; 65 | 66 | if (basememPage != effAddPage) 67 | { 68 | PageBoundsCrossed = true; 69 | } 70 | } 71 | 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | private void AddrModeRelative() 74 | { 75 | EffectiveAddr = PC + 1; 76 | } 77 | 78 | 79 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 80 | private void AddrModeIndirect() 81 | { 82 | var arg = BusRead(PC + 1) + BusRead(PC + 2) * 0x100; 83 | EffectiveAddr = BusRead(arg) + BusRead(arg + 1) * 0x100; 84 | } 85 | 86 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 | private void AddrModeIndirect_JMP() 88 | { 89 | var arg = BusRead(PC + 1); 90 | arg += BusRead(PC + 2) * 0x100; 91 | if ((arg & 0xFF) == 0xFF) 92 | { 93 | EffectiveAddr = BusRead(arg) + BusRead(arg + 1 - 0x100) * 0x100; 94 | } 95 | else 96 | { 97 | EffectiveAddr = BusRead(arg) + BusRead(arg + 1) * 0x100; 98 | } 99 | } 100 | 101 | 102 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 103 | private void AddrModeIndirectX() 104 | { 105 | var arg = BusRead(PC + 1); 106 | var ix = arg + X; 107 | EffectiveAddr = BusRead(ix % 0x100) + BusRead((ix + 1) % 0x100) * 0x100; 108 | } 109 | 110 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 111 | private void AddrModeIndirectY() 112 | { 113 | var arg = BusRead(PC + 1); 114 | var basemem = BusRead(arg) + BusRead((arg + 1) % 0x100) * 0x100; 115 | var basememPage = (basemem >> 8) & 0xFF; 116 | EffectiveAddr = (basemem + Y) & 0xFFFF; 117 | var effAddPage = (EffectiveAddr >> 8) & 0xff; 118 | 119 | if (basememPage != effAddPage) 120 | { 121 | PageBoundsCrossed = true; 122 | } 123 | } 124 | 125 | } 126 | } -------------------------------------------------------------------------------- /CS64.Core/CPU/AudioBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BizHawk.Emulation.Common; 3 | using CS64.Core.Audio; 4 | using CS64.Core.Video; 5 | 6 | namespace CS64.Core.CPU 7 | { 8 | public partial class MC6502State 9 | { 10 | private BlipBuffer blip = new BlipBuffer(4096); 11 | private const int blipbuffsize = 4096; 12 | private const int cpuclockrate = 1789773; // NTSC 13 | private int old_s; 14 | 15 | public SID Sid; 16 | 17 | public void GetSamplesSync(out short[] samples, out int nsamp) 18 | { 19 | if (Sid == null || blip == null) 20 | { 21 | nsamp = 0; 22 | samples = new short[nsamp * 2]; 23 | return; 24 | } 25 | 26 | blip.EndFrame(Sid.sampleclock); 27 | Sid.sampleclock = 0; 28 | 29 | nsamp = blip.SamplesAvailable(); 30 | samples = new short[nsamp * 2]; 31 | 32 | 33 | blip.ReadSamples(samples, nsamp, false); 34 | //HighPassFilter(samples, nsamp, 90.0, 0.25); 35 | //HighPassFilter(samples, nsamp, 440.0, 0.25); 36 | //LowPassFilter(samples, nsamp, 5000.0, 1.0); 37 | 38 | for (int i = nsamp - 1; i >= 0; i--) 39 | { 40 | samples[i * 2] = samples[i]; 41 | samples[i * 2 + 1] = samples[i]; 42 | } 43 | } 44 | 45 | static void LowPassFilter(short[] sample, int samples, double frequency, double q) 46 | { 47 | double O = 2.0 * Math.PI * frequency / 44100.0; 48 | double C = q / O; 49 | double L = 1 / q / O; 50 | for (int c = 0; c < 1; c++) 51 | { 52 | double V = 0, I = 0, T; 53 | for (int s = 0; s < samples; s++) 54 | { 55 | T = (I - V) / C; 56 | I += (sample[s] * O - V) / L; 57 | V += T; 58 | sample[s] = (short)(V / O); 59 | } 60 | } 61 | } 62 | 63 | static void HighPassFilter(short[] sample, int samples, double Frequency, double Q) 64 | { 65 | double O = 2.0 * Math.PI * Frequency / 44100; 66 | double C = Q / O; 67 | double L = 1 / Q / O; 68 | for (int c = 0; c < 1; c++) 69 | { 70 | double V = 0, I = 0, T; 71 | for (int s = 0; s < samples; s++) 72 | { 73 | T = sample[s] * O - V; 74 | V += (I + T) / C; 75 | I += T / L; 76 | sample[s] -= (short)(V / O); 77 | } 78 | } 79 | } 80 | 81 | public void DiscardSamples() 82 | { 83 | blip.Clear(); 84 | Sid.sampleclock = 0; 85 | } 86 | 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /CS64.Core/CPU/CpuExecution.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using CS64.Core.Audio; 5 | using CS64.Core.Video; 6 | 7 | namespace CS64.Core.CPU 8 | { 9 | public partial class MC6502State 10 | { 11 | //private bool running; 12 | private StringBuilder log; 13 | private int _instructionCyclesLeft; 14 | public bool Debug { get; set; } 15 | public VICII Vic; 16 | public CIA Cia1; 17 | public CIA Cia2; 18 | 19 | public MC6502State() 20 | { 21 | log = new StringBuilder(); 22 | Vic = new VICII(this); 23 | Sid = new SID(this); 24 | Cia1 = new CIA(); 25 | 26 | Cia1.RequestInterrupt = () => TriggerInterrupt(InterruptTypeEnum.IRQ); 27 | 28 | Cia2 = new CIA(); 29 | 30 | // https://retrocomputing.stackexchange.com/questions/7791/6510-i-o-port-initialisation 31 | 32 | //I/O lines with programmable direction are set to input on reset. The 6510 is no exception, 33 | //its data direction register at address 0 is set to 0 on reset, all 6 I/O lines become inputs. 34 | //The circuitry outside the processor includes a pullup resistor on the I/O lines controlling 35 | //banking, so that they are in a known stable state even if the data direction is set to input. 36 | 37 | // Set the IO Data direction register to input on startup, else I/O will be be inaccessible. 38 | //IO_Port_DDR = 0xFF; 39 | IO_Port_DR = 0x1F; 40 | } 41 | 42 | 43 | public void Init() 44 | { 45 | MC6502InstructionSet.InitOpcodeTable(); 46 | blip.SetRates((uint)cpuclockrate, 44100); 47 | } 48 | 49 | private int divider; 50 | 51 | public uint Step() 52 | { 53 | Vic.Clock(); 54 | if (BusAvailable) 55 | { 56 | ClockCpu(); 57 | } 58 | 59 | divider++; 60 | // TODO: is the counter driven by the CPU clock? 61 | if (divider == 8) 62 | { 63 | divider = 0; 64 | Cia1.Clock(); 65 | Cia2.Clock(); 66 | } 67 | 68 | int s = Sid.EmitSample(); 69 | 70 | if (s != old_s) 71 | { 72 | blip.AddDelta(Sid.sampleclock, s - old_s); 73 | old_s = s; 74 | } 75 | 76 | Sid.sampleclock++; 77 | 78 | return 1; 79 | } 80 | 81 | private void ClockCpu() 82 | { 83 | // TODO: is the counter driven by the CPU clock? 84 | Cia1.Count(); 85 | Cia2.Count(); 86 | 87 | if (_instructionCyclesLeft-- > 0) 88 | { 89 | return; 90 | } 91 | 92 | _instructionCyclesLeft += (int)ExecuteInstruction(); 93 | Cycles += (uint)_instructionCyclesLeft; 94 | Instructions++; 95 | } 96 | 97 | public void Execute() 98 | { 99 | int ctr = 0; 100 | try 101 | { 102 | Debug = true; 103 | var running = true; 104 | while (running) 105 | { 106 | Step(); 107 | if (ctr > 10000) 108 | { 109 | running = false; 110 | } 111 | } 112 | 113 | File.WriteAllText("c64.log", log.ToString()); 114 | } 115 | catch (Exception e) 116 | { 117 | Console.WriteLine(e); 118 | File.WriteAllText("c64.log", log.ToString()); 119 | } 120 | } 121 | 122 | /// 123 | /// Executes one instruction and return the number of cycles consumed 124 | /// 125 | /// 126 | public uint ExecuteInstruction() 127 | { 128 | for (var i = 0; i < _interrupts.Length; i++) 129 | { 130 | if (_interrupts[i]) 131 | { 132 | _interrupts[i] = false; 133 | switch ((InterruptTypeEnum)i) 134 | { 135 | case InterruptTypeEnum.NMI: 136 | return NonMaskableInterrupt(); 137 | case InterruptTypeEnum.IRQ: 138 | return InterruptRequest(); 139 | } 140 | } 141 | } 142 | 143 | //switch (PC) 144 | //{ 145 | 146 | // case 0xFDCD: // Setup CIA 147 | // //case 0xEA8E: // keyboard routine 148 | // { 149 | // var x = 1; 150 | // break; 151 | // } 152 | //} 153 | 154 | var ins = BusRead(PC); 155 | var bytes = MC6502InstructionSet.bytes[ins]; 156 | 157 | if (Debug) 158 | { 159 | Log(bytes); 160 | } 161 | 162 | // This could be moved into each instruction, but we would need to implement all 255 instructions separately 163 | switch (MC6502InstructionSet.addrmodes[ins]) 164 | { 165 | case MC6502InstructionSet.ACC: 166 | case MC6502InstructionSet.IMP: 167 | break; 168 | case MC6502InstructionSet.IMM: 169 | AddrModeImmediate(); 170 | break; 171 | case MC6502InstructionSet.DP_: 172 | AddrModeZeroPage(); 173 | break; 174 | case MC6502InstructionSet.DPX: 175 | AddrModeZeroPageX(); 176 | break; 177 | case MC6502InstructionSet.DPY: 178 | AddrModeZeroPageY(); 179 | break; 180 | case MC6502InstructionSet.IND: 181 | if (ins == 0x6c) 182 | { 183 | AddrModeIndirect_JMP(); 184 | } 185 | else 186 | { 187 | AddrModeIndirect(); 188 | } 189 | break; 190 | case MC6502InstructionSet.IDX: 191 | AddrModeIndirectX(); 192 | break; 193 | case MC6502InstructionSet.IDY: 194 | AddrModeIndirectY(); 195 | break; 196 | case MC6502InstructionSet.ABS: 197 | AddrModeAbsolute(); 198 | break; 199 | case MC6502InstructionSet.ABX: 200 | AddrModeAbsoluteX(); 201 | break; 202 | case MC6502InstructionSet.ABY: 203 | AddrModeAbsoluteY(); 204 | break; 205 | case MC6502InstructionSet.REL: 206 | AddrModeRelative(); 207 | break; 208 | default: 209 | //File.WriteAllText("mario.log", log.ToString()); 210 | 211 | throw new NotImplementedException(); 212 | } 213 | 214 | PC += bytes; 215 | 216 | var pcycles = MC6502InstructionSet.cycles[ins]; 217 | 218 | pcycles += MC6502InstructionSet.OpCodes[ins](this); 219 | 220 | if (PageBoundsCrossed) 221 | { 222 | //switch (ins) 223 | //{ 224 | // /* 225 | // According to documentation, these modes are affected 226 | // ADC 227 | // AND 228 | // CMP 229 | // EOR 230 | // LAX 231 | // LDA 232 | // LDX 233 | // LDY 234 | // NOP 235 | // ORA 236 | // SBC 237 | // (indirect),Y 238 | // absolute,X 239 | // absolute,Y 240 | // */ 241 | // case 0x71: 242 | // case 0x7D: 243 | // case 0x79: 244 | // case 0x31: 245 | // case 0x3D: 246 | // case 0x39: 247 | // case 0xD1: 248 | // case 0xDD: 249 | // case 0xD9: 250 | // case 0x51: 251 | // case 0x5D: 252 | // case 0x59: 253 | // case 0xB3: 254 | // case 0xBF: 255 | // case 0xB1: 256 | // case 0xBD: 257 | // case 0xB9: 258 | // case 0xBE: 259 | // case 0xBC: 260 | // case 0x1C: 261 | // case 0x3C: 262 | // case 0x5C: 263 | // case 0x7C: 264 | // case 0xDC: 265 | // case 0xFC: 266 | // case 0x11: 267 | // case 0x1D: 268 | // case 0x19: 269 | // case 0xF1: 270 | // case 0xFD: 271 | // case 0xF9: 272 | // case 0xF0: 273 | // pcycles++; 274 | // break; 275 | //} 276 | pcycles++; 277 | PageBoundsCrossed = false; 278 | } 279 | 280 | switch (ins) 281 | { 282 | // Just to align with nestest.log 283 | case 0xce: 284 | pcycles += 3; 285 | break; 286 | case 0xf3: 287 | pcycles += 4; 288 | break; 289 | } 290 | 291 | return pcycles; 292 | } 293 | 294 | private void Log(uint bytes) 295 | { 296 | var sb = new StringBuilder(256); 297 | for (var i = 0u; i < bytes; i++) 298 | { 299 | sb.Append($"{BusRead(PC + i):X2} "); 300 | } 301 | 302 | for (var i = 0; i < 3 - bytes; i++) 303 | { 304 | sb.Append($" "); 305 | } 306 | 307 | var logMessage = 308 | $"{PC:X4} {sb} A:{A:X2} X:{X:X2} Y:{Y:X2} P:{P:X2} SP:{S:X2} CYC:{Cycles}"; 309 | 310 | Console.WriteLine(logMessage); 311 | 312 | //log.AppendLine(logMessage); 313 | 314 | 315 | } 316 | 317 | } 318 | } -------------------------------------------------------------------------------- /CS64.Core/CPU/InitInstructionSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CS64.Core.CPU 4 | { 5 | public static partial class MC6502InstructionSet 6 | { 7 | public static OpCodeDelegate[] OpCodes; 8 | 9 | public static void InitOpcodeTable() 10 | { 11 | OpCodes = new OpCodeDelegate[256]; 12 | 13 | for (var opCode = 0; opCode < 256; opCode++) 14 | { 15 | OpCodes[opCode] = GetOpcodeDelegate(opCode); 16 | } 17 | } 18 | 19 | private static OpCodeDelegate GetOpcodeDelegate(int i) 20 | { 21 | return i switch 22 | { 23 | 0x00 => BRK, 24 | 0x01 => ORA, 25 | 0x02 => HLT, 26 | 0x03 => SLO, 27 | 0x04 => NOP, 28 | 0x05 => ORA, 29 | 0x06 => ASL_Mem, 30 | 0x07 => SLO, 31 | 0x08 => PHP, 32 | 0x09 => ORA, 33 | 0x0A => ASL, 34 | 0x0B => ANC, 35 | 0x0C => NOP, 36 | 0x0D => ORA, 37 | 0x0E => ASL_Mem, 38 | 0x0F => SLO, 39 | 0x10 => BPL, 40 | 0x11 => ORA, 41 | 0x12 => HLT, 42 | 0x13 => SLO, 43 | 0x14 => NOP, 44 | 0x15 => ORA, 45 | 0x16 => ASL_Mem, 46 | 0x17 => SLO, 47 | 0x18 => CLC, 48 | 0x19 => ORA, 49 | 0x1A => NOP, 50 | 0x1B => SLO, 51 | 0x1C => NOP, 52 | 0x1D => ORA, 53 | 0x1E => ASL_Mem, 54 | 0x1F => SLO, 55 | 0x20 => JSR, 56 | 0x21 => AND, 57 | 0x22 => HLT, 58 | 0x23 => RLA, 59 | 0x24 => BIT, 60 | 0x25 => AND, 61 | 0x26 => ROL_Mem, 62 | 0x27 => RLA, 63 | 0x28 => PLP, 64 | 0x29 => AND, 65 | 0x2A => ROL, 66 | 0x2B => ANC, 67 | 0x2C => BIT, 68 | 0x2D => AND, 69 | 0x2E => ROL_Mem, 70 | 0x2F => RLA, 71 | 0x30 => BMI, 72 | 0x31 => AND, 73 | 0x32 => HLT, 74 | 0x33 => RLA, 75 | 0x34 => NOP, 76 | 0x35 => AND, 77 | 0x36 => ROL_Mem, 78 | 0x37 => RLA, 79 | 0x38 => SEC, 80 | 0x39 => AND, 81 | 0x3A => NOP, 82 | 0x3B => RLA, 83 | 0x3C => NOP, 84 | 0x3D => AND, 85 | 0x3E => ROL_Mem, 86 | 0x3F => RLA, 87 | 0x40 => RTI, 88 | 0x41 => EOR, 89 | 0x42 => HLT, 90 | 0x43 => SRE, 91 | 0x44 => NOP, 92 | 0x45 => EOR, 93 | 0x46 => LSR_Mem, 94 | 0x47 => SRE, 95 | 0x48 => PHA, 96 | 0x49 => EOR, 97 | 0x4A => LSR, 98 | 0x4B => ALR, 99 | 0x4C => JMP, 100 | 0x4D => EOR, 101 | 0x4E => LSR_Mem, 102 | 0x4F => SRE, 103 | 0x50 => BVC, 104 | 0x51 => EOR, 105 | 0x52 => HLT, 106 | 0x53 => SRE, 107 | 0x54 => NOP, 108 | 0x55 => EOR, 109 | 0x56 => LSR_Mem, 110 | 0x57 => SRE, 111 | 0x58 => CLI, 112 | 0x59 => EOR, 113 | 0x5A => NOP, 114 | 0x5B => SRE, 115 | 0x5C => NOP, 116 | 0x5D => EOR, 117 | 0x5E => LSR_Mem, 118 | 0x5F => SRE, 119 | 0x60 => RTS, 120 | 0x61 => ADC, 121 | 0x62 => HLT, 122 | 0x63 => RRA, 123 | 0x64 => NOP, 124 | 0x65 => ADC, 125 | 0x66 => ROR_Mem, 126 | 0x67 => RRA, 127 | 0x68 => PLA, 128 | 0x69 => ADC, 129 | 0x6A => ROR, 130 | 0x6B => ARR, 131 | 0x6C => JMP, 132 | 0x6D => ADC, 133 | 0x6E => ROR_Mem, 134 | 0x6F => RRA, 135 | 0x70 => BVS, 136 | 0x71 => ADC, 137 | 0x72 => HLT, 138 | 0x73 => RRA, 139 | 0x74 => NOP, 140 | 0x75 => ADC, 141 | 0x76 => ROR_Mem, 142 | 0x77 => RRA, 143 | 0x78 => SEI, 144 | 0x79 => ADC, 145 | 0x7A => NOP, 146 | 0x7B => RRA, 147 | 0x7C => NOP, 148 | 0x7D => ADC, 149 | 0x7E => ROR_Mem, 150 | 0x7F => RRA, 151 | 0x80 => NOP, 152 | 0x81 => STA, 153 | 0x82 => NOP, 154 | 0x83 => SAX, 155 | 0x84 => STY, 156 | 0x85 => STA, 157 | 0x86 => STX, 158 | 0x87 => SAX, 159 | 0x88 => DEY, 160 | 0x89 => NOP, 161 | 0x8A => TXA, 162 | 0x8B => ANE, 163 | 0x8C => STY, 164 | 0x8D => STA, 165 | 0x8E => STX, 166 | 0x8F => SAX, 167 | 0x90 => BCC, 168 | 0x91 => STA, 169 | 0x92 => HLT, 170 | 0x93 => SHA, 171 | 0x94 => STY, 172 | 0x95 => STA, 173 | 0x96 => STX, 174 | 0x97 => SAX, 175 | 0x98 => TYA, 176 | 0x99 => STA, 177 | 0x9A => TXS, 178 | 0x9B => TAS, 179 | 0x9C => SHY, 180 | 0x9D => STA, 181 | 0x9E => SHX, 182 | 0x9F => SHA, 183 | 0xA0 => LDY, 184 | 0xA1 => LDA, 185 | 0xA2 => LDX, 186 | 0xA3 => LAX, 187 | 0xA4 => LDY, 188 | 0xA5 => LDA, 189 | 0xA6 => LDX, 190 | 0xA7 => LAX, 191 | 0xA8 => TAY, 192 | 0xA9 => LDA, 193 | 0xAA => TAX, 194 | 0xAB => LXA, 195 | 0xAC => LDY, 196 | 0xAD => LDA, 197 | 0xAE => LDX, 198 | 0xAF => LAX, 199 | 0xB0 => BCS, 200 | 0xB1 => LDA, 201 | 0xB2 => HLT, 202 | 0xB3 => LAX, 203 | 0xB4 => LDY, 204 | 0xB5 => LDA, 205 | 0xB6 => LDX, 206 | 0xB7 => LAX, 207 | 0xB8 => CLV, 208 | 0xB9 => LDA, 209 | 0xBA => TSX, 210 | 0xBB => LAS, 211 | 0xBC => LDY, 212 | 0xBD => LDA, 213 | 0xBE => LDX, 214 | 0xBF => LAX, 215 | 0xC0 => CPY, 216 | 0xC1 => CMP, 217 | 0xC2 => NOP, 218 | 0xC3 => DCP, 219 | 0xC4 => CPY, 220 | 0xC5 => CMP, 221 | 0xC6 => DEC, 222 | 0xC7 => DCP, 223 | 0xC8 => INY, 224 | 0xC9 => CMP, 225 | 0xCA => DEX, 226 | 0xCB => SBX, 227 | 0xCC => CPY, 228 | 0xCD => CMP, 229 | 0xCE => DEC, 230 | 0xCF => DCP, 231 | 0xD0 => BNE, 232 | 0xD1 => CMP, 233 | 0xD2 => HLT, 234 | 0xD3 => DCP, 235 | 0xD4 => NOP, 236 | 0xD5 => CMP, 237 | 0xD6 => DEC, 238 | 0xD7 => DCP, 239 | 0xD8 => CLD, 240 | 0xD9 => CMP, 241 | 0xDA => NOP, 242 | 0xDB => DCP, 243 | 0xDC => NOP, 244 | 0xDD => CMP, 245 | 0xDE => DEC, 246 | 0xDF => DCP, 247 | 0xE0 => CPX, 248 | 0xE1 => SBC, 249 | 0xE2 => NOP, 250 | 0xE3 => ISC, 251 | 0xE4 => CPX, 252 | 0xE5 => SBC, 253 | 0xE6 => INC, 254 | 0xE7 => ISC, 255 | 0xE8 => INX, 256 | 0xE9 => SBC, 257 | 0xEA => NOP, 258 | 0xEB => USB, 259 | 0xEC => CPX, 260 | 0xED => SBC, 261 | 0xEE => INC, 262 | 0xEF => ISC, 263 | 0xF0 => BEQ, 264 | 0xF1 => SBC, 265 | 0xF2 => HLT, 266 | 0xF3 => ISC, 267 | 0xF4 => NOP, 268 | 0xF5 => SBC, 269 | 0xF6 => INC, 270 | 0xF7 => ISC, 271 | 0xF8 => SED, 272 | 0xF9 => SBC, 273 | 0xFA => NOP, 274 | 0xFB => ISC, 275 | 0xFC => NOP, 276 | 0xFD => SBC, 277 | 0xFE => INC, 278 | 0xFF => ISC, 279 | 280 | _ => throw new ArgumentOutOfRangeException() 281 | }; 282 | } 283 | } 284 | } -------------------------------------------------------------------------------- /CS64.Core/CPU/MC6502State.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace CS64.Core.CPU 8 | { 9 | public enum InterruptTypeEnum 10 | { 11 | NMI, 12 | IRQ, 13 | BRK 14 | } 15 | 16 | public partial class MC6502State 17 | { 18 | public byte[] RAM = new byte[0x10000]; 19 | public byte[] KERNAL = new byte[0x2000]; 20 | public byte[] BASIC = new byte[0x2000]; 21 | public byte[] CHAROM = new byte[0x1000]; 22 | public byte[] COLOR = new byte[0x400]; 23 | 24 | public uint A; 25 | public uint X; 26 | public uint Y; 27 | public uint S; 28 | 29 | public uint PC; 30 | public uint N; // bit 7 31 | public uint V; // bit 6 32 | public uint U; // bit 5 33 | public uint B; // bit 4 34 | public uint D; // bit 3 35 | public uint I { get; set; } // bit 2 36 | public uint Z; // bit 1 37 | public uint C; // bit 0 38 | 39 | public uint Cycles; 40 | public uint Instructions; 41 | 42 | public bool BusAvailable = true; 43 | 44 | public uint dma_page; 45 | public uint dma_address; 46 | public byte dma_data; 47 | 48 | public bool dma_transfer; 49 | public bool dma_dummy; 50 | 51 | 52 | public uint P 53 | { 54 | get => 55 | (uint)( 56 | (N << 7) + 57 | (V << 6) + 58 | (U << 5) + 59 | (B << 4) + 60 | (D << 3) + 61 | (I << 2) + 62 | (Z << 1) + 63 | (C << 0) 64 | ); 65 | set 66 | { 67 | N = (value >> 7 & 1); 68 | V = (value >> 6 & 1); 69 | U = (value >> 5 & 1); 70 | B = (value >> 4 & 1); 71 | D = (value >> 3 & 1); 72 | I = (value >> 2 & 1); 73 | Z = (value >> 1 & 1); 74 | C = (value >> 0 & 1); 75 | } 76 | } 77 | 78 | 79 | public uint EffectiveAddr; 80 | public bool PageBoundsCrossed; 81 | 82 | 83 | public bool[] _interrupts = new bool[3]; 84 | 85 | public void TriggerInterrupt(InterruptTypeEnum type) 86 | { 87 | if (I != 1 || type == InterruptTypeEnum.NMI) 88 | { 89 | _interrupts[(int)type] = true; 90 | } 91 | } 92 | 93 | public void WriteState(Stream stream) 94 | { 95 | var w = new BinaryWriter(stream); 96 | w.Write(RAM, 0, RAM.Length); 97 | w.Write((byte)A); 98 | w.Write((byte)X); 99 | w.Write((byte)Y); 100 | w.Write((byte)S); 101 | w.Write((byte)P); 102 | w.Write((ushort)PC); 103 | w.Write(Cycles); 104 | } 105 | 106 | public void ReadState(Stream stream) 107 | { 108 | var w = new BinaryReader(stream); 109 | w.Read(RAM, 0, RAM.Length); 110 | A = w.ReadByte(); 111 | X = w.ReadByte(); 112 | Y = w.ReadByte(); 113 | S = w.ReadByte(); 114 | P = w.ReadByte(); 115 | PC = w.ReadUInt16(); 116 | Cycles = w.ReadUInt32(); 117 | } 118 | 119 | public void Reset() 120 | { 121 | // https://www.pagetable.com/?p=410 122 | S = 0xFD; // Actually 0xFF, but 3 Stack Pushes are done with writes supressed, 123 | P = 0x24; // Just to align with nestest.log: I is set, U shouldn't exist, but... 124 | PC = BusRead(0xFFFC) + BusRead(0xFFFD) * 0x100; // Fetch the reset vector 125 | I = 1; 126 | Cycles = 7; // takes 7 cycles to reset 127 | 128 | //IO_Port_DR = 0x1F; // enable KERNAL, I/O, BASIC 129 | 130 | _instructionCyclesLeft = 0; 131 | dma_page = 0; 132 | dma_address = 0x00; 133 | dma_transfer = false; 134 | for (var i = 0; i < _interrupts.Length; i++) 135 | { 136 | _interrupts[i] = false; 137 | } 138 | } 139 | 140 | public uint BusRead(uint address) 141 | { 142 | uint data = address switch 143 | { 144 | >= 0xE000 and <= 0xFFFF => KERNAL[address - 0xE000], 145 | >= 0xA000 and <= 0xBFFF => BASIC[address - 0xA000], 146 | >= 0xD000 and <= 0xDFFF => CharROMRead(address), 147 | 0x0001 => IO_Port_DR, 148 | 0x0000 => IO_Port_DDR, 149 | _ => RAM[address] 150 | }; 151 | 152 | return data; 153 | } 154 | 155 | public void BusWrite(uint address, uint data) 156 | { 157 | switch (address) 158 | { 159 | case >= 0xD000 and <= 0xDFFF: 160 | CharROMWrite(address, data); 161 | break; 162 | case 0x0001: 163 | _io_port = data; 164 | UpdateIOPort(); 165 | break; 166 | case 0x0000: 167 | IO_Port_DDR = data; 168 | UpdateIOPort(); 169 | break; 170 | case >= 0 and <= 0xFFFF: 171 | // fall through to RAM 172 | RAM[address] = (byte)data; 173 | break; 174 | } 175 | } 176 | 177 | private void UpdateIOPort() 178 | { 179 | var output = _io_port; 180 | output &= IO_Port_DDR; 181 | IO_Port_DR &= (IO_Port_DDR ^ 0xFF); 182 | IO_Port_DR |= output; 183 | } 184 | 185 | // https://www.c64-wiki.com/wiki/Bank_Switching 186 | 187 | private uint _io_port; 188 | //data direction register 189 | public uint IO_Port_DDR; 190 | //data register 191 | public uint IO_Port_DR; 192 | 193 | public uint CharROMRead(uint address) 194 | { 195 | uint data = 0; 196 | // CHAREN = bit 2 of I/O 197 | if ((IO_Port_DR & 0x04) == 0x04) 198 | { 199 | data = address switch 200 | { 201 | >= 0xD000 and <= 0xD3FF => Vic.Read(address), 202 | >= 0xD400 and <= 0xD7FF => Sid.Read(address), 203 | // maybe OR with the upper nybble of previous contents of the data bus - for accuracy 204 | >= 0xD800 and <= 0xDBFF => (COLOR[address - 0xD800] & 0xFU), 205 | >= 0xDC00 and <= 0xDCFF => Cia1.Read(address), // Keyboard 206 | >= 0xDD00 and <= 0xDDFF => Cia2.Read(address), // VIC bankswitch 207 | >= 0xDE00 and <= 0xDEFF => 0, // I/O 1 208 | >= 0xDF00 and <= 0xDFFF => 0, // I/O 2 209 | }; 210 | } 211 | else 212 | { 213 | data = CHAROM[address - 0xD000]; 214 | } 215 | return data; 216 | } 217 | 218 | public void CharROMWrite(uint address, uint value) 219 | { 220 | // CHAREN = bit 2 of I/O port 221 | if ((IO_Port_DR & 0x04) == 0x04) 222 | { 223 | if (address >= 0xD000 && address <= 0xD3FF) 224 | { 225 | Vic.Write(address, value); 226 | } 227 | else if (address >= 0xD400 && address <= 0xD7FF) 228 | { 229 | Sid.Write(address, value); 230 | } 231 | else if (address >= 0xD800 && address <= 0xDBFF) 232 | { 233 | // Technically, only the lower 4 bits are connected 234 | COLOR[address - 0xD800] = (byte)(COLOR[address - 0xD800] | (value & 0xFU)); 235 | } 236 | else if (address >= 0xDC00 && address <= 0xDCFF) 237 | { 238 | Cia1.Write(address, value); 239 | } 240 | else if (address >= 0xDD00 && address <= 0xDDFF) 241 | { 242 | Cia2.Write(address, value); // VIC bankswitch 243 | } 244 | } 245 | else 246 | { 247 | // fall through to RAM 248 | RAM[address] = (byte)value; 249 | } 250 | } 251 | 252 | 253 | 254 | public uint NonMaskableInterrupt() 255 | { 256 | Push((PC >> 8) & 0xFF); // Push the high byte of the PC 257 | Push((PC & 0xFF)); // Push the low byte of the PC 258 | B = 0; 259 | U = 1; 260 | Push(P); 261 | I = 1; 262 | PC = BusRead(0xFFFA) + BusRead(0xFFFB) * 0x100; // Jump to NMI handler 263 | return 7; 264 | } 265 | 266 | 267 | public uint InterruptRequest() 268 | { 269 | Push((PC >> 8) & 0xFF); // Push the high byte of the PC 270 | Push((PC & 0xFF)); // Push the low byte of the PC 271 | B = 0; 272 | U = 1; 273 | Push(P); 274 | I = 1; 275 | PC = BusRead(0xFFFE) + BusRead(0xFFFF) * 0x100; // Jump to IRQ handler 276 | return 7; 277 | } 278 | 279 | 280 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 281 | public uint Push(uint value) 282 | { 283 | BusWrite(S + 0x100, value); 284 | S -= 1; 285 | return 0; 286 | } 287 | 288 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 289 | public uint Pop() 290 | { 291 | S += 1; 292 | return BusRead(S + 0x100); 293 | } 294 | 295 | public void LoadMemory(uint address, byte[] data) 296 | { 297 | if (address == 0xE000) 298 | { 299 | Array.Copy(data, 0, KERNAL, 0, data.Length); 300 | } 301 | else if (address == 0xA000) 302 | { 303 | Array.Copy(data, 0, BASIC, 0, data.Length); 304 | } 305 | else if (address == 0xD000) 306 | { 307 | Array.Copy(data, 0, CHAROM, 0, data.Length); 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /CS64.Core/CPU/OpCodeDelegate.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.CPU 2 | { 3 | public delegate uint OpCodeDelegate(MC6502State cpu); 4 | } -------------------------------------------------------------------------------- /CS64.Core/CS64.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | CS64.Core 6 | CS64.Core 7 | latest 8 | 9 | 10 | 11 | true 12 | 13 | 14 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | PreserveNewest 33 | 34 | 35 | PreserveNewest 36 | 37 | 38 | PreserveNewest 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /CS64.Core/Fonts/Modeseven.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64.Core/Fonts/Modeseven.ttf -------------------------------------------------------------------------------- /CS64.Core/Interface/AudioProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using static SDL2.SDL; 4 | 5 | namespace CS64.Core.Interface 6 | { 7 | public class AudioProvider : IDisposable 8 | { 9 | private const uint AUDIO_SAMPLE_FULL_THRESHOLD = 2048; 10 | private const int SAMPLES_PER_CALLBACK = 32; // 735 = 44100 samples / 60 fps // 367.5? 1470 11 | 12 | private readonly IntPtr _audioTempBufPtr = Marshal.AllocHGlobal(16384); 13 | private SDL_AudioSpec _want, _have; 14 | private uint _audioDevice; 15 | 16 | public void Initialize() 17 | { 18 | _want.channels = 2; 19 | _want.freq = 44100; 20 | _want.samples = SAMPLES_PER_CALLBACK; 21 | _want.format = AUDIO_S16LSB; 22 | _audioDevice = SDL_OpenAudioDevice(null, 0, ref _want, out _have, (int)SDL_AUDIO_ALLOW_FORMAT_CHANGE); 23 | SDL_PauseAudioDevice(_audioDevice, 0); 24 | } 25 | 26 | public void AudioReady(short[] data) 27 | { 28 | // Don't queue audio if too much is in buffer 29 | if (GetAudioSamplesInQueue() < AUDIO_SAMPLE_FULL_THRESHOLD) 30 | { 31 | int bytes = sizeof(short) * data.Length; 32 | 33 | Marshal.Copy(data, 0, _audioTempBufPtr, data.Length); 34 | 35 | // Console.WriteLine("Outputting samples to SDL"); 36 | 37 | SDL_QueueAudio(_audioDevice, _audioTempBufPtr, (uint)bytes); 38 | } 39 | } 40 | 41 | public uint GetAudioSamplesInQueue() 42 | { 43 | return SDL_GetQueuedAudioSize(_audioDevice) / sizeof(short); 44 | } 45 | 46 | public void Dispose() 47 | { 48 | Marshal.FreeHGlobal(_audioTempBufPtr); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/DisplayMode.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Interface 2 | { 3 | public enum DisplayMode 4 | { 5 | Ratio1x1, 6 | Ratio4x3, 7 | Ratio8x7, 8 | Stretched, 9 | } 10 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/IMainInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CS64.Core.Interface.Input; 3 | 4 | namespace CS64.Core.Interface 5 | { 6 | public interface IMainInterface 7 | { 8 | 9 | IntPtr Handle { get; } 10 | Action OnHostResize { get; set; } 11 | Func LoadRom { get; set; } 12 | Action ChangeSlot { get; set; } 13 | Action LoadState { get; set; } 14 | Action SaveState { get; set; } 15 | Action ResizeWindow { get; set; } 16 | Action SetMapping { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/AnsiKeyboard.cs: -------------------------------------------------------------------------------- 1 | using static SDL2.SDL; 2 | 3 | namespace CS64.Core.Interface.Input 4 | { 5 | public class AnsiKeyboard : Keyboard 6 | { 7 | public override void SetupBinding() 8 | { 9 | //TODO: Bug - when holding SHIFT and alternating between characters rapidly, 10 | // you may end up with unshifted characters 11 | Bind(SDL_Keycode.SDLK_UP, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.CURSOR_DOWN }); 12 | Bind(SDL_Keycode.SDLK_DOWN, new[] { InputKeyEnum.CURSOR_DOWN }); 13 | Bind(SDL_Keycode.SDLK_LEFT, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.CURSOR_RIGHT }); 14 | Bind(SDL_Keycode.SDLK_RIGHT, new[] { InputKeyEnum.CURSOR_RIGHT }); 15 | 16 | BindWithShift(SDL_Keycode.SDLK_BACKSPACE, InputKeyEnum.DELETE); 17 | Bind(SDL_Keycode.SDLK_SPACE, new[] { InputKeyEnum.SPACE }); 18 | Bind(SDL_Keycode.SDLK_RETURN, new[] { InputKeyEnum.RETURN }); 19 | 20 | Bind(SDL_Keycode.SDLK_LSHIFT, SDL_Keymod.KMOD_LSHIFT, InputKeyEnum.LSHIFT); 21 | // When releasing shift, the modifer is gone! 22 | // Need to bind to this as well or we won't detect when SHIFT was released 23 | Bind(SDL_Keycode.SDLK_LSHIFT, InputKeyEnum.LSHIFT); 24 | Bind(SDL_Keycode.SDLK_RSHIFT, SDL_Keymod.KMOD_RSHIFT, InputKeyEnum.RSHIFT); 25 | Bind(SDL_Keycode.SDLK_RSHIFT, InputKeyEnum.RSHIFT); 26 | 27 | Bind(SDL_Keycode.SDLK_COLON, new[] { InputKeyEnum.COLON }); 28 | Bind(SDL_Keycode.SDLK_SEMICOLON, new[] { InputKeyEnum.SEMICOLON }); 29 | Bind(SDL_Keycode.SDLK_QUOTE, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.D7 }); 30 | Bind(SDL_Keycode.SDLK_QUOTE, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.D2 }); 31 | Bind(SDL_Keycode.SDLK_QUOTE, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.RSHIFT, InputKeyEnum.D2 }); 32 | 33 | Bind(SDL_Keycode.SDLK_COMMA, new[] { InputKeyEnum.COMMA }); 34 | Bind(SDL_Keycode.SDLK_PERIOD, new[] { InputKeyEnum.PERIOD }); 35 | 36 | BindWithShift(SDL_Keycode.SDLK_HOME, InputKeyEnum.HOME); 37 | BindWithShift(SDL_Keycode.SDLK_ESCAPE, InputKeyEnum.STOP); 38 | //_keyMapping.Add(SDL_Keycode.SDLK_ESCAPE, new[] { InputKeyEnum.RESTORE }); 39 | Bind(SDL_Keycode.SDLK_LALT, new[] { InputKeyEnum.CTRL }); 40 | Bind(SDL_Keycode.SDLK_RALT, new[] { InputKeyEnum.CTRL }); 41 | 42 | Bind(SDL_Keycode.SDLK_SLASH, new[] { InputKeyEnum.SLASH }); 43 | Bind(SDL_Keycode.SDLK_LEFTBRACKET, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.COLON }); 44 | Bind(SDL_Keycode.SDLK_RIGHTBRACKET, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.SEMICOLON }); 45 | 46 | Bind(SDL_Keycode.SDLK_EQUALS, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.PLUS }); 47 | Bind(SDL_Keycode.SDLK_EQUALS, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.PLUS }); 48 | 49 | Bind(SDL_Keycode.SDLK_2, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.AT }); 50 | Bind(SDL_Keycode.SDLK_2, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.AT }); 51 | Bind(SDL_Keycode.SDLK_6, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.CARET }); 52 | Bind(SDL_Keycode.SDLK_6, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.CARET }); 53 | Bind(SDL_Keycode.SDLK_7, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.POUND }); 54 | Bind(SDL_Keycode.SDLK_7, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.POUND }); 55 | Bind(SDL_Keycode.SDLK_8, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.ASTERISK }); 56 | Bind(SDL_Keycode.SDLK_8, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.ASTERISK }); 57 | Bind(SDL_Keycode.SDLK_9, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.D8 }); 58 | Bind(SDL_Keycode.SDLK_9, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.RSHIFT, InputKeyEnum.D8 }); 59 | Bind(SDL_Keycode.SDLK_0, SDL_Keymod.KMOD_LSHIFT, new[] { InputKeyEnum.LSHIFT, InputKeyEnum.D9 }); 60 | Bind(SDL_Keycode.SDLK_0, SDL_Keymod.KMOD_RSHIFT, new[] { InputKeyEnum.RSHIFT, InputKeyEnum.D9 }); 61 | 62 | Bind(SDL_Keycode.SDLK_EQUALS, new[] { InputKeyEnum.EQUALS }); 63 | Bind(SDL_Keycode.SDLK_MINUS, new[] { InputKeyEnum.MINUS }); 64 | Bind(SDL_Keycode.SDLK_BACKSLASH, new[] { InputKeyEnum.EQUALS }); 65 | 66 | BindWithShift(SDL_Keycode.SDLK_1, InputKeyEnum.D1); 67 | Bind(SDL_Keycode.SDLK_2, InputKeyEnum.D2); 68 | BindWithShift(SDL_Keycode.SDLK_3, InputKeyEnum.D3); 69 | BindWithShift(SDL_Keycode.SDLK_4, InputKeyEnum.D4); 70 | BindWithShift(SDL_Keycode.SDLK_5, InputKeyEnum.D5); 71 | Bind(SDL_Keycode.SDLK_6, InputKeyEnum.D6); 72 | Bind(SDL_Keycode.SDLK_7, InputKeyEnum.D7); 73 | Bind(SDL_Keycode.SDLK_8, InputKeyEnum.D8); 74 | Bind(SDL_Keycode.SDLK_9, InputKeyEnum.D9); 75 | Bind(SDL_Keycode.SDLK_0, InputKeyEnum.D0); 76 | 77 | BindWithShift(SDL_Keycode.SDLK_a, InputKeyEnum.A); 78 | BindWithShift(SDL_Keycode.SDLK_b, InputKeyEnum.B); 79 | BindWithShift(SDL_Keycode.SDLK_c, InputKeyEnum.C); 80 | BindWithShift(SDL_Keycode.SDLK_d, InputKeyEnum.D); 81 | BindWithShift(SDL_Keycode.SDLK_e, InputKeyEnum.E); 82 | BindWithShift(SDL_Keycode.SDLK_f, InputKeyEnum.F); 83 | BindWithShift(SDL_Keycode.SDLK_g, InputKeyEnum.G); 84 | BindWithShift(SDL_Keycode.SDLK_h, InputKeyEnum.H); 85 | BindWithShift(SDL_Keycode.SDLK_i, InputKeyEnum.I); 86 | BindWithShift(SDL_Keycode.SDLK_j, InputKeyEnum.J); 87 | BindWithShift(SDL_Keycode.SDLK_k, InputKeyEnum.K); 88 | BindWithShift(SDL_Keycode.SDLK_l, InputKeyEnum.L); 89 | BindWithShift(SDL_Keycode.SDLK_m, InputKeyEnum.M); 90 | BindWithShift(SDL_Keycode.SDLK_n, InputKeyEnum.N); 91 | BindWithShift(SDL_Keycode.SDLK_o, InputKeyEnum.O); 92 | BindWithShift(SDL_Keycode.SDLK_p, InputKeyEnum.P); 93 | BindWithShift(SDL_Keycode.SDLK_q, InputKeyEnum.Q); 94 | BindWithShift(SDL_Keycode.SDLK_r, InputKeyEnum.R); 95 | BindWithShift(SDL_Keycode.SDLK_s, InputKeyEnum.S); 96 | BindWithShift(SDL_Keycode.SDLK_t, InputKeyEnum.T); 97 | BindWithShift(SDL_Keycode.SDLK_u, InputKeyEnum.U); 98 | BindWithShift(SDL_Keycode.SDLK_v, InputKeyEnum.V); 99 | BindWithShift(SDL_Keycode.SDLK_w, InputKeyEnum.W); 100 | BindWithShift(SDL_Keycode.SDLK_x, InputKeyEnum.X); 101 | BindWithShift(SDL_Keycode.SDLK_y, InputKeyEnum.Y); 102 | BindWithShift(SDL_Keycode.SDLK_z, InputKeyEnum.Z); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/C64Keyboard.cs: -------------------------------------------------------------------------------- 1 | using static SDL2.SDL; 2 | 3 | namespace CS64.Core.Interface.Input 4 | { 5 | public class C64Keyboard : Keyboard 6 | { 7 | public override void SetupBinding() 8 | { 9 | BindWithShift(SDL_Keycode.SDLK_UP, InputKeyEnum.CURSOR_DOWN); 10 | BindWithShift(SDL_Keycode.SDLK_DOWN, InputKeyEnum.CURSOR_DOWN); 11 | BindWithShift(SDL_Keycode.SDLK_LEFT, InputKeyEnum.CURSOR_RIGHT); 12 | BindWithShift(SDL_Keycode.SDLK_RIGHT, InputKeyEnum.CURSOR_RIGHT); 13 | 14 | BindWithShift(SDL_Keycode.SDLK_DELETE, InputKeyEnum.DELETE); 15 | BindWithShift(SDL_Keycode.SDLK_BACKSPACE, InputKeyEnum.BACKSPACE); 16 | BindWithShift(SDL_Keycode.SDLK_SPACE, InputKeyEnum.SPACE); 17 | BindWithShift(SDL_Keycode.SDLK_RETURN, InputKeyEnum.RETURN); 18 | 19 | Bind(SDL_Keycode.SDLK_LSHIFT, SDL_Keymod.KMOD_LSHIFT, InputKeyEnum.LSHIFT); 20 | // When releasing shift, the modifer is gone! 21 | // Need to bind to this as well or we won't detect when SHIFT was released 22 | Bind(SDL_Keycode.SDLK_LSHIFT, InputKeyEnum.LSHIFT); 23 | 24 | Bind(SDL_Keycode.SDLK_RSHIFT, SDL_Keymod.KMOD_RSHIFT, InputKeyEnum.RSHIFT); 25 | Bind(SDL_Keycode.SDLK_RSHIFT, InputKeyEnum.RSHIFT); 26 | 27 | BindWithShift(SDL_Keycode.SDLK_SEMICOLON, InputKeyEnum.COLON); 28 | BindWithShift(SDL_Keycode.SDLK_QUOTE, InputKeyEnum.SEMICOLON); 29 | 30 | BindWithShift(SDL_Keycode.SDLK_COMMA, InputKeyEnum.COMMA); 31 | BindWithShift(SDL_Keycode.SDLK_PERIOD, InputKeyEnum.PERIOD); 32 | 33 | BindWithShift(SDL_Keycode.SDLK_HOME, InputKeyEnum.HOME); 34 | BindWithShift(SDL_Keycode.SDLK_END, InputKeyEnum.STOP); 35 | BindWithShift(SDL_Keycode.SDLK_ESCAPE, InputKeyEnum.RESTORE); 36 | BindWithShift(SDL_Keycode.SDLK_LALT, InputKeyEnum.CTRL); 37 | BindWithShift(SDL_Keycode.SDLK_RALT, InputKeyEnum.CTRL); 38 | 39 | BindWithShift(SDL_Keycode.SDLK_SLASH, InputKeyEnum.SLASH); 40 | BindWithShift(SDL_Keycode.SDLK_LEFTBRACKET, InputKeyEnum.AT); 41 | BindWithShift(SDL_Keycode.SDLK_RIGHTBRACKET, InputKeyEnum.ASTERISK); 42 | 43 | //TODO: CARET (uparrow) 44 | //_keyMapping.Add(SDL_Keycode.SDLK_BACKSLASH, InputKeyEnum.CARET); 45 | BindWithShift(SDL_Keycode.SDLK_EQUALS, InputKeyEnum.PLUS); 46 | BindWithShift(SDL_Keycode.SDLK_MINUS, InputKeyEnum.MINUS); 47 | BindWithShift(SDL_Keycode.SDLK_BACKSLASH, InputKeyEnum.EQUALS); 48 | 49 | BindWithShift(SDL_Keycode.SDLK_0, InputKeyEnum.D0); 50 | BindWithShift(SDL_Keycode.SDLK_1, InputKeyEnum.D1); 51 | BindWithShift(SDL_Keycode.SDLK_2, InputKeyEnum.D2); 52 | BindWithShift(SDL_Keycode.SDLK_3, InputKeyEnum.D3); 53 | BindWithShift(SDL_Keycode.SDLK_4, InputKeyEnum.D4); 54 | BindWithShift(SDL_Keycode.SDLK_5, InputKeyEnum.D5); 55 | BindWithShift(SDL_Keycode.SDLK_6, InputKeyEnum.D6); 56 | BindWithShift(SDL_Keycode.SDLK_7, InputKeyEnum.D7); 57 | BindWithShift(SDL_Keycode.SDLK_8, InputKeyEnum.D8); 58 | BindWithShift(SDL_Keycode.SDLK_9, InputKeyEnum.D9); 59 | 60 | BindWithShift(SDL_Keycode.SDLK_a, InputKeyEnum.A); 61 | BindWithShift(SDL_Keycode.SDLK_b, InputKeyEnum.B); 62 | BindWithShift(SDL_Keycode.SDLK_c, InputKeyEnum.C); 63 | BindWithShift(SDL_Keycode.SDLK_d, InputKeyEnum.D); 64 | BindWithShift(SDL_Keycode.SDLK_e, InputKeyEnum.E); 65 | BindWithShift(SDL_Keycode.SDLK_f, InputKeyEnum.F); 66 | BindWithShift(SDL_Keycode.SDLK_g, InputKeyEnum.G); 67 | BindWithShift(SDL_Keycode.SDLK_h, InputKeyEnum.H); 68 | BindWithShift(SDL_Keycode.SDLK_i, InputKeyEnum.I); 69 | BindWithShift(SDL_Keycode.SDLK_j, InputKeyEnum.J); 70 | BindWithShift(SDL_Keycode.SDLK_k, InputKeyEnum.K); 71 | BindWithShift(SDL_Keycode.SDLK_l, InputKeyEnum.L); 72 | BindWithShift(SDL_Keycode.SDLK_m, InputKeyEnum.M); 73 | BindWithShift(SDL_Keycode.SDLK_n, InputKeyEnum.N); 74 | BindWithShift(SDL_Keycode.SDLK_o, InputKeyEnum.O); 75 | BindWithShift(SDL_Keycode.SDLK_p, InputKeyEnum.P); 76 | BindWithShift(SDL_Keycode.SDLK_q, InputKeyEnum.Q); 77 | BindWithShift(SDL_Keycode.SDLK_r, InputKeyEnum.R); 78 | BindWithShift(SDL_Keycode.SDLK_s, InputKeyEnum.S); 79 | BindWithShift(SDL_Keycode.SDLK_t, InputKeyEnum.T); 80 | BindWithShift(SDL_Keycode.SDLK_u, InputKeyEnum.U); 81 | BindWithShift(SDL_Keycode.SDLK_v, InputKeyEnum.V); 82 | BindWithShift(SDL_Keycode.SDLK_w, InputKeyEnum.W); 83 | BindWithShift(SDL_Keycode.SDLK_x, InputKeyEnum.X); 84 | BindWithShift(SDL_Keycode.SDLK_y, InputKeyEnum.Y); 85 | BindWithShift(SDL_Keycode.SDLK_z, InputKeyEnum.Z); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/Controller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using static SDL2.SDL; 4 | 5 | namespace CS64.Core.Interface.Input 6 | { 7 | public class Controller 8 | { 9 | private IntPtr gamepad; 10 | private IntPtr joystick; 11 | private int instanceId; 12 | 13 | private readonly Dictionary _buttonMapping = new Dictionary(); 14 | 15 | public int ControllerIndex { get; set; } 16 | 17 | public Controller() 18 | { 19 | //_buttonMapping.Add(11, InputKeyEnum.Up); 20 | //_buttonMapping.Add(12, InputKeyEnum.Down); 21 | //_buttonMapping.Add(13, InputKeyEnum.Left); 22 | //_buttonMapping.Add(14, InputKeyEnum.Right); 23 | //_buttonMapping.Add(3, InputKeyEnum.B); 24 | //_buttonMapping.Add(2, InputKeyEnum.A); 25 | //_buttonMapping.Add(1, InputKeyEnum.B); 26 | //_buttonMapping.Add(0, InputKeyEnum.A); 27 | //_buttonMapping.Add(4, InputKeyEnum.Select); 28 | //_buttonMapping.Add(6, InputKeyEnum.Start); 29 | } 30 | 31 | public bool TryMap(int button, out InputKeyEnum[] mappedInput) 32 | { 33 | return _buttonMapping.TryGetValue(button, out mappedInput); 34 | } 35 | 36 | public static bool TryOpen(int deviceId, out Controller controller) 37 | { 38 | controller = new Controller(); 39 | controller.gamepad = SDL_GameControllerOpen(deviceId); 40 | controller.joystick = SDL_GameControllerGetJoystick(controller.gamepad); 41 | controller.instanceId = SDL_JoystickInstanceID(controller.joystick); 42 | return true; 43 | } 44 | 45 | public void Close() 46 | { 47 | SDL_GameControllerClose(gamepad); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/InputEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Interface.Input 2 | { 3 | public delegate void InputEvent(object sender, InputEventArgs args); 4 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/InputEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Interface.Input 2 | { 3 | public class InputEventArgs 4 | { 5 | public int Player { get; set; } 6 | public InputEventType EventType { get; set; } 7 | public InputKeyEnum[] Keys { get; set; } 8 | public int LightPenX { get; set; } 9 | public int LightPenY { get; set; } 10 | public bool LigntPenTrigger { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/InputEventType.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Interface.Input 2 | { 3 | public enum InputEventType 4 | { 5 | BUTTON_UP, 6 | BUTTON_DOWN, 7 | POINT, 8 | TRIGGER 9 | } 10 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/InputKeyEnum.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Interface.Input 2 | { 3 | public enum InputKeyEnum 4 | { 5 | // Add offset for controller buttons to make things easier 6 | BACKSPACE, 7 | D1, 8 | D2, 9 | D3, 10 | D4, 11 | D5, 12 | D6, 13 | D7, 14 | D8, 15 | D9, 16 | D0, 17 | PLUS, 18 | MINUS, 19 | POUND, 20 | HOME, 21 | DELETE, 22 | CTRL, 23 | AT, 24 | ASTERISK, 25 | CARET, 26 | RESTORE, 27 | STOP, 28 | COLON, 29 | SEMICOLON, 30 | EQUALS, 31 | RETURN, 32 | COMMODORE, 33 | LSHIFT, 34 | COMMA, 35 | PERIOD, 36 | SLASH, 37 | RSHIFT, 38 | CURSOR_DOWN, 39 | CURSOR_RIGHT, 40 | F1, 41 | F3, 42 | F5, 43 | F7, 44 | A, 45 | B, 46 | C, 47 | D, 48 | E, 49 | F, 50 | G, 51 | H, 52 | I, 53 | J, 54 | K, 55 | L, 56 | M, 57 | N, 58 | O, 59 | P, 60 | Q, 61 | R, 62 | S, 63 | T, 64 | U, 65 | V, 66 | W, 67 | X, 68 | Y, 69 | Z, 70 | SPACE, 71 | // Standard buttons go here 72 | Rewind = 0x1001, 73 | FastForward = 0x1002, 74 | SaveState = 0x1003, 75 | LoadState = 0x1004, 76 | NextState = 0x1005, 77 | PreviousState = 0x1006, 78 | } 79 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/InputProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using static SDL2.SDL; 4 | 5 | namespace CS64.Core.Interface.Input 6 | { 7 | public class InputProvider 8 | { 9 | private readonly Dictionary _deviceControllerMapping = new Dictionary(); 10 | 11 | private readonly Keyboard _keyboard = new AnsiKeyboard(); 12 | 13 | public InputEvent InputEvent { get; set; } 14 | 15 | public InputProvider(InputEvent inputEvent) 16 | { 17 | InputEvent = inputEvent; 18 | } 19 | 20 | public bool HandleEvent(SDL_MouseMotionEvent motionEvent) 21 | { 22 | // Get the position on screen where the Zapper is pointed at 23 | InputEvent?.Invoke(null, new InputEventArgs() { EventType = InputEventType.POINT, LightPenX = motionEvent.x, LightPenY = motionEvent.y }); 24 | return true; 25 | } 26 | 27 | public bool HandleEvent(SDL_MouseButtonEvent buttonEvent) 28 | { 29 | InputEvent?.Invoke(null, new InputEventArgs() { EventType = InputEventType.TRIGGER }); 30 | return true; 31 | } 32 | 33 | public bool HandleEvent(SDL_KeyboardEvent keyboardEvent) 34 | { 35 | var handled = false; 36 | 37 | switch (keyboardEvent.type) 38 | { 39 | case SDL_EventType.SDL_KEYUP: 40 | { 41 | if (_keyboard.TryMap(keyboardEvent.keysym.sym, keyboardEvent.keysym.mod, out InputKeyEnum[] mappedInput)) 42 | { 43 | InputEvent?.Invoke(null, new InputEventArgs() { EventType = InputEventType.BUTTON_UP, Player = 0, Keys = mappedInput }); 44 | handled = true; 45 | } 46 | 47 | break; 48 | } 49 | case SDL_EventType.SDL_KEYDOWN: 50 | { 51 | if (_mappingMode) 52 | { 53 | if (_keyboard.TrySetMap(keyboardEvent.keysym.sym, keyboardEvent.keysym.mod, _mappingTargets)) 54 | { 55 | _mappingMode = false; 56 | handled = true; 57 | } 58 | } 59 | else 60 | { 61 | if (_keyboard.TryMap(keyboardEvent.keysym.sym, keyboardEvent.keysym.mod, out InputKeyEnum[] mappedInput)) 62 | { 63 | InputEvent?.Invoke(null, new InputEventArgs() { EventType = InputEventType.BUTTON_DOWN, Player = 0, Keys = mappedInput }); 64 | handled = true; 65 | } 66 | } 67 | 68 | break; 69 | } 70 | } 71 | return handled; 72 | } 73 | 74 | public bool HandleControllerEvent(SDL_ControllerButtonEvent buttonEvent) 75 | { 76 | var handled = false; 77 | switch (buttonEvent.type) 78 | { 79 | case SDL_EventType.SDL_CONTROLLERBUTTONUP: 80 | Console.WriteLine($"{buttonEvent.type} {buttonEvent.button} {buttonEvent.which}"); 81 | { 82 | if (_deviceControllerMapping.TryGetValue(buttonEvent.which, out var controller)) 83 | { 84 | if (controller.TryMap(buttonEvent.button, out InputKeyEnum[] mappedInput)) 85 | { 86 | InputEvent?.Invoke(null, new InputEventArgs() { EventType = InputEventType.BUTTON_UP, Player = controller.ControllerIndex, Keys = mappedInput }); 87 | handled = true; 88 | } 89 | } 90 | } 91 | break; 92 | 93 | case SDL_EventType.SDL_CONTROLLERBUTTONDOWN: 94 | Console.WriteLine($"{buttonEvent.type} {buttonEvent.button} {buttonEvent.which}"); 95 | { 96 | if (_deviceControllerMapping.TryGetValue(buttonEvent.which, out var controller)) 97 | { 98 | if (controller.TryMap(buttonEvent.button, out InputKeyEnum[] mappedInput)) 99 | { 100 | InputEvent?.Invoke(null, new InputEventArgs() { EventType = InputEventType.BUTTON_DOWN, Player = controller.ControllerIndex, Keys = mappedInput }); 101 | handled = true; 102 | } 103 | } 104 | } 105 | break; 106 | 107 | } 108 | 109 | return handled; 110 | } 111 | 112 | public void HandleDeviceEvent(SDL_ControllerDeviceEvent deviceEvent) 113 | { 114 | //Console.WriteLine($"{deviceEvent.type} {deviceEvent.which}"); 115 | 116 | switch (deviceEvent.type) 117 | { 118 | case SDL_EventType.SDL_CONTROLLERDEVICEADDED: 119 | { 120 | if (Controller.TryOpen(deviceEvent.which, out var controller)) 121 | { 122 | _deviceControllerMapping.Add(deviceEvent.which, controller); 123 | } 124 | 125 | } 126 | break; 127 | case SDL_EventType.SDL_CONTROLLERDEVICEREMOVED: 128 | { 129 | if (_deviceControllerMapping.TryGetValue(deviceEvent.which, out var controller)) 130 | { 131 | controller.Close(); 132 | _deviceControllerMapping.Remove(deviceEvent.which); 133 | } 134 | } 135 | break; 136 | } 137 | } 138 | 139 | 140 | private bool _mappingMode = false; 141 | private InputKeyEnum[] _mappingTargets; 142 | 143 | public void SetMapping(InputKeyEnum[] key) 144 | { 145 | _mappingMode = true; 146 | _mappingTargets = key; 147 | } 148 | 149 | public void ClearMapping(InputKeyEnum key) 150 | { 151 | //_mappingTarget = button; 152 | } 153 | 154 | public string GetMapping(InputKeyEnum[] key) 155 | { 156 | if (_keyboard.TryGetMap(key, out var mappedKey)) 157 | { 158 | return mappedKey.ToString(); 159 | } 160 | 161 | return ""; 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/KeyBinding.cs: -------------------------------------------------------------------------------- 1 | using static SDL2.SDL; 2 | 3 | namespace CS64.Core.Interface.Input 4 | { 5 | public struct KeyBinding 6 | { 7 | public KeyBinding(SDL_Keycode keyCode) 8 | { 9 | KeyCode = keyCode; 10 | Modifier = SDL_Keymod.KMOD_NONE; 11 | } 12 | 13 | public KeyBinding(SDL_Keycode keyCode, SDL_Keymod modifier) 14 | { 15 | KeyCode = keyCode; 16 | // My keyboard is stuck on NUM 17 | modifier &= (SDL_Keymod)((int)(SDL_Keymod.KMOD_NUM) ^ 0xFFFF); 18 | modifier &= (SDL_Keymod)((int)(SDL_Keymod.KMOD_CAPS) ^ 0xFFFF); 19 | Modifier = modifier; 20 | } 21 | 22 | public SDL_Keycode KeyCode { get; set; } 23 | public SDL_Keymod Modifier { get; set; } 24 | 25 | public override bool Equals(object obj) 26 | { 27 | return obj != null && 28 | KeyCode == ((KeyBinding)obj).KeyCode && 29 | Modifier == ((KeyBinding)obj).Modifier; 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | unchecked // Overflow is fine, just wrap 35 | { 36 | int hash = 7199369; 37 | // Suitable nullity checks etc, of course :) 38 | hash = hash * 486187739 + KeyCode.GetHashCode(); 39 | hash = hash * 486187739 + Modifier.GetHashCode(); 40 | return hash; 41 | } 42 | 43 | //return HashCode.Combine(KeyCode, Modifier); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Input/Keyboard.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using static SDL2.SDL; 4 | 5 | namespace CS64.Core.Interface.Input 6 | { 7 | public abstract class Keyboard 8 | { 9 | 10 | protected readonly Dictionary _keyMapping = new Dictionary(); 11 | 12 | protected Keyboard() 13 | { 14 | SetupBinding(); 15 | } 16 | 17 | public abstract void SetupBinding(); 18 | 19 | public bool TryMap(SDL_Keycode keyCode, SDL_Keymod modifier, out InputKeyEnum[] mappedInputs) 20 | { 21 | return _keyMapping.TryGetValue(new KeyBinding(keyCode, modifier), out mappedInputs); 22 | } 23 | 24 | public bool TrySetMap(SDL_Keycode keyCode, SDL_Keymod modifier, InputKeyEnum[] target) 25 | { 26 | if (TryGetMap(target, out var oldkey)) 27 | { 28 | _keyMapping.Remove(oldkey.Value); 29 | } 30 | _keyMapping[new KeyBinding(keyCode, modifier)] = target; 31 | return true; 32 | } 33 | 34 | public bool TryGetMap(InputKeyEnum[] key, out KeyBinding? mappedKey) 35 | { 36 | mappedKey = null; 37 | 38 | if (_keyMapping.ContainsValue(key)) 39 | { 40 | var mapping = _keyMapping.First(m => m.Value == key); 41 | mappedKey = mapping.Key; 42 | return true; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | protected void BindWithShift(SDL_Keycode keyCode, InputKeyEnum key) 49 | { 50 | _keyMapping.Add(new KeyBinding(keyCode), new[] { key }); 51 | _keyMapping.Add(new KeyBinding(keyCode, SDL_Keymod.KMOD_LSHIFT), new[] { InputKeyEnum.LSHIFT, key }); 52 | _keyMapping.Add(new KeyBinding(keyCode, SDL_Keymod.KMOD_RSHIFT), new[] { InputKeyEnum.RSHIFT, key }); 53 | _keyMapping.Add(new KeyBinding(keyCode, SDL_Keymod.KMOD_ALT), new[] { InputKeyEnum.CTRL, key }); 54 | } 55 | 56 | protected void Bind(SDL_Keycode keyCode, InputKeyEnum key) 57 | { 58 | _keyMapping.Add(new KeyBinding(keyCode), new[] { key }); 59 | } 60 | 61 | protected void Bind(SDL_Keycode keyCode, InputKeyEnum[] keys) 62 | { 63 | _keyMapping.Add(new KeyBinding(keyCode), keys); 64 | } 65 | 66 | protected void Bind(SDL_Keycode keyCode, SDL_Keymod modifer, InputKeyEnum[] keys) 67 | { 68 | _keyMapping.Add(new KeyBinding(keyCode, modifer), keys); 69 | } 70 | 71 | protected void Bind(SDL_Keycode keyCode, SDL_Keymod modifer, InputKeyEnum key) 72 | { 73 | _keyMapping.Add(new KeyBinding(keyCode, modifer), new []{key}); 74 | } 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/KeyboardMatrix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CS64.Core.Interface.Input; 3 | 4 | namespace CS64.Core.Interface 5 | { 6 | public class KeyboardMatrix 7 | { 8 | // The COLUMN is stored in the first dimension (vertically here) 9 | // The ROW is stored in the second dimension (horizontally here) 10 | // COLUMN is the value strobed in CIA2 Port A 11 | // ROW is the value read in CIA2 Port B 12 | 13 | private static InputKeyEnum[,] _lookup = new InputKeyEnum[,] 14 | { 15 | { 16 | InputKeyEnum.STOP, InputKeyEnum.Q, InputKeyEnum.COMMODORE, InputKeyEnum.SPACE, InputKeyEnum.D2, 17 | InputKeyEnum.CTRL, InputKeyEnum.BACKSPACE, InputKeyEnum.D1 18 | }, 19 | { 20 | InputKeyEnum.SLASH, InputKeyEnum.CARET, InputKeyEnum.EQUALS, InputKeyEnum.RSHIFT, InputKeyEnum.HOME, 21 | InputKeyEnum.SEMICOLON, InputKeyEnum.ASTERISK, InputKeyEnum.POUND 22 | }, 23 | { 24 | InputKeyEnum.COMMA, InputKeyEnum.AT, InputKeyEnum.COLON, InputKeyEnum.PERIOD, InputKeyEnum.MINUS, 25 | InputKeyEnum.L, InputKeyEnum.P, InputKeyEnum.PLUS 26 | }, 27 | { 28 | InputKeyEnum.N, InputKeyEnum.O, InputKeyEnum.K, InputKeyEnum.M, InputKeyEnum.D0, InputKeyEnum.J, 29 | InputKeyEnum.I, InputKeyEnum.D9 30 | }, 31 | { 32 | InputKeyEnum.V, InputKeyEnum.U, InputKeyEnum.H, InputKeyEnum.B, InputKeyEnum.D8, 33 | InputKeyEnum.G, InputKeyEnum.Y, InputKeyEnum.D7 34 | }, 35 | { 36 | InputKeyEnum.X, InputKeyEnum.T, InputKeyEnum.F, InputKeyEnum.C, InputKeyEnum.D6, 37 | InputKeyEnum.D, InputKeyEnum.R, InputKeyEnum.D5 38 | }, 39 | { 40 | InputKeyEnum.LSHIFT, InputKeyEnum.E, InputKeyEnum.S, InputKeyEnum.Z, InputKeyEnum.D4, 41 | InputKeyEnum.A, InputKeyEnum.W, InputKeyEnum.D3 42 | }, 43 | { 44 | InputKeyEnum.CURSOR_DOWN, InputKeyEnum.F5, InputKeyEnum.F3, InputKeyEnum.F1, InputKeyEnum.F7, 45 | InputKeyEnum.CURSOR_RIGHT, InputKeyEnum.RETURN, InputKeyEnum.DELETE 46 | }, 47 | }; 48 | 49 | public Dictionary _matrix = new Dictionary(); 50 | 51 | public uint[] ColumnIndex = new uint[129]; 52 | 53 | public KeyboardMatrix() 54 | { 55 | // Produce the COL,ROW values for each key in the lookup 56 | // We COULD have just hardcoded this, but I was feeling lazy 57 | 58 | for (int col = 0; col < 8; col++) 59 | { 60 | for (int row = 0; row < 8; row++) 61 | { 62 | var key = _lookup[col, row]; 63 | 64 | _matrix.Add(key, new RowCol((uint)(1 << (7 - col)), (uint)(1 << (7 - row)))); 65 | } 66 | } 67 | 68 | // This is for quick lookup from the inverse of strobe position (0x01 to 0x80) to a column index lookup (0-7) 69 | // Note that we create a 129-byte array, and most of it will be empty 70 | 71 | for (var i = 0; i < 8; i++) 72 | { 73 | ColumnIndex[1 << i] = (uint)i; 74 | } 75 | } 76 | 77 | public bool TryEncode(InputKeyEnum key, out RowCol rowCol) 78 | { 79 | return _matrix.TryGetValue(key, out rowCol); 80 | } 81 | 82 | } 83 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Main.Keyboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CS64.Core.Interface.Input; 3 | 4 | namespace CS64.Core.Interface 5 | { 6 | public partial class Main 7 | { 8 | private readonly KeyboardMatrix _keyboardMatrix = new KeyboardMatrix(); 9 | private bool DeletePressed = false; 10 | 11 | public uint key_col; 12 | private uint[] rowCols = new uint[8]; 13 | 14 | private void SetupKeyboardScanRoutines() 15 | { 16 | _c64.Cia1.PortAWrite = PortAWrite; 17 | _c64.Cia1.PortBRead = PortBRead; 18 | } 19 | 20 | private uint PortBRead() 21 | { 22 | // Initially scan routine pulls all outputs low 23 | // (now high since we flipped it) 24 | if (key_col == 0xFF && 25 | // check if anything pressed 26 | ((rowCols[0] | 27 | rowCols[1] | 28 | rowCols[2] | 29 | rowCols[3] | 30 | rowCols[4] | 31 | rowCols[5] | 32 | rowCols[6] | 33 | rowCols[7]) > 0) 34 | 35 | | DeletePressed 36 | ) 37 | { 38 | // send any non-zero value to trigger scan routine 39 | return 0x01; 40 | } 41 | 42 | if (DeletePressed & key_col == 1) 43 | { 44 | return 0xFE; 45 | } 46 | 47 | // The scan routine will now have a shifting 1 bit (never 0xFF) 48 | // So we can compare each column test 49 | if (key_col != 0xFF) 50 | { 51 | // convert the column bit into an index 52 | var index = _keyboardMatrix.ColumnIndex[key_col]; 53 | // invert the row bit because CIA expects the line to be pulled to 0 54 | return rowCols[index] ^ 0xFF; 55 | } 56 | 57 | return 0xFF; 58 | } 59 | 60 | private void PortAWrite(uint value) 61 | { 62 | // flip the output so we get a shifting 1 bit instead of 0 63 | key_col = value ^ 0xFF; 64 | } 65 | 66 | private void InputEvent(object sender, InputEventArgs args) 67 | { 68 | switch (args.EventType) 69 | { 70 | case InputEventType.BUTTON_UP: 71 | foreach (var key in args.Keys) 72 | { 73 | Console.Write(key); 74 | if (((uint)key & 0x1000) == 0x1000) 75 | { 76 | switch (key) 77 | { 78 | case InputKeyEnum.Rewind: 79 | _rewind = false; 80 | break; 81 | case InputKeyEnum.FastForward: 82 | _fastForward = false; 83 | break; 84 | } 85 | } 86 | else 87 | { 88 | if (key == InputKeyEnum.DELETE) 89 | { 90 | DeletePressed = false; 91 | } 92 | else 93 | { 94 | _keyboardMatrix.TryEncode(key, out var rowCol); 95 | var index = _keyboardMatrix.ColumnIndex[rowCol.Col]; 96 | // Clear specific bit only 97 | rowCols[index] &= rowCol.Row ^ 0xFF; 98 | } 99 | } 100 | } 101 | Console.WriteLine(); 102 | 103 | break; 104 | case InputEventType.BUTTON_DOWN: 105 | 106 | foreach (var key in args.Keys) 107 | { 108 | if (((uint)key & 0x1000) == 0x1000) 109 | { 110 | switch (key) 111 | { 112 | case InputKeyEnum.Rewind: 113 | _rewind = true; 114 | break; 115 | case InputKeyEnum.FastForward: 116 | _fastForward = true; 117 | break; 118 | case InputKeyEnum.SaveState: 119 | _saveStatePending = true; 120 | break; 121 | case InputKeyEnum.LoadState: 122 | _loadStatePending = true; 123 | break; 124 | case InputKeyEnum.NextState: 125 | break; 126 | case InputKeyEnum.PreviousState: 127 | break; 128 | } 129 | } 130 | else 131 | { 132 | // For some reason DELETE key doesn't work properly 133 | // so, force it to work 134 | if (key == InputKeyEnum.DELETE) 135 | { 136 | DeletePressed = true; 137 | } 138 | else 139 | { 140 | _keyboardMatrix.TryEncode(key, out var rowCol); 141 | var index = _keyboardMatrix.ColumnIndex[rowCol.Col]; 142 | // Setr specific bit without erasing any other bits 143 | rowCols[index] |= rowCol.Row; 144 | } 145 | } 146 | } 147 | 148 | break; 149 | } 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using CS64.Core.CPU; 9 | using CS64.Core.Interface.Input; 10 | using static SDL2.SDL; 11 | 12 | namespace CS64.Core.Interface 13 | { 14 | public partial class Main : IDisposable 15 | { 16 | //PAL C64 master clock: 17.734475 MHz / 18 17 | //NTSC C64 master clock: 14.31818 MHz / 14 18 | // 19 | //CLOCK_PAL = 985248 Hz * 8 20 | //CLOCK_NTSC = 1022727 Hz * 8 21 | 22 | 23 | //CLOCK_VICII_PAL = 7881984 Hz / 60 = 131366 cycles / frame 24 | //CLOCK_VICII_NTSC = 8181816 Hz / 60 = 136363 cycles / frame 25 | 26 | 27 | public const int CYCLES_PER_FRAME = 131366; 28 | 29 | //private const double SECONDS_PER_FRAME = 1 / 60D; 30 | private const double SECONDS_PER_FRAME = 1 / 50D; 31 | 32 | private double _fps; 33 | private int _cyclesLeft; 34 | private long _cyclesRan; 35 | private Thread _emulationThread; 36 | private readonly AutoResetEvent _threadSync = new AutoResetEvent(false); 37 | private bool _saveStatePending; 38 | private bool _loadStatePending; 39 | 40 | private bool _running; 41 | private string _romPath; 42 | private bool _resetPending; 43 | 44 | private bool _paused; 45 | private bool _frameAdvance; 46 | private bool _rewind; 47 | private bool _fastForward; 48 | private int _stateSlot = 1; 49 | 50 | private MC6502State _c64; 51 | 52 | private uint _frames; 53 | private bool _hasState; 54 | 55 | public Action StateChanged { get; set; } 56 | private AudioProvider _audioProvider; 57 | private VideoProvider _videoProvider; 58 | 59 | private IntPtr Window; 60 | 61 | public InputProvider InputProvider { get; private set; } 62 | public int Width => _c64.Vic.Width; 63 | public int Height => _c64.Vic.Height; 64 | 65 | private Playback _playback; 66 | 67 | 68 | 69 | private void SaveState(Stream stream) 70 | { 71 | _c64.WriteState(stream); 72 | //_c64.Ppu.WriteState(stream); 73 | //_c64.Cartridge.WriteState(stream); 74 | } 75 | 76 | private void LoadState(Stream stream) 77 | { 78 | _c64.ReadState(stream); 79 | //_c64.Ppu.ReadState(stream); 80 | //_c64.Cartridge.ReadState(stream); 81 | } 82 | 83 | private string GetStateSavePath() 84 | { 85 | return Path.Combine(_romDirectory, $"{_romFilename}.s{_stateSlot:00}"); 86 | } 87 | 88 | public void SaveState() 89 | { 90 | using (var file = new FileStream(GetStateSavePath(), FileMode.Create, FileAccess.Write)) 91 | { 92 | SaveState(file); 93 | _videoProvider.SetMessage($"Saved State #{_stateSlot}"); 94 | } 95 | _hasState = true; 96 | } 97 | 98 | public void LoadState() 99 | { 100 | var path = GetStateSavePath(); 101 | if (File.Exists(path)) 102 | { 103 | using (var file = new FileStream(path, FileMode.Open, FileAccess.Read)) 104 | { 105 | LoadState(file); 106 | _videoProvider.SetMessage($"Loaded State #{_stateSlot}"); 107 | } 108 | } 109 | } 110 | 111 | private int _scale = 4; 112 | 113 | public void SetMapping(InputKeyEnum o) 114 | { 115 | InputProvider.SetMapping(new[] { o }); 116 | } 117 | 118 | public void Initialize(IMainInterface form) 119 | { 120 | 121 | form.OnHostResize = () => 122 | { 123 | _videoProvider.Destroy(); 124 | _videoProvider.Initialize(); 125 | _videoProvider.Clear(); 126 | }; 127 | 128 | form.ChangeSlot = i => 129 | { 130 | _stateSlot = i; 131 | _videoProvider.SetMessage($"Slot #{_stateSlot}"); 132 | }; 133 | 134 | form.SaveState = SaveState; 135 | form.LoadState = LoadState; 136 | //form.ResizeWindow = (width, height) => 137 | //{ 138 | // _paused = true; 139 | // Thread.Sleep(200); 140 | // //_videoProvider.Resize(width, height); 141 | // _paused = false; 142 | //}; 143 | form.SetMapping = o => 144 | { 145 | InputProvider.SetMapping(new[] { o }); 146 | }; 147 | 148 | form.LoadRom = s => 149 | { 150 | try 151 | { 152 | LoadInternal(s); 153 | return true; 154 | } 155 | catch (Exception ex) 156 | { 157 | return false; 158 | } 159 | }; 160 | 161 | _playback = new Playback() 162 | { 163 | LoadState = LoadState, 164 | SaveState = SaveState 165 | }; 166 | 167 | 168 | 169 | _c64.Init(); 170 | SetupKeyboardScanRoutines(); 171 | 172 | 173 | SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK); 174 | 175 | SDL2.SDL_ttf.TTF_Init(); 176 | 177 | Window = SDL_CreateWindowFrom(form.Handle); 178 | 179 | //Window = SDL_CreateWindow("Fami", 0, 0, WIDTH * _scale, HEIGHT * _scale, 180 | // SDL_WindowFlags.SDL_WINDOW_SHOWN | SDL_WindowFlags.SDL_WINDOW_RESIZABLE ); 181 | 182 | _videoProvider = new VideoProvider(Window, _c64.Vic.Width, _c64.Vic.Height); 183 | _audioProvider = new AudioProvider(); 184 | InputProvider = new InputProvider(InputEvent); 185 | 186 | _videoProvider.Initialize(); 187 | _videoProvider.Clear(); 188 | _audioProvider.Initialize(); 189 | 190 | form.ChangeSlot(1); 191 | 192 | 193 | 194 | } 195 | 196 | private bool _justLoaded; 197 | 198 | private void LoadInternal(string path) 199 | { 200 | _paused = true; 201 | // Ensure frame rendering has completed so we don't overwrite anything while the emulation thread is busy 202 | Thread.Sleep(200); 203 | Load(path); 204 | _justLoaded = true; 205 | // prevent the first click fron firing when loading light gun games 206 | _paused = false; 207 | } 208 | 209 | public Main() 210 | { 211 | _c64 = new MC6502State(); 212 | } 213 | 214 | 215 | public void EmulationThreadHandler() 216 | { 217 | void Render() 218 | { 219 | _videoProvider.Render(_c64.Vic.buffer); 220 | 221 | _c64.GetSamplesSync(out var lsamples, out int lnsamp); 222 | 223 | _audioProvider.AudioReady(lsamples); 224 | } 225 | 226 | while (_running) 227 | { 228 | try 229 | { 230 | _threadSync.WaitOne(); 231 | 232 | if (_resetPending) 233 | { 234 | _c64.Reset(); 235 | _resetPending = false; 236 | } 237 | 238 | RunFrame(); 239 | 240 | while (_fastForward) 241 | { 242 | RunFrame(); 243 | Render(); 244 | } 245 | 246 | _playback.PerFrame(ref _rewind); 247 | 248 | if (_saveStatePending) 249 | { 250 | SaveState(); 251 | _saveStatePending = false; 252 | } 253 | 254 | if (_loadStatePending) 255 | { 256 | LoadState(); 257 | _loadStatePending = false; 258 | } 259 | 260 | Render(); 261 | 262 | _c64.Cia1.TimeOfDay(); 263 | _c64.Cia1.TimeOfDay(); 264 | 265 | } 266 | catch (Exception e) 267 | { 268 | Console.WriteLine(e.ToString()); 269 | //Excepted = true; 270 | } 271 | } 272 | } 273 | 274 | public void RunFrame() 275 | { 276 | _cyclesRan += CYCLES_PER_FRAME; 277 | _cyclesLeft += CYCLES_PER_FRAME; 278 | 279 | while (_cyclesLeft > 0) 280 | { 281 | _cyclesLeft -= (int)_c64.Step(); 282 | } 283 | 284 | 285 | _frames++; 286 | } 287 | 288 | public void Test() 289 | { 290 | _c64.Init(); 291 | _c64.Reset(); 292 | _c64.Execute(); 293 | } 294 | 295 | private string _romDirectory; 296 | private string _romFilename; 297 | 298 | public void LoadROM(string path, uint address, int size) 299 | { 300 | byte[] data = new byte[size]; 301 | 302 | using (var f = File.Open(path, FileMode.Open, FileAccess.Read)) 303 | { 304 | f.Read(data, 0, size); 305 | } 306 | 307 | _c64.LoadMemory(address, data); 308 | } 309 | 310 | 311 | 312 | public void Load(string rompath) 313 | { 314 | _romDirectory = ""; 315 | _romFilename = ""; 316 | 317 | 318 | if (Path.GetExtension(rompath).ToLower() == ".zip") 319 | { 320 | using var zipFile = ZipFile.Open(rompath, ZipArchiveMode.Read); 321 | var nesFile = zipFile.Entries.FirstOrDefault(z => Path.GetExtension(z.Name).ToLower() == ".nes"); 322 | if (nesFile == default) 323 | { 324 | throw new Exception("No ROM found in archive!"); 325 | } 326 | else 327 | { 328 | using var s = nesFile.Open(); 329 | //cart = Cartridge.Load(s, _c64); 330 | } 331 | } 332 | else 333 | { 334 | //cart = Cartridge.Load(rompath, _c64); 335 | } 336 | 337 | _romDirectory = Path.GetDirectoryName(rompath); 338 | _romFilename = Path.GetFileNameWithoutExtension(rompath); 339 | 340 | //_c64.LoadCartridge(cart); 341 | _c64.Reset(); 342 | 343 | _videoProvider.SetMessage($"Loaded {_romFilename}"); 344 | 345 | if (_emulationThread == null) 346 | { 347 | _emulationThread = new Thread(EmulationThreadHandler); 348 | _emulationThread.Name = "Emulation Core"; 349 | _emulationThread.Start(); 350 | } 351 | } 352 | 353 | 354 | public void Run() 355 | { 356 | if (_emulationThread == null) 357 | { 358 | _emulationThread = new Thread(EmulationThreadHandler); 359 | _emulationThread.Name = "Emulation Core"; 360 | _emulationThread.Start(); 361 | } 362 | 363 | _running = true; 364 | 365 | var nextFrameAt = GetTime(); 366 | double fpsEvalTimer = 0; 367 | 368 | void ResetTimers() 369 | { 370 | var time = GetTime(); 371 | nextFrameAt = time; 372 | fpsEvalTimer = time; 373 | } 374 | 375 | ResetTimers(); 376 | 377 | while (_running) 378 | { 379 | try 380 | { 381 | SDL_Event evt; 382 | while (SDL_PollEvent(out evt) != 0) 383 | { 384 | switch (evt.type) 385 | { 386 | case SDL_EventType.SDL_QUIT: 387 | _running = false; 388 | break; 389 | case SDL_EventType.SDL_KEYUP: 390 | case SDL_EventType.SDL_KEYDOWN: 391 | KeyEvent(evt.key); 392 | break; 393 | case SDL_EventType.SDL_CONTROLLERDEVICEADDED: 394 | case SDL_EventType.SDL_CONTROLLERDEVICEREMOVED: 395 | InputProvider.HandleDeviceEvent(evt.cdevice); 396 | break; 397 | case SDL_EventType.SDL_CONTROLLERBUTTONDOWN: 398 | case SDL_EventType.SDL_CONTROLLERBUTTONUP: 399 | InputProvider.HandleControllerEvent(evt.cbutton); 400 | break; 401 | case SDL_EventType.SDL_MOUSEMOTION: 402 | // _videoProvider.ToScreenCoordinates(evt.motion.x, evt.motion.y); 403 | break; 404 | case SDL_EventType.SDL_MOUSEBUTTONDOWN: 405 | // prevent the first click fron firing when loading light gun games 406 | if (_justLoaded) 407 | { 408 | _justLoaded = false; 409 | break; 410 | } 411 | 412 | break; 413 | 414 | case SDL_EventType.SDL_DROPFILE: 415 | var filename = Marshal.PtrToStringAnsi(evt.drop.file); 416 | LoadInternal(filename); 417 | 418 | break; 419 | default: 420 | //Console.WriteLine(evt.type); 421 | break; 422 | } 423 | } 424 | 425 | 426 | if (_paused) 427 | { 428 | if (_frameAdvance) 429 | { 430 | _frameAdvance = false; 431 | _threadSync.Set(); 432 | } 433 | } 434 | else 435 | { 436 | double currentSec = GetTime(); 437 | 438 | // Reset time if behind schedule 439 | if (currentSec - nextFrameAt >= SECONDS_PER_FRAME) 440 | { 441 | double diff = currentSec - nextFrameAt; 442 | Console.WriteLine("Can't keep up! Skipping " + (int)(diff * 1000) + " milliseconds"); 443 | nextFrameAt = currentSec; 444 | } 445 | 446 | if (currentSec >= nextFrameAt) 447 | { 448 | nextFrameAt += SECONDS_PER_FRAME; 449 | 450 | _threadSync.Set(); 451 | } 452 | 453 | if (currentSec >= fpsEvalTimer) 454 | { 455 | double diff = currentSec - fpsEvalTimer + 1; 456 | double frames = _cyclesRan / CYCLES_PER_FRAME; 457 | _cyclesRan = 0; 458 | 459 | //double mips = (double)Gba.Cpu.InstructionsRan / 1000000D; 460 | //Gba.Cpu.InstructionsRan = 0; 461 | 462 | // Use Math.Floor to truncate to 2 decimal places 463 | _fps = Math.Floor((frames / diff) * 100) / 100; 464 | //Mips = Math.Floor((mips / diff) * 100) / 100; 465 | UpdateTitle(); 466 | //Seconds++; 467 | 468 | fpsEvalTimer += 1; 469 | } 470 | } 471 | 472 | } 473 | catch (Exception e) 474 | { 475 | Console.WriteLine(e); 476 | } 477 | 478 | 479 | 480 | //Draw(); 481 | } 482 | 483 | _threadSync.Close(); 484 | 485 | } 486 | 487 | private void UpdateTitle() 488 | { 489 | SDL_SetWindowTitle( 490 | _videoProvider.Window, 491 | "CS64 - " + _fps + " fps" + _audioProvider.GetAudioSamplesInQueue() + " samples queued" 492 | ); 493 | } 494 | 495 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 496 | private bool HasModifier(SDL_KeyboardEvent evtKey, SDL_Keymod modifier) 497 | { 498 | return (evtKey.keysym.mod & modifier) == modifier; 499 | } 500 | 501 | private void KeyEvent(SDL_KeyboardEvent evtKey) 502 | { 503 | if (evtKey.type == SDL_EventType.SDL_KEYDOWN) 504 | { 505 | if (!InputProvider.HandleEvent(evtKey)) 506 | { 507 | if (HasModifier(evtKey, SDL_Keymod.KMOD_LCTRL) || HasModifier(evtKey, SDL_Keymod.KMOD_RCTRL)) 508 | { 509 | switch (evtKey.keysym.sym) 510 | { 511 | 512 | case SDL_Keycode.SDLK_BACKSLASH: 513 | _fastForward = true; 514 | _videoProvider.SetMessage("Fast Forward"); 515 | break; 516 | case SDL_Keycode.SDLK_p: 517 | _paused = !_paused; 518 | _videoProvider.SetMessage(_paused ? "Paused" : "Resumed"); 519 | break; 520 | case SDL_Keycode.SDLK_f: 521 | if (_paused) 522 | { 523 | _videoProvider.SetMessage("Frame Advanced"); 524 | _frameAdvance = true; 525 | } 526 | break; 527 | case SDL_Keycode.SDLK_BACKSPACE: 528 | if (!_rewind) 529 | { 530 | _videoProvider.SetMessage("Rewinding..."); 531 | _rewind = true; 532 | if (_paused) 533 | { 534 | _frameAdvance = true; 535 | } 536 | } 537 | break; 538 | } 539 | } 540 | 541 | 542 | switch (evtKey.keysym.sym) 543 | { 544 | case SDL_Keycode.SDLK_F10: 545 | _resetPending = true; 546 | _videoProvider.SetMessage("Reset"); 547 | break; 548 | 549 | case SDL_Keycode.SDLK_F2: 550 | _saveStatePending = true; 551 | break; 552 | 553 | case SDL_Keycode.SDLK_F4: 554 | _loadStatePending = true; 555 | break; 556 | 557 | case { } keyCode when keyCode >= SDL_Keycode.SDLK_0 && keyCode <= SDL_Keycode.SDLK_9: 558 | if (keyCode == SDL_Keycode.SDLK_0) 559 | { 560 | _stateSlot = 10; 561 | } 562 | else 563 | { 564 | _stateSlot = keyCode - SDL_Keycode.SDLK_0; 565 | } 566 | _videoProvider.SetMessage($"Slot #{_stateSlot}"); 567 | StateChanged?.Invoke(_stateSlot); 568 | break; 569 | 570 | } 571 | } 572 | } 573 | 574 | if (evtKey.type == SDL_EventType.SDL_KEYUP) 575 | { 576 | if (!InputProvider.HandleEvent(evtKey)) 577 | { 578 | if (HasModifier(evtKey, SDL_Keymod.KMOD_LCTRL) || HasModifier(evtKey, SDL_Keymod.KMOD_RCTRL)) 579 | { 580 | switch (evtKey.keysym.sym) 581 | { 582 | case SDL_Keycode.SDLK_BACKSLASH: 583 | _fastForward = false; 584 | break; 585 | case SDL_Keycode.SDLK_BACKSPACE: 586 | _rewind = false; 587 | break; 588 | } 589 | } 590 | } 591 | } 592 | } 593 | 594 | private static double GetTime() 595 | { 596 | return (double)SDL_GetPerformanceCounter() / (double)SDL_GetPerformanceFrequency(); 597 | } 598 | 599 | public void Dispose() 600 | { 601 | // Wait for renderers to finish what they're doing 602 | // Not guaranteed, but it works 603 | Thread.Sleep(500); 604 | _threadSync.Dispose(); 605 | _audioProvider.Dispose(); 606 | _videoProvider.Dispose(); 607 | SDL2.SDL_ttf.TTF_Quit(); 608 | SDL_AudioQuit(); 609 | SDL_VideoQuit(); 610 | SDL_DestroyWindow(Window); 611 | SDL_Quit(); 612 | } 613 | 614 | public void Redraw() 615 | { 616 | if (_emulationThread == null) 617 | { 618 | _videoProvider.Clear(); 619 | } 620 | } 621 | 622 | public void Reset() 623 | { 624 | _c64.Reset(); 625 | } 626 | } 627 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/Playback.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace CS64.Core.Interface 5 | { 6 | public class Playback 7 | { 8 | private const int MAX_REWIND_BUFFER = 512; 9 | private readonly MemoryStream[] _rewindStateBuffer = new MemoryStream[MAX_REWIND_BUFFER]; 10 | 11 | private int _rewindStateHead = 0; 12 | private int _rewindStateTail = 0; 13 | 14 | public Action SaveState { get; set; } 15 | public Action LoadState { get; set; } 16 | 17 | public Playback() 18 | { 19 | for (var i = 0; i < MAX_REWIND_BUFFER; i++) 20 | { 21 | _rewindStateBuffer[i] = new MemoryStream(); 22 | } 23 | } 24 | 25 | public void PerFrame(ref bool rewind) 26 | { 27 | switch (rewind) 28 | { 29 | case true: 30 | { 31 | _rewindStateHead--; 32 | if (_rewindStateHead < 0) 33 | { 34 | _rewindStateHead = MAX_REWIND_BUFFER - 1; 35 | } 36 | if (_rewindStateHead == _rewindStateTail) 37 | { 38 | rewind = false; 39 | } 40 | _rewindStateBuffer[_rewindStateHead].Position = 0; 41 | LoadState(_rewindStateBuffer[_rewindStateHead]); 42 | break; 43 | } 44 | case false: 45 | { 46 | _rewindStateBuffer[_rewindStateHead].Position = 0; 47 | SaveState(_rewindStateBuffer[_rewindStateHead]); 48 | 49 | _rewindStateHead++; 50 | 51 | if (_rewindStateHead >= MAX_REWIND_BUFFER) 52 | { 53 | _rewindStateHead = 0; 54 | } 55 | else if (_rewindStateHead == _rewindStateTail) 56 | { 57 | _rewindStateTail = _rewindStateHead + 1; 58 | if (_rewindStateTail >= MAX_REWIND_BUFFER) 59 | { 60 | _rewindStateTail = 0; 61 | } 62 | } 63 | 64 | break; 65 | } 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/RowCol.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Interface 2 | { 3 | public struct RowCol 4 | { 5 | public RowCol(uint col, uint row) 6 | { 7 | Row = row; 8 | Col = col; 9 | } 10 | 11 | public uint Row; 12 | public uint Col; 13 | } 14 | } -------------------------------------------------------------------------------- /CS64.Core/Interface/VideoProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using static SDL2.SDL_ttf; 4 | using static SDL2.SDL; 5 | 6 | namespace CS64.Core.Interface 7 | { 8 | public class VideoProvider : IDisposable 9 | { 10 | private int VicWidth; 11 | private int VicHeight; 12 | 13 | 14 | private const double RATIO_4x3 = 4 / 3d; 15 | private const double RATIO_1x1 = 1; 16 | private const double RATIO_8x7 = 8 / 7d; 17 | 18 | private uint[] _displayBuf; 19 | public IntPtr Window; 20 | private IntPtr _renderer; 21 | private IntPtr _texture; 22 | private IntPtr _font; 23 | 24 | public VideoProvider(IntPtr window, int width, int height) 25 | { 26 | Window = window; 27 | 28 | VicWidth = width; 29 | VicHeight = height; 30 | 31 | _displayBuf = new uint[VicWidth * VicHeight]; 32 | _font = TTF_OpenFont(Path.Combine("Fonts", "Modeseven.ttf"), 24); 33 | 34 | //string fontsfolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); 35 | //_font = TTF_OpenFont(Path.Combine(fontsfolder, "LUCON.TTF"), 24); 36 | 37 | } 38 | 39 | public DisplayMode DisplayMode { get; set; } 40 | public bool IntegerScaling { get; set; } 41 | 42 | public int X { get; private set; } 43 | public int Y { get; private set; } 44 | public int Width { get; private set; } 45 | public int Height { get; private set; } 46 | 47 | public int TextWidth { get; private set; } 48 | public int TextHeight { get; private set; } 49 | 50 | public int CanvasWidth { get; private set; } 51 | public int CanvasHeight { get; private set; } 52 | 53 | public double Ratio { get; private set; } 54 | 55 | public (int X, int Y) ToScreenCoordinates(int x, int y) 56 | { 57 | var sx = (x - (CanvasWidth - Width) / 2) / Ratio; 58 | var sy = y / Ratio; 59 | 60 | return ((int)sx, (int)sy); 61 | } 62 | 63 | public void Initialize() 64 | { 65 | //SDL_SetWindowPosition(Window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); 66 | _renderer = SDL_CreateRenderer(Window, -1, 67 | SDL_RendererFlags.SDL_RENDERER_ACCELERATED | SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC); 68 | 69 | _texture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_ABGR8888, 70 | (int)SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, VicWidth, VicHeight); 71 | 72 | SDL_GetWindowSize(Window, out int w, out int h); 73 | 74 | CanvasWidth = w; 75 | CanvasHeight = h; 76 | 77 | Ratio = Math.Min((double)h / (double)VicHeight, (double)w / (double)VicWidth); 78 | 79 | double aspect_ratio = RATIO_1x1; 80 | 81 | switch (DisplayMode) 82 | { 83 | case DisplayMode.Ratio1x1: 84 | { 85 | int fillWidth; 86 | int fillHeight; 87 | if (IntegerScaling) 88 | { 89 | fillWidth = ((int)(Ratio * VicWidth) / VicWidth) * VicWidth; 90 | fillHeight = ((int)(Ratio * VicHeight) / VicHeight) * VicHeight; 91 | } 92 | else 93 | { 94 | fillWidth = (int)(Ratio * VicWidth); 95 | fillHeight = (int)(Ratio * VicHeight); 96 | } 97 | 98 | fillWidth = (int)(fillWidth * aspect_ratio); 99 | 100 | Width = fillWidth; 101 | Height = fillHeight; 102 | X = (int)((w - fillWidth) / 2); 103 | Y = (int)((h - fillHeight) / 2); 104 | break; 105 | } 106 | case DisplayMode.Stretched: 107 | Width = w; 108 | Height = h; 109 | X = 0; 110 | Y = 0; 111 | break; 112 | } 113 | 114 | } 115 | 116 | public void Clear() 117 | { 118 | SDL_RenderClear(_renderer); 119 | SDL_RenderPresent(_renderer); 120 | SDL_UpdateWindowSurface(Window); 121 | } 122 | 123 | public unsafe void Render(uint[] buffer) 124 | { 125 | for (var y = 0; y < VicHeight; y++) 126 | { 127 | for (var x = 0; x < VicWidth; x++) 128 | { 129 | var p = buffer[x + y * VicWidth]; 130 | //var r = p >> 16 & 0xFF; 131 | //var g = p >> 8 & 0xFF; 132 | //var b = p & 0xFF; 133 | //var q = DisplayBuf[x + y * WIDTH]; 134 | //var i = q >> 16 & 0xFF; 135 | //var j = q >> 8 & 0xFF; 136 | //var k = q & 0xFF; 137 | 138 | //if (r < 150 & g < 150 & b < 150) 139 | //{ 140 | // r = (uint)(i * 0.99); 141 | // g = (uint)(j * 0.99); 142 | // b = (uint)(k * 0.99); 143 | //} 144 | 145 | //p = 0xFF000000 | (r << 16) | (g << 8) | b; 146 | _displayBuf[x + y * VicWidth] = p; 147 | } 148 | } 149 | 150 | //CopyPixels(Gba.Ppu.Renderer.ScreenFront, DisplayBuf, WIDTH * HEIGHT, ColorCorrection); 151 | fixed (void* ptr = _displayBuf) 152 | SDL_UpdateTexture(_texture, IntPtr.Zero, (IntPtr)ptr, VicWidth * 4); 153 | 154 | SDL_Rect dest = new(); 155 | 156 | switch (DisplayMode) 157 | { 158 | case DisplayMode.Ratio1x1: 159 | dest.w = Width; 160 | dest.h = Height; 161 | dest.x = X; 162 | dest.y = Y; 163 | break; 164 | case DisplayMode.Stretched: 165 | dest.w = Width; 166 | dest.h = Height; 167 | dest.x = 0; 168 | dest.y = 0; 169 | break; 170 | } 171 | 172 | SDL_RenderClear(_renderer); 173 | SDL_RenderCopy(_renderer, _texture, IntPtr.Zero, ref dest); 174 | 175 | if (_messageTimeout > 0) 176 | { 177 | SDL_Rect textLocation = new SDL_Rect() { x = 0, y = dest.h - TextHeight, h = TextHeight, w = TextWidth }; 178 | if (_messageTimeout < 50) 179 | { 180 | SDL_SetTextureAlphaMod(textTexture, (byte)(((_messageTimeout) / 50f) * 255)); 181 | } 182 | 183 | var result = SDL_RenderCopy(_renderer, textTexture, IntPtr.Zero, ref textLocation); 184 | //var result = SDL_BlitSurface(pTexture, IntPtr.Zero, _texture, ref textLocation); 185 | if (result != 0) 186 | { 187 | Console.WriteLine(SDL_GetError()); 188 | } 189 | 190 | _messageTimeout--; 191 | } 192 | else 193 | { 194 | CleanMessage(); 195 | } 196 | 197 | SDL_RenderPresent(_renderer); 198 | SDL_UpdateWindowSurface(Window); 199 | } 200 | 201 | private string _message; 202 | private int _messageTimeout; 203 | private IntPtr textSurface = IntPtr.Zero; 204 | private IntPtr textTexture = IntPtr.Zero; 205 | 206 | private void CleanMessage() 207 | { 208 | if (textSurface != IntPtr.Zero) 209 | { 210 | SDL_FreeSurface(textSurface); 211 | SDL_DestroyTexture(textTexture); 212 | textSurface = IntPtr.Zero; 213 | textTexture = IntPtr.Zero; 214 | } 215 | } 216 | 217 | public void SetMessage(string message) 218 | { 219 | if (textSurface == IntPtr.Zero) 220 | { 221 | _message = message; 222 | _messageTimeout = 200; 223 | 224 | SDL_Color foregroundColor = new SDL_Color() {a = 0xFF, r = 0xFF, g = 0xFF, b = 0xFF}; 225 | SDL_Color backgroundColor = new SDL_Color() {a = 0x00, r = 0x00, g = 0x00, b = 0x00}; 226 | 227 | textSurface = TTF_RenderText_Blended(_font, _message, foregroundColor); 228 | textTexture = SDL_CreateTextureFromSurface(_renderer, textSurface); 229 | SDL_QueryTexture(textTexture, out uint format, out int access, out int width, out int height); 230 | TextHeight = height; 231 | TextWidth = width; 232 | SDL_SetTextureAlphaMod(textTexture, 255); 233 | } 234 | } 235 | 236 | 237 | public void Destroy() 238 | { 239 | SDL_DestroyTexture(_texture); 240 | SDL_DestroyRenderer(_renderer); 241 | } 242 | 243 | public void Dispose() 244 | { 245 | TTF_CloseFont(_font); 246 | Destroy(); 247 | } 248 | 249 | public void Resize(int width, int height) 250 | { 251 | //Destroy(); 252 | //SDL_SetWindowSize(Window, width, height); 253 | //Initialize(); 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /CS64.Core/SDL2_ttf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64.Core/SDL2_ttf.dll -------------------------------------------------------------------------------- /CS64.Core/UnsupportedMapperException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CS64.Core 4 | { 5 | public class UnsupportedMapperException : Exception 6 | { 7 | private readonly int _mapperId; 8 | 9 | public UnsupportedMapperException(int mapperId) : base($"Unsupported Mapper {mapperId}") 10 | { 11 | _mapperId = mapperId; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /CS64.Core/Utility/BinaryWriterExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace CS64.Core.Utility 5 | { 6 | public static class BinaryWriterExtensions 7 | { 8 | public static void Write(this BinaryWriter writer, uint[] values, int offset, int length) 9 | { 10 | for (var i = offset; i < length; i++) 11 | { 12 | writer.Write(values[i]); 13 | } 14 | } 15 | 16 | public static uint[] ReadUInt32Array(this BinaryReader writer, int length) 17 | { 18 | var result = new uint[length]; 19 | for (var i = 0; i < length; i++) 20 | { 21 | result[i] = writer.ReadUInt32(); 22 | } 23 | return result; 24 | } 25 | 26 | public static void Write2DArray(this BinaryWriter writer, byte[,] array, int outer, int inner) 27 | { 28 | var buffer = new byte[outer * inner]; 29 | Buffer.BlockCopy(array, 0, buffer, 0, outer * inner); 30 | writer.Write(buffer, 0, buffer.Length); 31 | } 32 | 33 | public static void Write2DArraySlow(this BinaryWriter writer, byte[,] array, int outer, int inner) 34 | { 35 | for (var i = 0; i < outer; i++) 36 | { 37 | for (var j = 0; j < inner; j++) 38 | { 39 | writer.Write(array[i, j]); 40 | } 41 | } 42 | } 43 | 44 | public static void Read2DArray(this BinaryReader writer, byte[,] array, int outer, int inner) 45 | { 46 | var buffer = new byte[outer * inner]; 47 | writer.Read(buffer, 0, buffer.Length); 48 | Buffer.BlockCopy(buffer, 0, array, 0, outer * inner); 49 | } 50 | 51 | public static byte[,] Read2DArray(this BinaryReader writer, int outer, int inner) 52 | { 53 | var result = new byte[outer, inner]; 54 | var buffer = new byte[outer * inner]; 55 | writer.Read(buffer, 0, buffer.Length); 56 | Buffer.BlockCopy(buffer, 0, result, 0, outer * inner); 57 | return result; 58 | } 59 | 60 | public static byte[,] Read2DArraySlow(this BinaryReader reader, int outer, int inner) 61 | { 62 | var result = new byte[outer, inner]; 63 | for (var i = 0; i < outer; i++) 64 | { 65 | for (var j = 0; j < inner; j++) 66 | { 67 | result[i, j] = reader.ReadByte(); 68 | } 69 | } 70 | return result; 71 | } 72 | 73 | } 74 | } -------------------------------------------------------------------------------- /CS64.Core/Video/CIA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace CS64.Core.Video 5 | { 6 | //https://www.c64-wiki.com/wiki/CIA 7 | 8 | // TODO: Interrupts, timers, everything 9 | 10 | public class CIA 11 | { 12 | public uint PortA { get; private set; } 13 | public uint PortB { get; private set; } 14 | 15 | // Bit X: 0=Input (read only), 1=Output (read and write) 16 | private uint port_a; 17 | private uint port_b; 18 | private uint readwrite_mask_a; 19 | private uint readwrite_mask_b; 20 | 21 | public Func PortARead { get; set; } 22 | public Action PortAWrite { get; set; } 23 | 24 | public Func PortBRead { get; set; } 25 | public Action PortBWrite { get; set; } 26 | 27 | public Action RequestInterrupt { get; set; } 28 | 29 | public CIA() 30 | { 31 | } 32 | 33 | public uint Read(uint address) 34 | { 35 | uint data = 0; 36 | address &= 0x0F; // Mirror every 16 address bytes 37 | data = address switch 38 | { 39 | 0x0 => ReadPortA(), 40 | 0x1 => ReadPortB(), 41 | 0x2 => readwrite_mask_a, 42 | 0x3 => readwrite_mask_b, 43 | 0x4 => (uint)timer_A & 0xFF, 44 | 0x5 => (uint)(timer_A >> 8) & 0xFF, 45 | 0x6 => (uint)timer_B & 0xFF, 46 | 0x7 => (uint)(timer_B >> 8) & 0xFF, 47 | 0x8 => rtc_tenths, 48 | 0x9 => rtc_seconds | (rtc_tenseconds << 4), 49 | 0xA => rtc_minutes | (rtc_tenminutes << 4), 50 | 0xB => rtc_hours | (rtc_tenhours << 4) | (rtc_am_pm << 7), 51 | 0xC => shift_register, 52 | 0xD => ReadInterrupts(), 53 | _ => data 54 | }; 55 | return data; 56 | } 57 | 58 | private uint int_mask; 59 | private uint int_data; 60 | 61 | private uint ReadPortA() 62 | { 63 | port_a = PortARead?.Invoke() ?? PortA; 64 | return port_a; 65 | } 66 | 67 | private uint ReadPortB() 68 | { 69 | port_b = PortBRead?.Invoke() ?? PortB; 70 | return port_b; 71 | } 72 | 73 | private uint ReadInterrupts() 74 | { 75 | var temp = int_data; 76 | int_data = 0; //Flags will be cleared after reading the register! 77 | return temp; 78 | } 79 | 80 | private void UpdatePortA() 81 | { 82 | //var output = port_b |= (readwrite_mask_b ^ 0xFF); 83 | var output = port_a; 84 | output &= readwrite_mask_a; 85 | 86 | if (PortAWrite != null) 87 | { 88 | PortAWrite(output); 89 | } 90 | else 91 | { 92 | PortA &= readwrite_mask_a ^ 0xFF; 93 | PortA |= output; 94 | } 95 | 96 | //var output = port_a |= (readwrite_mask_a ^ 0xFF); 97 | //if (PortAWrite != null) 98 | //{ 99 | // PortAWrite(output); 100 | //} 101 | //else 102 | //{ 103 | // PortA = output; 104 | //} 105 | } 106 | 107 | private void UpdatePortB() 108 | { 109 | var output = port_b; 110 | port_b &= readwrite_mask_b; 111 | 112 | if (PortBWrite != null) 113 | { 114 | PortBWrite(output); 115 | } 116 | else 117 | { 118 | PortB &= readwrite_mask_b ^ 0xFF; 119 | PortB |= output; 120 | } 121 | 122 | //var output = port_b |= (readwrite_mask_b ^ 0xFF); 123 | //if (PortBWrite != null) 124 | //{ 125 | // PortBWrite(output); 126 | //} 127 | //else 128 | //{ 129 | // PortB = output; 130 | //} 131 | } 132 | 133 | public void Write(uint address, uint data) 134 | { 135 | address &= 0x0F; // Mirror every 16 address bytes 136 | 137 | switch (address) 138 | { 139 | case 0x0: 140 | port_a = data; 141 | UpdatePortA(); 142 | 143 | break; 144 | case 0x1: 145 | port_b = data; 146 | UpdatePortB(); 147 | 148 | break; 149 | case 0x2: 150 | readwrite_mask_a = data; 151 | UpdatePortA(); 152 | break; 153 | case 0x3: 154 | readwrite_mask_b = data; 155 | UpdatePortB(); 156 | break; 157 | case 0x4: 158 | timer_A_latch |= (int)data; 159 | break; 160 | case 0x5: 161 | timer_A_latch |= (int)(data << 8); 162 | break; 163 | case 0x6: 164 | timer_B_latch |= (int)data; 165 | break; 166 | case 0x7: 167 | timer_B_latch |= (int)(data << 8); 168 | break; 169 | case 0x8: 170 | if (tod_set_mode == 1) 171 | { 172 | alarm_tenths = data & 0xF; 173 | } 174 | else 175 | { 176 | rtc_tenths = data & 0xF; 177 | } 178 | tod_stopped = false; 179 | break; 180 | case 0x9: 181 | if (tod_set_mode == 1) 182 | { 183 | alarm_seconds = data & 0xF; 184 | alarm_tenseconds = (data >> 4) & 0x7; 185 | } 186 | else 187 | { 188 | rtc_seconds = data & 0xF; 189 | rtc_tenseconds = (data >> 4) & 0x7; 190 | } 191 | break; 192 | case 0xA: 193 | if (tod_set_mode == 1) 194 | { 195 | alarm_minutes = data & 0xF; 196 | alarm_tenminutes = (data >> 4) & 0x7; 197 | } 198 | else 199 | { 200 | rtc_minutes = data & 0xF; 201 | rtc_tenminutes = (data >> 4) & 0x7; 202 | } 203 | break; 204 | case 0xB: 205 | if (tod_set_mode == 1) 206 | { 207 | alarm_hours = data & 0xF; 208 | alarm_tenhours = (data >> 4) & 0x7; 209 | alarm_am_pm = (data >> 7) & 1; 210 | } 211 | else 212 | { 213 | rtc_hours = data & 0xF; 214 | rtc_tenhours = (data >> 8) & 0x7; 215 | rtc_am_pm = (data >> 7) & 1; 216 | } 217 | // TODO: Writing into this register stops TOD, until register 8(TOD 10THS) will be read. 218 | tod_stopped = true; 219 | break; 220 | case 0xC: 221 | shift_register = data; 222 | break; 223 | case 0xD: 224 | // Bit 7: Source bit. 225 | // 0 = set bits 0..4 are clearing the according mask bit. 226 | // 1 = set bits 0..4 are setting the according mask bit. 227 | // If all bits 0..4 are cleared, there will be no change to the mask. 228 | if (((data >> 7) & 1) == 1) 229 | { 230 | // set bits 231 | int_mask |= (data & 0x1F); 232 | } 233 | else 234 | { 235 | int_mask &= (data & 0x1F) ^ 0xFF; 236 | } 237 | 238 | break; 239 | case 0xE: 240 | timer_A_start = (byte)(data & 1); 241 | timer_A_underflow_stop = (byte)((data >> 3) & 1); 242 | timer_A_load = (byte)((data >> 4) & 1); 243 | // should this be done here? 244 | if (timer_A_load == 1) 245 | { 246 | timer_A = timer_A_latch; 247 | } 248 | timer_A_count_mode = (byte)((data >> 5) & 1); 249 | shift_register_direction = (byte)((data >> 6) & 1); 250 | tod_frame_divider = 5 + ((data >> 7) & 1); // value will be 5 or 6 251 | // tod_frame_divider = (((data >> 7) & 1) == 0 ? 5U : 6U); // divide frame rate by this value 252 | break; 253 | case 0xF: 254 | timer_B_start = (byte)(data & 1); 255 | timer_B_underflow_stop = (byte)((data >> 3) & 1); 256 | timer_B_load = (byte)((data >> 4) & 1); 257 | // should this be done here? 258 | if (timer_B_load == 1) 259 | { 260 | timer_B = timer_B_latch; 261 | } 262 | timer_B_count_mode = (byte)((data >> 5) & 3); 263 | tod_set_mode = (data >> 7) & 1; 264 | break; 265 | } 266 | return; 267 | } 268 | 269 | private int timer_A; 270 | private int timer_B; 271 | private int timer_A_latch; 272 | private int timer_B_latch; 273 | private uint rtc_tenths; 274 | private uint rtc_seconds; 275 | private uint rtc_tenseconds; 276 | private uint rtc_minutes; 277 | private uint rtc_tenminutes; 278 | private uint rtc_hours; 279 | private uint rtc_tenhours; 280 | private uint rtc_am_pm; 281 | 282 | private uint alarm_tenths; 283 | private uint alarm_seconds; 284 | private uint alarm_tenseconds; 285 | private uint alarm_minutes; 286 | private uint alarm_tenminutes; 287 | private uint alarm_hours; 288 | private uint alarm_tenhours; 289 | private uint alarm_am_pm; 290 | 291 | private uint tod_set_mode; // 0 = clock, 1 = alarm 292 | private bool tod_stopped; 293 | 294 | private uint shift_register_bit; 295 | private uint shift_register_direction; // 0 = input, 1 = output 296 | private uint shift_register; 297 | 298 | private byte int_underflow_A; 299 | private byte int_underflow_B; 300 | private byte int_alarm; 301 | 302 | 303 | private byte timer_A_start; 304 | private byte timer_A_underflow_portB6; 305 | private byte timer_A_underflow_portB6_type; 306 | private byte timer_A_underflow_stop; 307 | private byte timer_A_load; 308 | private byte timer_A_count_mode; 309 | 310 | private byte timer_B_start; 311 | private byte timer_B_underflow_portB6; 312 | private byte timer_B_underflow_portB6_type; 313 | private byte timer_B_underflow_stop; 314 | private byte timer_B_load; 315 | private byte timer_B_count_mode; 316 | 317 | private uint tod_frame_divider; // 5 = 50Hz, 6 = 60hz; 318 | private uint tod_frame_counter; 319 | public void TimeOfDay() 320 | { 321 | if (tod_stopped) return; 322 | 323 | tod_frame_counter++; 324 | if (tod_frame_counter > tod_frame_divider) 325 | { 326 | tod_frame_counter = 0; 327 | rtc_tenths++; 328 | if (rtc_tenths > 9) 329 | { 330 | rtc_tenths = 0; 331 | rtc_seconds++; 332 | if (rtc_seconds > 9) 333 | { 334 | rtc_seconds = 0; 335 | rtc_tenseconds++; 336 | if (rtc_tenseconds > 5) 337 | { 338 | rtc_tenseconds = 0; 339 | rtc_minutes++; 340 | if (rtc_minutes > 9) 341 | { 342 | rtc_minutes = 0; 343 | rtc_tenminutes++; 344 | if (rtc_tenminutes > 5) 345 | { 346 | rtc_tenminutes = 0; 347 | rtc_hours++; 348 | if (rtc_tenhours < 2 && rtc_hours > 9) 349 | { 350 | rtc_hours = 0; 351 | rtc_tenhours++; 352 | } 353 | else if (rtc_tenhours == 2 && rtc_hours > 3) 354 | { 355 | rtc_tenths = 0; 356 | rtc_seconds = 0; 357 | rtc_tenseconds = 0; 358 | rtc_minutes = 0; 359 | rtc_tenminutes = 0; 360 | rtc_hours = 0; 361 | rtc_tenhours = 0; 362 | } 363 | } 364 | } 365 | } 366 | } 367 | //Console.WriteLine($"{rtc_tenhours}{rtc_hours}:{rtc_tenminutes}{rtc_minutes}:{rtc_tenseconds}{rtc_seconds}"); 368 | } 369 | } 370 | 371 | } 372 | 373 | public void Count() 374 | { 375 | // TODO: shift shift_register into shift_register_bit 376 | 377 | if (timer_A_count_mode == 1) 378 | { 379 | UpdateTimerA(); 380 | } 381 | if (timer_B_count_mode == 1) 382 | { 383 | UpdateTimerB(); 384 | } 385 | } 386 | 387 | 388 | private void UpdateTimerA() 389 | { 390 | if (timer_A_start == 1) 391 | { 392 | timer_A--; 393 | if (timer_A < 0) 394 | { 395 | if ((int_mask & 1) == 1) 396 | { 397 | int_data |= 1; 398 | RequestInterrupt?.Invoke(); 399 | } 400 | if (timer_A_underflow_stop == 0) 401 | { 402 | timer_A = timer_A_latch; 403 | if (timer_B_count_mode == 2) 404 | { 405 | //0b10 = Timer counts underflow of timer A 406 | //TODO: 0b11 = Timer counts underflow of timer A if the CNT - pin is high 407 | UpdateTimerB(); 408 | } 409 | } 410 | else 411 | { 412 | timer_A_start = 0; 413 | } 414 | } 415 | } 416 | } 417 | 418 | 419 | 420 | private void UpdateTimerB() 421 | { 422 | if (timer_B_start == 1) 423 | { 424 | timer_B--; 425 | if (timer_B < 0) 426 | { 427 | if ((int_mask & 2) == 2) 428 | { 429 | int_data |= 2; 430 | RequestInterrupt?.Invoke(); 431 | } 432 | if (timer_B_underflow_stop == 0) 433 | { 434 | timer_B = timer_B_latch; 435 | } 436 | } 437 | } 438 | } 439 | 440 | public void Clock() 441 | { 442 | if (timer_A_count_mode == 0) 443 | { 444 | UpdateTimerA(); 445 | } 446 | if (timer_B_count_mode == 0) 447 | { 448 | UpdateTimerB(); 449 | } 450 | } 451 | } 452 | } -------------------------------------------------------------------------------- /CS64.Core/Video/StateEnum.cs: -------------------------------------------------------------------------------- 1 | namespace CS64.Core.Video 2 | { 3 | public enum StateEnum 4 | { 5 | IdleState, 6 | DisplayState 7 | } 8 | } -------------------------------------------------------------------------------- /CS64.Core/blip_buf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64.Core/blip_buf.dll -------------------------------------------------------------------------------- /CS64.Core/libfreetype-6.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64.Core/libfreetype-6.dll -------------------------------------------------------------------------------- /CS64.Core/zlib1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64.Core/zlib1.dll -------------------------------------------------------------------------------- /CS64.UI/CS64.UI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net5.0-windows 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CS64.UI/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CS64.UI 4 | { 5 | public class Configuration 6 | { 7 | public Configuration() 8 | { 9 | RecentItems = new List(); 10 | } 11 | 12 | public List RecentItems { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /CS64.UI/ControlExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace CS64.UI 4 | { 5 | public static class ControlExtensions 6 | { 7 | public static void InvokeIfRequired(this Control control, MethodInvoker action) 8 | { 9 | // See Update 2 for edits Mike de Klerk suggests to insert here. 10 | 11 | if (control.InvokeRequired) 12 | { 13 | control.Invoke(action); 14 | } 15 | else 16 | { 17 | action(); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CS64.UI/FormExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace CS64.UI 4 | { 5 | public static class FormExtensions 6 | { 7 | public static void InvokeIfRequired(this Form control, MethodInvoker action) 8 | { 9 | // See Update 2 for edits Mike de Klerk suggests to insert here. 10 | 11 | if (control.InvokeRequired) 12 | { 13 | control.Invoke(action); 14 | } 15 | else 16 | { 17 | action(); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CS64.UI/KeyCodeMappings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Windows.Forms; 3 | using SDL2; 4 | 5 | namespace CS64.UI 6 | { 7 | public static class KeyCodeMappings 8 | { 9 | private static Dictionary mappings; 10 | 11 | public static bool TryGetKeyCode(Keys key, out SDL.SDL_Keycode code) 12 | { 13 | return mappings.TryGetValue(key, out code); 14 | } 15 | 16 | static KeyCodeMappings() 17 | { 18 | mappings = new Dictionary() 19 | { 20 | {Keys.A, SDL.SDL_Keycode.SDLK_a}, 21 | {Keys.B, SDL.SDL_Keycode.SDLK_b}, 22 | {Keys.C, SDL.SDL_Keycode.SDLK_c}, 23 | {Keys.D, SDL.SDL_Keycode.SDLK_d}, 24 | {Keys.E, SDL.SDL_Keycode.SDLK_e}, 25 | {Keys.F, SDL.SDL_Keycode.SDLK_f}, 26 | {Keys.G, SDL.SDL_Keycode.SDLK_g}, 27 | {Keys.H, SDL.SDL_Keycode.SDLK_h}, 28 | {Keys.I, SDL.SDL_Keycode.SDLK_i}, 29 | {Keys.J, SDL.SDL_Keycode.SDLK_j}, 30 | {Keys.K, SDL.SDL_Keycode.SDLK_k}, 31 | {Keys.L, SDL.SDL_Keycode.SDLK_l}, 32 | {Keys.M, SDL.SDL_Keycode.SDLK_m}, 33 | {Keys.N, SDL.SDL_Keycode.SDLK_n}, 34 | {Keys.O, SDL.SDL_Keycode.SDLK_o}, 35 | {Keys.P, SDL.SDL_Keycode.SDLK_p}, 36 | {Keys.Q, SDL.SDL_Keycode.SDLK_q}, 37 | {Keys.R, SDL.SDL_Keycode.SDLK_r}, 38 | {Keys.S, SDL.SDL_Keycode.SDLK_s}, 39 | {Keys.T, SDL.SDL_Keycode.SDLK_t}, 40 | {Keys.U, SDL.SDL_Keycode.SDLK_u}, 41 | {Keys.V, SDL.SDL_Keycode.SDLK_v}, 42 | {Keys.W, SDL.SDL_Keycode.SDLK_w}, 43 | {Keys.X, SDL.SDL_Keycode.SDLK_x}, 44 | {Keys.Y, SDL.SDL_Keycode.SDLK_y}, 45 | {Keys.Z, SDL.SDL_Keycode.SDLK_z}, 46 | {Keys.Up, SDL.SDL_Keycode.SDLK_UP}, 47 | {Keys.Down, SDL.SDL_Keycode.SDLK_DOWN}, 48 | {Keys.Left, SDL.SDL_Keycode.SDLK_LEFT}, 49 | {Keys.Right, SDL.SDL_Keycode.SDLK_RIGHT}, 50 | {Keys.LShiftKey, SDL.SDL_Keycode.SDLK_LSHIFT}, 51 | {Keys.RShiftKey, SDL.SDL_Keycode.SDLK_RSHIFT}, 52 | {Keys.LControlKey, SDL.SDL_Keycode.SDLK_LCTRL}, 53 | {Keys.RControlKey, SDL.SDL_Keycode.SDLK_RCTRL}, 54 | {Keys.Add, SDL.SDL_Keycode.SDLK_PLUS}, 55 | {Keys.Subtract, SDL.SDL_Keycode.SDLK_MINUS}, 56 | {Keys.NumPad0, SDL.SDL_Keycode.SDLK_KP_0}, 57 | {Keys.NumPad1, SDL.SDL_Keycode.SDLK_KP_1}, 58 | {Keys.NumPad2, SDL.SDL_Keycode.SDLK_KP_2}, 59 | {Keys.NumPad3, SDL.SDL_Keycode.SDLK_KP_3}, 60 | {Keys.NumPad4, SDL.SDL_Keycode.SDLK_KP_4}, 61 | {Keys.NumPad5, SDL.SDL_Keycode.SDLK_KP_5}, 62 | {Keys.NumPad6, SDL.SDL_Keycode.SDLK_KP_6}, 63 | {Keys.NumPad7, SDL.SDL_Keycode.SDLK_KP_7}, 64 | {Keys.NumPad8, SDL.SDL_Keycode.SDLK_KP_8}, 65 | {Keys.NumPad9, SDL.SDL_Keycode.SDLK_KP_9}, 66 | {Keys.D0, SDL.SDL_Keycode.SDLK_0}, 67 | {Keys.D1, SDL.SDL_Keycode.SDLK_1}, 68 | {Keys.D2, SDL.SDL_Keycode.SDLK_2}, 69 | {Keys.D3, SDL.SDL_Keycode.SDLK_3}, 70 | {Keys.D4, SDL.SDL_Keycode.SDLK_4}, 71 | {Keys.D5, SDL.SDL_Keycode.SDLK_5}, 72 | {Keys.D6, SDL.SDL_Keycode.SDLK_6}, 73 | {Keys.D7, SDL.SDL_Keycode.SDLK_7}, 74 | {Keys.D8, SDL.SDL_Keycode.SDLK_8}, 75 | {Keys.D9, SDL.SDL_Keycode.SDLK_9}, 76 | {Keys.Tab, SDL.SDL_Keycode.SDLK_TAB}, 77 | {Keys.Escape, SDL.SDL_Keycode.SDLK_ESCAPE}, 78 | {Keys.Back, SDL.SDL_Keycode.SDLK_BACKSPACE}, 79 | {Keys.Return, SDL.SDL_Keycode.SDLK_RETURN}, 80 | {Keys.PageUp, SDL.SDL_Keycode.SDLK_PAGEUP}, 81 | {Keys.PageDown, SDL.SDL_Keycode.SDLK_PAGEDOWN}, 82 | {Keys.End, SDL.SDL_Keycode.SDLK_END}, 83 | {Keys.Delete, SDL.SDL_Keycode.SDLK_DELETE}, 84 | }; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /CS64.UI/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace CS64.UI 3 | { 4 | partial class MainForm 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 33 | this.menuStrip1 = new System.Windows.Forms.MenuStrip(); 34 | this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 35 | this.loadROMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 36 | this.recentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 37 | this.clearItemsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 38 | this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); 39 | this.changeSlotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 40 | this.slot1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 41 | this.slot2ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 42 | this.slot3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 43 | this.slot4ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 44 | this.slot5ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 45 | this.slot6ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 46 | this.slot7ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 47 | this.slot8ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 48 | this.slot9ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 49 | this.slot10ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 50 | this.loadStateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 51 | this.saveStateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 52 | this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); 53 | this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 54 | this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 55 | this.controllersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 56 | this.windowSizeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 57 | this.x1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 58 | this.x2ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 59 | this.x3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 60 | this.x4ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 61 | this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); 62 | this.menuStrip1.SuspendLayout(); 63 | this.SuspendLayout(); 64 | // 65 | // menuStrip1 66 | // 67 | this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 68 | this.fileToolStripMenuItem, 69 | this.optionsToolStripMenuItem}); 70 | this.menuStrip1.Location = new System.Drawing.Point(0, 0); 71 | this.menuStrip1.Name = "menuStrip1"; 72 | this.menuStrip1.Size = new System.Drawing.Size(575, 24); 73 | this.menuStrip1.TabIndex = 0; 74 | this.menuStrip1.Text = "menuStrip1"; 75 | // 76 | // fileToolStripMenuItem 77 | // 78 | this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 79 | this.loadROMToolStripMenuItem, 80 | this.recentToolStripMenuItem, 81 | this.toolStripMenuItem1, 82 | this.changeSlotToolStripMenuItem, 83 | this.loadStateToolStripMenuItem, 84 | this.saveStateToolStripMenuItem, 85 | this.toolStripMenuItem2, 86 | this.exitToolStripMenuItem}); 87 | this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 88 | this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); 89 | this.fileToolStripMenuItem.Text = "&File"; 90 | // 91 | // loadROMToolStripMenuItem 92 | // 93 | this.loadROMToolStripMenuItem.Name = "loadROMToolStripMenuItem"; 94 | this.loadROMToolStripMenuItem.Size = new System.Drawing.Size(138, 22); 95 | this.loadROMToolStripMenuItem.Text = "&Load ROM"; 96 | this.loadROMToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click); 97 | // 98 | // recentToolStripMenuItem 99 | // 100 | this.recentToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 101 | this.clearItemsToolStripMenuItem}); 102 | this.recentToolStripMenuItem.Name = "recentToolStripMenuItem"; 103 | this.recentToolStripMenuItem.Size = new System.Drawing.Size(138, 22); 104 | this.recentToolStripMenuItem.Text = "Recent"; 105 | // 106 | // clearItemsToolStripMenuItem 107 | // 108 | this.clearItemsToolStripMenuItem.Name = "clearItemsToolStripMenuItem"; 109 | this.clearItemsToolStripMenuItem.Size = new System.Drawing.Size(133, 22); 110 | this.clearItemsToolStripMenuItem.Text = "Clear Items"; 111 | this.clearItemsToolStripMenuItem.Click += new System.EventHandler(this.clearItemsToolStripMenuItem_Click); 112 | // 113 | // toolStripMenuItem1 114 | // 115 | this.toolStripMenuItem1.Name = "toolStripMenuItem1"; 116 | this.toolStripMenuItem1.Size = new System.Drawing.Size(135, 6); 117 | // 118 | // changeSlotToolStripMenuItem 119 | // 120 | this.changeSlotToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 121 | this.slot1ToolStripMenuItem, 122 | this.slot2ToolStripMenuItem, 123 | this.slot3ToolStripMenuItem, 124 | this.slot4ToolStripMenuItem, 125 | this.slot5ToolStripMenuItem, 126 | this.slot6ToolStripMenuItem, 127 | this.slot7ToolStripMenuItem, 128 | this.slot8ToolStripMenuItem, 129 | this.slot9ToolStripMenuItem, 130 | this.slot10ToolStripMenuItem}); 131 | this.changeSlotToolStripMenuItem.Enabled = false; 132 | this.changeSlotToolStripMenuItem.Name = "changeSlotToolStripMenuItem"; 133 | this.changeSlotToolStripMenuItem.Size = new System.Drawing.Size(138, 22); 134 | this.changeSlotToolStripMenuItem.Text = "Change Slot"; 135 | // 136 | // slot1ToolStripMenuItem 137 | // 138 | this.slot1ToolStripMenuItem.Name = "slot1ToolStripMenuItem"; 139 | this.slot1ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 140 | this.slot1ToolStripMenuItem.Text = "Slot #1"; 141 | // 142 | // slot2ToolStripMenuItem 143 | // 144 | this.slot2ToolStripMenuItem.Name = "slot2ToolStripMenuItem"; 145 | this.slot2ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 146 | this.slot2ToolStripMenuItem.Text = "Slot #2"; 147 | // 148 | // slot3ToolStripMenuItem 149 | // 150 | this.slot3ToolStripMenuItem.Name = "slot3ToolStripMenuItem"; 151 | this.slot3ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 152 | this.slot3ToolStripMenuItem.Text = "Slot #3"; 153 | // 154 | // slot4ToolStripMenuItem 155 | // 156 | this.slot4ToolStripMenuItem.Name = "slot4ToolStripMenuItem"; 157 | this.slot4ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 158 | this.slot4ToolStripMenuItem.Text = "Slot #4"; 159 | // 160 | // slot5ToolStripMenuItem 161 | // 162 | this.slot5ToolStripMenuItem.Name = "slot5ToolStripMenuItem"; 163 | this.slot5ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 164 | this.slot5ToolStripMenuItem.Text = "Slot #5"; 165 | // 166 | // slot6ToolStripMenuItem 167 | // 168 | this.slot6ToolStripMenuItem.Name = "slot6ToolStripMenuItem"; 169 | this.slot6ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 170 | this.slot6ToolStripMenuItem.Text = "Slot #6"; 171 | // 172 | // slot7ToolStripMenuItem 173 | // 174 | this.slot7ToolStripMenuItem.Name = "slot7ToolStripMenuItem"; 175 | this.slot7ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 176 | this.slot7ToolStripMenuItem.Text = "Slot #7"; 177 | // 178 | // slot8ToolStripMenuItem 179 | // 180 | this.slot8ToolStripMenuItem.Name = "slot8ToolStripMenuItem"; 181 | this.slot8ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 182 | this.slot8ToolStripMenuItem.Text = "Slot #8"; 183 | // 184 | // slot9ToolStripMenuItem 185 | // 186 | this.slot9ToolStripMenuItem.Name = "slot9ToolStripMenuItem"; 187 | this.slot9ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 188 | this.slot9ToolStripMenuItem.Text = "Slot #9"; 189 | // 190 | // slot10ToolStripMenuItem 191 | // 192 | this.slot10ToolStripMenuItem.Name = "slot10ToolStripMenuItem"; 193 | this.slot10ToolStripMenuItem.Size = new System.Drawing.Size(116, 22); 194 | this.slot10ToolStripMenuItem.Text = "Slot #10"; 195 | // 196 | // loadStateToolStripMenuItem 197 | // 198 | this.loadStateToolStripMenuItem.Enabled = false; 199 | this.loadStateToolStripMenuItem.Name = "loadStateToolStripMenuItem"; 200 | this.loadStateToolStripMenuItem.Size = new System.Drawing.Size(138, 22); 201 | this.loadStateToolStripMenuItem.Text = "Load State"; 202 | // 203 | // saveStateToolStripMenuItem 204 | // 205 | this.saveStateToolStripMenuItem.Enabled = false; 206 | this.saveStateToolStripMenuItem.Name = "saveStateToolStripMenuItem"; 207 | this.saveStateToolStripMenuItem.Size = new System.Drawing.Size(138, 22); 208 | this.saveStateToolStripMenuItem.Text = "Save State"; 209 | // 210 | // toolStripMenuItem2 211 | // 212 | this.toolStripMenuItem2.Name = "toolStripMenuItem2"; 213 | this.toolStripMenuItem2.Size = new System.Drawing.Size(135, 6); 214 | // 215 | // exitToolStripMenuItem 216 | // 217 | this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; 218 | this.exitToolStripMenuItem.Size = new System.Drawing.Size(138, 22); 219 | this.exitToolStripMenuItem.Text = "E&xit"; 220 | this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); 221 | // 222 | // optionsToolStripMenuItem 223 | // 224 | this.optionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 225 | this.controllersToolStripMenuItem, 226 | this.windowSizeToolStripMenuItem}); 227 | this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; 228 | this.optionsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); 229 | this.optionsToolStripMenuItem.Text = "Options"; 230 | // 231 | // controllersToolStripMenuItem 232 | // 233 | this.controllersToolStripMenuItem.Name = "controllersToolStripMenuItem"; 234 | this.controllersToolStripMenuItem.Size = new System.Drawing.Size(141, 22); 235 | this.controllersToolStripMenuItem.Text = "Controllers"; 236 | this.controllersToolStripMenuItem.Click += new System.EventHandler(this.controllersToolStripMenuItem_Click); 237 | // 238 | // windowSizeToolStripMenuItem 239 | // 240 | this.windowSizeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 241 | this.x1ToolStripMenuItem, 242 | this.x2ToolStripMenuItem, 243 | this.x3ToolStripMenuItem, 244 | this.x4ToolStripMenuItem}); 245 | this.windowSizeToolStripMenuItem.Name = "windowSizeToolStripMenuItem"; 246 | this.windowSizeToolStripMenuItem.Size = new System.Drawing.Size(141, 22); 247 | this.windowSizeToolStripMenuItem.Text = "Window Size"; 248 | // 249 | // x1ToolStripMenuItem 250 | // 251 | this.x1ToolStripMenuItem.Name = "x1ToolStripMenuItem"; 252 | this.x1ToolStripMenuItem.Size = new System.Drawing.Size(86, 22); 253 | this.x1ToolStripMenuItem.Text = "x1"; 254 | // 255 | // x2ToolStripMenuItem 256 | // 257 | this.x2ToolStripMenuItem.Name = "x2ToolStripMenuItem"; 258 | this.x2ToolStripMenuItem.Size = new System.Drawing.Size(86, 22); 259 | this.x2ToolStripMenuItem.Text = "x2"; 260 | // 261 | // x3ToolStripMenuItem 262 | // 263 | this.x3ToolStripMenuItem.Name = "x3ToolStripMenuItem"; 264 | this.x3ToolStripMenuItem.Size = new System.Drawing.Size(86, 22); 265 | this.x3ToolStripMenuItem.Text = "x3"; 266 | // 267 | // x4ToolStripMenuItem 268 | // 269 | this.x4ToolStripMenuItem.Name = "x4ToolStripMenuItem"; 270 | this.x4ToolStripMenuItem.Size = new System.Drawing.Size(86, 22); 271 | this.x4ToolStripMenuItem.Text = "x4"; 272 | // 273 | // MainForm 274 | // 275 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 276 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 277 | this.BackColor = System.Drawing.SystemColors.ControlDark; 278 | this.ClientSize = new System.Drawing.Size(575, 535); 279 | this.Controls.Add(this.menuStrip1); 280 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 281 | this.MainMenuStrip = this.menuStrip1; 282 | this.Name = "MainForm"; 283 | this.Text = "CS64"; 284 | this.Load += new System.EventHandler(this.MainForm_Load); 285 | this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint); 286 | this.menuStrip1.ResumeLayout(false); 287 | this.menuStrip1.PerformLayout(); 288 | this.ResumeLayout(false); 289 | this.PerformLayout(); 290 | 291 | } 292 | 293 | #endregion 294 | 295 | private System.Windows.Forms.MenuStrip menuStrip1; 296 | private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; 297 | private System.Windows.Forms.ToolStripMenuItem loadROMToolStripMenuItem; 298 | private System.Windows.Forms.OpenFileDialog openFileDialog1; 299 | private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; 300 | private System.Windows.Forms.ToolStripMenuItem changeSlotToolStripMenuItem; 301 | private System.Windows.Forms.ToolStripMenuItem slot1ToolStripMenuItem; 302 | private System.Windows.Forms.ToolStripMenuItem loadStateToolStripMenuItem; 303 | private System.Windows.Forms.ToolStripMenuItem saveStateToolStripMenuItem; 304 | private System.Windows.Forms.ToolStripMenuItem slot2ToolStripMenuItem; 305 | private System.Windows.Forms.ToolStripMenuItem slot3ToolStripMenuItem; 306 | private System.Windows.Forms.ToolStripMenuItem slot4ToolStripMenuItem; 307 | private System.Windows.Forms.ToolStripMenuItem slot5ToolStripMenuItem; 308 | private System.Windows.Forms.ToolStripMenuItem slot6ToolStripMenuItem; 309 | private System.Windows.Forms.ToolStripMenuItem slot7ToolStripMenuItem; 310 | private System.Windows.Forms.ToolStripMenuItem slot8ToolStripMenuItem; 311 | private System.Windows.Forms.ToolStripMenuItem slot9ToolStripMenuItem; 312 | private System.Windows.Forms.ToolStripMenuItem slot10ToolStripMenuItem; 313 | private System.Windows.Forms.ToolStripMenuItem recentToolStripMenuItem; 314 | private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2; 315 | private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; 316 | private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; 317 | private System.Windows.Forms.ToolStripMenuItem controllersToolStripMenuItem; 318 | private System.Windows.Forms.ToolStripMenuItem windowSizeToolStripMenuItem; 319 | private System.Windows.Forms.ToolStripMenuItem x1ToolStripMenuItem; 320 | private System.Windows.Forms.ToolStripMenuItem x2ToolStripMenuItem; 321 | private System.Windows.Forms.ToolStripMenuItem x3ToolStripMenuItem; 322 | private System.Windows.Forms.ToolStripMenuItem x4ToolStripMenuItem; 323 | private System.Windows.Forms.ToolStripMenuItem clearItemsToolStripMenuItem; 324 | } 325 | } 326 | 327 | -------------------------------------------------------------------------------- /CS64.UI/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Windows.Forms; 5 | using CS64.Core.Interface; 6 | using CS64.Core.Interface.Input; 7 | 8 | namespace CS64.UI 9 | { 10 | public partial class MainForm : Form, IMainInterface 11 | { 12 | private readonly Main _main; 13 | public Action OnHostResize { get; set; } 14 | public Func LoadRom { get; set; } 15 | public Action ChangeSlot { get; set; } 16 | public Action LoadState { get; set; } 17 | public Action SaveState { get; set; } 18 | public Action ResizeWindow { get; set; } 19 | public Action SetMapping { get; set; } 20 | public Configuration Configuration { get;set;} 21 | 22 | public MainForm(Main main) 23 | { 24 | _main = main; 25 | _main.StateChanged = StateChanged; 26 | SizeChanged += (sender, args) => OnHostResize?.Invoke(); 27 | InitializeComponent(); 28 | Configuration = new Configuration(); 29 | } 30 | 31 | private void StateChanged(int slot) 32 | { 33 | UncheckSlots(); 34 | switch (slot) 35 | { 36 | case 1: slot1ToolStripMenuItem.Checked = true; break; 37 | case 2: slot2ToolStripMenuItem.Checked = true; break; 38 | case 3: slot3ToolStripMenuItem.Checked = true; break; 39 | case 4: slot4ToolStripMenuItem.Checked = true; break; 40 | case 5: slot5ToolStripMenuItem.Checked = true; break; 41 | case 6: slot6ToolStripMenuItem.Checked = true; break; 42 | case 7: slot7ToolStripMenuItem.Checked = true; break; 43 | case 8: slot8ToolStripMenuItem.Checked = true; break; 44 | case 9: slot9ToolStripMenuItem.Checked = true; break; 45 | case 10: slot10ToolStripMenuItem.Checked = true; break; 46 | 47 | } 48 | } 49 | 50 | private void MainForm_Load(object sender, EventArgs e) 51 | { 52 | SetSize(3); 53 | 54 | slot1ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(1); slot1ToolStripMenuItem.Checked = true; }; 55 | slot2ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(2); slot2ToolStripMenuItem.Checked = true; }; 56 | slot3ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(3); slot3ToolStripMenuItem.Checked = true; }; 57 | slot4ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(4); slot4ToolStripMenuItem.Checked = true; }; 58 | slot5ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(5); slot5ToolStripMenuItem.Checked = true; }; 59 | slot6ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(6); slot6ToolStripMenuItem.Checked = true; }; 60 | slot7ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(7); slot7ToolStripMenuItem.Checked = true; }; 61 | slot8ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(8); slot8ToolStripMenuItem.Checked = true; }; 62 | slot9ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(9); slot9ToolStripMenuItem.Checked = true; }; 63 | slot10ToolStripMenuItem.Click += (o, args) => { UncheckSlots(); ChangeSlot?.Invoke(10); slot10ToolStripMenuItem.Checked = true; }; 64 | 65 | loadStateToolStripMenuItem.Click += (o, args) => LoadState?.Invoke(); 66 | saveStateToolStripMenuItem.Click += (o, args) => SaveState?.Invoke(); 67 | 68 | x1ToolStripMenuItem.Click += (o, args) => { UncheckSizes(); SetSize(1); x1ToolStripMenuItem.Checked = true; }; 69 | x2ToolStripMenuItem.Click += (o, args) => { UncheckSizes(); SetSize(2); x2ToolStripMenuItem.Checked = true; }; 70 | x3ToolStripMenuItem.Click += (o, args) => { UncheckSizes(); SetSize(3); x3ToolStripMenuItem.Checked = true; }; 71 | x4ToolStripMenuItem.Click += (o, args) => { UncheckSizes(); SetSize(4); x4ToolStripMenuItem.Checked = true; }; 72 | } 73 | 74 | private void SetSize(int scale) 75 | { 76 | //ResizeWindow?.Invoke(256 * scale, 240 * scale + menuStrip1.Height); 77 | Size = new Size((int)_main.Width * scale, (int)_main.Height * scale + menuStrip1.Height); 78 | // This resizes the window *twice* 79 | //Width = 256 * scale; 80 | //Height = 240 * scale + menuStrip1.Height; 81 | } 82 | 83 | private void exitToolStripMenuItem_Click(object sender, EventArgs e) 84 | { 85 | Close(); 86 | } 87 | 88 | private const int MaxRecentItems = 10; 89 | 90 | private void AddRecentItem(string item) 91 | { 92 | if (!Configuration.RecentItems.Contains(item)) 93 | { 94 | Configuration.RecentItems.Add(item); 95 | var recentItem = new ToolStripMenuItem(Path.GetFileName(item)); 96 | recentItem.Click += (sender, args) => 97 | { 98 | LoadRom.Invoke(item); 99 | }; 100 | recentToolStripMenuItem.DropDownItems.Insert(0, recentItem); 101 | 102 | if (recentToolStripMenuItem.DropDownItems.Count > MaxRecentItems + 1) 103 | { 104 | recentToolStripMenuItem.DropDownItems.RemoveAt(MaxRecentItems); 105 | } 106 | } 107 | } 108 | 109 | private void openToolStripMenuItem_Click(object sender, EventArgs e) 110 | { 111 | openFileDialog1.Filter = "All supported files|*.t64;*.d64;*.prg;*.zip|All files|*.*"; 112 | var result = openFileDialog1.ShowDialog(); 113 | if (result == DialogResult.OK) 114 | { 115 | if (LoadRom != null) 116 | { 117 | if (LoadRom.Invoke(openFileDialog1.FileName)) 118 | { 119 | AddRecentItem(openFileDialog1.FileName); 120 | SetMenus(true); 121 | } 122 | } 123 | } 124 | } 125 | 126 | private void UncheckSizes() 127 | { 128 | x1ToolStripMenuItem.Checked = false; 129 | x2ToolStripMenuItem.Checked = false; 130 | x3ToolStripMenuItem.Checked = false; 131 | x4ToolStripMenuItem.Checked = false; 132 | } 133 | 134 | private void UncheckSlots() 135 | { 136 | slot1ToolStripMenuItem.Checked = false; 137 | slot2ToolStripMenuItem.Checked = false; 138 | slot3ToolStripMenuItem.Checked = false; 139 | slot4ToolStripMenuItem.Checked = false; 140 | slot5ToolStripMenuItem.Checked = false; 141 | slot6ToolStripMenuItem.Checked = false; 142 | slot7ToolStripMenuItem.Checked = false; 143 | slot8ToolStripMenuItem.Checked = false; 144 | slot9ToolStripMenuItem.Checked = false; 145 | slot10ToolStripMenuItem.Checked = false; 146 | } 147 | 148 | private void SetMenus(bool enabled) 149 | { 150 | changeSlotToolStripMenuItem.Enabled = enabled; 151 | loadStateToolStripMenuItem.Enabled = enabled; 152 | saveStateToolStripMenuItem.Enabled = enabled; 153 | } 154 | 155 | private void controllersToolStripMenuItem_Click(object sender, EventArgs e) 156 | { 157 | var mapper = new MappingForm(_main); 158 | mapper.Show(this); 159 | } 160 | 161 | private void clearItemsToolStripMenuItem_Click(object sender, EventArgs e) 162 | { 163 | Configuration.RecentItems.Clear(); 164 | while (recentToolStripMenuItem.DropDownItems.Count > 1) 165 | { 166 | recentToolStripMenuItem.DropDownItems.RemoveAt(recentToolStripMenuItem.DropDownItems.Count - 2); 167 | } 168 | } 169 | 170 | private void MainForm_Paint(object sender, PaintEventArgs e) 171 | { 172 | _main.Redraw(); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /CS64.UI/MapWaitForm.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace CS64.UI 3 | { 4 | partial class MapWaitForm 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | this.label1 = new System.Windows.Forms.Label(); 33 | this.SuspendLayout(); 34 | // 35 | // label1 36 | // 37 | this.label1.AutoSize = true; 38 | this.label1.Location = new System.Drawing.Point(74, 36); 39 | this.label1.Name = "label1"; 40 | this.label1.Size = new System.Drawing.Size(120, 15); 41 | this.label1.TabIndex = 0; 42 | this.label1.Text = "Press a button or key"; 43 | // 44 | // MapWaitForm 45 | // 46 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 47 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 48 | this.ClientSize = new System.Drawing.Size(268, 93); 49 | this.Controls.Add(this.label1); 50 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; 51 | this.Name = "MapWaitForm"; 52 | this.ShowInTaskbar = false; 53 | this.Text = "Press a key"; 54 | this.Load += new System.EventHandler(this.MapWaitForm_Load); 55 | this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.MapWaitForm_KeyDown); 56 | this.ResumeLayout(false); 57 | this.PerformLayout(); 58 | 59 | } 60 | 61 | #endregion 62 | 63 | private System.Windows.Forms.Label label1; 64 | } 65 | } -------------------------------------------------------------------------------- /CS64.UI/MapWaitForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Windows.Forms; 4 | using CS64.Core.Interface.Input; 5 | using SDL2; 6 | 7 | namespace CS64.UI 8 | { 9 | public partial class MapWaitForm : Form 10 | { 11 | private readonly InputProvider _inputProvider; 12 | private readonly InputKeyEnum _key; 13 | private System.Threading.Timer timer; 14 | private int countDown; 15 | public bool Success { get; set; } 16 | 17 | public MapWaitForm(InputProvider inputProvider, InputKeyEnum key) 18 | { 19 | _inputProvider = inputProvider; 20 | _key = key; 21 | Closing += (sender, args) => 22 | { 23 | timer.Change(Timeout.Infinite, Timeout.Infinite); 24 | }; 25 | 26 | InitializeComponent(); 27 | } 28 | 29 | private void MapWaitForm_Load(object sender, EventArgs e) 30 | { 31 | timer = new System.Threading.Timer(Callback, null, 0, 1000); 32 | countDown = 5; 33 | } 34 | 35 | 36 | 37 | private void UpdateText() 38 | { 39 | label1.Text = $"Press a button or key ({countDown})"; 40 | } 41 | 42 | private void Callback(object? state) 43 | { 44 | if (countDown == 0) 45 | { 46 | timer.Change(Timeout.Infinite, Timeout.Infinite); 47 | this.InvokeIfRequired(Close); 48 | } 49 | label1.InvokeIfRequired(UpdateText); 50 | countDown--; 51 | } 52 | 53 | private void MapWaitForm_KeyDown(object sender, KeyEventArgs e) 54 | { 55 | if(!KeyCodeMappings.TryGetKeyCode(e.KeyCode, out var code)) 56 | { 57 | code = (SDL.SDL_Keycode) e.KeyCode; 58 | } 59 | var keyEvent = new SDL.SDL_KeyboardEvent() 60 | { 61 | type = SDL.SDL_EventType.SDL_KEYDOWN, 62 | keysym = new SDL.SDL_Keysym() 63 | { 64 | sym = code 65 | } 66 | }; 67 | _inputProvider.HandleEvent(keyEvent); 68 | Success = true; 69 | timer.Change(Timeout.Infinite, Timeout.Infinite); 70 | Close(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /CS64.UI/MapWaitForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CS64.UI/MappingForm.Designer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace CS64.UI 3 | { 4 | partial class MappingForm 5 | { 6 | /// 7 | /// Required designer variable. 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// Clean up any resources being used. 13 | /// 14 | /// true if managed resources should be disposed; otherwise, false. 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows Form Designer generated code 25 | 26 | /// 27 | /// Required method for Designer support - do not modify 28 | /// the contents of this method with the code editor. 29 | /// 30 | private void InitializeComponent() 31 | { 32 | this.buttonUp = new System.Windows.Forms.Button(); 33 | this.buttonDown = new System.Windows.Forms.Button(); 34 | this.buttonLeft = new System.Windows.Forms.Button(); 35 | this.buttonRight = new System.Windows.Forms.Button(); 36 | this.buttonSelect = new System.Windows.Forms.Button(); 37 | this.buttonStart = new System.Windows.Forms.Button(); 38 | this.buttonB = new System.Windows.Forms.Button(); 39 | this.buttonA = new System.Windows.Forms.Button(); 40 | this.buttonTurboB = new System.Windows.Forms.Button(); 41 | this.buttonTurboA = new System.Windows.Forms.Button(); 42 | this.textBoxUp = new System.Windows.Forms.TextBox(); 43 | this.textBoxDown = new System.Windows.Forms.TextBox(); 44 | this.textBoxRight = new System.Windows.Forms.TextBox(); 45 | this.textBoxLeft = new System.Windows.Forms.TextBox(); 46 | this.textBoxA = new System.Windows.Forms.TextBox(); 47 | this.textBoxB = new System.Windows.Forms.TextBox(); 48 | this.textBoxTurboA = new System.Windows.Forms.TextBox(); 49 | this.textBoxTurboB = new System.Windows.Forms.TextBox(); 50 | this.textBoxStart = new System.Windows.Forms.TextBox(); 51 | this.textBoxSelect = new System.Windows.Forms.TextBox(); 52 | this.SuspendLayout(); 53 | // 54 | // buttonUp 55 | // 56 | this.buttonUp.Location = new System.Drawing.Point(319, 53); 57 | this.buttonUp.Name = "buttonUp"; 58 | this.buttonUp.Size = new System.Drawing.Size(65, 23); 59 | this.buttonUp.TabIndex = 0; 60 | this.buttonUp.Text = "Up"; 61 | this.buttonUp.UseVisualStyleBackColor = true; 62 | // 63 | // buttonDown 64 | // 65 | this.buttonDown.Location = new System.Drawing.Point(319, 82); 66 | this.buttonDown.Name = "buttonDown"; 67 | this.buttonDown.Size = new System.Drawing.Size(65, 23); 68 | this.buttonDown.TabIndex = 1; 69 | this.buttonDown.Text = "Down"; 70 | this.buttonDown.UseVisualStyleBackColor = true; 71 | // 72 | // buttonLeft 73 | // 74 | this.buttonLeft.Location = new System.Drawing.Point(319, 111); 75 | this.buttonLeft.Name = "buttonLeft"; 76 | this.buttonLeft.Size = new System.Drawing.Size(65, 23); 77 | this.buttonLeft.TabIndex = 2; 78 | this.buttonLeft.Text = "Left"; 79 | this.buttonLeft.UseVisualStyleBackColor = true; 80 | // 81 | // buttonRight 82 | // 83 | this.buttonRight.Location = new System.Drawing.Point(319, 140); 84 | this.buttonRight.Name = "buttonRight"; 85 | this.buttonRight.Size = new System.Drawing.Size(65, 23); 86 | this.buttonRight.TabIndex = 3; 87 | this.buttonRight.Text = "Right"; 88 | this.buttonRight.UseVisualStyleBackColor = true; 89 | // 90 | // buttonSelect 91 | // 92 | this.buttonSelect.Location = new System.Drawing.Point(459, 53); 93 | this.buttonSelect.Name = "buttonSelect"; 94 | this.buttonSelect.Size = new System.Drawing.Size(65, 23); 95 | this.buttonSelect.TabIndex = 4; 96 | this.buttonSelect.Text = "Select"; 97 | this.buttonSelect.UseVisualStyleBackColor = true; 98 | // 99 | // buttonStart 100 | // 101 | this.buttonStart.Location = new System.Drawing.Point(459, 82); 102 | this.buttonStart.Name = "buttonStart"; 103 | this.buttonStart.Size = new System.Drawing.Size(65, 23); 104 | this.buttonStart.TabIndex = 5; 105 | this.buttonStart.Text = "Start"; 106 | this.buttonStart.UseVisualStyleBackColor = true; 107 | // 108 | // buttonB 109 | // 110 | this.buttonB.Location = new System.Drawing.Point(319, 169); 111 | this.buttonB.Name = "buttonB"; 112 | this.buttonB.Size = new System.Drawing.Size(65, 23); 113 | this.buttonB.TabIndex = 6; 114 | this.buttonB.Text = "B"; 115 | this.buttonB.UseVisualStyleBackColor = true; 116 | // 117 | // buttonA 118 | // 119 | this.buttonA.Location = new System.Drawing.Point(319, 198); 120 | this.buttonA.Name = "buttonA"; 121 | this.buttonA.Size = new System.Drawing.Size(65, 23); 122 | this.buttonA.TabIndex = 7; 123 | this.buttonA.Text = "A"; 124 | this.buttonA.UseVisualStyleBackColor = true; 125 | // 126 | // buttonTurboB 127 | // 128 | this.buttonTurboB.Location = new System.Drawing.Point(459, 111); 129 | this.buttonTurboB.Name = "buttonTurboB"; 130 | this.buttonTurboB.Size = new System.Drawing.Size(65, 23); 131 | this.buttonTurboB.TabIndex = 8; 132 | this.buttonTurboB.Text = "Turbo B"; 133 | this.buttonTurboB.UseVisualStyleBackColor = true; 134 | // 135 | // buttonTurboA 136 | // 137 | this.buttonTurboA.Location = new System.Drawing.Point(459, 140); 138 | this.buttonTurboA.Name = "buttonTurboA"; 139 | this.buttonTurboA.Size = new System.Drawing.Size(65, 23); 140 | this.buttonTurboA.TabIndex = 9; 141 | this.buttonTurboA.Text = "Turbo A"; 142 | this.buttonTurboA.UseVisualStyleBackColor = true; 143 | // 144 | // textBoxUp 145 | // 146 | this.textBoxUp.Enabled = false; 147 | this.textBoxUp.Location = new System.Drawing.Point(390, 53); 148 | this.textBoxUp.Name = "textBoxUp"; 149 | this.textBoxUp.Size = new System.Drawing.Size(62, 23); 150 | this.textBoxUp.TabIndex = 10; 151 | this.textBoxUp.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 152 | // 153 | // textBoxDown 154 | // 155 | this.textBoxDown.Enabled = false; 156 | this.textBoxDown.Location = new System.Drawing.Point(390, 82); 157 | this.textBoxDown.Name = "textBoxDown"; 158 | this.textBoxDown.Size = new System.Drawing.Size(62, 23); 159 | this.textBoxDown.TabIndex = 11; 160 | this.textBoxDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 161 | // 162 | // textBoxRight 163 | // 164 | this.textBoxRight.Enabled = false; 165 | this.textBoxRight.Location = new System.Drawing.Point(390, 140); 166 | this.textBoxRight.Name = "textBoxRight"; 167 | this.textBoxRight.Size = new System.Drawing.Size(62, 23); 168 | this.textBoxRight.TabIndex = 13; 169 | this.textBoxRight.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 170 | // 171 | // textBoxLeft 172 | // 173 | this.textBoxLeft.Enabled = false; 174 | this.textBoxLeft.Location = new System.Drawing.Point(390, 111); 175 | this.textBoxLeft.Name = "textBoxLeft"; 176 | this.textBoxLeft.Size = new System.Drawing.Size(62, 23); 177 | this.textBoxLeft.TabIndex = 12; 178 | this.textBoxLeft.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 179 | // 180 | // textBoxA 181 | // 182 | this.textBoxA.Enabled = false; 183 | this.textBoxA.Location = new System.Drawing.Point(390, 198); 184 | this.textBoxA.Name = "textBoxA"; 185 | this.textBoxA.Size = new System.Drawing.Size(62, 23); 186 | this.textBoxA.TabIndex = 15; 187 | this.textBoxA.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 188 | // 189 | // textBoxB 190 | // 191 | this.textBoxB.Enabled = false; 192 | this.textBoxB.Location = new System.Drawing.Point(390, 169); 193 | this.textBoxB.Name = "textBoxB"; 194 | this.textBoxB.Size = new System.Drawing.Size(62, 23); 195 | this.textBoxB.TabIndex = 14; 196 | this.textBoxB.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 197 | // 198 | // textBoxTurboA 199 | // 200 | this.textBoxTurboA.Enabled = false; 201 | this.textBoxTurboA.Location = new System.Drawing.Point(530, 140); 202 | this.textBoxTurboA.Name = "textBoxTurboA"; 203 | this.textBoxTurboA.Size = new System.Drawing.Size(62, 23); 204 | this.textBoxTurboA.TabIndex = 19; 205 | this.textBoxTurboA.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 206 | // 207 | // textBoxTurboB 208 | // 209 | this.textBoxTurboB.Enabled = false; 210 | this.textBoxTurboB.Location = new System.Drawing.Point(530, 111); 211 | this.textBoxTurboB.Name = "textBoxTurboB"; 212 | this.textBoxTurboB.Size = new System.Drawing.Size(62, 23); 213 | this.textBoxTurboB.TabIndex = 18; 214 | this.textBoxTurboB.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 215 | // 216 | // textBoxStart 217 | // 218 | this.textBoxStart.Enabled = false; 219 | this.textBoxStart.Location = new System.Drawing.Point(530, 82); 220 | this.textBoxStart.Name = "textBoxStart"; 221 | this.textBoxStart.Size = new System.Drawing.Size(62, 23); 222 | this.textBoxStart.TabIndex = 17; 223 | this.textBoxStart.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 224 | // 225 | // textBoxSelect 226 | // 227 | this.textBoxSelect.Enabled = false; 228 | this.textBoxSelect.Location = new System.Drawing.Point(530, 53); 229 | this.textBoxSelect.Name = "textBoxSelect"; 230 | this.textBoxSelect.Size = new System.Drawing.Size(62, 23); 231 | this.textBoxSelect.TabIndex = 16; 232 | this.textBoxSelect.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 233 | // 234 | // MappingForm 235 | // 236 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 237 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 238 | this.ClientSize = new System.Drawing.Size(613, 357); 239 | this.Controls.Add(this.textBoxTurboA); 240 | this.Controls.Add(this.textBoxTurboB); 241 | this.Controls.Add(this.textBoxStart); 242 | this.Controls.Add(this.textBoxSelect); 243 | this.Controls.Add(this.textBoxA); 244 | this.Controls.Add(this.textBoxB); 245 | this.Controls.Add(this.textBoxRight); 246 | this.Controls.Add(this.textBoxLeft); 247 | this.Controls.Add(this.textBoxDown); 248 | this.Controls.Add(this.textBoxUp); 249 | this.Controls.Add(this.buttonTurboA); 250 | this.Controls.Add(this.buttonTurboB); 251 | this.Controls.Add(this.buttonA); 252 | this.Controls.Add(this.buttonB); 253 | this.Controls.Add(this.buttonStart); 254 | this.Controls.Add(this.buttonSelect); 255 | this.Controls.Add(this.buttonRight); 256 | this.Controls.Add(this.buttonLeft); 257 | this.Controls.Add(this.buttonDown); 258 | this.Controls.Add(this.buttonUp); 259 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; 260 | this.Name = "MappingForm"; 261 | this.Text = "Controllers"; 262 | this.Load += new System.EventHandler(this.MappingForm_Load); 263 | this.ResumeLayout(false); 264 | this.PerformLayout(); 265 | 266 | } 267 | 268 | #endregion 269 | 270 | private System.Windows.Forms.Button buttonUp; 271 | private System.Windows.Forms.Button buttonDown; 272 | private System.Windows.Forms.Button buttonLeft; 273 | private System.Windows.Forms.Button buttonRight; 274 | private System.Windows.Forms.Button buttonSelect; 275 | private System.Windows.Forms.Button buttonStart; 276 | private System.Windows.Forms.Button buttonB; 277 | private System.Windows.Forms.Button buttonA; 278 | private System.Windows.Forms.Button buttonTurboB; 279 | private System.Windows.Forms.Button buttonTurboA; 280 | private System.Windows.Forms.TextBox textBoxUp; 281 | private System.Windows.Forms.TextBox textBoxDown; 282 | private System.Windows.Forms.TextBox textBoxRight; 283 | private System.Windows.Forms.TextBox textBoxLeft; 284 | private System.Windows.Forms.TextBox textBoxA; 285 | private System.Windows.Forms.TextBox textBoxB; 286 | private System.Windows.Forms.TextBox textBoxTurboA; 287 | private System.Windows.Forms.TextBox textBoxTurboB; 288 | private System.Windows.Forms.TextBox textBoxStart; 289 | private System.Windows.Forms.TextBox textBoxSelect; 290 | } 291 | } -------------------------------------------------------------------------------- /CS64.UI/MappingForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using CS64.Core.Interface; 4 | using CS64.Core.Interface.Input; 5 | 6 | namespace CS64.UI 7 | { 8 | public partial class MappingForm : Form 9 | { 10 | private readonly Main _main; 11 | 12 | public MappingForm(Main main) 13 | { 14 | _main = main; 15 | InitializeComponent(); 16 | } 17 | 18 | private void ShowMapping(InputKeyEnum key) 19 | { 20 | _main.SetMapping(key); 21 | var mapWait = new MapWaitForm(_main.InputProvider, key); 22 | mapWait.Top = Top + (Height - mapWait.Height) / 2; 23 | mapWait.Left = Left + (Width - mapWait.Width) / 2; 24 | mapWait.ShowDialog(this); 25 | } 26 | 27 | //private void GetMapping(InputKeyEnum key, TextBox textbox) 28 | //{ 29 | // textbox.Text = _main.InputProvider.GetMapping(key); 30 | //} 31 | 32 | private void MappingForm_Load(object sender, EventArgs e) 33 | { 34 | //GetMapping(InputKeyEnum.Up, textBoxUp); 35 | //GetMapping(InputKeyEnum.Down, textBoxDown); 36 | //GetMapping(InputKeyEnum.Left, textBoxLeft); 37 | //GetMapping(InputKeyEnum.Right, textBoxRight); 38 | //GetMapping(InputKeyEnum.B, textBoxB); 39 | //GetMapping(InputKeyEnum.A, textBoxA); 40 | //GetMapping(InputKeyEnum.Select, textBoxSelect); 41 | //GetMapping(InputKeyEnum.Start, textBoxStart); 42 | 43 | 44 | //buttonUp.Click += (o, e) => { ShowMapping(InputKeyEnum.Up); GetMapping(InputKeyEnum.Up, textBoxUp); }; 45 | //buttonDown.Click += (o, e) => { ShowMapping(InputKeyEnum.Down); GetMapping(InputKeyEnum.Down, textBoxDown); }; 46 | //buttonLeft.Click += (o, e) => { ShowMapping(InputKeyEnum.Left); GetMapping(InputKeyEnum.Left, textBoxLeft); }; 47 | //buttonRight.Click += (o, e) => { ShowMapping(InputKeyEnum.Right); GetMapping(InputKeyEnum.Right, textBoxRight); }; 48 | //buttonB.Click += (o, e) => { ShowMapping(InputKeyEnum.B); GetMapping(InputKeyEnum.B, textBoxB); }; 49 | //buttonA.Click += (o, e) => { ShowMapping(InputKeyEnum.A); GetMapping(InputKeyEnum.A, textBoxA); }; 50 | //buttonSelect.Click += (o, e) => { ShowMapping(InputKeyEnum.Select); GetMapping(InputKeyEnum.Select, textBoxSelect); }; 51 | //buttonStart.Click += (o, e) => { ShowMapping(InputKeyEnum.Start); GetMapping(InputKeyEnum.Start, textBoxStart); }; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CS64.UI/MappingForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CS64.UI/famicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64.UI/famicon.ico -------------------------------------------------------------------------------- /CS64.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31515.178 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS64", "CS64\CS64.csproj", "{53299216-A392-4796-9C33-E094FF0472B1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS64.Core", "CS64.Core\CS64.Core.csproj", "{5B7D8A4F-1137-4F79-9218-E1C8232F6D10}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS64.UI", "CS64.UI\CS64.UI.csproj", "{B21BE846-762D-49E6-825C-BBF0903C4F4E}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {53299216-A392-4796-9C33-E094FF0472B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {53299216-A392-4796-9C33-E094FF0472B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {53299216-A392-4796-9C33-E094FF0472B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {53299216-A392-4796-9C33-E094FF0472B1}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {5B7D8A4F-1137-4F79-9218-E1C8232F6D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {5B7D8A4F-1137-4F79-9218-E1C8232F6D10}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {5B7D8A4F-1137-4F79-9218-E1C8232F6D10}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {5B7D8A4F-1137-4F79-9218-E1C8232F6D10}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {B21BE846-762D-49E6-825C-BBF0903C4F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {B21BE846-762D-49E6-825C-BBF0903C4F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {B21BE846-762D-49E6-825C-BBF0903C4F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {B21BE846-762D-49E6-825C-BBF0903C4F4E}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {D2F4EB27-904F-40DE-B728-C1A1673AFF02} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /CS64/CS64.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0-windows 6 | true 7 | win-x64 8 | true 9 | true 10 | true 11 | true 12 | false 13 | false 14 | 15 | 16 | 17 | true 18 | 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /CS64/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CS64.Core.Interface; 3 | using CS64.UI; 4 | 5 | namespace CS64 6 | { 7 | class Program 8 | { 9 | [STAThread] 10 | static void Main(string[] args) 11 | { 12 | using (var main = new Main()) 13 | { 14 | 15 | using (var form = new MainForm(main)) 16 | { 17 | form.Show(); 18 | 19 | main.Initialize(form); 20 | main.LoadROM("rom\\characters.bin", 0xD000, 0x1000); 21 | main.LoadROM("rom\\basic.bin", 0xA000, 0x2000); 22 | main.LoadROM("rom\\kernal.bin", 0xE000, 0x2000); 23 | 24 | string rom; 25 | //main.Test(); 26 | ////main.Load(args[0]); 27 | //if (args.Length > 0) 28 | //{ 29 | // main.Load(args[0]); 30 | //} 31 | main.Reset(); 32 | main.Run(); 33 | } 34 | 35 | } 36 | 37 | 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /CS64/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CS64": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /CS64/publish.cmd: -------------------------------------------------------------------------------- 1 | dotnet publish --configuration Release 2 | -------------------------------------------------------------------------------- /CS64/rom/basic.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64/rom/basic.bin -------------------------------------------------------------------------------- /CS64/rom/characters.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64/rom/characters.bin -------------------------------------------------------------------------------- /CS64/rom/kernal.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RupertAvery/CS64/eb089999233654cc494235ad89267d8ae60d38de/CS64/rom/kernal.bin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) David Khristepher Santos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS64 - Commodore 64 Emulator 2 | 3 | **CS64** is a Commodore 64 emulator written in C#. 4 | 5 | This is a work in progress and only the most basic functionality is working. Currently the emulator will boot into BASIC 6 | and you should be able to type in programs and run them. 7 | 8 | # Screenshots 9 | 10 | ![image](https://user-images.githubusercontent.com/1910659/133099604-836461b8-8f8a-4829-a3c3-6f364deea554.png) 11 | 12 | # Prerequisites 13 | 14 | * .NET 5 SDK 15 | * Visual Studio 2019 v16+ 16 | 17 | # Todo 18 | 19 | * Fix interrupts 20 | * Excessive borders hack - look for documentation 21 | * Graphics modes and sprites 22 | * Joysticks 23 | * Test timers 24 | * States 25 | * Rewind 26 | * Light Pen 27 | * Tape I/O 28 | * Disk I/O 29 | * SID (Probably not, might use another emulator's SID implementation, or reSID) 30 | 31 | # Key mapping 32 | 33 | | Key | Function | 34 | |---------|--------------| 35 | | F10 | Reset | 36 | 37 | 38 | # Known Issues 39 | 40 | --------------------------------------------------------------------------------