mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-01-21 23:29:46 +00:00
Project reorg. Separate executables for the SDL and console versions
This commit is contained in:
parent
cf4a7e2115
commit
f9d213a806
@ -4,10 +4,11 @@ import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go6502/core6502"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ivanizag/apple2/core6502"
|
||||
)
|
||||
|
||||
// Apple2 represents all the components and state of the emulated machine
|
@ -1,173 +0,0 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
Uses the console standard input and output to interface with the machine.
|
||||
Input is buffered until the next CR. This avoids working in place, a line
|
||||
for input is added at the end.
|
||||
Outut is done in place using ANSI escape sequences.
|
||||
|
||||
Those tricks do not work with the Apple2e ROM
|
||||
*/
|
||||
|
||||
type ansiConsoleFrontend struct {
|
||||
apple2 *Apple2
|
||||
keyChannel chan uint8
|
||||
extraLineFeeds chan int
|
||||
stdinKeyboard bool
|
||||
lastContent string
|
||||
}
|
||||
|
||||
func newAnsiConsoleFrontend(a *Apple2, stdinKeyboard bool) *ansiConsoleFrontend {
|
||||
var fe ansiConsoleFrontend
|
||||
fe.apple2 = a
|
||||
fe.stdinKeyboard = stdinKeyboard
|
||||
return &fe
|
||||
}
|
||||
|
||||
const refreshDelayMs = 100
|
||||
|
||||
func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) {
|
||||
|
||||
// Init the first time
|
||||
if fe.keyChannel == nil {
|
||||
stdinReader := func(c chan uint8) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
key, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
c <- key
|
||||
}
|
||||
}
|
||||
|
||||
fe.keyChannel = make(chan uint8, 100)
|
||||
go stdinReader(fe.keyChannel)
|
||||
}
|
||||
|
||||
if !strobed {
|
||||
// We must use the strobe to control the flow from stdin
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case key = <-fe.keyChannel:
|
||||
if key == 10 {
|
||||
key = 13
|
||||
if fe.extraLineFeeds != nil {
|
||||
fe.extraLineFeeds <- 1
|
||||
}
|
||||
}
|
||||
ok = true
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ansiCursorUp(steps int) string {
|
||||
return fmt.Sprintf("\033[%vA", steps)
|
||||
}
|
||||
|
||||
func (fe *ansiConsoleFrontend) textModeGoRoutine() {
|
||||
fe.extraLineFeeds = make(chan int, 100)
|
||||
|
||||
fmt.Printf(strings.Repeat("\n", textLines+3))
|
||||
for {
|
||||
// Go up
|
||||
content := ansiCursorUp(textLines + 3)
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case lineFeeds := <-fe.extraLineFeeds:
|
||||
content += ansiCursorUp(lineFeeds)
|
||||
default:
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
content += "\n"
|
||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
||||
|
||||
pageIndex := 0
|
||||
if fe.apple2.io.isSoftSwitchActive(ioFlagSecondPage) {
|
||||
pageIndex = 1
|
||||
}
|
||||
isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(ioFlagAltChar)
|
||||
|
||||
for l := 0; l < textLines; l++ {
|
||||
line := ""
|
||||
for c := 0; c < textColumns; c++ {
|
||||
char := getTextChar(fe.apple2, c, l, pageIndex)
|
||||
line += textMemoryByteToString(char, isAltText)
|
||||
}
|
||||
content += fmt.Sprintf("# %v #\n", line)
|
||||
}
|
||||
|
||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
||||
if fe.stdinKeyboard {
|
||||
content += "\033[KLine: "
|
||||
}
|
||||
|
||||
if content != fe.lastContent {
|
||||
fmt.Print(content)
|
||||
fe.lastContent = content
|
||||
}
|
||||
time.Sleep(refreshDelayMs * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func textMemoryByteToString(value uint8, isAltCharSet bool) string {
|
||||
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
||||
// Supports the new lowercase characters in the Apple2e
|
||||
// Only ascii from 0x20 to 0x5F is visible
|
||||
topBits := value >> 6
|
||||
isInverse := topBits == 0
|
||||
isFlash := topBits == 1
|
||||
if isFlash && isAltCharSet {
|
||||
// On the Apple2e with lowercase chars there is not flash mode.
|
||||
isFlash = false
|
||||
isInverse = true
|
||||
}
|
||||
|
||||
if isAltCharSet {
|
||||
value = value & 0x7F
|
||||
} else {
|
||||
value = value & 0x3F
|
||||
}
|
||||
|
||||
if value < 0x20 {
|
||||
value += 0x40
|
||||
}
|
||||
|
||||
if value == 0x7f {
|
||||
// DEL is full box
|
||||
value = '_'
|
||||
}
|
||||
|
||||
if isFlash {
|
||||
if value == ' ' {
|
||||
// Flashing space in Apple is the full box. It can't be done with ANSI codes
|
||||
value = '_'
|
||||
}
|
||||
return fmt.Sprintf("\033[5m%v\033[0m", string(value))
|
||||
} else if isInverse {
|
||||
return fmt.Sprintf("\033[7m%v\033[0m", string(value))
|
||||
} else {
|
||||
return string(value)
|
||||
}
|
||||
}
|
||||
|
||||
func textMemoryByteToStringHex(value uint8, _ bool) string {
|
||||
return fmt.Sprintf("%02x ", value)
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,8 +1,6 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"go6502/core6502"
|
||||
)
|
||||
import "github.com/ivanizag/apple2/core6502"
|
||||
|
||||
// NewApple2 instantiates an apple2
|
||||
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
||||
@ -70,22 +68,6 @@ func (a *Apple2) AddCardInOut(slot int) {
|
||||
a.insertCard(&cardInOut{}, slot)
|
||||
}
|
||||
|
||||
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
||||
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
||||
if !stdinKeyboard && !stdoutScreen {
|
||||
return
|
||||
}
|
||||
|
||||
// Init frontend
|
||||
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
||||
if stdinKeyboard {
|
||||
a.io.setKeyboardProvider(fe)
|
||||
}
|
||||
if stdoutScreen {
|
||||
go fe.textModeGoRoutine()
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeyboardProvider attaches an external keyboard provider
|
||||
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||
a.io.setKeyboardProvider(kb)
|
BIN
apple2console/apple2console
Executable file
BIN
apple2console/apple2console
Executable file
Binary file not shown.
112
apple2console/main.go
Normal file
112
apple2console/main.go
Normal file
@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apple2 "github.com/ivanizag/apple2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := apple2.MainApple()
|
||||
fe := &ansiConsoleFrontend{}
|
||||
a.SetKeyboardProvider(fe)
|
||||
go fe.textModeGoRoutine(a)
|
||||
|
||||
a.Run(false)
|
||||
}
|
||||
|
||||
/*
|
||||
Uses the console standard input and output to interface with the machine.
|
||||
Input is buffered until the next CR. This avoids working in place, a line
|
||||
for input is added at the end.
|
||||
Outut is done in place using ANSI escape sequences.
|
||||
|
||||
Those tricks do not work with the Apple2e ROM
|
||||
*/
|
||||
|
||||
type ansiConsoleFrontend struct {
|
||||
keyChannel chan uint8
|
||||
extraLineFeeds chan int
|
||||
lastContent string
|
||||
}
|
||||
|
||||
const refreshDelayMs = 100
|
||||
|
||||
func (fe *ansiConsoleFrontend) GetKey(strobed bool) (key uint8, ok bool) {
|
||||
|
||||
// Init the first time
|
||||
if fe.keyChannel == nil {
|
||||
stdinReader := func(c chan uint8) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
key, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if key == 10 {
|
||||
key = 13
|
||||
if fe.extraLineFeeds != nil {
|
||||
fe.extraLineFeeds <- 1
|
||||
}
|
||||
}
|
||||
|
||||
c <- key
|
||||
}
|
||||
}
|
||||
|
||||
fe.keyChannel = make(chan uint8, 100)
|
||||
go stdinReader(fe.keyChannel)
|
||||
}
|
||||
|
||||
if !strobed {
|
||||
// We must use the strobe to control the flow from stdin
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case key = <-fe.keyChannel:
|
||||
ok = true
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ansiCursorUp(steps int) string {
|
||||
return fmt.Sprintf("\033[%vA", steps)
|
||||
}
|
||||
|
||||
func (fe *ansiConsoleFrontend) textModeGoRoutine(a *apple2.Apple2) {
|
||||
fe.extraLineFeeds = make(chan int, 100)
|
||||
|
||||
fmt.Printf(strings.Repeat("\n", 24+3))
|
||||
for {
|
||||
// Go up
|
||||
content := ansiCursorUp(24 + 3)
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case lineFeeds := <-fe.extraLineFeeds:
|
||||
content += ansiCursorUp(lineFeeds)
|
||||
default:
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
content += apple2.DumpTextModeAnsi(a)
|
||||
content += "\033[KLine: "
|
||||
|
||||
if content != fe.lastContent {
|
||||
fmt.Print(content)
|
||||
fe.lastContent = content
|
||||
}
|
||||
time.Sleep(refreshDelayMs * time.Millisecond)
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
package main
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go6502/apple2"
|
||||
"go6502/apple2sdl"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// MainApple is a device independant main. Video, keyboard and speaker won't be defined
|
||||
func MainApple() *Apple2 {
|
||||
romFile := flag.String(
|
||||
"rom",
|
||||
"apple2/romdumps/Apple2_Plus.rom",
|
||||
"../romdumps/Apple2_Plus.rom",
|
||||
"main rom file")
|
||||
disk2RomFile := flag.String(
|
||||
"diskRom",
|
||||
"apple2/romdumps/DISK2.rom",
|
||||
"../romdumps/DISK2.rom",
|
||||
"rom file for the disk drive controller")
|
||||
disk2Slot := flag.Int(
|
||||
"disk2Slot",
|
||||
@ -21,15 +21,15 @@ func main() {
|
||||
"slot for the disk driver. -1 for none.")
|
||||
diskImage := flag.String(
|
||||
"disk",
|
||||
"../dos33.dsk",
|
||||
"../romdumps/dos33.dsk",
|
||||
"file to load on the first disk drive")
|
||||
cpuClock := flag.Float64(
|
||||
"mhz",
|
||||
apple2.CpuClockMhz,
|
||||
CpuClockMhz,
|
||||
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
|
||||
charRomFile := flag.String(
|
||||
"charRom",
|
||||
"apple2/romdumps/Apple2rev7CharGen.rom",
|
||||
"../romdumps/Apple2rev7CharGen.rom",
|
||||
"rom file for the disk drive controller")
|
||||
languageCardSlot := flag.Int(
|
||||
"languageCardSlot",
|
||||
@ -39,15 +39,6 @@ func main() {
|
||||
"saturnCardSlot",
|
||||
-1,
|
||||
"slot for the 256kb Saturn card. -1 for none")
|
||||
|
||||
useSdl := flag.Bool(
|
||||
"sdl",
|
||||
true,
|
||||
"use SDL")
|
||||
stdoutScreen := flag.Bool(
|
||||
"stdout",
|
||||
false,
|
||||
"show the text screen on the standard output")
|
||||
mono := flag.Bool(
|
||||
"mono",
|
||||
false,
|
||||
@ -71,13 +62,13 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *dumpChars {
|
||||
cg := apple2.NewCharacterGenerator(*charRomFile)
|
||||
cg := NewCharacterGenerator(*charRomFile)
|
||||
cg.Dump()
|
||||
return
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
log := false
|
||||
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
||||
a := NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
||||
if *languageCardSlot >= 0 {
|
||||
a.AddLanguageCard(*languageCardSlot)
|
||||
}
|
||||
@ -91,11 +82,13 @@ func main() {
|
||||
//a.AddCardInOut(2)
|
||||
//a.AddCardLogger(4)
|
||||
|
||||
if *useSdl {
|
||||
a.ConfigureStdConsole(false, *stdoutScreen)
|
||||
apple2sdl.SDLRun(a)
|
||||
} else {
|
||||
a.ConfigureStdConsole(true, true)
|
||||
a.Run(log)
|
||||
}
|
||||
return a
|
||||
/* if *useSDL {
|
||||
a.ConfigureStdConsole(false, *stdoutScreen)
|
||||
apple2sdl.SDLRun(a)
|
||||
} else {
|
||||
a.ConfigureStdConsole(true, true)
|
||||
a.Run(log)
|
||||
}
|
||||
*/
|
||||
}
|
BIN
apple2sdl/apple2sdl
Executable file
BIN
apple2sdl/apple2sdl
Executable file
Binary file not shown.
@ -1,16 +1,20 @@
|
||||
package apple2sdl
|
||||
package main
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ivanizag/apple2"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
"go6502/apple2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := apple2.MainApple()
|
||||
SDLRun(a)
|
||||
}
|
||||
|
||||
// SDLRun starts the Apple2 emulator on SDL
|
||||
func SDLRun(a *apple2.Apple2) {
|
||||
s := newSdlSpeaker()
|
||||
s := newSDLSpeaker()
|
||||
s.start()
|
||||
|
||||
window, renderer, err := sdl.CreateWindowAndRenderer(4*40*7, 4*24*8,
|
@ -1,9 +1,9 @@
|
||||
package apple2sdl
|
||||
package main
|
||||
|
||||
import (
|
||||
"go6502/apple2"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ivanizag/apple2"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package apple2sdl
|
||||
package main
|
||||
|
||||
/*
|
||||
typedef unsigned char Uint8;
|
||||
@ -7,10 +7,10 @@ void SpeakerCallback(void *userdata, Uint8 *stream, int len);
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"go6502/apple2"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ivanizag/apple2"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
@ -34,9 +34,9 @@ type sdlSpeaker struct {
|
||||
I have not found a way to encode the pointer to sdlSpeaker on the userdata of
|
||||
the call to SpeakerCallback(). I use a global as workaround...
|
||||
*/
|
||||
var theSdlSpeaker *sdlSpeaker
|
||||
var theSDLSpeaker *sdlSpeaker
|
||||
|
||||
func newSdlSpeaker() *sdlSpeaker {
|
||||
func newSDLSpeaker() *sdlSpeaker {
|
||||
var s sdlSpeaker
|
||||
s.clickChannel = make(chan uint64, bufferSize)
|
||||
s.pendingClicks = make([]uint64, 0, bufferSize)
|
||||
@ -57,7 +57,7 @@ func stateToLevel(state bool) C.Uint8 {
|
||||
|
||||
//export SpeakerCallback
|
||||
func SpeakerCallback(userdata unsafe.Pointer, stream *C.Uint8, length C.int) {
|
||||
s := theSdlSpeaker
|
||||
s := theSDLSpeaker
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
@ -152,7 +152,7 @@ func (s *sdlSpeaker) start() {
|
||||
return
|
||||
}
|
||||
sdl.PauseAudio(false)
|
||||
theSdlSpeaker = s
|
||||
theSDLSpeaker = s
|
||||
}
|
||||
|
||||
func (s *sdlSpeaker) close() {
|
||||
|
@ -15,7 +15,7 @@ be used in slot 0 anyway.
|
||||
Note also that language cards for the Apple ][ had ROM on
|
||||
board to replace the main board F8 ROM with Autostart. That
|
||||
was not used/needed on the Apple ][+. As this emulates the
|
||||
Apple ][+, it is not considered. For the PLus it is often
|
||||
Apple ][+, it is not considered. For the Plus it is often
|
||||
refered as Language card but it is really a 16 KB Ram card,
|
||||
|
||||
|
BIN
romdumps/dos33.dsk
Normal file
BIN
romdumps/dos33.dsk
Normal file
Binary file not shown.
@ -1,8 +1,10 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -73,3 +75,69 @@ func snapshotTextMode(a *Apple2, page int, mixMode bool, light color.Color) *ima
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes
|
||||
// for reverse and flash
|
||||
func DumpTextModeAnsi(a *Apple2) string {
|
||||
content := "\n"
|
||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
||||
|
||||
pageIndex := 0
|
||||
if a.io.isSoftSwitchActive(ioFlagSecondPage) {
|
||||
pageIndex = 1
|
||||
}
|
||||
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||
|
||||
for l := 0; l < textLines; l++ {
|
||||
line := ""
|
||||
for c := 0; c < textColumns; c++ {
|
||||
char := getTextChar(a, c, l, pageIndex)
|
||||
line += textMemoryByteToString(char, isAltText)
|
||||
}
|
||||
content += fmt.Sprintf("# %v #\n", line)
|
||||
}
|
||||
|
||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
||||
return content
|
||||
}
|
||||
|
||||
func textMemoryByteToString(value uint8, isAltCharSet bool) string {
|
||||
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
||||
// Supports the new lowercase characters in the Apple2e
|
||||
// Only ascii from 0x20 to 0x5F is visible
|
||||
topBits := value >> 6
|
||||
isInverse := topBits == 0
|
||||
isFlash := topBits == 1
|
||||
if isFlash && isAltCharSet {
|
||||
// On the Apple2e with lowercase chars there is not flash mode.
|
||||
isFlash = false
|
||||
isInverse = true
|
||||
}
|
||||
|
||||
if isAltCharSet {
|
||||
value = value & 0x7F
|
||||
} else {
|
||||
value = value & 0x3F
|
||||
}
|
||||
|
||||
if value < 0x20 {
|
||||
value += 0x40
|
||||
}
|
||||
|
||||
if value == 0x7f {
|
||||
// DEL is full box
|
||||
value = '_'
|
||||
}
|
||||
|
||||
if isFlash {
|
||||
if value == ' ' {
|
||||
// Flashing space in Apple is the full box. It can't be done with ANSI codes
|
||||
value = '_'
|
||||
}
|
||||
return fmt.Sprintf("\033[5m%v\033[0m", string(value))
|
||||
} else if isInverse {
|
||||
return fmt.Sprintf("\033[7m%v\033[0m", string(value))
|
||||
} else {
|
||||
return string(value)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user