Compare commits

...

7 Commits

Author SHA1 Message Date
Peter Ferrie
3a980611d7
Merge d4d3c59b8c into b9ae1990e8 2024-03-15 10:32:05 -05:00
Brendan Robert
b9ae1990e8 Changed timing strategy, got ~2x better performance 2024-03-15 10:32:01 -05:00
Brendan Robert
f9114248d1 Metacheat speedup; program identity experimental feature 2024-03-15 00:34:15 -05:00
Peter Ferrie
d4d3c59b8c defer case check to be faster 2023-09-03 15:08:16 -07:00
Peter Ferrie
c540527370 another fix 2023-09-03 14:42:51 -07:00
Peter Ferrie
3f359cf3ff fix a couple of bugs, compare a bit faster 2023-09-03 14:41:10 -07:00
Peter Ferrie
b258ed441a decompress faster 2023-09-02 11:30:15 -07:00
12 changed files with 285 additions and 89 deletions

View File

@ -63,7 +63,7 @@ public class Apple2e extends Computer {
static int IRQ_VECTOR = 0x003F2; static int IRQ_VECTOR = 0x003F2;
@ConfigurableField(name = "Production mode", shortName = "production") @ConfigurableField(name = "Production mode", shortName = "production")
public boolean PRODUCTION_MODE = true; public boolean PRODUCTION_MODE = false;
@ConfigurableField(name = "Slot 1", shortName = "s1card") @ConfigurableField(name = "Slot 1", shortName = "s1card")
public DeviceSelection<Cards> card1 = new DeviceSelection<>(Cards.class, null); public DeviceSelection<Cards> card1 = new DeviceSelection<>(Cards.class, null);
@ConfigurableField(name = "Slot 2", shortName = "s2card") @ConfigurableField(name = "Slot 2", shortName = "s2card")

View File

@ -1246,13 +1246,11 @@ public class MOS65C02 extends CPU {
} }
public String getState() { public String getState() {
return String.format("%s %s %s 01%s %s", return byte2(A) +
byte2(A), " " + byte2(X) +
byte2(X), " " + byte2(Y) +
byte2(Y), " 01" + byte2(STACK) +
byte2(STACK), getFlags();
getFlags()
);
} }
public String getFlags() { public String getFlags() {

View File

@ -223,7 +223,7 @@ abstract public class RAM128k extends RAM {
if (SoftSwitches.INTC8ROM.isOn()) { if (SoftSwitches.INTC8ROM.isOn()) {
rstate += "C8"; rstate += "C8";
} else { } else {
rstate += String.format("C8%d", getActiveSlot()); rstate += "C8"+getActiveSlot();
} }
} }

View File

@ -39,7 +39,8 @@ public abstract class Cheats extends Device {
Metacheat("Metacheat", MetaCheat.class, MetaCheat::new), Metacheat("Metacheat", MetaCheat.class, MetaCheat::new),
MontezumasRevenge("Montezuma's Revenge", MontezumasRevengeCheats.class, MontezumasRevengeCheats::new), MontezumasRevenge("Montezuma's Revenge", MontezumasRevengeCheats.class, MontezumasRevengeCheats::new),
PrinceOfPersia("Prince of Persia", PrinceOfPersiaCheats.class, PrinceOfPersiaCheats::new), PrinceOfPersia("Prince of Persia", PrinceOfPersiaCheats.class, PrinceOfPersiaCheats::new),
LawlessHacks("Lawless Legends Enhancements", LawlessHacks.class, LawlessHacks::new); LawlessHacks("Lawless Legends Enhancements", LawlessHacks.class, LawlessHacks::new),
ProgramIdentity("Identify program", ProgramIdentity.class, ProgramIdentity::new);
Supplier<Cheats> factory; Supplier<Cheats> factory;
String name; String name;

View File

@ -6,6 +6,7 @@
package jace.cheat; package jace.cheat;
import java.util.ArrayList; import java.util.ArrayList;
import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
@ -77,6 +78,15 @@ public class MemoryCell implements Comparable<MemoryCell> {
return address - o.address; return address - o.address;
} }
@Override
public boolean equals(Object o) {
if (o instanceof MemoryCell) {
MemoryCell om = (MemoryCell) o;
return address == om.address || (x == om.x && y == om.y);
}
return false;
}
public boolean hasCounts() { public boolean hasCounts() {
return hasCount.get(); return hasCount.get();
} }

View File

@ -64,7 +64,7 @@ public class MetaCheat extends Cheats {
public int historyLength = 10; public int historyLength = 10;
private int startAddress = 0; private int startAddress = 0;
private int endAddress = 0x0ffff; private int endAddress = 0x0BFFF;
private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress)); private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress));
private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress)); private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress));
private boolean byteSized = true; private boolean byteSized = true;
@ -248,26 +248,33 @@ public class MetaCheat extends Cheats {
int last = result.lastObservedValue; int last = result.lastObservedValue;
result.lastObservedValue = val; result.lastObservedValue = val;
switch (searchType) { switch (searchType) {
case VALUE: case VALUE -> {
int compare = parseInt(searchValueProperty.get()); int compare = parseInt(searchValueProperty.get());
return compare != val; return compare != val;
case CHANGE: }
case CHANGE -> {
switch (searchChangeType) { switch (searchChangeType) {
case AMOUNT: case AMOUNT -> {
int amount = parseInt(searchChangeByProperty().getValue()); int amount = parseInt(searchChangeByProperty().getValue());
return (val - last) != amount; return (val - last) != amount;
case GREATER: }
case GREATER -> {
return val <= last; return val <= last;
case ANY_CHANGE: }
case ANY_CHANGE -> {
return val == last; return val == last;
case LESS: }
case LESS -> {
return val >= last; return val >= last;
case NO_CHANGE: }
case NO_CHANGE -> {
return val != last; return val != last;
}
} }
break; }
case TEXT:
break; case TEXT -> {
}
} }
return false; return false;
}); });
@ -309,15 +316,9 @@ public class MetaCheat extends Cheats {
memoryCells.values().stream() memoryCells.values().stream()
.filter(MemoryCell::hasCounts) .filter(MemoryCell::hasCounts)
.forEach((cell) -> { .forEach((cell) -> {
if (cell.execCount.get() > 0) { cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate));
cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate)); cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate));
} cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate));
if (cell.readCount.get() > 0) {
cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate));
}
if (cell.writeCount.get() > 0) {
cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate));
}
if (MemoryCell.listener != null) { if (MemoryCell.listener != null) {
MemoryCell.listener.changed(null, cell, cell); MemoryCell.listener.changed(null, cell, cell);
} }

