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 }