mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-06-01 07:41:56 +00:00
Refactor memory in pages. Addes text, ram and rom pages
This commit is contained in:
parent
139597b0d5
commit
49f2436c7b
|
@ -7,11 +7,12 @@ import (
|
||||||
|
|
||||||
func TestFunctional(t *testing.T) {
|
func TestFunctional(t *testing.T) {
|
||||||
var s state
|
var s state
|
||||||
|
// Test suite from https://github.com/Klaus2m5/6502_65C02_functional_tests
|
||||||
s.memory.loadBinary("tests/6502_functional_test.bin")
|
s.memory.loadBinary("tests/6502_functional_test.bin")
|
||||||
|
|
||||||
s.registers.setPC(0x0400)
|
s.registers.setPC(0x0400)
|
||||||
for true {
|
for true {
|
||||||
testCase := s.memory[0x0200]
|
testCase := s.memory.peek(0x0200)
|
||||||
if testCase >= 240 {
|
if testCase >= 240 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
23
execute.go
23
execute.go
|
@ -82,12 +82,12 @@ func resolve(s *state, line []uint8, opcode opcode) (value uint8, address uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasAddress {
|
if hasAddress {
|
||||||
value = s.memory[address]
|
value = s.memory.peek(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue = func(value uint8) {
|
setValue = func(value uint8) {
|
||||||
if hasAddress {
|
if hasAddress {
|
||||||
s.memory[address] = value
|
s.memory.poke(address, value)
|
||||||
} else if register != regNone {
|
} else if register != regNone {
|
||||||
s.registers.setRegister(register, value)
|
s.registers.setRegister(register, value)
|
||||||
} else {
|
} else {
|
||||||
|
@ -99,7 +99,7 @@ func resolve(s *state, line []uint8, opcode opcode) (value uint8, address uint16
|
||||||
|
|
||||||
type opcode struct {
|
type opcode struct {
|
||||||
name string
|
name string
|
||||||
bytes int8
|
bytes uint8
|
||||||
cycles int
|
cycles int
|
||||||
addressMode int
|
addressMode int
|
||||||
action opFunc
|
action opFunc
|
||||||
|
@ -279,14 +279,14 @@ const stackAddress uint16 = 0x0100
|
||||||
|
|
||||||
func pushByte(s *state, value uint8) {
|
func pushByte(s *state, value uint8) {
|
||||||
adresss := stackAddress + uint16(s.registers.getSP())
|
adresss := stackAddress + uint16(s.registers.getSP())
|
||||||
s.memory[adresss] = value
|
s.memory.poke(adresss, value)
|
||||||
s.registers.setSP(s.registers.getSP() - 1)
|
s.registers.setSP(s.registers.getSP() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pullByte(s *state) uint8 {
|
func pullByte(s *state) uint8 {
|
||||||
s.registers.setSP(s.registers.getSP() + 1)
|
s.registers.setSP(s.registers.getSP() + 1)
|
||||||
adresss := stackAddress + uint16(s.registers.getSP())
|
adresss := stackAddress + uint16(s.registers.getSP())
|
||||||
return s.memory[adresss]
|
return s.memory.peek(adresss)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushWord(s *state, value uint16) {
|
func pushWord(s *state, value uint16) {
|
||||||
|
@ -530,10 +530,15 @@ func executeLine(s *state, line []uint8) {
|
||||||
|
|
||||||
func executeInstruction(s *state, log bool) {
|
func executeInstruction(s *state, log bool) {
|
||||||
pc := s.registers.getPC()
|
pc := s.registers.getPC()
|
||||||
opcode := opcodes[s.memory[pc]]
|
opcode := opcodes[s.memory.peek(pc)]
|
||||||
pcNext := pc + uint16(opcode.bytes)
|
|
||||||
s.registers.setPC(pcNext)
|
line := make([]uint8, opcode.bytes)
|
||||||
line := s.memory[pc:pcNext]
|
for i := uint8(0); i < opcode.bytes; i++ {
|
||||||
|
line[i] = s.memory.peek(pc)
|
||||||
|
pc++
|
||||||
|
}
|
||||||
|
s.registers.setPC(pc)
|
||||||
|
|
||||||
if log {
|
if log {
|
||||||
fmt.Printf("%#04x %-12s: ", pc, lineString(s, line, opcode))
|
fmt.Printf("%#04x %-12s: ", pc, lineString(s, line, opcode))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
func TestLoad(t *testing.T) {
|
||||||
var s state
|
var s state
|
||||||
|
s.memory.initWithRam()
|
||||||
|
|
||||||
executeLine(&s, []uint8{0xA9, 0x42})
|
executeLine(&s, []uint8{0xA9, 0x42})
|
||||||
if s.registers.getA() != 0x42 {
|
if s.registers.getA() != 0x42 {
|
||||||
|
@ -27,59 +28,59 @@ func TestLoad(t *testing.T) {
|
||||||
t.Error("Error in LDY #")
|
t.Error("Error in LDY #")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0x38] = 0x87
|
s.memory.poke(0x38, 0x87)
|
||||||
executeLine(&s, []uint8{0xA5, 0x38})
|
executeLine(&s, []uint8{0xA5, 0x38})
|
||||||
if s.registers.getA() != 0x87 {
|
if s.registers.getA() != 0x87 {
|
||||||
t.Error("Error in LDA zpg")
|
t.Error("Error in LDA zpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0x57] = 0x90
|
s.memory.poke(0x57, 0x90)
|
||||||
s.registers.setX(0x10)
|
s.registers.setX(0x10)
|
||||||
executeLine(&s, []uint8{0xB5, 0x47})
|
executeLine(&s, []uint8{0xB5, 0x47})
|
||||||
if s.registers.getA() != 0x90 {
|
if s.registers.getA() != 0x90 {
|
||||||
t.Error("Error in LDA zpg, X")
|
t.Error("Error in LDA zpg, X")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0x38] = 0x12
|
s.memory.poke(0x38, 0x12)
|
||||||
s.registers.setX(0x89)
|
s.registers.setX(0x89)
|
||||||
executeLine(&s, []uint8{0xB5, 0xAF})
|
executeLine(&s, []uint8{0xB5, 0xAF})
|
||||||
if s.registers.getA() != 0x12 {
|
if s.registers.getA() != 0x12 {
|
||||||
t.Error("Error in LDA zpgX with sero page overflow")
|
t.Error("Error in LDA zpgX with sero page overflow")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0x1234] = 0x67
|
s.memory.poke(0x1234, 0x67)
|
||||||
executeLine(&s, []uint8{0xAD, 0x34, 0x12})
|
executeLine(&s, []uint8{0xAD, 0x34, 0x12})
|
||||||
if s.registers.getA() != 0x67 {
|
if s.registers.getA() != 0x67 {
|
||||||
t.Error("Error in LDA abs")
|
t.Error("Error in LDA abs")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0xC057] = 0x7E
|
s.memory.poke(0xC057, 0x7E)
|
||||||
s.registers.setX(0x57)
|
s.registers.setX(0x57)
|
||||||
executeLine(&s, []uint8{0xBD, 0x00, 0xC0})
|
executeLine(&s, []uint8{0xBD, 0x00, 0xC0})
|
||||||
if s.registers.getA() != 0x7E {
|
if s.registers.getA() != 0x7E {
|
||||||
t.Error("Error in LDA abs, X")
|
t.Error("Error in LDA abs, X")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0xD059] = 0x7A
|
s.memory.poke(0xD059, 0x7A)
|
||||||
s.registers.setY(0x59)
|
s.registers.setY(0x59)
|
||||||
executeLine(&s, []uint8{0xB9, 0x00, 0xD0})
|
executeLine(&s, []uint8{0xB9, 0x00, 0xD0})
|
||||||
if s.registers.getA() != 0x7A {
|
if s.registers.getA() != 0x7A {
|
||||||
t.Error("Error in LDA abs, Y")
|
t.Error("Error in LDA abs, Y")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0x24] = 0x74
|
s.memory.poke(0x24, 0x74)
|
||||||
s.memory[0x25] = 0x20
|
s.memory.poke(0x25, 0x20)
|
||||||
s.registers.setX(0x04)
|
s.registers.setX(0x04)
|
||||||
s.memory[0x2074] = 0x66
|
s.memory.poke(0x2074, 0x66)
|
||||||
executeLine(&s, []uint8{0xA1, 0x20})
|
executeLine(&s, []uint8{0xA1, 0x20})
|
||||||
if s.registers.getA() != 0x66 {
|
if s.registers.getA() != 0x66 {
|
||||||
t.Error("Error in LDA (oper,X)")
|
t.Error("Error in LDA (oper,X)")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.memory[0x86] = 0x28
|
s.memory.poke(0x86, 0x28)
|
||||||
s.memory[0x87] = 0x40
|
s.memory.poke(0x87, 0x40)
|
||||||
s.registers.setY(0x10)
|
s.registers.setY(0x10)
|
||||||
s.memory[0x4038] = 0x99
|
s.memory.poke(0x4038, 0x99)
|
||||||
executeLine(&s, []uint8{0xB1, 0x86})
|
executeLine(&s, []uint8{0xB1, 0x86})
|
||||||
if s.registers.getA() != 0x99 {
|
if s.registers.getA() != 0x99 {
|
||||||
t.Error("Error in LDA (oper),Y")
|
t.Error("Error in LDA (oper),Y")
|
||||||
|
@ -88,32 +89,33 @@ func TestLoad(t *testing.T) {
|
||||||
|
|
||||||
func TestStore(t *testing.T) {
|
func TestStore(t *testing.T) {
|
||||||
var s state
|
var s state
|
||||||
|
s.memory.initWithRam()
|
||||||
s.registers.setA(0x10)
|
s.registers.setA(0x10)
|
||||||
s.registers.setX(0x40)
|
s.registers.setX(0x40)
|
||||||
s.registers.setY(0x80)
|
s.registers.setY(0x80)
|
||||||
|
|
||||||
executeLine(&s, []uint8{0x85, 0x50})
|
executeLine(&s, []uint8{0x85, 0x50})
|
||||||
if s.memory[0x0050] != 0x10 {
|
if s.memory.peek(0x0050) != 0x10 {
|
||||||
t.Error("Error in STA zpg")
|
t.Error("Error in STA zpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
executeLine(&s, []uint8{0x86, 0x51})
|
executeLine(&s, []uint8{0x86, 0x51})
|
||||||
if s.memory[0x0051] != 0x40 {
|
if s.memory.peek(0x0051) != 0x40 {
|
||||||
t.Error("Error in STX zpg")
|
t.Error("Error in STX zpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
executeLine(&s, []uint8{0x84, 0x52})
|
executeLine(&s, []uint8{0x84, 0x52})
|
||||||
if s.memory[0x0052] != 0x80 {
|
if s.memory.peek(0x0052) != 0x80 {
|
||||||
t.Error("Error in STY zpg")
|
t.Error("Error in STY zpg")
|
||||||
}
|
}
|
||||||
|
|
||||||
executeLine(&s, []uint8{0x8D, 0x20, 0xC0})
|
executeLine(&s, []uint8{0x8D, 0x20, 0xC0})
|
||||||
if s.memory[0xC020] != 0x10 {
|
if s.memory.peek(0xC020) != 0x10 {
|
||||||
t.Error("Error in STA abs")
|
t.Error("Error in STA abs")
|
||||||
}
|
}
|
||||||
|
|
||||||
executeLine(&s, []uint8{0x9D, 0x08, 0x10})
|
executeLine(&s, []uint8{0x9D, 0x08, 0x10})
|
||||||
if s.memory[0x1048] != 0x10 {
|
if s.memory.peek(0x1048) != 0x10 {
|
||||||
t.Error("Error in STA abs, X")
|
t.Error("Error in STA abs, X")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,23 +401,24 @@ func TestCompare(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestBit(t *testing.T) {
|
func TestBit(t *testing.T) {
|
||||||
var s state
|
var s state
|
||||||
|
s.memory.initWithRam()
|
||||||
|
|
||||||
s.registers.setA(0x0F)
|
s.registers.setA(0x0F)
|
||||||
s.memory[0x0040] = 0xF0
|
s.memory.poke(0x0040, 0xF0)
|
||||||
executeLine(&s, []uint8{0x24, 0x40})
|
executeLine(&s, []uint8{0x24, 0x40})
|
||||||
if s.registers.getP() != 0xC2 {
|
if s.registers.getP() != 0xC2 {
|
||||||
t.Errorf("Error in BIT. %v", s.registers)
|
t.Errorf("Error in BIT. %v", s.registers)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.registers.setA(0xF0)
|
s.registers.setA(0xF0)
|
||||||
s.memory[0x0040] = 0xF0
|
s.memory.poke(0x0040, 0xF0)
|
||||||
executeLine(&s, []uint8{0x24, 0x40})
|
executeLine(&s, []uint8{0x24, 0x40})
|
||||||
if s.registers.getP() != 0xC0 {
|
if s.registers.getP() != 0xC0 {
|
||||||
t.Errorf("Error in BIT, 2. %v", s.registers)
|
t.Errorf("Error in BIT, 2. %v", s.registers)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.registers.setA(0xF0)
|
s.registers.setA(0xF0)
|
||||||
s.memory[0x01240] = 0x80
|
s.memory.poke(0x01240, 0x80)
|
||||||
executeLine(&s, []uint8{0x2C, 0x40, 0x12})
|
executeLine(&s, []uint8{0x2C, 0x40, 0x12})
|
||||||
if s.registers.getP() != 0x80 {
|
if s.registers.getP() != 0x80 {
|
||||||
t.Errorf("Error in BIT, 2. %v", s.registers)
|
t.Errorf("Error in BIT, 2. %v", s.registers)
|
||||||
|
@ -424,6 +427,7 @@ func TestBit(t *testing.T) {
|
||||||
|
|
||||||
func TestBranch(t *testing.T) {
|
func TestBranch(t *testing.T) {
|
||||||
var s state
|
var s state
|
||||||
|
|
||||||
s.registers.setPC(0xC600)
|
s.registers.setPC(0xC600)
|
||||||
s.registers.setFlag(flagV)
|
s.registers.setFlag(flagV)
|
||||||
executeLine(&s, []uint8{0x50, 0x20})
|
executeLine(&s, []uint8{0x50, 0x20})
|
||||||
|
@ -446,6 +450,7 @@ func TestBranch(t *testing.T) {
|
||||||
|
|
||||||
func TestStack(t *testing.T) {
|
func TestStack(t *testing.T) {
|
||||||
var s state
|
var s state
|
||||||
|
s.memory.initWithRam()
|
||||||
|
|
||||||
s.registers.setSP(0xF0)
|
s.registers.setSP(0xF0)
|
||||||
s.registers.setA(0xA0)
|
s.registers.setA(0xA0)
|
||||||
|
@ -454,7 +459,7 @@ func TestStack(t *testing.T) {
|
||||||
if s.registers.getSP() != 0xEF {
|
if s.registers.getSP() != 0xEF {
|
||||||
t.Errorf("Error in PHA stack pointer, %v", s.registers)
|
t.Errorf("Error in PHA stack pointer, %v", s.registers)
|
||||||
}
|
}
|
||||||
if s.memory[0x01F0] != 0xA0 {
|
if s.memory.peek(0x01F0) != 0xA0 {
|
||||||
t.Errorf("Error in PHA, %v", s.registers)
|
t.Errorf("Error in PHA, %v", s.registers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +467,7 @@ func TestStack(t *testing.T) {
|
||||||
if s.registers.getSP() != 0xEE {
|
if s.registers.getSP() != 0xEE {
|
||||||
t.Errorf("Error in PHP stack pointer, %v", s.registers)
|
t.Errorf("Error in PHP stack pointer, %v", s.registers)
|
||||||
}
|
}
|
||||||
if s.memory[0x01EF] != 0x3A {
|
if s.memory.peek(0x01EF) != 0x3A {
|
||||||
t.Errorf("Error in PHP, %v", s.registers)
|
t.Errorf("Error in PHP, %v", s.registers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
main.go
2
main.go
|
@ -8,7 +8,7 @@ func main() {
|
||||||
|
|
||||||
s.registers.setPC(0x0400)
|
s.registers.setPC(0x0400)
|
||||||
for true {
|
for true {
|
||||||
testCase := s.memory[0x0200]
|
testCase := s.memory.peek(0x0200)
|
||||||
if testCase >= 240 {
|
if testCase >= 240 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
55
memory.go
55
memory.go
|
@ -6,18 +6,62 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type memory [65536]uint8
|
type memoryPage interface {
|
||||||
|
peek(uint8) uint8
|
||||||
|
poke(uint8, uint8)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ramPage [256]uint8
|
||||||
|
type romPage [256]uint8
|
||||||
|
|
||||||
|
type memory [256]memoryPage
|
||||||
|
|
||||||
|
func (p *ramPage) peek(address uint8) uint8 {
|
||||||
|
return p[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ramPage) poke(address uint8, value uint8) {
|
||||||
|
p[address] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *romPage) peek(address uint8) uint8 {
|
||||||
|
return p[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *romPage) poke(address uint8, value uint8) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memory) peek(address uint16) uint8 {
|
||||||
|
hi := uint8(address >> 8)
|
||||||
|
lo := uint8(address)
|
||||||
|
return m[hi].peek(lo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memory) poke(address uint16, value uint8) {
|
||||||
|
hi := uint8(address >> 8)
|
||||||
|
lo := uint8(address)
|
||||||
|
//fmt.Println(hi)
|
||||||
|
m[hi].poke(lo, value)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *memory) getWord(address uint16) uint16 {
|
func (m *memory) getWord(address uint16) uint16 {
|
||||||
return uint16(m[address]) + 0x100*uint16(m[address+1])
|
return uint16(m.peek(address)) + 0x100*uint16(m.peek(address+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memory) getZeroPageWord(address uint8) uint16 {
|
func (m *memory) getZeroPageWord(address uint8) uint16 {
|
||||||
return uint16(m[address]) + 0x100*uint16(m[address+1])
|
return uint16(m.peek(uint16(address))) + 0x100*uint16(m.peek(uint16(address+1)))
|
||||||
// TODO: Does address + 1 wraps around the zero page?
|
}
|
||||||
|
|
||||||
|
func (m *memory) initWithRam() {
|
||||||
|
var ramPages [256]ramPage
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
m[i] = &ramPages[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memory) loadBinary(filename string) {
|
func (m *memory) loadBinary(filename string) {
|
||||||
|
// Load file
|
||||||
f, err := os.Open(filename)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -35,8 +79,9 @@ func (m *memory) loadBinary(filename string) {
|
||||||
buf := bufio.NewReader(f)
|
buf := bufio.NewReader(f)
|
||||||
buf.Read(bytes)
|
buf.Read(bytes)
|
||||||
|
|
||||||
|
m.initWithRam()
|
||||||
for i, v := range bytes {
|
for i, v := range bytes {
|
||||||
m[i] = uint8(v)
|
m.poke(uint16(i), uint8(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
73
textPages.go
Normal file
73
textPages.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type textPages struct {
|
||||||
|
dirty bool
|
||||||
|
pages *[4]textPage
|
||||||
|
}
|
||||||
|
|
||||||
|
type textPage struct {
|
||||||
|
textPages *textPages
|
||||||
|
data [256]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textPage) peek(address uint8) uint8 {
|
||||||
|
return p.data[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textPage) poke(address uint8, value uint8) {
|
||||||
|
p.data[address] = value
|
||||||
|
// Note: we could avoid setting dirty on the 16 blocks of 8 hidden bytes
|
||||||
|
p.textPages.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textMemoryByteToString(value uint8) string {
|
||||||
|
return string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *textPages) dump() {
|
||||||
|
// See "Understand the Apple II", page 5-10
|
||||||
|
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||||
|
|
||||||
|
var i, j, h uint8
|
||||||
|
// Top, middle and botton screen
|
||||||
|
for i = 0; i < 128; i = i + 40 {
|
||||||
|
// Memory pages
|
||||||
|
for _, p := range tp.pages {
|
||||||
|
// The two half pages
|
||||||
|
for _, h = range []uint8{0, 128} {
|
||||||
|
line := ""
|
||||||
|
for j = i + h; j < i+h+40; j++ {
|
||||||
|
line += string(p.peek(j))
|
||||||
|
}
|
||||||
|
fmt.Println(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *textPages) dumpIfDirty() {
|
||||||
|
if !tp.dirty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tp.dirty = false
|
||||||
|
tp.dump()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *textPages) charAddress(column uint8, line uint8) (page uint8, address uint8) {
|
||||||
|
page = (line / 3) % 4
|
||||||
|
address = column + (line/8)*40 + (line%2)*128
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *textPages) read(column uint8, line uint8) uint8 {
|
||||||
|
page, address := tp.charAddress(column, line)
|
||||||
|
return tp.pages[page].peek(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *textPages) write(column uint8, line uint8, value uint8) {
|
||||||
|
page, address := tp.charAddress(column, line)
|
||||||
|
tp.pages[page].poke(address, value)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user