View File

@ -0,0 +1,119 @@
package jace.cheat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.CRC32;
import jace.apple2e.RAM128k;
import jace.apple2e.SoftSwitches;
import jace.core.PagedMemory;
import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE;
public class ProgramIdentity extends Cheats {
private Map<String, String> programIdentities;
@Override
public void registerListeners() {
addCheat("Track execution", TYPE.ANY, this::trackActivity, 0, 0x0ffff);
}
@Override
protected String getDeviceName() {
return "Program Identity";
}
int INTERVAL = 1000000;
int THRESHOLD_VALUE = 10000;
int CLIP_VALUE = THRESHOLD_VALUE * 2;
int DECAY = THRESHOLD_VALUE / 2;
int[] programRegions = new int[512];
private void trackActivity(RAMEvent e) {
int bank = e.getAddress() >> 8;
if (bank >= 0xc0 && bank < 0xd0) {
// Skip I/O region
return;
}
// Detect language card ram execution
if (bank >= 0xd0 && SoftSwitches.LCRAM.isOff()) {
// Skip rom execution
return;
}
if (!e.isMainMemory()) {
bank += 256;
}
if (e.getType() == RAMEvent.TYPE.EXECUTE) {
programRegions[bank] = Math.min(CLIP_VALUE, programRegions[bank] + 1);
} else if (e.getType() == RAMEvent.TYPE.WRITE) {
programRegions[bank] = 0;
}
}
private String generateChecksum() {
CRC32 crc = new CRC32();
RAM128k ram = (RAM128k) getMemory();
int bankCount = 0;
for (int i=0; i < 512; i++) {
if (programRegions[i] > THRESHOLD_VALUE) {
PagedMemory mem = ram.getMainMemory();
if (i >= 0x0d0 && i < 0x0100) {
mem = ram.getLanguageCard();
} else if (i >= 0x0100 && i < 0x01d0) {
mem = ram.getAuxMemory();
} else if (i >= 0x01d0) {
mem = ram.getAuxLanguageCard();
}
bankCount++;
crc.update(mem.getMemoryPage((i & 0x0ff) << 8));
}
}
return Long.toHexString(crc.getValue())+"-"+bankCount;
}
@Override
public void resume() {
super.resume();
Arrays.fill(programRegions, 0);
readProgramIdentities();
}
int counter = 0;
String lastChecksum = "";
@Override
public void tick() {
if (counter++ >= INTERVAL) {
String checksum = generateChecksum();
if (!checksum.equals(lastChecksum)) {
String identity = programIdentities.getOrDefault(checksum, "UNKNOWN");
System.out.println(checksum + "," + identity);
lastChecksum = checksum;
}
counter = 0;
for (int i=0; i < 512; i++) {
programRegions[i] = Math.max(0, programRegions[i] - DECAY);
}
}
}
private void readProgramIdentities() {
// Read from resources file
InputStream in = Cheats.class.getResourceAsStream("/jace/cheats/program-identities.txt");
try {
programIdentities = new HashMap<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
programIdentities.put(parts[0], parts[1]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -100,10 +100,20 @@ public abstract class IndependentTimedDevice extends TimedDevice {
} }
} }
public static int SLEEP_PRECISION_LIMIT = 100;
public void sleepUntil(Long time) { public void sleepUntil(Long time) {
if (time != null) { if (time != null) {
while (System.nanoTime() < time) { while (System.nanoTime() < time) {
Thread.onSpinWait(); int waitTime = (int) ((time - System.nanoTime()) / 1000000);
if (waitTime >= SLEEP_PRECISION_LIMIT) {
try {
Thread.sleep(waitTime);
} catch (InterruptedException ex) {
return;
}
} else {
Thread.onSpinWait();
}
} }
} }
} }

View File

@ -1,9 +1,8 @@
package jace.ui; package jace.ui;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.Map;
import java.util.Set; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -384,8 +383,7 @@ public class MetacheatUI {
public static final int MEMORY_BOX_TOTAL_SIZE = (MEMORY_BOX_SIZE + MEMORY_BOX_GAP); public static final int MEMORY_BOX_TOTAL_SIZE = (MEMORY_BOX_SIZE + MEMORY_BOX_GAP);
public int memoryViewColumns; public int memoryViewColumns;
public int memoryViewRows; public int memoryViewRows;
public static Map<Integer, MemoryCell> redrawNodes = new ConcurrentSkipListMap<>();
public static Set<MemoryCell> redrawNodes = new ConcurrentSkipListSet<>();
ScheduledExecutorService animationTimer = null; ScheduledExecutorService animationTimer = null;
@SuppressWarnings("all") @SuppressWarnings("all")
ScheduledFuture animationFuture = null; ScheduledFuture animationFuture = null;
@ -432,13 +430,11 @@ public class MetacheatUI {
} }
private void processMemoryViewUpdates() { private void processMemoryViewUpdates() {
boolean isRunning = Emulator.withComputer(c->c.getRunningProperty().get(), false); boolean isRunning = Emulator.withComputer(c->c.getMotherboard().isRunning(), false);
if (!isRunning) return; if (!isRunning) return;
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D(); GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
Set<MemoryCell> draw = new HashSet<>(redrawNodes);
redrawNodes.clear();
Platform.runLater(() -> { Platform.runLater(() -> {
draw.stream().forEach((jace.cheat.MemoryCell cell) -> { redrawNodes.values().stream().forEach((jace.cheat.MemoryCell cell) -> {
if (showValuesCheckbox.isSelected()) { if (showValuesCheckbox.isSelected()) {
int val = cell.value.get() & 0x0ff; int val = cell.value.get() & 0x0ff;
context.setFill(Color.rgb(val, val, val)); context.setFill(Color.rgb(val, val, val));
@ -450,10 +446,11 @@ public class MetacheatUI {
} }
context.fillRect(cell.getX(), cell.getY(), cell.getWidth(), cell.getHeight()); context.fillRect(cell.getX(), cell.getY(), cell.getWidth(), cell.getHeight());
}); });
redrawNodes.clear();
}); });
} }
public static int FRAME_RATE = 1000 / 60; public static int FRAME_RATE = 1000 / 30;
public void redrawMemoryView() { public void redrawMemoryView() {
if (cheatEngine == null) { if (cheatEngine == null) {
@ -468,8 +465,6 @@ public class MetacheatUI {
animationFuture.cancel(false); animationFuture.cancel(false);
} }
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
cheatEngine.initMemoryView(); cheatEngine.initMemoryView();
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE; int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16; memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
@ -490,13 +485,14 @@ public class MetacheatUI {
(int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale), (int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale),
(int) (MEMORY_BOX_SIZE * drawScale), (int) (MEMORY_BOX_SIZE * drawScale),
(int) (MEMORY_BOX_SIZE * drawScale)); (int) (MEMORY_BOX_SIZE * drawScale));
redrawNodes.add(cell); redrawNodes.put(cell.address, cell);
} }
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> { MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
redrawNodes.add(newCell); redrawNodes.put(newCell.address, newCell);
}); });
setZoom(1/drawScale); setZoom(1/drawScale);
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
}); });
} }

