mirror of
https://github.com/badvision/lawless-legends.git
synced 2024-06-09 23:29:37 +00:00
390 lines
13 KiB
Java
390 lines
13 KiB
Java
/**
|
|
* Copyright 2024 Brendan Robert
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
|
|
package jace.core;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentSkipListSet;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Consumer;
|
|
|
|
import jace.Emulator;
|
|
import jace.apple2e.SoftSwitches;
|
|
import jace.config.Reconfigurable;
|
|
|
|
/**
|
|
* RAM is a 64K address space of paged memory. It also manages sets of memory
|
|
* listeners, used by I/O as well as emulator add-ons (and cheats). RAM also
|
|
* manages cards in the emulator because they are tied into the MMU memory
|
|
* bankswitch logic.
|
|
*
|
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
|
*/
|
|
public abstract class RAM implements Reconfigurable {
|
|
|
|
public PagedMemory activeRead;
|
|
public PagedMemory activeWrite;
|
|
private final Set<RAMListener> listeners = new ConcurrentSkipListSet<>();
|
|
@SuppressWarnings("unchecked")
|
|
private final Set<RAMListener>[] listenerMap = (Set<RAMListener>[]) new Set[256];
|
|
@SuppressWarnings("unchecked")
|
|
private final Set<RAMListener>[] ioListenerMap = (Set<RAMListener>[]) new Set[256];
|
|
@SuppressWarnings("unchecked")
|
|
public Optional<Card>[] cards = (Optional<Card>[]) new Optional[8];
|
|
// card 0 = 80 column card firmware / system rom
|
|
public int activeSlot = 0;
|
|
|
|
/**
|
|
* Creates a new instance of RAM
|
|
*
|
|
* @param computer
|
|
*/
|
|
public RAM() {
|
|
for (int i = 0; i < 8; i++) {
|
|
cards[i] = Optional.empty();
|
|
}
|
|
refreshListenerMap();
|
|
}
|
|
|
|
public void setActiveCard(int slot) {
|
|
if (activeSlot != slot) {
|
|
activeSlot = slot;
|
|
configureActiveMemory();
|
|
} else if (!SoftSwitches.CXROM.getState()) {
|
|
configureActiveMemory();
|
|
}
|
|
}
|
|
|
|
public int getActiveSlot() {
|
|
return activeSlot;
|
|
}
|
|
|
|
public Optional<Card>[] getAllCards() {
|
|
return cards;
|
|
}
|
|
|
|
public Optional<Card> getCard(int slot) {
|
|
if (slot >= 1 && slot <= 7) {
|
|
return cards[slot];
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
public void addCard(Card c, int slot) {
|
|
removeCard(slot);
|
|
cards[slot] = Optional.of(c);
|
|
c.setSlot(slot);
|
|
c.attach();
|
|
configureActiveMemory();
|
|
}
|
|
|
|
public void removeCard(Card c) {
|
|
removeCard(c.getSlot());
|
|
}
|
|
|
|
public void removeCard(int slot) {
|
|
cards[slot].ifPresent(Card::suspend);
|
|
cards[slot].ifPresent(Card::detach);
|
|
cards[slot] = Optional.empty();
|
|
}
|
|
|
|
abstract public void configureActiveMemory();
|
|
public void copyFrom(RAM other) {
|
|
cards = other.cards;
|
|
activeSlot = other.activeSlot;
|
|
listeners.addAll(other.listeners);
|
|
refreshListenerMap();
|
|
configureActiveMemory();
|
|
}
|
|
|
|
public void write(int address, byte b, boolean generateEvent, boolean requireSynchronization) {
|
|
byte[] page = activeWrite.getMemoryPage(address);
|
|
if (page == null) {
|
|
if (generateEvent) {
|
|
callListener(RAMEvent.TYPE.WRITE, address, 0, b, requireSynchronization);
|
|
}
|
|
} else {
|
|
int offset = address & 0x0FF;
|
|
byte old = page[offset];
|
|
if (generateEvent) {
|
|
page[offset] = callListener(RAMEvent.TYPE.WRITE, address, old, b, requireSynchronization);
|
|
} else {
|
|
page[offset] = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void writeWord(int address, int w, boolean generateEvent, boolean requireSynchronization) {
|
|
write(address, (byte) (w & 0x0ff), generateEvent, requireSynchronization);
|
|
write(address + 1, (byte) ((w >> 8) & 0x0ff), generateEvent, requireSynchronization);
|
|
}
|
|
|
|
public byte readRaw(int address) {
|
|
// if (address >= 65536) return 0;
|
|
return activeRead.getMemoryPage(address)[address & 0x0FF];
|
|
}
|
|
|
|
public byte read(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSyncronization) {
|
|
// if (address >= 65536) return 0;
|
|
byte value = activeRead.getMemoryPage(address)[address & 0x0FF];
|
|
// if (triggerEvent || ((address & 0x0FF00) == 0x0C000)) {
|
|
// if (triggerEvent || (address & 0x0FFF0) == 0x0c030) {
|
|
if (triggerEvent) {
|
|
value = callListener(eventType, address, value, value, requireSyncronization);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public int readWordRaw(int address) {
|
|
int lsb = 0x00ff & readRaw(address);
|
|
int msb = (0x00ff & readRaw(address + 1)) << 8;
|
|
return msb + lsb;
|
|
}
|
|
|
|
public int readWord(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSynchronization) {
|
|
int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization);
|
|
int msb = (0x00ff & read(address + 1, eventType, triggerEvent, requireSynchronization)) << 8;
|
|
return msb + lsb;
|
|
}
|
|
|
|
private synchronized void mapListener(RAMListener l, int address) {
|
|
if ((address & 0x0FF00) == 0x0C000) {
|
|
int index = address & 0x0FF;
|
|
Set<RAMListener> ioListeners = ioListenerMap[index];
|
|
if (ioListeners == null) {
|
|
ioListeners = new ConcurrentSkipListSet<>();
|
|
ioListenerMap[index] = ioListeners;
|
|
}
|
|
ioListeners.add(l);
|
|
} else {
|
|
int index = (address >> 8) & 0x0FF;
|
|
Set<RAMListener> otherListeners = listenerMap[index];
|
|
if (otherListeners == null) {
|
|
otherListeners = new ConcurrentSkipListSet<>();
|
|
listenerMap[index] = otherListeners;
|
|
}
|
|
otherListeners.add(l);
|
|
}
|
|
}
|
|
|
|
private void addListenerRange(RAMListener l) {
|
|
if (l.getScope() == RAMEvent.SCOPE.ADDRESS) {
|
|
mapListener(l, l.getScopeStart());
|
|
} else {
|
|
int start = l.getScopeStart();
|
|
int end = l.getScopeEnd();
|
|
if (l.getScope() == RAMEvent.SCOPE.ANY) {
|
|
start = 0;
|
|
end = 0x0FFFF;
|
|
}
|
|
for (int i = start; i <= end; i++) {
|
|
mapListener(l, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void refreshListenerMap() {
|
|
// Wipe out existing maps
|
|
for (int i = 0; i < 256; i++) {
|
|
listenerMap[i] = null;
|
|
ioListenerMap[i] = null;
|
|
}
|
|
listeners.forEach(this::addListenerRange);
|
|
}
|
|
|
|
public RAMListener observeOnce(String observerationName, RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
|
|
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
setScopeStart(address);
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
handler.handleEvent(e);
|
|
unregister();
|
|
}
|
|
});
|
|
}
|
|
|
|
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
|
|
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
setScopeStart(address);
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
handler.handleEvent(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int address, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
|
|
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
setScopeStart(address);
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
if (isAuxFlagCorrect(e, auxFlag)) {
|
|
handler.handleEvent(e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) {
|
|
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
setScopeStart(addressStart);
|
|
setScopeEnd(addressEnd);
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
handler.handleEvent(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
public RAMListener observe(String observerationName, RAMEvent.TYPE type, int addressStart, int addressEnd, Boolean auxFlag, RAMEvent.RAMEventHandler handler) {
|
|
return addListener(new RAMListener(observerationName, type, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
setScopeStart(addressStart);
|
|
setScopeEnd(addressEnd);
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
if (isAuxFlagCorrect(e, auxFlag)) {
|
|
handler.handleEvent(e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private boolean isAuxFlagCorrect(RAMEvent e, Boolean auxFlag) {
|
|
if (e.getAddress() < 0x0100) {
|
|
return SoftSwitches.AUXZP.getState() == auxFlag;
|
|
} else if (e.getAddress() >= 0x0C000 && e.getAddress() <= 0x0CFFF) {
|
|
// I/O page doesn't care about the aux flag
|
|
return true;
|
|
} else return auxFlag == null || SoftSwitches.RAMRD.getState() == auxFlag;
|
|
}
|
|
|
|
public RAMListener addListener(final RAMListener l) {
|
|
if (l == null) {
|
|
return l;
|
|
}
|
|
if (listeners.contains(l)) {
|
|
removeListener(l);
|
|
}
|
|
listeners.add(l);
|
|
Emulator.whileSuspended((c)->addListenerRange(l));
|
|
return l;
|
|
}
|
|
|
|
public RAMListener addExecutionTrap(String observerationName, int address, Consumer<RAMEvent> handler) {
|
|
RAMListener listener = new RAMListener(observerationName, RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
setScopeStart(address);
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
handler.accept(e);
|
|
}
|
|
};
|
|
addListener(listener);
|
|
return listener;
|
|
}
|
|
|
|
public void removeListener(final RAMListener l) {
|
|
if (l == null) {
|
|
return;
|
|
}
|
|
if (!listeners.contains(l)) {
|
|
return;
|
|
}
|
|
listeners.remove(l);
|
|
Emulator.whileSuspended(c->refreshListenerMap());
|
|
}
|
|
|
|
private byte _callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue) {
|
|
Set<RAMListener> activeListeners;
|
|
if ((address & 0x0FF00) == 0x0C000) {
|
|
activeListeners = ioListenerMap[address & 0x0FF];
|
|
if (activeListeners == null && t.isRead()) {
|
|
return Emulator.withComputer(c->c.getVideo().getFloatingBus(), (byte) 0);
|
|
}
|
|
} else {
|
|
activeListeners = listenerMap[(address >> 8) & 0x0ff];
|
|
}
|
|
if (activeListeners != null && !activeListeners.isEmpty()) {
|
|
RAMEvent e = new RAMEvent(t, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, address, oldValue, newValue);
|
|
activeListeners.forEach((l) -> l.handleEvent(e));
|
|
if (!e.isIntercepted() && (address & 0x0FF00) == 0x0C000) {
|
|
return Emulator.withComputer(c->c.getVideo().getFloatingBus(), (byte) 0);
|
|
}
|
|
return (byte) e.getNewValue();
|
|
}
|
|
return (byte) newValue;
|
|
}
|
|
|
|
public byte callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue, boolean requireSyncronization) {
|
|
if (requireSyncronization && Emulator.withComputer(c->c.getMotherboard().isDeviceThread(), false)) {
|
|
AtomicInteger returnValue = new AtomicInteger();
|
|
Emulator.withComputer(computer -> {
|
|
computer.getCpu().whileSuspended(()-> returnValue.set(_callListener(t, address, oldValue, newValue)));
|
|
_callListener(t, address, oldValue, newValue);
|
|
});
|
|
return (byte) returnValue.get();
|
|
} else {
|
|
return _callListener(t, address, oldValue, newValue);
|
|
}
|
|
}
|
|
|
|
abstract protected void loadRom(String path) throws IOException;
|
|
|
|
abstract public void attach();
|
|
|
|
public void detach() {
|
|
detachListeners.forEach(Runnable::run);
|
|
}
|
|
|
|
abstract public void performExtendedCommand(int i);
|
|
|
|
abstract public String getState();
|
|
|
|
abstract public void resetState();
|
|
|
|
List<Runnable> detachListeners = new ArrayList<>();
|
|
public void onDetach(Runnable r) {
|
|
detachListeners.add(r);
|
|
}
|
|
}
|