diff --git a/apple2/apple2.go b/apple2.go similarity index 99% rename from apple2/apple2.go rename to apple2.go index edf7263..54f095f 100644 --- a/apple2/apple2.go +++ b/apple2.go @@ -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 diff --git a/apple2/ansiConsoleFrontend.go b/apple2/ansiConsoleFrontend.go deleted file mode 100644 index 0afedba..0000000 --- a/apple2/ansiConsoleFrontend.go +++ /dev/null @@ -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) -} diff --git a/apple2/romdumps/Apple II+ - Lowercase Character Generator - 2716.bin b/apple2/romdumps/Apple II+ - Lowercase Character Generator - 2716.bin deleted file mode 100644 index 0cc83d3..0000000 Binary files a/apple2/romdumps/Apple II+ - Lowercase Character Generator - 2716.bin and /dev/null differ diff --git a/apple2/romdumps/Apple II+ - Pig Font Character Generator - 2716.bin b/apple2/romdumps/Apple II+ - Pig Font Character Generator - 2716.bin deleted file mode 100644 index fb199b7..0000000 Binary files a/apple2/romdumps/Apple II+ - Pig Font Character Generator - 2716.bin and /dev/null differ diff --git a/apple2/romdumps/Apple2.rom b/apple2/romdumps/Apple2.rom deleted file mode 100644 index e53fa17..0000000 Binary files a/apple2/romdumps/Apple2.rom and /dev/null differ diff --git a/apple2/romdumps/Apple2e.rom b/apple2/romdumps/Apple2e.rom deleted file mode 100644 index 916b318..0000000 Binary files a/apple2/romdumps/Apple2e.rom and /dev/null differ diff --git a/apple2/romdumps/Apple2e_Enhanced.rom b/apple2/romdumps/Apple2e_Enhanced.rom deleted file mode 100644 index 65bb648..0000000 Binary files a/apple2/romdumps/Apple2e_Enhanced.rom and /dev/null differ diff --git a/apple2/romdumps/LanguageCard_3410020F8.bin b/apple2/romdumps/LanguageCard_3410020F8.bin deleted file mode 100644 index e6a4515..0000000 Binary files a/apple2/romdumps/LanguageCard_3410020F8.bin and /dev/null differ diff --git a/apple2/apple2Setup.go b/apple2Setup.go similarity index 82% rename from apple2/apple2Setup.go rename to apple2Setup.go index 4ab0bf7..928203f 100644 --- a/apple2/apple2Setup.go +++ b/apple2Setup.go @@ -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) diff --git a/apple2console/apple2console b/apple2console/apple2console new file mode 100755 index 0000000..33ac3fc Binary files /dev/null and b/apple2console/apple2console differ diff --git a/apple2console/main.go b/apple2console/main.go new file mode 100644 index 0000000..6f05417 --- /dev/null +++ b/apple2console/main.go @@ -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) + } +} diff --git a/main.go b/apple2main.go similarity index 68% rename from main.go rename to apple2main.go index 73c8d49..7be9c39 100644 --- a/main.go +++ b/apple2main.go @@ -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) + } + */ } diff --git a/apple2sdl/apple2sdl b/apple2sdl/apple2sdl new file mode 100755 index 0000000..4eaa0d3 Binary files /dev/null and b/apple2sdl/apple2sdl differ diff --git a/apple2sdl/run.go b/apple2sdl/main.go similarity index 93% rename from apple2sdl/run.go rename to apple2sdl/main.go index 0598a31..55f931a 100644 --- a/apple2sdl/run.go +++ b/apple2sdl/main.go @@ -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, diff --git a/apple2sdl/sdlKeyboard.go b/apple2sdl/sdlKeyboard.go index 705c32a..56e80d3 100644 --- a/apple2sdl/sdlKeyboard.go +++ b/apple2sdl/sdlKeyboard.go @@ -1,9 +1,9 @@ -package apple2sdl +package main import ( - "go6502/apple2" "unicode/utf8" + "github.com/ivanizag/apple2" "github.com/veandco/go-sdl2/sdl" ) diff --git a/apple2sdl/sdlSpeaker.go b/apple2sdl/sdlSpeaker.go index 392a6c7..df131cc 100644 --- a/apple2sdl/sdlSpeaker.go +++ b/apple2sdl/sdlSpeaker.go @@ -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() { diff --git a/apple2/cardBase.go b/cardBase.go similarity index 100% rename from apple2/cardBase.go rename to cardBase.go diff --git a/apple2/cardDisk2.go b/cardDisk2.go similarity index 100% rename from apple2/cardDisk2.go rename to cardDisk2.go diff --git a/apple2/cardInOut.go b/cardInOut.go similarity index 100% rename from apple2/cardInOut.go rename to cardInOut.go diff --git a/apple2/cardLanguage.go b/cardLanguage.go similarity index 98% rename from apple2/cardLanguage.go rename to cardLanguage.go index 97d237a..47c342c 100644 --- a/apple2/cardLanguage.go +++ b/cardLanguage.go @@ -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, diff --git a/apple2/cardLogger.go b/cardLogger.go similarity index 100% rename from apple2/cardLogger.go rename to cardLogger.go diff --git a/apple2/cardSaturn.go b/cardSaturn.go similarity index 100% rename from apple2/cardSaturn.go rename to cardSaturn.go diff --git a/apple2/characterGenerator.go b/characterGenerator.go similarity index 100% rename from apple2/characterGenerator.go rename to characterGenerator.go diff --git a/apple2/diskette16sector.go b/diskette16sector.go similarity index 100% rename from apple2/diskette16sector.go rename to diskette16sector.go diff --git a/apple2/ioC0Page.go b/ioC0Page.go similarity index 100% rename from apple2/ioC0Page.go rename to ioC0Page.go diff --git a/apple2/memoryManager.go b/memoryManager.go similarity index 100% rename from apple2/memoryManager.go rename to memoryManager.go diff --git a/apple2/memoryRange.go b/memoryRange.go similarity index 100% rename from apple2/memoryRange.go rename to memoryRange.go diff --git a/apple2/romdumps/Apple2_Plus.rom b/romdumps/Apple2_Plus.rom similarity index 100% rename from apple2/romdumps/Apple2_Plus.rom rename to romdumps/Apple2_Plus.rom diff --git a/apple2/romdumps/Apple2rev7CharGen.rom b/romdumps/Apple2rev7CharGen.rom similarity index 100% rename from apple2/romdumps/Apple2rev7CharGen.rom rename to romdumps/Apple2rev7CharGen.rom diff --git a/apple2/romdumps/DISK2.rom b/romdumps/DISK2.rom similarity index 100% rename from apple2/romdumps/DISK2.rom rename to romdumps/DISK2.rom diff --git a/romdumps/dos33.dsk b/romdumps/dos33.dsk new file mode 100644 index 0000000..ef257aa Binary files /dev/null and b/romdumps/dos33.dsk differ diff --git a/apple2/screen.go b/screen.go similarity index 100% rename from apple2/screen.go rename to screen.go diff --git a/apple2/screenHiRes.go b/screenHiRes.go similarity index 100% rename from apple2/screenHiRes.go rename to screenHiRes.go diff --git a/apple2/screenHiRes_test.go b/screenHiRes_test.go similarity index 100% rename from apple2/screenHiRes_test.go rename to screenHiRes_test.go diff --git a/apple2/screenLoRes.go b/screenLoRes.go similarity index 100% rename from apple2/screenLoRes.go rename to screenLoRes.go diff --git a/apple2/screenNtscFilter.go b/screenNtscFilter.go similarity index 100% rename from apple2/screenNtscFilter.go rename to screenNtscFilter.go diff --git a/apple2/screenText.go b/screenText.go similarity index 51% rename from apple2/screenText.go rename to screenText.go index 80567bf..14a7f17 100644 --- a/apple2/screenText.go +++ b/screenText.go @@ -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) + } +} diff --git a/apple2/ansiConsoleFrontend_test.go b/screenText_test.go similarity index 100% rename from apple2/ansiConsoleFrontend_test.go rename to screenText_test.go diff --git a/apple2/softSwitches2.go b/softSwitches2.go similarity index 100% rename from apple2/softSwitches2.go rename to softSwitches2.go diff --git a/apple2/softSwitches2e.go b/softSwitches2e.go similarity index 100% rename from apple2/softSwitches2e.go rename to softSwitches2e.go