View File

@ -0,0 +1,66 @@
0-0,Firmware/Basic/Monitor
a3156625-1,Total Replay
158df73d-4,Miner 2049er
111cb7b9-5,Miner 2049er
d006d71b-6,A City Dies
d0d083aa-2,Alcazar (Title)
c05809d8-2,Alcazar (Menu)
6accc221-4,Alcazar (Game)
7381db02-4,Alien Downpour
580b57a0-5,Alien Downpour
f019024b-6,Alien Downpour (die)
2f1031b5-2,Alien Downpour (fail)
54d3700-3,Arctic Fox (Title)
b7fd2db6-13,Arctic Fox (Game)
d31a0d0a-14,Arctic Fox (Game)
82acd9ae-15,Arctic Fox (Game)
952fea92-16,Arctic Fox (Game)
bd7cf4f7-17,Arctic Fox (Game)
207242ca-2,Agent USA (Title)
9f9e6890-4,Agent USA (Menu)
536dad3-4,Agent USA (Menu)
90a1fd4f-2,Airheart (Title)
9a3b93f7-3,Airheart (Game)
6930a244-2,Apple Invader
aace1907-3,Apple Invader
c5c52757-2,Apple Invader (Title)
288ec83d-1,Apple Invader (Title)
bc3b2166-1,Arkanoid (Intro)
53a99672-7,Arkanoid (Demo)
87d4eab2-3,Arkanoid (Start)
cd93f077-3,Arkanoid (Game)
a6c1d770-3,Arkanoid (Game)
7b85b563-5,Arkanoid (Game)
53a99672-7,Arkanoid (Game)
6af90e52-1,Arkanoid (Game Over)
cde319e5-2,Montezuma's Revenge (Load)
eb28db32-3,Montezuma's Revenge (Title)
f447d42a-3,Montezuma's Revenge (Level Start 1 - Joystick)
312b8850-4,Montezuma's Revenge (Level Start 2 - Joystick)
73031104-5,Montezuma's Revenge (Level Start 3 - Joystick)
4b2cb3da-5,Montezuma's Revenge (Game 1 - Joystick)
72e913f2-3,Montezuma's Revenge (Game 2 - Joystick)
25e9bde4-4,Montezuma's Revenge (Game 3 - Joystick)
2e2db603-5,Montezuma's Revenge (Game 4 - Joystick)
160214dd-5,Montezuma's Revenge (Game 5 - Joystick)
43135e8-5,Montezuma's Revenge (Die)
b84a512-6,Montezuma's Revenge (Jump - Joystick)
d56c6cda-3,Montezuma's Revenge (Level Start 1 - Keyboard)
3cd3b2c5-4,Montezuma's Revenge (Level Start 2 - Keyboard)
23745a95-5,Montezuma's Revenge (Level Start 3 - Keyboard)
1b5bf84b-5,Montezuma's Revenge (Game - Keyboard)
b14ac573-6,Montezuma's Revenge (Jump - Keyboard)
f476b2d-2,Moon Patrol (Title)
4f94aa9-3,Moon Patrol (Menu)
396f7baf-3,Moon Patrol (Start 1)
6dfabc61-3,Moon Patrol (Start 2)
fb88733a-2,Moon Patrol (Start 3)
4e99aefb-5,Moon Patrol (Game 1)
dfd5fd5b-5,Moon Patrol (Game 2)
17e0326e-6,Moon Patrol (Game 3)
80243d6f-2,Moon Patrol (Game 5)
57a21d5d-6,Moon Patrol (Game - Shoot)
96130f7-6,Moon Patrol (Passed Pit or Checkpoint)
457104a-3,Moon Patrol (Die)
6a8bd750-2,Moon Patrol (Game Over)
1afccd0a-3,Moon Patrol (Bonus Screen)

