-
Notifications
You must be signed in to change notification settings - Fork 9
/
romX.go
205 lines (172 loc) · 5.17 KB
/
romX.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package izapple2
import (
"fmt"
"github.com/ivanizag/iz6502"
)
/*
RomX from https://theromexchange.com/
This complement uses the RomX API spec to switch main ROM and character generator ROM
Only the font switch is implemented
See:
https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf
https://theromexchange.com/downloads/ROM%20X%2020-10-22.zip
https://theromexchange.com/documentation/romxce/ROMXce%20API%20Reference.pdf
For romX:
It is not enough to intercept the ROM accesses. RomX intercept the 74LS138 in
position F12, that has access to the full 0xc000-0xf000 on the Apple II+
Firmware:
- It first copies $D000-$DFFF to $6000 and runs there.
go run *.go -rom ROMX.FIRM.dump -disk ROM\ X\ 20-10-22.dsk
*/
type romX struct {
a *Apple2
memory iz6502.Memory
activationStep int
systemBank uint8
mainBank uint8
tempBank uint8
textBank uint8
debug bool
}
var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe}
var romXceActivationSequence = []uint16{0xfaca, 0xfaca, 0xfafe}
const (
romxSetupBank = uint8(0)
romXPlusSetSystemBankBaseAddress = uint16(0xcef0)
romXPlusSetTextBankBaseAddress = uint16(0xcfd0)
// Unknown
// romXFirmwareMark0Address = uint16(0xdffe)
// romXFirmwareMark0Value = uint8(0x4a)
// romXFirmwareMark1Address = uint16(0xdfff)
// romXFirmwareMark1Value = uint8(0xcd)
romXceSelectTempBank = uint16(0xf850)
romXceSelectMainBank = uint16(0xf851)
romXceSetTempBank = uint16(0xf830) // 16 positions
romXceSetMainBank = uint16(0xf800) // 16 positions
romXcePresetTextBank = uint16(0xf810) // 16 positions
romXceMCP7940SDC = uint16(0xf860) // 16 positions
romXceLowerUpperBanks = uint16(0xf820) // 16 positions
romXGetDefaultSystemBank = uint16(0xd034) // $00 to $0f
romXGetDefaultTextBank = uint16(0xd02e) // $10 to $1f
romXGetCurrentBootDelay = uint16(0xdeca) // $00 to $0f
/*
romXceEntryPointSetClock = uint16(0xc803)
romXceEntryPointReadClock = uint16(0xc803)
romXceEntryPointLauncherToRam = uint16(0xdfd9)
romXceEntryPointLauncher = uint16(0xdfd0)
*/
)
func newRomX(a *Apple2) (*romX, error) {
var rx romX
rx.a = a
rx.memory = a.mmu
rx.systemBank = 1
rx.mainBank = 1
rx.tempBank = 1
rx.textBank = 0
rx.debug = true
if a.isApple2e {
err := a.cg.load("<internal>/ROMXce Production 1Mb Text ROM V5.bin")
if err != nil {
return nil, err
}
}
// Intercept all memory accesses
a.cpu.SetMemory(&rx)
return &rx, nil
}
func (rx *romX) Peek(address uint16) uint8 {
intercepted, value := rx.interceptAccess(address)
if intercepted {
return value
}
return rx.memory.Peek(address)
}
func (rx *romX) PeekCode(address uint16) uint8 {
intercepted, value := rx.interceptAccess(address)
if intercepted {
return value
}
return rx.memory.PeekCode(address)
}
func (rx *romX) Poke(address uint16, value uint8) {
rx.interceptAccess(address)
rx.memory.Poke(address, value)
}
func (rx *romX) logf(format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
fmt.Printf("[romX]%s\n", msg)
}
func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
// Intercept only $C080 to $FFFF as seen by the F12 chip
if address < 0xc080 {
return false, 0
}
// Setup mode when the setup bank is active
if rx.systemBank == romxSetupBank {
// Range commands
nibble := uint8(address & 0xf)
switch address & 0xfff0 {
case romXceSetMainBank:
rx.mainBank = nibble
rx.logf("Main bank set to $%x", nibble)
case romXcePresetTextBank:
textBank := int(nibble)
rx.a.cg.setPage(textBank)
rx.logf("[romX]Text bank set to $%x", nibble)
case romXceLowerUpperBanks:
rx.logf("Configure lower upper banks $%x", address)
case romXceSetTempBank:
rx.tempBank = nibble
rx.logf("Temp bank set to $%x", nibble)
case romXceMCP7940SDC:
rx.logf("Configure MCP7940 $%x", address)
}
// More commands
switch address {
case romXceSelectTempBank:
rx.systemBank = rx.tempBank
rx.logf("System bank set to temp bank $%x", rx.systemBank)
case romXceSelectMainBank:
rx.systemBank = rx.mainBank
rx.logf("System bank set to main bank $%x", rx.systemBank)
}
// Queries
switch address {
case romXGetDefaultSystemBank:
bank := rx.systemBank
rx.logf("Peek in $%04x, current system bank %v", address, bank)
return true, bank
case romXGetDefaultTextBank:
page := uint8(rx.a.cg.getPage() & 0xf)
rx.logf("PeeK in $%04x, current text bank %v", address, page)
return true, 0x10 + page
case romXGetCurrentBootDelay:
delay := uint8(5) // We don't care
rx.logf("PeeK in $%04x, current boot delay %v", address, delay)
return true, delay
}
return false, 0
}
// Activation sequence detection
if address == romXceActivationSequence[rx.activationStep] {
rx.activationStep++
rx.logf("Activation step %v", rx.activationStep)
if rx.activationStep == len(romXActivationSequence) {
// Activation sequence completed
rx.systemBank = romxSetupBank
rx.activationStep = 0
rx.logf("System bank set to 0, %v", rx.systemBank)
}
} else {
rx.activationStep = 0
}
return false, 0
}
func setupRomX(a *Apple2) error {
_, err := newRomX(a)
if err != nil {
return err
}
return nil
}