mirror of
https://github.com/zellyn/goapple2.git
synced 2024-12-26 16:29:22 +00:00
debugging disk operation
This commit is contained in:
parent
8f3ce4748b
commit
5e28969748
46
a2/disasm.go
Normal file
46
a2/disasm.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/zellyn/go6502/asm"
|
||||
)
|
||||
|
||||
var cmdDisasm = &commander.Command{
|
||||
Run: runDisasm,
|
||||
UsageLine: "disasm [-a address] filename",
|
||||
Short: "disassemble binary files",
|
||||
Long: `
|
||||
Disasm is a very simple disassembler for 6502 binary files.
|
||||
`,
|
||||
}
|
||||
|
||||
var disasmAddress uint // disasm -a flag
|
||||
|
||||
func init() {
|
||||
cmdDisasm.Flag.UintVar(&disasmAddress, "a", 0, "The starting memory address.")
|
||||
}
|
||||
|
||||
func runDisasm(cmd *commander.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadFile(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if len(bytes) > 0x10000 {
|
||||
log.Fatalf("File %s is %04X bytes long, which is more than $10000.", args[0], len(bytes))
|
||||
}
|
||||
if int(disasmAddress)+len(bytes) > 0x10000 {
|
||||
log.Fatalf("Starting address ($%04X) + file length ($%04X) = $%X, which is > $10000",
|
||||
disasmAddress, len(bytes), int(disasmAddress)+len(bytes))
|
||||
}
|
||||
|
||||
asm.DisasmBlock(bytes, uint16(disasmAddress), os.Stdout)
|
||||
}
|
31
a2/main.go
Normal file
31
a2/main.go
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
var a2cmd *commander.Commander
|
||||
|
||||
func init() {
|
||||
a2cmd = &commander.Commander{
|
||||
Name: os.Args[0],
|
||||
Commands: []*commander.Command{
|
||||
cmdDisasm,
|
||||
},
|
||||
Flag: flag.NewFlagSet("a2", flag.ExitOnError),
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := a2cmd.Flag.Parse(os.Args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
args := a2cmd.Flag.Args()
|
||||
if err := a2cmd.Run(args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -10,6 +10,8 @@ type Card interface {
|
||||
Write256(address byte, value byte) // Write to the $C(slot)XX addresses
|
||||
Read(address uint16) byte // Read from any address ($C800-$FFFF)
|
||||
Write(address uint16, value byte) // Write to any address ($C800-$FFFF)
|
||||
WantTicker() bool
|
||||
Tick()
|
||||
}
|
||||
|
||||
type CardManager interface {
|
||||
|
100
cards/disk.go
100
cards/disk.go
@ -8,6 +8,7 @@ import (
|
||||
|
||||
type Disk interface {
|
||||
Read() byte
|
||||
Skip(int)
|
||||
Write(byte)
|
||||
SetHalfTrack(byte)
|
||||
HalfTrack() byte
|
||||
@ -16,6 +17,13 @@ type Disk interface {
|
||||
Writeable() bool
|
||||
}
|
||||
|
||||
const (
|
||||
MODE_READ = 0
|
||||
MODE_WRITE = 1
|
||||
MODE_SHIFT = 0
|
||||
MODE_LOAD = 2
|
||||
)
|
||||
|
||||
type DiskCard struct {
|
||||
rom [256]byte
|
||||
cm CardManager
|
||||
@ -24,6 +32,10 @@ type DiskCard struct {
|
||||
disks [2]Disk
|
||||
active int
|
||||
phases byte
|
||||
mode byte
|
||||
onOff bool
|
||||
dataRegister byte
|
||||
lastAccess int
|
||||
}
|
||||
|
||||
func NewDiskCard(rom []byte, slot byte, cm CardManager) (*DiskCard, error) {
|
||||
@ -53,6 +65,9 @@ func (dc *DiskCard) ROMDisabled() {
|
||||
}
|
||||
|
||||
func (dc *DiskCard) handlePhase(phase byte, onOff bool) {
|
||||
if !dc.onOff {
|
||||
return
|
||||
}
|
||||
phaseBit := byte(1 << phase)
|
||||
if onOff {
|
||||
dc.phases |= phaseBit
|
||||
@ -98,15 +113,90 @@ func (dc *DiskCard) handleAccess(address byte) {
|
||||
dc.handlePhase(phase, onOff)
|
||||
return
|
||||
}
|
||||
switch address {
|
||||
case 0x8:
|
||||
dc.onOff = false
|
||||
case 0x9:
|
||||
dc.onOff = true
|
||||
case 0xA, 0xB:
|
||||
which := int(address & 1)
|
||||
if dc.active != which {
|
||||
dc.active = which
|
||||
dc.handlePhase(0, dc.phases&1 == 1) // No change: force update
|
||||
}
|
||||
case 0xC, 0xD:
|
||||
dc.mode = dc.mode&^2 | address&1<<2
|
||||
case 0xE, 0xF:
|
||||
dc.mode = dc.mode&^1 | address&1
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Read16(address byte) byte {
|
||||
dc.handleAccess(address)
|
||||
return dc.cm.EmptyRead()
|
||||
if address != 0xC && address != 0xE {
|
||||
return 0xFF
|
||||
}
|
||||
if dc.onOff {
|
||||
switch dc.mode {
|
||||
case MODE_READ | MODE_SHIFT:
|
||||
// Normal read
|
||||
return dc.readOne()
|
||||
case MODE_READ | MODE_LOAD:
|
||||
// Check write-protect
|
||||
if dc.disks[dc.active].Writeable() {
|
||||
return 0x00
|
||||
} else {
|
||||
return 0xFF
|
||||
}
|
||||
case MODE_WRITE | MODE_SHIFT:
|
||||
// Doesn't do anything in our simulation: just return last data
|
||||
return dc.dataRegister
|
||||
case MODE_WRITE | MODE_LOAD:
|
||||
// Nonsense for reading: just return last data
|
||||
return dc.dataRegister
|
||||
}
|
||||
}
|
||||
return 0xFF
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Write16(address byte, value byte) {
|
||||
dc.handleAccess(address)
|
||||
if dc.onOff {
|
||||
switch dc.mode {
|
||||
case MODE_READ | MODE_SHIFT:
|
||||
// Normal read
|
||||
panic("Write while in read mode")
|
||||
case MODE_READ | MODE_LOAD:
|
||||
// Check write-protect
|
||||
panic("Write while in check-write-protect mode")
|
||||
case MODE_WRITE | MODE_SHIFT:
|
||||
// Shifting data to disk
|
||||
panic("Write while in shift mode")
|
||||
case MODE_WRITE | MODE_LOAD:
|
||||
if dc.disks[dc.active].Writeable() {
|
||||
dc.writeOne(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DiskCard) readOne() byte {
|
||||
if dc.lastAccess < 4 {
|
||||
return dc.dataRegister
|
||||
}
|
||||
disk := dc.disks[dc.active]
|
||||
if dc.lastAccess > 300 {
|
||||
disk.Skip(dc.lastAccess / 36)
|
||||
}
|
||||
dc.lastAccess = 0
|
||||
dc.dataRegister = disk.Read()
|
||||
return dc.dataRegister
|
||||
}
|
||||
|
||||
func (dc *DiskCard) writeOne(value byte) {
|
||||
disk := dc.disks[dc.active]
|
||||
dc.dataRegister = value
|
||||
disk.Write(value)
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Read(address uint16) byte {
|
||||
@ -128,3 +218,11 @@ func (dc *DiskCard) Write256(address byte, value byte) {
|
||||
func (dc *DiskCard) LoadDisk(d Disk, which int) {
|
||||
dc.disks[which] = d
|
||||
}
|
||||
|
||||
func (dc *DiskCard) WantTicker() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Tick() {
|
||||
dc.lastAccess++
|
||||
}
|
||||
|
@ -70,3 +70,11 @@ func (fc *FirmwareCard) Read256(address byte) byte {
|
||||
func (fc *FirmwareCard) Write256(address byte, value byte) {
|
||||
// Firmware is ROM: do nothing
|
||||
}
|
||||
|
||||
func (fc *FirmwareCard) WantTicker() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (fc *FirmwareCard) Tick() {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -96,12 +96,13 @@ func appendSyncs(target []byte, count int) []byte {
|
||||
|
||||
// append44 appends bytes using the 4-4 encoded format used for volume, track, sector, checksum.
|
||||
func append44(target []byte, b byte) []byte {
|
||||
return append(target, 0xAA|b<<1, 0xAA|b)
|
||||
return append(target, 0xAA|(b>>1), 0xAA|b)
|
||||
}
|
||||
|
||||
// appendAddress appends the encoded sector address to the slice, and returns the resulting slice.
|
||||
func appendAddress(target []byte, t, s, v byte) []byte {
|
||||
target = append(target, 0xD5, 0xAA, 0x96)
|
||||
fmt.Println("s=", s)
|
||||
target = append44(target, v)
|
||||
target = append44(target, t)
|
||||
target = append44(target, s)
|
||||
|
@ -3,7 +3,11 @@ package disk
|
||||
type Dummy byte
|
||||
|
||||
func (v Dummy) Read() byte {
|
||||
return 0xFF
|
||||
return 0x00
|
||||
}
|
||||
|
||||
func (v Dummy) Skip(int) {
|
||||
// pass
|
||||
}
|
||||
|
||||
func (v Dummy) Write(b byte) {
|
||||
|
@ -55,6 +55,11 @@ func (disk *Nybble) Read() byte {
|
||||
return track[disk.position]
|
||||
}
|
||||
|
||||
func (disk *Nybble) Skip(amount int) {
|
||||
track := disk.Tracks[disk.halfTrack/2]
|
||||
disk.position = (disk.position + amount) % len(track)
|
||||
}
|
||||
|
||||
func (disk *Nybble) Write(b byte) {
|
||||
track := disk.Tracks[disk.halfTrack/2]
|
||||
disk.position = (disk.position + 1) % len(track)
|
||||
|
@ -155,6 +155,59 @@ Apple II notes
|
||||
| $F3 | 243 | FLASH MASK |
|
||||
| $F9 | 249 | ROT |
|
||||
|
||||
* Page 00 DOS
|
||||
[BAD 8-42]
|
||||
|
||||
|---------+-----+-----------------------------------------------|
|
||||
| Address | Dec | Description |
|
||||
|---------+-----+-----------------------------------------------|
|
||||
| 24 | | Cursor horizontal |
|
||||
| 26,27 | | Sector read buffer address (ROM) |
|
||||
| 28,29 | | BASL/BASH (DOS) |
|
||||
| 2A | | Segment merge counter (ROM,BOOT) |
|
||||
| | | Scratch space (RWTS) |
|
||||
| 2B | | BOOT slot*16 (ROM) |
|
||||
| | | Scratch space (RWTS) |
|
||||
| 2C | | Checksum from sector header (RWTS) |
|
||||
| 2D | | Sector number form sector header (RWTS) |
|
||||
| 2E | | Track number form sector header (RWTS) |
|
||||
| 2F | | Volume number form sector header (RWTS) |
|
||||
| 33 | | Prompt character (DOS) |
|
||||
| 35 | | Drive number in high bit (RWTS) |
|
||||
| 36,37 | | CSWL,CSWH (DOS) |
|
||||
| 38,39 | | KSWL,KSWH (DOS) |
|
||||
| 3C | | Workbyte (ROM) |
|
||||
| | | Merge workbyte (BOOT) |
|
||||
| | | Device characteristics table address (RWTS) |
|
||||
| 3D | | Sector number (ROM) |
|
||||
| | | Device characteristics table address (RWTS) |
|
||||
| 3E,3F | | Address of ROM sector-read subroutine (BOOT) |
|
||||
| | | Buffer address (RWTS) |
|
||||
| 40,41 | | DOS image address (BOOT) |
|
||||
| | | File buffer address (DOS) |
|
||||
| 41 | | Format track counter (RWTS) |
|
||||
| 42,43 | | Buffer address (DOS) |
|
||||
| 44,45 | | Numeric operand (DOS) |
|
||||
| 46,47 | | Scratch space (RWTS) |
|
||||
| 48,49 | | IOB address (RWTS) |
|
||||
| 4A,4B | | INTEGER BASIC LOMEM address (DOS) |
|
||||
| | | Format diskette workspace (RWTS) |
|
||||
| 4C,4D | | INTEGER BASIC HIMEM address (DOS) |
|
||||
| 67,68 | | APPLESOFT BASIC PROGRAM START (DOS) |
|
||||
| 69,6A | | APPLESOFT BASIC VARIABLES START (DOS) |
|
||||
| 6F,70 | | APPLESOFT BASIC STRING START (DOS) |
|
||||
| 73,74 | | APPLESOFT BASIC HIMEM address (DOS) |
|
||||
| 76 | | APPLESOFT BASIC line number high (DOS) |
|
||||
| AF,B0 | | APPLESOFT BASIC PROGRAM END (DOS) |
|
||||
| CA,CB | | INTEGER BASIC PROGRAM START (DOS) |
|
||||
| CC,CD | | INTEGER BASIC VARIABLES END (DOS) |
|
||||
| D6 | | APPLESOFT BASIC PROGRAM protection flag (DOS) |
|
||||
| D8,D9 | | INTEGER BASIC line number (DOS) |
|
||||
| | | APPLESOFT BASIC ONERR (DOS) |
|
||||
|---------+-----+-----------------------------------------------|
|
||||
|
||||
|
||||
|
||||
* Page 03
|
||||
|
||||
|-------------+--------------------------------------|
|
||||
@ -217,7 +270,7 @@ Apple II notes
|
||||
- http://www.textfiles.com/apple/peeks.pokes.2
|
||||
- http://www.applefritter.com/node/24236
|
||||
- http://www.easy68k.com/paulrsm/6502/index.html - Information about firmware
|
||||
- http://www-personal.umich.edu/~mressl/a2dp/index.html - Apple II documentation project
|
||||
- http://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/ - Apple II documentation project
|
||||
|
||||
* Language card
|
||||
Install in slot 0.
|
||||
@ -665,3 +718,30 @@ If track 0 has >15 sync count, the rest of the tracks will have (sync count - 2)
|
||||
tracksize 51024
|
||||
(/ 51024 8)
|
||||
6378
|
||||
|
||||
* System disk masters
|
||||
https://github.com/cmosher01/Apple-II-System-Masters.git
|
||||
http://beagle.applearchives.com/the_software/vintage_beagle_bros_softwar/prontodos.html
|
||||
|
||||
|
||||
| 452f496e7c9c447052331f72f643117f | dos331/clean331sysmas.do |
|
||||
| 452f496e7c9c447052331f72f643117f | dos331/original331sysmas.do |
|
||||
| 452f496e7c9c447052331f72f643117f | dos331/stock331mastercreated.do |
|
||||
| 5a72964c04e4a37bb8b13a471aa046de | dos331/stock331init.do |
|
||||
| 5ed656da29221d891d056b6b576ae2ce | dos330/stock330init.do |
|
||||
| 6987207d16a72c60de03d8d1c5e267f3 | dos332/clean332sysmas.do |
|
||||
| 6987207d16a72c60de03d8d1c5e267f3 | dos332/original332sysmas.do |
|
||||
| 6987207d16a72c60de03d8d1c5e267f3 | dos332/stock332mastercreated.do |
|
||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | dos330/clean330sysmas.do |
|
||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | dos330/original330sysmas.do |
|
||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | dos330/stock330mastercreated.do |
|
||||
| 9f79e87fcbcb9885a47f2f462868833f | dos332/stock332init.do |
|
||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | Apple DOS 3.3 August 1980.dsk |
|
||||
| 452f496e7c9c447052331f72f643117f | Apple DOS 3.3 January 1983.dsk |
|
||||
| 92e8fc0c4e79e50f2d81a14cfa55b151 | DavidDOS.dsk |
|
||||
| c17928ee915b9bf5c4041621fa8a0cb4 | DiversiDOS.dsk |
|
||||
| 6390fc84edf64dd88682b920ff88bbfa | Prontodos/Prontodos.dsk |
|
||||
| 5b9894778b6b97d56d1199ab13b0db66 | EsDos.dsk |
|
||||
|
||||
| db809f937c0de0a12cb70897ed9a72da | chivalry.dsk |
|
||||
|
||||
|
91
docs/chivalry-debug.org
Normal file
91
docs/chivalry-debug.org
Normal file
@ -0,0 +1,91 @@
|
||||
* Debugging the Chivalry disk
|
||||
Ascending memory references from $1F76.
|
||||
|
||||
** Disassembly of routine that is overwriting from $1F76
|
||||
|
||||
$73,$74
|
||||
$77,$78
|
||||
$5B
|
||||
$5C
|
||||
$5D
|
||||
$62
|
||||
|
||||
$1F51: A0 00 LDY #$00
|
||||
$1F53: B1 73 LDA ($73),Y
|
||||
$1F55: 85 5C STA $5C
|
||||
$1F57: C8 INY
|
||||
$1F58: B1 73 LDA ($73),Y
|
||||
$1F5A: 85 5B STA $5B
|
||||
$1F5C: C8 INY
|
||||
$1F5D: 18 CLC
|
||||
$1F5E: 98 TYA
|
||||
$1F5F: 65 73 ADC $73
|
||||
$1F61: 85 73 STA $73
|
||||
$1F63: A9 00 LDA #$00
|
||||
$1F65: 65 74 ADC $74
|
||||
$1F67: 85 74 STA $74
|
||||
$1F69: 20 9C 1D JSR $1D9C
|
||||
$1F6C: A2 00 LDX #$00
|
||||
$1F6E: A0 00 LDY #$00
|
||||
$1F70: A5 5C LDA $5C
|
||||
$1F72: 85 5D STA $5D
|
||||
$1F74: B1 73 LDA ($73),Y
|
||||
$1F76: 91 77 STA ($77),Y
|
||||
$1F78: C8 INY
|
||||
$1F79: C6 5D DEC $5D
|
||||
$1F7B: D0 F7 BNE $1F74
|
||||
$1F7D: 18 CLC
|
||||
$1F7E: 98 TYA
|
||||
$1F7F: 65 73 ADC $73
|
||||
$1F81: 85 73 STA $73
|
||||
$1F83: A9 00 LDA #$00
|
||||
$1F85: 65 74 ADC $74
|
||||
$1F87: 85 74 STA $74
|
||||
$1F89: E6 62 INC $62
|
||||
$1F8B: C6 5B DEC $5B
|
||||
$1F8D: D0 DA BNE $1F69
|
||||
$1F8F: 60 RTS
|
||||
|
||||
** Routine it calls: $1D9C
|
||||
|
||||
$5E
|
||||
$61
|
||||
$62
|
||||
$64
|
||||
$77
|
||||
$78
|
||||
|
||||
$1D9C: A5 62 LDA $62
|
||||
$1D9E: AA TAX
|
||||
$1D9F: 0A ASL
|
||||
$1DA0: 0A ASL
|
||||
$1DA1: 29 1C AND #$1C
|
||||
$1DA3: 85 78 STA $78
|
||||
$1DA5: 8A TXA
|
||||
$1DA6: 6A ROR
|
||||
$1DA7: AA TAX
|
||||
$1DA8: 6A ROR
|
||||
$1DA9: 6A ROR
|
||||
$1DAA: 6A ROR
|
||||
$1DAB: 08 PHP
|
||||
$1DAC: 29 03 AND #$03
|
||||
$1DAE: 05 78 ORA $78
|
||||
$1DB0: 05 5E ORA $5E
|
||||
$1DB2: 85 78 STA $78
|
||||
$1DB4: 8A TXA
|
||||
$1DB5: 29 60 AND #$60
|
||||
$1DB7: 85 77 STA $77
|
||||
$1DB9: 6A ROR
|
||||
$1DBA: 28 PLP
|
||||
$1DBB: 6A ROR
|
||||
$1DBC: 29 98 AND #$98
|
||||
$1DBE: 05 77 ORA $77
|
||||
$1DC0: 85 77 STA $77
|
||||
$1DC2: A6 61 LDX $61
|
||||
$1DC4: BD D2 1D LDA $1DD2,X
|
||||
$1DC7: 18 CLC
|
||||
$1DC8: 65 77 ADC $77
|
||||
$1DCA: 85 77 STA $77
|
||||
$1DCC: BD 5E 1E LDA $1E5E,X
|
||||
$1DCF: 85 64 STA $64
|
||||
$1DD1: 60 RTS
|
3538
docs/disk-md5s.org
Normal file
3538
docs/disk-md5s.org
Normal file
File diff suppressed because it is too large
Load Diff
75
goapple2.go
75
goapple2.go
@ -2,13 +2,30 @@ package goapple2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/zellyn/go6502/cpu"
|
||||
"github.com/zellyn/goapple2/cards"
|
||||
"github.com/zellyn/goapple2/videoscan"
|
||||
)
|
||||
|
||||
// Memory for the tests. Satisfies the cpu.Memory interface.
|
||||
type PCActionType int
|
||||
|
||||
const (
|
||||
ActionDumpMem PCActionType = iota + 1
|
||||
ActionLogRegisters
|
||||
)
|
||||
|
||||
type PCAction struct {
|
||||
Type PCActionType
|
||||
String string
|
||||
}
|
||||
|
||||
// Apple II struct
|
||||
type Apple2 struct {
|
||||
mem [65536]byte
|
||||
cpu cpu.Cpu
|
||||
@ -26,12 +43,15 @@ type Apple2 struct {
|
||||
card12kMask byte
|
||||
card12kConflict bool "True if more than one card is handling the 12k ROM area"
|
||||
card12kHandler byte
|
||||
cardTickerMask byte
|
||||
pcActions map[uint16][]PCAction
|
||||
}
|
||||
|
||||
func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 {
|
||||
a2 := Apple2{
|
||||
// BUG(zellyn): this is not how the apple2 keyboard actually works
|
||||
keys: make(chan byte, 16),
|
||||
pcActions: make(map[uint16][]PCAction),
|
||||
}
|
||||
copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom)
|
||||
a2.scanner = videoscan.NewScanner(&a2, p, charRom)
|
||||
@ -47,6 +67,9 @@ func (a2 *Apple2) AddCard(card cards.Card) error {
|
||||
return fmt.Errorf("Slot %d already has a card: %s", slot, a2.cards[slot])
|
||||
}
|
||||
a2.cardMask |= slotbit
|
||||
if card.WantTicker() {
|
||||
a2.cardTickerMask |= slotbit
|
||||
}
|
||||
a2.cards[slot] = card
|
||||
return nil
|
||||
}
|
||||
@ -75,20 +98,28 @@ func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
|
||||
}
|
||||
switch address {
|
||||
case 0xC050: // GRAPHICS
|
||||
fmt.Printf("$%04X: GRAPHICS\n", a2.cpu.PC())
|
||||
a2.scanner.SetGraphics(true)
|
||||
case 0xC051: // TEXT
|
||||
fmt.Printf("$%04X: NO GRAPHICS\n", a2.cpu.PC())
|
||||
a2.scanner.SetGraphics(false)
|
||||
case 0xC052: // NOMIX
|
||||
fmt.Printf("$%04X: NOMIX\n", a2.cpu.PC())
|
||||
a2.scanner.SetMix(false)
|
||||
case 0xC053: // MIX
|
||||
fmt.Printf("$%04X: MIX\n", a2.cpu.PC())
|
||||
a2.scanner.SetMix(true)
|
||||
case 0xC054: // PAGE 1
|
||||
fmt.Printf("$%04X: PAGE1\n", a2.cpu.PC())
|
||||
a2.scanner.SetPage(1)
|
||||
case 0xC055: // PAGE 2
|
||||
fmt.Printf("$%04X: PAGE2\n", a2.cpu.PC())
|
||||
a2.scanner.SetPage(2)
|
||||
case 0xC056: // LORES
|
||||
fmt.Printf("$%04X: LORES\n", a2.cpu.PC())
|
||||
a2.scanner.SetHires(false)
|
||||
case 0xC057: // HIRES
|
||||
fmt.Printf("$%04X: HIRES\n", a2.cpu.PC())
|
||||
a2.scanner.SetHires(true)
|
||||
}
|
||||
}
|
||||
@ -197,12 +228,33 @@ func (a2 *Apple2) Keypress(key byte) {
|
||||
a2.keys <- key | 0x80
|
||||
}
|
||||
|
||||
func (a2 *Apple2) AddPCAction(address uint16, action PCAction) {
|
||||
a2.pcActions[address] = append(a2.pcActions[address], action)
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Step() error {
|
||||
if actions, ok := a2.pcActions[a2.cpu.PC()]; ok {
|
||||
for _, action := range actions {
|
||||
switch action.Type {
|
||||
case ActionDumpMem:
|
||||
a2.DumpRAM(action.String, true)
|
||||
case ActionLogRegisters:
|
||||
a2.LogRegisters()
|
||||
}
|
||||
}
|
||||
}
|
||||
return a2.cpu.Step()
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Tick() {
|
||||
a2.scanner.Scan1()
|
||||
tickerMask := a2.cardTickerMask
|
||||
for i := 0; i < 8 && tickerMask > 0; i++ {
|
||||
if tickerMask&1 == 1 {
|
||||
a2.cards[i].Tick()
|
||||
}
|
||||
tickerMask >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
func (a2 *Apple2) Quit() {
|
||||
@ -245,3 +297,24 @@ func (a2 *Apple2) Handle12k(onOff bool, slot byte) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a2 *Apple2) LogRegisters() {
|
||||
c := a2.cpu
|
||||
log.Printf("Registers: PC=$%04X A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%02X=$%08b",
|
||||
c.PC(), c.A(), c.X(), c.Y(), c.SP(), c.P(), c.P())
|
||||
}
|
||||
func (a2 *Apple2) DumpRAM(filename string, addTimestamp bool) error {
|
||||
f := filename
|
||||
if addTimestamp {
|
||||
ts := "-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
if ext := filepath.Ext(filename); ext == "" {
|
||||
f = filename + ts
|
||||
} else {
|
||||
dir, file := filepath.Split(filename[:len(filename)-len(ext)])
|
||||
f = dir + file + ts + ext
|
||||
}
|
||||
}
|
||||
log.Printf("Dumping RAM to %s", f)
|
||||
a2.LogRegisters()
|
||||
return ioutil.WriteFile(f, a2.mem[:0xC000], 0644)
|
||||
}
|
||||
|
17
sdl/sdl.go
17
sdl/sdl.go
@ -261,6 +261,7 @@ func (s SdlPlotter) OncePerFrame() {
|
||||
}
|
||||
|
||||
func typeProgram(a2 *goapple2.Apple2) {
|
||||
return
|
||||
lines := []string{
|
||||
"10 GR",
|
||||
"20 POKE -16302,0",
|
||||
@ -272,6 +273,14 @@ func typeProgram(a2 *goapple2.Apple2) {
|
||||
"80 NEXT",
|
||||
"RUN",
|
||||
}
|
||||
lines = []string{
|
||||
"10 HGR2",
|
||||
"20 FOR I = 0 to 7",
|
||||
"30 HCOLOR=7-I",
|
||||
"40 HPLOT I*10, 0 TO 191 + I*10, 191",
|
||||
"50 NEXT",
|
||||
"RUN",
|
||||
}
|
||||
for _, line := range lines {
|
||||
for _, ch := range line {
|
||||
a2.Keypress(byte(ch))
|
||||
@ -315,10 +324,11 @@ func RunEmulator() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
disk1 := disk.NewNybble()
|
||||
// if err = disk1.LoadDosDisk("../data/disks/spedtest.dsk"); err != nil {
|
||||
// if err = disk1.LoadDosDisk("../data/disks/dung_beetles.dsk"); err != nil {
|
||||
if err = disk1.LoadDosDisk("../data/disks/chivalry.dsk"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
disk1.SetHalfTrack(50)
|
||||
diskCard.LoadDisk(disk1, 0)
|
||||
|
||||
steps := *steplimit
|
||||
@ -332,7 +342,10 @@ func RunEmulator() {
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
go typeProgram(a2)
|
||||
a2.AddPCAction(0xC6EB, goapple2.PCAction{goapple2.ActionDumpMem, "chivalry-dump.bin"})
|
||||
a2.AddPCAction(0x1F76, goapple2.PCAction{goapple2.ActionDumpMem, "chivalry-dump.bin"})
|
||||
|
||||
// go typeProgram(a2)
|
||||
|
||||
for !a2.Done {
|
||||
err := a2.Step()
|
||||
|
@ -96,15 +96,19 @@ func (s *Scanner) inc() {
|
||||
var last [192][40]uint16
|
||||
|
||||
func (s *Scanner) Scan1() {
|
||||
m := s.m.RamRead(s.address())
|
||||
address := s.address()
|
||||
if address >= 0xC000 {
|
||||
fmt.Printf("\n\n\nWOAH! $%04X\n\n\n", address)
|
||||
}
|
||||
m := s.m.RamRead(address)
|
||||
row, column := s.row(), s.column()
|
||||
_, _, _ = m, row, column
|
||||
var data uint16
|
||||
switch {
|
||||
case !s.graphics || (s.mix && s.lastFour):
|
||||
data = s.textData(m, row, column)
|
||||
data = s.textData(m, row)
|
||||
case s.hires:
|
||||
data = s.hiresData(m, row, column)
|
||||
data = s.hiresData(m)
|
||||
default: // lores
|
||||
data = s.loresData(m, row, column)
|
||||
}
|
||||
@ -112,6 +116,9 @@ func (s *Scanner) Scan1() {
|
||||
if !s.hbl && !s.vbl {
|
||||
change := last[row][column] != (data | s.graphicsBit)
|
||||
if change || s.lastChange {
|
||||
// if row <= 8 && column == 0 {
|
||||
// fmt.Printf("%d,%d: RawData=%02X, Data=%04X\n", row, column, m, data)
|
||||
// }
|
||||
s.plotter.Plot(PlotData{
|
||||
Row: byte(row),
|
||||
Column: byte(column),
|
||||
@ -218,7 +225,7 @@ func (s *Scanner) SetPage(page int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) textData(m byte, row int, column int) uint16 {
|
||||
func (s *Scanner) textData(m byte, row int) uint16 {
|
||||
line := s.rom[int(m)*8+((row+800)%8)]
|
||||
// Invert if flash
|
||||
if (m^0x80)&line&s.flasher > 127 {
|
||||
@ -226,7 +233,7 @@ func (s *Scanner) textData(m byte, row int, column int) uint16 {
|
||||
}
|
||||
line &= 0x7f // Mask out high bit
|
||||
// Now it's just like hires data
|
||||
return s.hiresData(line, row, column)
|
||||
return s.hiresData(line)
|
||||
}
|
||||
|
||||
// Double each bit to go from pixel info to color info
|
||||
@ -249,11 +256,11 @@ var HIRES_DOUBLES = [128]uint16{
|
||||
0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC, 0x3FFF,
|
||||
}
|
||||
|
||||
func (s *Scanner) hiresData(m byte, row int, column int) uint16 {
|
||||
func (s *Scanner) hiresData(m byte) uint16 {
|
||||
if m < 128 {
|
||||
return HIRES_DOUBLES[m]
|
||||
}
|
||||
return ((HIRES_DOUBLES[m&0x7f] << 1) & 0x3ff) | s.lastBit
|
||||
return ((HIRES_DOUBLES[m&0x7f] << 1) & 0x3fff) | s.lastBit
|
||||
}
|
||||
|
||||
func (s *Scanner) loresData(m byte, row int, column int) uint16 {
|
||||
|
@ -228,3 +228,43 @@ func TestSpecificLocationAddresses(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHiresConversion(t *testing.T) {
|
||||
var m K64
|
||||
var f fakePlotter
|
||||
s := NewScanner(&m, &f, [2048]byte{})
|
||||
|
||||
s.SetMix(false)
|
||||
s.SetGraphics(true)
|
||||
s.SetHires(true)
|
||||
|
||||
hiresIn0 := []byte{0x03, 0x06, 0x0C, 0x18, 0x30}
|
||||
hiresOut0 := []uint16{0xf, 0x3c, 0xf0, 0x3c0, 0xf00}
|
||||
hiresIn1 := []byte{0x83, 0x86, 0x8C, 0x98, 0xB0}
|
||||
hiresOut1 := []uint16{0x1e, 0x78, 0x1e0, 0x780, 0x1e00}
|
||||
for i := range hiresIn0 {
|
||||
s.lastBit = 0
|
||||
out := s.hiresData(hiresIn0[i])
|
||||
if out != hiresOut0[i] {
|
||||
t.Errorf("Expected hiresData(0x%02X) to be %04X, got %04X", hiresIn0[i], hiresOut0[i], out)
|
||||
}
|
||||
s.lastBit = 1
|
||||
out = s.hiresData(hiresIn0[i])
|
||||
if out != hiresOut0[i] {
|
||||
t.Errorf("Expected hiresData(0x%02X) to be %04X, got %04X", hiresIn0[i], hiresOut0[i], out)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range hiresIn1 {
|
||||
s.lastBit = 0
|
||||
out := s.hiresData(hiresIn1[i])
|
||||
if out != hiresOut1[i] {
|
||||
t.Errorf("Expected hiresData(0x%02X) lb=0 to be %04X, got %04X", hiresIn1[i], hiresOut1[i], out)
|
||||
}
|
||||
s.lastBit = 1
|
||||
out = s.hiresData(hiresIn1[i])
|
||||
if out != hiresOut1[i]|1 {
|
||||
t.Errorf("Expected hiresData(0x%02X) lb=1 to be %04X, got %04X", hiresIn1[i], hiresOut1[i]|1, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user