View File

@ -33,8 +33,9 @@ DEBUG = 0
decomp !zone { decomp !zone {
jsr .chkdst jsr .chkdst
ldy #0 ; In lit loop Y must be zero ldy #0 ; In lit loop Y must be zero
sec
.fill1A jsr .getbt2 .fill1A jsr .getbt2
jmp .fill1B bne .fill1B ; always taken
.incdst inc pDst+1 .incdst inc pDst+1
.chkdst ldx pDst+1 .chkdst ldx pDst+1
@ -174,19 +175,19 @@ decomp !zone {
bne .gshift ; always taken bne .gshift ; always taken
; Get another 8 bits into our bit buffer. Destroys X. Preserves A. Requires Y=0. ; Get another 8 bits into our bit buffer. Destroys X. Preserves A. Requires Y=0.
; Carry is always set on entry, Z always clear on exit
; Alternately, use .getbt2 to preserve X and destroy A ; Alternately, use .getbt2 to preserve X and destroy A
.getbts tax .getbts tax
.getbt2 lda (pSrc),y .getbt2 lda (pSrc),y
inc pSrc
beq .src3A
.src3B sec
rol rol
sta bits sta bits
txa txa
inc pSrc
beq .src3A
rts rts
.src3A inc pSrc+1 .src3A inc pSrc+1
bne .src3B ; always taken rts
} ; end of zone } ; end of zone

View File

@ -364,7 +364,7 @@ export asm memcpy(pSrc, pDst, len, auxWr)#0
inc pTmp+1 inc pTmp+1
bne .pglup ; always taken bne .pglup ; always taken
.part: .part:
cpx #0 txa
beq .done beq .done
- lda (tmp),y - lda (tmp),y
sta (pTmp),y sta (pTmp),y
@ -390,8 +390,8 @@ export asm memset(pDst, val, len)#0
lda evalStkL,x ; len lo lda evalStkL,x ; len lo
pha pha
lda evalStkH,x ; len hi lda evalStkH,x ; len hi
tax
beq + beq +
tax
lda tmp lda tmp
- sta (pTmp),y - sta (pTmp),y
iny iny
@ -420,10 +420,10 @@ export asm readAuxByte(ptr)#1
sta $10-1,y sta $10-1,y
dey dey
bne - bne -
jmp $10
.rdauxb
sei ; prevent interrupts while in aux mem sei ; prevent interrupts while in aux mem
sta setAuxRd sta setAuxRd
jmp $10
.rdauxb
lda (pTmp),y lda (pTmp),y
sta clrAuxRd sta clrAuxRd
cli cli
@ -462,19 +462,19 @@ end
export asm finishString(isPlural)#1 export asm finishString(isPlural)#1
!zone { !zone {
+asmPlasmRet 1 +asmPlasmRet 1
ldy prevCSWL+ABS_OFFSET ; put the cout vector back to default
sty cswl
ldy prevCSWL+1+ABS_OFFSET ; put the cout vector back to default
sty cswh
ldy #0 ; dest offset in Y (will be incremented before store)
cpy inbuf
beq .done1 ; failsafe: handle zero-length string
tax ; test for isPlural == 0 tax ; test for isPlural == 0
beq + beq +
lda #$40 ; for setting V later lda #$40 ; for setting V later
+ sta tmp ; save isPlural flag + sta tmp ; save isPlural flag
lda prevCSWL+ABS_OFFSET ; put the cout vector back to default
sta cswl
lda prevCSWL+1+ABS_OFFSET ; put the cout vector back to default
sta cswh
clv ; V flag for prev-is-alpha clv ; V flag for prev-is-alpha
ldy #0 ; dest offset in Y (will be incremented before store)
ldx #0 ; source offset in X (will be incremented before load) ldx #0 ; source offset in X (will be incremented before load)
cpx inbuf
beq .done ; failsafe: handle zero-length string
.fetch .fetch
inx inx
lda inbuf,x ; get next input char lda inbuf,x ; get next input char
@ -488,11 +488,11 @@ export asm finishString(isPlural)#1
dey ; undo copy of the paren dey ; undo copy of the paren
stx tmp+1 ; save position in input stx tmp+1 ; save position in input
dex ; needed for failsafe operation
bit tmp ; set copy flag (V) initially to same as isPlural flag bit tmp ; set copy flag (V) initially to same as isPlural flag
.findsl ; see if there's a slash within the parens .findsl ; see if there's a slash within the parens
inx inx
cpx inbuf cpx inbuf
bcs .done ; failsafe: handle missing end-paren
lda inbuf,x lda inbuf,x
ora #$80 ; normalize hi-bit for comparisons below ora #$80 ; normalize hi-bit for comparisons below
cmp #"/" cmp #"/"
@ -503,11 +503,8 @@ export asm finishString(isPlural)#1
pha pha
plp plp
+ cmp #")" ; scan until ending paren + cmp #")" ; scan until ending paren
beq + bne .findsl ; loop to scan next char
cpx inbuf ldx tmp+1 ; get back to start of parens
bcc .findsl ; loop to scan next char
bcs .done ; failsafe: handle missing end-paren (always taken)
+ ldx tmp+1 ; get back to start of parens
; copy mode flag is now in V: if slash present, single=copy, plural=nocopy ; copy mode flag is now in V: if slash present, single=copy, plural=nocopy
; if no slash: single=nocopy, plural=copy ; if no slash: single=nocopy, plural=copy
.plup .plup
@ -541,6 +538,7 @@ export asm finishString(isPlural)#1
.done .done
sty inbuf ; save new length sty inbuf ; save new length
.done1
lda #<inbuf ; return pointer to string lda #<inbuf ; return pointer to string
ldy #>inbuf ldy #>inbuf
rts rts
@ -575,8 +573,8 @@ export asm blit(isAux, srcData, dstScreenPtr, nLines, lineSize)#0
lsr ; to carry bit lsr ; to carry bit
bcc + bcc +
ldy #15 ; put aux copy routine in zero page ldy #15 ; put aux copy routine in zero page
- lda .cpaux + ABS_OFFSET,y - ldx .cpaux + ABS_OFFSET,y
sta $10,y stx $10,y
dey dey
bpl - bpl -
+ pla ; get line count + pla ; get line count
@ -694,8 +692,9 @@ export asm puts(str)#0
sta pTmp sta pTmp
lda #'!' lda #'!'
ldx #1 ldx #1
sty pTmp+1 tya
beq + ; safety: print '!' instead of null string beq + ; safety: print '!' instead of null string
sty pTmp+1
ldy #0 ldy #0
lda (pTmp),y lda (pTmp),y
tax tax
@ -913,9 +912,8 @@ export asm rawDisplayStr(pStr)#0
lda (pTmp),y lda (pTmp),y
sta tmp sta tmp
- cpy tmp - cpy tmp
bcc + bcs ++
rts iny
+ iny
lda (pTmp),y lda (pTmp),y
ora #$80 ora #$80
cmp #"^" cmp #"^"
@ -929,7 +927,8 @@ export asm rawDisplayStr(pStr)#0
+ sty tmp+1 + sty tmp+1
jsr DisplayChar jsr DisplayChar
ldy tmp+1 ldy tmp+1
bne - bne - ; always taken
++rts
end end
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -991,8 +990,8 @@ export asm streqi(a, b)#1
lda evalStkL+1,x lda evalStkL+1,x
sta pTmp sta pTmp
lda evalStkH+1,x lda evalStkH+1,x
sta pTmp+1
beq .null beq .null
sta pTmp+1
ldy #0 ldy #0
lda (tmp),y lda (tmp),y
cmp (pTmp),y cmp (pTmp),y
@ -1004,21 +1003,16 @@ export asm streqi(a, b)#1
+ tax ; count up to (verified same) length of the strings + tax ; count up to (verified same) length of the strings
- iny - iny
lda (tmp),y lda (tmp),y
cmp #('z'&$7F)+1 ; convert to upper case eor (pTmp),y
bcs + beq + ; matched
cmp #$20 ; check for case bit
bne .noteqi ; abort on alpha inequality
ora (tmp),y ; convert to lower case
cmp #('z'&$7F)+1
bcs .noteqi ; abort on inequality
cmp #'a'&$7F cmp #'a'&$7F
bcc + bcc .noteqi ; abort on inequality
sbc #$20 + dex
+ sta ysav
lda (pTmp),y
cmp #('z'&$7F)+1 ; convert to upper case
bcs +
cmp #'a'&$7F
bcc +
sbc #$20
+ cmp ysav
bne .noteqi ; abort on inequality
dex
bne - bne -
lda #1 lda #1
ldy #0 ; okay, they're equal. Return 1 (not just any char; so that PLASMA when...is can work) ldy #0 ; okay, they're equal. Return 1 (not just any char; so that PLASMA when...is can work)