// Simplest possible Apple II that will possibly boot, in SDL. ~ (tilde) to quit. package main import ( "flag" "fmt" "log" "os" "runtime" "runtime/pprof" "unsafe" "github.com/0xe2-0x9a-0x9b/Go-SDL/sdl" "github.com/zellyn/go6502/cpu" "github.com/zellyn/goapple2" "github.com/zellyn/goapple2/cards" "github.com/zellyn/goapple2/disk" "github.com/zellyn/goapple2/util" "github.com/zellyn/goapple2/videoscan" ) var ( cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") steplimit = flag.Uint64("steplimit", 0, "limit on number of steps to take") ) const ( BORDER_H = 20 BORDER_W = 20 SCREEN_WIDTH = 560 + 2*BORDER_W SCREEN_HEIGHT = 384 + 2*BORDER_H SCREEN_BPP = 32 ) func plot(x, y uint, color uint32, screen *sdl.Surface) { x = x + BORDER_W y = y + BORDER_H pixels := uintptr(screen.Pixels) offset := uintptr(y*uint(screen.Pitch) + x*(SCREEN_BPP/8)) addr := pixels + offset *(*uint32)(unsafe.Pointer(addr)) = color } func Init() (screen *sdl.Surface, err error) { // Intialise the SDL subsystems if ok := sdl.Init(sdl.INIT_EVERYTHING); ok != 0 { return nil, fmt.Errorf("%s", sdl.GetError()) } // Set up the screen screen = sdl.SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, sdl.SWSURFACE) // If couldn't get the screen, return false if screen == nil { return nil, fmt.Errorf("%s", sdl.GetError()) } // Set the window caption (window title) sdl.WM_SetCaption("Apple ][", "") // Everything went fine return screen, nil } const ( M_NONE = iota M_SHIFT_OR_NONE M_SHIFT M_CTRL M_ANY ) type Key struct { sym uint32 mod uint32 } var KeyToApple = map[Key]byte{ Key{sdl.K_a, M_SHIFT_OR_NONE}: 'A', Key{sdl.K_b, M_SHIFT_OR_NONE}: 'B', Key{sdl.K_c, M_SHIFT_OR_NONE}: 'C', Key{sdl.K_d, M_SHIFT_OR_NONE}: 'D', Key{sdl.K_e, M_SHIFT_OR_NONE}: 'E', Key{sdl.K_f, M_SHIFT_OR_NONE}: 'F', Key{sdl.K_g, M_SHIFT_OR_NONE}: 'G', Key{sdl.K_h, M_SHIFT_OR_NONE}: 'H', Key{sdl.K_i, M_SHIFT_OR_NONE}: 'I', Key{sdl.K_j, M_SHIFT_OR_NONE}: 'J', Key{sdl.K_k, M_SHIFT_OR_NONE}: 'K', Key{sdl.K_l, M_SHIFT_OR_NONE}: 'L', Key{sdl.K_m, M_SHIFT_OR_NONE}: 'M', Key{sdl.K_n, M_SHIFT_OR_NONE}: 'N', Key{sdl.K_o, M_SHIFT_OR_NONE}: 'O', Key{sdl.K_p, M_SHIFT_OR_NONE}: 'P', Key{sdl.K_q, M_SHIFT_OR_NONE}: 'Q', Key{sdl.K_r, M_SHIFT_OR_NONE}: 'R', Key{sdl.K_s, M_SHIFT_OR_NONE}: 'S', Key{sdl.K_t, M_SHIFT_OR_NONE}: 'T', Key{sdl.K_u, M_SHIFT_OR_NONE}: 'U', Key{sdl.K_v, M_SHIFT_OR_NONE}: 'V', Key{sdl.K_w, M_SHIFT_OR_NONE}: 'W', Key{sdl.K_x, M_SHIFT_OR_NONE}: 'X', Key{sdl.K_y, M_SHIFT_OR_NONE}: 'Y', Key{sdl.K_z, M_SHIFT_OR_NONE}: 'Z', Key{sdl.K_a, M_CTRL}: 1, Key{sdl.K_b, M_CTRL}: 2, Key{sdl.K_c, M_CTRL}: 3, Key{sdl.K_d, M_CTRL}: 4, Key{sdl.K_e, M_CTRL}: 5, Key{sdl.K_f, M_CTRL}: 6, Key{sdl.K_g, M_CTRL}: 7, Key{sdl.K_h, M_CTRL}: 8, Key{sdl.K_i, M_CTRL}: 9, Key{sdl.K_j, M_CTRL}: 10, Key{sdl.K_k, M_CTRL}: 11, Key{sdl.K_l, M_CTRL}: 12, Key{sdl.K_m, M_CTRL}: 13, Key{sdl.K_n, M_CTRL}: 14, Key{sdl.K_o, M_CTRL}: 15, Key{sdl.K_p, M_CTRL}: 16, Key{sdl.K_q, M_CTRL}: 17, Key{sdl.K_r, M_CTRL}: 18, Key{sdl.K_s, M_CTRL}: 19, Key{sdl.K_t, M_CTRL}: 20, Key{sdl.K_u, M_CTRL}: 21, Key{sdl.K_v, M_CTRL}: 22, Key{sdl.K_w, M_CTRL}: 23, Key{sdl.K_x, M_CTRL}: 24, Key{sdl.K_y, M_CTRL}: 25, Key{sdl.K_z, M_CTRL}: 26, Key{sdl.K_0, M_NONE}: '0', Key{sdl.K_1, M_NONE}: '1', Key{sdl.K_2, M_NONE}: '2', Key{sdl.K_3, M_NONE}: '3', Key{sdl.K_4, M_NONE}: '4', Key{sdl.K_5, M_NONE}: '5', Key{sdl.K_6, M_NONE}: '6', Key{sdl.K_7, M_NONE}: '7', Key{sdl.K_8, M_NONE}: '8', Key{sdl.K_9, M_NONE}: '9', Key{sdl.K_1, M_SHIFT}: '!', Key{sdl.K_2, M_SHIFT}: '@', Key{sdl.K_3, M_SHIFT}: '#', Key{sdl.K_4, M_SHIFT}: '$', Key{sdl.K_5, M_SHIFT}: '%', Key{sdl.K_6, M_SHIFT}: '^', Key{sdl.K_7, M_SHIFT}: '&', Key{sdl.K_8, M_SHIFT}: '*', Key{sdl.K_9, M_SHIFT}: '(', Key{sdl.K_0, M_SHIFT}: ')', Key{sdl.K_MINUS, M_NONE}: '-', Key{sdl.K_MINUS, M_SHIFT}: '_', Key{sdl.K_EQUALS, M_NONE}: '=', Key{sdl.K_EQUALS, M_SHIFT}: '+', Key{sdl.K_LEFTBRACKET, M_NONE}: '[', Key{sdl.K_RIGHTBRACKET, M_NONE}: ']', Key{sdl.K_SEMICOLON, M_NONE}: ';', Key{sdl.K_SEMICOLON, M_SHIFT}: ':', Key{sdl.K_QUOTE, M_NONE}: '\'', Key{sdl.K_QUOTE, M_SHIFT}: '"', Key{sdl.K_COMMA, M_NONE}: ',', Key{sdl.K_COMMA, M_SHIFT}: '<', Key{sdl.K_PERIOD, M_NONE}: '.', Key{sdl.K_PERIOD, M_SHIFT}: '>', Key{sdl.K_SLASH, M_NONE}: '/', Key{sdl.K_SLASH, M_SHIFT}: '?', Key{sdl.K_BACKSLASH, M_NONE}: '\\', Key{sdl.K_SPACE, M_SHIFT_OR_NONE}: ' ', Key{sdl.K_RETURN, M_ANY}: 13, Key{sdl.K_BACKSPACE, M_ANY}: 8, Key{sdl.K_LEFT, M_ANY}: 8, Key{sdl.K_RIGHT, M_ANY}: 21, } func sdlToAppleKeyboard(k sdl.Keysym) (key byte, err error) { if b, ok := KeyToApple[Key{k.Sym, M_ANY}]; ok { return b, nil } switch k.Mod { case sdl.KMOD_NONE: if b, ok := KeyToApple[Key{k.Sym, M_NONE}]; ok { return b, nil } if b, ok := KeyToApple[Key{k.Sym, M_SHIFT_OR_NONE}]; ok { return b, nil } case sdl.KMOD_LSHIFT, sdl.KMOD_RSHIFT, sdl.KMOD_LSHIFT | sdl.KMOD_RSHIFT: if b, ok := KeyToApple[Key{k.Sym, M_SHIFT}]; ok { return b, nil } if b, ok := KeyToApple[Key{k.Sym, M_SHIFT_OR_NONE}]; ok { return b, nil } case sdl.KMOD_LCTRL, sdl.KMOD_RCTRL, sdl.KMOD_LCTRL | sdl.KMOD_RCTRL: if b, ok := KeyToApple[Key{k.Sym, M_CTRL}]; ok { return b, nil } } return 0, fmt.Errorf("hi") } func ProcessEvents(events <-chan interface{}, a2 *goapple2.Apple2) (done bool) { select { case _event := <-events: switch e := _event.(type) { case sdl.QuitEvent: return true case sdl.KeyboardEvent: if e.Type == sdl.KEYDOWN { if e.Keysym.Sym == sdl.K_F1 { return true } if e.Keysym.Mod == sdl.KMOD_LCTRL && e.Keysym.Sym == sdl.K_LEFTBRACKET { return true } if key, err := sdlToAppleKeyboard(e.Keysym); err == nil { a2.Keypress(key) } } } default: // Nothing to do here } return false } type SdlPlotter struct { screen *sdl.Surface oncePerFrame func() } func (s SdlPlotter) Plot(pd videoscan.PlotData) { y := uint(pd.Row) x := uint(pd.Column) * 14 data := pd.Data for i := uint(0); i < 14; i++ { color1 := uint32(0) color2 := uint32(0) if data&1 > 0 { color1 = 0x00ff00 color2 = 0x008800 } plot(x+i, y*2+0, color1, s.screen) plot(x+i, y*2+1, color2, s.screen) data >>= 1 } } func (s SdlPlotter) OncePerFrame() { s.screen.Unlock() s.screen.Flip() s.screen.Lock() s.oncePerFrame() } func typeProgram(a2 *goapple2.Apple2) { return lines := []string{ "10 GR", "20 POKE -16302,0", "30 FOR Y=0 TO 47", "40 FOR X=0 TO 39", "50 COLOR=INT(RND(1)*16)", "60 PLOT X,Y", "70 NEXT", "80 NEXT", "RUN", } lines = []string{ "10 HGR2", "20 FOR I = 0 to 7", "30 HCOLOR=7-I", "40 HPLOT I*10, 0 TO 191 + I*10, 191", "50 NEXT", "RUN", } for _, line := range lines { for _, ch := range line { a2.Keypress(byte(ch)) } a2.Keypress(13) } } // Run the emulator func RunEmulator() { rom := util.ReadRomOrDie("../data/roms/apple2+.rom") // charRom = util.ReadFullCharacterRomOrDie("../data/roms/apple2char.rom") charRom := util.ReadSmallCharacterRomOrDie("../data/roms/apple2-chars.rom") screen, err := Init() if err != nil { panic(err) } var a2 *goapple2.Apple2 oncePerFrame := func() { a2.Done = a2.Done || ProcessEvents(sdl.Events, a2) runtime.Gosched() } plotter := SdlPlotter{screen, oncePerFrame} a2 = goapple2.NewApple2(plotter, rom, charRom) intBasicRom := util.ReadRomOrDie("../data/roms/apple2.rom") firmwareCard, err := cards.NewFirmwareCard(intBasicRom, "Intbasic Firmware Card", 0, a2) if err != nil { panic(err) } if err := a2.AddCard(firmwareCard); err != nil { log.Fatal(err) } diskCardRom := util.ReadRomOrDie("../data/roms/Apple Disk II 16 Sector Interface Card ROM P5 - 341-0027.bin") diskCard, err := cards.NewDiskCard(diskCardRom, 6, a2) if err != nil { panic(err) } if err := a2.AddCard(diskCard); err != nil { log.Fatal(err) } // disk1, err := disk.DiskFromFile("../data/disks/spedtest.dsk", 0) // disk1, err := disk.DiskFromFile("../data/disks/dung_beetles.dsk", 0) disk1, err := disk.DiskFromFile("../data/disks/chivalry.dsk", 0) if err != nil { log.Fatal(err) } diskCard.LoadDisk(disk1, 0) steps := *steplimit if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } /* a2.AddPCAction( 0xB940, goapple2.PCAction{Type: goapple2.ActionDumpMem, String: "0xB940-goa2.bin", Mask: cpu.FLAG_Z, Masked: cpu.FLAG_Z, Delay: 68}) a2.AddPCAction(0xB7B5, goapple2.PCAction{Type: goapple2.ActionHere, String: "ENTER.RWTS"}) a2.AddPCAction(0xB7BE, goapple2.PCAction{Type: goapple2.ActionHere, String: "ENTER.RWTS - Success"}) a2.AddPCAction(0xB7C1, goapple2.PCAction{Type: goapple2.ActionHere, String: "ENTER.RWTS - Fail"}) a2.AddPCAction(0xBD00, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS"}) a2.AddPCAction(0xBDAF, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS Command"}) // a2.AddPCAction(0xBE35, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS READ.SECTOR call"}) // a2.AddPCAction(0xBE38, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS READ.SECTOR success"}) a2.AddPCAction(0xBE46, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS Success"}) a2.AddPCAction(0xBE48, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS Error"}) a2.AddPCAction(0xBDAF, goapple2.PCAction{Type: goapple2.ActionDiskStatus}) a2.AddPCAction(0xBDAF, goapple2.PCAction{Type: goapple2.ActionTrace, String: "on", Delay: 70}) a2.AddPCAction( 0xBE48, goapple2.PCAction{Type: goapple2.ActionSetLimit, String: "1"}) a2.AddPCAction( 0xBDAF, goapple2.PCAction{Type: goapple2.ActionDumpMem, String: "0xBDAF-goa2.bin", Delay: 68}) */ _ = cpu.FLAG_Z // go typeProgram(a2) for !a2.Done { err := a2.Step() if err != nil { fmt.Println(err) break } // runtime.Gosched() // So the keyboard-reading goroutines can run if steps > 0 { steps-- if steps == 0 { a2.Quit() } } } a2.Quit() sdl.Quit() } func main() { flag.Parse() RunEmulator() }