]> Shamusworld >> Repos - apple2/blobdiff - src/harddrive.cpp
Miscellaneous bugfixes.
[apple2] / src / harddrive.cpp
index 0e6a99487bf7900d88b1289603a13d97eea4182a..8dede0d5073ea0925f6de69e8645ea478506e648 100644 (file)
 //
 // First 1K is the driver ROM, repeated four times.  After that, there are 31 1K
 // chunks that are addressed in the $CC00-$CFFF address range; $C800-$CBFF is a
-// 1K RAM space (internally, it's an 8K static RAM).
+// 1K RAM space (8K static RAM, bank switched).
 //
 
 #include "harddrive.h"
 #include "apple2.h"
-#include "firmware.h"
+#include "dis65c02.h"
+#include "fileio.h"
+#include "firmware/a2hs-scsi.h"
+#include "log.h"
 #include "mmu.h"
+#include "settings.h"
+#include "v65c02.h"            // For dumpDis...
 
 
-void InstallHardDrive(uint8_t slot)
+static uint8_t romBank = 0;
+static uint8_t ramBank = 0;
+static uint8_t deviceID = 7;
+static bool dmaSwitch = false;
+static uint8_t staticRAM[0x2000] = { 0 };
+static uint8_t reg[16];
+
+// Stuff that will have to GTFO of here
+static uint8_t * hdData = NULL;
+
+enum {
+       DVM_DATA_OUT = 0, DVM_DATA_IN = 1, DVM_COMMAND = 2, DVM_STATUS = 3,
+       DVM_MESSAGE_OUT = 6, DVM_MESSAGE_IN = 7, DVM_BUS_FREE = 8,
+       DVM_ARBITRATE = 16, DVM_SELECT = 32
+};
+
+static bool DATA_BUS = false;
+static bool DMA_MODE = false;
+static bool BSY = false;
+static bool ATN = false;
+static bool SEL = false;
+static bool ACK = false;
+static bool RST = false;
+static bool MSG = false;
+static bool C_D = false;
+static bool I_O = false;
+static bool REQ = false;
+static bool DEV_BSY = false;
+static bool DRQ = false;
+static bool DACK = false;
+static uint8_t devMode = DVM_BUS_FREE;
+static uint8_t cmdLength;
+static uint8_t cmd[256];
+static uint32_t bytesToSend;
+static uint8_t * buf;
+static uint32_t bufPtr;
+static uint8_t response;
+
+
+static inline void SetNextState(uint8_t state)
+{
+       devMode = state;
+       MSG = (state & 0x04 ? true : false);
+       C_D = (state & 0x02 ? true : false);
+       I_O = (state & 0x01 ? true : false);
+       cmdLength = 0;
+}
+
+
+static void RunDevice(void)
+{
+       // Let's see where it's really going...
+/*     if (mainCPU.pc == 0xCE7E)
+               dumpDis = true;//*/
+
+       // These are SCSI messages sent in response to certain commands
+       static uint8_t readCapacity[8] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 };
+       static uint8_t inquireData[30] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'S', 'E', 'A', 'G', 'A', 'T', 'E', ' ', '3', '1', '3', '3', '7', ' ' };
+       static uint8_t badSense[20] = { 0x70, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+       if (RST)
+       {
+               devMode = DVM_BUS_FREE;
+               DEV_BSY = false;
+               return;
+       }
+
+       switch (devMode)
+       {
+       case DVM_BUS_FREE:
+               // We never initiate, so this we don't worry about whether or not the
+               // bus is free.
+       case DVM_ARBITRATE:
+               // Likewise, we don't arbitrate either.
+               break;
+
+       case DVM_SELECT:
+               // If we're in Selection phase, see if our ID is on the bus, and, if so,
+               // go on to the next phase (since the Target drives the phase dance).
+               if ((reg[0] & 0x40) && DATA_BUS)
+               {
+                       DEV_BSY = true;
+
+                       // Preset response code to "Good"
+                       response = 0x00;
+
+                       if (ATN)
+                               SetNextState(DVM_MESSAGE_OUT);
+                       else
+                               // If no ATN is asserted, go to COMMAND?  Dunno, the firmware
+                               // doesn't ever go there; it *always* starts with MESSAGE OUT.
+                               SetNextState(DVM_COMMAND);
+               }
+
+               break;
+
+       case DVM_DATA_OUT:
+//WriteLog("   >>> DATA OUT PHASE (bts=%u)\n", bytesToSend);
+               if (!ACK)
+                       REQ = true;
+
+               if (DMA_MODE)
+               {
+                       if (!DACK)
+                       {
+                               DRQ = true;
+                       }
+                       else if (DRQ && DACK)
+                       {
+                               if (buf)
+                                       buf[bufPtr] = reg[0];
+
+                               DRQ = DACK = false;
+                               bytesToSend--;
+                               bufPtr++;
+
+                               if (bytesToSend == 0)
+                               {
+                                       REQ = false;
+                                       SetNextState(DVM_STATUS);
+                                       buf = NULL;
+                               }
+                       }
+               }
+
+               break;
+
+       case DVM_DATA_IN:
+//WriteLog("   >>> DATA IN PHASE (bts=%u)\n", bytesToSend);
+               if (!ACK)
+                       REQ = true;
+
+               if (DMA_MODE)
+               {
+                       if (!DACK)
+                       {
+                               // If there's no buffer set up, send zeroes...
+                               reg[6] = (buf == NULL ? 0 : buf[bufPtr]);
+                               DRQ = true;
+                       }
+                       else if (DRQ && DACK)
+                       {
+                               DRQ = DACK = false;
+                               bytesToSend--;
+                               bufPtr++;
+
+                               if (bytesToSend == 0)
+                               {
+                                       REQ = false;
+                                       SetNextState(DVM_STATUS);
+                                       buf = NULL;
+                               }
+                       }
+               }
+
+               break;
+
+       case DVM_COMMAND:
+       {
+               if (!ACK)
+                       REQ = true;
+               else if (REQ && ACK)
+               {
+                       cmd[cmdLength++] = reg[0];
+                       REQ = false;
+               }
+
+               uint8_t cmdType = (cmd[0] & 0xE0) >> 5;
+
+               if ((cmdType == 0) && (cmdLength == 6))
+               {
+                       // Handle "Test Unit Ready" command
+                       if (cmd[0] == 0)
+                       {
+                               WriteLog("HD: Received command TEST UNIT READY\n");
+                               SetNextState(DVM_STATUS);
+                       }
+                       // Handle "Request Sense" command
+                       else if (cmd[0] == 0x03)
+                       {
+                               WriteLog("HD: Received command REQUEST SENSE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
+                               SetNextState(DVM_DATA_IN);
+                               bytesToSend = cmd[4];
+
+                               // Return error for LUNs other than 0
+                               if ((cmd[1] & 0xE0) != 0)
+                               {
+                                       buf = badSense;
+                                       bufPtr = 0;
+                               }
+                       }
+                       // Handle "Read" (6) command
+                       else if (cmd[0] == 0x08)
+                       {
+                               WriteLog("HD: Received command READ(6) [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
+                               SetNextState(DVM_DATA_IN);
+                               bytesToSend = cmd[4] * 512; // amount is set in blocks
+                               uint32_t lba = ((cmd[1] & 0x1F) << 16) | (cmd[2] << 8) | cmd[3];
+                               buf = (hdData != NULL ? &hdData[lba * 512] : NULL);
+                               bufPtr = 0;
+                       }
+                       // Handle "Inquire" command
+                       else if (cmd[0] == 0x12)
+                       {
+                               WriteLog("HD: Received command INQUIRE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
+                               SetNextState(DVM_DATA_IN);
+                               bytesToSend = cmd[4];
+                               buf = inquireData;
+                               bufPtr = 0;
+
+                               // Reject all but LUN 0
+                               if ((cmd[1] & 0xE0) != 0)
+                                       response = 0x02; // "Check Condition" code
+                       }
+                       // Handle "Mode Select" command
+                       else if (cmd[0] == 0x15)
+                       {
+                               WriteLog("HD: Received command MODE SELECT [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
+                               SetNextState(DVM_DATA_OUT);
+                               bytesToSend = cmd[4];
+                       }
+                       // Handle "Mode Sense" command
+                       else if (cmd[0] == 0x1A)
+                       {
+                               WriteLog("HD: Received command MODE SENSE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
+                               SetNextState(DVM_DATA_IN);
+                               bytesToSend = cmd[4];
+                       }
+                       else
+                       {
+                               WriteLog("HD: Received unhandled 6 command [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
+                               // Return failure...
+                               SetNextState(DVM_STATUS);
+                               response = 0x02; // Check condition code
+                       }
+               }
+               else if (((cmdType == 1) || (cmdType == 2)) && (cmdLength == 10))
+               {
+                       // Handle "Read Capacity" command
+                       if (cmd[0] == 0x25)
+                       {
+                               WriteLog("HD: Received command READ CAPACITY [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]);
+                               SetNextState(DVM_DATA_IN);
+                               bytesToSend = 8;//it's always 8...//cmd[4];
+                               // N.B.: We need to hook this up to the actual emulated HD size...
+                               buf = readCapacity;
+                               bufPtr = 0;
+                       }
+                       // Handle "Read" (10) command
+                       else if (cmd[0] == 0x28)
+                       {
+                               WriteLog("HD: Received command READ(10) [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]);
+                               // Drive next phase
+                               SetNextState(DVM_DATA_IN);
+                               bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
+                               uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
+                               buf = (hdData != NULL ? &hdData[lba * 512] : NULL);
+                               bufPtr = 0;
+                       }
+                       // Handle "Write" (10) command
+                       else if (cmd[0] == 0x2A)
+                       {
+                               WriteLog("HD: Received command WRITE(10) [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]);
+                               // Drive next phase
+                               SetNextState(DVM_DATA_OUT);
+                               bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
+                               uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
+                               buf = (hdData != NULL ? &hdData[lba * 512] : NULL);
+                               bufPtr = 0;
+                       }
+                       else
+                       {
+                               WriteLog("HD: Received unhandled 10 command [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]);
+                               // Return failure...
+                               SetNextState(DVM_STATUS);
+                               response = 0x02; // "Check Condition" code
+                       }
+               }
+               else if ((cmdType == 5) && (cmdLength == 12))
+               {
+                       WriteLog("HD: Received unhandled 12 command [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9], cmd[10], cmd[11]);
+                       // Return failure...
+                       SetNextState(DVM_STATUS);
+                       response = 0x02; // "Check Condition" code
+               }
+
+               break;
+       }
+
+       case DVM_STATUS:
+               if (!ACK)
+               {
+                       // Return A-OK for everything for now...
+                       reg[0] = 0; // N.B.: This is necessary for some reason...
+                       REQ = true;
+               }
+               else if (REQ && ACK)
+               {
+                       REQ = false;
+                       SetNextState(DVM_MESSAGE_IN);
+               }
+
+               break;
+
+       case DVM_MESSAGE_OUT:
+               if (!ACK)
+                       REQ = true;
+               if (REQ && ACK)
+               {
+//                     WriteLog("HD: Write to target value $%02X\n", reg[0]);
+                       REQ = false;
+                       SetNextState(DVM_COMMAND);
+               }
+
+               break;
+
+       case DVM_MESSAGE_IN:
+               if (!ACK)
+               {
+                       // Return appropriate response
+                       reg[0] = response;
+                       REQ = true;
+               }
+               else if (REQ && ACK)
+               {
+                       REQ = false;
+                       DEV_BSY = false;
+                       SetNextState(DVM_BUS_FREE);
+               }
+
+               break;
+       }
+}
+
+
+static uint8_t SlotIOR(uint16_t address)
+{
+       // This should prolly go somewhere else...
+       RunDevice();
+
+       uint8_t response = reg[address & 0x0F];
+
+       switch (address & 0x0F)
+       {
+               case 0x00:
+                       // (RO) Current SCSI Data register
+                       break;
+               case 0x01:
+                       // Initiator Command register.  Bits, from hi to lo:
+                       // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
+
+                       // Simulate ARBITRATE signal
+                       if (reg[2] & 0x01)
+                               response |= 0x40;
+
+                       break;
+               case 0x02:
+                       // Mode register (chip control)
+                       break;
+               case 0x03:
+                       // Target Command register (SCSI bus info xfer phase)
+                       break;
+               case 0x04:
+                       // (RO) Current SCSI Bus Status register:  Bits from hi to lo:
+                       // /RST, /BSY, /REQ, /MSG, /C/D, /I/O, /SEL, /DBP
+/*if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (romBank != 16))
+       WriteLog("  [%02X %02X %02X %02X %02X %02X %02X %02X] [$C81F=$%02X $C80D=$%02X $C80A=$%02X $C887=$%02X $C806=$%02X $C88F=$%02X $C8EC=$%02X $4F=$%02X]\n", reg[0], reg[1], reg[2], reg[3], reg[4], reg[5], reg[6], reg[7], staticRAM[0x1F], staticRAM[0x0D], staticRAM[0x0A], staticRAM[0x87], staticRAM[0x06], staticRAM[0x8F], staticRAM[0xEC], ram[0x4F]);//*/
+
+                       response = (RST ? 0x80 : 0) | (BSY | DEV_BSY ? 0x40 : 0) | (REQ ? 0x20 : 0) | (MSG ? 0x10 : 0) | (C_D ? 0x08 : 0) | (I_O ? 0x04 : 0) | (SEL ? 0x02 : 0);
+                       break;
+               case 0x05:
+               {
+                       // (RO) Bus and Status register
+                       response = (ACK ? 0x01 : 0) | (ATN ? 0x02 : 0) | (DRQ ? 0x40 : 0);
+                       uint8_t tgtMode = (MSG ? 0x04 : 0) | (C_D ? 0x02 : 0) | (I_O ? 0x01 : 0);
+
+                       if ((reg[3] & 0x07) == tgtMode)
+                               response |= 0x08;
+
+                       break;
+               }
+               case 0x06:
+                       // (RO) Input Data register (read from from SCSI bus)
+                       if (DRQ)
+                               DACK = true;
+
+                       break;
+               case 0x07:
+                       // (RO) Reset Parity/Interrupt
+                       // Resets PARITY ERR (bit 6), IRQ (bit 5), BUSY ERROR (bit 3) in
+                       // register 5 (Bus & Status)
+                       break;
+               case 0x0C:
+                       response = 0x10 | (dmaSwitch ? 0x40 : 0);
+                       break;
+               case 0x0E:
+                       response = romBank | (deviceID << 5);
+                       break;
+       }
+
+#if 0
+       char SCSIName[16][256] = {
+               "(RO) Current SCSI Data",
+               "Initiator Command",
+               "Mode",
+               "Target Command",
+               "(RO) Current SCSI Bus Status",
+               "(RO) Bus and Status",
+               "(RO) Input Data",
+               "(RO) Reset Parity/Interrupt",
+               "DMA Address LO",
+               "DMA Address HI",
+               "DMA Count LO",
+               "DMA Count HI",
+               "$C",
+               "$D",
+               "Bank/SCSI ID",
+               "$F"
+       };
+
+       if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (romBank != 16))
+               WriteLog("HD Slot I/O read %s ($%02X <- $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], response, address & 0x0F, mainCPU.pc, romBank);
+#endif
+
+       return response;
+}
+
+
+static void SlotIOW(uint16_t address, uint8_t byte)
 {
+       switch (address & 0x0F)
+       {
+               case 0x00:
+                       // (WO) Output Data register (data sent over SCSI bus)
+                       if (DRQ)
+                               DACK = true;
+
+                       break;
+               case 0x01:
+                       // Initiator Command register.  Bits, from hi to lo:
+                       // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
+                       DATA_BUS = (byte & 0x01 ? true : false);
+                       ATN = (byte & 0x02 ? true : false);
+                       SEL = (byte & 0x04 ? true : false);
+                       BSY = (byte & 0x08 ? true : false);
+                       ACK = (byte & 0x10 ? true : false);
+                       RST = (byte & 0x80 ? true : false);
+
+                       if (!(SEL || BSY || DEV_BSY))
+                               devMode = DVM_BUS_FREE;
+
+                       if (SEL && (devMode == DVM_ARBITRATE))
+                               devMode = DVM_SELECT;
+
+                       break;
+               case 0x02:
+                       // Mode register (chip control)
+                       if ((byte & 0x01) && (devMode == DVM_BUS_FREE))
+                               devMode = DVM_ARBITRATE;
+
+                       // Dma ReQuest is reset here (as well as by hitting a pin)
+                       DMA_MODE = (byte & 0x02 ? true : false);
+
+                       if (!DMA_MODE)
+                               DRQ = DACK = false;
+
+                       break;
+               case 0x03:
+                       // Target Command register (SCSI bus info xfer phase)
+                       break;
+               case 0x04:
+                       // (WO) Select Enable register
+                       break;
+               case 0x05:
+                       // (WO) Start DMA Send (initiates DMA send)
+                       DRQ = true;
+                       break;
+               case 0x06:
+                       // (WO) Start DMA Target Receive (initiate DMA receive--tgt mode)
+                       DRQ = true;
+                       break;
+               case 0x07:
+                       // (WO) Start DMA Initiator Receive (initiate DMA receive--ini mode)
+                       DRQ = true;
+                       break;
+               case 0x08:
+                       // Lo byte of DMA address?
+                       break;
+               case 0x09:
+                       // Hi byte of DMA address?
+                       break;
+               case 0x0A:
+                       // 2's complement of lo byte of transfer amount?
+                       break;
+               case 0x0B:
+                       // 2's complement of hi byte of transfer amount?
+                       break;
+               case 0x0C:
+                       // Control/status register?
+                       break;
+               case 0x0D:
+                       // ???
+                       break;
+               case 0x0E:
+                       // Bottom 5 bits of $E set the ROM bank
+                       romBank = byte & 0x1F;
+                       break;
+               case 0x0F:
+                       // Bottom 3 bits of $F set the RAM bank
+                       ramBank = byte & 0x07;
+                       break;
+       }
+
+       reg[address & 0x0F] = byte;
+
+#if 0
+       char SCSIName[16][256] = {
+               "(WO) Output Data",
+               "Initiator Command",
+               "Mode",
+               "Target Command",
+               "(WO) Select Enable",
+               "(WO) Start DMA Send",
+               "(WO) Start DMA Target Receive",
+               "(WO) Start DMA Initiator Receive",
+               "DMA Address LO",
+               "DMA Address HI",
+               "DMA Count LO",
+               "DMA Count HI",
+               "$C",
+               "$D",
+               "Bank/SCSI ID",
+               "$F"
+       };
+       char SCSIPhase[11][256] = { "DATA OUT", "DATA IN", "COMMAND", "STATUS", "ERR4", "ERR5", "MESSAGE OUT", "MESSAGE IN", "BUS FREE", "ARBITRATE", "SELECT" };
+
+
+       WriteLog("HD Slot I/O write %s ($%02X -> $%X, PC=%04X:%u) [%s]\n", SCSIName[address & 0x0F], byte, address & 0x0F, mainCPU.pc, romBank, SCSIPhase[devMode]);
+
+       if ((address & 0x0F) == 0x0E)
+       {
+               if (mainCPU.pc == 0xC78B)
+               {
+                       uint16_t sp = mainCPU.sp;
+                       uint16_t pc = ram[0x100 + sp + 1] | (ram[0x100 + sp + 2] << 8);
+                       WriteLog("   *** Returning to bank %u, $%04X\n", romBank, pc + 1);
+               }
+               else if (mainCPU.pc == 0xC768)
+               {
+                       WriteLog("   *** Calling to bank %u:%u\n", mainCPU.a, (mainCPU.y & 0xE0) >> 5);
+               }
+
+               WriteLog("  [%02X %02X %02X %02X %02X %02X %02X %02X] [$C81F=$%02X $C80D=$%02X $C80A=$%02X $C887=$%02X $C806=$%02X $C88F=$%02X $C8EC=$%02X $4F=$%02X]\n", reg[0], reg[1], reg[2], reg[3], reg[4], reg[5], reg[6], reg[7], staticRAM[0x1F], staticRAM[0x0D], staticRAM[0x0A], staticRAM[0x87], staticRAM[0x06], staticRAM[0x8F], staticRAM[0xEC], ram[0x4F]);
+       }
+#endif
+
+       // This should prolly go somewhere else...
+       RunDevice();
 }
 
+
+static uint8_t SlotROM(uint16_t address)
+{
+       return a2hsScsiROM[address];
+}
+
+
+static uint8_t SlotIOExtraR(uint16_t address)
+{
+       if (address < 0x400)
+               return staticRAM[(ramBank * 0x400) + address];
+       else
+               return a2hsScsiROM[(romBank * 0x400) + address - 0x400];
+}
+
+
+static void SlotIOExtraW(uint16_t address, uint8_t byte)
+{
+       if (address < 0x400)
+               staticRAM[(ramBank * 0x400) + address] = byte;
+       else
+//     {
+               WriteLog("HD: Unhandled HD 1K ROM write ($%02X) @ $C%03X...\n", byte, address + 0x800);
+
+/*             if ((mainCPU.pc == 0xCDDD) && (romBank == 11))
+                       dumpDis = true;//*/
+//     }
+}
+
+
+void InstallHardDrive(uint8_t slot)
+{
+       SlotData hd = { SlotIOR, SlotIOW, SlotROM, 0, SlotIOExtraR, SlotIOExtraW };
+       InstallSlotHandler(slot, &hd);
+       char fnBuf[(MAX_PATH * 2) + 1];
+
+       // If this fails to read the file, the pointer is set to NULL
+       uint32_t size = 0, skip = (uint32_t)-1;
+       sprintf(fnBuf, "%s%s", settings.disksPath, settings.hd[0]);
+
+       // Check to see which type of HD image we have...
+       char * ext = strrchr(settings.hd[0], '.');
+
+       if (ext != NULL)
+       {
+               if (strcasecmp(ext, ".2mg") == 0)
+                       skip = 0x40;
+               else if (strcasecmp(ext, ".hdv") == 0)
+                       skip = 0;
+       }
+
+       if (skip == (uint32_t)-1)
+       {
+               hdData = NULL;
+               WriteLog("HD: Unknown HD image file: %s\n", settings.hd[0]);
+               return;
+       }
+
+       hdData = ReadFile(fnBuf, &size, skip);
+
+       if (hdData)
+               WriteLog("HD: Read Hard Drive image file '%s', %u bytes ($%X)\n", settings.hd[0], size, size);
+       else
+               WriteLog("HD: Could not read Hard Drive image file!\n");
+}