diff --git a/AppleWinExpress2015.vcxproj b/AppleWinExpress2015.vcxproj
index da8de854..ad61ff30 100644
--- a/AppleWinExpress2015.vcxproj
+++ b/AppleWinExpress2015.vcxproj
@@ -369,6 +369,7 @@
true
source\cpu;source\emulator;source\debugger;zlib;zip_lib;libyaml\include;%(AdditionalIncludeDirectories)
MultiThreadedDebug
+ Default
Windows
diff --git a/bin/History.txt b/bin/History.txt
index 899d480e..d5f9454c 100644
--- a/bin/History.txt
+++ b/bin/History.txt
@@ -8,6 +8,34 @@ https://github.com/AppleWin/AppleWin/issues/new
Tom Charlesworth
+1.29.0.0 - 8 Jul 2019
+---------------------
+. [Change #544] Support for .woz disk images.
+ - WOZ1 and WOZ2 formats supported.
+ - read-only: images forced to write-protected (so 'Stickybear Town Builder' doesn't work).
+ - only 5.25" (not 3.5").
+ - known issues: 'Wizardry III' not booting.
+
+
+1.28.8.0 - 28 Jun 2019
+----------------------
+. [Change #648] Support 50Hz(PAL) video refresh rate and implicitly PAL 1.016MHz.
+ - NB. TV video modes still use NTSC rendering.
+. [Bug #656] Fix for PAGE1/2 ($C054/55) not having a 1 cycle delay.
+
+
+1.28.7.0 - 15 Jun 2019
+----------------------
+. [Bug #654] Fix for Sather's "Little Text Window" not rendering correctly.
+. [Bug #652] Fix for 6522 TIMER1's period to be N+2 cycles.
+
+
+1.28.6.0 - 2 Jun 2019
+---------------------
+. [Bug #651] Cycle-accurate interrupts:
+ - Interrupts sources are checked after every opcode (full-speed after every 40 opcodes).
+ - 6522 TIMERs in free-running mode now account for the underflowed cycles when resetting the count.
+
1.28.5.0 - 6 Apr 2019
---------------------
@@ -63,7 +91,7 @@ Tom Charlesworth
- Any v1 save-state files should be loaded into AppleWin 1.27, and then re-saved to a v2 save-state file.
. [Change #597] Removed the functionality for CTRL+F10 to reveal the mouse cursor.
. [Change #585] Added a 'Swap' HDD button to the Configuration->Input property sheet.
-. [Bug #608] Mockingboard's 6522 TIMER1 wasn't generating an interrupt quickly enough for Broadside's detection routine.
+. [Bug #608,#236] Mockingboard's 6522 TIMER1 wasn't generating an interrupt quickly enough for detection routines for Broadside and Ultima III Jukebox.
1.27.13.0 - 8 Dec 2018
diff --git a/docs/compiling.txt b/docs/compiling.txt
index 0dbc3150..ed4bf3cf 100644
--- a/docs/compiling.txt
+++ b/docs/compiling.txt
@@ -27,6 +27,7 @@ MSVC 2017 Community
* [x] Graphics debugger and GPU profiler for DirectX
* [x] Static analysis tools
* [x] VC++ 2017 v141 toolset (x86,x64)
+ * [x] Windows Universal CRT SDK
* [x] Visual Studio C++ core features
* [x] Windows 8.1 SDK
* [x] Windows 10 SDK (10.0.15063.0) for Desktop C++ x86 and x64
diff --git a/help/CommandLine.html b/help/CommandLine.html
index aca64edb..4965a2a1 100644
--- a/help/CommandLine.html
+++ b/help/CommandLine.html
@@ -98,7 +98,11 @@
Or: Allow the emulated Apple II to read the Enter key state when Alt (Open Apple key) is pressed.
-rgb-card-invert-bit7
- Force the RGB card (in "Color (RGB Monitor)" video mode) to invert bit7 in MIX mode. Enables the correct rendering for Dragon Wars.
+ Force the RGB card (in "Color (RGB Monitor)" video mode) to invert bit7 in MIX mode. Enables the correct rendering for Dragon Wars.
+ -50hz
+ Support 50Hz(PAL) video refresh rate and PAL 1.016MHz base CPU clock.
+ -60hz
+ Support 60Hz(NTSC) video refresh rate and NTSC 1.020MHz base CPU clock (default).
Debug arguments:
diff --git a/help/cfg-config.html b/help/cfg-config.html
index 17daf96e..f954ce6a 100644
--- a/help/cfg-config.html
+++ b/help/cfg-config.html
@@ -78,6 +78,11 @@
processor speed from half-speed to as fast as your PC can emulate.
+ 50Hz video:
+ When checked, this option will run the emulated machine with a 50Hz(PAL) video refresh rate.
+ The default is unchecked, for 60Hz(NTSC).
+
+
Benchmark Emulator:
This will run a benchmark test that will show how fast your PC can emulate an
Apple //e system with this emulator. In order to run the benchmark, the
diff --git a/help/ddi-formats.html b/help/ddi-formats.html
index 0b197b37..b16c688d 100644
--- a/help/ddi-formats.html
+++ b/help/ddi-formats.html
@@ -57,7 +57,7 @@ successfully detect the format. Otherwise, it will revert to DOS
order, which is by far the most common format. To force ProDOS
order, give the file an extension of ".PO".
-Nibble Images :
+Nibble Images:
Nibble images contain all of the data on a
disk; not just the data in sectors but also the sector headers
@@ -66,13 +66,18 @@ that would be recorded on a real disk's surface. At 232,960
bytes, nibble images are bigger than other images, but they can
be useful for making images of copy protected software.
-2mg Images :
+2mg Images:
2mg (or 2img) images are a wrapper around DOS, ProDOS or Nibble images.
They contain extra meta-data describing for example, DOS volume number and
write-protection.
+WOZ Images:
+
+The WOZ Disk Image format is an offshoot of the Applesauce project . Capturing highly accurate bit data is of no use if you don't have a container to hold the data. The WOZ format was designed to be able to contain every possible Apple ][ disk structure and layout. It can be so accurate that even copy protected software can't tell that it isn't an original disk.
+
+
Compressed Images :
All of the above can optionally be either gzip'ed or zipped. If a zip archive
@@ -80,8 +85,8 @@ contains multiple files, then AppleWin only supports using the first file. For b
with hard disk images, uncompress first, as writing back to the image requires a full
image re-compression after every block write. Examples of typical extensions are:
-.gz, .dsk.gz, .nib.gz, .2mg.gz
-.zip, .dsk.zip, .nib.zip, .2mg.zip
+.gz, .dsk.gz, .nib.gz, .2mg.gz, .woz.gz
+.zip, .dsk.zip, .nib.zip, .2mg.zip, .woz.zip
diff --git a/help/img/config.png b/help/img/config.png
index c4e0b941..d35785ca 100644
Binary files a/help/img/config.png and b/help/img/config.png differ
diff --git a/resource/Applewin.rc b/resource/Applewin.rc
index 2e2f4a68..a20b23a0 100644
--- a/resource/Applewin.rc
+++ b/resource/Applewin.rc
@@ -111,6 +111,7 @@ BEGIN
CTEXT "2.0",IDC_2_0_MHz,96,180,20,10
RTEXT "Fastest",IDC_MAX_MHz,150,180,29,10
PUSHBUTTON "&Benchmark Emulator",IDC_BENCHMARK,15,194,85,15
+ CONTROL "50Hz video",IDC_CHECK_50HZ_VIDEO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,142,141,51,10
END
IDD_PROPPAGE_INPUT DIALOGEX 0, 0, 210, 215
diff --git a/resource/resource.h b/resource/resource.h
index 3b6df8e2..7c6b5402 100644
--- a/resource/resource.h
+++ b/resource/resource.h
@@ -116,6 +116,7 @@
#define IDC_COMBO_DISK2 1081
#define IDC_CHECK_FS_SHOW_SUBUNIT_STATUS 1082
#define IDC_CHECK_VERTICAL_BLEND 1083
+#define IDC_CHECK_50HZ_VIDEO 1084
#define IDM_EXIT 40001
#define IDM_HELP 40002
#define IDM_ABOUT 40003
diff --git a/resource/version.h b/resource/version.h
index 4b56dec7..327790e8 100644
--- a/resource/version.h
+++ b/resource/version.h
@@ -1,4 +1,4 @@
-#define APPLEWIN_VERSION 1,28,5,0
+#define APPLEWIN_VERSION 1,29,0,0
#define xstr(a) str(a)
#define str(a) #a
diff --git a/source/Applewin.cpp b/source/Applewin.cpp
index 2a879c3e..9b9cd088 100644
--- a/source/Applewin.cpp
+++ b/source/Applewin.cpp
@@ -91,7 +91,7 @@ bool g_bRestart = false;
bool g_bRestartFullScreen = false;
DWORD g_dwSpeed = SPEED_NORMAL; // Affected by Config dialog's speed slider bar
-double g_fCurrentCLK6502 = CLK_6502; // Affected by Config dialog's speed slider bar
+double g_fCurrentCLK6502 = CLK_6502_NTSC; // Affected by Config dialog's speed slider bar
static double g_fMHz = 1.0; // Affected by Config dialog's speed slider bar
int g_nCpuCyclesFeedback = 0;
@@ -138,6 +138,7 @@ void LogFileTimeUntilFirstKeyReadReset(void)
// Log the time from emulation restart/reboot until the first key read: BIT $C000
// . AZTEC.DSK (DOS 3.3) does prior LDY $C000 reads, but the BIT $C000 is at the "Press any key" message
// . Phasor1.dsk / ProDOS 1.1.1: PC=E797: B1 50: LDA ($50),Y / "Select an Option:" message
+// . Rescue Raiders v1.3,v1.5: PC=895: LDA $C000 / boot to intro
void LogFileTimeUntilFirstKeyRead(void)
{
if (!g_fh || bLogKeyReadDone)
@@ -145,6 +146,7 @@ void LogFileTimeUntilFirstKeyRead(void)
if ( (mem[regs.pc-3] != 0x2C) // AZTEC: bit $c000
&& !((regs.pc-2) == 0xE797 && mem[regs.pc-2] == 0xB1 && mem[regs.pc-1] == 0x50) // Phasor1: lda ($50),y
+ && !((regs.pc-3) == 0x0895 && mem[regs.pc-3] == 0xAD) // Rescue Raiders v1.3,v1.5: lda $c000
)
return;
@@ -345,6 +347,7 @@ static void ContinueExecution(void)
//
+ const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
if (g_dwCyclesThisFrame >= dwClksPerFrame)
{
g_dwCyclesThisFrame -= dwClksPerFrame;
@@ -376,14 +379,21 @@ void SingleStep(bool bReinit)
//===========================================================================
+double Get6502BaseClock(void)
+{
+ return (GetVideoRefreshRate() == VR_50HZ) ? CLK_6502_PAL : CLK_6502_NTSC;
+}
+
void SetCurrentCLK6502(void)
{
static DWORD dwPrevSpeed = (DWORD) -1;
+ static VideoRefreshRate_e prevVideoRefreshRate = VR_NONE;
- if(dwPrevSpeed == g_dwSpeed)
+ if (dwPrevSpeed == g_dwSpeed && GetVideoRefreshRate() == prevVideoRefreshRate)
return;
dwPrevSpeed = g_dwSpeed;
+ prevVideoRefreshRate = GetVideoRefreshRate();
// SPEED_MIN = 0 = 0.50 MHz
// SPEED_NORMAL = 10 = 1.00 MHz
@@ -396,7 +406,7 @@ void SetCurrentCLK6502(void)
else
g_fMHz = (double)g_dwSpeed / 10.0;
- g_fCurrentCLK6502 = CLK_6502 * g_fMHz;
+ g_fCurrentCLK6502 = Get6502BaseClock() * g_fMHz;
//
// Now re-init modules that are dependent on /g_fCurrentCLK6502/
@@ -622,17 +632,15 @@ void LoadConfiguration(void)
}
REGLOAD(TEXT(REGVALUE_EMULATION_SPEED) ,&g_dwSpeed);
+ Config_Load_Video();
+ SetCurrentCLK6502(); // Pre: g_dwSpeed && Config_Load_Video()->SetVideoRefreshRate()
DWORD dwEnhanceDisk;
REGLOAD(TEXT(REGVALUE_ENHANCE_DISK_SPEED), &dwEnhanceDisk);
sg_Disk2Card.SetEnhanceDisk(dwEnhanceDisk ? true : false);
- Config_Load_Video();
-
REGLOAD(TEXT("Uthernet Active") ,(DWORD *)&tfe_enabled);
- SetCurrentCLK6502();
-
//
DWORD dwTmp;
@@ -1181,6 +1189,7 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int)
int newVideoType = -1;
int newVideoStyleEnableMask = 0;
int newVideoStyleDisableMask = 0;
+ VideoRefreshRate_e newVideoRefreshRate = VR_NONE;
LPSTR szScreenshotFilename = NULL;
while (*lpCmdLine)
@@ -1427,6 +1436,14 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int)
szScreenshotFilename = GetCurrArg(lpNextArg);
lpNextArg = GetNextArg(lpNextArg);
}
+ else if (_stricmp(lpCmdLine, "-50hz") == 0) // (case-insensitive)
+ {
+ newVideoRefreshRate = VR_50HZ;
+ }
+ else if (_stricmp(lpCmdLine, "-60hz") == 0) // (case-insensitive)
+ {
+ newVideoRefreshRate = VR_60HZ;
+ }
else // unsupported
{
LogFileOutput("Unsupported arg: %s\n", lpCmdLine);
@@ -1537,9 +1554,19 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int)
LogFileOutput("Main: LoadConfiguration()\n");
if (newVideoType >= 0)
+ {
SetVideoType( (VideoType_e)newVideoType );
+ newVideoType = -1; // Don't reapply after a restart
+ }
SetVideoStyle( (VideoStyle_e) ((GetVideoStyle() | newVideoStyleEnableMask) & ~newVideoStyleDisableMask) );
+ if (newVideoRefreshRate != VR_NONE)
+ {
+ SetVideoRefreshRate(newVideoRefreshRate);
+ newVideoRefreshRate = VR_NONE; // Don't reapply after a restart
+ SetCurrentCLK6502();
+ }
+
// Apply the memory expansion switches after loading the Apple II machine type
#ifdef RAMWORKS
if (uRamWorksExPages)
@@ -1613,7 +1640,7 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int)
}
// Need to test if it's safe to call ResetMachineState(). In the meantime, just call DiskReset():
- sg_Disk2Card.Reset(); // Switch from a booting A][+ to a non-autostart A][, so need to turn off floppy motor
+ sg_Disk2Card.Reset(true); // Switch from a booting A][+ to a non-autostart A][, so need to turn off floppy motor
LogFileOutput("Main: DiskReset()\n");
HD_Reset(); // GH#515
LogFileOutput("Main: HDDReset()\n");
diff --git a/source/Applewin.h b/source/Applewin.h
index 1d3d950f..c1c5899a 100644
--- a/source/Applewin.h
+++ b/source/Applewin.h
@@ -6,7 +6,6 @@
void LogFileTimeUntilFirstKeyReadReset(void);
void LogFileTimeUntilFirstKeyRead(void);
-void SetCurrentCLK6502();
bool SetCurrentImageDir(const char* pszImageDir);
extern const UINT16* GetOldAppleWinVersion(void);
@@ -18,6 +17,9 @@ extern eApple2Type g_Apple2Type;
eApple2Type GetApple2Type(void);
void SetApple2Type(eApple2Type type);
+double Get6502BaseClock(void);
+void SetCurrentCLK6502(void);
+
void SingleStep(bool bReinit);
extern bool g_bFullSpeed;
diff --git a/source/CPU.cpp b/source/CPU.cpp
index c951431d..4db08186 100644
--- a/source/CPU.cpp
+++ b/source/CPU.cpp
@@ -130,12 +130,7 @@ regsrec regs;
unsigned __int64 g_nCumulativeCycles = 0;
static ULONG g_nCyclesExecuted; // # of cycles executed up to last IO access
-
//static signed long g_uInternalExecutedCycles;
-// TODO: Use IRQ_CHECK_TIMEOUT=128 when running at full-speed else with IRQ_CHECK_TIMEOUT=1
-// - What about when running benchmark?
-static const int IRQ_CHECK_TIMEOUT = 128;
-static signed int g_nIrqCheckTimeout = IRQ_CHECK_TIMEOUT;
//
@@ -417,21 +412,29 @@ static __forceinline void IRQ(ULONG& uExecutedCycles, BOOL& flagc, BOOL& flagn,
}
}
-static __forceinline void CheckInterruptSources(ULONG uExecutedCycles)
+const int IRQ_CHECK_OPCODE_FULL_SPEED = 40; // ~128 cycles (assume 3 cycles per opcode)
+static int g_fullSpeedOpcodeCount = IRQ_CHECK_OPCODE_FULL_SPEED;
+
+static __forceinline void CheckInterruptSources(ULONG uExecutedCycles, const bool bVideoUpdate)
{
- if (g_nIrqCheckTimeout < 0)
+ if (!bVideoUpdate)
{
- MB_UpdateCycles(uExecutedCycles);
- sg_Mouse.SetVBlank( !VideoGetVblBar(uExecutedCycles) );
- g_nIrqCheckTimeout = IRQ_CHECK_TIMEOUT;
+ g_fullSpeedOpcodeCount--;
+ if (g_fullSpeedOpcodeCount >= 0)
+ return;
+ g_fullSpeedOpcodeCount = IRQ_CHECK_OPCODE_FULL_SPEED;
}
+
+ MB_UpdateCycles(uExecutedCycles);
+ sg_Mouse.SetVBlank( !VideoGetVblBar(uExecutedCycles) );
}
// GH#608: IRQ needs to occur within 17 cycles (6 opcodes) of configuring the timer interrupt
void CpuAdjustIrqCheck(UINT uCyclesUntilInterrupt)
{
- if (uCyclesUntilInterrupt < IRQ_CHECK_TIMEOUT)
- g_nIrqCheckTimeout = uCyclesUntilInterrupt;
+ const UINT opcodesUntilInterrupt = uCyclesUntilInterrupt/3; // assume 3 cycles per opcode
+ if (g_bFullSpeed && opcodesUntilInterrupt < IRQ_CHECK_OPCODE_FULL_SPEED)
+ g_fullSpeedOpcodeCount = opcodesUntilInterrupt;
}
//===========================================================================
diff --git a/source/CPU/cpu6502.h b/source/CPU/cpu6502.h
index a9b87701..29b8452b 100644
--- a/source/CPU/cpu6502.h
+++ b/source/CPU/cpu6502.h
@@ -318,7 +318,7 @@ static DWORD Cpu6502(DWORD uTotalCycles, const bool bVideoUpdate)
#undef $
}
- CheckInterruptSources(uExecutedCycles);
+ CheckInterruptSources(uExecutedCycles, bVideoUpdate);
NMI(uExecutedCycles, flagc, flagn, flagv, flagz);
IRQ(uExecutedCycles, flagc, flagn, flagv, flagz);
diff --git a/source/CPU/cpu65C02.h b/source/CPU/cpu65C02.h
index 67279db3..1dc06227 100644
--- a/source/CPU/cpu65C02.h
+++ b/source/CPU/cpu65C02.h
@@ -321,7 +321,7 @@ static DWORD Cpu65C02(DWORD uTotalCycles, const bool bVideoUpdate)
#undef $
}
- CheckInterruptSources(uExecutedCycles);
+ CheckInterruptSources(uExecutedCycles, bVideoUpdate);
NMI(uExecutedCycles, flagc, flagn, flagv, flagz);
IRQ(uExecutedCycles, flagc, flagn, flagv, flagz);
diff --git a/source/CPU/cpu65d02.h b/source/CPU/cpu65d02.h
index e7e5b2e9..bdb78634 100644
--- a/source/CPU/cpu65d02.h
+++ b/source/CPU/cpu65d02.h
@@ -406,7 +406,7 @@ static DWORD Cpu65D02(DWORD uTotalCycles, const bool bVideoUpdate)
}
#undef $
- CheckInterruptSources(uExecutedCycles);
+ CheckInterruptSources(uExecutedCycles, bVideoUpdate);
NMI(uExecutedCycles, flagc, flagn, flagv, flagz);
IRQ(uExecutedCycles, flagc, flagn, flagv, flagz);
diff --git a/source/CPU/cpu_general.inl b/source/CPU/cpu_general.inl
index 4d565efb..8f0b7a87 100644
--- a/source/CPU/cpu_general.inl
+++ b/source/CPU/cpu_general.inl
@@ -49,7 +49,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
| (flagz ? AF_ZERO : 0) \
| AF_RESERVED | AF_BREAK;
// CYC(a): This can be optimised, as only certain opcodes will affect uExtraCycles
-#define CYC(a) uExecutedCycles += (a)+uExtraCycles; g_nIrqCheckTimeout -= (a)+uExtraCycles;
+#define CYC(a) uExecutedCycles += (a)+uExtraCycles;
#define POP (*(mem+((regs.sp >= 0x1FF) ? (regs.sp = 0x100) : ++regs.sp)))
#define PUSH(a) *(mem+regs.sp--) = (a); \
if (regs.sp < 0x100) \
diff --git a/source/Common.h b/source/Common.h
index f72fbfa4..05662a8a 100644
--- a/source/Common.h
+++ b/source/Common.h
@@ -1,19 +1,11 @@
#pragma once
-const double _M14 = (157500000.0 / 11.0); // 14.3181818... * 10^6
-const double CLK_6502 = ((_M14 * 65.0) / 912.0); // 65 cycles per 912 14M clocks
+const double _14M_NTSC = (157500000.0 / 11.0); // 14.3181818... * 10^6
+const double _14M_PAL = 14.25045e6; // UTAIIe:3-17
+const double CLK_6502_NTSC = (_14M_NTSC * 65.0) / (65.0*14.0+2.0); // 65 cycles per 912 14M clocks
+const double CLK_6502_PAL = (_14M_PAL * 65.0) / (65.0*14.0+2.0);
//const double CLK_6502 = 23 * 44100; // 1014300
-// The effective Z-80 clock rate is 2.041MHz
-// See: http://www.apple2info.net/hardware/softcard/SC-SWHW_a2in.pdf
-const double CLK_Z80 = (CLK_6502 * 2);
-
-// TODO: Clean up from Common.h, Video.cpp, and NTSC.h !!!
-const UINT uCyclesPerLine = 65; // 25 cycles of HBL & 40 cycles of HBL'
-const UINT uVisibleLinesPerFrame = 64*3; // 192
-const UINT uLinesPerFrame = 262; // 64 in each third of the screen & 70 in VBL
-const DWORD dwClksPerFrame = uCyclesPerLine * uLinesPerFrame; // 17030
-
#define NUM_SLOTS 8
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
@@ -107,6 +99,7 @@ enum AppMode_e
#define REGVALUE_VIDEO_STYLE "Video Style" // GH#616: Added at 1.28.2
#define REGVALUE_VIDEO_HALF_SCAN_LINES "Half Scan Lines" // GH#616: Deprecated from 1.28.2
#define REGVALUE_VIDEO_MONO_COLOR "Monochrome Color"
+#define REGVALUE_VIDEO_REFRESH_RATE "Video Refresh Rate"
#define REGVALUE_SERIAL_PORT_NAME "Serial Port Name"
#define REGVALUE_ENHANCE_DISK_SPEED "Enhance Disk Speed"
#define REGVALUE_CUSTOM_SPEED "Custom Speed"
diff --git a/source/Configuration/Config.h b/source/Configuration/Config.h
index e60a3285..909f0d21 100644
--- a/source/Configuration/Config.h
+++ b/source/Configuration/Config.h
@@ -4,6 +4,7 @@
#include "../CPU.h"
#include "../DiskImage.h" // Disk_Status_e
#include "../Harddisk.h" // HD_CardIsEnabled()
+#include "../Video.h" // VideoRefreshRate_e, GetVideoRefreshRate()
class CConfigNeedingRestart
{
@@ -11,7 +12,8 @@ public:
CConfigNeedingRestart(UINT bEnableTheFreezesF8Rom = false) :
m_Apple2Type( GetApple2Type() ),
m_CpuType( GetMainCpu() ),
- m_uSaveLoadStateMsg(0)
+ m_uSaveLoadStateMsg(0),
+ m_videoRefreshRate( GetVideoRefreshRate() )
{
m_bEnableHDD = HD_CardIsEnabled();
m_bEnableTheFreezesF8Rom = bEnableTheFreezesF8Rom;
@@ -29,17 +31,19 @@ public:
m_bEnableHDD = other.m_bEnableHDD;
m_bEnableTheFreezesF8Rom = other.m_bEnableTheFreezesF8Rom;
m_uSaveLoadStateMsg = other.m_uSaveLoadStateMsg;
+ m_videoRefreshRate = other.m_videoRefreshRate;
return *this;
}
bool operator== (const CConfigNeedingRestart& other) const
{
return m_Apple2Type == other.m_Apple2Type &&
- m_CpuType == other.m_CpuType &&
- memcmp(m_Slot, other.m_Slot, sizeof(m_Slot)) == 0 &&
- m_bEnableHDD == other.m_bEnableHDD &&
- m_bEnableTheFreezesF8Rom == other.m_bEnableTheFreezesF8Rom &&
- m_uSaveLoadStateMsg == other.m_uSaveLoadStateMsg;
+ m_CpuType == other.m_CpuType &&
+ memcmp(m_Slot, other.m_Slot, sizeof(m_Slot)) == 0 &&
+ m_bEnableHDD == other.m_bEnableHDD &&
+ m_bEnableTheFreezesF8Rom == other.m_bEnableTheFreezesF8Rom &&
+ m_uSaveLoadStateMsg == other.m_uSaveLoadStateMsg &&
+ m_videoRefreshRate == other.m_videoRefreshRate;
}
bool operator!= (const CConfigNeedingRestart& other) const
@@ -54,4 +58,5 @@ public:
bool m_bEnableHDD;
UINT m_bEnableTheFreezesF8Rom;
UINT m_uSaveLoadStateMsg;
+ VideoRefreshRate_e m_videoRefreshRate;
};
diff --git a/source/Configuration/PageConfig.cpp b/source/Configuration/PageConfig.cpp
index ca4b5faf..7d548589 100644
--- a/source/Configuration/PageConfig.cpp
+++ b/source/Configuration/PageConfig.cpp
@@ -121,6 +121,7 @@ BOOL CPageConfig::DlgProcInternal(HWND hWnd, UINT message, WPARAM wparam, LPARAM
case IDC_CHECK_HALF_SCAN_LINES:
case IDC_CHECK_VERTICAL_BLEND:
case IDC_CHECK_FS_SHOW_SUBUNIT_STATUS:
+ case IDC_CHECK_50HZ_VIDEO:
// Checked in DlgOK()
break;
@@ -205,6 +206,8 @@ BOOL CPageConfig::DlgProcInternal(HWND hWnd, UINT message, WPARAM wparam, LPARAM
m_PropertySheetHelper.FillComboBox(hWnd,IDC_SERIALPORT, sg_SSC.GetSerialPortChoices(), sg_SSC.GetSerialPort());
EnableWindow(GetDlgItem(hWnd, IDC_SERIALPORT), !sg_SSC.IsActive() ? TRUE : FALSE);
+ CheckDlgButton(hWnd, IDC_CHECK_50HZ_VIDEO, (GetVideoRefreshRate() == VR_50HZ) ? BST_CHECKED : BST_UNCHECKED);
+
SendDlgItemMessage(hWnd,IDC_SLIDER_CPU_SPEED,TBM_SETRANGE,1,MAKELONG(0,40));
SendDlgItemMessage(hWnd,IDC_SLIDER_CPU_SPEED,TBM_SETPAGESIZE,0,5);
SendDlgItemMessage(hWnd,IDC_SLIDER_CPU_SPEED,TBM_SETTICFREQ,10,0);
@@ -286,6 +289,13 @@ void CPageConfig::DlgOK(HWND hWnd)
bVideoReinit = true;
}
+ const bool isNewVideoRate50Hz = IsDlgButtonChecked(hWnd, IDC_CHECK_50HZ_VIDEO) != 0;
+ const bool isCurrentVideoRate50Hz = GetVideoRefreshRate() == VR_50HZ;
+ if (isCurrentVideoRate50Hz != isNewVideoRate50Hz)
+ {
+ m_PropertySheetHelper.GetConfigNew().m_videoRefreshRate = isNewVideoRate50Hz ? VR_50HZ : VR_60HZ;
+ }
+
if (bVideoReinit)
{
Config_Save_Video();
diff --git a/source/Configuration/PropertySheetHelper.cpp b/source/Configuration/PropertySheetHelper.cpp
index 37c79443..94967fc4 100644
--- a/source/Configuration/PropertySheetHelper.cpp
+++ b/source/Configuration/PropertySheetHelper.cpp
@@ -407,6 +407,11 @@ void CPropertySheetHelper::ApplyNewConfig(const CConfigNeedingRestart& ConfigNew
{
REGSAVE(TEXT(REGVALUE_THE_FREEZES_F8_ROM), ConfigNew.m_bEnableTheFreezesF8Rom);
}
+
+ if (CONFIG_CHANGED_LOCAL(m_videoRefreshRate))
+ {
+ REGSAVE(TEXT(REGVALUE_VIDEO_REFRESH_RATE), ConfigNew.m_videoRefreshRate);
+ }
}
void CPropertySheetHelper::ApplyNewConfig(void)
@@ -423,6 +428,7 @@ void CPropertySheetHelper::SaveCurrentConfig(void)
m_ConfigOld.m_Slot[5] = g_Slot5;
m_ConfigOld.m_bEnableHDD = HD_CardIsEnabled();
m_ConfigOld.m_bEnableTheFreezesF8Rom = sg_PropertySheet.GetTheFreezesF8Rom();
+ m_ConfigOld.m_videoRefreshRate = GetVideoRefreshRate();
// Reset flags each time:
m_ConfigOld.m_uSaveLoadStateMsg = 0;
@@ -441,6 +447,7 @@ void CPropertySheetHelper::RestoreCurrentConfig(void)
g_Slot5 = m_ConfigOld.m_Slot[5];
HD_SetEnabled(m_ConfigOld.m_bEnableHDD);
sg_PropertySheet.SetTheFreezesF8Rom(m_ConfigOld.m_bEnableTheFreezesF8Rom);
+ SetVideoRefreshRate(m_ConfigOld.m_videoRefreshRate);
}
bool CPropertySheetHelper::IsOkToSaveLoadState(HWND hWnd, const bool bConfigChanged)
@@ -491,6 +498,9 @@ bool CPropertySheetHelper::HardwareConfigChanged(HWND hWnd)
if (CONFIG_CHANGED(m_CpuType))
strMsgMain += ". Emulated main CPU has changed\n";
+ if (CONFIG_CHANGED(m_videoRefreshRate))
+ strMsgMain += ". Video refresh rate has changed\n";
+
if (CONFIG_CHANGED(m_Slot[4]))
strMsgMain += GetSlot(4);
diff --git a/source/Debugger/Debug.cpp b/source/Debugger/Debug.cpp
index 71f47157..2fc7e920 100644
--- a/source/Debugger/Debug.cpp
+++ b/source/Debugger/Debug.cpp
@@ -3727,15 +3727,16 @@ Update_t CmdDisk ( int nArgs)
if (nArgs > 2)
goto _Help;
- int drive = sg_Disk2Card.GetCurrentDrive() + 1;
char buffer[200] = "";
- ConsoleBufferPushFormat(buffer, "D%d at T$%X (%d), phase $%X, offset $%X, %s",
- drive,
- sg_Disk2Card.GetCurrentTrack(),
- sg_Disk2Card.GetCurrentTrack(),
- sg_Disk2Card.GetCurrentPhase(),
+ ConsoleBufferPushFormat(buffer, "D%d at T$%s, phase $%s, offset $%X, mask $%02X, extraCycles %.2f, %s",
+ sg_Disk2Card.GetCurrentDrive() + 1,
+ sg_Disk2Card.GetCurrentTrackString().c_str(),
+ sg_Disk2Card.GetCurrentPhaseString().c_str(),
sg_Disk2Card.GetCurrentOffset(),
- sg_Disk2Card.GetCurrentState());
+ sg_Disk2Card.GetCurrentLSSBitMask(),
+ sg_Disk2Card.GetCurrentExtraCycles(),
+ sg_Disk2Card.GetCurrentState()
+ );
return ConsoleUpdate();
}
diff --git a/source/Disk.cpp b/source/Disk.cpp
index c7ea7073..4ac08a95 100644
--- a/source/Disk.cpp
+++ b/source/Disk.cpp
@@ -56,18 +56,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Disk2InterfaceCard::Disk2InterfaceCard(void)
{
- m_currDrive = 0;
+ ResetSwitches();
+
m_floppyLatch = 0;
- m_floppyMotorOn = 0;
- m_floppyLoadMode = 0;
- m_floppyWriteMode = 0;
- m_phases = 0;
m_saveDiskImage = true; // Save the DiskImage name to Registry
m_slot = 0;
m_diskLastCycle = 0;
m_diskLastReadLatchCycle = 0;
m_enhanceDisk = true;
+ ResetLogicStateSequencer();
+
// Debug:
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
m_bLogDisk_NibblesRW = false;
@@ -82,11 +81,40 @@ bool Disk2InterfaceCard::GetEnhanceDisk(void) { return m_enhanceDisk; }
void Disk2InterfaceCard::SetEnhanceDisk(bool bEnhanceDisk) { m_enhanceDisk = bEnhanceDisk; }
int Disk2InterfaceCard::GetCurrentDrive(void) { return m_currDrive; }
-int Disk2InterfaceCard::GetCurrentTrack(void) { return m_floppyDrive[m_currDrive].m_track; }
-int Disk2InterfaceCard::GetCurrentPhase(void) { return m_floppyDrive[m_currDrive].m_phase; }
+int Disk2InterfaceCard::GetCurrentTrack(void) { return ImagePhaseToTrack(m_floppyDrive[m_currDrive].m_disk.m_imagehandle, m_floppyDrive[m_currDrive].m_phasePrecise, false); }
+float Disk2InterfaceCard::GetCurrentPhase(void) { return m_floppyDrive[m_currDrive].m_phasePrecise; }
int Disk2InterfaceCard::GetCurrentOffset(void) { return m_floppyDrive[m_currDrive].m_disk.m_byte; }
-int Disk2InterfaceCard::GetTrack(const int drive) { return m_floppyDrive[drive].m_track; }
+BYTE Disk2InterfaceCard::GetCurrentLSSBitMask(void) { return m_floppyDrive[m_currDrive].m_disk.m_bitMask; }
+double Disk2InterfaceCard::GetCurrentExtraCycles(void) { return m_floppyDrive[m_currDrive].m_disk.m_extraCycles; }
+int Disk2InterfaceCard::GetTrack(const int drive) { return ImagePhaseToTrack(m_floppyDrive[drive].m_disk.m_imagehandle, m_floppyDrive[drive].m_phasePrecise, false); }
+std::string Disk2InterfaceCard::GetCurrentTrackString(void)
+{
+ const UINT trackInt = (UINT)(m_floppyDrive[m_currDrive].m_phasePrecise / 2);
+ const float trackFrac = (m_floppyDrive[m_currDrive].m_phasePrecise / 2) - (float)trackInt;
+
+ char szInt[8] = "";
+ sprintf(szInt, "%02X", trackInt); // "$NN"
+
+ char szFrac[8] = "";
+ sprintf(szFrac, "%.02f", trackFrac); // "0.nn"
+
+ return std::string(szInt) + std::string(szFrac+1);
+}
+
+std::string Disk2InterfaceCard::GetCurrentPhaseString(void)
+{
+ const UINT phaseInt = (UINT)(m_floppyDrive[m_currDrive].m_phasePrecise);
+ const float phaseFrac = m_floppyDrive[m_currDrive].m_phasePrecise - (float)phaseInt;
+
+ char szInt[8] = "";
+ sprintf(szInt, "%02X", phaseInt); // "$NN"
+
+ char szFrac[8] = "";
+ sprintf(szFrac, "%.02f", phaseFrac); // "0.nn"
+
+ return std::string(szInt) + std::string(szFrac+1);
+}
LPCTSTR Disk2InterfaceCard::GetCurrentState(void)
{
if (m_floppyDrive[m_currDrive].m_disk.m_imagehandle == NULL)
@@ -175,7 +203,7 @@ void Disk2InterfaceCard::SaveLastDiskImage(const int drive)
//===========================================================================
// Called by ControlMotor() & Enable()
-void Disk2InterfaceCard::CheckSpinning(const ULONG nExecutedCycles)
+void Disk2InterfaceCard::CheckSpinning(const ULONG uExecutedCycles)
{
DWORD modechange = (m_floppyMotorOn && !m_floppyDrive[m_currDrive].m_spinning);
@@ -188,7 +216,7 @@ void Disk2InterfaceCard::CheckSpinning(const ULONG nExecutedCycles)
if (modechange)
{
// Set m_diskLastCycle when motor changes: not spinning (ie. off for 1 sec) -> on
- CpuCalcCycles(nExecutedCycles);
+ CpuCalcCycles(uExecutedCycles);
m_diskLastCycle = g_nCumulativeCycles;
}
}
@@ -237,7 +265,7 @@ void Disk2InterfaceCard::AllocTrack(const int drive)
//===========================================================================
-void Disk2InterfaceCard::ReadTrack(const int drive)
+void Disk2InterfaceCard::ReadTrack(const int drive, ULONG uExecutedCycles)
{
if (! IsDriveValid( drive ))
return;
@@ -245,8 +273,9 @@ void Disk2InterfaceCard::ReadTrack(const int drive)
FloppyDrive* pDrive = &m_floppyDrive[ drive ];
FloppyDisk* pFloppy = &pDrive->m_disk;
- if (pDrive->m_track >= ImageGetNumTracks(pFloppy->m_imagehandle))
+ if (ImagePhaseToTrack(pFloppy->m_imagehandle, pDrive->m_phasePrecise, false) >= ImageGetNumTracks(pFloppy->m_imagehandle))
{
+ _ASSERT(0); // What can cause this? Add a comment to replace this assert.
pFloppy->m_trackimagedata = false;
return;
}
@@ -257,17 +286,45 @@ void Disk2InterfaceCard::ReadTrack(const int drive)
if (pFloppy->m_trackimage && pFloppy->m_imagehandle)
{
#if LOG_DISK_TRACKS
- LOG_DISK("track $%02X%s read\r\n", pDrive->m_track, (pDrive->m_phase & 1) ? ".5" : " ");
+ CpuCalcCycles(uExecutedCycles);
+ const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - pDrive->m_lastStepperCycle);
+ LOG_DISK("track $%s read (time since last stepper %.3fms)\r\n", GetCurrentTrackString().c_str(), ((float)cycleDelta) / (CLK_6502_NTSC / 1000.0));
#endif
+ const UINT32 currentPosition = pFloppy->m_byte;
+ const UINT32 currentTrackLength = pFloppy->m_nibbles;
+
ImageReadTrack(
pFloppy->m_imagehandle,
- pDrive->m_track,
- pDrive->m_phase,
+ pDrive->m_phasePrecise,
pFloppy->m_trackimage,
&pFloppy->m_nibbles,
+ &pFloppy->m_bitCount,
m_enhanceDisk);
- pFloppy->m_byte = 0;
+ if (!ImageIsWOZ(pFloppy->m_imagehandle) || (currentTrackLength == 0))
+ {
+ pFloppy->m_byte = 0;
+ }
+ else
+ {
+ _ASSERT(pFloppy->m_nibbles && pFloppy->m_bitCount);
+ if (pFloppy->m_nibbles == 0 || pFloppy->m_bitCount == 0)
+ {
+ pFloppy->m_nibbles = 1;
+ pFloppy->m_bitCount = 8;
+ }
+
+ pFloppy->m_byte = (currentPosition * pFloppy->m_nibbles) / currentTrackLength; // Ref: WOZ-1.01
+
+ if (pFloppy->m_byte == (pFloppy->m_nibbles-1)) // Last nibble may not be complete, so advance by 1 nibble
+ pFloppy->m_byte = 0;
+
+ pFloppy->m_bitOffset = pFloppy->m_byte*8;
+ pFloppy->m_bitMask = 1 << 7;
+ pFloppy->m_extraCycles = 0.0;
+ pDrive->m_headWindow = 0;
+ }
+
pFloppy->m_trackimagedata = (pFloppy->m_nibbles != 0);
}
}
@@ -308,8 +365,11 @@ void Disk2InterfaceCard::WriteTrack(const int drive)
FloppyDrive* pDrive = &m_floppyDrive[ drive ];
FloppyDisk* pFloppy = &pDrive->m_disk;
- if (pDrive->m_track >= ImageGetNumTracks(pFloppy->m_imagehandle))
+ if (ImagePhaseToTrack(pFloppy->m_imagehandle, pDrive->m_phasePrecise, false) >= ImageGetNumTracks(pFloppy->m_imagehandle))
+ {
+ _ASSERT(0); // What can cause this? Add a comment to replace this assert.
return;
+ }
if (pFloppy->m_bWriteProtected)
return;
@@ -317,12 +377,11 @@ void Disk2InterfaceCard::WriteTrack(const int drive)
if (pFloppy->m_trackimage && pFloppy->m_imagehandle)
{
#if LOG_DISK_TRACKS
- LOG_DISK("track $%02X%s write\r\n", pDrive->m_track, (pDrive->m_phase & 0) ? ".5" : " "); // TODO: hard-coded to whole tracks - see below (nickw)
+ LOG_DISK("track $%s write\r\n", GetCurrentTrackString().c_str());
#endif
ImageWriteTrack(
pFloppy->m_imagehandle,
- pDrive->m_track,
- pDrive->m_phase, // TODO: this should never be used; it's the current phase (half-track), not that of the track to be written (nickw)
+ pDrive->m_phasePrecise,
pFloppy->m_trackimage,
pFloppy->m_nibbles);
}
@@ -359,7 +418,7 @@ void __stdcall Disk2InterfaceCard::ControlMotor(WORD, WORD address, BYTE, BYTE,
m_floppyMotorOn = newState;
// NB. Motor off doesn't reset the Command Decoder like reset. (UTAIIe figures 9.7 & 9.8 chip C2)
- // - so it doesn't reset this state: m_floppyLoadMode, m_floppyWriteMode, m_phases
+ // - so it doesn't reset this state: m_floppyLoadMode, m_floppyWriteMode, m_magnetStates
#if LOG_DISK_MOTOR
LOG_DISK("motor %s\r\n", (m_floppyMotorOn) ? "on" : "off");
#endif
@@ -388,68 +447,75 @@ void __stdcall Disk2InterfaceCard::ControlStepper(WORD, WORD address, BYTE, BYTE
#endif
}
- int phase = (address >> 1) & 3;
- int phase_bit = (1 << phase);
+ // update phases (magnet states)
+ {
+ const int phase = (address >> 1) & 3;
+ const int phase_bit = (1 << phase);
-#if 1
- // update the magnet states
- if (address & 1)
- {
- // phase on
- m_phases |= phase_bit;
- }
- else
- {
- // phase off
- m_phases &= ~phase_bit;
+ // update the magnet states
+ if (address & 1)
+ m_magnetStates |= phase_bit; // phase on
+ else
+ m_magnetStates &= ~phase_bit; // phase off
}
+ CpuCalcCycles(uExecutedCycles);
+#if LOG_DISK_PHASES
+ const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - pDrive->m_lastStepperCycle);
+#endif
+ pDrive->m_lastStepperCycle = g_nCumulativeCycles;
+
// check for any stepping effect from a magnet
// - move only when the magnet opposite the cog is off
// - move in the direction of an adjacent magnet if one is on
- // - do not move if both adjacent magnets are on
+ // - do not move if both adjacent magnets are on (ie. quarter track)
// momentum and timing are not accounted for ... maybe one day!
int direction = 0;
- if (m_phases & (1 << ((pDrive->m_phase + 1) & 3)))
+ if (m_magnetStates & (1 << ((pDrive->m_phase + 1) & 3)))
direction += 1;
- if (m_phases & (1 << ((pDrive->m_phase + 3) & 3)))
+ if (m_magnetStates & (1 << ((pDrive->m_phase + 3) & 3)))
direction -= 1;
- // apply magnet step, if any
- if (direction)
+ // Only calculate quarterDirection for WOZ, as NIB/DSK don't support half phases.
+ int quarterDirection = 0;
+ if (ImageIsWOZ(pFloppy->m_imagehandle))
{
- pDrive->m_phase = MAX(0, MIN(79, pDrive->m_phase + direction));
- const int nNumTracksInImage = ImageGetNumTracks(pFloppy->m_imagehandle);
- const int newtrack = (nNumTracksInImage == 0) ? 0
- : MIN(nNumTracksInImage-1, pDrive->m_phase >> 1); // (round half tracks down)
- if (newtrack != pDrive->m_track)
+ if ((m_magnetStates == 0xC || // 1100
+ m_magnetStates == 0x6 || // 0110
+ m_magnetStates == 0x3 || // 0011
+ m_magnetStates == 0x9)) // 1001
{
- FlushCurrentTrack(m_currDrive);
- pDrive->m_track = newtrack;
- pFloppy->m_trackimagedata = false;
-
- m_formatTrack.DriveNotWritingTrack();
+ quarterDirection = direction;
+ direction = 0;
}
-
- // Feature Request #201 Show track status
- // https://github.com/AppleWin/AppleWin/issues/201
- FrameDrawDiskStatus( (HDC)0 );
}
-#else
- // substitute alternate stepping code here to test
-#endif
+
+ pDrive->m_phase = MAX(0, MIN(79, pDrive->m_phase + direction));
+ float newPhasePrecise = (float)(pDrive->m_phase) + (float)quarterDirection * 0.5f;
+ if (newPhasePrecise < 0)
+ newPhasePrecise = 0;
+
+ // apply magnet step, if any
+ if (newPhasePrecise != pDrive->m_phasePrecise)
+ {
+ FlushCurrentTrack(m_currDrive);
+ pDrive->m_phasePrecise = newPhasePrecise;
+ pFloppy->m_trackimagedata = false;
+ m_formatTrack.DriveNotWritingTrack();
+ FrameDrawDiskStatus((HDC)0); // Show track status (GH#201)
+ }
#if LOG_DISK_PHASES
- LOG_DISK("track $%02X%s phases %d%d%d%d phase %d %s address $%4X\r\n",
- pDrive->m_phase >> 1,
- (pDrive->m_phase & 1) ? ".5" : " ",
- (m_phases >> 3) & 1,
- (m_phases >> 2) & 1,
- (m_phases >> 1) & 1,
- (m_phases >> 0) & 1,
- phase,
+ LOG_DISK("track $%s magnet-states %d%d%d%d phase %d %s address $%4X last-stepper %.3fms\r\n",
+ GetCurrentTrackString().c_str(),
+ (m_magnetStates >> 3) & 1,
+ (m_magnetStates >> 2) & 1,
+ (m_magnetStates >> 1) & 1,
+ (m_magnetStates >> 0) & 1,
+ (address >> 1) & 3, // phase
(address & 1) ? "on " : "off",
- address);
+ address,
+ ((float)cycleDelta)/(CLK_6502_NTSC/1000.0));
#endif
}
@@ -797,14 +863,14 @@ bool Disk2InterfaceCard::LogWriteCheckSyncFF(ULONG& uCycleDelta)
//===========================================================================
-void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
+void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles)
{
/* m_floppyLoadMode = 0; */
FloppyDrive* pDrive = &m_floppyDrive[m_currDrive];
FloppyDisk* pFloppy = &pDrive->m_disk;
if (!pFloppy->m_trackimagedata && pFloppy->m_imagehandle)
- ReadTrack(m_currDrive);
+ ReadTrack(m_currDrive, uExecutedCycles);
if (!pFloppy->m_trackimagedata)
{
@@ -814,7 +880,7 @@ void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BY
// Improve precision of "authentic" drive mode - GH#125
UINT uSpinNibbleCount = 0;
- CpuCalcCycles(nExecutedCycles); // g_nCumulativeCycles required for uSpinNibbleCount & LogWriteCheckSyncFF()
+ CpuCalcCycles(uExecutedCycles); // g_nCumulativeCycles required for uSpinNibbleCount & LogWriteCheckSyncFF()
if (!m_enhanceDisk && pDrive->m_spinning)
{
@@ -877,6 +943,9 @@ void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BY
}
else if (!pFloppy->m_bWriteProtected) // && m_floppyWriteMode
{
+ if (!pDrive->m_spinning)
+ return; // If not spinning then only 1 bit-cell gets written?
+
*(pFloppy->m_trackimage + pFloppy->m_byte) = m_floppyLatch;
pFloppy->m_trackimagedirty = true;
@@ -911,17 +980,246 @@ void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BY
//===========================================================================
+void Disk2InterfaceCard::ResetLogicStateSequencer(void)
+{
+ m_shiftReg = 0;
+ m_latchDelay = 0;
+ m_resetSequencer = true;
+ m_dbgLatchDelayedCnt = 0;
+}
+
+void Disk2InterfaceCard::UpdateBitStreamPositionAndDiskCycle(const ULONG uExecutedCycles)
+{
+ FloppyDisk& floppy = m_floppyDrive[m_currDrive].m_disk;
+
+ CpuCalcCycles(uExecutedCycles);
+ const UINT bitCellDelta = GetBitCellDelta(ImageGetOptimalBitTiming(floppy.m_imagehandle));
+ UpdateBitStreamPosition(floppy, bitCellDelta);
+
+ m_diskLastCycle = g_nCumulativeCycles;
+}
+
+UINT Disk2InterfaceCard::GetBitCellDelta(const BYTE optimalBitTiming)
+{
+ FloppyDisk& floppy = m_floppyDrive[m_currDrive].m_disk;
+
+ // NB. m_extraCycles is needed to retain accuracy:
+ // . Read latch #1: 0-> 9: cycleDelta= 9, bitCellDelta=2, extraCycles=1
+ // . Read latch #2: 9->20: cycleDelta=11, bitCellDelta=2, extraCycles=3
+ // . Overall: 0->20: cycleDelta=20, bitCellDelta=5, extraCycles=0
+ UINT bitCellDelta;
+#if 0
+ if (optimalBitTiming == 32)
+ {
+ const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - m_diskLastCycle) + (BYTE) m_extraCycles;
+ bitCellDelta = cycleDelta / 4; // DIV 4 for 4us per bit-cell
+ m_extraCycles = cycleDelta & 3; // MOD 4 : remainder carried forward for next time
+ }
+ else
+#endif
+ {
+ const double cycleDelta = (double)(g_nCumulativeCycles - m_diskLastCycle) + floppy.m_extraCycles;
+ const double bitTime = 0.125 * (double)optimalBitTiming; // 125ns units
+ bitCellDelta = (UINT) floor( cycleDelta / bitTime );
+ floppy.m_extraCycles = (double)cycleDelta - ((double)bitCellDelta * bitTime);
+ }
+ return bitCellDelta;
+}
+
+void Disk2InterfaceCard::UpdateBitStreamPosition(FloppyDisk& floppy, const ULONG bitCellDelta)
+{
+ _ASSERT(floppy.m_bitCount); // Should never happen - ReadTrack() will handle this
+ if (floppy.m_bitCount == 0)
+ return;
+
+ floppy.m_bitOffset += bitCellDelta;
+ if (floppy.m_bitOffset >= floppy.m_bitCount)
+ floppy.m_bitOffset %= floppy.m_bitCount;
+
+ UpdateBitStreamOffsets(floppy);
+}
+
+void Disk2InterfaceCard::UpdateBitStreamOffsets(FloppyDisk& floppy)
+{
+ floppy.m_byte = floppy.m_bitOffset / 8;
+ const UINT remainder = 7 - (floppy.m_bitOffset & 7);
+ floppy.m_bitMask = 1 << remainder;
+}
+
+void __stdcall Disk2InterfaceCard::ReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles)
+{
+ /* m_floppyLoadMode = 0; */
+ FloppyDrive& drive = m_floppyDrive[m_currDrive];
+ FloppyDisk& floppy = drive.m_disk;
+
+ if (!floppy.m_trackimagedata && floppy.m_imagehandle)
+ ReadTrack(m_currDrive, uExecutedCycles);
+
+ if (!floppy.m_trackimagedata)
+ {
+ _ASSERT(0); // Can't happen for WOZ - ReadTrack() should return an empty track
+ m_floppyLatch = 0xFF;
+ return;
+ }
+
+ // Don't change latch if drive off after 1 second drive-off delay (UTAIIe page 9-13)
+ // "DRIVES OFF forces the data register to hold its present state." (UTAIIe page 9-12)
+ // Note: Sherwood Forest sets shift mode and reads with the drive off.
+ // TODO: And same for a write?
+ if (!drive.m_spinning) // GH#599
+ return;
+
+ CpuCalcCycles(uExecutedCycles);
+
+ // Skipping forward a large amount of bitcells means the bitstream will very likely be out-of-sync.
+ // The first 1-bit will produce a latch nibble, and this 1-bit is unlikely to be the nibble's high bit.
+ // So we need to ensure we run enough bits through the sequencer to re-sync.
+ // NB. For Planetfall 13 bitcells(NG) / 14 bitcells(OK)
+ const UINT significantBitCells = 50; // 5x 10-bit sync FF nibbles
+ UINT bitCellDelta = GetBitCellDelta(ImageGetOptimalBitTiming(floppy.m_imagehandle));
+
+ UINT bitCellRemainder;
+ if (bitCellDelta <= significantBitCells)
+ {
+ bitCellRemainder = bitCellDelta;
+ }
+ else
+ {
+ bitCellRemainder = significantBitCells;
+ bitCellDelta -= significantBitCells;
+
+ UpdateBitStreamPosition(floppy, bitCellDelta);
+
+ m_latchDelay = 0;
+ }
+
+ m_diskLastCycle = g_nCumulativeCycles;
+
+ //
+
+ if (!m_floppyWriteMode)
+ {
+ // m_diskLastReadLatchCycle = g_nCumulativeCycles; // Not used by WOZ (only by NIB)
+#if LOG_DISK_NIBBLES_READ
+ bool newLatchData = false;
+#endif
+
+ for (UINT i = 0; i < bitCellRemainder; i++)
+ {
+ BYTE n = floppy.m_trackimage[floppy.m_byte];
+
+ drive.m_headWindow <<= 1;
+ drive.m_headWindow |= (n & floppy.m_bitMask) ? 1 : 0;
+ BYTE outputBit = (drive.m_headWindow & 0xf) ? (drive.m_headWindow >> 1) & 1
+ : rand() & 1;
+
+ floppy.m_bitMask >>= 1;
+ if (!floppy.m_bitMask)
+ {
+ floppy.m_bitMask = 1 << 7;
+ floppy.m_byte++;
+ }
+
+ floppy.m_bitOffset++;
+ if (floppy.m_bitOffset == floppy.m_bitCount)
+ {
+ floppy.m_bitMask = 1 << 7;
+ floppy.m_bitOffset = 0;
+ floppy.m_byte = 0;
+ }
+
+ if (m_resetSequencer)
+ {
+ m_resetSequencer = false; // LSS takes some cycles to reset (ref?)
+ continue;
+ }
+
+ //
+
+ m_shiftReg <<= 1;
+ m_shiftReg |= outputBit;
+
+ if (m_latchDelay)
+ {
+ m_latchDelay -= 4;
+ if (m_latchDelay < 0)
+ m_latchDelay = 0;
+
+ if (m_shiftReg)
+ {
+ m_dbgLatchDelayedCnt = 0;
+ }
+ else // m_shiftReg==0
+ {
+ m_latchDelay += 4; // extend by 4us (so 7us again) - GH#662
+
+ m_dbgLatchDelayedCnt++;
+#if LOG_DISK_NIBBLES_READ
+ if (m_dbgLatchDelayedCnt >= 3)
+ {
+ LOG_DISK("read: latch held due to 0: PC=%04X, cnt=%02X\r\n", regs.pc, m_dbgLatchDelayedCnt);
+ }
+#endif
+ }
+ }
+
+ if (!m_latchDelay)
+ {
+#if LOG_DISK_NIBBLES_READ
+ if (newLatchData)
+ {
+ LOG_DISK("read skipped latch data: %04X = %02X\r\n", floppy.m_byte, m_floppyLatch);
+ newLatchData = false;
+ }
+#endif
+ m_floppyLatch = m_shiftReg;
+
+ if (m_shiftReg & 0x80)
+ {
+ m_latchDelay = 7;
+ m_shiftReg = 0;
+#if LOG_DISK_NIBBLES_READ
+ // May not actually be read by 6502 (eg. Prologue's CHKSUM 4&4 nibble pair), but still pass to the log's nibble reader
+ m_formatTrack.DecodeLatchNibbleRead(m_floppyLatch);
+ newLatchData = true;
+#endif
+ }
+ }
+ }
+
+#if LOG_DISK_NIBBLES_READ
+ if (m_floppyLatch & 0x80)
+ {
+ #if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
+ if (m_bLogDisk_NibblesRW)
+ #endif
+ {
+ LOG_DISK("read %04X = %02X\r\n", floppy.m_byte, m_floppyLatch);
+ }
+ }
+#endif
+ }
+ else if (!floppy.m_bWriteProtected) // && m_floppyWriteMode
+ {
+ //TODO
+ }
+
+ // Show track status (GH#201) - NB. Prevent flooding of forcing UI to redraw!!!
+ if ((floppy.m_byte & 0xFF) == 0)
+ FrameDrawDiskStatus( (HDC)0 );
+}
+
+//===========================================================================
+
void Disk2InterfaceCard::Reset(const bool bIsPowerCycle/*=false*/)
{
// RESET forces all switches off (UTAIIe Table 9.1)
- m_currDrive = 0;
- m_floppyMotorOn = 0;
- m_floppyLoadMode = 0;
- m_floppyWriteMode = 0;
- m_phases = 0;
+ ResetSwitches();
m_formatTrack.Reset();
+ ResetLogicStateSequencer();
+
if (bIsPowerCycle) // GH#460
{
// NB. This doesn't affect the drive head (ie. drive's track position)
@@ -937,6 +1235,15 @@ void Disk2InterfaceCard::Reset(const bool bIsPowerCycle/*=false*/)
}
}
+void Disk2InterfaceCard::ResetSwitches(void)
+{
+ m_currDrive = 0;
+ m_floppyMotorOn = 0;
+ m_floppyLoadMode = 0;
+ m_floppyWriteMode = 0;
+ m_magnetStates = 0;
+}
+
//===========================================================================
bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilename/*=""*/)
@@ -958,8 +1265,8 @@ bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilen
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = g_hFrameWindow;
ofn.hInstance = g_hInstance;
- ofn.lpstrFilter = TEXT("All Images\0*.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.zip;*.2mg;*.2img;*.iie;*.apl\0")
- TEXT("Disk Images (*.bin,*.do,*.dsk,*.nib,*.po,*.gz,*.zip,*.2mg,*.2img,*.iie)\0*.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.zip;*.2mg;*.2img;*.iie\0")
+ ofn.lpstrFilter = TEXT("All Images\0*.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.woz;*.zip;*.2mg;*.2img;*.iie;*.apl\0")
+ TEXT("Disk Images (*.bin,*.do,*.dsk,*.nib,*.po,*.gz,*.woz,*.zip,*.2mg,*.2img,*.iie)\0*.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.woz;*.zip;*.2mg;*.2img;*.iie\0")
TEXT("All Files\0*.*\0");
ofn.lpstrFile = filename;
ofn.nMaxFile = MAX_PATH;
@@ -990,7 +1297,7 @@ bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilen
//===========================================================================
-void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG)
+void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG uExecutedCycles)
{
/* m_floppyLoadMode = 1; */
@@ -1007,11 +1314,22 @@ void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE
// . write mode doesn't prevent reading write protect (GH#537):
// "If for some reason the above write protect check were entered with the READ/WRITE switch in WRITE,
// the write protect switch would still be read correctly" (UTAIIe page 9-21)
+ // . Sequencer "SR" (Shift Right) command only loads QA (bit7) of data register (UTAIIe page 9-21)
if (m_floppyDrive[m_currDrive].m_disk.m_bWriteProtected)
m_floppyLatch |= 0x80;
else
m_floppyLatch &= 0x7F;
}
+
+ if (ImageIsWOZ(m_floppyDrive[m_currDrive].m_disk.m_imagehandle))
+ {
+#if LOG_DISK_NIBBLES_READ
+ LOG_DISK("reset LSS: ~PC=%04X\r\n", regs.pc);
+#endif
+ ResetLogicStateSequencer(); // reset sequencer (Ref: WOZ-1.01)
+// m_latchDelay = 7; // TODO: Treat like a regular $C0EC latch load?
+ UpdateBitStreamPositionAndDiskCycle(uExecutedCycles); // Fix E7-copy protection
+ }
}
//===========================================================================
@@ -1181,6 +1499,9 @@ BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE
UINT uSlot = ((addr & 0xff) >> 4) - 8;
Disk2InterfaceCard* pCard = (Disk2InterfaceCard*) MemGetSlotParameters(uSlot);
+ ImageInfo* pImage = pCard->m_floppyDrive[pCard->m_currDrive].m_disk.m_imagehandle;
+ bool isWOZ = ImageIsWOZ(pImage);
+
switch (addr & 0xF)
{
case 0x0: pCard->ControlStepper(pc, addr, bWrite, d, nExecutedCycles); break;
@@ -1195,7 +1516,9 @@ BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE
case 0x9: pCard->ControlMotor(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xA: pCard->Enable(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xB: pCard->Enable(pc, addr, bWrite, d, nExecutedCycles); break;
- case 0xC: pCard->ReadWrite(pc, addr, bWrite, d, nExecutedCycles); break;
+ case 0xC: if (!isWOZ) pCard->ReadWrite(pc, addr, bWrite, d, nExecutedCycles);
+ else pCard->ReadWriteWOZ(pc, addr, bWrite, d, nExecutedCycles);
+ break;
case 0xD: pCard->LoadWriteProtect(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xE: pCard->SetReadMode(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xF: pCard->SetWriteMode(pc, addr, bWrite, d, nExecutedCycles); break;
@@ -1213,6 +1536,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE
UINT uSlot = ((addr & 0xff) >> 4) - 8;
Disk2InterfaceCard* pCard = (Disk2InterfaceCard*) MemGetSlotParameters(uSlot);
+ ImageInfo* pImage = pCard->m_floppyDrive[pCard->m_currDrive].m_disk.m_imagehandle;
+ bool isWOZ = ImageIsWOZ(pImage);
+
switch (addr & 0xF)
{
case 0x0: pCard->ControlStepper(pc, addr, bWrite, d, nExecutedCycles); break;
@@ -1227,7 +1553,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE
case 0x9: pCard->ControlMotor(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xA: pCard->Enable(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xB: pCard->Enable(pc, addr, bWrite, d, nExecutedCycles); break;
- case 0xC: pCard->ReadWrite(pc, addr, bWrite, d, nExecutedCycles); break;
+ case 0xC: if (!isWOZ) pCard->ReadWrite(pc, addr, bWrite, d, nExecutedCycles);
+ else pCard->ReadWriteWOZ(pc, addr, bWrite, d, nExecutedCycles);
+ break;
case 0xD: pCard->LoadWriteProtect(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xE: pCard->SetReadMode(pc, addr, bWrite, d, nExecutedCycles); break;
case 0xF: pCard->SetWriteMode(pc, addr, bWrite, d, nExecutedCycles); break;
@@ -1246,7 +1574,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE
// Unit version history:
// 2: Added: Format Track state & DiskLastCycle
// 3: Added: DiskLastReadLatchCycle
-static const UINT kUNIT_VERSION = 3;
+// 4: Added: WOZ state
+// Split up 'Unit' putting some state into a new 'Floppy'
+static const UINT kUNIT_VERSION = 4;
#define SS_YAML_VALUE_CARD_DISK2 "Disk]["
@@ -1259,16 +1589,27 @@ static const UINT kUNIT_VERSION = 3;
#define SS_YAML_KEY_FLOPPY_WRITE_MODE "Floppy Write Mode"
#define SS_YAML_KEY_LAST_CYCLE "Last Cycle"
#define SS_YAML_KEY_LAST_READ_LATCH_CYCLE "Last Read Latch Cycle"
+#define SS_YAML_KEY_LSS_SHIFT_REG "LSS Shift Reg"
+#define SS_YAML_KEY_LSS_LATCH_DELAY "LSS Latch Delay"
+#define SS_YAML_KEY_LSS_RESET_SEQUENCER "LSS Reset Sequencer"
#define SS_YAML_KEY_DISK2UNIT "Unit"
#define SS_YAML_KEY_FILENAME "Filename"
-#define SS_YAML_KEY_TRACK "Track"
#define SS_YAML_KEY_PHASE "Phase"
+#define SS_YAML_KEY_PHASE_PRECISE "Phase (precise)"
+#define SS_YAML_KEY_TRACK "Track" // deprecated at v4
+#define SS_YAML_KEY_HEAD_WINDOW "Head Window"
+#define SS_YAML_KEY_LAST_STEPPER_CYCLE "Last Stepper Cycle"
+
+#define SS_YAML_KEY_FLOPPY "Floppy"
#define SS_YAML_KEY_BYTE "Byte"
+#define SS_YAML_KEY_NIBBLES "Nibbles"
+#define SS_YAML_KEY_BIT_OFFSET "Bit Offset"
+#define SS_YAML_KEY_BIT_COUNT "Bit Count"
+#define SS_YAML_KEY_EXTRA_CYCLES "Extra Cycles"
#define SS_YAML_KEY_WRITE_PROTECTED "Write Protected"
#define SS_YAML_KEY_SPINNING "Spinning"
#define SS_YAML_KEY_WRITE_LIGHT "Write Light"
-#define SS_YAML_KEY_NIBBLES "Nibbles"
#define SS_YAML_KEY_TRACK_IMAGE_DATA "Track Image Data"
#define SS_YAML_KEY_TRACK_IMAGE_DIRTY "Track Image Dirty"
#define SS_YAML_KEY_TRACK_IMAGE "Track Image"
@@ -1279,17 +1620,16 @@ std::string Disk2InterfaceCard::GetSnapshotCardName(void)
return name;
}
-void Disk2InterfaceCard::SaveSnapshotDisk2Unit(YamlSaveHelper& yamlSaveHelper, UINT unit)
+void Disk2InterfaceCard::SaveSnapshotFloppy(YamlSaveHelper& yamlSaveHelper, UINT unit)
{
- YamlSaveHelper::Label label(yamlSaveHelper, "%s%d:\n", SS_YAML_KEY_DISK2UNIT, unit);
+ YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_FLOPPY);
yamlSaveHelper.SaveString(SS_YAML_KEY_FILENAME, m_floppyDrive[unit].m_disk.m_fullname);
- yamlSaveHelper.SaveUint(SS_YAML_KEY_TRACK, m_floppyDrive[unit].m_track);
- yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASE, m_floppyDrive[unit].m_phase);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_BYTE, m_floppyDrive[unit].m_disk.m_byte);
- yamlSaveHelper.SaveBool(SS_YAML_KEY_WRITE_PROTECTED, m_floppyDrive[unit].m_disk.m_bWriteProtected);
- yamlSaveHelper.SaveUint(SS_YAML_KEY_SPINNING, m_floppyDrive[unit].m_spinning);
- yamlSaveHelper.SaveUint(SS_YAML_KEY_WRITE_LIGHT, m_floppyDrive[unit].m_writelight);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_NIBBLES, m_floppyDrive[unit].m_disk.m_nibbles);
+ yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_BIT_OFFSET, m_floppyDrive[unit].m_disk.m_bitOffset); // v4
+ yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_BIT_COUNT, m_floppyDrive[unit].m_disk.m_bitCount); // v4
+ yamlSaveHelper.SaveDouble(SS_YAML_KEY_EXTRA_CYCLES, m_floppyDrive[unit].m_disk.m_extraCycles); // v4
+ yamlSaveHelper.SaveBool(SS_YAML_KEY_WRITE_PROTECTED, m_floppyDrive[unit].m_disk.m_bWriteProtected);
yamlSaveHelper.SaveUint(SS_YAML_KEY_TRACK_IMAGE_DATA, m_floppyDrive[unit].m_disk.m_trackimagedata);
yamlSaveHelper.SaveUint(SS_YAML_KEY_TRACK_IMAGE_DIRTY, m_floppyDrive[unit].m_disk.m_trackimagedirty);
@@ -1300,13 +1640,26 @@ void Disk2InterfaceCard::SaveSnapshotDisk2Unit(YamlSaveHelper& yamlSaveHelper, U
}
}
+void Disk2InterfaceCard::SaveSnapshotDriveUnit(YamlSaveHelper& yamlSaveHelper, UINT unit)
+{
+ YamlSaveHelper::Label label(yamlSaveHelper, "%s%d:\n", SS_YAML_KEY_DISK2UNIT, unit);
+ yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASE, m_floppyDrive[unit].m_phase);
+ yamlSaveHelper.SaveFloat(SS_YAML_KEY_PHASE_PRECISE, m_floppyDrive[unit].m_phasePrecise); // v4
+ yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_HEAD_WINDOW, m_floppyDrive[unit].m_headWindow); // v4
+ yamlSaveHelper.SaveHexUint64(SS_YAML_KEY_LAST_STEPPER_CYCLE, m_floppyDrive[unit].m_lastStepperCycle); // v4
+ yamlSaveHelper.SaveUint(SS_YAML_KEY_SPINNING, m_floppyDrive[unit].m_spinning);
+ yamlSaveHelper.SaveUint(SS_YAML_KEY_WRITE_LIGHT, m_floppyDrive[unit].m_writelight);
+
+ SaveSnapshotFloppy(yamlSaveHelper, unit);
+}
+
void Disk2InterfaceCard::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION);
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
- yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_PHASES, m_phases);
yamlSaveHelper.SaveUint(SS_YAML_KEY_CURRENT_DRIVE, m_currDrive);
+ yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_PHASES, m_magnetStates);
yamlSaveHelper.SaveBool(SS_YAML_KEY_DISK_ACCESSED, false); // deprecated
yamlSaveHelper.SaveBool(SS_YAML_KEY_ENHANCE_DISK, m_enhanceDisk);
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_FLOPPY_LATCH, m_floppyLatch);
@@ -1314,29 +1667,24 @@ void Disk2InterfaceCard::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper)
yamlSaveHelper.SaveBool(SS_YAML_KEY_FLOPPY_WRITE_MODE, m_floppyWriteMode == TRUE);
yamlSaveHelper.SaveHexUint64(SS_YAML_KEY_LAST_CYCLE, m_diskLastCycle); // v2
yamlSaveHelper.SaveHexUint64(SS_YAML_KEY_LAST_READ_LATCH_CYCLE, m_diskLastReadLatchCycle); // v3
+ yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_LSS_SHIFT_REG, m_shiftReg); // v4
+ yamlSaveHelper.SaveInt(SS_YAML_KEY_LSS_LATCH_DELAY, m_latchDelay); // v4
+ yamlSaveHelper.SaveBool(SS_YAML_KEY_LSS_RESET_SEQUENCER, m_resetSequencer); // v4
m_formatTrack.SaveSnapshot(yamlSaveHelper); // v2
- SaveSnapshotDisk2Unit(yamlSaveHelper, DRIVE_1);
- SaveSnapshotDisk2Unit(yamlSaveHelper, DRIVE_2);
+ SaveSnapshotDriveUnit(yamlSaveHelper, DRIVE_1);
+ SaveSnapshotDriveUnit(yamlSaveHelper, DRIVE_2);
}
-void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit)
+bool Disk2InterfaceCard::LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track)
{
- std::string disk2UnitName = std::string(SS_YAML_KEY_DISK2UNIT) + (unit == DRIVE_1 ? std::string("0") : std::string("1"));
- if (!yamlLoadHelper.GetSubMap(disk2UnitName))
- throw std::string("Card: Expected key: ") + disk2UnitName;
-
- bool bImageError = false;
-
- m_floppyDrive[unit].m_disk.m_fullname[0] = 0;
- m_floppyDrive[unit].m_disk.m_imagename[0] = 0;
- m_floppyDrive[unit].m_disk.m_bWriteProtected = false; // Default to false (until image is successfully loaded below)
-
std::string filename = yamlLoadHelper.LoadString(SS_YAML_KEY_FILENAME);
- if (!filename.empty())
+ bool bImageError = filename.empty();
+
+ if (!bImageError)
{
DWORD dwAttributes = GetFileAttributes(filename.c_str());
- if(dwAttributes == INVALID_FILE_ATTRIBUTES)
+ if (dwAttributes == INVALID_FILE_ATTRIBUTES)
{
// Get user to browse for file
UserSelectNewDiskImage(unit, filename.c_str());
@@ -1347,38 +1695,106 @@ void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, U
bImageError = (dwAttributes == INVALID_FILE_ATTRIBUTES);
if (!bImageError)
{
- if(InsertDisk(unit, filename.c_str(), dwAttributes & FILE_ATTRIBUTE_READONLY, IMAGE_DONT_CREATE) != eIMAGE_ERROR_NONE)
+ if (InsertDisk(unit, filename.c_str(), dwAttributes & FILE_ATTRIBUTE_READONLY, IMAGE_DONT_CREATE) != eIMAGE_ERROR_NONE)
bImageError = true;
// DiskInsert() zeros m_floppyDrive[unit], then sets up:
- // . imagename
- // . fullname
- // . writeprotected
+ // . m_imagename
+ // . m_fullname
+ // . m_bWriteProtected
}
}
- m_floppyDrive[unit].m_track = yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK);
- m_floppyDrive[unit].m_phase = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASE);
- m_floppyDrive[unit].m_disk.m_byte = yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTE);
yamlLoadHelper.LoadBool(SS_YAML_KEY_WRITE_PROTECTED); // Consume
- m_floppyDrive[unit].m_spinning = yamlLoadHelper.LoadUint(SS_YAML_KEY_SPINNING);
- m_floppyDrive[unit].m_writelight = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_LIGHT);
- m_floppyDrive[unit].m_disk.m_nibbles = yamlLoadHelper.LoadUint(SS_YAML_KEY_NIBBLES);
- m_floppyDrive[unit].m_disk.m_trackimagedata = yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK_IMAGE_DATA) ? true : false;
- m_floppyDrive[unit].m_disk.m_trackimagedirty = yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK_IMAGE_DIRTY) ? true : false;
+ m_floppyDrive[unit].m_disk.m_byte = yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTE);
+ m_floppyDrive[unit].m_disk.m_nibbles = yamlLoadHelper.LoadUint(SS_YAML_KEY_NIBBLES);
+ m_floppyDrive[unit].m_disk.m_trackimagedata = yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK_IMAGE_DATA) ? true : false;
+ m_floppyDrive[unit].m_disk.m_trackimagedirty = yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK_IMAGE_DIRTY) ? true : false;
+
+ if (version >= 4)
+ {
+ m_floppyDrive[unit].m_disk.m_bitOffset = yamlLoadHelper.LoadUint(SS_YAML_KEY_BIT_OFFSET);
+ m_floppyDrive[unit].m_disk.m_bitCount = yamlLoadHelper.LoadUint(SS_YAML_KEY_BIT_COUNT);
+ m_floppyDrive[unit].m_disk.m_extraCycles = yamlLoadHelper.LoadDouble(SS_YAML_KEY_EXTRA_CYCLES);
+
+ if (m_floppyDrive[unit].m_disk.m_bitCount && (m_floppyDrive[unit].m_disk.m_bitOffset >= m_floppyDrive[unit].m_disk.m_bitCount))
+ throw std::string("Disk image: bitOffset >= bitCount");
+
+ if (ImageIsWOZ(m_floppyDrive[unit].m_disk.m_imagehandle))
+ UpdateBitStreamOffsets(m_floppyDrive[unit].m_disk); // overwrites m_byte, inits m_bitMask
+ }
- std::vector track(NIBBLES_PER_TRACK);
if (yamlLoadHelper.GetSubMap(SS_YAML_KEY_TRACK_IMAGE))
{
yamlLoadHelper.LoadMemory(&track[0], NIBBLES_PER_TRACK);
yamlLoadHelper.PopMap();
}
+ return bImageError;
+}
+
+bool Disk2InterfaceCard::LoadSnapshotDriveUnitv3(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track)
+{
+ _ASSERT(version <= 3);
+
+ std::string disk2UnitName = std::string(SS_YAML_KEY_DISK2UNIT) + (unit == DRIVE_1 ? std::string("0") : std::string("1"));
+ if (!yamlLoadHelper.GetSubMap(disk2UnitName))
+ throw std::string("Card: Expected key: ") + disk2UnitName;
+
+ bool bImageError = LoadSnapshotFloppy(yamlLoadHelper, unit, version, track);
+
+ yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK); // consume
+ m_floppyDrive[unit].m_phase = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASE);
+ m_floppyDrive[unit].m_phasePrecise = (float) m_floppyDrive[unit].m_phase;
+ m_floppyDrive[unit].m_spinning = yamlLoadHelper.LoadUint(SS_YAML_KEY_SPINNING);
+ m_floppyDrive[unit].m_writelight = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_LIGHT);
+
+ yamlLoadHelper.PopMap();
+
+ return bImageError;
+}
+
+bool Disk2InterfaceCard::LoadSnapshotDriveUnitv4(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track)
+{
+ _ASSERT(version >= 4);
+
+ std::string disk2UnitName = std::string(SS_YAML_KEY_DISK2UNIT) + (unit == DRIVE_1 ? std::string("0") : std::string("1"));
+ if (!yamlLoadHelper.GetSubMap(disk2UnitName))
+ throw std::string("Card: Expected key: ") + disk2UnitName;
+
+ if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_FLOPPY))
+ throw std::string("Card: Expected key: ") + SS_YAML_KEY_FLOPPY;
+
+ bool bImageError = LoadSnapshotFloppy(yamlLoadHelper, unit, version, track);
+
yamlLoadHelper.PopMap();
//
- if (!filename.empty() && !bImageError)
+ m_floppyDrive[unit].m_phase = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASE);
+ m_floppyDrive[unit].m_phasePrecise = yamlLoadHelper.LoadFloat(SS_YAML_KEY_PHASE_PRECISE);
+ m_floppyDrive[unit].m_headWindow = yamlLoadHelper.LoadUint(SS_YAML_KEY_HEAD_WINDOW) & 0xf;
+ m_floppyDrive[unit].m_lastStepperCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_LAST_STEPPER_CYCLE);
+ m_floppyDrive[unit].m_spinning = yamlLoadHelper.LoadUint(SS_YAML_KEY_SPINNING);
+ m_floppyDrive[unit].m_writelight = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_LIGHT);
+
+ yamlLoadHelper.PopMap();
+
+ return bImageError;
+}
+
+void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version)
+{
+ bool bImageError = false;
+ std::vector track(NIBBLES_PER_TRACK);
+
+ if (version <= 3)
+ bImageError = LoadSnapshotDriveUnitv3(yamlLoadHelper, unit, version, track);
+ else
+ bImageError = LoadSnapshotDriveUnitv4(yamlLoadHelper, unit, version, track);
+
+
+ if (!bImageError)
{
if ((m_floppyDrive[unit].m_disk.m_trackimage == NULL) && m_floppyDrive[unit].m_disk.m_nibbles)
AllocTrack(unit);
@@ -1391,9 +1807,9 @@ void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, U
if (bImageError)
{
- m_floppyDrive[unit].m_disk.m_trackimagedata = false;
- m_floppyDrive[unit].m_disk.m_trackimagedirty = false;
- m_floppyDrive[unit].m_disk.m_nibbles = 0;
+ m_floppyDrive[unit].m_disk.m_trackimagedata = false;
+ m_floppyDrive[unit].m_disk.m_trackimagedirty = false;
+ m_floppyDrive[unit].m_disk.m_nibbles = 0;
}
}
@@ -1405,8 +1821,8 @@ bool Disk2InterfaceCard::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT
if (version < 1 || version > kUNIT_VERSION)
throw std::string("Card: wrong version");
- m_phases = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASES);
- m_currDrive = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_DRIVE);
+ m_currDrive = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_DRIVE);
+ m_magnetStates = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASES);
(void) yamlLoadHelper.LoadBool(SS_YAML_KEY_DISK_ACCESSED); // deprecated - but retrieve the value to avoid the "State: Unknown key (Disk Accessed)" warning
m_enhanceDisk = yamlLoadHelper.LoadBool(SS_YAML_KEY_ENHANCE_DISK);
m_floppyLatch = yamlLoadHelper.LoadUint(SS_YAML_KEY_FLOPPY_LATCH);
@@ -1424,6 +1840,13 @@ bool Disk2InterfaceCard::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT
m_diskLastReadLatchCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_LAST_READ_LATCH_CYCLE);
}
+ if (version >= 4)
+ {
+ m_shiftReg = yamlLoadHelper.LoadUint(SS_YAML_KEY_LSS_SHIFT_REG) & 0xff;
+ m_latchDelay = yamlLoadHelper.LoadInt(SS_YAML_KEY_LSS_LATCH_DELAY);
+ m_resetSequencer = yamlLoadHelper.LoadBool(SS_YAML_KEY_LSS_RESET_SEQUENCER);
+ }
+
// Eject all disks first in case Drive-2 contains disk to be inserted into Drive-1
for (UINT i=0; i
ImageInfo* m_imagehandle; // Init'd by InsertDisk() -> ImageOpen()
bool m_bWriteProtected;
- int m_byte;
- int m_nibbles; // Init'd by ReadTrack() -> ImageReadTrack()
+ int m_byte; // byte offset
+ int m_nibbles; // # nibbles in track / Init'd by ReadTrack() -> ImageReadTrack()
+ UINT m_bitOffset; // bit offset
+ UINT m_bitCount; // # bits in track
+ BYTE m_bitMask;
+ double m_extraCycles;
LPBYTE m_trackimage;
bool m_trackimagedata;
bool m_trackimagedirty;
@@ -91,16 +99,20 @@ public:
void clear()
{
+ m_phasePrecise = 0;
m_phase = 0;
- m_track = 0;
+ m_lastStepperCycle = 0;
+ m_headWindow = 0;
m_spinning = 0;
m_writelight = 0;
m_disk.clear();
}
public:
- int m_phase;
- int m_track;
+ float m_phasePrecise; // Phase precise to half a phase (aka quarter track)
+ int m_phase; // Integral phase number
+ unsigned __int64 m_lastStepperCycle;
+ BYTE m_headWindow;
DWORD m_spinning;
DWORD m_writelight;
FloppyDisk m_disk;
@@ -132,10 +144,14 @@ public:
bool GetProtect(const int drive);
void SetProtect(const int drive, const bool bWriteProtect);
int GetCurrentDrive(void);
- int GetCurrentTrack();
- int GetTrack(const int drive);
- int GetCurrentPhase(void);
+ int GetCurrentTrack(void);
+ float GetCurrentPhase(void);
int GetCurrentOffset(void);
+ BYTE GetCurrentLSSBitMask(void);
+ double GetCurrentExtraCycles(void);
+ int GetTrack(const int drive);
+ std::string GetCurrentTrackString(void);
+ std::string GetCurrentPhaseString(void);
LPCTSTR GetCurrentState(void);
bool UserSelectNewDiskImage(const int drive, LPCSTR pszFilename="");
void UpdateDriveState(DWORD cycles);
@@ -158,21 +174,32 @@ public:
static BYTE __stdcall IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles);
private:
- void CheckSpinning(const ULONG nExecutedCycles);
+ void ResetSwitches(void);
+ void CheckSpinning(const ULONG uExecutedCycles);
Disk_Status_e GetDriveLightStatus(const int drive);
bool IsDriveValid(const int drive);
void AllocTrack(const int drive);
- void ReadTrack(const int drive);
+ void ReadTrack(const int drive, ULONG uExecutedCycles);
void RemoveDisk(const int drive);
void WriteTrack(const int drive);
LPCTSTR DiskGetFullPathName(const int drive);
- void SaveSnapshotDisk2Unit(YamlSaveHelper& yamlSaveHelper, UINT unit);
- void LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit);
+ void ResetLogicStateSequencer(void);
+ void UpdateBitStreamPositionAndDiskCycle(const ULONG uExecutedCycles);
+ UINT GetBitCellDelta(const BYTE optimalBitTiming);
+ void UpdateBitStreamPosition(FloppyDisk& floppy, const ULONG bitCellDelta);
+ void UpdateBitStreamOffsets(FloppyDisk& floppy);
+ void SaveSnapshotFloppy(YamlSaveHelper& yamlSaveHelper, UINT unit);
+ void SaveSnapshotDriveUnit(YamlSaveHelper& yamlSaveHelper, UINT unit);
+ bool LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track);
+ bool LoadSnapshotDriveUnitv3(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track);
+ bool LoadSnapshotDriveUnitv4(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track);
+ void LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version);
void __stdcall ControlStepper(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles);
void __stdcall ControlMotor(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles);
void __stdcall Enable(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles);
- void __stdcall ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles);
+ void __stdcall ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
+ void __stdcall ReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles);
void __stdcall LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG);
void __stdcall SetReadMode(WORD, WORD, BYTE, BYTE, ULONG);
void __stdcall SetWriteMode(WORD, WORD, BYTE, BYTE, ULONG uExecutedCycles);
@@ -189,7 +216,11 @@ private:
BOOL m_floppyMotorOn;
BOOL m_floppyLoadMode; // for efficiency this is not used; it's extremely unlikely to affect emulation (nickw)
BOOL m_floppyWriteMode;
- WORD m_phases; // state bits for stepper magnet phases 0 - 3
+
+ // Although the magnets are a property of the drive, their state is a property of the controller card,
+ // since the magnets will only be on for whichever of the 2 drives is currently selected.
+ WORD m_magnetStates; // state bits for stepper motor magnet states (phases 0 - 3)
+
bool m_saveDiskImage;
UINT m_slot;
unsigned __int64 m_diskLastCycle;
@@ -197,8 +228,14 @@ private:
FormatTrack m_formatTrack;
bool m_enhanceDisk;
- static const UINT SPINNING_CYCLES = 20000*64; // 1280000 cycles = 1.25s
- static const UINT WRITELIGHT_CYCLES = 20000*64; // 1280000 cycles = 1.25s
+ static const UINT SPINNING_CYCLES = 1000*1000; // 1M cycles = ~1.000s
+ static const UINT WRITELIGHT_CYCLES = 1000*1000; // 1M cycles = ~1.000s
+
+ // Logic State Sequencer (for WOZ):
+ BYTE m_shiftReg;
+ int m_latchDelay;
+ bool m_resetSequencer;
+ UINT m_dbgLatchDelayedCnt;
// Debug:
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
diff --git a/source/DiskDefs.h b/source/DiskDefs.h
index 90a2e17b..1b809218 100644
--- a/source/DiskDefs.h
+++ b/source/DiskDefs.h
@@ -1,5 +1,7 @@
#pragma once
-#define NIBBLES_PER_TRACK 0x1A00
+#define NIBBLES_PER_TRACK_NIB 0x1A00
+#define NIBBLES_PER_TRACK_WOZ2 0x1A18 // 6680
+#define NIBBLES_PER_TRACK NIBBLES_PER_TRACK_WOZ2 // MAX(NIBBLES_PER_TRACK_NIB, NIBBLES_PER_TRACK_WOZ2)
const UINT NUM_SECTORS = 16;
diff --git a/source/DiskFormatTrack.cpp b/source/DiskFormatTrack.cpp
index 201ac392..229fedee 100644
--- a/source/DiskFormatTrack.cpp
+++ b/source/DiskFormatTrack.cpp
@@ -264,17 +264,18 @@ void FormatTrack::DecodeLatchNibble(BYTE floppylatch, bool bIsWrite, bool bIsSyn
m_VolTrkSecChk[i] = ((m_VolTrkSecChk4and4[i*2] & 0x55) << 1) | (m_VolTrkSecChk4and4[i*2+1] & 0x55);
#if LOG_DISK_NIBBLES_READ
+ const bool chk = (m_VolTrkSecChk[0] ^ m_VolTrkSecChk[1] ^ m_VolTrkSecChk[2] ^ m_VolTrkSecChk[3]) == 0;
if (!bIsWrite)
{
BYTE addrPrologue = m_bAddressPrologueIsDOS3_2 ? (BYTE)kADDR_PROLOGUE_DOS3_2 : (BYTE)kADDR_PROLOGUE_DOS3_3;
- LOG_DISK("read D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3]);
+ LOG_DISK("read D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X %s\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3], chk?"":"(bad)");
}
#endif
#if LOG_DISK_NIBBLES_WRITE
if (bIsWrite)
{
BYTE addrPrologue = m_bAddressPrologueIsDOS3_2 ? (BYTE)kADDR_PROLOGUE_DOS3_2 : (BYTE)kADDR_PROLOGUE_DOS3_3;
- LOG_DISK("write D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3]);
+ LOG_DISK("write D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X %s\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3], chk?"":"(bad)");
}
#endif
diff --git a/source/DiskImage.cpp b/source/DiskImage.cpp
index 0d2d300e..8fb3beac 100644
--- a/source/DiskImage.cpp
+++ b/source/DiskImage.cpp
@@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "StdAfx.h"
+#include "Common.h"
#include "DiskImage.h"
#include "DiskImageHelper.h"
@@ -152,19 +153,21 @@ void ImageInitialize(void)
//===========================================================================
void ImageReadTrack( ImageInfo* const pImageInfo,
- const int nTrack,
- const int nQuarterTrack,
+ float phase, // phase [0..79] +/- 0.5
LPBYTE pTrackImageBuffer,
int* pNibbles,
+ UINT* pBitCount,
bool enhanceDisk)
{
- _ASSERT(nTrack >= 0);
- if (nTrack < 0)
- return;
+ _ASSERT(phase >= 0);
+ if (phase < 0)
+ phase = 0;
- if (pImageInfo->pImageType->AllowRW() && pImageInfo->ValidTrack[nTrack])
+ const UINT track = pImageInfo->pImageType->PhaseToTrack(phase);
+
+ if (pImageInfo->pImageType->AllowRW() && pImageInfo->ValidTrack[track])
{
- pImageInfo->pImageType->Read(pImageInfo, nTrack, nQuarterTrack, pTrackImageBuffer, pNibbles, enhanceDisk);
+ pImageInfo->pImageType->Read(pImageInfo, phase, pTrackImageBuffer, pNibbles, pBitCount, enhanceDisk);
}
else
{
@@ -176,19 +179,20 @@ void ImageReadTrack( ImageInfo* const pImageInfo,
//===========================================================================
void ImageWriteTrack( ImageInfo* const pImageInfo,
- const int nTrack,
- const int nQuarterTrack,
- LPBYTE pTrackImage,
+ float phase, // phase [0..79] +/- 0.5
+ LPBYTE pTrackImageBuffer,
const int nNibbles)
{
- _ASSERT(nTrack >= 0);
- if (nTrack < 0)
- return;
+ _ASSERT(phase >= 0);
+ if (phase < 0)
+ phase = 0;
+
+ const UINT track = pImageInfo->pImageType->PhaseToTrack(phase);
if (pImageInfo->pImageType->AllowRW() && !pImageInfo->bWriteProtected)
{
- pImageInfo->pImageType->Write(pImageInfo, nTrack, nQuarterTrack, pTrackImage, nNibbles);
- pImageInfo->ValidTrack[nTrack] = 1;
+ pImageInfo->pImageType->Write(pImageInfo, phase, pTrackImageBuffer, nNibbles);
+ pImageInfo->ValidTrack[track] = 1;
}
}
@@ -220,7 +224,7 @@ bool ImageWriteBlock( ImageInfo* const pImageInfo,
//===========================================================================
-int ImageGetNumTracks(ImageInfo* const pImageInfo)
+UINT ImageGetNumTracks(ImageInfo* const pImageInfo)
{
return pImageInfo ? pImageInfo->uNumTracks : 0;
}
@@ -246,6 +250,33 @@ UINT ImageGetImageSize(ImageInfo* const pImageInfo)
return pImageInfo ? pImageInfo->uImageSize : 0;
}
+bool ImageIsWOZ(ImageInfo* const pImageInfo)
+{
+ return pImageInfo ? (pImageInfo->pImageType->GetType() == eImageWOZ1 || pImageInfo->pImageType->GetType() == eImageWOZ2) : false;
+}
+
+BYTE ImageGetOptimalBitTiming(ImageInfo* const pImageInfo)
+{
+ return pImageInfo ? pImageInfo->optimalBitTiming : 32;
+}
+
+UINT ImagePhaseToTrack(ImageInfo* const pImageInfo, const float phase, const bool limit/*=true*/)
+{
+ if (!pImageInfo)
+ return 0;
+
+ UINT track = pImageInfo->pImageType->PhaseToTrack(phase);
+
+ if (limit)
+ {
+ const UINT numTracksInImage = ImageGetNumTracks(pImageInfo);
+ track = (numTracksInImage == 0) ? 0
+ : MIN(numTracksInImage - 1, track);
+ }
+
+ return track;
+}
+
void GetImageTitle(LPCTSTR pPathname, TCHAR* pImageName, TCHAR* pFullName)
{
TCHAR imagetitle[ MAX_DISK_FULL_NAME+1 ];
diff --git a/source/DiskImage.h b/source/DiskImage.h
index 7a8277c4..14489677 100644
--- a/source/DiskImage.h
+++ b/source/DiskImage.h
@@ -71,15 +71,18 @@ BOOL ImageBoot(ImageInfo* const pImageInfo);
void ImageDestroy(void);
void ImageInitialize(void);
-void ImageReadTrack(ImageInfo* const pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk);
-void ImageWriteTrack(ImageInfo* const pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles);
+void ImageReadTrack(ImageInfo* const pImageInfo, float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk);
+void ImageWriteTrack(ImageInfo* const pImageInfo, float phase, LPBYTE pTrackImageBuffer, int nNibbles);
bool ImageReadBlock(ImageInfo* const pImageInfo, UINT nBlock, LPBYTE pBlockBuffer);
bool ImageWriteBlock(ImageInfo* const pImageInfo, UINT nBlock, LPBYTE pBlockBuffer);
-int ImageGetNumTracks(ImageInfo* const pImageInfo);
+UINT ImageGetNumTracks(ImageInfo* const pImageInfo);
bool ImageIsWriteProtected(ImageInfo* const pImageInfo);
bool ImageIsMultiFileZip(ImageInfo* const pImageInfo);
const char* ImageGetPathname(ImageInfo* const pImageInfo);
UINT ImageGetImageSize(ImageInfo* const pImageInfo);
+bool ImageIsWOZ(ImageInfo* const pImageInfo);
+BYTE ImageGetOptimalBitTiming(ImageInfo* const pImageInfo);
+UINT ImagePhaseToTrack(ImageInfo* const pImageInfo, const float phase, const bool limit=true);
void GetImageTitle(LPCTSTR pPathname, TCHAR* pImageName, TCHAR* pFullName);
diff --git a/source/DiskImageHelper.cpp b/source/DiskImageHelper.cpp
index e84b2faf..fefc440a 100644
--- a/source/DiskImageHelper.cpp
+++ b/source/DiskImageHelper.cpp
@@ -70,8 +70,8 @@ LPBYTE CImageBase::ms_pWorkBuffer = NULL;
bool CImageBase::ReadTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize)
{
- const long Offset = pImageInfo->uOffset + nTrack * uTrackSize;
- memcpy(pTrackBuffer, &pImageInfo->pImageBuffer[Offset], uTrackSize);
+ const long offset = pImageInfo->uOffset + nTrack * uTrackSize;
+ memcpy(pTrackBuffer, &pImageInfo->pImageBuffer[offset], uTrackSize);
return true;
}
@@ -630,18 +630,20 @@ public:
return ePossibleMatch;
}
- virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk)
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
{
- ReadTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
- *pNibbles = NibblizeTrack(pTrackImageBuffer, eDOSOrder, nTrack);
+ const UINT track = PhaseToTrack(phase);
+ ReadTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
+ *pNibbles = NibblizeTrack(pTrackImageBuffer, eDOSOrder, track);
if (!enhanceDisk)
- SkewTrack(nTrack, *pNibbles, pTrackImageBuffer);
+ SkewTrack(track, *pNibbles, pTrackImageBuffer);
}
- virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles)
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
{
- DenibblizeTrack(pTrackImage, eDOSOrder, nNibbles);
- WriteTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
+ const UINT track = PhaseToTrack(phase);
+ DenibblizeTrack(pTrackImageBuffer, eDOSOrder, nNibbles);
+ WriteTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
}
virtual bool AllowCreate(void) { return true; }
@@ -696,23 +698,25 @@ public:
return ePossibleMatch;
}
- virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk)
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
{
- ReadTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
- *pNibbles = NibblizeTrack(pTrackImageBuffer, eProDOSOrder, nTrack);
+ const UINT track = PhaseToTrack(phase);
+ ReadTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
+ *pNibbles = NibblizeTrack(pTrackImageBuffer, eProDOSOrder, track);
if (!enhanceDisk)
- SkewTrack(nTrack, *pNibbles, pTrackImageBuffer);
+ SkewTrack(track, *pNibbles, pTrackImageBuffer);
}
- virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles)
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
{
- DenibblizeTrack(pTrackImage, eProDOSOrder, nNibbles);
- WriteTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
+ const UINT track = PhaseToTrack(phase);
+ DenibblizeTrack(pTrackImageBuffer, eProDOSOrder, nNibbles);
+ WriteTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
}
virtual eImageType GetType(void) { return eImagePO; }
virtual const char* GetCreateExtensions(void) { return ".po"; }
- virtual const char* GetRejectExtensions(void) { return ".do;.iie;.nib;.prg"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.iie;.nib;.prg;.woz"; }
};
//-------------------------------------
@@ -724,7 +728,7 @@ public:
CNib1Image(void) {}
virtual ~CNib1Image(void) {}
- static const UINT NIB1_TRACK_SIZE = NIBBLES_PER_TRACK;
+ static const UINT NIB1_TRACK_SIZE = NIBBLES_PER_TRACK_NIB;
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
{
@@ -735,16 +739,18 @@ public:
return eMatch;
}
- virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk)
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
{
- ReadTrack(pImageInfo, nTrack, pTrackImageBuffer, NIB1_TRACK_SIZE);
+ const UINT track = PhaseToTrack(phase);
+ ReadTrack(pImageInfo, track, pTrackImageBuffer, NIB1_TRACK_SIZE);
*pNibbles = NIB1_TRACK_SIZE;
}
- virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles)
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
{
_ASSERT(nNibbles == NIB1_TRACK_SIZE); // Must be true - as nNibbles gets init'd by ImageReadTrace()
- WriteTrack(pImageInfo, nTrack, pTrackImage, nNibbles);
+ const UINT track = PhaseToTrack(phase);
+ WriteTrack(pImageInfo, track, pTrackImageBuffer, nNibbles);
}
virtual bool AllowCreate(void) { return true; }
@@ -752,7 +758,7 @@ public:
virtual eImageType GetType(void) { return eImageNIB1; }
virtual const char* GetCreateExtensions(void) { return ".nib"; }
- virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.woz"; }
};
//-------------------------------------
@@ -775,21 +781,23 @@ public:
return eMatch;
}
- virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk)
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
{
- ReadTrack(pImageInfo, nTrack, pTrackImageBuffer, NIB2_TRACK_SIZE);
+ const UINT track = PhaseToTrack(phase);
+ ReadTrack(pImageInfo, track, pTrackImageBuffer, NIB2_TRACK_SIZE);
*pNibbles = NIB2_TRACK_SIZE;
}
- virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles)
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
{
_ASSERT(nNibbles == NIB2_TRACK_SIZE); // Must be true - as nNibbles gets init'd by ImageReadTrace()
- WriteTrack(pImageInfo, nTrack, pTrackImage, nNibbles);
+ const UINT track = PhaseToTrack(phase);
+ WriteTrack(pImageInfo, track, pTrackImageBuffer, nNibbles);
}
virtual eImageType GetType(void) { return eImageNIB2; }
virtual const char* GetCreateExtensions(void) { return ".nb2"; }
- virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.2mg;.2img"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.woz;.2mg;.2img"; }
};
//-------------------------------------
@@ -851,8 +859,10 @@ public:
return eMatch;
}
- virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk)
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
{
+ UINT track = PhaseToTrack(phase);
+
// IF WE HAVEN'T ALREADY DONE SO, READ THE IMAGE FILE HEADER
if (!m_pHeader)
{
@@ -872,19 +882,19 @@ public:
if (*(m_pHeader+13) <= 2)
{
ConvertSectorOrder(m_pHeader+14);
- SetFilePointer(pImageInfo->hFile, nTrack*TRACK_DENIBBLIZED_SIZE+30, NULL, FILE_BEGIN);
+ SetFilePointer(pImageInfo->hFile, track*TRACK_DENIBBLIZED_SIZE+30, NULL, FILE_BEGIN);
ZeroMemory(ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
DWORD bytesread;
ReadFile(pImageInfo->hFile, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE, &bytesread, NULL);
- *pNibbles = NibblizeTrack(pTrackImageBuffer, eSIMSYSTEMOrder, nTrack);
+ *pNibbles = NibblizeTrack(pTrackImageBuffer, eSIMSYSTEMOrder, track);
}
// OTHERWISE, IF THIS IMAGE CONTAINS NIBBLE INFORMATION, READ IT DIRECTLY INTO THE TRACK BUFFER
else
{
- *pNibbles = *(LPWORD)(m_pHeader+nTrack*2+14);
+ *pNibbles = *(LPWORD)(m_pHeader+track*2+14);
LONG Offset = 88;
- while (nTrack--)
- Offset += *(LPWORD)(m_pHeader+nTrack*2+14);
+ while (track--)
+ Offset += *(LPWORD)(m_pHeader+track*2+14);
SetFilePointer(pImageInfo->hFile, Offset, NULL,FILE_BEGIN);
ZeroMemory(pTrackImageBuffer, *pNibbles);
DWORD dwBytesRead;
@@ -892,14 +902,14 @@ public:
}
}
- virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles)
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
{
// note: unimplemented
}
virtual eImageType GetType(void) { return eImageIIE; }
virtual const char* GetCreateExtensions(void) { return ".iie"; }
- virtual const char* GetRejectExtensions(void) { return ".do.;.nib;.po;.prg;.2mg;.2img"; }
+ virtual const char* GetRejectExtensions(void) { return ".do.;.nib;.po;.prg;.woz;.2mg;.2img"; }
private:
void ConvertSectorOrder(LPBYTE sourceorder)
@@ -973,7 +983,7 @@ public:
virtual eImageType GetType(void) { return eImageAPL; }
virtual const char* GetCreateExtensions(void) { return ".apl"; }
- virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.2mg;.2img"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.woz;.2mg;.2img"; }
};
//-------------------------------------
@@ -1024,7 +1034,154 @@ public:
virtual eImageType GetType(void) { return eImagePRG; }
virtual const char* GetCreateExtensions(void) { return ".prg"; }
- virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.2mg;.2img"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.woz;.2mg;.2img"; }
+};
+
+//-------------------------------------
+
+class CWOZEmptyTrack
+{
+public:
+ CWOZEmptyTrack(void)
+ {
+ m_pWOZEmptyTrack = new BYTE[CWOZHelper::EMPTY_TRACK_SIZE];
+
+ srand(1); // Use a fixed seed for determinism
+ for (UINT i = 0; i < CWOZHelper::EMPTY_TRACK_SIZE; i++)
+ {
+ BYTE n = 0;
+ for (UINT j = 0; j < 8; j++)
+ {
+ if (rand() < ((RAND_MAX * 3) / 10)) // ~30% of buffer are 1 bits
+ n |= 1 << j;
+ }
+ m_pWOZEmptyTrack[i] = n;
+ }
+ }
+ virtual ~CWOZEmptyTrack(void) { delete m_pWOZEmptyTrack; }
+
+ void ReadEmptyTrack(LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount)
+ {
+ memcpy(pTrackImageBuffer, m_pWOZEmptyTrack, CWOZHelper::EMPTY_TRACK_SIZE);
+ *pNibbles = CWOZHelper::EMPTY_TRACK_SIZE;
+ *pBitCount = CWOZHelper::EMPTY_TRACK_SIZE * 8;
+ return;
+ }
+
+private:
+ BYTE* m_pWOZEmptyTrack;
+};
+
+//-------------------------------------
+
+class CWOZ1Image : public CImageBase, private CWOZEmptyTrack
+{
+public:
+ CWOZ1Image(void) {}
+ virtual ~CWOZ1Image(void) {}
+
+ virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
+ {
+ CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage;
+
+ if (pWozHdr->id1 != CWOZHelper::ID1_WOZ1 || pWozHdr->id2 != CWOZHelper::ID2)
+ return eMismatch;
+
+ if (pWozHdr->crc32)
+ {
+ // TODO: check crc
+ }
+
+ m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25;
+ return eMatch;
+ }
+
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
+ {
+ BYTE*& pTrackMap = pImageInfo->pTrackMap;
+
+ const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)];
+ if (trackFromTMAP == 0xFF)
+ return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount);
+
+ ReadTrack(pImageInfo, trackFromTMAP, pTrackImageBuffer, CWOZHelper::WOZ1_TRACK_SIZE);
+ CWOZHelper::TRKv1* pTRK = (CWOZHelper::TRKv1*) &pTrackImageBuffer[CWOZHelper::WOZ1_TRK_OFFSET];
+ *pNibbles = pTRK->bytesUsed;
+ *pBitCount = pTRK->bitCount;
+ }
+
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
+ {
+ // TODO
+ _ASSERT(0);
+ }
+
+ // TODO: Uncomment and fix-up if we want to allow .woz image creation (eg. for INIT or FORMAT)
+// virtual bool AllowCreate(void) { return true; }
+// virtual UINT GetImageSizeForCreate(void) { return 0; }//TODO
+
+ virtual eImageType GetType(void) { return eImageWOZ1; }
+ virtual const char* GetCreateExtensions(void) { return ".woz"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.nib;.iie;.po;.prg"; }
+};
+
+//-------------------------------------
+
+class CWOZ2Image : public CImageBase, private CWOZEmptyTrack
+{
+public:
+ CWOZ2Image(void) {}
+ virtual ~CWOZ2Image(void) {}
+
+ virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
+ {
+ CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage;
+
+ if (pWozHdr->id1 != CWOZHelper::ID1_WOZ2 || pWozHdr->id2 != CWOZHelper::ID2)
+ return eMismatch;
+
+ if (pWozHdr->crc32)
+ {
+ // TODO: check crc
+ }
+
+ m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25;
+ return eMatch;
+ }
+
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
+ {
+ BYTE*& pTrackMap = pImageInfo->pTrackMap;
+
+ const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)];
+ if (trackFromTMAP == 0xFF)
+ return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount);
+
+ CWOZHelper::TRKv2* pTRKS = (CWOZHelper::TRKv2*) &pImageInfo->pImageBuffer[pImageInfo->uOffset];
+ CWOZHelper::TRKv2* pTRK = &pTRKS[trackFromTMAP];
+ *pBitCount = pTRK->bitCount;
+ *pNibbles = (pTRK->bitCount+7) / 8;
+
+ _ASSERT(*pNibbles <= NIBBLES_PER_TRACK_WOZ2);
+ if (*pNibbles > NIBBLES_PER_TRACK_WOZ2)
+ return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); // TODO: Enlarge track buffer, but for now just return an empty track
+
+ memcpy(pTrackImageBuffer, &pImageInfo->pImageBuffer[pTRK->startBlock*512], *pNibbles);
+ }
+
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
+ {
+ // TODO
+ _ASSERT(0);
+ }
+
+ // TODO: Uncomment and fix-up if we want to allow .woz image creation (eg. for INIT or FORMAT)
+ // virtual bool AllowCreate(void) { return true; }
+ // virtual UINT GetImageSizeForCreate(void) { return 0; }//TODO
+
+ virtual eImageType GetType(void) { return eImageWOZ2; }
+ virtual const char* GetCreateExtensions(void) { return ".woz"; }
+ virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.nib;.iie;.po;.prg"; }
};
//-----------------------------------------------------------------------------
@@ -1048,6 +1205,8 @@ eDetectResult CMacBinaryHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DW
return eMismatch;
}
+//-----------------------------------------------------------------------------
+
eDetectResult C2IMGHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& dwOffset)
{
Header2IMG* pHdr = (Header2IMG*) pImage;
@@ -1099,7 +1258,7 @@ eDetectResult C2IMGHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD&
break;
case e2IMGFormatNIBData:
{
- if (pHdr->DiskDataLength != TRACKS_STANDARD*NIBBLES_PER_TRACK)
+ if (pHdr->DiskDataLength != TRACKS_STANDARD*NIBBLES_PER_TRACK_NIB)
return eMismatch;
}
break;
@@ -1126,11 +1285,59 @@ bool C2IMGHelper::IsLocked(void)
//-----------------------------------------------------------------------------
+// Pre: already matched the WOZ header
+eDetectResult CWOZHelper::ProcessChunks(const LPBYTE pImage, const DWORD dwImageSize, DWORD& dwOffset, BYTE*& pTrackMap)
+{
+ UINT32* pImage32 = (uint32_t*) (pImage + sizeof(WOZHeader));
+ UINT32 imageSizeRemaining = dwImageSize - sizeof(WOZHeader);
+
+ while(imageSizeRemaining > 8)
+ {
+ UINT32 chunkId = *pImage32++;
+ UINT32 chunkSize = *pImage32++;
+ imageSizeRemaining -= 8;
+
+ switch(chunkId)
+ {
+ case INFO_CHUNK_ID:
+ m_pInfo = (InfoChunkv2*)(pImage32-2);
+ if (m_pInfo->v1.version > InfoChunk::maxSupportedVersion)
+ return eMismatch;
+ if (m_pInfo->v1.diskType != InfoChunk::diskType5_25)
+ return eMismatch;
+ break;
+ case TMAP_CHUNK_ID:
+ pTrackMap = (uint8_t*)pImage32;
+ break;
+ case TRKS_CHUNK_ID:
+ dwOffset = dwImageSize - imageSizeRemaining; // offset into image of track data
+ break;
+ case WRIT_CHUNK_ID: // WOZ v2 (optional)
+ break;
+ case META_CHUNK_ID: // (optional)
+ break;
+ default: // no idea what this chunk is, so skip it
+ _ASSERT(0);
+ break;
+ }
+
+ pImage32 = (UINT32*) ((BYTE*)pImage32 + chunkSize);
+ imageSizeRemaining -= chunkSize;
+ _ASSERT(imageSizeRemaining >= 0);
+ if (imageSizeRemaining < 0)
+ return eMismatch;
+ }
+
+ return eMatch;
+}
+
+//-----------------------------------------------------------------------------
+
// NB. Of the 6 cases (floppy/harddisk x gzip/zip/normal) only harddisk-normal isn't read entirely to memory
// - harddisk-normal-create also doesn't create a max size image-buffer
// DETERMINE THE FILE'S EXTENSION AND CONVERT IT TO LOWERCASE
-void GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize)
+void CImageHelperBase::GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize)
{
LPCTSTR pImageFileExt = pszImageFilename;
@@ -1146,7 +1353,7 @@ void GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSiz
CharLowerBuff(pszExt, _tcslen(pszExt));
}
-void GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize)
+void CImageHelperBase::GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize)
{
TCHAR szFilename[MAX_PATH];
_tcsncpy(szFilename, pszImageFilename, MAX_PATH);
@@ -1187,7 +1394,7 @@ ImageError_e CImageHelperBase::CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo
DWORD dwSize = nLen;
DWORD dwOffset = 0;
- CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, &pImageInfo->bWriteProtected);
+ CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming);
if (!pImageType)
return eIMAGE_ERROR_UNSUPPORTED;
@@ -1196,11 +1403,7 @@ ImageError_e CImageHelperBase::CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo
if (Type == eImageAPL || Type == eImageIIE || Type == eImagePRG)
return eIMAGE_ERROR_UNSUPPORTED;
- pImageInfo->FileType = eFileGZip;
- pImageInfo->uOffset = dwOffset;
- pImageInfo->pImageType = pImageType;
- pImageInfo->uImageSize = dwSize;
-
+ SetImageInfo(pImageInfo, eFileGZip, dwOffset, pImageType, dwSize);
return eIMAGE_ERROR_NONE;
}
@@ -1283,7 +1486,7 @@ ImageError_e CImageHelperBase::CheckZipFile(LPCTSTR pszImageFilename, ImageInfo*
DWORD dwSize = nLen;
DWORD dwOffset = 0;
- CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, &pImageInfo->bWriteProtected);
+ CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming);
if (!pImageType)
{
@@ -1300,11 +1503,7 @@ ImageError_e CImageHelperBase::CheckZipFile(LPCTSTR pszImageFilename, ImageInfo*
if (global_info.number_entry > 1)
pImageInfo->bWriteProtected = 1; // Zip archives with multiple files are read-only (for now)
- pImageInfo->FileType = eFileZip;
- pImageInfo->uOffset = dwOffset;
- pImageInfo->pImageType = pImageType;
- pImageInfo->uImageSize = dwSize;
-
+ SetImageInfo(pImageInfo, eFileZip, dwOffset, pImageType, dwSize);
return eIMAGE_ERROR_NONE;
}
@@ -1384,7 +1583,7 @@ ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageIn
return eIMAGE_ERROR_BAD_SIZE;
}
- pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, &pImageInfo->bWriteProtected);
+ pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming);
if (bTempDetectBuffer)
{
delete [] pImageInfo->pImageBuffer;
@@ -1437,12 +1636,18 @@ ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageIn
return eIMAGE_ERROR_UNSUPPORTED;
}
- pImageInfo->FileType = eFileNormal;
+ SetImageInfo(pImageInfo, eFileNormal, dwOffset, pImageType, dwSize);
+ return eIMAGE_ERROR_NONE;
+}
+
+//-------------------------------------
+
+void CImageHelperBase::SetImageInfo(ImageInfo* pImageInfo, FileType_e eFileGZip, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize)
+{
+ pImageInfo->FileType = eFileGZip;
pImageInfo->uOffset = dwOffset;
pImageInfo->pImageType = pImageType;
pImageInfo->uImageSize = dwSize;
-
- return eIMAGE_ERROR_NONE;
}
//-------------------------------------
@@ -1517,54 +1722,68 @@ CDiskImageHelper::CDiskImageHelper(void) :
m_vecImageTypes.push_back( new CIIeImage );
m_vecImageTypes.push_back( new CAplImage );
m_vecImageTypes.push_back( new CPrgImage );
+ m_vecImageTypes.push_back( new CWOZ1Image );
+ m_vecImageTypes.push_back( new CWOZ2Image );
}
-CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool* pWriteProtected_)
+CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming)
{
dwOffset = 0;
m_MacBinaryHelper.DetectHdr(pImage, dwSize, dwOffset);
m_Result2IMG = m_2IMGHelper.DetectHdr(pImage, dwSize, dwOffset);
// CALL THE DETECTION FUNCTIONS IN ORDER, LOOKING FOR A MATCH
- eImageType ImageType = eImageUNKNOWN;
- eImageType PossibleType = eImageUNKNOWN;
+ eImageType imageType = eImageUNKNOWN;
+ eImageType possibleType = eImageUNKNOWN;
if (m_Result2IMG == eMatch)
{
if (m_2IMGHelper.IsImageFormatDOS33())
- ImageType = eImageDO;
+ imageType = eImageDO;
else if (m_2IMGHelper.IsImageFormatProDOS())
- ImageType = eImagePO;
+ imageType = eImagePO;
- if (ImageType != eImageUNKNOWN)
+ if (imageType != eImageUNKNOWN)
{
- CImageBase* pImageType = GetImage(ImageType);
+ CImageBase* pImageType = GetImage(imageType);
if (!pImageType || !pImageType->IsValidImageSize(dwSize))
- ImageType = eImageUNKNOWN;
+ imageType = eImageUNKNOWN;
}
}
- if (ImageType == eImageUNKNOWN)
+ if (imageType == eImageUNKNOWN)
{
- for (UINT uLoop=0; uLoop < GetNumImages() && ImageType == eImageUNKNOWN; uLoop++)
+ for (UINT uLoop=0; uLoop < GetNumImages() && imageType == eImageUNKNOWN; uLoop++)
{
if (*pszExt && _tcsstr(GetImage(uLoop)->GetRejectExtensions(), pszExt))
continue;
eDetectResult Result = GetImage(uLoop)->Detect(pImage, dwSize, pszExt);
if (Result == eMatch)
- ImageType = GetImage(uLoop)->GetType();
- else if ((Result == ePossibleMatch) && (PossibleType == eImageUNKNOWN))
- PossibleType = GetImage(uLoop)->GetType();
+ imageType = GetImage(uLoop)->GetType();
+ else if ((Result == ePossibleMatch) && (possibleType == eImageUNKNOWN))
+ possibleType = GetImage(uLoop)->GetType();
}
}
- if (ImageType == eImageUNKNOWN)
- ImageType = PossibleType;
+ if (imageType == eImageUNKNOWN)
+ imageType = possibleType;
- CImageBase* pImageType = GetImage(ImageType);
+ CImageBase* pImageType = GetImage(imageType);
+ if (!pImageType)
+ return NULL;
- if (pImageType)
+ if (imageType == eImageWOZ1 || imageType == eImageWOZ2)
+ {
+ if (m_WOZHelper.ProcessChunks(pImage, dwSize, dwOffset, pTrackMap) != eMatch)
+ return NULL;
+
+// if (m_WOZHelper.IsWriteProtected() && !writeProtected) // Force write-protected until writing is supported
+ writeProtected = true;
+
+ optimalBitTiming = m_WOZHelper.GetOptimalBitTiming();
+ }
+ else
{
if (pImageType->AllowRW())
{
@@ -1578,8 +1797,8 @@ CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* p
{
pImageType->SetVolumeNumber( m_2IMGHelper.GetVolumeNumber() );
- if (m_2IMGHelper.IsLocked() && !*pWriteProtected_)
- *pWriteProtected_ = 1;
+ if (m_2IMGHelper.IsLocked() && !writeProtected)
+ writeProtected = true;
}
else
{
@@ -1634,7 +1853,7 @@ CHardDiskImageHelper::CHardDiskImageHelper(void) :
m_vecImageTypes.push_back( new CHDVImage );
}
-CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool* pWriteProtected_)
+CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming)
{
dwOffset = 0;
m_Result2IMG = m_2IMGHelper.DetectHdr(pImage, dwSize, dwOffset);
@@ -1659,11 +1878,14 @@ CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHA
{
if (m_Result2IMG == eMatch)
{
- if (m_2IMGHelper.IsLocked() && !*pWriteProtected_)
- *pWriteProtected_ = 1;
+ if (m_2IMGHelper.IsLocked() && !writeProtected)
+ writeProtected = true;
}
}
+ pTrackMap = 0; // TODO: WOZ
+ optimalBitTiming = 0; // TODO: WOZ
+
return pImageType;
}
diff --git a/source/DiskImageHelper.h b/source/DiskImageHelper.h
index a6e8b773..cf64bdac 100644
--- a/source/DiskImageHelper.h
+++ b/source/DiskImageHelper.h
@@ -10,7 +10,7 @@
#define ZIP_SUFFIX_LEN (sizeof(ZIP_SUFFIX)-1)
-enum eImageType {eImageUNKNOWN, eImageDO, eImagePO, eImageNIB1, eImageNIB2, eImageHDV, eImageIIE, eImageAPL, eImagePRG};
+enum eImageType {eImageUNKNOWN, eImageDO, eImagePO, eImageNIB1, eImageNIB2, eImageHDV, eImageIIE, eImageAPL, eImagePRG, eImageWOZ1, eImageWOZ2};
enum eDetectResult {eMismatch, ePossibleMatch, eMatch};
class CImageBase;
@@ -35,6 +35,8 @@ struct ImageInfo
BYTE ValidTrack[TRACKS_MAX];
UINT uNumTracks;
BYTE* pImageBuffer;
+ BYTE* pTrackMap; // WOZ only
+ BYTE optimalBitTiming; // WOZ only
};
//-------------------------------------
@@ -54,9 +56,9 @@ public:
virtual bool Boot(ImageInfo* pImageInfo) { return false; }
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt) = 0;
- virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) { }
+ virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { }
virtual bool Read(ImageInfo* pImageInfo, UINT nBlock, LPBYTE pBlockBuffer) { return false; }
- virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) { }
+ virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { }
virtual bool Write(ImageInfo* pImageInfo, UINT nBlock, LPBYTE pBlockBuffer) { return false; }
virtual bool AllowBoot(void) { return false; } // Only: APL and PRG
@@ -71,6 +73,11 @@ public:
void SetVolumeNumber(const BYTE uVolumeNumber) { m_uVolumeNumber = uVolumeNumber; }
bool IsValidImageSize(const DWORD uImageSize);
+ // To accurately convert a half phase (quarter track) back to a track (round half tracks down), use: ceil(phase)/2, eg:
+ // . phase=4,+1 half phase = phase 4.5 => ceil(4.5)/2 = track 2 (OK)
+ // . phase=4,-1 half phase = phase 3.5 => ceil(3.5)/2 = track 2 (OK)
+ UINT PhaseToTrack(const float phase) { return ((UINT)ceil(phase)) >> 1; }
+
enum SectorOrder_e {eProDOSOrder, eDOSOrder, eSIMSYSTEMOrder, NUM_SECTOR_ORDERS};
protected:
@@ -122,7 +129,7 @@ private:
// http://apple2.org.za/gswv/a2zine/Docs/DiskImage_2MG_Info.txt
#pragma pack(push)
-#pragma pack(1) // Ensure Header2IMG is packed
+#pragma pack(1) // Ensure Header2IMG & WOZ structs are packed
class C2IMGHelper : public CHdrHelper
{
@@ -181,6 +188,98 @@ private:
bool m_bIsFloppy;
};
+class CWOZHelper : public CHdrHelper
+{
+public:
+ CWOZHelper() :
+ m_pInfo(NULL)
+ {}
+ virtual ~CWOZHelper(void) {}
+ virtual eDetectResult DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& dwOffset) { _ASSERT(0); return eMismatch; }
+ virtual UINT GetMaxHdrSize(void) { return sizeof(WOZHeader); }
+ eDetectResult ProcessChunks(const LPBYTE pImage, const DWORD dwImageSize, DWORD& dwOffset, BYTE*& pTrackMap);
+ bool IsWriteProtected(void) { return m_pInfo->v1.writeProtected == 1; }
+ BYTE GetOptimalBitTiming(void) { return (m_pInfo->v1.version == 1) ? CWOZHelper::InfoChunkv2::optimalBitTiming5_25 : m_pInfo->optimalBitTiming; }
+
+ static const UINT32 ID1_WOZ1 = '1ZOW'; // 'WOZ1'
+ static const UINT32 ID1_WOZ2 = '2ZOW'; // 'WOZ2'
+ static const UINT32 ID2 = 0x0A0D0AFF;
+
+ struct WOZHeader
+ {
+ UINT32 id1; // 'WOZ1' or 'WOZ2'
+ UINT32 id2;
+ UINT32 crc32;
+ };
+
+ static const UINT32 MAX_TRACKS_5_25 = 40;
+ static const UINT32 WOZ1_TRACK_SIZE = 6656; // 0x1A00
+ static const UINT32 WOZ1_TRK_OFFSET = 6646;
+ static const UINT32 EMPTY_TRACK_SIZE = 6400;
+
+ struct TRKv1
+ {
+ UINT16 bytesUsed;
+ UINT16 bitCount;
+ UINT16 splicePoint;
+ BYTE spliceNibble;
+ BYTE spliceBitCount;
+ UINT16 reserved;
+ };
+
+ struct TRKv2
+ {
+ UINT16 startBlock; // relative to start of file
+ UINT16 blockCount; // number of blocks for this BITS data
+ UINT32 bitCount;
+ };
+
+private:
+ static const UINT32 INFO_CHUNK_ID = 'OFNI'; // 'INFO'
+ static const UINT32 TMAP_CHUNK_ID = 'PAMT'; // 'TMAP'
+ static const UINT32 TRKS_CHUNK_ID = 'SKRT'; // 'TRKS'
+ static const UINT32 WRIT_CHUNK_ID = 'TIRW'; // 'WRIT' - WOZv2
+ static const UINT32 META_CHUNK_ID = 'ATEM'; // 'META'
+
+ struct InfoChunk
+ {
+ UINT32 id;
+ UINT32 size;
+ BYTE version;
+ BYTE diskType;
+ BYTE writeProtected; // 1 = Floppy is write protected
+ BYTE synchronized; // 1 = Cross track sync was used during imaging
+ BYTE cleaned; // 1 = MC3470 fake bits have been removed
+ BYTE creator[32]; // Name of software that created the WOZ file.
+ // String in UTF-8. No BOM. Padded to 32 bytes
+ // using space character (0x20).
+
+ static const BYTE maxSupportedVersion = 2;
+ static const BYTE diskType5_25 = 1;
+ static const BYTE diskType3_5 = 2;
+ };
+
+ struct InfoChunkv2
+ {
+ InfoChunk v1;
+ BYTE diskSides; // 5.25 will always be 1; 3.5 can be 1 or 2
+ BYTE bootSectorFormat;
+ BYTE optimalBitTiming; // in 125ns increments (And a standard bit rate for 5.25 disk would be 32 (4us))
+ UINT16 compatibleHardware;
+ UINT16 requiredRAM; // in K (1024 bytes)
+ UINT16 largestTrack; // in blocks (512 bytes)
+
+ static const BYTE bootUnknown = 0;
+ static const BYTE bootSector16 = 1;
+ static const BYTE bootSector13 = 2;
+ static const BYTE bootSectorBoth = 3;
+
+ static const BYTE optimalBitTiming5_25 = 32;
+ };
+
+ InfoChunkv2* m_pInfo;
+};
+
#pragma pack(pop)
//-------------------------------------
@@ -190,7 +289,8 @@ class CImageHelperBase
public:
CImageHelperBase(const bool bIsFloppy) :
m_2IMGHelper(bIsFloppy),
- m_Result2IMG(eMismatch)
+ m_Result2IMG(eMismatch),
+ m_WOZHelper()
{
}
virtual ~CImageHelperBase(void)
@@ -202,7 +302,7 @@ public:
ImageError_e Open(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, const bool bCreateIfNecessary, std::string& strFilenameInZip);
void Close(ImageInfo* pImageInfo, const bool bDeleteFile);
- virtual CImageBase* Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool* pWriteProtected_) = 0;
+ virtual CImageBase* Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming) = 0;
virtual CImageBase* GetImageForCreation(const TCHAR* pszExt, DWORD* pCreateImageSize) = 0;
virtual UINT GetMaxImageSize(void) = 0;
virtual UINT GetMinDetectSize(const UINT uImageSize, bool* pTempDetectBuffer) = 0;
@@ -211,6 +311,9 @@ protected:
ImageError_e CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo);
ImageError_e CheckZipFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, std::string& strFilenameInZip);
ImageError_e CheckNormalFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, const bool bCreateIfNecessary);
+ void GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize);
+ void GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize);
+ void SetImageInfo(ImageInfo* pImageInfo, FileType_e eFileGZip, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize);
UINT GetNumImages(void) { return m_vecImageTypes.size(); };
CImageBase* GetImage(UINT uIndex) { _ASSERT(uIndex= 2)
keywaiting = (BOOL) yamlLoadHelper.LoadBool(SS_YAML_KEY_KEYWAITING);
yamlLoadHelper.PopMap();
diff --git a/source/Memory.cpp b/source/Memory.cpp
index 2329d886..da593286 100644
--- a/source/Memory.cpp
+++ b/source/Memory.cpp
@@ -1745,6 +1745,8 @@ void MemReset()
g_eExpansionRomType = eExpRomNull;
g_uPeripheralRomSlot = 0;
+ ZeroMemory(memdirty, 0x100);
+
//
int iByte;
@@ -2202,7 +2204,7 @@ bool MemLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT unitVersion)
SetLastRamWrite( yamlLoadHelper.LoadUint(SS_YAML_KEY_LASTRAMWRITE) ? TRUE : FALSE ); // NB. This is set later for II,II+ by slot-0 LC or Saturn
}
- if (unitVersion == 3)
+ if (unitVersion >= 3)
{
for (UINT i=0; i -ve, as this isn't an underflow
+ int timer = timerCounter;
+ timer -= nClocks;
+ timerCounter = (USHORT)timer;
+
+ bool timerIrq = false;
+
+ if (timerIrqDelay) // Deal with any previous counter underflow which didn't yet result in an IRQ
+ {
+ timerIrqDelay -= nClocks;
+ if (timerIrqDelay <= 0)
+ {
+ timerIrqDelay = 0;
+ timerIrq = true;
+ }
+ // don't re-underflow if TIMER = 0x0000 or 0xFFFF (so just return)
+ }
+ else if (oldTimer > 0 && timer <= 0) // Underflow occurs for 0x0001 -> 0x0000
+ {
+ if (pTimerUnderflow)
+ *pTimerUnderflow = true; // Just for Willy Byte!
+
+ if (timer <= -2)
+ timerIrq = true;
+ else // TIMER = 0x0000 or 0xFFFF
+ timerIrqDelay = 2 + timer; // ...so 2 or 1 cycles until IRQ
+ }
+
+ return timerIrq;
+}
+
// Called by:
// . CpuExecute() every ~1000 @ 1MHz
// . CheckInterruptSources() every 128 cycles
// . MB_Read() / MB_Write()
void MB_UpdateCycles(ULONG uExecutedCycles)
{
- if(g_SoundcardType == CT_Empty)
+ if (g_SoundcardType == CT_Empty)
return;
CpuCalcCycles(uExecutedCycles);
@@ -1752,19 +1786,13 @@ void MB_UpdateCycles(ULONG uExecutedCycles)
_ASSERT(uCycles < 0x10000);
USHORT nClocks = (USHORT) uCycles;
- for(int i=0; isy6522.TIMER1_COUNTER.w;
- USHORT OldTimer2 = pMB->sy6522.TIMER2_COUNTER.w;
-
- pMB->sy6522.TIMER1_COUNTER.w -= nClocks;
- pMB->sy6522.TIMER2_COUNTER.w -= nClocks;
-
- // Check for counter underflow
- bool bTimer1Underflow = (!(OldTimer1 & 0x8000) && (pMB->sy6522.TIMER1_COUNTER.w & 0x8000));
- bool bTimer2Underflow = (!(OldTimer2 & 0x8000) && (pMB->sy6522.TIMER2_COUNTER.w & 0x8000));
+ bool bTimer1Underflow = false; // Just for Willy Byte!
+ const bool bTimer1Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER1_COUNTER.w, pMB->sy6522.timer1IrqDelay, nClocks, &bTimer1Underflow);
+ const bool bTimer2Irq = CheckTimerUnderflowAndIrq(pMB->sy6522.TIMER2_COUNTER.w, pMB->sy6522.timer2IrqDelay, nClocks);
if (!pMB->bTimer1Active && bTimer1Underflow)
{
@@ -1774,11 +1802,12 @@ void MB_UpdateCycles(ULONG uExecutedCycles)
{
// Fix for Willy Byte - need to confirm that 6522 really does this!
// . It never accesses IER/IFR/TIMER1 regs to clear IRQ
+ // . NB. Willy Byte doesn't work with Phasor.
UpdateIFR(pMB, IxR_TIMER1); // Deassert the TIMER IRQ
}
}
- if (pMB->bTimer1Active && bTimer1Underflow)
+ if (pMB->bTimer1Active && bTimer1Irq)
{
UpdateIFR(pMB, 0, IxR_TIMER1);
@@ -1786,7 +1815,7 @@ void MB_UpdateCycles(ULONG uExecutedCycles)
if (g_nMBTimerDevice == i)
MB_Update();
- if((pMB->sy6522.ACR & RUNMODE) == RM_ONESHOT)
+ if ((pMB->sy6522.ACR & RUNMODE) == RM_ONESHOT)
{
// One-shot mode
// - Phasor's playback code uses one-shot mode
@@ -1797,11 +1826,21 @@ void MB_UpdateCycles(ULONG uExecutedCycles)
{
// Free-running mode
// - Ultima4/5 change ACCESS_TIMER1 after a couple of IRQs into tune
- pMB->sy6522.TIMER1_COUNTER.w = pMB->sy6522.TIMER1_LATCH.w;
+ pMB->sy6522.TIMER1_COUNTER.w += pMB->sy6522.TIMER1_LATCH.w; // GH#651: account for underflowed cycles too
+ pMB->sy6522.TIMER1_COUNTER.w += 2; // GH#652: account for extra 2 cycles (Rockwell, Fig.16: period=N+2cycles)
+ // - or maybe the counter doesn't count down during these 2 cycles?
+ if (pMB->sy6522.TIMER1_COUNTER.w > pMB->sy6522.TIMER1_LATCH.w)
+ {
+ if (pMB->sy6522.TIMER1_LATCH.w)
+ pMB->sy6522.TIMER1_COUNTER.w %= pMB->sy6522.TIMER1_LATCH.w; // Only occurs if LATCH.w<0x0007 (# cycles for longest opcode)
+ else
+ pMB->sy6522.TIMER1_COUNTER.w = 0;
+ }
StartTimer1(pMB);
}
}
- else if (pMB->bTimer2Active && bTimer2Underflow)
+
+ if (pMB->bTimer2Active && bTimer2Irq)
{
UpdateIFR(pMB, 0, IxR_TIMER2);
@@ -1811,7 +1850,14 @@ void MB_UpdateCycles(ULONG uExecutedCycles)
}
else
{
- pMB->sy6522.TIMER2_COUNTER.w = pMB->sy6522.TIMER2_LATCH.w;
+ pMB->sy6522.TIMER2_COUNTER.w += pMB->sy6522.TIMER2_LATCH.w;
+ if (pMB->sy6522.TIMER2_COUNTER.w > pMB->sy6522.TIMER2_LATCH.w)
+ {
+ if (pMB->sy6522.TIMER2_LATCH.w)
+ pMB->sy6522.TIMER2_COUNTER.w %= pMB->sy6522.TIMER2_LATCH.w;
+ else
+ pMB->sy6522.TIMER2_COUNTER.w = 0;
+ }
StartTimer2(pMB);
}
}
@@ -1903,7 +1949,8 @@ void MB_GetSnapshot_v1(SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot)
// Unit version history:
// 2: Added: Timer1 & Timer2 active
// 3: Added: Unit state
-const UINT kUNIT_VERSION = 3;
+// 4: Added: 6522 timerIrqDelay
+const UINT kUNIT_VERSION = 4;
const UINT NUM_MB_UNITS = 2;
const UINT NUM_PHASOR_UNITS = 2;
@@ -1937,6 +1984,8 @@ const UINT NUM_PHASOR_UNITS = 2;
#define SS_YAML_KEY_SPEECH_IRQ "Speech IRQ Pending"
#define SS_YAML_KEY_TIMER1_ACTIVE "Timer1 Active"
#define SS_YAML_KEY_TIMER2_ACTIVE "Timer2 Active"
+#define SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY "Timer1 IRQ Delay"
+#define SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY "Timer2 IRQ Delay"
#define SS_YAML_KEY_PHASOR_UNIT "Unit"
#define SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR "Clock Scale Factor"
@@ -1964,8 +2013,10 @@ static void SaveSnapshotSY6522(YamlSaveHelper& yamlSaveHelper, SY6522& sy6522)
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_DDRA, sy6522.DDRA);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T1_COUNTER, sy6522.TIMER1_COUNTER.w);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T1_LATCH, sy6522.TIMER1_LATCH.w);
+ yamlSaveHelper.SaveUint(SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY, sy6522.timer1IrqDelay); // v4
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T2_COUNTER, sy6522.TIMER2_COUNTER.w);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T2_LATCH, sy6522.TIMER2_LATCH.w);
+ yamlSaveHelper.SaveUint(SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY, sy6522.timer2IrqDelay); // v4
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT, sy6522.SERIAL_SHIFT);
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ACR, sy6522.ACR);
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_PCR, sy6522.PCR);
@@ -2017,7 +2068,7 @@ void MB_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot)
}
}
-static void LoadSnapshotSY6522(YamlLoadHelper& yamlLoadHelper, SY6522& sy6522)
+static void LoadSnapshotSY6522(YamlLoadHelper& yamlLoadHelper, SY6522& sy6522, UINT version)
{
if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SY6522))
throw std::string("Card: Expected key: ") + std::string(SS_YAML_KEY_SY6522);
@@ -2037,6 +2088,14 @@ static void LoadSnapshotSY6522(YamlLoadHelper& yamlLoadHelper, SY6522& sy6522)
sy6522.IER = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_IER);
sy6522.ORA_NO_HS = 0; // Not saved
+ sy6522.timer1IrqDelay = sy6522.timer2IrqDelay = 0;
+
+ if (version >= 4)
+ {
+ sy6522.timer1IrqDelay = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY);
+ sy6522.timer2IrqDelay = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY);
+ }
+
yamlLoadHelper.PopMap();
}
@@ -2079,7 +2138,7 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version)
if (!yamlLoadHelper.GetSubMap(unit))
throw std::string("Card: Expected key: ") + std::string(unit);
- LoadSnapshotSY6522(yamlLoadHelper, pMB->sy6522);
+ LoadSnapshotSY6522(yamlLoadHelper, pMB->sy6522, version);
AY8910_LoadSnapshot(yamlLoadHelper, nDeviceNum, std::string(""));
LoadSnapshotSSI263(yamlLoadHelper, pMB->SpeechChip);
@@ -2131,7 +2190,7 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version)
pMB++;
}
- AY8910_InitClock((int)CLK_6502);
+ AY8910_InitClock((int)Get6502BaseClock());
// NB. g_SoundcardType & g_bPhasorEnable setup in MB_InitializeIO() -> MB_SetSoundcardType()
@@ -2201,7 +2260,7 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version
if (!yamlLoadHelper.GetSubMap(unit))
throw std::string("Card: Expected key: ") + std::string(unit);
- LoadSnapshotSY6522(yamlLoadHelper, pMB->sy6522);
+ LoadSnapshotSY6522(yamlLoadHelper, pMB->sy6522, version);
AY8910_LoadSnapshot(yamlLoadHelper, nDeviceNum+0, std::string("-A"));
AY8910_LoadSnapshot(yamlLoadHelper, nDeviceNum+1, std::string("-B"));
LoadSnapshotSSI263(yamlLoadHelper, pMB->SpeechChip);
@@ -2254,7 +2313,7 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version
pMB++;
}
- AY8910_InitClock((int)(CLK_6502 * g_PhasorClockScaleFactor));
+ AY8910_InitClock((int)(Get6502BaseClock() * g_PhasorClockScaleFactor));
// NB. g_SoundcardType & g_bPhasorEnable setup in MB_InitializeIO() -> MB_SetSoundcardType()
diff --git a/source/NTSC.cpp b/source/NTSC.cpp
index fe7b7c82..0d2eb052 100644
--- a/source/NTSC.cpp
+++ b/source/NTSC.cpp
@@ -125,13 +125,22 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
static int g_nHiresPage = 1;
static int g_nTextPage = 1;
+ static bool g_bDelayVideoMode = false;
+ static uint32_t g_uNewVideoModeFlags = 0;
+
// Understanding the Apple II, Timing Generation and the Video Scanner, Pg 3-11
// Vertical Scanning
// Horizontal Scanning
// "There are exactly 17030 (65 x 262) 6502 cycles in every television scan of an American Apple."
#define VIDEO_SCANNER_MAX_HORZ 65 // TODO: use Video.cpp: kHClocks
#define VIDEO_SCANNER_MAX_VERT 262 // TODO: use Video.cpp: kNTSCScanLines
- static const int VIDEO_SCANNER_6502_CYCLES = VIDEO_SCANNER_MAX_HORZ * VIDEO_SCANNER_MAX_VERT;
+ static const UINT VIDEO_SCANNER_6502_CYCLES = VIDEO_SCANNER_MAX_HORZ * VIDEO_SCANNER_MAX_VERT;
+
+ #define VIDEO_SCANNER_MAX_VERT_PAL 312
+ static const UINT VIDEO_SCANNER_6502_CYCLES_PAL = VIDEO_SCANNER_MAX_HORZ * VIDEO_SCANNER_MAX_VERT_PAL;
+
+ static UINT g_videoScannerMaxVert = VIDEO_SCANNER_MAX_VERT; // default to NTSC
+ static UINT g_videoScanner6502Cycles = VIDEO_SCANNER_6502_CYCLES; // default to NTSC
#define VIDEO_SCANNER_HORZ_COLORBURST_BEG 12
#define VIDEO_SCANNER_HORZ_COLORBURST_END 16
@@ -209,9 +218,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// Tables
// Video scanner tables are now runtime-generated using UTAIIe logic
- static unsigned short g_aClockVertOffsetsHGR[VIDEO_SCANNER_MAX_VERT];
- static unsigned short g_aClockVertOffsetsTXT[33];
- static unsigned short APPLE_IIP_HORZ_CLOCK_OFFSET[5][VIDEO_SCANNER_MAX_HORZ];
+ static unsigned short g_aClockVertOffsetsHGR[VIDEO_SCANNER_MAX_VERT_PAL];
+ static unsigned short g_aClockVertOffsetsTXT[VIDEO_SCANNER_MAX_VERT_PAL/8];
+ static unsigned short APPLE_IIP_HORZ_CLOCK_OFFSET[5][VIDEO_SCANNER_MAX_HORZ]; // 5 = CEILING(312/64) = CEILING(262/64)
static unsigned short APPLE_IIE_HORZ_CLOCK_OFFSET[5][VIDEO_SCANNER_MAX_HORZ];
#ifdef _DEBUG
@@ -240,7 +249,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0x0B80,0x0F80,0x1380,0x1780,0x1B80,0x1F80
};
- static unsigned short g_kClockVertOffsetsTXT[33] =
+ static unsigned short g_kClockVertOffsetsTXT[33] = // 33 = CEILING(262/8)
{
0x0000,0x0080,0x0100,0x0180,0x0200,0x0280,0x0300,0x0380,
0x0000,0x0080,0x0100,0x0180,0x0200,0x0280,0x0300,0x0380,
@@ -765,7 +774,7 @@ inline void updateVideoScannerHorzEOLSimple()
{
g_nVideoClockHorz = 0;
- if (++g_nVideoClockVert == VIDEO_SCANNER_MAX_VERT)
+ if (++g_nVideoClockVert == g_videoScannerMaxVert)
{
g_nVideoClockVert = 0;
@@ -804,7 +813,7 @@ inline void updateVideoScannerHorzEOL()
g_nVideoClockHorz = 0;
- if (++g_nVideoClockVert == VIDEO_SCANNER_MAX_VERT)
+ if (++g_nVideoClockVert == g_videoScannerMaxVert)
{
g_nVideoClockVert = 0;
@@ -848,7 +857,7 @@ inline void updateVideoScannerHorzEOL_14M()
g_nVideoClockHorz = 0;
- if (++g_nVideoClockVert == VIDEO_SCANNER_MAX_VERT)
+ if (++g_nVideoClockVert == g_videoScannerMaxVert)
{
g_nVideoClockVert = 0;
@@ -894,7 +903,7 @@ inline void updateVideoScannerHorzEOL()
g_nVideoClockHorz = 0;
- if (++g_nVideoClockVert == VIDEO_SCANNER_MAX_VERT)
+ if (++g_nVideoClockVert == g_videoScannerMaxVert)
{
g_nVideoClockVert = 0;
@@ -1835,8 +1844,8 @@ uint32_t*NTSC_VideoGetChromaTable( bool bHueTypeMonochrome, bool bMonitorTypeCol
//===========================================================================
void NTSC_VideoClockResync(const DWORD dwCyclesThisFrame)
{
- g_nVideoClockVert = (uint16_t) (dwCyclesThisFrame / VIDEO_SCANNER_MAX_HORZ) % VIDEO_SCANNER_MAX_VERT;
- g_nVideoClockHorz = (uint16_t) (dwCyclesThisFrame % VIDEO_SCANNER_MAX_HORZ);
+ g_nVideoClockVert = (uint16_t)(dwCyclesThisFrame / VIDEO_SCANNER_MAX_HORZ) % g_videoScannerMaxVert;
+ g_nVideoClockHorz = (uint16_t)(dwCyclesThisFrame % VIDEO_SCANNER_MAX_HORZ);
}
//===========================================================================
@@ -1852,13 +1861,13 @@ uint16_t NTSC_VideoGetScannerAddress ( const ULONG uExecutedCycles )
const uint16_t currVideoClockHorz = g_nVideoClockHorz;
// Required for ANSI STORY (end credits) vert scrolling mid-scanline mixed mode: DGR80, TEXT80, DGR80
- g_nVideoClockHorz -= 2;
+ g_nVideoClockHorz -= 1;
if ((SHORT)g_nVideoClockHorz < 0)
{
g_nVideoClockHorz += VIDEO_SCANNER_MAX_HORZ;
g_nVideoClockVert -= 1;
if ((SHORT)g_nVideoClockVert < 0)
- g_nVideoClockVert = VIDEO_SCANNER_MAX_VERT-1;
+ g_nVideoClockVert = g_videoScannerMaxVert-1;
}
uint16_t addr;
@@ -1884,8 +1893,15 @@ void NTSC_SetVideoTextMode( int cols )
}
//===========================================================================
-void NTSC_SetVideoMode( uint32_t uVideoModeFlags )
+void NTSC_SetVideoMode( uint32_t uVideoModeFlags, bool bDelay/*=false*/ )
{
+ if (bDelay)
+ {
+ g_bDelayVideoMode = true;
+ g_uNewVideoModeFlags = uVideoModeFlags;
+ return;
+ }
+
g_nVideoMixed = uVideoModeFlags & VF_MIXED;
g_nVideoCharSet = VideoGetSWAltCharSet() ? 1 : 0;
@@ -2144,8 +2160,8 @@ void NTSC_VideoInit( uint8_t* pFramebuffer ) // wsVideoInit
//===========================================================================
void NTSC_VideoReinitialize( DWORD cyclesThisFrame, bool bInitVideoScannerAddress )
{
- _ASSERT(cyclesThisFrame < VIDEO_SCANNER_6502_CYCLES);
- if (cyclesThisFrame >= VIDEO_SCANNER_6502_CYCLES) cyclesThisFrame = 0; // error
+ _ASSERT(cyclesThisFrame < g_videoScanner6502Cycles);
+ if (cyclesThisFrame >= g_videoScanner6502Cycles) cyclesThisFrame = 0; // error
g_nVideoClockVert = (uint16_t) (cyclesThisFrame / VIDEO_SCANNER_MAX_HORZ);
g_nVideoClockHorz = cyclesThisFrame % VIDEO_SCANNER_MAX_HORZ;
@@ -2183,7 +2199,7 @@ void NTSC_VideoInitChroma()
//===========================================================================
-// Pre: cyclesLeftToUpdate = [0...VIDEO_SCANNER_6502_CYCLES]
+// Pre: cyclesLeftToUpdate = [0...g_videoScanner6502Cycles]
// . 2-14: After one emulated 6502/65C02 opcode (optionally with IRQ)
// . ~1000: After 1ms of Z80 emulation
// . 17030: From NTSC_VideoRedrawWholeScreen()
@@ -2198,7 +2214,7 @@ static void VideoUpdateCycles( int cyclesLeftToUpdate )
g_pFuncUpdateGraphicsScreen(cycles); // lines [currV...159]
cyclesLeftToUpdate -= cycles;
- const int cyclesFromLine160ToLine261 = VIDEO_SCANNER_6502_CYCLES - (VIDEO_SCANNER_MAX_HORZ * VIDEO_SCANNER_Y_MIXED);
+ const int cyclesFromLine160ToLine261 = g_videoScanner6502Cycles - (VIDEO_SCANNER_MAX_HORZ * VIDEO_SCANNER_Y_MIXED);
cycles = cyclesLeftToUpdate < cyclesFromLine160ToLine261 ? cyclesLeftToUpdate : cyclesFromLine160ToLine261;
g_pFuncUpdateGraphicsScreen(cycles); // lines [160..191..261]
cyclesLeftToUpdate -= cycles;
@@ -2207,7 +2223,7 @@ static void VideoUpdateCycles( int cyclesLeftToUpdate )
}
else
{
- const int cyclesToLine262 = VIDEO_SCANNER_MAX_HORZ * (VIDEO_SCANNER_MAX_VERT - g_nVideoClockVert - 1) + cyclesToEndOfLine;
+ const int cyclesToLine262 = VIDEO_SCANNER_MAX_HORZ * (g_videoScannerMaxVert - g_nVideoClockVert - 1) + cyclesToEndOfLine;
int cycles = cyclesLeftToUpdate < cyclesToLine262 ? cyclesLeftToUpdate : cyclesToLine262;
g_pFuncUpdateGraphicsScreen(cycles); // lines [currV...261]
cyclesLeftToUpdate -= cycles;
@@ -2225,9 +2241,21 @@ static void VideoUpdateCycles( int cyclesLeftToUpdate )
}
//===========================================================================
-void NTSC_VideoUpdateCycles( long cycles6502 )
+void NTSC_VideoUpdateCycles( UINT cycles6502 )
{
- _ASSERT(cycles6502 < VIDEO_SCANNER_6502_CYCLES); // Use NTSC_VideoRedrawWholeScreen() instead
+ _ASSERT(cycles6502 && cycles6502 < g_videoScanner6502Cycles); // Use NTSC_VideoRedrawWholeScreen() instead
+
+ if (g_bDelayVideoMode)
+ {
+ VideoUpdateCycles(1); // Video mode change is delayed by 1 cycle
+
+ g_bDelayVideoMode = false;
+ NTSC_SetVideoMode(g_uNewVideoModeFlags);
+
+ cycles6502--;
+ if (!cycles6502)
+ return;
+ }
VideoUpdateCycles(cycles6502);
}
@@ -2247,7 +2275,7 @@ void NTSC_VideoRedrawWholeScreen( void )
g_nVideoClockHorz = 0;
updateVideoScannerAddress();
- VideoUpdateCycles(VIDEO_SCANNER_6502_CYCLES);
+ VideoUpdateCycles(g_videoScanner6502Cycles);
VideoUpdateCycles(horz); // Finally update to get to correct H-pos
@@ -2302,19 +2330,37 @@ static void CheckVideoTables( void )
CheckVideoTables2(A2TYPE_APPLE2E, VF_TEXT);
}
+static bool IsNTSC(void)
+{
+ return g_videoScannerMaxVert == VIDEO_SCANNER_MAX_VERT;
+}
+
static void GenerateVideoTables( void )
{
eApple2Type currentApple2Type = GetApple2Type();
+ uint32_t currentVideoMode = g_uVideoMode;
+ int currentHiresPage = g_nHiresPage;
+ int currentTextPage = g_nTextPage;
+
+ g_nHiresPage = g_nTextPage = 1;
//
// g_aClockVertOffsetsHGR[]
//
g_uVideoMode = VF_HIRES;
- for (UINT i=0, cycle=VIDEO_SCANNER_HORZ_START; i= 4)
+ {
+ VideoRefreshRate_e rate = (VideoRefreshRate_e)yamlLoadHelper.LoadUint(SS_YAML_KEY_VIDEO_REFRESH_RATE);
+ SetVideoRefreshRate(rate); // Trashes: g_dwCyclesThisFrame
+ SetCurrentCLK6502();
+ }
+
+ g_nAltCharSetOffset = yamlLoadHelper.LoadBool(SS_YAML_KEY_ALT_CHARSET) ? 256 : 0;
+ g_uVideoMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_VIDEO_MODE);
+ g_dwCyclesThisFrame = yamlLoadHelper.LoadUint(SS_YAML_KEY_CYCLES_THIS_FRAME);
yamlLoadHelper.PopMap();
}
@@ -771,7 +776,7 @@ WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr /*= V
//
const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
const int kScanCycles = kScanLines * kHClocks;
- _ASSERT(nCycles < kScanCycles);
+ _ASSERT(nCycles < (UINT)kScanCycles);
nCycles %= kScanCycles;
// calculate horizontal scanning state
@@ -789,7 +794,7 @@ WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr /*= V
int h_4 = (nHState >> 4) & 1;
int h_5 = (nHState >> 5) & 1;
- // calculate vertical scanning state
+ // calculate vertical scanning state (UTAIIe:3-15,T3.2)
//
int nVLine = nCycles / kHClocks; // which vertical scanning line
int nVState = kVLine0State + nVLine; // V state bits
@@ -862,7 +867,7 @@ WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr /*= V
nAddressP |= p2b << 11; // a11
}
- // VBL' = v_4' | v_3' = (v_4 & v_3)' (UTAIIe:5-10,#3)
+ // VBL' = v_4' | v_3' = (v_4 & v_3)' (UTAIIe:5-10,#3), (UTAIIe:3-15,T3.2)
if (videoScannerAddr == VS_PartialAddrH)
return nAddressH;
@@ -875,6 +880,7 @@ WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr /*= V
//===========================================================================
+// TODO: Consider replacing simply with: return g_nVideoClockVert < kVDisplayableScanLines
bool VideoGetVblBar(const DWORD uExecutedCycles)
{
// get video scanner position
@@ -1225,6 +1231,10 @@ void Config_Load_Video()
REGLOAD(TEXT(REGVALUE_VIDEO_STYLE) ,(DWORD*)&g_eVideoStyle);
REGLOAD(TEXT(REGVALUE_VIDEO_MONO_COLOR),&g_nMonochromeRGB);
+ DWORD rate = VR_60HZ;
+ REGLOAD(TEXT(REGVALUE_VIDEO_REFRESH_RATE), &rate);
+ SetVideoRefreshRate((VideoRefreshRate_e)rate);
+
//
const UINT16* pOldVersion = GetOldAppleWinVersion();
@@ -1269,6 +1279,7 @@ void Config_Save_Video()
REGSAVE(TEXT(REGVALUE_VIDEO_MODE) ,g_eVideoType);
REGSAVE(TEXT(REGVALUE_VIDEO_STYLE) ,g_eVideoStyle);
REGSAVE(TEXT(REGVALUE_VIDEO_MONO_COLOR),g_nMonochromeRGB);
+ REGSAVE(TEXT(REGVALUE_VIDEO_REFRESH_RATE), GetVideoRefreshRate());
}
//===========================================================================
@@ -1299,6 +1310,22 @@ bool IsVideoStyle(VideoStyle_e mask)
return (g_eVideoStyle & mask) != 0;
}
+//===========================================================================
+
+VideoRefreshRate_e GetVideoRefreshRate(void)
+{
+ return (g_bVideoScannerNTSC == false) ? VR_50HZ : VR_60HZ;
+}
+
+void SetVideoRefreshRate(VideoRefreshRate_e rate)
+{
+ if (rate != VR_50HZ)
+ rate = VR_60HZ;
+
+ g_bVideoScannerNTSC = (rate == VR_60HZ);
+ NTSC_SetRefreshRate(rate);
+}
+
//===========================================================================
static void videoCreateDIBSection()
{
diff --git a/source/Video.h b/source/Video.h
index 2a7e2253..693e7dc7 100644
--- a/source/Video.h
+++ b/source/Video.h
@@ -29,6 +29,13 @@
// VS_TEXT_OPTIMIZED=4,
};
+ enum VideoRefreshRate_e
+ {
+ VR_NONE,
+ VR_50HZ,
+ VR_60HZ
+ };
+
enum VideoFlag_e
{
VF_80COL = 0x00000001,
@@ -193,7 +200,7 @@ bool VideoGetSWTEXT(void);
bool VideoGetSWAltCharSet(void);
void VideoSaveSnapshot(class YamlSaveHelper& yamlSaveHelper);
-void VideoLoadSnapshot(class YamlLoadHelper& yamlLoadHelper);
+void VideoLoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT version);
extern bool g_bDisplayPrintScreenFileName;
extern bool g_bShowPrintScreenWarningDialog;
@@ -226,3 +233,6 @@ void SetVideoType(VideoType_e newVideoType);
VideoStyle_e GetVideoStyle(void);
void SetVideoStyle(VideoStyle_e newVideoStyle);
bool IsVideoStyle(VideoStyle_e mask);
+
+VideoRefreshRate_e GetVideoRefreshRate(void);
+void SetVideoRefreshRate(VideoRefreshRate_e rate);
diff --git a/source/YamlHelper.cpp b/source/YamlHelper.cpp
index 0e9f96cb..dbcaddcd 100644
--- a/source/YamlHelper.cpp
+++ b/source/YamlHelper.cpp
@@ -342,6 +342,34 @@ std::string YamlLoadHelper::LoadString(const std::string& key)
return value;
}
+float YamlLoadHelper::LoadFloat(const std::string key)
+{
+ bool bFound;
+ std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
+ if (value == "")
+ {
+ m_bDoGetMapRemainder = false;
+ throw std::string(m_currentMapName + ": Missing: " + key);
+ }
+#if (_MSC_VER >= 1900)
+ return strtof(value.c_str(), NULL); // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015 version 14.0)
+#else
+ return (float) strtod(value.c_str(), NULL); // NB. strtof() requires VS2015
+#endif
+}
+
+double YamlLoadHelper::LoadDouble(const std::string key)
+{
+ bool bFound;
+ std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound);
+ if (value == "")
+ {
+ m_bDoGetMapRemainder = false;
+ throw std::string(m_currentMapName + ": Missing: " + key);
+ }
+ return strtod(value.c_str(), NULL);
+}
+
void YamlLoadHelper::LoadMemory(const LPBYTE pMemBase, const size_t size)
{
m_yamlHelper.LoadMemory(*m_pMapYaml, pMemBase, size);
@@ -371,7 +399,7 @@ void YamlSaveHelper::SaveUint(const char* key, UINT value)
void YamlSaveHelper::SaveHexUint4(const char* key, UINT value)
{
- Save("%s: 0x%01X\n", key, value);
+ Save("%s: 0x%01X\n", key, value & 0xf);
}
void YamlSaveHelper::SaveHexUint8(const char* key, UINT value)
@@ -414,6 +442,16 @@ void YamlSaveHelper::SaveString(const char* key, const char* value)
Save("%s: %s\n", key, (value[0] != 0) ? value : "\"\"");
}
+void YamlSaveHelper::SaveFloat(const char* key, float value)
+{
+ Save("%s: %f\n", key, value);
+}
+
+void YamlSaveHelper::SaveDouble(const char* key, double value)
+{
+ Save("%s: %f\n", key, value);
+}
+
// Pre: uMemSize must be multiple of 8
void YamlSaveHelper::SaveMemory(const LPBYTE pMemBase, const UINT uMemSize)
{
diff --git a/source/YamlHelper.h b/source/YamlHelper.h
index c96cca2c..48f0999a 100644
--- a/source/YamlHelper.h
+++ b/source/YamlHelper.h
@@ -97,6 +97,8 @@ public:
bool LoadBool(const std::string key);
std::string LoadString_NoThrow(const std::string& key, bool& bFound);
std::string LoadString(const std::string& key);
+ float LoadFloat(const std::string key);
+ double LoadDouble(const std::string key);
void LoadMemory(const LPBYTE pMemBase, const size_t size);
bool GetSubMap(const std::string key)
@@ -213,7 +215,9 @@ public:
void SaveHexUint32(const char* key, UINT value);
void SaveHexUint64(const char* key, UINT64 value);
void SaveBool(const char* key, bool value);
- void SaveString(const char* key, const char* value);
+ void SaveString(const char* key, const char* value);
+ void SaveFloat(const char* key, float value);
+ void SaveDouble(const char* key, double value);
void SaveMemory(const LPBYTE pMemBase, const UINT uMemSize);
class Label
diff --git a/source/Z80VICE/z80.cpp b/source/Z80VICE/z80.cpp
index c4b1ba5c..27563cb4 100644
--- a/source/Z80VICE/z80.cpp
+++ b/source/Z80VICE/z80.cpp
@@ -5510,7 +5510,10 @@ static void opcode_fd(BYTE ip1, BYTE ip2, BYTE ip3, WORD ip12, WORD ip23)
/* Z80 mainloop. */
-static const double uZ80ClockMultiplier = CLK_Z80 / CLK_6502;
+// The effective Z-80 clock rate is 2.041MHz
+// See: http://www.apple2info.net/hardware/softcard/SC-SWHW_a2in.pdf
+static const double uZ80ClockMultiplier = 2;
+
inline static ULONG ConvertZ80TStatesTo6502Cycles(UINT uTStates)
{
return (uTStates < 0) ? 0 : (ULONG) ((double)uTStates / uZ80ClockMultiplier);
diff --git a/test/TestCPU6502/TestCPU6502.cpp b/test/TestCPU6502/TestCPU6502.cpp
index 08342e73..2e869469 100644
--- a/test/TestCPU6502/TestCPU6502.cpp
+++ b/test/TestCPU6502/TestCPU6502.cpp
@@ -54,7 +54,7 @@ static __forceinline void DoIrqProfiling(DWORD uCycles)
{
}
-static __forceinline void CheckInterruptSources(ULONG uExecutedCycles)
+static __forceinline void CheckInterruptSources(ULONG uExecutedCycles, const bool bVideoUpdate)
{
}