Compare commits

...

4 Commits

Author SHA1 Message Date
Will Angenent 5899396fb6 Added scale to the command line 2019-11-25 23:47:11 +00:00
Will Angenent 85417562c1 Added colored hires mode and monochrome keyboard toggle 2019-11-25 21:15:14 +00:00
Will Angenent 9323078bc0 Added flag to disable DOS delay code 2019-11-10 12:32:16 +00:00
Will Angenent 4e5449f3ca Migrated to GO11 modules 2019-11-02 15:50:17 +00:00
23 changed files with 281 additions and 140 deletions

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
vendor
glide.lock
apple2
apple2-go
apple2.test
apple2e.rom
dos33.dsk

View File

@ -7,8 +7,8 @@ An Apple //e emulator written in Go using [ebiten](https://github.com/hajimehosh
* MOS 6502 CPU
* Keyboard
* 40 column text mode
* Low resolution color graphics
* High resolution monochrome graphics
* Low resolution monochrome and color graphics
* High resolution monochrome and color graphics
* Upper memory bank switching: $d000 page and ROM/RAM
* Main memory page1/page2 switching in text, lores and hires
* Disk image reading & writing
@ -16,9 +16,7 @@ An Apple //e emulator written in Go using [ebiten](https://github.com/hajimehosh
## Installation
Install prerequisites with glide
glide up
The installation requires go modules go be installed
Build the executable
@ -29,14 +27,14 @@ Download `apple2e.rom` from
## Running it
./apple2
./apple2 my_disk_image.dsk
./apple2 -drive-head-click my_disk_image.dsk
./apple2-go
./apple2-go my_disk_image.dsk
./apple2-go -drive-head-click my_disk_image.dsk
## Keyboard shortcuts
* ctrl-alt-R reset
* ctrl-alt-M mute
* ctrl-alt-M toggle monochrome/color display
* ctrl-alt-C caps lock
* ctrl-alt-F show FPS
@ -57,7 +55,7 @@ The CPU tests make use of [Klaus2m5's](https://github.com/Klaus2m5/6502_65C02_fu
### Creating the CPU test ROMs
The source files are `6502_functional_test.a65` and `6502_interrupt_test.a65`. They are assembled using `as65` into a binary file which contains a memory image of the test code. They are compressed into gzip files which are loaded into the apple memory by the unit tests.
The source files are `6502_functional_test.a65` and `6502_interrupt_test.a65`. They are assembled using `as65` into a binary file which contains a memory image of the test code. They are compressed into gzip files which are loaded into the apple memory by the unit tests.
Download [as65](http://www.kingswood-consulting.co.uk/assemblers/as65_142.zip) and unzip it to get the `as65` assembler binary.

View File

@ -10,23 +10,26 @@ import (
"github.com/hajimehoshi/ebiten"
"github.com/freewilll/apple2/audio"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/disk"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/utils"
"github.com/freewilll/apple2/video"
"github.com/freewilll/apple2-go/audio"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/disk"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/utils"
"github.com/freewilll/apple2-go/video"
)
var (
showInstructions *bool // Display all instructions as they are executed
disableFirmwareWait *bool // Disable the WAIT function at $fca8
disableDosDelay *bool // Disable DOS delay functions
breakAddress *uint16 // Break address from the command line
scale float64 // Scale
resetKeysDown bool // Keep track of ctrl-alt-R key down state
fpsKeysDown bool // Keep track of ctrl-alt-F key down state
resetKeysDown bool // Keep track of ctrl-alt-R key down state
fpsKeysDown bool // Keep track of ctrl-alt-F key down state
monochromeKeysDown bool // Keep track of ctrl-alt-M key down state
)
// checkSpecialKeys checks
@ -52,20 +55,33 @@ func checkSpecialKeys() {
} else {
fpsKeysDown = false
}
// Check for ctrl-alt-M and toggle FPS display
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyM) {
monochromeKeysDown = true
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyM) && monochromeKeysDown {
monochromeKeysDown = false
video.Monochrome = !video.Monochrome
} else {
monochromeKeysDown = false
}
}
// update is the main ebiten loop
func update(screen *ebiten.Image) error {
keyboard.Poll() // Convert ebiten's keyboard state to an interal value
checkSpecialKeys() // Poll the keyboard and check for R and F keys
if !(fpsKeysDown || monochromeKeysDown) {
keyboard.Poll() // Convert ebiten's keyboard state to an interal value
}
system.FrameCycles = 0 // Reset cycles processed this frame
system.LastAudioCycles = 0 // Reset processed audio cycles
exitAtBreak := true // Die if a BRK instruction is seen
// Run for 1/60 of a second, the duration of an ebiten frame
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, system.CPUFrequency/60)
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, *disableDosDelay, system.CPUFrequency/60)
// Process any audio speaker clicks from this frame
audio.ForwardToFrameCycle()
@ -88,8 +104,10 @@ func main() {
showInstructions = flag.Bool("show-instructions", false, "Show instructions code while running")
disableFirmwareWait = flag.Bool("disable-wait", false, "Ignore JSRs to firmware wait at $FCA8")
disableDosDelay = flag.Bool("disable-dos-delay", false, "Ignore DOS ARM move and motor on waits")
breakAddressString := flag.String("break", "", "Break on address")
mute := flag.Bool("mute", false, "Mute sound")
scale := flag.Float64("scale", 2, "Video scale")
clickWhenDriveHeadMoves := flag.Bool("drive-head-click", false, "Click speaker when drive head moves")
flag.Parse()
@ -120,7 +138,7 @@ func main() {
// Start the ebiten main loop
ebiten.SetRunnableInBackground(true)
ebiten.Run(update, 280*video.ScreenSizeFactor, 192*video.ScreenSizeFactor, 2, "Apple //e")
ebiten.Run(update, 560, 384, *scale, "Apple //e")
// The main loop has ended, flush any data to the disk image if any writes have been done.
disk.FlushImage()

View File

@ -5,7 +5,7 @@ package audio
// channel is filled with the last audio samples. The channel is also
// filled at the end of the frame.
import "github.com/freewilll/apple2/system"
import "github.com/freewilll/apple2-go/system"
// Click handles a speaker click
func Click() {

View File

@ -5,7 +5,7 @@ package audio
import (
"errors"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/system"
ebiten_audio "github.com/hajimehoshi/ebiten/audio"
)

View File

@ -3,11 +3,11 @@ package main
import (
"testing"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/video"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/video"
"github.com/stretchr/testify/assert"
)

View File

@ -4,9 +4,9 @@ import (
"fmt"
"testing"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
)
func testBellCycles(delay int) {
@ -23,8 +23,9 @@ func testBellCycles(delay int) {
breakAddress := uint16(0x805)
exitAtBreak := false
disableFirmwareWait := false
disableDosDelay := false
cpu.State.PC = 0x800
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, system.CPUFrequency*1000)
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, disableDosDelay, system.CPUFrequency*1000)
// See http://apple2.org.za/gswv/a2zine/GS.WorldView/Resources/USEFUL.TABLES/WAIT.DELAY.CR.txt
expectedCycles := (26 + 27*delay + 5*delay*delay) / 2

View File

@ -7,9 +7,9 @@ import (
"fmt"
"os"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/utils"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/utils"
)
func main() {

View File

@ -4,8 +4,8 @@ import (
"fmt"
"os"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
)
const (
@ -551,7 +551,7 @@ func nmi() {
// Run runs the CPU until either wantedCycles has been reached (if non-zero) or the program counter reaches breakAddress.
// system.FrameCycles is the amount of cycles executed so far.
func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableFirmwareWait bool, wantedCycles uint64) {
func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableFirmwareWait bool, disableDosDelay bool, wantedCycles uint64) {
system.FrameCycles = 0
for {
@ -632,6 +632,10 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
State.PC += 3
State.A = 0
continue
} else if disableDosDelay && value == 0xba00 {
// Don't call the delay, just move forward and pretend it happened.
State.PC += 3
continue
}
push16(State.PC + 2)
@ -647,6 +651,15 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
case 0xa2, 0xa6, 0xb6, 0xae, 0xbe: // LDX
State.X = load(addressMode)
case 0xa0, 0xa4, 0xb4, 0xac, 0xbc: // LDY
if disableDosDelay {
pc := uint16(mmu.ReadMemory(State.PC+1)) + uint16(mmu.ReadMemory(State.PC+2))<<8
if pc == 0xbd9e {
// Don't do delay, just move forward and pretend it happened.
State.PC += 13
continue
}
}
State.Y = load(addressMode)
case 0x85, 0x95, 0x8d, 0x9d, 0x99, 0x81, 0x91: //STA

View File

@ -9,10 +9,10 @@ import (
"fmt"
"testing"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/utils"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/utils"
)
func TestCPU(t *testing.T) {
@ -76,7 +76,7 @@ func TestCPU(t *testing.T) {
mmu.WritePageTable[0xc0+i] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100]
}
cpu.Run(*showInstructions, breakAddress, true, false, 0)
cpu.Run(*showInstructions, breakAddress, true, false, false, 0)
fmt.Printf("Finished running %s\n\n", rom)
}
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2-go/mmu"
)
// printFlag prints a lower or uppercase letter depending on the state of the flag

View File

@ -4,7 +4,7 @@ import (
"fmt"
"io/ioutil"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/system"
)
const tracksPerDisk = 35

View File

@ -5,13 +5,13 @@ import (
"testing"
"time"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/disk"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/utils"
"github.com/freewilll/apple2/video"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/disk"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/utils"
"github.com/freewilll/apple2-go/video"
)
const dosDiskImage = "dos33.dsk"

View File

@ -3,13 +3,13 @@ package main
import (
"testing"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/disk"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/utils"
"github.com/freewilll/apple2/video"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/disk"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/utils"
"github.com/freewilll/apple2-go/video"
)
const rwtsDosDiskImage = "dos33.dsk"

View File

@ -1,12 +0,0 @@
package: github.com/freewilll/apple2
import:
- package: github.com/hajimehoshi/ebiten
version: ^1.7.0
subpackages:
- audio
- ebitenutil
testImport:
- package: github.com/stretchr/testify
version: ^1.2.1
subpackages:
- assert

20
go.mod Normal file
View File

@ -0,0 +1,20 @@
module github.com/freewilll/apple2-go
go 1.13
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080 // indirect
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326 // indirect
github.com/gofrs/flock v0.7.1 // indirect
github.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb // indirect
github.com/gopherjs/webgl v0.0.0-20180508003723-39bd6d41eeb5 // indirect
github.com/hajimehoshi/ebiten v1.7.0
github.com/hajimehoshi/oto v0.0.0-20180404145402-7a1d13b19d82 // indirect
github.com/stretchr/testify v1.4.0
github.com/theckman/go-flock v0.7.1 // indirect
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/mobile v0.0.0-20180522193631-5665cf37628b // indirect
golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect
)

38
go.sum Normal file
View File

@ -0,0 +1,38 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080 h1:pNxZva3052YM+z2p1aP08FgaTE2NzrRJZ5BHJCmKLzE=
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326 h1:QqWaXlVeUGwSH7hO8giZP2Y06Qjl1LWR+FWC22YQsU8=
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb h1:g0omhilXoAZ+6sFcF6puAzT+/MoKK3ZBQ9e7nVIRjrc=
github.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/webgl v0.0.0-20180508003723-39bd6d41eeb5 h1:vrKguNTgy5fq7lTzG9YNM9u8QOsNbEN2ejPt1k6gR/4=
github.com/gopherjs/webgl v0.0.0-20180508003723-39bd6d41eeb5/go.mod h1:obh2agNa9TmQ5C1MrSr2jgLIqV0b4Cl96m/ig2VAXwM=
github.com/hajimehoshi/ebiten v1.7.0 h1:wco7g543jnaTCoNdRS8PJDvfs354tpgD2yeMlqBhAU0=
github.com/hajimehoshi/ebiten v1.7.0/go.mod h1:gK6oXr/7HwFjJZfV7RssGfm18GGNIJmpBsAd1saFLFU=
github.com/hajimehoshi/oto v0.0.0-20180404145402-7a1d13b19d82 h1:Ub4U4W87BWF85gDemt36PiuitaO4LRVaQkDCgzp77hM=
github.com/hajimehoshi/oto v0.0.0-20180404145402-7a1d13b19d82/go.mod h1:Co7jIdNa4+UYZF0whfBysf8qY6o7oV8dFC1Ld//5HmY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/theckman/go-flock v0.7.1 h1:YdJyIjDuQdEU7voZ9YaeXSO4OnrxdI+WejPUwyZ/Txs=
github.com/theckman/go-flock v0.7.1/go.mod h1:kjuth3y9VJ2aNlkNEO99G/8lp9fMIKaGyBmh84IBheM=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de h1:xSjD6HQTqT0H/k60N5yYBtnN1OEkVy7WIo/DYyxKRO0=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20180522193631-5665cf37628b h1:76cRE6suQlpxQe7chtgJiEOaG6vNrh2kHhFQ6WncHQQ=
golang.org/x/mobile v0.0.0-20180522193631-5665cf37628b/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -3,11 +3,11 @@ package main
import (
"testing"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/video"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/video"
"github.com/stretchr/testify/assert"
)

View File

@ -3,10 +3,10 @@ package mmu
import (
"fmt"
"github.com/freewilll/apple2/audio"
"github.com/freewilll/apple2/disk"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/audio"
"github.com/freewilll/apple2-go/disk"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/system"
)
// Adapted from

View File

@ -4,7 +4,7 @@ import (
"fmt"
"io/ioutil"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/system"
)
// RomPath is a hardcoded path to an Apple //e ROM file that's loaded at startup

View File

@ -5,13 +5,13 @@ import (
"testing"
"time"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/disk"
"github.com/freewilll/apple2/keyboard"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2/utils"
"github.com/freewilll/apple2/video"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/disk"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/utils"
"github.com/freewilll/apple2-go/video"
)
const prodosDiskImage = "prodos19.dsk"

View File

@ -8,8 +8,8 @@ import (
"os"
"testing"
"github.com/freewilll/apple2/cpu"
"github.com/freewilll/apple2/system"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/system"
)
// ReadMemoryFromGzipFile just reads and uncompresses a gzip file
@ -60,7 +60,8 @@ func RunUntilBreakPoint(t *testing.T, breakAddress uint16, seconds int, showInst
system.LastAudioCycles = 0
exitAtBreak := false
disableFirmwareWait := false
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, uint64(system.CPUFrequency*seconds))
disableDosDelay := false
cpu.Run(showInstructions, &breakAddress, exitAtBreak, disableFirmwareWait, disableDosDelay, uint64(system.CPUFrequency*seconds))
if cpu.State.PC != breakAddress {
t.Fatalf("Did not reach breakpoint at %04x. Got to %04x", breakAddress, cpu.State.PC)
}

View File

@ -8,13 +8,10 @@ import (
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/freewilll/apple2/mmu"
"github.com/freewilll/apple2-go/mmu"
)
const (
// ScreenSizeFactor is the fFactor by which the whole screen is resized
ScreenSizeFactor = 1
textVideoMemory = 0x400 // Base location of page 1 text video memory
flashFrames = 11 // Number of frames when FLASH mode is toggled
)
@ -23,19 +20,27 @@ const (
type drawTextLoresByte func(*ebiten.Image, int, int, uint8) error
var (
flashCounter int // Counter used for flashing characters on the text screen
flashOn bool // Are we currently flashing?
loresSquares [16]*ebiten.Image // Colored blocks for lores rendering
flashCounter int // Counter used for flashing characters on the text screen
flashOn bool // Are we currently flashing?
monochromeLoresSquares [16]*ebiten.Image // Monochrome blocks for lores rendering
colorLoresSquares [16]*ebiten.Image // Colored blocks for lores rendering
colors [16]color.NRGBA // 4-bit Colors
// ShowFPS determines if the FPS is shown in the corner of the video
ShowFPS bool
ShowFPS bool
Monochrome bool
)
// initLoresSquares creates 16 colored squares for the lores renderer
func initLoresSquares() {
var err error
for i := 0; i < 16; i++ {
loresSquares[i], err = ebiten.NewImage(7, 4, ebiten.FilterNearest)
monochromeLoresSquares[i], err = ebiten.NewImage(7, 4, ebiten.FilterNearest)
if err != nil {
panic(err)
}
colorLoresSquares[i], err = ebiten.NewImage(7, 4, ebiten.FilterNearest)
if err != nil {
panic(err)
}
@ -45,27 +50,36 @@ func initLoresSquares() {
// https://mrob.com/pub/xgithub.com/freewilll/apple2/colors.html
// https://archive.org/details/IIgs_2523063_Master_Color_Values
alpha := uint8(0xff)
loresSquares[0x00].Fill(color.NRGBA{0, 0, 0, alpha})
loresSquares[0x01].Fill(color.NRGBA{221, 0, 51, alpha})
loresSquares[0x02].Fill(color.NRGBA{0, 0, 153, alpha})
loresSquares[0x03].Fill(color.NRGBA{221, 34, 221, alpha})
loresSquares[0x04].Fill(color.NRGBA{0, 119, 34, alpha})
loresSquares[0x05].Fill(color.NRGBA{85, 85, 85, alpha})
loresSquares[0x06].Fill(color.NRGBA{34, 34, 255, alpha})
loresSquares[0x07].Fill(color.NRGBA{102, 170, 255, alpha})
loresSquares[0x08].Fill(color.NRGBA{136, 85, 0, alpha})
loresSquares[0x09].Fill(color.NRGBA{255, 102, 0, alpha})
loresSquares[0x0A].Fill(color.NRGBA{170, 170, 170, alpha})
loresSquares[0x0B].Fill(color.NRGBA{255, 153, 136, alpha})
loresSquares[0x0C].Fill(color.NRGBA{17, 221, 0, alpha})
loresSquares[0x0D].Fill(color.NRGBA{255, 255, 0, alpha})
loresSquares[0x0E].Fill(color.NRGBA{68, 255, 153, alpha})
loresSquares[0x0F].Fill(color.NRGBA{255, 255, 255, alpha})
colors[0x00] = color.NRGBA{0, 0, 0, alpha}
colors[0x01] = color.NRGBA{221, 0, 51, alpha}
colors[0x02] = color.NRGBA{0, 0, 153, alpha}
colors[0x03] = color.NRGBA{221, 34, 221, alpha}
colors[0x04] = color.NRGBA{0, 119, 34, alpha}
colors[0x05] = color.NRGBA{85, 85, 85, alpha}
colors[0x06] = color.NRGBA{34, 34, 255, alpha}
colors[0x07] = color.NRGBA{102, 170, 255, alpha}
colors[0x08] = color.NRGBA{136, 85, 0, alpha}
colors[0x09] = color.NRGBA{255, 102, 0, alpha}
colors[0x0A] = color.NRGBA{170, 170, 170, alpha}
colors[0x0B] = color.NRGBA{255, 153, 136, alpha}
colors[0x0C] = color.NRGBA{17, 221, 0, alpha}
colors[0x0D] = color.NRGBA{255, 255, 0, alpha}
colors[0x0E] = color.NRGBA{68, 255, 153, alpha}
colors[0x0F] = color.NRGBA{255, 255, 255, alpha}
for i := 0; i < 0x10; i++ {
colorLoresSquares[i].Fill(colors[i])
avgIntensity := float64(int(colors[i].R)+int(colors[i].G)+int(colors[i].B)) / 3
avgColor := color.NRGBA{byte(avgIntensity * 0.2), byte(avgIntensity * 0.75), byte(avgIntensity * 0.2), alpha}
monochromeLoresSquares[i].Fill(avgColor)
}
}
// Init the video data structures used for rendering
func Init() {
ShowFPS = false
Monochrome = true
initTextCharMap()
initLoresSquares()
@ -95,8 +109,8 @@ func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
}
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(ScreenSizeFactor, ScreenSizeFactor)
op.GeoM.Translate(ScreenSizeFactor*7*float64(x), ScreenSizeFactor*8*float64(y))
op.GeoM.Scale(2, 2)
op.GeoM.Translate(2*7*float64(x), 2*8*float64(y))
r := image.Rect(0, 0, 7, 8)
op.SourceRect = &r
@ -107,8 +121,10 @@ func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
op.ColorM.Translate(1, 1, 1, 0)
}
// Make it look greenish
op.ColorM.Scale(0.20, 0.75, 0.20, 1)
if Monochrome {
// Make it look greenish
op.ColorM.Scale(0.20, 0.75, 0.20, 1)
}
return screen.DrawImage(charMap[value], op)
}
@ -121,9 +137,17 @@ func drawLores(screen *ebiten.Image, x int, y int, value uint8) error {
// Render top & bottom squares
for i := 0; i < 2; i++ {
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(ScreenSizeFactor, ScreenSizeFactor)
op.GeoM.Translate(ScreenSizeFactor*7*float64(x), ScreenSizeFactor*8*float64(y)+float64(i)*4)
if err := screen.DrawImage(loresSquares[values[i]], op); err != nil {
op.GeoM.Scale(2, 2)
op.GeoM.Translate(2*7*float64(x), 2*8*float64(y)+2*float64(i)*4)
var loresSquare *ebiten.Image
if Monochrome {
loresSquare = monochromeLoresSquares[values[i]]
} else {
loresSquare = colorLoresSquares[values[i]]
}
if err := screen.DrawImage(loresSquare, op); err != nil {
return err
}
}
@ -187,11 +211,8 @@ func drawTextOrLoresScreen(screen *ebiten.Image) error {
// drawHiresScreen draws an entire hires screen. If it's in mixed mode, the lower end is drawn in text.
func drawHiresScreen(screen *ebiten.Image) error {
if ScreenSizeFactor != 1 {
panic("Hires mode for ScreenSizeFactor != 1 not implemented")
}
pixels := make([]byte, 280*192*4)
pixels := make([]byte, 560*384*4)
halfPixels := make([]byte, 14)
// Loop over all hires lines
for y := 0; y < 192; y++ {
@ -207,21 +228,65 @@ func drawHiresScreen(screen *ebiten.Image) error {
yOffset += 0x2000
}
// For each byte, flip the 7 bits and write it to the pixels array
// Initialize 4-bit 4-color and 14 half-pixels
var color uint8 // Current 4-bit color
colorPos := uint8(0) // Current half-pixel in the 4-bit color
for i := 0; i < 14; i++ {
halfPixels[i] = 0
}
// For each byte, expand the 7 bits to the 14 half-pixels array
// If the high bit is set, shift one half pixel over.
// Don't shift half-bits in monochrome mode
for x := 0; x < 40; x++ {
offset := yOffset + x
value := mmu.ReadPageTable[offset>>8][offset&0xff]
value &= 0x7f
phaseShifted := value >> 7
var hp uint8
if Monochrome {
hp = 0
} else {
hp = phaseShifted
}
halfPixels[0] = halfPixels[13] // Rotate the last phase shifted pixel in
// Double up the pixels into half pixels starting at offset hp
for bit := 0; bit < 7; bit++ {
b := float64(value & 1)
halfPixels[hp] = value & 1
hp = hp + 1
if hp < 14 {
halfPixels[hp] = value & 1
hp = hp + 1
}
value = value >> 1
p := (y*280 + x*7 + bit) * 4
}
pixels[p+0] = byte(0xff * float64(0.20) * b)
pixels[p+1] = byte(0xff * float64(0.75) * b)
pixels[p+2] = byte(0xff * float64(0.20) * b)
pixels[p+3] = 0xff
for hp = 0; hp < 14; hp++ {
// Update the color bit in colorPos with the half pixel value
color &= ((1 << colorPos) ^ 0xf)
color |= halfPixels[hp] << colorPos
colorPos = (colorPos + 1) & 3
// Draw two lines at a time
for rowDouble := 0; rowDouble < 2; rowDouble++ {
p := ((y*2+rowDouble)*560 + x*2*7 + int(hp)) * 4
if Monochrome {
b := float64(halfPixels[hp])
pixels[p+0] = byte(0xff * float64(0.20) * b)
pixels[p+1] = byte(0xff * float64(0.75) * b)
pixels[p+2] = byte(0xff * float64(0.20) * b)
pixels[p+3] = 0xff
} else {
pixels[p+0] = colors[color].R
pixels[p+1] = colors[color].G
pixels[p+2] = colors[color].B
pixels[p+3] = 0xff
}
}
}
}
}