diff --git a/Virtu/CustomDictionary.xml b/Virtu/CustomDictionary.xml index 0f57381..fc1a31c 100644 --- a/Virtu/CustomDictionary.xml +++ b/Virtu/CustomDictionary.xml @@ -2,7 +2,7 @@ - CXXX + Io RPC diff --git a/Virtu/DiskIIController.cs b/Virtu/DiskIIController.cs new file mode 100644 index 0000000..8af33cd --- /dev/null +++ b/Virtu/DiskIIController.cs @@ -0,0 +1,251 @@ +using System.Diagnostics.CodeAnalysis; +using Jellyfish.Library; +using Jellyfish.Virtu.Services; +using Jellyfish.Virtu.Settings; + +namespace Jellyfish.Virtu +{ + public sealed class DiskIIController : PeripheralCard + { + public DiskIIController(Machine machine) : + base(machine) + { + } + + public override void Initialize() + { + var romStream = StorageService.GetResourceStream("Roms/DiskII.rom", 0x0100); + romStream.ReadBlock(_romRegionC1C7, 0x0000, 0x0100); + + _drives[0].InsertDisk("Default.dsk", StorageService.GetResourceStream("Disks/Default.dsk", 0x23000), false); +#if WINDOWS + var settings = Machine.Settings.DiskII; + if (settings.Disk1.Name.Length > 0) + { + _drives[0].InsertDisk(settings.Disk1.Name, settings.Disk1.IsWriteProtected); + } + if (settings.Disk2.Name.Length > 0) + { + _drives[1].InsertDisk(settings.Disk2.Name, settings.Disk2.IsWriteProtected); + } +#endif + } + + public override void Reset() + { + _phaseStates = 0; + SetMotorOn(false); + SetDriveNumber(0); + _loadMode = false; + _writeMode = false; + } + + public override void Uninitialize() + { + Flush(); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public override int ReadIoRegionC0C0(int address) + { + switch (address & 0xF) + { + case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: + SetPhase(address); + break; + + case 0x8: + SetMotorOn(false); + break; + + case 0x9: + SetMotorOn(true); + break; + + case 0xA: + SetDriveNumber(0); + break; + + case 0xB: + SetDriveNumber(1); + break; + + case 0xC: + _loadMode = false; + if (_motorOn) + { + if (!_writeMode) + { + return _latch = _drives[_driveNumber].Read(); + } + else + { + WriteLatch(); + } + } + break; + + case 0xD: + _loadMode = true; + if (_motorOn && !_writeMode) + { + // write protect is forced if phase 1 is on [F9.7] + _latch &= 0x7F; + if (_drives[_driveNumber].IsWriteProtected || + (_phaseStates & Phase1On) != 0) + { + _latch |= 0x80; + } + } + break; + + case 0xE: + _writeMode = false; + break; + + case 0xF: + _writeMode = true; + break; + } + + if ((address & 1) == 0) + { + // only even addresses return the latch + if (_motorOn) + { + return _latch; + } + + // simple hack to fool DOS SAMESLOT drive spin check (usually at $BD34) + _driveSpin = !_driveSpin; + return _driveSpin ? 0x7E : 0x7F; + } + + return ReadFloatingBus(); + } + + public override int ReadIoRegionC1C7(int address) + { + return _romRegionC1C7[address & 0xFF]; + } + + public override void WriteIoRegionC0C0(int address, int data) + { + switch (address & 0xF) + { + case 0x0: case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: + SetPhase(address); + break; + + case 0x8: + SetMotorOn(false); + break; + + case 0x9: + SetMotorOn(true); + break; + + case 0xA: + SetDriveNumber(0); + break; + + case 0xB: + SetDriveNumber(1); + break; + + case 0xC: + _loadMode = false; + if (_writeMode) + { + WriteLatch(); + } + break; + + case 0xD: + _loadMode = true; + break; + + case 0xE: + _writeMode = false; + break; + + case 0xF: + _writeMode = true; + break; + } + + if (_motorOn && _writeMode) + { + if (_loadMode) + { + // any address writes latch for sequencer LD; OE1/2 irrelevant ['323 datasheet] + _latch = data; + } + } + } + + private void WriteLatch() + { + // write protect is forced if phase 1 is on [F9.7] + if ((_phaseStates & Phase1On) == 0) + { + _drives[_driveNumber].Write(_latch); + } + } + + private void Flush() + { + _drives[_driveNumber].FlushTrack(); + } + + private void SetDriveNumber(int driveNumber) + { + if (_driveNumber != driveNumber) + { + Flush(); + _driveNumber = driveNumber; + } + } + + private void SetMotorOn(bool state) + { + if (_motorOn && !state) + { + Flush(); + } + _motorOn = state; + } + + private void SetPhase(int address) + { + int phase = (address >> 1) & 0x3; + int state = address & 1; + _phaseStates &= ~(1 << phase); + _phaseStates |= (state << phase); + + if (_motorOn) + { + _drives[_driveNumber].ApplyPhaseChange(_phaseStates); + } + } + + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public DiskIIDrive[] Drives { get { return _drives; } } + + private const int Phase0On = 1 << 0; + private const int Phase1On = 1 << 1; + private const int Phase2On = 1 << 2; + private const int Phase3On = 1 << 3; + + private DiskIIDrive[] _drives = new DiskIIDrive[] { new DiskIIDrive(), new DiskIIDrive() }; + private int _latch; + private int _phaseStates; + private bool _motorOn; + private int _driveNumber; + private bool _loadMode; + private bool _writeMode; + private bool _driveSpin; + + private byte[] _romRegionC1C7 = new byte[0x0100]; + } +} diff --git a/Virtu/DiskIIDrive.cs b/Virtu/DiskIIDrive.cs new file mode 100644 index 0000000..7a390b9 --- /dev/null +++ b/Virtu/DiskIIDrive.cs @@ -0,0 +1,120 @@ +using System; +using System.IO; +using System.Security; +using Jellyfish.Library; + +namespace Jellyfish.Virtu +{ + public sealed class DiskIIDrive + { + public DiskIIDrive() + { + DriveArmStepDelta[0] = new int[] { 0, 0, 1, 1, 0, 0, 1, 1, -1, -1, 0, 0, -1, -1, 0, 0 }; // phase 0 + DriveArmStepDelta[1] = new int[] { 0, -1, 0, -1, 1, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0 }; // phase 1 + DriveArmStepDelta[2] = new int[] { 0, 0, -1, -1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0 }; // phase 2 + DriveArmStepDelta[3] = new int[] { 0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0 }; // phase 3 + } + + [SecurityCritical] + public void InsertDisk(string fileName, bool isWriteProtected) + { + using (var stream = File.OpenRead(fileName)) + { + InsertDisk(fileName, stream, isWriteProtected); + } + } + + public void InsertDisk(string name, Stream stream, bool isWriteProtected) + { + FlushTrack(); + + // TODO handle null param/empty string for eject, or add Eject() + + _disk = Disk525.CreateDisk(name, stream.ReadAllBytes(), isWriteProtected); + _trackLoaded = false; + } + + public void ApplyPhaseChange(int phaseState) + { + // step the drive head according to stepper magnet changes + int delta = DriveArmStepDelta[_trackNumber & 0x3][phaseState]; + if (delta != 0) + { + int newTrackNumber = MathHelpers.Clamp(_trackNumber + delta, 0, TrackNumberMax); + if (newTrackNumber != _trackNumber) + { + FlushTrack(); + _trackNumber = newTrackNumber; + _trackOffset = 0; + _trackLoaded = false; + } + } + } + + public int Read() + { + if (LoadTrack()) + { + int data = _trackData[_trackOffset++]; + if (_trackOffset >= Disk525.TrackSize) + { + _trackOffset = 0; + } + + return data; + } + + return _random.Next(0x01, 0xFF); + } + + public void Write(int data) + { + if (LoadTrack()) + { + _trackChanged = true; + _trackData[_trackOffset++] = (byte)data; + if (_trackOffset >= Disk525.TrackSize) + { + _trackOffset = 0; + } + } + } + + private bool LoadTrack() + { + if (!_trackLoaded && (_disk != null)) + { + _disk.ReadTrack(_trackNumber, 0, _trackData); + _trackLoaded = true; + } + + return _trackLoaded; + } + + public void FlushTrack() + { + if (_trackChanged) + { + _disk.WriteTrack(_trackNumber, 0, _trackData); + _trackChanged = false; + } + } + + public bool IsWriteProtected { get { return _disk.IsWriteProtected; } } + + private const int TrackNumberMax = 0x44; + + private const int PhaseCount = 4; + + private readonly int[][] DriveArmStepDelta = new int[PhaseCount][]; + + private Disk525 _disk; + private bool _trackLoaded; + private bool _trackChanged; + private int _trackNumber; + private int _trackOffset; + private byte[] _trackData = new byte[Disk525.TrackSize]; + + private Random _random = new Random(); + } +} diff --git a/Virtu/GamePort.cs b/Virtu/GamePort.cs index d8f8164..5267f09 100644 --- a/Virtu/GamePort.cs +++ b/Virtu/GamePort.cs @@ -42,7 +42,7 @@ public bool ReadButton2() { var settings = Machine.Settings.GamePort; - return (_gamePortService.IsButton2Down || + return (_gamePortService.IsButton2Down || _keyboardService.IsShiftKeyDown || // [TN9] (settings.UseKeyboard && (settings.Key.Button2 > 0) && _keyboardService.IsKeyDown(settings.Key.Button2))); } diff --git a/Virtu/Machine.cs b/Virtu/Machine.cs index 5646033..6eefcf5 100644 --- a/Virtu/Machine.cs +++ b/Virtu/Machine.cs @@ -19,13 +19,24 @@ public Machine() Cpu = new Cpu(this); Memory = new Memory(this); - DiskII = new DiskII(this); Keyboard = new Keyboard(this); GamePort = new GamePort(this); Cassette = new Cassette(this); Speaker = new Speaker(this); Video = new Video(this); - Components = new Collection { Cpu, Memory, DiskII, Keyboard, GamePort, Cassette, Speaker, Video }; + NoSlotClock = new NoSlotClock(); + + var emptySlot = new PeripheralCard(this); + Slot1 = emptySlot; + Slot2 = emptySlot; + Slot3 = emptySlot; + Slot4 = emptySlot; + Slot5 = emptySlot; + Slot6 = new DiskIIController(this); + Slot7 = emptySlot; + + Slots = new Collection { null, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 }; + Components = new Collection { Cpu, Memory, Keyboard, GamePort, Cassette, Speaker, Video, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 }; Thread = new Thread(Run) { Name = "Machine" }; } @@ -76,6 +87,20 @@ public void Stop() } } + public DiskIIController FindDiskIIController() + { + for (int i = 7; i >= 1; i--) + { + var diskII = Slots[i] as DiskIIController; + if (diskII != null) + { + return diskII; + } + } + + return null; + } + private void Run() // machine thread { Components.ForEach(component => component.Initialize()); @@ -108,12 +133,22 @@ private void Run() // machine thread public Cpu Cpu { get; private set; } public Memory Memory { get; private set; } - public DiskII DiskII { get; private set; } public Keyboard Keyboard { get; private set; } public GamePort GamePort { get; private set; } public Cassette Cassette { get; private set; } public Speaker Speaker { get; private set; } public Video Video { get; private set; } + public NoSlotClock NoSlotClock { get; private set; } + + public PeripheralCard Slot1 { get; private set; } + public PeripheralCard Slot2 { get; private set; } + public PeripheralCard Slot3 { get; private set; } + public PeripheralCard Slot4 { get; private set; } + public PeripheralCard Slot5 { get; private set; } + public PeripheralCard Slot6 { get; private set; } + public PeripheralCard Slot7 { get; private set; } + + public Collection Slots { get; private set; } public Collection Components { get; private set; } public Thread Thread { get; private set; } diff --git a/Virtu/Memory.cs b/Virtu/Memory.cs index 1ffa964..2920af0 100644 --- a/Virtu/Memory.cs +++ b/Virtu/Memory.cs @@ -81,12 +81,12 @@ public Memory(Machine machine) : public override void Initialize() { - _diskII = Machine.DiskII; _keyboard = Machine.Keyboard; _gamePort = Machine.GamePort; _cassette = Machine.Cassette; _speaker = Machine.Speaker; _video = Machine.Video; + _noSlotClock = Machine.NoSlotClock; var romStream = StorageService.GetResourceStream("Roms/AppleIIe.rom", 0x4000); romStream.Seek(0x0100, SeekOrigin.Current); @@ -94,9 +94,6 @@ public override void Initialize() romStream.ReadBlock(_romRegionD0DF, 0x0000, 0x1000); romStream.ReadBlock(_romRegionE0FF, 0x0000, 0x2000); - romStream = StorageService.GetResourceStream("Roms/DiskII.rom", 0x0100); - romStream.ReadBlock(_romExternalRegionC1CF, 0x0500, 0x0100); - if ((ReadRomRegionE0FF(0xFBB3) == 0x06) && (ReadRomRegionE0FF(0xFBBF) == 0xC1)) { Monitor = MonitorType.Standard; @@ -105,14 +102,12 @@ public override void Initialize() { Monitor = MonitorType.Enhanced; } - - Buffer.BlockCopy(_romInternalRegionC1CF, 0x0700, _romExternalRegionC1CF, 0x0700, 0x0800); } public override void Reset() // [7-3] { ResetState(State80Col | State80Store | StateAltChrSet | StateAltZP | StateBank1 | StateHRamRd | StateHRamPreWrt | StateHRamWrt | // HRamWrt' [5-23] - StateHires | StatePage2 | StateRamRd | StateRamWrt | StateSlotC3Rom | StateIntCXRom | StateAn0 | StateAn1 | StateAn2 | StateAn3); + StateHires | StatePage2 | StateRamRd | StateRamWrt | StateIntCXRom | StateSlotC3Rom | StateIntC8Rom | StateAn0 | StateAn1 | StateAn2 | StateAn3); SetState(StateDRes); // An3' -> DRes [8-20] MapRegion0001(); @@ -125,8 +120,7 @@ public override void Reset() // [7-3] public int Read(int address) { int region = PageRegion[address >> 8]; - - return (region == RegionC0C0) ? ReadIoC0XX(address) : _regionRead[region][address - RegionBaseAddress[region]]; + return ((address & 0xF000) != 0xC000) ? _regionRead[region][address - RegionBaseAddress[region]] : ReadIoRegionC0CF(address); } public int ReadZeroPage(int address) @@ -154,9 +148,29 @@ public void WriteZeroPage(int address, int data) #endregion #region Read Actions + private int ReadIoRegionC0CF(int address) + { + switch (address & 0xFF00) + { + case 0xC000: + return ReadIoRegionC0C0(address); + + case 0xC100: case 0xC200: case 0xC400: case 0xC500: case 0xC600: case 0xC700: + return ReadIoRegionC1C7(address); + + case 0xC300: + return ReadIoRegionC3C3(address); + + case 0xC800: case 0xC900: case 0xCA00: case 0xCB00: case 0xCC00: case 0xCD00: case 0xCE00: case 0xCF00: + return ReadIoRegionC8CF(address); + } + + return _video.ReadFloatingBus(); + } + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")] - private int ReadIoC0XX(int address) + private int ReadIoRegionC0C0(int address) { switch (address) { @@ -181,13 +195,13 @@ private int ReadIoC0XX(int address) return SetBit7(_keyboard.ReadLatch(), IsRamWriteAux); case 0xC015: - return SetBit7(_keyboard.ReadLatch(), IsRomCXXXInternal); + return SetBit7(_keyboard.ReadLatch(), IsRomC1CFInternal); case 0xC016: return SetBit7(_keyboard.ReadLatch(), IsZeroPageAux); case 0xC017: - return SetBit7(_keyboard.ReadLatch(), IsRomC3XXExternal); + return SetBit7(_keyboard.ReadLatch(), IsRomC3C3External); case 0xC018: return SetBit7(_keyboard.ReadLatch(), Is80Store); @@ -296,37 +310,62 @@ private int ReadIoC0XX(int address) case 0xC090: case 0xC091: case 0xC092: case 0xC093: case 0xC094: case 0xC095: case 0xC096: case 0xC097: // slot1 case 0xC098: case 0xC099: case 0xC09A: case 0xC09B: case 0xC09C: case 0xC09D: case 0xC09E: case 0xC09F: - break; + return Machine.Slot1.ReadIoRegionC0C0(address); case 0xC0A0: case 0xC0A1: case 0xC0A2: case 0xC0A3: case 0xC0A4: case 0xC0A5: case 0xC0A6: case 0xC0A7: // slot2 case 0xC0A8: case 0xC0A9: case 0xC0AA: case 0xC0AB: case 0xC0AC: case 0xC0AD: case 0xC0AE: case 0xC0AF: - break; + return Machine.Slot2.ReadIoRegionC0C0(address); case 0xC0B0: case 0xC0B1: case 0xC0B2: case 0xC0B3: case 0xC0B4: case 0xC0B5: case 0xC0B6: case 0xC0B7: // slot3 case 0xC0B8: case 0xC0B9: case 0xC0BA: case 0xC0BB: case 0xC0BC: case 0xC0BD: case 0xC0BE: case 0xC0BF: - break; + return Machine.Slot3.ReadIoRegionC0C0(address); case 0xC0C0: case 0xC0C1: case 0xC0C2: case 0xC0C3: case 0xC0C4: case 0xC0C5: case 0xC0C6: case 0xC0C7: // slot4 case 0xC0C8: case 0xC0C9: case 0xC0CA: case 0xC0CB: case 0xC0CC: case 0xC0CD: case 0xC0CE: case 0xC0CF: - break; + return Machine.Slot4.ReadIoRegionC0C0(address); case 0xC0D0: case 0xC0D1: case 0xC0D2: case 0xC0D3: case 0xC0D4: case 0xC0D5: case 0xC0D6: case 0xC0D7: // slot5 case 0xC0D8: case 0xC0D9: case 0xC0DA: case 0xC0DB: case 0xC0DC: case 0xC0DD: case 0xC0DE: case 0xC0DF: - break; + return Machine.Slot5.ReadIoRegionC0C0(address); case 0xC0E0: case 0xC0E1: case 0xC0E2: case 0xC0E3: case 0xC0E4: case 0xC0E5: case 0xC0E6: case 0xC0E7: // slot6 case 0xC0E8: case 0xC0E9: case 0xC0EA: case 0xC0EB: case 0xC0EC: case 0xC0ED: case 0xC0EE: case 0xC0EF: - return _diskII.Read(address); + return Machine.Slot6.ReadIoRegionC0C0(address); case 0xC0F0: case 0xC0F1: case 0xC0F2: case 0xC0F3: case 0xC0F4: case 0xC0F5: case 0xC0F6: case 0xC0F7: // slot7 case 0xC0F8: case 0xC0F9: case 0xC0FA: case 0xC0FB: case 0xC0FC: case 0xC0FD: case 0xC0FE: case 0xC0FF: - break; + return Machine.Slot7.ReadIoRegionC0C0(address); default: throw new ArgumentOutOfRangeException("address"); } - return _video.ReadFloatingBus(); // [5-40] + return _video.ReadFloatingBus(); + } + + private int ReadIoRegionC1C7(int address) + { + _slotRegionC8CF = (address >> 8) & 0x07; + return IsRomC1CFInternal ? _romInternalRegionC1CF[address - 0xC100] : Machine.Slots[_slotRegionC8CF].ReadIoRegionC1C7(address); + } + + private int ReadIoRegionC3C3(int address) + { + _slotRegionC8CF = (address >> 8) & 0x07; + if (!IsRomC3C3External) + { + SetRomC8CF(true); // $C3XX sets IntC8Rom; inhibits I/O Strobe' [5-28, 7-21] + } + return _noSlotClock.Read(address, (IsRomC1CFInternal || !IsRomC3C3External) ? _romInternalRegionC1CF[address - 0xC100] : Machine.Slot3.ReadIoRegionC1C7(address)); + } + + private int ReadIoRegionC8CF(int address) + { + if (address == 0xCFFF) + { + SetRomC8CF(false); // $CFFF resets IntC8Rom [5-28, 7-21] + } + return (IsRomC1CFInternal || IsRomC8CFInternal) ? _romInternalRegionC1CF[address - 0xC100] : Machine.Slots[_slotRegionC8CF].ReadIoRegionC8CF(address); } [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "address-512")] @@ -351,7 +390,7 @@ public int ReadRomRegionE0FF(int address) #region Write Actions [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")] - private void WriteIoC0XX(int address, byte data) + private void WriteIoRegionC0C0(int address, byte data) { switch (address) { @@ -368,7 +407,7 @@ private void WriteIoC0XX(int address, byte data) break; case 0xC006: case 0xC007: - SetRomCXXX(TestBit(address, 0)); + SetRomC1CF(TestBit(address, 0)); break; case 0xC008: case 0xC009: @@ -376,7 +415,7 @@ private void WriteIoC0XX(int address, byte data) break; case 0xC00A: case 0xC00B: - SetRomC3XX(TestBit(address, 0)); + SetRomC3C3(TestBit(address, 0)); break; case 0xC00C: case 0xC00D: // [7-5] @@ -455,31 +494,37 @@ private void WriteIoC0XX(int address, byte data) case 0xC090: case 0xC091: case 0xC092: case 0xC093: case 0xC094: case 0xC095: case 0xC096: case 0xC097: // slot1 case 0xC098: case 0xC099: case 0xC09A: case 0xC09B: case 0xC09C: case 0xC09D: case 0xC09E: case 0xC09F: + Machine.Slot1.WriteIoRegionC0C0(address, data); break; case 0xC0A0: case 0xC0A1: case 0xC0A2: case 0xC0A3: case 0xC0A4: case 0xC0A5: case 0xC0A6: case 0xC0A7: // slot2 case 0xC0A8: case 0xC0A9: case 0xC0AA: case 0xC0AB: case 0xC0AC: case 0xC0AD: case 0xC0AE: case 0xC0AF: + Machine.Slot2.WriteIoRegionC0C0(address, data); break; case 0xC0B0: case 0xC0B1: case 0xC0B2: case 0xC0B3: case 0xC0B4: case 0xC0B5: case 0xC0B6: case 0xC0B7: // slot3 case 0xC0B8: case 0xC0B9: case 0xC0BA: case 0xC0BB: case 0xC0BC: case 0xC0BD: case 0xC0BE: case 0xC0BF: + Machine.Slot3.WriteIoRegionC0C0(address, data); break; case 0xC0C0: case 0xC0C1: case 0xC0C2: case 0xC0C3: case 0xC0C4: case 0xC0C5: case 0xC0C6: case 0xC0C7: // slot4 case 0xC0C8: case 0xC0C9: case 0xC0CA: case 0xC0CB: case 0xC0CC: case 0xC0CD: case 0xC0CE: case 0xC0CF: + Machine.Slot4.WriteIoRegionC0C0(address, data); break; case 0xC0D0: case 0xC0D1: case 0xC0D2: case 0xC0D3: case 0xC0D4: case 0xC0D5: case 0xC0D6: case 0xC0D7: // slot5 case 0xC0D8: case 0xC0D9: case 0xC0DA: case 0xC0DB: case 0xC0DC: case 0xC0DD: case 0xC0DE: case 0xC0DF: + Machine.Slot5.WriteIoRegionC0C0(address, data); break; case 0xC0E0: case 0xC0E1: case 0xC0E2: case 0xC0E3: case 0xC0E4: case 0xC0E5: case 0xC0E6: case 0xC0E7: // slot6 case 0xC0E8: case 0xC0E9: case 0xC0EA: case 0xC0EB: case 0xC0EC: case 0xC0ED: case 0xC0EE: case 0xC0EF: - _diskII.Write(address, data); + Machine.Slot6.WriteIoRegionC0C0(address, data); break; case 0xC0F0: case 0xC0F1: case 0xC0F2: case 0xC0F3: case 0xC0F4: case 0xC0F5: case 0xC0F6: case 0xC0F7: // slot7 case 0xC0F8: case 0xC0F9: case 0xC0FA: case 0xC0FB: case 0xC0FC: case 0xC0FD: case 0xC0FE: case 0xC0FF: + Machine.Slot7.WriteIoRegionC0C0(address, data); break; default: @@ -487,6 +532,41 @@ private void WriteIoC0XX(int address, byte data) } } + private void WriteIoRegionC1C7(int address, byte data) + { + _slotRegionC8CF = (address >> 8) & 0x07; + if (!IsRomC1CFInternal) + { + Machine.Slots[_slotRegionC8CF].WriteIoRegionC1C7(address, data); + } + } + + private void WriteIoRegionC3C3(int address, byte data) + { + _slotRegionC8CF = (address >> 8) & 0x07; + if (!IsRomC3C3External) + { + SetRomC8CF(true); // $C3XX sets IntC8Rom; inhibits I/O Strobe' [5-28, 7-21] + } + else if (!IsRomC1CFInternal) + { + Machine.Slot3.WriteIoRegionC1C7(address, data); + } + _noSlotClock.Write(address); + } + + private void WriteIoRegionC8CF(int address, byte data) + { + if (address == 0xCFFF) + { + SetRomC8CF(false); // $CFFF resets IntC8Rom [5-28, 7-21] + } + if (!IsRomC1CFInternal && !IsRomC8CFInternal) + { + Machine.Slots[_slotRegionC8CF].WriteIoRegionC8CF(address, data); + } + } + private void WriteRamMode0MainRegion0407(int address, byte data) { if (_ramMainRegion02BF[address - 0x0200] != data) @@ -991,7 +1071,7 @@ private void WriteRamModeFAuxRegion405F(int address, byte data) } } - private void WriteRomRegionC1FF(int address, byte data) + private void WriteRomRegionD0FF(int address, byte data) { } #endregion @@ -1100,7 +1180,7 @@ private void MapRegion203F() private void MapRegionC0CF() { _regionRead[RegionC0C0] = null; - if (IsRomCXXXInternal) + if (IsRomC1CFInternal) { _regionRead[RegionC1C7] = _romInternalRegionC1CF; _regionRead[RegionC3C3] = _romInternalRegionC1CF; @@ -1109,17 +1189,17 @@ private void MapRegionC0CF() else { _regionRead[RegionC1C7] = _romExternalRegionC1CF; - _regionRead[RegionC3C3] = IsRomC3XXExternal ? _romExternalRegionC1CF : _romInternalRegionC1CF; - _regionRead[RegionC8CF] = _romExternalRegionC1CF; + _regionRead[RegionC3C3] = IsRomC3C3External ? _romExternalRegionC1CF : _romInternalRegionC1CF; + _regionRead[RegionC8CF] = !IsRomC8CFInternal ? _romExternalRegionC1CF : _romInternalRegionC1CF; } _regionWrite[RegionC0C0] = null; _regionWrite[RegionC1C7] = null; _regionWrite[RegionC3C3] = null; _regionWrite[RegionC8CF] = null; - _writeRegion[RegionC0C0] = WriteIoC0XX; - _writeRegion[RegionC1C7] = WriteRomRegionC1FF; - _writeRegion[RegionC3C3] = WriteRomRegionC1FF; - _writeRegion[RegionC8CF] = WriteRomRegionC1FF; + _writeRegion[RegionC0C0] = WriteIoRegionC0C0; + _writeRegion[RegionC1C7] = WriteIoRegionC1C7; + _writeRegion[RegionC3C3] = WriteIoRegionC3C3; + _writeRegion[RegionC8CF] = WriteIoRegionC8CF; } private void MapRegionD0FF() @@ -1161,8 +1241,8 @@ private void MapRegionD0FF() { _regionWrite[RegionD0DF] = null; _regionWrite[RegionE0FF] = null; - _writeRegion[RegionD0DF] = WriteRomRegionC1FF; - _writeRegion[RegionE0FF] = WriteRomRegionC1FF; + _writeRegion[RegionD0DF] = WriteRomRegionD0FF; + _writeRegion[RegionE0FF] = WriteRomRegionD0FF; } } @@ -1315,7 +1395,16 @@ private void SetRamWrite(bool value) } } - private void SetRomC3XX(bool value) + private void SetRomC1CF(bool value) + { + if (!TestState(StateIntCXRom, value)) + { + SetState(StateIntCXRom, value); + MapRegionC0CF(); + } + } + + private void SetRomC3C3(bool value) { if (!TestState(StateSlotC3Rom, value)) { @@ -1324,11 +1413,11 @@ private void SetRomC3XX(bool value) } } - private void SetRomCXXX(bool value) + private void SetRomC8CF(bool value) { - if (!TestState(StateIntCXRom, value)) + if (!TestState(StateIntC8Rom, value)) { - SetState(StateIntCXRom, value); + SetState(StateIntC8Rom, value); MapRegionC0CF(); } } @@ -1427,8 +1516,9 @@ private bool TestState(int mask, int value) public bool IsRamWriteAux { get { return TestState(StateRamWrt); } } public bool IsRamWriteAuxRegion0407 { get { return Is80Store ? IsPage2 : IsRamWriteAux; } } public bool IsRamWriteAuxRegion203F { get { return TestState(State80Store | StateHires, State80Store | StateHires) ? IsPage2 : IsRamWriteAux; } } - public bool IsRomC3XXExternal { get { return TestState(StateSlotC3Rom); } } - public bool IsRomCXXXInternal { get { return TestState(StateIntCXRom); } } + public bool IsRomC1CFInternal { get { return TestState(StateIntCXRom); } } + public bool IsRomC3C3External { get { return TestState(StateSlotC3Rom); } } + public bool IsRomC8CFInternal { get { return TestState(StateIntC8Rom); } } public bool IsText { get { return TestState(StateText); } } public bool IsVideoPage2 { get { return TestState(State80Store | StatePage2, StatePage2); } } // 80Store inhibits video Page2 [5-7, 8-19] public bool IsZeroPageAux { get { return TestState(StateAltZP); } } @@ -1436,14 +1526,15 @@ private bool TestState(int mask, int value) public MonitorType Monitor { get; private set; } public int VideoMode { get { return StateVideoMode[_state & StateVideo]; } } - private DiskII _diskII; private Keyboard _keyboard; private GamePort _gamePort; private Cassette _cassette; private Speaker _speaker; private Video _video; + private NoSlotClock _noSlotClock; private int _state; + private int _slotRegionC8CF; private byte[] _zeroPage; private byte[][] _regionRead = new byte[RegionCount][]; diff --git a/Virtu/MemoryData.cs b/Virtu/MemoryData.cs index 0fc7111..b3697ec 100644 --- a/Virtu/MemoryData.cs +++ b/Virtu/MemoryData.cs @@ -83,11 +83,12 @@ public partial class Memory private const int StateRamRd = 0x002000; private const int StateRamWrt = 0x004000; private const int StateSlotC3Rom = 0x008000; - private const int StateIntCXRom = 0x010000; - private const int StateAn0 = 0x020000; - private const int StateAn1 = 0x040000; - private const int StateAn2 = 0x080000; - private const int StateAn3 = 0x100000; + private const int StateIntC8Rom = 0x010000; // [5-28] + private const int StateIntCXRom = 0x020000; + private const int StateAn0 = 0x040000; + private const int StateAn1 = 0x080000; + private const int StateAn2 = 0x100000; + private const int StateAn3 = 0x200000; private const int StateVideo = State80Col | StateText | StateMixed | StateHires | StateDRes; private const int StateVideoModeCount = 32; diff --git a/Virtu/NoSlotClock.cs b/Virtu/NoSlotClock.cs new file mode 100644 index 0000000..1b68e73 --- /dev/null +++ b/Virtu/NoSlotClock.cs @@ -0,0 +1,18 @@ +namespace Jellyfish.Virtu +{ + public sealed class NoSlotClock + { + public NoSlotClock() + { + } + + public int Read(int address, int data) + { + return data; + } + + public void Write(int address) + { + } + } +} diff --git a/Virtu/PeripheralCard.cs b/Virtu/PeripheralCard.cs new file mode 100644 index 0000000..7aa7b23 --- /dev/null +++ b/Virtu/PeripheralCard.cs @@ -0,0 +1,48 @@ +namespace Jellyfish.Virtu +{ + public class PeripheralCard : MachineComponent + { + public PeripheralCard(Machine machine) : + base(machine) + { + } + + public virtual int ReadIoRegionC0C0(int address) + { + // read Device Select' address $C0nX; n = slot number + 8 + return ReadFloatingBus(); + } + + public virtual int ReadIoRegionC1C7(int address) + { + // read I/O Select' address $CsXX; s = slot number + return ReadFloatingBus(); + } + + public virtual int ReadIoRegionC8CF(int address) + { + // read I/O Strobe' address $C800-$CFFF + return ReadFloatingBus(); + } + + public virtual void WriteIoRegionC0C0(int address, int data) + { + // write Device Select' address $C0nX; n = slot number + 8 + } + + public virtual void WriteIoRegionC1C7(int address, int data) + { + // write I/O Select' address $CsXX; s = slot number + } + + public virtual void WriteIoRegionC8CF(int address, int data) + { + // write I/O Strobe' address $C800-$CFFF + } + + protected int ReadFloatingBus() + { + return Machine.Video.ReadFloatingBus(); + } + } +} diff --git a/Virtu/Services/KeyboardService.cs b/Virtu/Services/KeyboardService.cs index 353c1f2..4b291cf 100644 --- a/Virtu/Services/KeyboardService.cs +++ b/Virtu/Services/KeyboardService.cs @@ -28,6 +28,9 @@ public virtual void Update() // main thread } public bool IsAnyKeyDown { get; protected set; } + public bool IsControlKeyDown { get; protected set; } + public bool IsShiftKeyDown { get; protected set; } + public bool IsOpenAppleKeyDown { get; protected set; } public bool IsCloseAppleKeyDown { get; protected set; } diff --git a/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj b/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj index 030a152..5a94459 100644 --- a/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj +++ b/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj @@ -128,15 +128,15 @@ Core\DiskDsk.cs - - Core\DiskII.cs + + Core\DiskIIController.cs + + + Core\DiskIIDrive.cs Core\DiskNib.cs - - Core\Drive525.cs - Core\GamePort.cs @@ -164,6 +164,12 @@ Core\MemoryData.cs + + Core\NoSlotClock.cs + + + Core\PeripheralCard.cs + Properties\Strings.Designer.cs True diff --git a/Virtu/Silverlight/MainPage.xaml.cs b/Virtu/Silverlight/MainPage.xaml.cs index af3a8af..55d5760 100644 --- a/Virtu/Silverlight/MainPage.xaml.cs +++ b/Virtu/Silverlight/MainPage.xaml.cs @@ -73,7 +73,11 @@ private void OnDiskButtonClick(int drive) using (var stream = dialog.File.OpenRead()) { _machine.Pause(); - _machine.DiskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false); + var diskII = _machine.FindDiskIIController(); + if (diskII != null) + { + diskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false); + } _machine.Unpause(); } } diff --git a/Virtu/Silverlight/Phone/Jellyfish.Virtu.Silverlight.Phone.csproj b/Virtu/Silverlight/Phone/Jellyfish.Virtu.Silverlight.Phone.csproj index 143186b..cfbc095 100644 --- a/Virtu/Silverlight/Phone/Jellyfish.Virtu.Silverlight.Phone.csproj +++ b/Virtu/Silverlight/Phone/Jellyfish.Virtu.Silverlight.Phone.csproj @@ -113,15 +113,15 @@ Core\DiskDsk.cs - - Core\DiskII.cs + + Core\DiskIIController.cs + + + Core\DiskIIDrive.cs Core\DiskNib.cs - - Core\Drive525.cs - Core\GamePort.cs @@ -149,6 +149,12 @@ Core\MemoryData.cs + + Core\NoSlotClock.cs + + + Core\PeripheralCard.cs + Properties\Strings.Designer.cs True diff --git a/Virtu/Silverlight/Services/SilverlightKeyboardService.cs b/Virtu/Silverlight/Services/SilverlightKeyboardService.cs index ebd4b48..dbfa0ec 100644 --- a/Virtu/Silverlight/Services/SilverlightKeyboardService.cs +++ b/Virtu/Silverlight/Services/SilverlightKeyboardService.cs @@ -44,11 +44,12 @@ public override void Update() // main thread } var modifiers = System.Windows.Input.Keyboard.Modifiers; - bool control = ((modifiers & ModifierKeys.Control) != 0); + IsControlKeyDown = ((modifiers & ModifierKeys.Control) != 0); + IsShiftKeyDown = ((modifiers & ModifierKeys.Shift) != 0); IsOpenAppleKeyDown = ((modifiers & ModifierKeys.Alt) != 0) || IsKeyDown(Key.NumPad0); IsCloseAppleKeyDown = ((modifiers & ModifierKeys.Windows) != 0) || IsKeyDown(Key.Decimal); - IsResetKeyDown = control && IsKeyDown(Key.Back); + IsResetKeyDown = IsControlKeyDown && IsKeyDown(Key.Back); base.Update(); } diff --git a/Virtu/Video.cs b/Virtu/Video.cs index 2aa745b..4d57e3c 100644 --- a/Virtu/Video.cs +++ b/Virtu/Video.cs @@ -90,7 +90,7 @@ public void DirtyScreenText() } } - public int ReadFloatingBus() + public int ReadFloatingBus() // [5-40] { // derive scanner counters from current cycles into frame; assumes hcount and vcount preset at start of frame [3-13, 3-15, 3-16] int cycles = _cyclesPerVSync - Machine.Events.FindEvent(_resetVSyncEvent); diff --git a/Virtu/Wpf/Jellyfish.Virtu.Wpf.csproj b/Virtu/Wpf/Jellyfish.Virtu.Wpf.csproj index 4fb1345..6dbffec 100644 --- a/Virtu/Wpf/Jellyfish.Virtu.Wpf.csproj +++ b/Virtu/Wpf/Jellyfish.Virtu.Wpf.csproj @@ -118,15 +118,15 @@ Core\DiskDsk.cs - - Core\DiskII.cs + + Core\DiskIIController.cs + + + Core\DiskIIDrive.cs Core\DiskNib.cs - - Core\Drive525.cs - Core\GamePort.cs @@ -154,6 +154,12 @@ Core\MemoryData.cs + + Core\NoSlotClock.cs + + + Core\PeripheralCard.cs + Properties\Strings.Designer.cs True diff --git a/Virtu/Wpf/MainPage.xaml.cs b/Virtu/Wpf/MainPage.xaml.cs index 36edda6..0bacba1 100644 --- a/Virtu/Wpf/MainPage.xaml.cs +++ b/Virtu/Wpf/MainPage.xaml.cs @@ -75,15 +75,19 @@ private void OnDiskButtonClick(int drive) using (var stream = File.OpenRead(dialog.FileName)) { _machine.Pause(); - _machine.DiskII.Drives[drive].InsertDisk(dialog.FileName, stream, false); - var settings = _machine.Settings.DiskII; - if (drive == 0) + var diskII = _machine.FindDiskIIController(); + if (diskII != null) { - settings.Disk1.Name = dialog.FileName; - } - else - { - settings.Disk2.Name = dialog.FileName; + diskII.Drives[drive].InsertDisk(dialog.FileName, stream, false); + var settings = _machine.Settings.DiskII; + if (drive == 0) + { + settings.Disk1.Name = dialog.FileName; + } + else + { + settings.Disk2.Name = dialog.FileName; + } } _machine.Unpause(); } diff --git a/Virtu/Wpf/Services/WpfKeyboardService.cs b/Virtu/Wpf/Services/WpfKeyboardService.cs index b335e11..fc11267 100644 --- a/Virtu/Wpf/Services/WpfKeyboardService.cs +++ b/Virtu/Wpf/Services/WpfKeyboardService.cs @@ -44,11 +44,12 @@ public override void Update() // main thread } } - bool control = ((keyboard.Modifiers & ModifierKeys.Control) != 0); + IsControlKeyDown = ((keyboard.Modifiers & ModifierKeys.Control) != 0); + IsShiftKeyDown = ((keyboard.Modifiers & ModifierKeys.Shift) != 0); IsOpenAppleKeyDown = keyboard.IsKeyDown(Key.LeftAlt) || IsKeyDown(Key.NumPad0); IsCloseAppleKeyDown = keyboard.IsKeyDown(Key.RightAlt) || IsKeyDown(Key.Decimal); - IsResetKeyDown = control && keyboard.IsKeyDown(Key.Back); + IsResetKeyDown = IsControlKeyDown && keyboard.IsKeyDown(Key.Back); base.Update(); } diff --git a/Virtu/Xna/Jellyfish.Virtu.Xna.Phone.csproj b/Virtu/Xna/Jellyfish.Virtu.Xna.Phone.csproj index 74103fb..fbbd0a2 100644 --- a/Virtu/Xna/Jellyfish.Virtu.Xna.Phone.csproj +++ b/Virtu/Xna/Jellyfish.Virtu.Xna.Phone.csproj @@ -122,15 +122,15 @@ Core\DiskDsk.cs - - Core\DiskII.cs + + Core\DiskIIController.cs + + + Core\DiskIIDrive.cs Core\DiskNib.cs - - Core\Drive525.cs - Core\GamePort.cs @@ -158,6 +158,12 @@ Core\MemoryData.cs + + Core\NoSlotClock.cs + + + Core\PeripheralCard.cs + Properties\Strings.Designer.cs True diff --git a/Virtu/Xna/Jellyfish.Virtu.Xna.Xbox.csproj b/Virtu/Xna/Jellyfish.Virtu.Xna.Xbox.csproj index fe5ea17..d497617 100644 --- a/Virtu/Xna/Jellyfish.Virtu.Xna.Xbox.csproj +++ b/Virtu/Xna/Jellyfish.Virtu.Xna.Xbox.csproj @@ -128,15 +128,15 @@ Core\DiskDsk.cs - - Core\DiskII.cs + + Core\DiskIIController.cs + + + Core\DiskIIDrive.cs Core\DiskNib.cs - - Core\Drive525.cs - Core\GamePort.cs @@ -164,6 +164,12 @@ Core\MemoryData.cs + + Core\NoSlotClock.cs + + + Core\PeripheralCard.cs + Properties\Strings.Designer.cs True diff --git a/Virtu/Xna/Jellyfish.Virtu.Xna.csproj b/Virtu/Xna/Jellyfish.Virtu.Xna.csproj index 88bc97d..6f8b23b 100644 --- a/Virtu/Xna/Jellyfish.Virtu.Xna.csproj +++ b/Virtu/Xna/Jellyfish.Virtu.Xna.csproj @@ -132,15 +132,15 @@ Core\DiskDsk.cs - - Core\DiskII.cs + + Core\DiskIIController.cs + + + Core\DiskIIDrive.cs Core\DiskNib.cs - - Core\Drive525.cs - Core\GamePort.cs @@ -168,6 +168,12 @@ Core\MemoryData.cs + + Core\NoSlotClock.cs + + + Core\PeripheralCard.cs + Properties\Strings.Designer.cs True diff --git a/Virtu/Xna/Services/XnaKeyboardService.cs b/Virtu/Xna/Services/XnaKeyboardService.cs index 43af711..51961a0 100644 --- a/Virtu/Xna/Services/XnaKeyboardService.cs +++ b/Virtu/Xna/Services/XnaKeyboardService.cs @@ -67,11 +67,12 @@ public override void Update() // main thread } } - bool control = IsKeyDown(Keys.LeftControl) || IsKeyDown(Keys.RightControl); + IsControlKeyDown = IsKeyDown(Keys.LeftControl) || IsKeyDown(Keys.RightControl); + IsShiftKeyDown = IsKeyDown(Keys.LeftShift) || IsKeyDown(Keys.RightShift); IsOpenAppleKeyDown = IsKeyDown(Keys.LeftAlt) || IsKeyDown(Keys.NumPad0) || (gamePadState.Buttons.LeftShoulder == ButtonState.Pressed); IsCloseAppleKeyDown = IsKeyDown(Keys.RightAlt) || IsKeyDown(Keys.Decimal) || (gamePadState.Buttons.RightShoulder == ButtonState.Pressed); - IsResetKeyDown = (control && IsKeyDown(Keys.Back)) || (gamePadControl && (gamePadState.Buttons.Start == ButtonState.Pressed)); + IsResetKeyDown = (IsControlKeyDown && IsKeyDown(Keys.Back)) || (gamePadControl && (gamePadState.Buttons.Start == ButtonState.Pressed)); base.Update(); }