mirror of
https://github.com/badvision/jace.git
synced 2024-06-14 03:29:34 +00:00
284 lines
10 KiB
Java
284 lines
10 KiB
Java
/*
|
|
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
package jace.hardware.massStorage;
|
|
|
|
import jace.EmulatorUILogic;
|
|
import jace.apple2e.MOS65C02;
|
|
import jace.config.ConfigurableField;
|
|
import jace.config.Name;
|
|
import jace.core.Card;
|
|
import jace.core.Computer;
|
|
import jace.core.RAMEvent;
|
|
import jace.core.RAMEvent.TYPE;
|
|
import jace.core.Utility;
|
|
import jace.hardware.ProdosDriver;
|
|
import jace.hardware.SmartportDriver;
|
|
import jace.library.MediaCache;
|
|
import jace.library.MediaConsumer;
|
|
import jace.library.MediaConsumerParent;
|
|
import jace.library.MediaEntry;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* Hard disk and 800k floppy (smartport) controller card. HDV and 2MG images are
|
|
* both supported.
|
|
*
|
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
|
*/
|
|
@Name("Mass Storage Device")
|
|
public class CardMassStorage extends Card implements MediaConsumerParent {
|
|
|
|
@ConfigurableField(category = "Disk", defaultValue = "", shortName = "d1", name = "Drive 1 disk image", description = "Path of disk 1")
|
|
public String disk1;
|
|
@ConfigurableField(category = "Disk", defaultValue = "", shortName = "d2", name = "Drive 2 disk image", description = "Path of disk 2")
|
|
public String disk2;
|
|
MassStorageDrive drive1;
|
|
MassStorageDrive drive2;
|
|
|
|
public CardMassStorage(Computer computer) {
|
|
super(computer);
|
|
drive1 = new MassStorageDrive();
|
|
drive2 = new MassStorageDrive();
|
|
drive1.setIcon(Utility.loadIconLabel("drive-harddisk.png"));
|
|
drive2.setIcon(Utility.loadIconLabel("drive-harddisk.png"));
|
|
drive1.onInsert(this::reconfigure);
|
|
currentDrive = drive1;
|
|
}
|
|
|
|
@Override
|
|
public void setSlot(int slot) {
|
|
super.setSlot(slot);
|
|
drive1.getIcon().ifPresent(icon -> icon.setText("S" + getSlot() + "D1"));
|
|
drive2.getIcon().ifPresent(icon -> icon.setText("S" + getSlot() + "D2"));
|
|
}
|
|
|
|
@Override
|
|
public String getDeviceName() {
|
|
return "Mass Storage Device";
|
|
}
|
|
// boot0 stores cards*16 of boot device here
|
|
static int SLT16 = 0x02B;
|
|
// "rom" offset where device driver is called by MLI
|
|
// static int DEVICE_DRIVER_OFFSET = 0x042;
|
|
static int DEVICE_DRIVER_OFFSET = 0x0A;
|
|
byte[] cardSignature = new byte[]{
|
|
(byte) 0x0a9 /*NOP*/, 0x020, (byte) 0x0a9, 0x00,
|
|
(byte) 0x0a9, 0x03 /*currentDisk cards*/, (byte) 0x0a9, 0x03c /*currentDisk cards*/,
|
|
(byte) 0xd0, 0x07, 0x60, (byte) 0x0b0,
|
|
0x01 /*firmware cards*/, 0x18, (byte) 0x0b0, 0x5a
|
|
};
|
|
Card theCard = this;
|
|
public MassStorageDrive currentDrive;
|
|
|
|
public IDisk getCurrentDisk() {
|
|
if (currentDrive != null) {
|
|
return currentDrive.getCurrentDisk();
|
|
}
|
|
return null;
|
|
}
|
|
ProdosDriver driver = new ProdosDriver(computer) {
|
|
@Override
|
|
public boolean changeUnit(int unit) {
|
|
currentDrive = unit == 0 ? drive1 : drive2;
|
|
return getCurrentDisk() != null;
|
|
}
|
|
|
|
@Override
|
|
public int getSize() {
|
|
return getCurrentDisk() != null ? getCurrentDisk().getSize() : 0;
|
|
}
|
|
|
|
@Override
|
|
public boolean isWriteProtected() {
|
|
return getCurrentDisk() != null ? getCurrentDisk().isWriteProtected() : true;
|
|
}
|
|
|
|
@Override
|
|
public void mliFormat() throws IOException {
|
|
getCurrentDisk().mliFormat();
|
|
}
|
|
|
|
@Override
|
|
public void mliRead(int block, int bufferAddress) throws IOException {
|
|
getCurrentDisk().mliRead(block, bufferAddress, computer.getMemory());
|
|
}
|
|
|
|
@Override
|
|
public void mliWrite(int block, int bufferAddress) throws IOException {
|
|
getCurrentDisk().mliWrite(block, bufferAddress, computer.getMemory());
|
|
}
|
|
|
|
@Override
|
|
public Card getOwner() {
|
|
return theCard;
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void reconfigure() {
|
|
unregisterListeners();
|
|
if (disk1 != null && !disk1.isEmpty()) {
|
|
try {
|
|
MediaEntry entry = MediaCache.getMediaFromFile(new File(disk1));
|
|
disk1 = null;
|
|
drive1.insertMedia(entry, entry.files.get(0));
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
if (disk2 != null && !disk2.isEmpty()) {
|
|
try {
|
|
MediaEntry entry = MediaCache.getMediaFromFile(new File(disk2));
|
|
disk2 = null;
|
|
drive2.insertMedia(entry, entry.files.get(0));
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
if (computer.getCpu() != null) {
|
|
int pc = computer.getCpu().getProgramCounter();
|
|
if (drive1.getCurrentDisk() != null && getSlot() == 7 && (pc >= 0x0c65e && pc <= 0x0c66F)) {
|
|
// If the computer is in a loop trying to boot from cards 6, fast-boot from here instead
|
|
// This is a convenience to boot a hard-drive if the emulator has started waiting for a currentDisk
|
|
currentDrive = drive1;
|
|
EmulatorUILogic.simulateCtrlAppleReset();
|
|
}
|
|
}
|
|
registerListeners();
|
|
}
|
|
|
|
@Override
|
|
public void reset() {
|
|
}
|
|
|
|
@Override
|
|
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
|
// There is no c8 rom for this card
|
|
}
|
|
|
|
@Override
|
|
protected void handleFirmwareAccess(int offset, TYPE type, int value, RAMEvent e) {
|
|
MOS65C02 cpu = (MOS65C02) computer.getCpu();
|
|
// System.out.println(e.getType()+" "+Integer.toHexString(e.getAddress())+" from instruction at "+Integer.toHexString(cpu.getProgramCounter()));
|
|
if (type.isRead()) {
|
|
// Emulator.getFrame().addIndicator(this, currentDrive.getIcon());
|
|
if (drive1.getCurrentDisk() == null && drive2.getCurrentDisk() == null) {
|
|
e.setNewValue(0);
|
|
return;
|
|
}
|
|
if (type == TYPE.EXECUTE) {
|
|
// Virtual functions, handle accordingly
|
|
String error;
|
|
if (offset == 0x00) {
|
|
// NOP unless otherwise specified
|
|
e.setNewValue(0x0ea);
|
|
try {
|
|
if (drive1.getCurrentDisk() != null) {
|
|
currentDrive = drive1;
|
|
getCurrentDisk().boot0(getSlot(), computer);
|
|
} else {
|
|
// Patch for crash on start when no image is mounted
|
|
e.setNewValue(0x060);
|
|
}
|
|
return;
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
|
error = ex.getMessage();
|
|
// Jump to the basic interpreter for now
|
|
cpu.setProgramCounter(0x0dfff);
|
|
int address = 0x0480;
|
|
for (char c : error.toCharArray()) {
|
|
computer.getMemory().write(address++, (byte) (c + 0x080), false, false);
|
|
}
|
|
}
|
|
} else {
|
|
if (offset == DEVICE_DRIVER_OFFSET) {
|
|
driver.handleMLI();
|
|
} else if (offset == DEVICE_DRIVER_OFFSET + 3) {
|
|
smartport.handleSmartport();
|
|
} else {
|
|
System.out.println("Call to unknown handler " + Integer.toString(e.getAddress(), 16) + "-- returning");
|
|
}
|
|
/* act like RTS was called */
|
|
e.setNewValue(0x060);
|
|
}
|
|
}
|
|
if (offset < 16) {
|
|
e.setNewValue(cardSignature[offset]);
|
|
} else {
|
|
switch (offset) {
|
|
// Disk capacity = 65536 blocks
|
|
case 0x0FC:
|
|
e.setNewValue(0x0ff);
|
|
break;
|
|
case 0x0FD:
|
|
e.setNewValue(0x07f);
|
|
break;
|
|
// Status bits
|
|
case 0x0FE:
|
|
e.setNewValue(0x0D7);
|
|
break;
|
|
case 0x0FF:
|
|
e.setNewValue(DEVICE_DRIVER_OFFSET);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
|
// Ignore IO registers
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
// Nothing is done per CPU cycle
|
|
}
|
|
SmartportDriver smartport = new SmartportDriver(computer) {
|
|
@Override
|
|
public boolean changeUnit(int unitNumber) {
|
|
currentDrive = unitNumber == 1 ? drive1 : drive2;
|
|
return getCurrentDisk() != null;
|
|
}
|
|
|
|
@Override
|
|
public void read(int blockNum, int buffer) throws IOException {
|
|
getCurrentDisk().mliRead(blockNum, buffer, computer.getMemory());
|
|
}
|
|
|
|
@Override
|
|
public void write(int blockNum, int buffer) throws IOException {
|
|
getCurrentDisk().mliWrite(blockNum, buffer, computer.getMemory());
|
|
}
|
|
|
|
@Override
|
|
public ERROR_CODE returnStatus(int dataBuffer, int[] params) {
|
|
return ERROR_CODE.NO_ERROR;
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public MediaConsumer[] getConsumers() {
|
|
return new MediaConsumer[]{drive1, drive2};
|
|
}
|
|
}
|