Nothing to see here
This commit is contained in:
parent
72ca2d15f8
commit
5183a8e20d
|
@ -145,6 +145,8 @@ namespace BizHawk.Client.Common
|
|||
return SystemInfo.Lynx;
|
||||
case "PSX":
|
||||
return SystemInfo.PSX;
|
||||
case "AppleII":
|
||||
return SystemInfo.AppleII;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ using BizHawk.Emulation.Cores.Sony.PSP;
|
|||
using BizHawk.Emulation.Cores.Sony.PSX;
|
||||
using BizHawk.Emulation.DiscSystem;
|
||||
using BizHawk.Emulation.Cores.WonderSwan;
|
||||
using BizHawk.Emulation.Cores.Computers.AppleII;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
|
@ -472,6 +473,10 @@ namespace BizHawk.Client.Common
|
|||
var c64 = new C64(nextComm, game, rom.RomData, rom.Extension);
|
||||
nextEmulator = c64;
|
||||
break;
|
||||
case "AppleII":
|
||||
var appleII = new AppleII(nextComm, game, rom.RomData, rom.Extension);
|
||||
nextEmulator = appleII;
|
||||
break;
|
||||
case "GBA":
|
||||
//core = CoreInventory.Instance["GBA", "Meteor"];
|
||||
core = CoreInventory.Instance["GBA", "VBA-Next"];
|
||||
|
|
|
@ -307,5 +307,16 @@ namespace BizHawk.Client.Common
|
|||
};
|
||||
}
|
||||
}
|
||||
public static SystemInfo AppleII
|
||||
{
|
||||
get
|
||||
{
|
||||
return new SystemInfo
|
||||
{
|
||||
DisplayName = "Apple II",
|
||||
ByteSize = 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,6 +339,10 @@ namespace BizHawk.Emulation.Common
|
|||
case ".83P":
|
||||
game.System = "83P";
|
||||
break;
|
||||
|
||||
case ".DSK":
|
||||
game.System = "AppleII";
|
||||
break;
|
||||
}
|
||||
|
||||
game.Name = Path.GetFileNameWithoutExtension(fileName).Replace('_', ' ');
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<Link>VersionInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Calculator\TI83.IEmulator.cs">
|
||||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Calculator\TI83.IInputPollable.cs">
|
||||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
|
@ -112,9 +112,46 @@
|
|||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Calculator\TI83.IVideoProvider.cs">
|
||||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
<DependentUpon>TI83.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Calculator\TI83LinkPort.cs" />
|
||||
<Compile Include="Computers\AppleII\AppleII.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Cassette.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Cpu.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\CpuData.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Disk525.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\DiskDsk.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\DiskIIController.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\DiskIIDrive.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\DiskNib.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\GamePort.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Keyboard.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\DisposableBase.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\IEnumerableExtensions.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\MarshalHelpers.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\MathHelpers.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\StreamExtensions.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\StringBuilderExtensions.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Library\Strings.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Machine.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\MachineComponent.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\MachineEvents.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Memory.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\MemoryData.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\NoSlotClock.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\PeripheralCard.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\AudioService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\DebugService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\GamePortService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\IsolatedStorageService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\KeyboardService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\MachineService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\MachineServices.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\StorageService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Services\VideoService.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Speaker.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\Video.cs" />
|
||||
<Compile Include="Computers\AppleII\Virtu\VideoData.cs" />
|
||||
<Compile Include="Computers\Commodore64\C64.cs" />
|
||||
<Compile Include="Computers\Commodore64\C64.IDebuggable.cs">
|
||||
<DependentUpon>C64.cs</DependentUpon>
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
using System.IO;
|
||||
using BizHawk.Emulation.Common;
|
||||
using Jellyfish.Virtu;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
[CoreAttributes(
|
||||
"Virtu",
|
||||
"TODO",
|
||||
isPorted: true,
|
||||
isReleased: false
|
||||
)]
|
||||
public partial class AppleII : IEmulator, IVideoProvider
|
||||
{
|
||||
[CoreConstructor("AppleII")]
|
||||
public AppleII(CoreComm comm, GameInfo game, byte[] rom, object Settings)
|
||||
{
|
||||
var ser = new BasicServiceProvider(this);
|
||||
ServiceProvider = ser;
|
||||
CoreComm = comm;
|
||||
|
||||
_disk1 = rom;
|
||||
|
||||
// TODO: get from Firmware provider
|
||||
_appleIIRom = File.ReadAllBytes("C:\\apple\\AppleIIe.rom");
|
||||
_diskIIRom = File.ReadAllBytes("C:\\apple\\DiskII.rom");
|
||||
|
||||
_machine = new Machine();
|
||||
_machine.Pause();
|
||||
}
|
||||
|
||||
private readonly Machine _machine;
|
||||
private readonly byte[] _disk1;
|
||||
private readonly byte[] _appleIIRom;
|
||||
private readonly byte[] _diskIIRom;
|
||||
|
||||
private static readonly ControllerDefinition AppleIIController =
|
||||
new ControllerDefinition
|
||||
{
|
||||
Name = "Apple II Keyboard",
|
||||
BoolButtons =
|
||||
{
|
||||
"Up", "Down", "Left", "Right"
|
||||
}
|
||||
};
|
||||
|
||||
private void FrameAdv(bool render, bool rendersound)
|
||||
{
|
||||
_machine.Unpause();
|
||||
while (!_machine.Video.IsVBlank)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
while (_machine.Video.IsVBlank)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
_machine.Pause();
|
||||
|
||||
Frame++;
|
||||
}
|
||||
|
||||
#region IVideoProvider
|
||||
|
||||
public int VirtualWidth { get { return 256; } }
|
||||
public int VirtualHeight { get { return 192; } }
|
||||
public int BufferWidth { get { return 256; } }
|
||||
public int BufferHeight { get { return 192; } }
|
||||
public int BackgroundColor { get { return 0; } }
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
//_machine.Video // Uh, yeah, something
|
||||
return new int[BufferWidth * BufferHeight];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEmulator
|
||||
|
||||
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public ISoundProvider SoundProvider
|
||||
{
|
||||
get { return NullSound.SilenceProvider; }
|
||||
}
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public ISyncSoundProvider SyncSoundProvider
|
||||
{
|
||||
get { return new FakeSyncSound(NullSound.SilenceProvider, 735); }
|
||||
}
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public bool StartAsyncSound()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void EndAsyncSound() { }
|
||||
|
||||
public ControllerDefinition ControllerDefinition
|
||||
{
|
||||
get { return AppleIIController; }
|
||||
}
|
||||
|
||||
public IController Controller { get; set; }
|
||||
|
||||
public int Frame { get; set; }
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void FrameAdvance(bool render, bool rendersound)
|
||||
{
|
||||
FrameAdv(render, rendersound);
|
||||
}
|
||||
|
||||
public string SystemId { get { return "AppleII"; } }
|
||||
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_machine.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class Cassette : MachineComponent
|
||||
{
|
||||
public Cassette(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
||||
public bool ReadInput()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
||||
public void ToggleOutput()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class Cpu
|
||||
{
|
||||
private const int OpCodeCount = 256;
|
||||
|
||||
private readonly Action[] ExecuteOpCode65N02;
|
||||
private readonly Action[] ExecuteOpCode65C02;
|
||||
|
||||
private const int PC = 0x01;
|
||||
private const int PZ = 0x02;
|
||||
private const int PI = 0x04;
|
||||
private const int PD = 0x08;
|
||||
private const int PB = 0x10;
|
||||
private const int PR = 0x20;
|
||||
private const int PV = 0x40;
|
||||
private const int PN = 0x80;
|
||||
|
||||
private const int DataCount = 256;
|
||||
|
||||
private static readonly int[] DataPN = new int[DataCount]
|
||||
{
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN
|
||||
};
|
||||
|
||||
private static readonly int[] DataPZ = new int[DataCount]
|
||||
{
|
||||
PZ, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
|
||||
};
|
||||
|
||||
private static readonly int[] DataPNZ = new int[DataCount]
|
||||
{
|
||||
PZ, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public abstract class Disk525
|
||||
{
|
||||
protected Disk525(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
Name = name;
|
||||
Data = data;
|
||||
IsWriteProtected = isWriteProtected;
|
||||
}
|
||||
|
||||
public static Disk525 CreateDisk(string name, Stream stream, bool isWriteProtected)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
if (name.EndsWith(".do", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase)) // assumes dos sector skew
|
||||
{
|
||||
return new DiskDsk(name, stream, isWriteProtected, SectorSkew.Dos);
|
||||
}
|
||||
else if (name.EndsWith(".nib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskNib(name, stream, isWriteProtected);
|
||||
}
|
||||
else if (name.EndsWith(".po", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskDsk(name, stream, isWriteProtected, SectorSkew.ProDos);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "version")]
|
||||
public static Disk525 LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
string name = reader.ReadString();
|
||||
var data = reader.ReadBytes(reader.ReadInt32());
|
||||
bool isWriteProtected = reader.ReadBoolean();
|
||||
|
||||
if (name.EndsWith(".do", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase)) // assumes dos sector skew
|
||||
{
|
||||
return new DiskDsk(name, data, isWriteProtected, SectorSkew.Dos);
|
||||
}
|
||||
else if (name.EndsWith(".nib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskNib(name, data, isWriteProtected);
|
||||
}
|
||||
else if (name.EndsWith(".po", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskDsk(name, data, isWriteProtected, SectorSkew.ProDos);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(Name);
|
||||
writer.Write(Data.Length);
|
||||
writer.Write(Data);
|
||||
writer.Write(IsWriteProtected);
|
||||
}
|
||||
|
||||
public abstract void ReadTrack(int number, int fraction, byte[] buffer);
|
||||
public abstract void WriteTrack(int number, int fraction, byte[] buffer);
|
||||
|
||||
public string Name { get; private set; }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public byte[] Data { get; protected set; }
|
||||
public bool IsWriteProtected { get; private set; }
|
||||
|
||||
public const int SectorCount = 16;
|
||||
public const int SectorSize = 0x100;
|
||||
public const int TrackCount = 35;
|
||||
public const int TrackSize = 0x1A00;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public enum SectorSkew { None = 0, Dos, ProDos };
|
||||
|
||||
public sealed class DiskDsk : Disk525
|
||||
{
|
||||
public DiskDsk(string name, byte[] data, bool isWriteProtected, SectorSkew sectorSkew) :
|
||||
base(name, data, isWriteProtected)
|
||||
{
|
||||
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
||||
}
|
||||
|
||||
public DiskDsk(string name, Stream stream, bool isWriteProtected, SectorSkew sectorSkew) :
|
||||
base(name, new byte[TrackCount * SectorCount * SectorSize], isWriteProtected)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
stream.ReadBlock(Data);
|
||||
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
||||
}
|
||||
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
int track = number / 2;
|
||||
|
||||
_trackBuffer = buffer;
|
||||
_trackOffset = 0;
|
||||
|
||||
WriteNibble(0xFF, 48); // gap 0
|
||||
|
||||
for (int sector = 0; sector < SectorCount; sector++)
|
||||
{
|
||||
WriteNibble(0xD5); // address prologue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0x96);
|
||||
|
||||
WriteNibble44(Volume);
|
||||
WriteNibble44(track);
|
||||
WriteNibble44(sector);
|
||||
WriteNibble44(Volume ^ track ^ sector);
|
||||
|
||||
WriteNibble(0xDE); // address epilogue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xEB);
|
||||
WriteNibble(0xFF, 8);
|
||||
|
||||
WriteNibble(0xD5); // data prologue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xAD);
|
||||
|
||||
WriteDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize);
|
||||
|
||||
WriteNibble(0xDE); // data epilogue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xEB);
|
||||
WriteNibble(0xFF, 16);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
if (IsWriteProtected)
|
||||
return;
|
||||
|
||||
int track = number / 2;
|
||||
|
||||
_trackBuffer = buffer;
|
||||
_trackOffset = 0;
|
||||
int sectorsDone = 0;
|
||||
|
||||
for (int sector = 0; sector < SectorCount; sector++)
|
||||
{
|
||||
if (!Read3Nibbles(0xD5, 0xAA, 0x96, 0x304))
|
||||
break; // no address prologue
|
||||
|
||||
/*int readVolume = */ReadNibble44();
|
||||
|
||||
int readTrack = ReadNibble44();
|
||||
if (readTrack != track)
|
||||
break; // bad track number
|
||||
|
||||
int readSector = ReadNibble44();
|
||||
if (readSector > SectorCount)
|
||||
break; // bad sector number
|
||||
if ((sectorsDone & (0x1 << readSector)) != 0)
|
||||
break; // already done this sector
|
||||
|
||||
if (ReadNibble44() != (Volume ^ readTrack ^ readSector))
|
||||
break; // bad address checksum
|
||||
|
||||
if ((ReadNibble() != 0xDE) || (ReadNibble() != 0xAA))
|
||||
break; // bad address epilogue
|
||||
|
||||
if (!Read3Nibbles(0xD5, 0xAA, 0xAD, 0x20))
|
||||
break; // no data prologue
|
||||
|
||||
if (!ReadDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize))
|
||||
break; // bad data checksum
|
||||
|
||||
if ((ReadNibble() != 0xDE) || (ReadNibble() != 0xAA))
|
||||
break; // bad data epilogue
|
||||
|
||||
sectorsDone |= 0x1 << sector;
|
||||
}
|
||||
|
||||
if (sectorsDone != 0xFFFF)
|
||||
throw new InvalidOperationException("disk error"); // TODO: we should alert the user and "dump" a NIB
|
||||
}
|
||||
|
||||
private byte ReadNibble()
|
||||
{
|
||||
byte data = _trackBuffer[_trackOffset];
|
||||
if (_trackOffset++ == TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private bool Read3Nibbles(byte data1, byte data2, byte data3, int maxReads)
|
||||
{
|
||||
bool result = false;
|
||||
while (--maxReads > 0)
|
||||
{
|
||||
if (ReadNibble() != data1)
|
||||
continue;
|
||||
|
||||
if (ReadNibble() != data2)
|
||||
continue;
|
||||
|
||||
if (ReadNibble() != data3)
|
||||
continue;
|
||||
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int ReadNibble44()
|
||||
{
|
||||
return (((ReadNibble() << 1) | 0x1) & ReadNibble());
|
||||
}
|
||||
|
||||
private byte ReadTranslatedNibble()
|
||||
{
|
||||
byte data = NibbleToByte[ReadNibble()];
|
||||
// TODO: check that invalid nibbles aren't used
|
||||
// (put 0xFFs for invalid nibbles in the table)
|
||||
//if (data == 0xFF)
|
||||
//{
|
||||
//throw an exception
|
||||
//}
|
||||
return data;
|
||||
}
|
||||
|
||||
private bool ReadDataNibbles(int sectorOffset)
|
||||
{
|
||||
byte a, x, y;
|
||||
|
||||
y = SecondaryBufferLength;
|
||||
a = 0;
|
||||
do // fill and de-nibblize secondary buffer
|
||||
{
|
||||
a = _secondaryBuffer[--y] = (byte)(a ^ ReadTranslatedNibble());
|
||||
}
|
||||
while (y > 0);
|
||||
|
||||
do // fill and de-nibblize secondary buffer
|
||||
{
|
||||
a = _primaryBuffer[y++] = (byte)(a ^ ReadTranslatedNibble());
|
||||
}
|
||||
while (y != 0);
|
||||
|
||||
int checksum = a ^ ReadTranslatedNibble(); // should be 0
|
||||
|
||||
x = y = 0;
|
||||
do // decode data
|
||||
{
|
||||
if (x == 0)
|
||||
{
|
||||
x = SecondaryBufferLength;
|
||||
}
|
||||
a = (byte)((_primaryBuffer[y] << 2) | SwapBits[_secondaryBuffer[--x] & 0x03]);
|
||||
_secondaryBuffer[x] >>= 2;
|
||||
Data[sectorOffset + y] = a;
|
||||
}
|
||||
while (++y != 0);
|
||||
|
||||
return (checksum == 0);
|
||||
}
|
||||
|
||||
private void WriteNibble(int data)
|
||||
{
|
||||
_trackBuffer[_trackOffset++] = (byte)data;
|
||||
}
|
||||
|
||||
private void WriteNibble(int data, int count)
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
WriteNibble(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteNibble44(int data)
|
||||
{
|
||||
WriteNibble((data >> 1) | 0xAA);
|
||||
WriteNibble(data | 0xAA);
|
||||
}
|
||||
|
||||
private void WriteDataNibbles(int sectorOffset)
|
||||
{
|
||||
byte a, x, y;
|
||||
|
||||
for (x = 0; x < SecondaryBufferLength; x++)
|
||||
{
|
||||
_secondaryBuffer[x] = 0; // zero secondary buffer
|
||||
}
|
||||
|
||||
y = 2;
|
||||
do // fill buffers
|
||||
{
|
||||
x = 0;
|
||||
do
|
||||
{
|
||||
a = Data[sectorOffset + --y];
|
||||
_secondaryBuffer[x] = (byte)((_secondaryBuffer[x] << 2) | SwapBits[a & 0x03]); // b1,b0 -> secondary buffer
|
||||
_primaryBuffer[y] = (byte)(a >> 2); // b7-b2 -> primary buffer
|
||||
}
|
||||
while (++x < SecondaryBufferLength);
|
||||
}
|
||||
while (y != 0);
|
||||
|
||||
y = SecondaryBufferLength;
|
||||
do // write secondary buffer
|
||||
{
|
||||
WriteNibble(ByteToNibble[_secondaryBuffer[y] ^ _secondaryBuffer[y - 1]]);
|
||||
}
|
||||
while (--y != 0);
|
||||
|
||||
a = _secondaryBuffer[0];
|
||||
do // write primary buffer
|
||||
{
|
||||
WriteNibble(ByteToNibble[a ^ _primaryBuffer[y]]);
|
||||
a = _primaryBuffer[y];
|
||||
}
|
||||
while (++y != 0);
|
||||
|
||||
WriteNibble(ByteToNibble[a]); // data checksum
|
||||
}
|
||||
|
||||
private byte[] _trackBuffer;
|
||||
private int _trackOffset;
|
||||
private byte[] _primaryBuffer = new byte[0x100];
|
||||
private const int SecondaryBufferLength = 0x56;
|
||||
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
|
||||
private int[] _sectorSkew;
|
||||
private const int Volume = 0xFE;
|
||||
|
||||
private static readonly byte[] SwapBits = { 0, 2, 1, 3 };
|
||||
|
||||
private static readonly int[] SectorSkewNone = new int[SectorCount]
|
||||
{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
|
||||
};
|
||||
|
||||
private static readonly int[] SectorSkewDos = new int[SectorCount]
|
||||
{
|
||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
||||
};
|
||||
|
||||
private static readonly int[] SectorSkewProDos = new int[SectorCount]
|
||||
{
|
||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF
|
||||
};
|
||||
|
||||
private const int SectorSkewCount = 3;
|
||||
|
||||
private static readonly int[][] SectorSkewMode = new int[SectorSkewCount][]
|
||||
{
|
||||
SectorSkewNone, SectorSkewDos, SectorSkewProDos
|
||||
};
|
||||
|
||||
private static readonly byte[] ByteToNibble = new byte[]
|
||||
{
|
||||
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
|
||||
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
|
||||
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
|
||||
};
|
||||
|
||||
private static readonly byte[] NibbleToByte = new byte[]
|
||||
{
|
||||
// padding for offset (not used)
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
|
||||
|
||||
// nibble translate table
|
||||
0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06,
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
||||
0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
|
||||
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E,
|
||||
0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskIIController : PeripheralCard
|
||||
{
|
||||
public DiskIIController(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
Drive1 = new DiskIIDrive(machine);
|
||||
Drive2 = new DiskIIDrive(machine);
|
||||
|
||||
Drives = new Collection<DiskIIDrive> { Drive1, Drive2 };
|
||||
|
||||
BootDrive = Drive1;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
StorageService.LoadResource("Roms/DiskII.rom", stream => stream.ReadBlock(_romRegionC1C7));
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_phaseStates = 0;
|
||||
SetMotorOn(false);
|
||||
SetDriveNumber(0);
|
||||
_loadMode = false;
|
||||
_writeMode = false;
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
_latch = reader.ReadInt32();
|
||||
_phaseStates = reader.ReadInt32();
|
||||
_motorOn = reader.ReadBoolean();
|
||||
_driveNumber = reader.ReadInt32();
|
||||
_loadMode = reader.ReadBoolean();
|
||||
_writeMode = reader.ReadBoolean();
|
||||
_driveSpin = reader.ReadBoolean();
|
||||
foreach (var drive in Drives)
|
||||
{
|
||||
DebugService.WriteMessage("Loading machine '{0}'", drive.GetType().Name);
|
||||
drive.LoadState(reader, version);
|
||||
//DebugService.WriteMessage("Loaded machine '{0}'", drive.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(_latch);
|
||||
writer.Write(_phaseStates);
|
||||
writer.Write(_motorOn);
|
||||
writer.Write(_driveNumber);
|
||||
writer.Write(_loadMode);
|
||||
writer.Write(_writeMode);
|
||||
writer.Write(_driveSpin);
|
||||
foreach (var drive in Drives)
|
||||
{
|
||||
DebugService.WriteMessage("Saving machine '{0}'", drive.GetType().Name);
|
||||
drive.SaveState(writer);
|
||||
//DebugService.WriteMessage("Saved machine '{0}'", drive.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
public DiskIIDrive Drive1 { get; private set; }
|
||||
public DiskIIDrive Drive2 { get; private set; }
|
||||
|
||||
public Collection<DiskIIDrive> Drives { get; private set; }
|
||||
|
||||
public DiskIIDrive BootDrive { get; private set; }
|
||||
|
||||
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 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];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskIIDrive : MachineComponent
|
||||
{
|
||||
public DiskIIDrive(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
_trackLoaded = reader.ReadBoolean();
|
||||
_trackChanged = reader.ReadBoolean();
|
||||
_trackNumber = reader.ReadInt32();
|
||||
_trackOffset = reader.ReadInt32();
|
||||
if (_trackLoaded)
|
||||
{
|
||||
reader.Read(_trackData, 0, _trackData.Length);
|
||||
}
|
||||
if (reader.ReadBoolean())
|
||||
{
|
||||
DebugService.WriteMessage("Loading machine '{0}'", typeof(Disk525).Name);
|
||||
_disk = Disk525.LoadState(reader, version);
|
||||
}
|
||||
else
|
||||
{
|
||||
_disk = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(_trackLoaded);
|
||||
writer.Write(_trackChanged);
|
||||
writer.Write(_trackNumber);
|
||||
writer.Write(_trackOffset);
|
||||
if (_trackLoaded)
|
||||
{
|
||||
writer.Write(_trackData);
|
||||
}
|
||||
writer.Write(_disk != null);
|
||||
if (_disk != null)
|
||||
{
|
||||
DebugService.WriteMessage("Saving machine '{0}'", _disk.GetType().Name);
|
||||
_disk.SaveState(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertDisk(string name, Stream stream, bool isWriteProtected)
|
||||
{
|
||||
DebugService.WriteMessage("Inserting disk '{0}'", name);
|
||||
FlushTrack();
|
||||
_disk = Disk525.CreateDisk(name, stream, isWriteProtected);
|
||||
_trackLoaded = false;
|
||||
}
|
||||
|
||||
public void RemoveDisk()
|
||||
{
|
||||
if (_disk != null)
|
||||
{
|
||||
DebugService.WriteMessage("Removing disk '{0}'", _disk.Name);
|
||||
_trackLoaded = false;
|
||||
_trackChanged = false;
|
||||
_trackNumber = 0;
|
||||
_trackOffset = 0;
|
||||
_disk = null;
|
||||
}
|
||||
}
|
||||
|
||||
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 bool _trackLoaded;
|
||||
private bool _trackChanged;
|
||||
private int _trackNumber;
|
||||
private int _trackOffset;
|
||||
private byte[] _trackData = new byte[Disk525.TrackSize];
|
||||
private Disk525 _disk;
|
||||
|
||||
private Random _random = new Random();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskNib : Disk525
|
||||
{
|
||||
public DiskNib(string name, byte[] data, bool isWriteProtected) :
|
||||
base(name, data, isWriteProtected)
|
||||
{
|
||||
}
|
||||
|
||||
public DiskNib(string name, Stream stream, bool isWriteProtected) :
|
||||
base(name, new byte[TrackCount * TrackSize], isWriteProtected)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
stream.ReadBlock(Data);
|
||||
}
|
||||
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
Buffer.BlockCopy(Data, (number / 2) * TrackSize, buffer, 0, TrackSize);
|
||||
}
|
||||
|
||||
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, 0, Data, (number / 2) * TrackSize, TrackSize);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class GamePort : MachineComponent
|
||||
{
|
||||
public GamePort(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
_resetPaddle0StrobeEvent = ResetPaddle0StrobeEvent; // cache delegates; avoids garbage
|
||||
_resetPaddle1StrobeEvent = ResetPaddle1StrobeEvent;
|
||||
_resetPaddle2StrobeEvent = ResetPaddle2StrobeEvent;
|
||||
_resetPaddle3StrobeEvent = ResetPaddle3StrobeEvent;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_keyboardService = Machine.Services.GetService<KeyboardService>();
|
||||
_gamePortService = Machine.Services.GetService<GamePortService>();
|
||||
|
||||
JoystickDeadZone = 0.4f;
|
||||
|
||||
InvertPaddles = true; // Raster Blaster
|
||||
SwapPaddles = true;
|
||||
Joystick0TouchX = 0.35f;
|
||||
Joystick0TouchY = 0.6f;
|
||||
Joystick0TouchWidth = 0.25f;
|
||||
Joystick0TouchHeight = 0.4f;
|
||||
Joystick0TouchRadius = 0.2f;
|
||||
Joystick0TouchKeepLast = true;
|
||||
Button0TouchX = 0;
|
||||
Button0TouchY = 0;
|
||||
Button0TouchWidth = 0.5f;
|
||||
Button0TouchHeight = 1;
|
||||
Button1TouchX = 0.5f;
|
||||
Button1TouchY = 0;
|
||||
Button1TouchWidth = 0.5f;
|
||||
Button1TouchHeight = 1;
|
||||
Button2TouchX = 0.75f;
|
||||
Button2TouchY = 0;
|
||||
Button2TouchWidth = 0.25f;
|
||||
Button2TouchHeight = 0.25f;
|
||||
Button2TouchOrder = 1;
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
InvertPaddles = reader.ReadBoolean();
|
||||
SwapPaddles = reader.ReadBoolean();
|
||||
UseShiftKeyMod = reader.ReadBoolean();
|
||||
JoystickDeadZone = reader.ReadSingle();
|
||||
|
||||
UseKeyboard = reader.ReadBoolean();
|
||||
Joystick0UpLeftKey = reader.ReadInt32();
|
||||
Joystick0UpKey = reader.ReadInt32();
|
||||
Joystick0UpRightKey = reader.ReadInt32();
|
||||
Joystick0LeftKey = reader.ReadInt32();
|
||||
Joystick0RightKey = reader.ReadInt32();
|
||||
Joystick0DownLeftKey = reader.ReadInt32();
|
||||
Joystick0DownKey = reader.ReadInt32();
|
||||
Joystick0DownRightKey = reader.ReadInt32();
|
||||
Joystick1UpLeftKey = reader.ReadInt32();
|
||||
Joystick1UpKey = reader.ReadInt32();
|
||||
Joystick1UpRightKey = reader.ReadInt32();
|
||||
Joystick1LeftKey = reader.ReadInt32();
|
||||
Joystick1RightKey = reader.ReadInt32();
|
||||
Joystick1DownLeftKey = reader.ReadInt32();
|
||||
Joystick1DownKey = reader.ReadInt32();
|
||||
Joystick1DownRightKey = reader.ReadInt32();
|
||||
Button0Key = reader.ReadInt32();
|
||||
Button1Key = reader.ReadInt32();
|
||||
Button2Key = reader.ReadInt32();
|
||||
|
||||
UseTouch = reader.ReadBoolean();
|
||||
Joystick0TouchX = reader.ReadSingle();
|
||||
Joystick0TouchY = reader.ReadSingle();
|
||||
Joystick0TouchWidth = reader.ReadSingle();
|
||||
Joystick0TouchHeight = reader.ReadSingle();
|
||||
Joystick0TouchOrder = reader.ReadInt32();
|
||||
Joystick0TouchRadius = reader.ReadSingle();
|
||||
Joystick0TouchKeepLast = reader.ReadBoolean();
|
||||
Joystick1TouchX = reader.ReadSingle();
|
||||
Joystick1TouchY = reader.ReadSingle();
|
||||
Joystick1TouchWidth = reader.ReadSingle();
|
||||
Joystick1TouchHeight = reader.ReadSingle();
|
||||
Joystick1TouchOrder = reader.ReadInt32();
|
||||
Joystick1TouchRadius = reader.ReadSingle();
|
||||
Joystick1TouchKeepLast = reader.ReadBoolean();
|
||||
Button0TouchX = reader.ReadSingle();
|
||||
Button0TouchY = reader.ReadSingle();
|
||||
Button0TouchWidth = reader.ReadSingle();
|
||||
Button0TouchHeight = reader.ReadSingle();
|
||||
Button0TouchOrder = reader.ReadInt32();
|
||||
Button1TouchX = reader.ReadSingle();
|
||||
Button1TouchY = reader.ReadSingle();
|
||||
Button1TouchWidth = reader.ReadSingle();
|
||||
Button1TouchHeight = reader.ReadSingle();
|
||||
Button1TouchOrder = reader.ReadInt32();
|
||||
Button2TouchX = reader.ReadSingle();
|
||||
Button2TouchY = reader.ReadSingle();
|
||||
Button2TouchWidth = reader.ReadSingle();
|
||||
Button2TouchHeight = reader.ReadSingle();
|
||||
Button2TouchOrder = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(InvertPaddles);
|
||||
writer.Write(SwapPaddles);
|
||||
writer.Write(UseShiftKeyMod);
|
||||
writer.Write(JoystickDeadZone);
|
||||
|
||||
writer.Write(UseKeyboard);
|
||||
writer.Write(Joystick0UpLeftKey);
|
||||
writer.Write(Joystick0UpKey);
|
||||
writer.Write(Joystick0UpRightKey);
|
||||
writer.Write(Joystick0LeftKey);
|
||||
writer.Write(Joystick0RightKey);
|
||||
writer.Write(Joystick0DownLeftKey);
|
||||
writer.Write(Joystick0DownKey);
|
||||
writer.Write(Joystick0DownRightKey);
|
||||
writer.Write(Joystick1UpLeftKey);
|
||||
writer.Write(Joystick1UpKey);
|
||||
writer.Write(Joystick1UpRightKey);
|
||||
writer.Write(Joystick1LeftKey);
|
||||
writer.Write(Joystick1RightKey);
|
||||
writer.Write(Joystick1DownLeftKey);
|
||||
writer.Write(Joystick1DownKey);
|
||||
writer.Write(Joystick1DownRightKey);
|
||||
writer.Write(Button0Key);
|
||||
writer.Write(Button1Key);
|
||||
writer.Write(Button2Key);
|
||||
|
||||
writer.Write(UseTouch);
|
||||
writer.Write(Joystick0TouchX);
|
||||
writer.Write(Joystick0TouchY);
|
||||
writer.Write(Joystick0TouchWidth);
|
||||
writer.Write(Joystick0TouchHeight);
|
||||
writer.Write(Joystick0TouchOrder);
|
||||
writer.Write(Joystick0TouchRadius);
|
||||
writer.Write(Joystick0TouchKeepLast);
|
||||
writer.Write(Joystick1TouchX);
|
||||
writer.Write(Joystick1TouchY);
|
||||
writer.Write(Joystick1TouchWidth);
|
||||
writer.Write(Joystick1TouchHeight);
|
||||
writer.Write(Joystick1TouchOrder);
|
||||
writer.Write(Joystick1TouchRadius);
|
||||
writer.Write(Joystick1TouchKeepLast);
|
||||
writer.Write(Button0TouchX);
|
||||
writer.Write(Button0TouchY);
|
||||
writer.Write(Button0TouchWidth);
|
||||
writer.Write(Button0TouchHeight);
|
||||
writer.Write(Button0TouchOrder);
|
||||
writer.Write(Button1TouchX);
|
||||
writer.Write(Button1TouchY);
|
||||
writer.Write(Button1TouchWidth);
|
||||
writer.Write(Button1TouchHeight);
|
||||
writer.Write(Button1TouchOrder);
|
||||
writer.Write(Button2TouchX);
|
||||
writer.Write(Button2TouchY);
|
||||
writer.Write(Button2TouchWidth);
|
||||
writer.Write(Button2TouchHeight);
|
||||
writer.Write(Button2TouchOrder);
|
||||
}
|
||||
|
||||
public bool ReadButton0()
|
||||
{
|
||||
return (_gamePortService.IsButton0Down || _keyboardService.IsOpenAppleKeyDown ||
|
||||
(UseKeyboard && (Button0Key > 0) && _keyboardService.IsKeyDown(Button0Key)));
|
||||
}
|
||||
|
||||
public bool ReadButton1()
|
||||
{
|
||||
return (_gamePortService.IsButton1Down || _keyboardService.IsCloseAppleKeyDown ||
|
||||
(UseKeyboard && (Button1Key > 0) && _keyboardService.IsKeyDown(Button1Key)));
|
||||
}
|
||||
|
||||
public bool ReadButton2()
|
||||
{
|
||||
return (_gamePortService.IsButton2Down || (UseShiftKeyMod && !_keyboardService.IsShiftKeyDown) || // Shift' [TN9]
|
||||
(UseKeyboard && (Button2Key > 0) && _keyboardService.IsKeyDown(Button2Key)));
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
public void TriggerTimers()
|
||||
{
|
||||
int paddle0 = _gamePortService.Paddle0;
|
||||
int paddle1 = _gamePortService.Paddle1;
|
||||
int paddle2 = _gamePortService.Paddle2;
|
||||
int paddle3 = _gamePortService.Paddle3;
|
||||
|
||||
if (UseKeyboard) // override
|
||||
{
|
||||
if (((Joystick0UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0UpLeftKey)) ||
|
||||
((Joystick0LeftKey > 0) && _keyboardService.IsKeyDown(Joystick0LeftKey)) ||
|
||||
((Joystick0DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0DownLeftKey)))
|
||||
{
|
||||
paddle0 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick0UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick0UpRightKey)) ||
|
||||
((Joystick0RightKey > 0) && _keyboardService.IsKeyDown(Joystick0RightKey)) ||
|
||||
((Joystick0DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick0DownRightKey)))
|
||||
{
|
||||
paddle0 += PaddleScale;
|
||||
}
|
||||
if (((Joystick0UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0UpLeftKey)) ||
|
||||
((Joystick0UpKey > 0) && _keyboardService.IsKeyDown(Joystick0UpKey)) ||
|
||||
((Joystick0UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick0UpRightKey)))
|
||||
{
|
||||
paddle1 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick0DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0DownLeftKey)) ||
|
||||
((Joystick0DownKey > 0) && _keyboardService.IsKeyDown(Joystick0DownKey)) ||
|
||||
((Joystick0DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick0DownRightKey)))
|
||||
{
|
||||
paddle1 += PaddleScale;
|
||||
}
|
||||
if (((Joystick1UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1UpLeftKey)) ||
|
||||
((Joystick1LeftKey > 0) && _keyboardService.IsKeyDown(Joystick1LeftKey)) ||
|
||||
((Joystick1DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1DownLeftKey)))
|
||||
{
|
||||
paddle2 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick1UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick1UpRightKey)) ||
|
||||
((Joystick1RightKey > 0) && _keyboardService.IsKeyDown(Joystick1RightKey)) ||
|
||||
((Joystick1DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick1DownRightKey)))
|
||||
{
|
||||
paddle2 += PaddleScale;
|
||||
}
|
||||
if (((Joystick1UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1UpLeftKey)) ||
|
||||
((Joystick1UpKey > 0) && _keyboardService.IsKeyDown(Joystick1UpKey)) ||
|
||||
((Joystick1UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick1UpRightKey)))
|
||||
{
|
||||
paddle3 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick1DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1DownLeftKey)) ||
|
||||
((Joystick1DownKey > 0) && _keyboardService.IsKeyDown(Joystick1DownKey)) ||
|
||||
((Joystick1DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick1DownRightKey)))
|
||||
{
|
||||
paddle3 += PaddleScale;
|
||||
}
|
||||
}
|
||||
if (InvertPaddles)
|
||||
{
|
||||
paddle0 = 2 * PaddleScale - paddle0;
|
||||
paddle1 = 2 * PaddleScale - paddle1;
|
||||
paddle2 = 2 * PaddleScale - paddle2;
|
||||
paddle3 = 2 * PaddleScale - paddle3;
|
||||
}
|
||||
|
||||
Paddle0Strobe = true;
|
||||
Paddle1Strobe = true;
|
||||
Paddle2Strobe = true;
|
||||
Paddle3Strobe = true;
|
||||
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle1 : paddle0) * CyclesPerValue, _resetPaddle0StrobeEvent); // [7-29]
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle0 : paddle1) * CyclesPerValue, _resetPaddle1StrobeEvent);
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle3 : paddle2) * CyclesPerValue, _resetPaddle2StrobeEvent);
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle2 : paddle3) * CyclesPerValue, _resetPaddle3StrobeEvent);
|
||||
}
|
||||
|
||||
private void ResetPaddle0StrobeEvent()
|
||||
{
|
||||
Paddle0Strobe = false;
|
||||
}
|
||||
|
||||
private void ResetPaddle1StrobeEvent()
|
||||
{
|
||||
Paddle1Strobe = false;
|
||||
}
|
||||
|
||||
private void ResetPaddle2StrobeEvent()
|
||||
{
|
||||
Paddle2Strobe = false;
|
||||
}
|
||||
|
||||
private void ResetPaddle3StrobeEvent()
|
||||
{
|
||||
Paddle3Strobe = false;
|
||||
}
|
||||
|
||||
public const int PaddleScale = 128;
|
||||
|
||||
public bool InvertPaddles { get; set; }
|
||||
public bool SwapPaddles { get; set; }
|
||||
public bool UseShiftKeyMod { get; set; }
|
||||
public float JoystickDeadZone { get; set; }
|
||||
|
||||
public bool UseKeyboard { get; set; }
|
||||
public int Joystick0UpLeftKey { get; set; }
|
||||
public int Joystick0UpKey { get; set; }
|
||||
public int Joystick0UpRightKey { get; set; }
|
||||
public int Joystick0LeftKey { get; set; }
|
||||
public int Joystick0RightKey { get; set; }
|
||||
public int Joystick0DownLeftKey { get; set; }
|
||||
public int Joystick0DownKey { get; set; }
|
||||
public int Joystick0DownRightKey { get; set; }
|
||||
public int Joystick1UpLeftKey { get; set; }
|
||||
public int Joystick1UpKey { get; set; }
|
||||
public int Joystick1UpRightKey { get; set; }
|
||||
public int Joystick1LeftKey { get; set; }
|
||||
public int Joystick1RightKey { get; set; }
|
||||
public int Joystick1DownLeftKey { get; set; }
|
||||
public int Joystick1DownKey { get; set; }
|
||||
public int Joystick1DownRightKey { get; set; }
|
||||
public int Button0Key { get; set; }
|
||||
public int Button1Key { get; set; }
|
||||
public int Button2Key { get; set; }
|
||||
|
||||
public bool UseTouch { get; set; }
|
||||
public float Joystick0TouchX { get; set; }
|
||||
public float Joystick0TouchY { get; set; }
|
||||
public float Joystick0TouchWidth { get; set; }
|
||||
public float Joystick0TouchHeight { get; set; }
|
||||
public int Joystick0TouchOrder { get; set; }
|
||||
public float Joystick0TouchRadius { get; set; }
|
||||
public bool Joystick0TouchKeepLast { get; set; }
|
||||
public float Joystick1TouchX { get; set; }
|
||||
public float Joystick1TouchY { get; set; }
|
||||
public float Joystick1TouchWidth { get; set; }
|
||||
public float Joystick1TouchHeight { get; set; }
|
||||
public int Joystick1TouchOrder { get; set; }
|
||||
public float Joystick1TouchRadius { get; set; }
|
||||
public bool Joystick1TouchKeepLast { get; set; }
|
||||
public float Button0TouchX { get; set; }
|
||||
public float Button0TouchY { get; set; }
|
||||
public float Button0TouchWidth { get; set; }
|
||||
public float Button0TouchHeight { get; set; }
|
||||
public int Button0TouchOrder { get; set; }
|
||||
public float Button1TouchX { get; set; }
|
||||
public float Button1TouchY { get; set; }
|
||||
public float Button1TouchWidth { get; set; }
|
||||
public float Button1TouchHeight { get; set; }
|
||||
public int Button1TouchOrder { get; set; }
|
||||
public float Button2TouchX { get; set; }
|
||||
public float Button2TouchY { get; set; }
|
||||
public float Button2TouchWidth { get; set; }
|
||||
public float Button2TouchHeight { get; set; }
|
||||
public int Button2TouchOrder { get; set; }
|
||||
|
||||
public bool Paddle0Strobe { get; private set; }
|
||||
public bool Paddle1Strobe { get; private set; }
|
||||
public bool Paddle2Strobe { get; private set; }
|
||||
public bool Paddle3Strobe { get; private set; }
|
||||
|
||||
private const int CyclesPerValue = 11;
|
||||
|
||||
private Action _resetPaddle0StrobeEvent;
|
||||
private Action _resetPaddle1StrobeEvent;
|
||||
private Action _resetPaddle2StrobeEvent;
|
||||
private Action _resetPaddle3StrobeEvent;
|
||||
|
||||
private KeyboardService _keyboardService;
|
||||
private GamePortService _gamePortService;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class Keyboard : MachineComponent
|
||||
{
|
||||
public Keyboard(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_keyboardService = Machine.Services.GetService<KeyboardService>();
|
||||
|
||||
UseGamePort = true; // Raster Blaster
|
||||
Button2Key = ' ';
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
DisableResetKey = reader.ReadBoolean();
|
||||
|
||||
UseGamePort = reader.ReadBoolean();
|
||||
Joystick0UpLeftKey = reader.ReadInt32();
|
||||
Joystick0UpKey = reader.ReadInt32();
|
||||
Joystick0UpRightKey = reader.ReadInt32();
|
||||
Joystick0LeftKey = reader.ReadInt32();
|
||||
Joystick0RightKey = reader.ReadInt32();
|
||||
Joystick0DownLeftKey = reader.ReadInt32();
|
||||
Joystick0DownKey = reader.ReadInt32();
|
||||
Joystick0DownRightKey = reader.ReadInt32();
|
||||
Joystick1UpLeftKey = reader.ReadInt32();
|
||||
Joystick1UpKey = reader.ReadInt32();
|
||||
Joystick1UpRightKey = reader.ReadInt32();
|
||||
Joystick1LeftKey = reader.ReadInt32();
|
||||
Joystick1RightKey = reader.ReadInt32();
|
||||
Joystick1DownLeftKey = reader.ReadInt32();
|
||||
Joystick1DownKey = reader.ReadInt32();
|
||||
Joystick1DownRightKey = reader.ReadInt32();
|
||||
Button0Key = reader.ReadInt32();
|
||||
Button1Key = reader.ReadInt32();
|
||||
Button2Key = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(DisableResetKey);
|
||||
|
||||
writer.Write(UseGamePort);
|
||||
writer.Write(Joystick0UpLeftKey);
|
||||
writer.Write(Joystick0UpKey);
|
||||
writer.Write(Joystick0UpRightKey);
|
||||
writer.Write(Joystick0LeftKey);
|
||||
writer.Write(Joystick0RightKey);
|
||||
writer.Write(Joystick0DownLeftKey);
|
||||
writer.Write(Joystick0DownKey);
|
||||
writer.Write(Joystick0DownRightKey);
|
||||
writer.Write(Joystick1UpLeftKey);
|
||||
writer.Write(Joystick1UpKey);
|
||||
writer.Write(Joystick1UpRightKey);
|
||||
writer.Write(Joystick1LeftKey);
|
||||
writer.Write(Joystick1RightKey);
|
||||
writer.Write(Joystick1DownLeftKey);
|
||||
writer.Write(Joystick1DownKey);
|
||||
writer.Write(Joystick1DownRightKey);
|
||||
writer.Write(Button0Key);
|
||||
writer.Write(Button1Key);
|
||||
writer.Write(Button2Key);
|
||||
}
|
||||
|
||||
public void ResetStrobe()
|
||||
{
|
||||
Strobe = false;
|
||||
}
|
||||
|
||||
public bool DisableResetKey { get; set; }
|
||||
|
||||
public bool UseGamePort { get; set; }
|
||||
public int Joystick0UpLeftKey { get; set; }
|
||||
public int Joystick0UpKey { get; set; }
|
||||
public int Joystick0UpRightKey { get; set; }
|
||||
public int Joystick0LeftKey { get; set; }
|
||||
public int Joystick0RightKey { get; set; }
|
||||
public int Joystick0DownLeftKey { get; set; }
|
||||
public int Joystick0DownKey { get; set; }
|
||||
public int Joystick0DownRightKey { get; set; }
|
||||
public int Joystick1UpLeftKey { get; set; }
|
||||
public int Joystick1UpKey { get; set; }
|
||||
public int Joystick1UpRightKey { get; set; }
|
||||
public int Joystick1LeftKey { get; set; }
|
||||
public int Joystick1RightKey { get; set; }
|
||||
public int Joystick1DownLeftKey { get; set; }
|
||||
public int Joystick1DownKey { get; set; }
|
||||
public int Joystick1DownRightKey { get; set; }
|
||||
public int Button0Key { get; set; }
|
||||
public int Button1Key { get; set; }
|
||||
public int Button2Key { get; set; }
|
||||
|
||||
public bool IsAnyKeyDown { get { return _keyboardService.IsAnyKeyDown; } }
|
||||
public int Latch { get { return _latch; } set { _latch = value; Strobe = true; } }
|
||||
public bool Strobe { get; private set; }
|
||||
|
||||
private KeyboardService _keyboardService;
|
||||
|
||||
private int _latch;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public abstract class DisposableBase : IDisposable
|
||||
{
|
||||
protected DisposableBase()
|
||||
{
|
||||
}
|
||||
|
||||
~DisposableBase()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException("source");
|
||||
}
|
||||
if (action == null)
|
||||
{
|
||||
throw new ArgumentNullException("action");
|
||||
}
|
||||
|
||||
foreach (T item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class MarshalHelpers
|
||||
{
|
||||
[SecurityCritical]
|
||||
public static void FillMemory(IntPtr buffer, int bufferSize, byte value)
|
||||
{
|
||||
NativeMethods.FillMemory(buffer, (IntPtr)bufferSize, value);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
public static void ZeroMemory(IntPtr buffer, int bufferSize)
|
||||
{
|
||||
NativeMethods.ZeroMemory(buffer, (IntPtr)bufferSize);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static class NativeMethods
|
||||
{
|
||||
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")]
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern void FillMemory(IntPtr destination, IntPtr length, byte fill);
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")]
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern void ZeroMemory(IntPtr destination, IntPtr length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class MathHelpers
|
||||
{
|
||||
public static int Clamp(int value, int min, int max)
|
||||
{
|
||||
return (value < min) ? min : (value > max) ? max : value;
|
||||
}
|
||||
|
||||
public static int ClampByte(int value)
|
||||
{
|
||||
return Clamp(value, byte.MinValue, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
[SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "3#")]
|
||||
public static int ReadBlock(this Stream stream, byte[] buffer, int offset, ref int count)
|
||||
{
|
||||
int read = ReadBlock(stream, buffer, offset, count, count);
|
||||
count -= read;
|
||||
return read;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
||||
public static int ReadBlock(this Stream stream, byte[] buffer, int offset = 0, int count = int.MaxValue, int minCount = int.MaxValue)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException("buffer");
|
||||
}
|
||||
|
||||
count = Math.Min(count, buffer.Length - offset);
|
||||
minCount = Math.Min(minCount, buffer.Length - offset);
|
||||
|
||||
int total = 0;
|
||||
int read;
|
||||
do
|
||||
{
|
||||
total += read = stream.Read(buffer, offset + total, count - total);
|
||||
}
|
||||
while ((read > 0) && (total < count));
|
||||
|
||||
if (total < minCount)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
|
||||
public static int ReadWord(this Stream stream, bool optional = false)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
int lowByte = stream.ReadByte();
|
||||
int highByte = stream.ReadByte();
|
||||
int word = lowByte | (highByte << 8);
|
||||
if ((word < 0) && !optional)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
public static void SkipBlock(this Stream stream, int count)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(count, SeekOrigin.Current);
|
||||
}
|
||||
else
|
||||
{
|
||||
int total = 0;
|
||||
int read;
|
||||
do
|
||||
{
|
||||
total += read = stream.Read(_skipBuffer, 0, Math.Min(count - total, SkipBufferSize));
|
||||
}
|
||||
while ((read > 0) && (total < count));
|
||||
}
|
||||
}
|
||||
|
||||
private const int SkipBufferSize = 1024;
|
||||
|
||||
private static byte[] _skipBuffer = new byte[SkipBufferSize];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
public static StringBuilder AppendHex(this StringBuilder builder, short value) // little endian
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
return builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}{1:X2}", value & 0xFF, value >> 8);
|
||||
}
|
||||
|
||||
public static StringBuilder AppendHex(this StringBuilder builder, int value) // little endian
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
return builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}{1:X2}{2:X2}{3:X2}", value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, value >> 24);
|
||||
}
|
||||
|
||||
public static StringBuilder AppendWithoutGarbage(this StringBuilder builder, int value)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException("builder");
|
||||
}
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
builder.Append('-');
|
||||
}
|
||||
|
||||
int index = builder.Length;
|
||||
do
|
||||
{
|
||||
builder.Insert(index, Digits, (value % 10) + 9, 1);
|
||||
value /= 10;
|
||||
}
|
||||
while (value != 0);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static readonly char[] Digits = new char[] { '9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Jellyfish.Virtu.Properties
|
||||
{
|
||||
public static class Strings // Hack because we don't want resources in the core
|
||||
{
|
||||
public static string InvalidAddressRange
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Invalid address range ${0:X04}-${1:X04}.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string MarkerNotFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Marker ${0:X04} not found.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ResourceNotFound
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Resource '{0}' not found.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ServiceAlreadyPresent
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Service type '{0}' already present.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string ServiceMustBeAssignable
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Service type '{0}' must be assignable from service provider '{1}'.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public enum MachineState { Stopped = 0, Starting, Running, Pausing, Paused, Stopping }
|
||||
|
||||
public sealed class Machine : IDisposable
|
||||
{
|
||||
public Machine()
|
||||
{
|
||||
Events = new MachineEvents();
|
||||
Services = new MachineServices();
|
||||
|
||||
Cpu = new Cpu(this);
|
||||
Memory = new Memory(this);
|
||||
Keyboard = new Keyboard(this);
|
||||
GamePort = new GamePort(this);
|
||||
Cassette = new Cassette(this);
|
||||
Speaker = new Speaker(this);
|
||||
Video = new Video(this);
|
||||
NoSlotClock = new NoSlotClock(this);
|
||||
|
||||
var emptySlot = new PeripheralCard(this);
|
||||
Slot1 = emptySlot;
|
||||
Slot2 = emptySlot;
|
||||
Slot3 = emptySlot;
|
||||
Slot4 = emptySlot;
|
||||
Slot5 = emptySlot;
|
||||
Slot6 = new DiskIIController(this);
|
||||
Slot7 = emptySlot;
|
||||
|
||||
Slots = new Collection<PeripheralCard> { null, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 };
|
||||
Components = new Collection<MachineComponent> { Cpu, Memory, Keyboard, GamePort, Cassette, Speaker, Video, NoSlotClock, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 };
|
||||
|
||||
BootDiskII = Slots.OfType<DiskIIController>().Last();
|
||||
|
||||
Thread = new Thread(Run) { Name = "Machine" };
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pauseEvent.Close();
|
||||
_unpauseEvent.Close();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach (var component in Components)
|
||||
{
|
||||
_debugService.WriteMessage("Resetting machine '{0}'", component.GetType().Name);
|
||||
component.Reset();
|
||||
//_debugService.WriteMessage("Reset machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")]
|
||||
public void Start()
|
||||
{
|
||||
_debugService = Services.GetService<DebugService>();
|
||||
_storageService = Services.GetService<StorageService>();
|
||||
|
||||
_debugService.WriteMessage("Starting machine");
|
||||
State = MachineState.Starting;
|
||||
Thread.Start();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")]
|
||||
public void Pause()
|
||||
{
|
||||
_debugService.WriteMessage("Pausing machine");
|
||||
State = MachineState.Pausing;
|
||||
_pauseEvent.WaitOne();
|
||||
State = MachineState.Paused;
|
||||
_debugService.WriteMessage("Paused machine");
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")]
|
||||
public void Unpause()
|
||||
{
|
||||
_debugService.WriteMessage("Running machine");
|
||||
State = MachineState.Running;
|
||||
_unpauseEvent.Set();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")]
|
||||
public void Stop()
|
||||
{
|
||||
_debugService.WriteMessage("Stopping machine");
|
||||
State = MachineState.Stopping;
|
||||
_unpauseEvent.Set();
|
||||
if (Thread.IsAlive)
|
||||
{
|
||||
Thread.Join();
|
||||
}
|
||||
State = MachineState.Stopped;
|
||||
_debugService.WriteMessage("Stopped machine");
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
foreach (var component in Components)
|
||||
{
|
||||
_debugService.WriteMessage("Initializing machine '{0}'", component.GetType().Name);
|
||||
component.Initialize();
|
||||
//_debugService.WriteMessage("Initialized machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadState()
|
||||
{
|
||||
#if WINDOWS
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
if (args.Length > 1)
|
||||
{
|
||||
string name = args[1];
|
||||
Func<string, Action<Stream>, bool> loader = StorageService.LoadFile;
|
||||
|
||||
if (name.StartsWith("res://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name = name.Substring(6);
|
||||
loader = StorageService.LoadResource;
|
||||
}
|
||||
|
||||
if (name.EndsWith(".bin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loader(name, stream => LoadState(stream));
|
||||
}
|
||||
else if (name.EndsWith(".prg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loader(name, stream => Memory.LoadPrg(stream));
|
||||
}
|
||||
else if (name.EndsWith(".xex", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loader(name, stream => Memory.LoadXex(stream));
|
||||
}
|
||||
else
|
||||
{
|
||||
loader(name, stream => BootDiskII.BootDrive.InsertDisk(name, stream, false));
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (!_storageService.Load(Machine.StateFileName, stream => LoadState(stream)))
|
||||
{
|
||||
StorageService.LoadResource("Disks/Default.dsk", stream => BootDiskII.BootDrive.InsertDisk("Default.dsk", stream, false));
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadState(Stream stream)
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
string signature = reader.ReadString();
|
||||
var version = new Version(reader.ReadString());
|
||||
if ((signature != StateSignature) || (version != new Version(Machine.Version))) // avoid state version mismatch (for now)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
foreach (var component in Components)
|
||||
{
|
||||
_debugService.WriteMessage("Loading machine '{0}'", component.GetType().Name);
|
||||
component.LoadState(reader, version);
|
||||
//_debugService.WriteMessage("Loaded machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveState()
|
||||
{
|
||||
_storageService.Save(Machine.StateFileName, stream => SaveState(stream));
|
||||
}
|
||||
|
||||
private void SaveState(Stream stream)
|
||||
{
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(StateSignature);
|
||||
writer.Write(Machine.Version);
|
||||
foreach (var component in Components)
|
||||
{
|
||||
_debugService.WriteMessage("Saving machine '{0}'", component.GetType().Name);
|
||||
component.SaveState(writer);
|
||||
//_debugService.WriteMessage("Saved machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Uninitialize()
|
||||
{
|
||||
foreach (var component in Components)
|
||||
{
|
||||
_debugService.WriteMessage("Uninitializing machine '{0}'", component.GetType().Name);
|
||||
component.Uninitialize();
|
||||
//_debugService.WriteMessage("Uninitialized machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")]
|
||||
private void Run() // machine thread
|
||||
{
|
||||
Initialize();
|
||||
Reset();
|
||||
LoadState();
|
||||
|
||||
_debugService.WriteMessage("Running machine");
|
||||
State = MachineState.Running;
|
||||
do
|
||||
{
|
||||
do
|
||||
{
|
||||
Events.HandleEvents(Cpu.Execute());
|
||||
}
|
||||
while (State == MachineState.Running);
|
||||
|
||||
if (State == MachineState.Pausing)
|
||||
{
|
||||
_pauseEvent.Set();
|
||||
_unpauseEvent.WaitOne();
|
||||
}
|
||||
}
|
||||
while (State != MachineState.Stopping);
|
||||
|
||||
SaveState();
|
||||
Uninitialize();
|
||||
}
|
||||
|
||||
public const string Version = "0.9.4.0";
|
||||
|
||||
public MachineEvents Events { get; private set; }
|
||||
public MachineServices Services { get; private set; }
|
||||
public MachineState State { get { return _state; } private set { _state = value; } }
|
||||
|
||||
public Cpu Cpu { get; private set; }
|
||||
public Memory Memory { 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<PeripheralCard> Slots { get; private set; }
|
||||
public Collection<MachineComponent> Components { get; private set; }
|
||||
|
||||
public DiskIIController BootDiskII { get; private set; }
|
||||
|
||||
public Thread Thread { get; private set; }
|
||||
|
||||
private const string StateFileName = "State.bin";
|
||||
private const string StateSignature = "Virtu";
|
||||
|
||||
private DebugService _debugService;
|
||||
private StorageService _storageService;
|
||||
private volatile MachineState _state;
|
||||
|
||||
private AutoResetEvent _pauseEvent = new AutoResetEvent(false);
|
||||
private AutoResetEvent _unpauseEvent = new AutoResetEvent(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public abstract class MachineComponent
|
||||
{
|
||||
protected MachineComponent(Machine machine)
|
||||
{
|
||||
Machine = machine;
|
||||
|
||||
_debugService = new Lazy<DebugService>(() => Machine.Services.GetService<DebugService>());
|
||||
}
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Uninitialize()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void SaveState(BinaryWriter writer)
|
||||
{
|
||||
}
|
||||
|
||||
protected Machine Machine { get; private set; }
|
||||
protected DebugService DebugService { get { return _debugService.Value; } }
|
||||
|
||||
private Lazy<DebugService> _debugService;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class MachineEvent
|
||||
{
|
||||
public MachineEvent(int delta, Action action)
|
||||
{
|
||||
Delta = delta;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "Delta = {0} Action = {{{1}.{2}}}", Delta, Action.Method.DeclaringType.Name, Action.Method.Name);
|
||||
}
|
||||
|
||||
public int Delta { get; set; }
|
||||
public Action Action { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MachineEvents
|
||||
{
|
||||
public void AddEvent(int delta, Action action)
|
||||
{
|
||||
var node = _used.First;
|
||||
for (; node != null; node = node.Next)
|
||||
{
|
||||
if (delta < node.Value.Delta)
|
||||
{
|
||||
node.Value.Delta -= delta;
|
||||
break;
|
||||
}
|
||||
if (node.Value.Delta > 0)
|
||||
{
|
||||
delta -= node.Value.Delta;
|
||||
}
|
||||
}
|
||||
|
||||
var newNode = _free.First;
|
||||
if (newNode != null)
|
||||
{
|
||||
_free.RemoveFirst();
|
||||
newNode.Value.Delta = delta;
|
||||
newNode.Value.Action = action;
|
||||
}
|
||||
else
|
||||
{
|
||||
newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, action));
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
_used.AddBefore(node, newNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_used.AddLast(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
public int FindEvent(Action action)
|
||||
{
|
||||
int delta = 0;
|
||||
|
||||
for (var node = _used.First; node != null; node = node.Next)
|
||||
{
|
||||
delta += node.Value.Delta;
|
||||
if (object.ReferenceEquals(node.Value.Action, action)) // assumes delegate cached
|
||||
{
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void HandleEvents(int delta)
|
||||
{
|
||||
var node = _used.First;
|
||||
node.Value.Delta -= delta;
|
||||
|
||||
while (node.Value.Delta <= 0)
|
||||
{
|
||||
node.Value.Action();
|
||||
RemoveEvent(node);
|
||||
node = _used.First;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEvent(LinkedListNode<MachineEvent> node)
|
||||
{
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Value.Delta += node.Value.Delta;
|
||||
}
|
||||
|
||||
_used.Remove(node);
|
||||
_free.AddFirst(node); // cache node; avoids garbage
|
||||
}
|
||||
|
||||
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
|
||||
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class Memory
|
||||
{
|
||||
private const int BankCount = 2;
|
||||
|
||||
private const int BankMain = 0;
|
||||
private const int BankAux = 1;
|
||||
|
||||
private const int RegionCount = 12;
|
||||
|
||||
private const int Region0001 = 0;
|
||||
private const int Region02BF = 1;
|
||||
private const int Region0407 = 2;
|
||||
private const int Region080B = 3;
|
||||
private const int Region203F = 4;
|
||||
private const int Region405F = 5;
|
||||
private const int RegionC0C0 = 6;
|
||||
private const int RegionC1C7 = 7;
|
||||
private const int RegionC3C3 = 8;
|
||||
private const int RegionC8CF = 9;
|
||||
private const int RegionD0DF = 10;
|
||||
private const int RegionE0FF = 11;
|
||||
|
||||
private static readonly int[] RegionBaseAddress = new int[RegionCount]
|
||||
{
|
||||
0x0000, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0xC000, 0xC100, 0xC100, 0xC100, 0xD000, 0xE000
|
||||
};
|
||||
|
||||
private const int PageCount = 256;
|
||||
|
||||
private static readonly int[] PageRegion = new int[PageCount]
|
||||
{
|
||||
Region0001, Region0001, Region02BF, Region02BF, Region0407, Region0407, Region0407, Region0407,
|
||||
Region080B, Region080B, Region080B, Region080B, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
RegionC0C0, RegionC1C7, RegionC1C7, RegionC3C3, RegionC1C7, RegionC1C7, RegionC1C7, RegionC1C7,
|
||||
RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF,
|
||||
RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF,
|
||||
RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF
|
||||
};
|
||||
|
||||
private const int State80Col = 0x000001;
|
||||
private const int StateText = 0x000002;
|
||||
private const int StateMixed = 0x000004;
|
||||
private const int StateHires = 0x000008;
|
||||
private const int StateDRes = 0x000010;
|
||||
private const int State80Store = 0x000020;
|
||||
private const int StateAltChrSet = 0x000040;
|
||||
private const int StateAltZP = 0x000080;
|
||||
private const int StateBank1 = 0x000100;
|
||||
private const int StateHRamRd = 0x000200;
|
||||
private const int StateHRamPreWrt = 0x000400;
|
||||
private const int StateHRamWrt = 0x000800;
|
||||
private const int StatePage2 = 0x001000;
|
||||
private const int StateRamRd = 0x002000;
|
||||
private const int StateRamWrt = 0x004000;
|
||||
private const int StateSlotC3Rom = 0x008000;
|
||||
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;
|
||||
|
||||
private static readonly int[] StateVideoMode = new int[StateVideoModeCount]
|
||||
{
|
||||
Video.Mode0, Video.Mode0, Video.Mode1, Video.Mode2, Video.Mode3, Video.Mode4, Video.Mode1, Video.Mode2,
|
||||
Video.Mode5, Video.Mode5, Video.Mode1, Video.Mode2, Video.Mode6, Video.Mode7, Video.Mode1, Video.Mode2,
|
||||
Video.Mode8, Video.Mode9, Video.Mode1, Video.Mode2, Video.ModeA, Video.ModeB, Video.Mode1, Video.Mode2,
|
||||
Video.ModeC, Video.ModeD, Video.Mode1, Video.Mode2, Video.ModeE, Video.ModeF, Video.Mode1, Video.Mode2
|
||||
};
|
||||
|
||||
private readonly Action<int, byte>[][][] WriteRamModeBankRegion;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class NoSlotClock : MachineComponent
|
||||
{
|
||||
public NoSlotClock(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_clockEnabled = false;
|
||||
_writeEnabled = true;
|
||||
_clockRegister = new RingRegister(0x0, 0x1);
|
||||
_comparisonRegister = new RingRegister(ClockInitSequence, 0x1);
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
_clockEnabled = reader.ReadBoolean();
|
||||
_writeEnabled = reader.ReadBoolean();
|
||||
_clockRegister = new RingRegister(reader.ReadUInt64(), reader.ReadUInt64());
|
||||
_comparisonRegister = new RingRegister(reader.ReadUInt64(), reader.ReadUInt64());
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(_clockEnabled);
|
||||
writer.Write(_writeEnabled);
|
||||
writer.Write(_clockRegister.Data);
|
||||
writer.Write(_clockRegister.Mask);
|
||||
writer.Write(_comparisonRegister.Data);
|
||||
writer.Write(_comparisonRegister.Mask);
|
||||
}
|
||||
|
||||
public int Read(int address, int data)
|
||||
{
|
||||
// this may read or write the clock
|
||||
if ((address & 0x4) != 0)
|
||||
{
|
||||
return ReadClock(data);
|
||||
}
|
||||
|
||||
WriteClock(address);
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Write(int address)
|
||||
{
|
||||
// this may read or write the clock
|
||||
if ((address & 0x4) != 0)
|
||||
{
|
||||
ReadClock(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteClock(address);
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadClock(int data)
|
||||
{
|
||||
// for a ROM, A2 high = read, and data out (if any) is on D0
|
||||
if (!_clockEnabled)
|
||||
{
|
||||
_comparisonRegister.Reset();
|
||||
_writeEnabled = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
data = _clockRegister.ReadBit(Machine.Video.ReadFloatingBus());
|
||||
if (_clockRegister.NextBit())
|
||||
{
|
||||
_clockEnabled = false;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void WriteClock(int address)
|
||||
{
|
||||
// for a ROM, A2 low = write, and data in is on A0
|
||||
if (!_writeEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_clockEnabled)
|
||||
{
|
||||
if ((_comparisonRegister.CompareBit(address)))
|
||||
{
|
||||
if (_comparisonRegister.NextBit())
|
||||
{
|
||||
_clockEnabled = true;
|
||||
PopulateClockRegister();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// mismatch ignores further writes
|
||||
_writeEnabled = false;
|
||||
}
|
||||
}
|
||||
else if (_clockRegister.NextBit())
|
||||
{
|
||||
// simulate writes, but our clock register is read-only
|
||||
_clockEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateClockRegister()
|
||||
{
|
||||
// all values are in packed BCD format (4 bits per decimal digit)
|
||||
var now = DateTime.Now;
|
||||
|
||||
int centisecond = now.Millisecond / 10; // 00-99
|
||||
_clockRegister.WriteNibble(centisecond % 10);
|
||||
_clockRegister.WriteNibble(centisecond / 10);
|
||||
|
||||
int second = now.Second; // 00-59
|
||||
_clockRegister.WriteNibble(second % 10);
|
||||
_clockRegister.WriteNibble(second / 10);
|
||||
|
||||
int minute = now.Minute; // 00-59
|
||||
_clockRegister.WriteNibble(minute % 10);
|
||||
_clockRegister.WriteNibble(minute / 10);
|
||||
|
||||
int hour = now.Hour; // 01-23
|
||||
_clockRegister.WriteNibble(hour % 10);
|
||||
_clockRegister.WriteNibble(hour / 10);
|
||||
|
||||
int day = (int)now.DayOfWeek + 1; // 01-07 (1 = Sunday)
|
||||
_clockRegister.WriteNibble(day % 10);
|
||||
_clockRegister.WriteNibble(day / 10);
|
||||
|
||||
int date = now.Day; // 01-31
|
||||
_clockRegister.WriteNibble(date % 10);
|
||||
_clockRegister.WriteNibble(date / 10);
|
||||
|
||||
int month = now.Month; // 01-12
|
||||
_clockRegister.WriteNibble(month % 10);
|
||||
_clockRegister.WriteNibble(month / 10);
|
||||
|
||||
int year = now.Year % 100; // 00-99
|
||||
_clockRegister.WriteNibble(year % 10);
|
||||
_clockRegister.WriteNibble(year / 10);
|
||||
}
|
||||
|
||||
private const ulong ClockInitSequence = 0x5CA33AC55CA33AC5;
|
||||
|
||||
private bool _clockEnabled;
|
||||
private bool _writeEnabled;
|
||||
private RingRegister _clockRegister;
|
||||
private RingRegister _comparisonRegister;
|
||||
|
||||
private struct RingRegister
|
||||
{
|
||||
public RingRegister(ulong data, ulong mask)
|
||||
{
|
||||
_data = data;
|
||||
_mask = mask;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_mask = 0x1;
|
||||
}
|
||||
|
||||
public void WriteNibble(int data)
|
||||
{
|
||||
WriteBits(data, 4);
|
||||
}
|
||||
|
||||
public void WriteBits(int data, int count)
|
||||
{
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
WriteBit(data);
|
||||
NextBit();
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteBit(int data)
|
||||
{
|
||||
_data = ((data & 0x1) != 0) ? (_data | _mask) : (_data & ~_mask);
|
||||
}
|
||||
|
||||
public int ReadBit(int data)
|
||||
{
|
||||
return ((_data & _mask) != 0) ? (data | 0x1) : (data & ~0x1);
|
||||
}
|
||||
|
||||
public bool CompareBit(int data)
|
||||
{
|
||||
return (((_data & _mask) != 0) == ((data & 0x1) != 0));
|
||||
}
|
||||
|
||||
public bool NextBit()
|
||||
{
|
||||
if ((_mask <<= 1) == 0)
|
||||
{
|
||||
_mask = 0x1;
|
||||
return true; // wrap
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ulong Data { get { return _data; } } // no auto props
|
||||
public ulong Mask { get { return _mask; } }
|
||||
|
||||
private ulong _data;
|
||||
private ulong _mask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public abstract class AudioService : MachineService
|
||||
{
|
||||
protected AudioService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public void Output(int data) // machine thread
|
||||
{
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
_buffer[_index + 0] = (byte)(data & 0xFF);
|
||||
_buffer[_index + 1] = (byte)(data >> 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buffer[_index + 0] = (byte)(data >> 8);
|
||||
_buffer[_index + 1] = (byte)(data & 0xFF);
|
||||
}
|
||||
_index = (_index + 2) % SampleSize;
|
||||
if (_index == 0)
|
||||
{
|
||||
if (Machine.Cpu.IsThrottled)
|
||||
{
|
||||
_writeEvent.WaitOne(SampleLatency * 2); // allow timeout; avoids deadlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Buffer.BlockCopy(SampleZero, 0, _buffer, 0, SampleSize);
|
||||
}
|
||||
|
||||
public abstract void SetVolume(float volume);
|
||||
|
||||
protected void Update() // audio thread
|
||||
{
|
||||
_writeEvent.Set();
|
||||
}
|
||||
|
||||
public const int SampleRate = 44100; // hz
|
||||
public const int SampleChannels = 1;
|
||||
public const int SampleBits = 16;
|
||||
public const int SampleLatency = 40; // ms
|
||||
public const int SampleSize = (SampleRate * SampleLatency / 1000) * SampleChannels * (SampleBits / 8);
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")]
|
||||
protected static readonly byte[] SampleZero = new byte[SampleSize];
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
protected byte[] Source { get { return _buffer; } }
|
||||
|
||||
private byte[] _buffer = new byte[SampleSize];
|
||||
private int _index;
|
||||
|
||||
private AutoResetEvent _writeEvent = new AutoResetEvent(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public class DebugService : MachineService
|
||||
{
|
||||
public DebugService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public void WriteMessage(string message)
|
||||
{
|
||||
OnWriteMessage(FormatMessage(message));
|
||||
}
|
||||
|
||||
public void WriteMessage(string format, params object[] args)
|
||||
{
|
||||
OnWriteMessage(FormatMessage(format, args));
|
||||
}
|
||||
|
||||
protected virtual void OnWriteMessage(string message)
|
||||
{
|
||||
#if SILVERLIGHT
|
||||
Debug.WriteLine(message);
|
||||
#else
|
||||
Trace.WriteLine(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
private string FormatMessage(string format, params object[] args)
|
||||
{
|
||||
var message = new StringBuilder(256);
|
||||
message.AppendFormat(CultureInfo.InvariantCulture, "[{0} T{1:X3} Virtu] ", DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture), Thread.CurrentThread.ManagedThreadId);
|
||||
if (args.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
message.AppendFormat(CultureInfo.InvariantCulture, format, args);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
WriteMessage("[DebugService.FormatMessage] format: {0}; args: {1}; exception: {2}", format, string.Join(", ", args), ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Append(format);
|
||||
}
|
||||
|
||||
return message.ToString();
|
||||
}
|
||||
|
||||
public static DebugService Default { get { return _default.Value; } }
|
||||
|
||||
private static readonly Lazy<DebugService> _default = new Lazy<DebugService>(() => new DebugService(null));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public class GamePortService : MachineService
|
||||
{
|
||||
public GamePortService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
Paddle0 = Paddle1 = Paddle2 = Paddle3 = 255; // not connected
|
||||
}
|
||||
|
||||
public virtual void Update() // main thread
|
||||
{
|
||||
var keyboard = Machine.Keyboard;
|
||||
|
||||
if (keyboard.UseGamePort)
|
||||
{
|
||||
UpdateKey(keyboard.Joystick0UpKey, IsJoystick0Up, ref _isJoystick0UpKeyDown, ref _wasJoystick0UpKeyDown);
|
||||
UpdateKey(keyboard.Joystick0LeftKey, IsJoystick0Left, ref _isJoystick0LeftKeyDown, ref _wasJoystick0LeftKeyDown);
|
||||
UpdateKey(keyboard.Joystick0RightKey, IsJoystick0Right, ref _isJoystick0RightKeyDown, ref _wasJoystick0RightKeyDown);
|
||||
UpdateKey(keyboard.Joystick0DownKey, IsJoystick0Down, ref _isJoystick0DownKeyDown, ref _wasJoystick0DownKeyDown);
|
||||
UpdateKey(keyboard.Joystick0UpLeftKey, IsJoystick0Up && IsJoystick0Left, ref _isJoystick0UpLeftKeyDown, ref _wasJoystick0UpLeftKeyDown);
|
||||
UpdateKey(keyboard.Joystick0UpRightKey, IsJoystick0Up && IsJoystick0Right, ref _isJoystick0UpRightKeyDown, ref _wasJoystick0UpRightKeyDown);
|
||||
UpdateKey(keyboard.Joystick0DownLeftKey, IsJoystick0Down && IsJoystick0Left, ref _isJoystick0DownLeftKeyDown, ref _wasJoystick0DownLeftKeyDown);
|
||||
UpdateKey(keyboard.Joystick0DownRightKey, IsJoystick0Down && IsJoystick0Right, ref _isJoystick0DownRightKeyDown, ref _wasJoystick0DownRightKeyDown);
|
||||
UpdateKey(keyboard.Joystick1UpKey, IsJoystick1Up, ref _isJoystick1UpKeyDown, ref _wasJoystick1UpKeyDown);
|
||||
UpdateKey(keyboard.Joystick1LeftKey, IsJoystick1Left, ref _isJoystick1LeftKeyDown, ref _wasJoystick1LeftKeyDown);
|
||||
UpdateKey(keyboard.Joystick1RightKey, IsJoystick1Right, ref _isJoystick1RightKeyDown, ref _wasJoystick1RightKeyDown);
|
||||
UpdateKey(keyboard.Joystick1DownKey, IsJoystick1Down, ref _isJoystick1DownKeyDown, ref _wasJoystick1DownKeyDown);
|
||||
UpdateKey(keyboard.Joystick1UpLeftKey, IsJoystick1Up && IsJoystick1Left, ref _isJoystick1UpLeftKeyDown, ref _wasJoystick1UpLeftKeyDown);
|
||||
UpdateKey(keyboard.Joystick1UpRightKey, IsJoystick1Up && IsJoystick1Right, ref _isJoystick1UpRightKeyDown, ref _wasJoystick1UpRightKeyDown);
|
||||
UpdateKey(keyboard.Joystick1DownLeftKey, IsJoystick1Down && IsJoystick1Left, ref _isJoystick1DownLeftKeyDown, ref _wasJoystick1DownLeftKeyDown);
|
||||
UpdateKey(keyboard.Joystick1DownRightKey, IsJoystick1Down && IsJoystick1Right, ref _isJoystick1DownRightKeyDown, ref _wasJoystick1DownRightKeyDown);
|
||||
UpdateKey(keyboard.Button0Key, IsButton0Down, ref _isButton0KeyDown, ref _wasButton0KeyDown);
|
||||
UpdateKey(keyboard.Button1Key, IsButton1Down, ref _isButton1KeyDown, ref _wasButton1KeyDown);
|
||||
UpdateKey(keyboard.Button2Key, IsButton2Down, ref _isButton2KeyDown, ref _wasButton2KeyDown);
|
||||
|
||||
if (_lastKey > 0) // repeat last key
|
||||
{
|
||||
long time = DateTime.UtcNow.Ticks;
|
||||
if (time - _lastTime >= _repeatTime)
|
||||
{
|
||||
_lastTime = time;
|
||||
_repeatTime = RepeatSpeed;
|
||||
keyboard.Latch = _lastKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateKey(int key, bool isActive, ref bool isKeyDown, ref bool wasKeyDown)
|
||||
{
|
||||
wasKeyDown = isKeyDown;
|
||||
isKeyDown = (key > 0) && isActive;
|
||||
|
||||
if (isKeyDown != wasKeyDown)
|
||||
{
|
||||
if (isKeyDown)
|
||||
{
|
||||
_lastKey = key;
|
||||
_lastTime = DateTime.UtcNow.Ticks;
|
||||
_repeatTime = RepeatDelay;
|
||||
Machine.Keyboard.Latch = key;
|
||||
}
|
||||
else if (key == _lastKey)
|
||||
{
|
||||
_lastKey = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Paddle0 { get; protected set; }
|
||||
public int Paddle1 { get; protected set; }
|
||||
public int Paddle2 { get; protected set; }
|
||||
public int Paddle3 { get; protected set; }
|
||||
|
||||
public bool IsJoystick0Up { get; protected set; }
|
||||
public bool IsJoystick0Left { get; protected set; }
|
||||
public bool IsJoystick0Right { get; protected set; }
|
||||
public bool IsJoystick0Down { get; protected set; }
|
||||
|
||||
public bool IsJoystick1Up { get; protected set; }
|
||||
public bool IsJoystick1Left { get; protected set; }
|
||||
public bool IsJoystick1Right { get; protected set; }
|
||||
public bool IsJoystick1Down { get; protected set; }
|
||||
|
||||
public bool IsButton0Down { get; protected set; }
|
||||
public bool IsButton1Down { get; protected set; }
|
||||
public bool IsButton2Down { get; protected set; }
|
||||
|
||||
private static readonly long RepeatDelay = TimeSpan.FromMilliseconds(500).Ticks;
|
||||
private static readonly long RepeatSpeed = TimeSpan.FromMilliseconds(32).Ticks;
|
||||
|
||||
private bool _isJoystick0UpLeftKeyDown;
|
||||
private bool _isJoystick0UpKeyDown;
|
||||
private bool _isJoystick0UpRightKeyDown;
|
||||
private bool _isJoystick0LeftKeyDown;
|
||||
private bool _isJoystick0RightKeyDown;
|
||||
private bool _isJoystick0DownLeftKeyDown;
|
||||
private bool _isJoystick0DownKeyDown;
|
||||
private bool _isJoystick0DownRightKeyDown;
|
||||
private bool _isJoystick1UpLeftKeyDown;
|
||||
private bool _isJoystick1UpKeyDown;
|
||||
private bool _isJoystick1UpRightKeyDown;
|
||||
private bool _isJoystick1LeftKeyDown;
|
||||
private bool _isJoystick1RightKeyDown;
|
||||
private bool _isJoystick1DownLeftKeyDown;
|
||||
private bool _isJoystick1DownKeyDown;
|
||||
private bool _isJoystick1DownRightKeyDown;
|
||||
private bool _isButton0KeyDown;
|
||||
private bool _isButton1KeyDown;
|
||||
private bool _isButton2KeyDown;
|
||||
|
||||
private bool _wasJoystick0UpLeftKeyDown;
|
||||
private bool _wasJoystick0UpKeyDown;
|
||||
private bool _wasJoystick0UpRightKeyDown;
|
||||
private bool _wasJoystick0LeftKeyDown;
|
||||
private bool _wasJoystick0RightKeyDown;
|
||||
private bool _wasJoystick0DownLeftKeyDown;
|
||||
private bool _wasJoystick0DownKeyDown;
|
||||
private bool _wasJoystick0DownRightKeyDown;
|
||||
private bool _wasJoystick1UpLeftKeyDown;
|
||||
private bool _wasJoystick1UpKeyDown;
|
||||
private bool _wasJoystick1UpRightKeyDown;
|
||||
private bool _wasJoystick1LeftKeyDown;
|
||||
private bool _wasJoystick1RightKeyDown;
|
||||
private bool _wasJoystick1DownLeftKeyDown;
|
||||
private bool _wasJoystick1DownKeyDown;
|
||||
private bool _wasJoystick1DownRightKeyDown;
|
||||
private bool _wasButton0KeyDown;
|
||||
private bool _wasButton1KeyDown;
|
||||
private bool _wasButton2KeyDown;
|
||||
|
||||
private int _lastKey;
|
||||
private long _lastTime;
|
||||
private long _repeatTime;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.IO.IsolatedStorage;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public class IsolatedStorageService : StorageService
|
||||
{
|
||||
public IsolatedStorageService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnLoad(string fileName, Action<Stream> reader)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
using (var store = GetStore())
|
||||
{
|
||||
using (var stream = store.OpenFile(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
reader(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSave(string fileName, Action<Stream> writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
using (var store = GetStore())
|
||||
{
|
||||
using (var stream = store.OpenFile(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
writer(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
|
||||
protected virtual IsolatedStorageFile GetStore()
|
||||
{
|
||||
return IsolatedStorageFile.GetUserStoreForApplication();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public abstract class KeyboardService : MachineService
|
||||
{
|
||||
protected KeyboardService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract bool IsKeyDown(int key);
|
||||
|
||||
public virtual void Update() // main thread
|
||||
{
|
||||
var keyboard = Machine.Keyboard;
|
||||
|
||||
if (IsResetKeyDown && !keyboard.DisableResetKey)
|
||||
{
|
||||
if (!_resetKeyDown)
|
||||
{
|
||||
_resetKeyDown = true; // entering reset; pause until key released
|
||||
Machine.Pause();
|
||||
Machine.Reset();
|
||||
}
|
||||
}
|
||||
else if (_resetKeyDown)
|
||||
{
|
||||
_resetKeyDown = false; // leaving reset
|
||||
Machine.Unpause();
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
protected bool IsResetKeyDown { get; set; }
|
||||
|
||||
private bool _resetKeyDown;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public abstract class MachineService : DisposableBase
|
||||
{
|
||||
protected MachineService(Machine machine)
|
||||
{
|
||||
Machine = machine;
|
||||
|
||||
_debugService = new Lazy<DebugService>(() => Machine.Services.GetService<DebugService>());
|
||||
}
|
||||
|
||||
protected Machine Machine { get; private set; }
|
||||
protected DebugService DebugService { get { return _debugService.Value; } }
|
||||
|
||||
private Lazy<DebugService> _debugService;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Jellyfish.Virtu.Properties;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class MachineServices : IServiceProvider
|
||||
{
|
||||
public void AddService(Type serviceType, MachineService serviceProvider)
|
||||
{
|
||||
if (serviceType == null)
|
||||
{
|
||||
throw new ArgumentNullException("serviceType");
|
||||
}
|
||||
if (_serviceProviders.ContainsKey(serviceType))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, Strings.ServiceAlreadyPresent, serviceType.FullName), "serviceType");
|
||||
}
|
||||
if (serviceProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException("serviceProvider");
|
||||
}
|
||||
if (!serviceType.IsAssignableFrom(serviceProvider.GetType()))
|
||||
{
|
||||
throw new ArgumentException(string.Format(CultureInfo.CurrentUICulture, Strings.ServiceMustBeAssignable, serviceType.FullName, serviceProvider.GetType().FullName));
|
||||
}
|
||||
|
||||
_serviceProviders.Add(serviceType, serviceProvider);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
|
||||
public T GetService<T>()
|
||||
{
|
||||
return (T)((IServiceProvider)this).GetService(typeof(T));
|
||||
}
|
||||
|
||||
public void RemoveService(Type serviceType)
|
||||
{
|
||||
_serviceProviders.Remove(serviceType);
|
||||
}
|
||||
|
||||
object IServiceProvider.GetService(Type serviceType)
|
||||
{
|
||||
return _serviceProviders.ContainsKey(serviceType) ? _serviceProviders[serviceType] : null;
|
||||
}
|
||||
|
||||
private Dictionary<Type, MachineService> _serviceProviders = new Dictionary<Type, MachineService>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using Jellyfish.Virtu.Properties;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public abstract class StorageService : MachineService
|
||||
{
|
||||
protected StorageService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public bool Load(string fileName, Action<Stream> reader)
|
||||
{
|
||||
try
|
||||
{
|
||||
DebugService.WriteMessage("Loading file '{0}'", fileName);
|
||||
OnLoad(fileName, reader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
[SecuritySafeCritical]
|
||||
#endif
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public static bool LoadFile(string fileName, Action<Stream> reader)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DebugService.Default.WriteMessage("Loading file '{0}'", fileName);
|
||||
using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
reader(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.Default.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
[SecuritySafeCritical]
|
||||
#endif
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public static bool LoadFile(FileInfo fileInfo, Action<Stream> reader)
|
||||
{
|
||||
if (fileInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException("fileInfo");
|
||||
}
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DebugService.Default.WriteMessage("Loading file '{0}'", fileInfo.Name);
|
||||
using (var stream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
reader(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.Default.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public static bool LoadResource(string resourceName, Action<Stream> reader)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DebugService.Default.WriteMessage("Loading resource '{0}'", resourceName);
|
||||
using (var stream = GetResourceStream(resourceName))
|
||||
{
|
||||
reader(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.Default.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public bool Save(string fileName, Action<Stream> writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
DebugService.WriteMessage("Saving file '{0}'", fileName);
|
||||
OnSave(fileName, writer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
[SecuritySafeCritical]
|
||||
#endif
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public static bool SaveFile(string fileName, Action<Stream> writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DebugService.Default.WriteMessage("Saving file '{0}'", fileName);
|
||||
using (var stream = File.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
writer(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.Default.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
[SecuritySafeCritical]
|
||||
#endif
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
|
||||
public static bool SaveFile(FileInfo fileInfo, Action<Stream> writer)
|
||||
{
|
||||
if (fileInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException("fileInfo");
|
||||
}
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DebugService.Default.WriteMessage("Saving file '{0}'", fileInfo.Name);
|
||||
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
writer(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugService.Default.WriteMessage(ex.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void OnLoad(string fileName, Action<Stream> reader);
|
||||
|
||||
protected abstract void OnSave(string fileName, Action<Stream> writer);
|
||||
|
||||
private static Stream GetResourceStream(string resourceName)
|
||||
{
|
||||
resourceName = "Jellyfish.Virtu." + resourceName.Replace('/', '.');
|
||||
var resourceStream = typeof(StorageService).Assembly.GetManifestResourceStream(resourceName);
|
||||
if (resourceStream == null)
|
||||
{
|
||||
throw new FileNotFoundException(string.Format(CultureInfo.CurrentUICulture, Strings.ResourceNotFound, resourceName));
|
||||
}
|
||||
|
||||
return resourceStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public abstract class VideoService : MachineService
|
||||
{
|
||||
protected VideoService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void SetFullScreen(bool isFullScreen)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void SetPixel(int x, int y, uint color);
|
||||
public abstract void Update(); // main thread
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class Speaker : MachineComponent
|
||||
{
|
||||
public Speaker(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
_flushOutputEvent = FlushOutputEvent; // cache delegates; avoids garbage
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_audioService = Machine.Services.GetService<AudioService>();
|
||||
|
||||
Volume = 0.5f;
|
||||
Machine.Events.AddEvent(CyclesPerFlush * Machine.Cpu.Multiplier, _flushOutputEvent);
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_audioService.Reset();
|
||||
_isHigh = false;
|
||||
_highCycles = _totalCycles = 0;
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader, Version version)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException("reader");
|
||||
}
|
||||
|
||||
Volume = reader.ReadSingle();
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException("writer");
|
||||
}
|
||||
|
||||
writer.Write(Volume);
|
||||
}
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
UpdateCycles();
|
||||
_isHigh ^= true;
|
||||
}
|
||||
|
||||
private void FlushOutputEvent()
|
||||
{
|
||||
UpdateCycles();
|
||||
_audioService.Output(_highCycles * short.MaxValue / _totalCycles); // quick and dirty decimation
|
||||
_highCycles = _totalCycles = 0;
|
||||
|
||||
Machine.Events.AddEvent(CyclesPerFlush * Machine.Cpu.Multiplier, _flushOutputEvent);
|
||||
}
|
||||
|
||||
private void UpdateCycles()
|
||||
{
|
||||
int delta = (int)(Machine.Cpu.Cycles - _lastCycles);
|
||||
if (_isHigh)
|
||||
{
|
||||
_highCycles += delta;
|
||||
}
|
||||
_totalCycles += delta;
|
||||
_lastCycles = Machine.Cpu.Cycles;
|
||||
}
|
||||
|
||||
public float Volume { get { return _volume; } set { _volume = value; _audioService.SetVolume(_volume); } }
|
||||
|
||||
private const int CyclesPerFlush = 23;
|
||||
|
||||
private Action _flushOutputEvent;
|
||||
|
||||
private AudioService _audioService;
|
||||
|
||||
private bool _isHigh;
|
||||
private int _highCycles;
|
||||
private int _totalCycles;
|
||||
private long _lastCycles;
|
||||
private float _volume;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue