diff --git a/README.md b/README.md index ebbbbe4..737dab9 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Portable emulator of an Apple II+ or //e. Written in Go. - ThunderClock Plus real time clock - Apple //e 80 columns with 64Kb extra RAM and optional RGB modes - No Slot Clock based on the DS1216 + - Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only) - Useful cards not emulating a real card - Bootable Smartport / ProDOS card - VidHd, limited to the ROM signature and SHR as used by Total Replay, only for //e models with 128Kb @@ -35,7 +36,7 @@ Portable emulator of an Apple II+ or //e. Written in Go. - Graphic modes: - Text 40 columns - - Text 80 columns (Apple //e only) + - Text 80 columns (Apple //e and Videx VideoTerm) - Low-Resolution graphics - Double-Width Low-Resolution graphics (Apple //e only) - High-Resolution graphics @@ -231,6 +232,8 @@ Only valid on SDL mode dump to the console the sofswitch registrations -vidHDSlot int slot for the VidHD card, only for //e models. -1 for none (default 2) + -videxCardSlot int + slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none (default 3) ``` diff --git a/apple2.go b/apple2.go index cb2946b..1b296e4 100644 --- a/apple2.go +++ b/apple2.go @@ -15,6 +15,7 @@ type Apple2 struct { io *ioC0Page cg *CharacterGenerator cards [8]Card + softVideoSwitch *SoftVideoSwitch isApple2e bool commandChannel chan int cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz @@ -53,7 +54,7 @@ func (a *Apple2) Run() { for i := 0; i < cpuSpinLoops; i++ { // Conditional tracing //pc, _ := a.cpu.GetPCAndSP() - //a.cpu.SetTrace(pc >= 0x300 && pc <= 0x400) + //a.cpu.SetTrace((pc >= 0xc300 && pc <= 0xc400) || (pc >= 0xc800 && pc <= 0xce00)) // Execution a.cpu.ExecuteInstruction() diff --git a/apple2Setup.go b/apple2Setup.go index 3bb832b..5540ee6 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -158,6 +158,13 @@ func (a *Apple2) AddMouseCard(slot int) { a.insertCard(NewCardMouse(), slot) } +// AddVidexCard inserts a Videx card +func (a *Apple2) AddVidexCard(slot int) { + c := NewCardVidex() + a.insertCard(c, slot) + a.softVideoSwitch = NewSoftVideoSwitch(c) +} + // AddRGBCard inserts an RBG option to the Apple IIe 80 col 64KB card func (a *Apple2) AddRGBCard() { setupRGBCard(a) diff --git a/apple2main.go b/apple2main.go index f4c0e2a..f988f1d 100644 --- a/apple2main.go +++ b/apple2main.go @@ -82,6 +82,10 @@ func MainApple() *Apple2 { "mouseCardSlot", 4, "slot for the Mouse card. -1 for none") + videxCardSlot := flag.Int( + "videxCardSlot", + 3, + "slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none") nsc := flag.Int( "nsc", -1, @@ -204,11 +208,17 @@ func MainApple() *Apple2 { } charGenMap = charGenColumnsMapBase64a *vidHDCardSlot = -1 + *videxCardSlot = -1 // The videx firmware crashes the BASE64A, probably by use of ANN0 default: panic("Model not supported") } + if a.isApple2e { + // Videx not used on Apple IIe + *videxCardSlot = -1 + } + a.cpu.SetTrace(*traceCPU) // Load ROM if not loaded already @@ -249,6 +259,9 @@ func MainApple() *Apple2 { if *mouseCardSlot > 0 { a.AddMouseCard(*mouseCardSlot) } + if *videxCardSlot > 0 { + a.AddVidexCard(*videxCardSlot) + } if *smartPortImage != "" { err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD) diff --git a/cardBase.go b/cardBase.go index 4dd6842..79b461b 100644 --- a/cardBase.go +++ b/cardBase.go @@ -1,6 +1,8 @@ package izapple2 -import "github.com/ivanizag/izapple2/storage" +import ( + "github.com/ivanizag/izapple2/storage" +) // Card represents an Apple II card to be inserted in a slot type Card interface { @@ -15,9 +17,9 @@ type Card interface { type cardBase struct { a *Apple2 name string - romCsxx *memoryRangeROM - romC8xx *memoryRangeROM - romCxxx *memoryRangeROM + romCsxx memoryHandler + romC8xx memoryHandler + romCxxx memoryHandler slot int _ssr [16]softSwitchR @@ -54,8 +56,14 @@ func (c *cardBase) loadRom(data []uint8) { if len(data) == 0x100 { // Just 256 bytes in Cs00 c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") + } else if len(data) == 0x400 { + // The file has C800 to CBFF for ROM + // The 256 bytes in Cx00 are copied from the last page in C800-CBFF + // Used on the Videx 80 columns card + c.romCsxx = newMemoryRangeROM(0, data[0x300:], "Slot ROM") + c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") } else if len(data) == 0x800 { - // The file has C800 to C8FF + // The file has C800 to CFFF // The 256 bytes in Cx00 are copied from the first page in C800 c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM") c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM") diff --git a/cardFastChip.go b/cardFastChip.go index 76d4ea3..cf5b5dd 100644 --- a/cardFastChip.go +++ b/cardFastChip.go @@ -6,6 +6,10 @@ from controlled speed to max speed the emulator can do. Note: It ends up not being useful for Total Replay as loading from HD is already very fast. HD blocks are loaded directly on the emulated RAM. +Note that it doesn't intefere with the Apple IIe 80 columns in slot 3. It doesn't +have ROM or slot specific sofswitches. + + See: https://github.com/a2-4am/4cade/blob/master/src/hw.accel.a http://www.a2heaven.com/webshop/resources/pdf_document/18/82/c.pdf @@ -26,15 +30,9 @@ type CardFastChip struct { func NewCardFastChip() *CardFastChip { var c CardFastChip c.name = "FASTChip IIe Card - limited" - c.loadRom(buildFastChipRom()) return &c } -func buildFastChipRom() []uint8 { - data := make([]uint8, 256) - return data -} - const ( fastChipUnlockToken = 0x6a fastChipUnlockRepeats = 4 diff --git a/cardLogger.go b/cardLogger.go index 7526112..a01ca6a 100644 --- a/cardLogger.go +++ b/cardLogger.go @@ -28,7 +28,7 @@ func (c *CardLogger) assign(a *Apple2, slot int) { return 0 }, "LOGGERR") c.addCardSoftSwitchW(i, func(_ *ioC0Page, value uint8) { - fmt.Printf("[cardLogger] Write access to softswith 0x%x for slot %v, value 0x%v.\n", iCopy, slot, value) + fmt.Printf("[cardLogger] Write access to softswith 0x%x for slot %v, value 0x%02x.\n", iCopy, slot, value) }, "LOGGERW") } diff --git a/cardVidex.go b/cardVidex.go new file mode 100644 index 0000000..cfeaf95 --- /dev/null +++ b/cardVidex.go @@ -0,0 +1,163 @@ +package izapple2 + +import ( + "errors" + "fmt" + "image" + "image/color" + + "github.com/ivanizag/izapple2/component" + "github.com/ivanizag/izapple2/storage" +) + +/* + Videx 80 columns card for the Apple II+ + +See: + https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/80%20Column%20Cards/Videx%20Videoterm/Manuals/Videx%20Videoterm%20-%20Installation%20and%20Operation%20Manual.pdf + http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf + https://glasstty.com/?p=660 + +*/ + +// CardVidex represents a Videx compatible 80 column card +type CardVidex struct { + cardBase + mc6845 component.MC6845 + sramPage uint8 + sram [0x800]uint8 + upperROM memoryHandler + charGen []uint8 +} + +// NewCardVidex creates a new CardVidex +func NewCardVidex() *CardVidex { + var c CardVidex + c.name = "Videx 80 col Card" + + // The C800 area has ROM and RAM + c.loadRomFromResource("/Videx Videoterm ROM 2.4.bin") + c.upperROM = c.romC8xx + c.romC8xx = &c + + // The resource should be internal and never fail + c.loadCharacterMap("/80ColumnP110.BIN") + return &c +} + +func (c *CardVidex) loadCharacterMap(filename string) error { + bytes, _, err := storage.LoadResource(filename) + if err != nil { + return err + } + size := len(bytes) + if size < 0x800 { + return errors.New("Character ROM size not supported for Videx") + } + c.charGen = bytes + return nil +} + +func (c *CardVidex) assign(a *Apple2, slot int) { + for i := uint8(0x0); i <= 0xf; i++ { + // Bit 0 goes to the RS pin of the MC6548. It controls + // whether a register is being accesed or the contents + // of the register is being accessed + rsPin := (i & 1) == 1 + + // Bits 2 and 3 determine which page will be selected + sramPage := i >> 2 + + ssName := fmt.Sprintf("VIDEXPAGE%v", sramPage) + if rsPin { + ssName += "REG" + } else { + ssName += "ADDRESS" + } + + c.addCardSoftSwitchR(i, func(*ioC0Page) uint8 { + c.sramPage = sramPage + return c.mc6845.Read(rsPin) + }, ssName+"R") + c.addCardSoftSwitchW(i, func(_ *ioC0Page, value uint8) { + c.sramPage = sramPage + c.mc6845.Write(rsPin, value) + }, ssName+"W") + } + + c.cardBase.assign(a, slot) +} + +const videxRomLimit = uint16(0xcc00) +const videxSramLimit = uint16(0xce00) +const videxSramMask = uint16(0x01ff) + +func (c *CardVidex) peek(address uint16) uint8 { + if address < videxRomLimit { + return c.upperROM.peek(address) + } else if address < videxSramLimit { + return c.sram[address&videxSramMask+uint16(c.sramPage)*0x200] + } + return 0 +} + +func (c *CardVidex) poke(address uint16, value uint8) { + if address >= videxRomLimit && address < videxSramLimit { + c.sram[address&videxSramMask+uint16(c.sramPage)*0x200] = value + } +} + +func (c *CardVidex) setBase(base uint16) { + // Nothing +} + +const ( + videxCharWidth = uint8(8) +) + +func (c *CardVidex) buildImage(light color.Color) *image.RGBA { + params := c.mc6845.ImageData() + width, height := params.DisplayedWidthHeight(videxCharWidth) + if (width == 0) || (height == 0) { + // No image available + size := image.Rect(0, 0, 3, 3) + img := image.NewRGBA(size) + img.Set(1, 1, color.White) + return img + } + + size := image.Rect(0, 0, width, height) + img := image.NewRGBA(size) + + params.IterateScreen(func(address uint16, charLine uint8, + cursor bool, displayEnable bool, + column uint8, y int) { + + bits := uint8(0) + if displayEnable { + char := c.sram[address&0x7ff] + bits = c.charGen[(uint16(char&0x7f)<<4)+uint16(charLine)] + if cursor { + bits = ^bits + } + if char >= 128 { + bits = ^bits + } + } + + x := int(column) * int(videxCharWidth) + + for i := 0; i < int(videxCharWidth); i++ { + pixel := (bits & 0x80) != 0 + if pixel { + img.Set(x, y, light) + } else { + img.Set(x, y, color.Black) + } + bits <<= 1 + x++ + } + }) + + return img +} diff --git a/component/mc6845.go b/component/mc6845.go new file mode 100644 index 0000000..643e0f9 --- /dev/null +++ b/component/mc6845.go @@ -0,0 +1,105 @@ +package component + +/* + MC6845 CRT Controller + See: + Motorola MC6845 datasheet + + Pins: + RW, RS, D0-D7: Read() and Write() + MA0-13, RA04, CURSOR, DE: MC6845RasterCallBack() +*/ + +type MC6845 struct { + reg [18]uint8 // Internal registers R0 to R17 + sel uint8 // Selected address register AR +} + +func (m *MC6845) Read(rs bool) uint8 { + if !rs { + // AR is not readable + return 0x00 + } else if m.sel >= 14 && m.sel <= 17 { + // Only R14 to R17 are readable + // Should we mask R14 and R16? + return m.reg[m.sel] + } + return 0x00 +} + +func (m *MC6845) Write(rs bool, value uint8) { + if !rs { + // AR is 5 bits + // What happens if AR > 17 ? + m.sel = value & 0x1f + } else if m.sel <= 15 { + // R0 to R15 are writable + m.reg[m.sel] = value + //fmt.Printf("Set %v to %v\n", m.sel, value) + } +} + +func (m *MC6845) ImageData() MC6845ImageData { + var data MC6845ImageData + + data.firstChar = uint16(m.reg[12]&0x3f)<<8 + uint16(m.reg[13]) + data.charLines = (m.reg[9] + 1) & 0x1f + data.columns = m.reg[1] + data.lines = m.reg[6] & 0x7f + data.adjustLines = m.reg[5] & 0x1f + + data.cursorPos = uint16(m.reg[14]&0x3f)<<8 + uint16(m.reg[15]) + data.cursorStart = m.reg[10] & 0x1f + data.cursorEnd = m.reg[11] & 0x1f + // cursor mode is on bits 6 and 5 of R10 + return data +} + +type MC6845ImageData struct { + firstChar uint16 // 14 bits, address of the firt char on the first line + charLines uint8 // 5 bits, lines par character + columns uint8 // 8 bits, chars per line + lines uint8 // 7 bits, char lines per screen + adjustLines uint8 // 5 bits, extra blank lines + + cursorPos uint16 // 14 bits, address? of the cursor position + cursorStart uint8 // 5 bits, cursor starting char row + cursorEnd uint8 // 5 bits, cursos ending char row + // cursor mode + +} + +func (data *MC6845ImageData) DisplayedWidthHeight(charWidth uint8) (int, int) { + return int(data.columns) * int(charWidth), + int(data.lines)*int(data.charLines) + int(data.adjustLines) +} + +type MC6845RasterCallBack func(address uint16, charLine uint8, // Lookup in char ROM + cursor bool, displayEnable bool, // Modifiers + column uint8, y int) // Position in screen + +func (data *MC6845ImageData) IterateScreen(callBack MC6845RasterCallBack) { + lineAddress := data.firstChar + y := 0 + var address uint16 + for line := uint8(0); line < data.lines; line++ { + for charLine := uint8(0); charLine < data.charLines; charLine++ { + address = lineAddress // Back to the first char of the line + for column := uint8(0); column < data.columns; column++ { + isCursor := (address == data.cursorPos) && + (charLine >= data.cursorStart) && + (charLine <= data.cursorEnd) + callBack(address, charLine, isCursor, true, column, y) + address = (address + 1) & 0x3fff // 14 bits + } + y++ + } + lineAddress = address + } + for adjust := uint8(0); adjust <= data.adjustLines; adjust++ { + for column := uint8(0); column < data.columns; column++ { + callBack(0, 0, false, false, column, y) // lines with display not enabled + } + y++ + } +} diff --git a/ioC0Page.go b/ioC0Page.go index 2301acd..8b45ab3 100644 --- a/ioC0Page.go +++ b/ioC0Page.go @@ -149,6 +149,10 @@ func (p *ioC0Page) poke(address uint16, value uint8) { ss(p, value) } +func (p *ioC0Page) setBase(_ uint16) { + // Ignore +} + func ssFromBool(value bool) uint8 { if value { return ssOn diff --git a/memoryManager.go b/memoryManager.go index 43c6ecf..67daea9 100644 --- a/memoryManager.go +++ b/memoryManager.go @@ -70,6 +70,7 @@ const ( type memoryHandler interface { peek(uint16) uint8 poke(uint16, uint8) + setBase(uint16) } func newMemoryManager(a *Apple2) *memoryManager { diff --git a/memoryRange.go b/memoryRange.go index 36f9051..5fc27e4 100644 --- a/memoryRange.go +++ b/memoryRange.go @@ -5,11 +5,9 @@ import ( ) type memoryRange struct { - base uint16 - data []uint8 - name string - address string - //basePtr uintptr + base uint16 + data []uint8 + name string } type memoryRangeROM struct { @@ -23,7 +21,6 @@ func newMemoryRange(base uint16, data []uint8, name string) *memoryRange { m.setBase(base) m.name = name - m.address = fmt.Sprintf("%p", &m) return &m } @@ -31,30 +28,16 @@ func newMemoryRangeROM(base uint16, data []uint8, name string) *memoryRangeROM { var m memoryRangeROM m.base = base m.data = data - m.setBase(base) - m.name = name - m.address = fmt.Sprintf("%p", &m) return &m } func (m *memoryRange) setBase(base uint16) { m.base = base - //p := unsafe.Pointer(&m.data[0]) - //m.basePtr = (uintptr)(p) - (uintptr)(base) } func (m *memoryRange) peek(address uint16) uint8 { - // Safe version: return m.data[address-m.base] - - // Really overkill - // go-vet warns the caching of basePtr - // This wouldn't have a warning - // indexp := unsafe.Pointer((uintptr)(unsafe.Pointer(&m.data[0])) - (uintptr)(m.base) + uintptr(address)) - // But it makes sense to precalculate that - //indexp := unsafe.Pointer(m.basePtr + uintptr(address)) - //return *(*uint8)(indexp) } func (m *memoryRange) poke(address uint16, value uint8) { @@ -72,12 +55,12 @@ func (m *memoryRangeROM) poke(address uint16, value uint8) { func identifyMemory(m memoryHandler) string { ram, ok := m.(*memoryRange) if ok { - return fmt.Sprintf("RAM 0x%04x %s at %s", ram.base, ram.name, ram.address) + return fmt.Sprintf("RAM 0x%04x %s", ram.base, ram.name) } rom, ok := m.(*memoryRangeROM) if ok { - return fmt.Sprintf("ROM 0x%04x %s at %s", rom.base, ram.name, rom.address) + return fmt.Sprintf("ROM 0x%04x %s", rom.base, ram.name) } return ("Unknown memory") diff --git a/noSlotClockDS1216.go b/noSlotClockDS1216.go index 1f948cf..c8cceaa 100644 --- a/noSlotClockDS1216.go +++ b/noSlotClockDS1216.go @@ -138,6 +138,10 @@ func (nsc *noSlotClockDS1216) poke(address uint16, value uint8) { nsc.memory.poke(address, value) } +func (nsc *noSlotClockDS1216) setBase(base uint16) { + nsc.memory.setBase(base) +} + func (nsc *noSlotClockDS1216) loadTime() { now := time.Now() diff --git a/romdumps/romdumps_vfsdata.go b/romdumps/romdumps_vfsdata.go index 88ecff8..857f64f 100644 --- a/romdumps/romdumps_vfsdata.go +++ b/romdumps/romdumps_vfsdata.go @@ -19,7 +19,21 @@ var Assets = func() http.FileSystem { fs := vfsgen۰FS{ "/": &vfsgen۰DirInfo{ name: "/", - modTime: time.Date(2021, 1, 25, 17, 41, 15, 529030569, time.UTC), + modTime: time.Date(2021, 3, 14, 16, 59, 4, 46831383, time.UTC), + }, + "/80ColumnP109.BIN": &vfsgen۰CompressedFileInfo{ + name: "80ColumnP109.BIN", + modTime: time.Date(2021, 3, 7, 21, 7, 53, 641037891, time.UTC), + uncompressedSize: 1024, + + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x93\x5f\x68\x1c\xd5\x17\xc7\xef\xee\xce\xbd\xb3\x3b\x6d\x93\x25\x69\x7e\x6c\xfb\x53\x7a\x71\xd3\x92\x95\x88\x41\x8c\x5e\x1f\x0c\x42\x1f\x9c\xac\x0b\x0d\xf8\x2e\x83\x4f\x77\x8a\x82\xbe\xc8\x80\xc4\x6c\x68\x37\x86\xd8\x95\x2b\x52\xd8\x55\x06\x02\x4d\x21\x37\x64\xc3\xa8\x2f\xd7\x92\x81\x79\x49\x99\x3b\xb1\xf4\xa2\x4f\x45\x1b\xfa\xe0\x9f\xc4\x87\x7a\xfb\x52\x06\x75\x53\x99\xf1\xd5\xb7\xf3\x3d\x1c\xee\x39\xe7\x73\xbe\x37\x14\xa8\x91\xca\x19\x3d\xc7\x67\x7c\x81\x7c\x62\x62\x12\xaf\xd9\x21\x31\x1b\xd5\xad\xb5\xc6\x68\xbf\x1d\x85\x57\xe2\xfe\x72\x74\xb8\xd6\x18\x55\x0f\xe9\x16\x07\x3e\x41\xad\x1b\x32\xaf\xd0\x06\xaf\xa9\x22\x1f\xeb\xd4\x1d\x7c\x3b\xfe\xf5\x12\x26\xf1\x00\x44\xd5\xc7\xd3\xd5\x48\x7e\xa2\x8a\xfc\x47\xb9\xac\xac\x90\x98\xf3\xaf\xf9\xc4\x0c\x7e\xca\x9e\xb5\x2c\xca\x90\x8c\x58\x71\x1e\xdb\x8d\x25\x5f\x40\xea\x7c\xb8\xf0\xf6\x99\xd3\xe5\xb1\x71\x70\x22\x3a\x01\x00\x00\x99\xfe\x7f\xb9\x76\x06\x94\xa3\x72\xa6\x7d\x01\x6f\x9e\xbf\x4f\xa0\x46\x3e\x81\xb8\x9e\xdc\x9c\xbc\x2f\x0c\x56\xf2\x85\x81\x49\x1c\x0a\x88\xaf\x26\xdb\x29\xe2\xf9\xac\xc2\xa8\x2d\x8a\x52\x7f\x39\xe2\x23\xfd\x76\xc4\xc1\x22\x31\xfa\xcb\x91\x13\x0a\x54\x29\xfa\x02\x39\x3c\xb2\x79\xfb\xa8\xa0\xfe\x1e\xcc\x44\xf4\xa8\xa0\x1e\x39\xbb\xc2\xc0\x6e\x72\x2c\x8c\x50\x18\x72\x81\x35\x79\x57\x9e\x52\x88\x03\x5f\x18\x4e\xa5\x2d\xd7\x83\x03\xf9\x31\x7b\x51\xdf\x76\x3f\x60\xcf\xeb\x69\x77\xc8\xde\xd0\x6f\xba\x43\x76\x59\x7f\x24\x4f\xb2\xa7\xf4\x3b\xee\x23\x36\xa2\x4f\xbb\x43\xb6\x9b\xe7\xaf\x07\xe3\x59\x33\x23\x6b\x76\x20\x8c\xea\x9f\xfc\x92\x2f\x8c\x90\x40\xad\x0f\x08\x6c\xd5\x13\x32\x1f\xe9\xe7\xdc\x21\xeb\xea\x07\xee\x90\xcd\xe9\x9f\xdd\x21\x3b\xab\x7e\xd8\xcd\x8b\x6c\x7c\x21\xc1\xb5\x64\x1d\x50\x17\xec\xd5\x99\x0e\xfe\xd7\x9a\x48\xf0\x40\xf6\xf4\x2f\xbf\xb7\xc2\x38\x9f\xca\x27\x70\xb3\x55\x4f\xf2\xf8\x98\xc0\x90\xc0\xbd\x3a\x7b\xeb\x80\xc0\xda\x76\x8a\x42\x82\x5c\xd8\x58\xf2\x09\xc2\x2f\x24\xfc\x54\x8e\x24\xc7\x71\x32\x0b\x73\x18\x1b\xf5\xd5\x1e\xbe\x90\xac\x03\x5c\x4b\x82\xfa\xf4\xfe\x93\xf7\x0c\xc2\x6a\xa2\xfb\xe4\x2e\x9e\xd8\x77\x3e\x9b\x22\x81\xde\x15\x06\x5f\xc7\x6e\x12\x47\x0b\x2c\x75\x42\x02\xbb\xa9\x61\x59\xb5\x77\x53\x63\x91\x20\xbb\xd9\x6c\x36\x7d\x62\x50\xcb\xb2\x2c\x5f\x94\x9c\x4f\x3d\xb3\x97\x51\xef\x7a\x30\x07\xde\x2c\x37\x4a\x96\x75\x2a\x45\x57\x67\x37\xbf\x6d\x47\x1b\xb3\x53\x3b\x1e\x74\xac\xec\xfa\x4d\x7a\xd9\xc6\x17\x13\x1a\x40\x1f\x7c\xc7\x4a\x3e\xb8\xb3\xe3\x99\x8e\xbd\x9d\x22\xfc\xdb\x7e\x66\x45\x53\x19\xb4\x35\x2e\x1b\x86\x7a\x96\x1e\xe1\xc6\x92\x7d\x20\x50\x96\x2f\xa9\x09\xba\x57\x0f\x4a\x3e\x81\x83\x14\xca\x85\x20\x3b\x7f\xab\x9e\xd0\x6e\x0a\x9d\xdc\xba\xb4\xd2\x96\x81\x32\x43\x62\x56\xaa\xaa\x22\xbf\x52\xe5\xcc\xa2\x7f\xb4\x46\x62\xf9\xb5\xaa\xf0\xe3\x57\x89\xe9\x13\xd3\x91\xdf\xa8\x0a\x2f\x3c\xfd\xaf\x68\x15\xe4\x7f\xee\x6f\xaf\xd9\x3d\xbb\xbc\x32\x7b\x8b\x52\x4a\x37\x29\xfd\x62\xab\x67\xaf\x65\xeb\x6e\x5e\x4b\x11\x9d\xb2\x17\xaa\xbc\xda\x21\x2b\xaf\x70\xb3\xf3\xd2\xca\xcb\x18\xc4\x35\xe6\xd1\x8d\x59\xfd\xcc\xea\xc0\x43\x72\x55\x9f\xbd\x07\x8a\x7a\x62\x1e\xdf\x03\x45\x35\x37\xf0\x50\x1f\x14\x03\x84\xed\x18\xbf\x2f\x79\x1b\x3f\x4e\xb0\x1d\xcb\x2f\xf5\x43\xd9\x55\xd0\xc6\x13\x09\x95\xd7\xd5\x58\xe6\xc3\x8b\x49\x00\xc3\x0c\x4c\x08\xee\x54\xda\x5d\x0f\xa9\x32\xb6\xe3\x75\x70\xcd\x43\xb7\x0e\x0f\x0f\x7d\x50\xd8\xf1\x4c\x0e\x3a\x93\x21\x81\x9d\xf3\x74\x93\x6e\x51\x87\xee\x12\xb3\x5a\xde\xf6\x50\xf4\x80\x15\x7a\xf8\x46\xcc\x97\xf0\x51\x12\x0a\x43\xa3\xa3\xd7\xd9\xf7\xee\xb9\x5a\x67\x92\xdd\x1d\x80\x48\x7e\xae\x46\xa7\xab\x51\xf6\x41\xff\x92\x57\x74\x69\xba\x1a\x11\xe7\xdc\x3f\x01\x00\x00\xff\xff\x98\xef\x4f\xd7\x00\x04\x00\x00"), + }, + "/80ColumnP110.BIN": &vfsgen۰CompressedFileInfo{ + name: "80ColumnP110.BIN", + modTime: time.Date(2021, 3, 14, 15, 10, 13, 458446698, time.UTC), + uncompressedSize: 2048, + + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x95\x3f\x6b\xdb\x4e\x18\xc7\x0f\x71\x98\x1b\x44\xf2\x60\x32\xdc\x20\xc2\x21\x34\x98\x1f\xbf\xc1\x53\x30\x45\x88\xe3\xe1\x08\xa6\x43\x31\x21\x43\x86\x0c\xc6\x53\xc6\x4e\xc5\x14\x23\x84\xe8\x10\x42\x07\x23\x32\x15\x9a\xa1\x63\x5e\x44\xc9\xd4\xd7\x91\xa1\x63\x06\x8f\x9e\xac\xa2\xab\x7d\xf7\xe8\x48\xfa\xe0\x21\x1f\x7d\xef\xbe\x77\xcf\x1f\x29\x2c\x88\xb6\x6d\xff\xf9\xa8\xb5\xf1\xf6\x92\xbf\x7f\xbd\xbd\xa5\x3d\xc4\x9e\x9f\xd7\xcf\x0f\x9b\x38\x4a\x0e\x8b\xd7\xeb\xcd\xfa\xa7\x10\xe2\xc0\x55\x55\x3d\x6c\x24\xc0\x81\x6f\x6f\x67\xaa\x20\xfa\xa6\x7a\x7a\x58\x13\xfd\x63\x55\xad\x86\xc9\xc9\xd0\xf3\x1c\x5e\x86\xc3\x98\xf0\xe9\x6f\xce\x8f\xc2\x0c\x0e\x21\x04\x31\xdf\xc7\x71\xa0\x1f\xbf\xb6\xe7\x8d\xb0\xfb\x83\x67\xc7\x81\xbe\x0d\xbc\xb6\x81\x7f\x1b\xe8\x61\xf9\xb7\x81\xff\x36\xf0\x6f\x03\xbd\x0d\x74\x1a\xb2\x0b\x26\x1d\x9f\x9d\x0d\x33\xaa\x67\x59\x99\x95\x99\x7f\x24\x8a\x69\x3e\x5c\xb9\xf2\xb3\xc5\xfd\x0d\xfc\x7f\xf1\xd5\xb1\x9a\xcd\x16\xf7\xb7\x9f\x88\xbf\x00\xea\x17\x83\x52\x0a\x5c\x7b\xd8\x58\x70\xce\xc5\xd8\x31\x34\x97\x93\xcb\x86\x6c\x01\xd8\x01\x84\x77\xee\x79\x96\x61\x4e\x4c\xfa\x7c\x22\x2e\x40\xe9\xca\xf1\xc4\xdc\x35\x3f\xcc\xc4\xe7\x33\xe9\xaa\x52\x38\xce\x31\x4a\x94\xf6\x96\x25\x17\x49\x84\xb9\x63\x1e\x9f\x64\x25\xe7\x5e\xd7\xab\x88\xea\xa7\x4a\xaf\x90\x70\x69\x2f\xa0\x88\x3f\xe6\x54\xcf\x11\x8b\x88\x2f\x7b\x77\x27\xd7\xdf\x33\xc9\x77\x20\xe7\x72\xc0\x7a\xe9\xf7\x0a\x30\x97\x03\x39\x67\x34\x9f\x18\x18\x10\xfe\xf6\x78\xaf\xfd\xf9\x32\x43\x2c\x11\x1d\xaf\x10\xbb\x9f\xe3\x24\xd5\x5a\xa7\xee\x75\x65\x4b\x83\x88\x66\x49\xf2\xd7\x4b\x4d\xeb\x65\x59\xd3\xfd\x1f\x30\x3d\x75\x8c\xf6\x38\x7f\x5e\x62\xa7\xd2\xfb\x1f\x71\xce\x39\xe9\x0f\x9a\xe9\xec\xc6\xf8\xf5\xda\x86\x3f\xaf\xfe\xf5\xd8\xd4\x75\xed\xd7\x2f\x2e\xde\x9f\x13\xff\x89\xa9\xeb\x9a\xf8\x75\xc9\xd1\xfb\x75\xfa\x9d\x79\xd7\xd3\xa7\xe4\xbc\x1c\x75\x4e\xfb\xbb\x03\x1b\x34\x1f\xa4\xfd\xac\x6b\x63\x46\x23\x20\x5c\xd7\xcd\xa3\xf1\x6c\x46\x30\x32\x75\x8f\xa9\xdf\xce\xce\xeb\xce\x71\x31\xee\xc2\xcf\x67\xa5\x15\x08\x1e\x79\x7d\xd0\x85\xd7\x45\xf2\xdf\x6b\xaf\xb9\x33\x94\x12\x02\x71\xcb\x57\x5f\x3e\x3b\xd2\xdd\xf8\x92\xfe\xdb\x0a\x68\xef\x1f\x45\x05\x22\x16\x3d\xbd\x24\xf3\x94\xa4\x6a\xa9\xc8\xbc\xb3\xc8\x4e\xb8\xab\x90\xd6\xd7\x0b\xda\x7f\x11\x7c\x81\x39\xeb\x06\x80\x67\xd2\xad\x3f\xbf\x9a\x5f\x9d\x3b\x5d\x06\xf3\xc2\xd8\x4b\xd3\x34\x0d\xe1\xeb\x34\x4d\xd3\xde\xfd\x68\x7f\x6c\x87\xbb\x11\x70\x6c\xf3\x89\x7c\x45\xaf\x17\x74\x3e\x18\x2b\x74\x1e\x91\x7a\x40\x01\x00\xf4\xdf\x89\x31\x86\xcc\x8f\x9d\x80\x11\xf4\x98\xf6\x9f\x31\xcc\x64\x86\xe1\x7a\xf5\xe4\xb8\xe4\x52\xf9\xf9\x8e\x41\x8c\x05\xf9\x5e\x76\xb5\xa2\xf5\xea\x44\x20\xdf\xcf\x79\x13\x33\x1a\xd9\xb4\xb1\xbf\x3d\x7e\xff\x13\x00\x00\xff\xff\xf1\x70\x46\x09\x00\x08\x00\x00"), }, "/Apple IIe Video Enhanced - 342-0265-A - 2732.bin": &vfsgen۰CompressedFileInfo{ name: "Apple IIe Video Enhanced - 342-0265-A - 2732.bin", @@ -133,6 +147,13 @@ var Assets = func() http.FileSystem { compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x52\xc1\x6b\x23\x55\x1c\x9e\x4e\x67\x3a\xcd\xd8\x6e\x97\x76\x29\x51\x56\xfc\x95\xee\x4a\xb3\x14\x9b\x83\xae\x03\x96\xc5\xb8\x4d\x7d\x29\xd3\x74\x48\x13\xdd\x95\x25\xe4\xe0\xe1\x99\x93\x07\x0f\xb9\xa8\x39\xb8\xb0\x04\x02\x3d\x78\x48\x0a\x81\x61\x5c\xcb\xbc\x92\x66\x83\x2b\xec\xa4\x18\x8c\x20\xd8\xf7\xe6\x90\x77\xf0\x20\x82\xcb\x1e\xf6\x60\x7b\x90\x57\xb5\xec\x20\x48\x65\xd2\x52\x2f\xfe\x07\xf6\x3b\x7c\x6f\xbe\x99\xef\xf7\xfd\x3e\x86\x37\x5a\x9a\x9b\xbf\x75\xfc\xa1\x6a\xb4\x87\xa2\xde\x68\x09\x55\x50\x0d\xed\x1c\xf7\xe1\x85\x3d\x8c\x77\xab\x81\xa6\xeb\xba\x5e\x2d\x69\x18\x63\x17\x6f\x46\x14\x34\x57\x6b\x96\xb4\x56\xa0\xc5\x3e\x45\xd6\x05\xef\xcb\xeb\x5c\x79\xfa\xba\xd0\xc9\xe8\x5d\xc3\xd8\x98\x36\x2d\x4a\x9e\xbb\x7b\x9d\xe4\x1b\x9e\x42\xf4\x86\xa1\x10\xa9\x61\xa8\x98\x5e\x15\x77\xe8\xcb\xe2\x16\x5d\x14\x59\x7a\x43\x58\x74\x56\x98\x34\x2f\xde\xa3\x33\x62\x85\x5e\x13\x19\x07\xe8\xbc\xc8\x38\x73\xf4\x15\x61\x3a\x71\xba\x20\x96\x5b\x81\x86\xba\x9e\x42\x67\x44\x82\xe6\xc5\x05\x1c\x7b\x49\x37\xf6\x87\xc2\xc0\x3c\x9f\xc5\xc6\x7e\x22\x0e\x74\xfa\xe2\xe5\xed\x6e\xd5\x37\x15\xbf\x61\xa8\xe6\x02\x25\x00\x8f\x18\x19\x0d\x69\xa6\xe1\x29\xe6\x02\xad\xc0\x23\xc6\x9f\x61\x3a\x2e\xa6\x28\x88\xa5\xd8\x04\xf4\x99\x90\xc3\x62\x5a\x74\xe3\x35\xe8\xb3\x8f\x0d\x0d\x91\x48\xc3\x18\x81\x3e\x43\x8f\x8d\x11\xfe\x2c\xfc\x38\x82\xa1\xcf\x4e\xd4\xc5\x30\xe1\xab\x5d\x8c\x6b\x08\x63\x52\x45\x9b\xad\x40\x6b\x96\xb4\xae\x37\xfc\x8b\xa1\xf1\xb1\x17\x0d\x4d\x68\xf4\x4d\x3e\x5c\x2f\xf7\xb0\x8b\xb7\xf1\x5c\x01\x93\xe8\x59\x8d\xd3\x64\xaa\xc7\x65\x22\x0d\xf2\x0f\x43\x39\x3e\x90\x5d\x43\x15\x0a\x9d\xe5\x79\x5b\xc2\x54\x8f\x2b\x7b\xc6\xbe\x8e\x6a\x8e\x04\x5b\x0c\xc3\x16\x6b\x06\x5a\xc7\x50\xf9\x35\xf2\x00\x5c\x86\x6d\x05\x9f\xbe\xbf\x27\x34\xf2\x06\xb8\x8c\xff\x46\xec\xaa\x2a\x77\xfe\xf0\xc5\xf3\xe0\xb2\x3d\x7e\x44\x24\x64\xab\xf0\x80\x9d\x39\xf9\xef\xe6\x26\xc3\xdb\x58\xd7\xdd\x8a\xae\xa3\x56\xa0\x75\x0d\xd5\xb1\xe9\x62\x5b\x76\x9c\x8a\x23\x81\xcb\x3a\x5f\xf8\x7b\xe0\x32\x6a\xf3\x23\xec\x76\xbe\xfd\x57\xc0\xfd\x41\x06\x3c\x64\xf6\x70\x28\xf8\xf0\xd9\xfe\x08\xd9\x0d\x2b\x99\xb1\xd3\x8e\xf4\xaa\x50\xe8\x0d\x6e\xd9\x89\x9d\x31\x99\xda\x5c\x26\xf1\xc1\xa5\x01\x79\x67\x5c\x8e\x4d\x8c\x83\x4c\x27\xe3\xb2\x6d\x51\x89\x2b\x64\x92\x47\xe8\x54\x7c\x3a\x30\xf6\x27\x7f\x72\xc6\x10\x1c\xb3\xd8\x44\xb8\x2b\x36\x01\x5b\xcc\xb9\x04\x0f\x59\x0d\x5c\x46\x56\xc1\x65\x15\x97\x54\xc1\x65\xe6\x15\x2a\xc6\x23\xf1\x48\xb9\x21\xc9\xbf\x16\x48\x93\x1f\x11\x9b\x1f\xd6\xcb\xbd\x88\x52\x2f\xf7\xe0\x07\x96\x1a\x9c\x8c\x21\x84\x71\x01\x11\xa5\xe1\x8d\x10\xa9\xe1\xa9\x9d\x72\x4f\xff\xc4\x53\x31\x8a\x0d\x85\x6e\xb9\x5e\xee\xa5\x42\xc2\x45\xf4\xd8\x1b\xe1\x4f\x70\xd7\x53\xa3\xc5\x62\xb1\x58\x38\xf9\x35\xe1\x20\x58\x3e\x46\x60\xfa\x18\xa5\x02\x30\x7d\xc7\x86\x0f\x4e\xc8\x8e\x42\xc1\x6f\x05\x5a\x38\xf9\x33\xb6\xbf\x61\xfc\xef\x7b\xfc\x2f\x73\x81\xa2\x0d\x15\x2c\x5f\x0c\xc3\x6d\x1f\xeb\xfc\xb0\xe0\x58\xa1\x7b\x8e\x6b\x4e\x34\x7c\x58\x72\xa6\x06\xd6\xa0\xd0\x2c\x69\x04\xea\xe5\xde\xc1\xc1\x41\xea\xbf\x4e\xc6\x9f\x14\xde\x7f\x7a\xe5\xfe\xa5\xcf\xb3\x3f\x7e\xf4\xe7\xab\x5f\x2b\x9f\x2d\x7d\x3f\xbf\x78\xc7\xbc\x3c\xb6\x9e\x4b\xc3\xea\x5a\x1a\xb2\xb9\x24\xbc\x9b\x5c\x82\x2c\xca\xc1\x72\x26\x05\xeb\x89\x2c\x24\x33\x19\x58\x49\xa4\x61\x39\xf9\x16\xac\x26\x32\x90\xb0\x32\xb0\x9a\xb8\x0d\x2b\xb9\x34\xac\xe4\x4c\x48\xe4\xde\x86\xf5\xa4\x05\x6b\x37\xb3\x90\x5e\x7b\x07\x96\x92\x37\xa1\xd5\x6e\xb7\xa5\x01\xd9\xf6\x77\xc7\xe7\x38\xc7\x39\xfe\xb7\xf8\x27\x00\x00\xff\xff\xe3\xc4\x99\x1b\x00\x08\x00\x00"), }, + "/Videx Videoterm ROM 2.4.bin": &vfsgen۰CompressedFileInfo{ + name: "Videx Videoterm ROM 2.4.bin", + modTime: time.Date(2021, 3, 7, 20, 59, 17, 80032721, time.UTC), + uncompressedSize: 1024, + + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\x92\x51\x68\x1c\x45\x1c\xc6\x27\x77\x3b\xbb\x77\x4b\x93\x9e\x49\xa4\x07\x5a\x32\xa6\x2d\xf5\xf0\xd4\xb4\x18\x3d\xf1\xc5\x87\x82\x9b\x63\x69\xcf\x37\x83\x45\x46\x84\x76\x23\x04\xcd\xdb\xe1\x3c\x24\x2a\x49\xd3\xea\xc8\x4a\x28\x44\x34\x70\x1e\x06\x33\x21\x8b\x9b\xf8\xd2\x08\x81\xd1\xfa\xb0\xff\x4d\x73\x99\x82\x2f\xa5\x08\x79\xaa\x69\xc0\xb2\x29\x04\x0f\x46\x13\xd9\x13\xf7\xe9\xbf\xcc\x37\xcc\xff\xfb\x7d\x5f\xc0\xac\x52\x1b\x86\x92\x67\xc4\x10\x67\x16\xd7\x96\x40\x5c\x9b\xe4\x5d\x68\xa2\x1b\x3c\x94\x1b\xdf\x46\x7c\x55\xee\xee\x14\xd4\x3e\x1f\x95\x34\xd0\x56\x29\x97\xe4\xc9\xdc\x21\x19\xfc\x9b\x5c\x3f\xf4\x96\xbc\x65\x8f\x92\xbb\xd1\x83\x8b\x2a\xf3\xe0\x52\x80\x64\xe1\x80\x5c\x8e\xfc\xa4\x5c\x90\x45\x0a\x9f\xa9\x8c\xb8\x0f\x1f\x2b\x3b\xd0\xd6\xc8\xeb\x5c\x5b\xe1\x1f\x4e\xa0\x2d\xdb\xf6\xfc\x01\x08\xfd\xa7\xca\xef\xc9\xa1\x7e\x08\x93\x1e\x90\x2a\x23\x14\xdc\xf7\x73\xa5\x6d\x65\x88\xdf\xf3\xc4\x29\x4d\x72\x66\x7a\x15\xfa\x51\xed\x9d\x17\x07\x51\xf1\x24\xca\xed\xe4\x10\x42\x88\x33\x73\xf1\xcc\x96\xc6\x89\xc9\x35\x26\x46\xbc\x78\x7a\x8b\x61\x3f\xcb\x19\x0e\x98\x49\xae\xc7\xe2\x38\x0f\x65\xc0\x30\xd4\xc2\xbe\x71\x66\xf0\x55\x29\x7a\x78\x28\x05\x1a\xd7\xe9\x0f\x1d\x91\x90\x0b\x4f\x2e\x09\x70\xd6\x1f\x6f\x3a\x74\x6f\x85\x61\xd1\x20\x13\x71\x24\x6b\x7e\x9b\x8a\x97\x38\xb3\xa8\x38\xaf\xda\x0d\xd9\x9c\x8a\xd5\x3f\xc1\x90\x9c\x55\x07\x74\x85\x61\x59\xf3\xb1\x43\x42\xf0\x56\x18\x26\x13\xf1\x23\x86\xcb\x75\xa3\x60\xfd\xf7\x94\x97\x2a\x02\x8d\x1d\x62\xc5\xc4\x80\x06\xf2\xc6\x10\x14\xfd\x24\x3c\x45\xae\xc2\x7c\xb2\x2b\x10\x67\x98\x6b\xbc\x94\xf4\xb6\x18\x2e\x2c\x88\x4b\xbc\xa3\x4f\xe6\x5a\x1a\xbb\x46\xdc\xa1\xef\xe5\xa7\x60\x55\x5d\x15\x39\xfe\x96\x54\x6f\xc3\x9a\x7a\x53\x1c\x3e\xaf\xd3\x50\x28\x67\xe6\xc5\xba\xe1\x6e\x46\xe4\x6c\xfc\x48\xa7\x37\xa1\xe8\x57\x5b\xe9\x64\x8e\x19\xa5\xc9\x34\xb3\xde\x58\x74\x77\xbc\x77\x4c\x1f\x4b\xc7\x8e\x65\x71\x82\x58\x71\x03\x11\x03\xc2\x9b\xf0\xa3\xea\x11\x5d\xdd\xda\x52\x02\x42\xf5\x8d\x9b\x8f\xdc\xb3\x10\x68\xcc\xdb\x86\x6d\x8f\xb7\x8d\x71\x6d\x3a\xd5\x6a\xb5\xca\xb5\xe1\xd9\xb6\x6d\x73\x66\x50\xe8\x56\x66\x67\x7f\x9a\x9f\x82\x46\xd8\x82\x6b\x7e\xae\xc3\x6f\x1d\x1c\x5a\x9c\xe8\x5b\x73\xe8\x0f\x0b\x8f\xfb\xfe\xff\xae\xf4\xf4\x55\x50\x3f\x2b\xce\x8f\x33\xc3\xe9\x60\x77\xec\xd2\xb1\xe5\x8d\x50\x7a\x55\x6f\x99\xda\x69\x03\xaa\xde\xfb\x0e\x19\x8d\xbd\x10\x2f\xa0\x3b\x7e\x76\x01\x6d\x51\x47\xfc\x45\x1a\xc0\x47\x65\xda\x48\x4b\x19\x9e\x7b\x2a\x2e\x19\x49\xd6\xbd\x06\x5e\xe5\x21\x29\x4d\x3a\x2d\x66\xa5\x67\x59\xf5\xa4\x07\xc5\x30\xcb\x35\x0e\xda\x29\xfb\x34\x7b\xd7\x88\x3d\xde\xc6\x34\x40\x12\xe6\xd4\xf1\x72\x41\xa6\x5d\xd4\xf0\x69\x92\x2d\x17\x24\x5d\x5a\x3f\xb7\x49\xf6\x23\x72\x21\x82\x56\x98\x03\xf0\x0d\xb8\xa3\xf6\xdc\xfd\x68\xaf\xbc\x79\xf4\xe1\xb9\x8a\x5f\xbc\x55\x7b\xae\xeb\x93\x27\xfa\x9f\x1e\x74\x51\x2a\x2b\x4d\x36\x11\x25\xdf\x43\x13\x51\x40\x49\x3e\x40\xd2\xf6\xb3\xe4\x72\xd4\x44\xf4\xcb\x67\x2b\xb7\xf8\xd1\xb6\x33\x3d\x7c\xc3\x99\x77\x16\x87\x67\x86\x9b\xb7\xbf\xa8\x1b\x4e\xad\x20\xce\x4f\x57\x66\x5e\x15\xd6\xf4\xcb\x33\xaf\x10\x14\x15\xfd\x0f\xbc\xef\x86\x93\x81\xd9\xa0\x6e\xc2\x6c\x72\xe2\x1e\xca\x24\xbd\x23\xe4\x1e\xca\xa8\xd7\x82\xba\xf9\x15\xca\x84\x59\xf2\x67\x2c\xa6\xc8\x01\xa4\x9b\x7d\x9d\xec\x03\x57\xd8\x21\x5d\xe0\xc1\x4d\xd5\x9b\xf6\x6c\x34\x0e\xf1\x46\x8a\x68\x03\x6d\xe5\xa7\x78\xdd\x54\x39\x72\x21\x6a\xa0\xcf\xeb\xe6\x4f\xbb\xbb\xbb\x0b\xa8\x4b\xa0\xe9\xd3\x81\xc6\xd3\x67\xdc\x17\x22\x6f\x45\x5b\x85\xdc\x4a\xdd\x94\x3b\x7e\xd7\x3c\x59\x8d\xc8\x76\x2c\x26\x49\x03\x02\x86\x1f\xbe\xe1\xff\x36\x36\x50\xf4\xef\xd2\xca\xc4\x1a\x43\xce\x95\x5f\x7f\xf9\xf9\xe8\xf6\xde\xbf\x01\x00\x00\xff\xff\x28\xbb\xe3\xbb\x00\x04\x00\x00"), + }, "/dos33.dsk": &vfsgen۰CompressedFileInfo{ name: "dos33.dsk", modTime: time.Date(2021, 1, 23, 23, 12, 20, 701418936, time.UTC), @@ -142,6 +163,8 @@ var Assets = func() http.FileSystem { }, } fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{ + fs["/80ColumnP109.BIN"].(os.FileInfo), + fs["/80ColumnP110.BIN"].(os.FileInfo), fs["/Apple IIe Video Enhanced - 342-0265-A - 2732.bin"].(os.FileInfo), fs["/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin"].(os.FileInfo), fs["/Apple2_Plus.rom"].(os.FileInfo), @@ -158,6 +181,7 @@ var Assets = func() http.FileSystem { fs["/DISK2.rom"].(os.FileInfo), fs["/MemoryExpansionCard-341-0344a.bin"].(os.FileInfo), fs["/ThunderclockPlusROM.bin"].(os.FileInfo), + fs["/Videx Videoterm ROM 2.4.bin"].(os.FileInfo), fs["/dos33.dsk"].(os.FileInfo), } diff --git a/screen/snapshots.go b/screen/snapshots.go index e6dd26e..a417152 100644 --- a/screen/snapshots.go +++ b/screen/snapshots.go @@ -80,6 +80,9 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB case VideoSHR: snap = snapshotSuperHiRes(vs) applyNTSCFilter = false + case VideoVidex: + snap = vs.GetCardImage(lightColor) + applyNTSCFilter = false } if applyNTSCFilter { diff --git a/screen/snapshotsDebug.go b/screen/snapshotsDebug.go index 8ecd7e6..572ebf5 100644 --- a/screen/snapshotsDebug.go +++ b/screen/snapshotsDebug.go @@ -71,6 +71,8 @@ func VideoModeName(vs VideoSource) string { name = "RGB160" case VideoSHR: name = "SHR" + case VideoVidex: + name = "VIDEX" default: name = "Unknown video mode" } diff --git a/screen/testScenarios.go b/screen/testScenarios.go index 0f3c011..68c0861 100644 --- a/screen/testScenarios.go +++ b/screen/testScenarios.go @@ -3,6 +3,8 @@ package screen import ( "encoding/json" "fmt" + "image" + "image/color" "image/png" "io/ioutil" "os" @@ -128,6 +130,11 @@ func (ts *TestScenario) GetSuperVideoMemory() []uint8 { return ts.SVideoPage } +// GetCardImage returns an image provided by a card, like the videx card +func (ts *TestScenario) GetCardImage(light color.Color) *image.RGBA { + return nil +} + func buildImageName(name string, screenMode int, altSet bool) string { var screenName string switch screenMode { diff --git a/screen/videoSource.go b/screen/videoSource.go index c423b63..419b6a5 100644 --- a/screen/videoSource.go +++ b/screen/videoSource.go @@ -1,5 +1,10 @@ package screen +import ( + "image" + "image/color" +) + // Base Video Modes const ( VideoBaseMask uint16 = 0x1f @@ -14,6 +19,7 @@ const ( VideoRGBMix uint16 = 0x12 VideoRGB160 uint16 = 0x13 VideoSHR uint16 = 0x14 + VideoVidex uint16 = 0x15 ) // Mix text video mdes modifiers @@ -43,4 +49,6 @@ type VideoSource interface { GetCharacterPixel(char uint8, rowInChar int, colInChar int, isAltText bool, isFlashedFrame bool) bool // GetSuperVideoMemory returns a slice to the SHR video memory GetSuperVideoMemory() []uint8 + // GetCardImage returns an image provided by a card, like the videx card + GetCardImage(light color.Color) *image.RGBA } diff --git a/softVideoSwitch.go b/softVideoSwitch.go new file mode 100644 index 0000000..123b0e9 --- /dev/null +++ b/softVideoSwitch.go @@ -0,0 +1,44 @@ +package izapple2 + +import ( + "image" + "image/color" +) + +/* + Videx Soft Video Switch + + See: + https://archive.org/details/videx-soft-video-switch + +*/ + +// SoftVideoSwitch represents a Videx soft video switch +type SoftVideoSwitch struct { + card *CardVidex +} + +// NewSoftVideoSwitch creates a new SoftVideoSwitch +func NewSoftVideoSwitch(card *CardVidex) *SoftVideoSwitch { + var vs SoftVideoSwitch + vs.card = card + return &vs +} + +func (vs *SoftVideoSwitch) isActive() bool { + if vs == nil { + return false + } + + isTextMode := vs.card.a.io.isSoftSwitchActive(ioFlagText) + ann0 := vs.card.a.io.isSoftSwitchActive(ioFlagAnnunciator0) + return isTextMode && ann0 +} + +func (vs *SoftVideoSwitch) BuildAlternateImage(light color.Color) *image.RGBA { + return vs.card.buildImage(light) +} + +func (a *Apple2) SoftVideoSwitch() *SoftVideoSwitch { + return a.softVideoSwitch +} diff --git a/videoSourceImpl.go b/videoSourceImpl.go index 58bb6ae..914e504 100644 --- a/videoSourceImpl.go +++ b/videoSourceImpl.go @@ -1,6 +1,9 @@ package izapple2 import ( + "image" + "image/color" + "github.com/ivanizag/izapple2/screen" ) @@ -23,6 +26,7 @@ func (a *Apple2) GetCurrentVideoMode() uint16 { isStore80Active := a.mmu.store80Active isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3) isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo) + isVidex := a.softVideoSwitch.isActive() isRGBCard := a.io.isSoftSwitchActive(ioFlagRGBCardActive) rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard) @@ -39,6 +43,9 @@ func (a *Apple2) GetCurrentVideoMode() uint16 { if isSuperHighResMode { mode = screen.VideoSHR isMixMode = false + } else if isVidex { + mode = screen.VideoVidex + isMixMode = false } else if isTextMode { if is80Columns { mode = screen.VideoText80 @@ -137,6 +144,11 @@ func (a *Apple2) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isA return pixel } +// GetCardImage returns an image provided by a card, like the videx card +func (a *Apple2) GetCardImage(light color.Color) *image.RGBA { + return a.softVideoSwitch.BuildAlternateImage(light) +} + // DumpTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash func DumpTextModeAnsi(a *Apple2) string { is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)