diff --git a/README.md b/README.md index 02d955a..c7396f7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ Portable emulator of an Apple II+. Written in Go. - Emulated extension cards: - DiskII controller - 16Kb Language Card - - 256Kb Saturn RAM Card + - 256Kb Saturn RAM + - ThunderClock Plus real time clock - Graphic modes: - Text, Lores and Hires - Displays: @@ -40,7 +41,7 @@ casa@servidor:~$ ./apple2sdl ![DOS 3.3 started](doc/dos33.png) ### Play games -Download an DSK file locally or use the a link ([Asimov](https://www.apple.asimov.net/images/) is an excellent source) with the `-disk` parameter: +Download a DSK file locally or use an URL ([Asimov](https://www.apple.asimov.net/images/) is an excellent source) with the `-disk` parameter: ``` casa@servidor:~$ ./apple2sdl -disk "https://www.apple.asimov.net/images/games/action/karateka/karateka (includes intro).dsk" ``` @@ -88,7 +89,7 @@ Line: - F6: Toggle between NTSC color TV and green phosphor monochrome monitor - F7: Save current state to disk - F8: Restore state from disk -- F10: Cycle character generator codepages. Only if the character generator ROM has more than one 2Kb pages. +- F10: Cycle character generator codepages. Only if the character generator ROM has more than one 2Kb page. - F12: Save a screen snapshot to a file `snapshot.png` Only valid on SDL mode @@ -122,6 +123,8 @@ Only valid on SDL mode main rom file (default "/Apple2_Plus.rom") -saturnCardSlot int slot for the 256kb Saturn card. -1 for none (default -1) + -thunderClockCardSlot int + slot for the ThunderClock Plus card. -1 for none (default 5) -traceCpu dump to the console the CPU execution -traceSS diff --git a/apple2Setup.go b/apple2Setup.go index 832506e..342b736 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -87,6 +87,13 @@ func (a *Apple2) AddSaturnCard(slot int) { a.insertCard(&cardSaturn{}, slot) } +// AddThunderClockPlusCard inserts a ThunderClock Plus clock card +func (a *Apple2) AddThunderClockPlusCard(slot int, romFile string) { + var c cardThunderClockPlus + c.loadRom(romFile) + a.insertCard(&c, slot) +} + // AddCardLogger inserts a fake card that logs accesses func (a *Apple2) AddCardLogger(slot int) { a.insertCard(&cardLogger{}, slot) diff --git a/apple2main.go b/apple2main.go index 6683353..6a4c0a5 100644 --- a/apple2main.go +++ b/apple2main.go @@ -39,6 +39,10 @@ func MainApple() *Apple2 { "saturnCardSlot", -1, "slot for the 256kb Saturn card. -1 for none") + thunderClockCardSlot := flag.Int( + "thunderClockCardSlot", + 5, + "slot for the ThunderClock Plus card. -1 for none") mono := flag.Bool( "mono", false, @@ -49,7 +53,6 @@ func MainApple() *Apple2 { true, "set fast mode when the disks are spinning", ) - panicSS := flag.Bool( "panicss", false, @@ -94,6 +97,9 @@ func MainApple() *Apple2 { if *saturnCardSlot >= 0 { a.AddSaturnCard(*saturnCardSlot) } + if *thunderClockCardSlot > 0 { + a.AddThunderClockPlusCard(*thunderClockCardSlot, "/ThunderclockPlusROM.bin") + } if *disk2Slot >= 0 { a.AddDisk2(*disk2Slot, *disk2RomFile, *diskImage) } diff --git a/apple2sdl/apple2.state b/apple2sdl/apple2.state new file mode 100644 index 0000000..2419393 Binary files /dev/null and b/apple2sdl/apple2.state differ diff --git a/cardBase.go b/cardBase.go index 14d7f33..9d54279 100644 --- a/cardBase.go +++ b/cardBase.go @@ -11,11 +11,12 @@ type card interface { } type cardBase struct { - a *Apple2 - rom *memoryRange - slot int - ssr [16]softSwitchR - ssw [16]softSwitchW + a *Apple2 + rom *memoryRange + romExtra *memoryRange + slot int + ssr [16]softSwitchR + ssw [16]softSwitchW } func (c *cardBase) loadRom(filename string) { @@ -23,7 +24,12 @@ func (c *cardBase) loadRom(filename string) { panic("Rom must be loaded before inserting the card in the slot") } data := loadResource(filename) - c.rom = newMemoryRange(0, data) + if len(data) >= 0x100 { + c.rom = newMemoryRange(0, data) + } + if len(data) >= 0x800 { + c.romExtra = newMemoryRange(0, data) + } } func (c *cardBase) assign(a *Apple2, slot int) { @@ -34,6 +40,11 @@ func (c *cardBase) assign(a *Apple2, slot int) { a.mmu.setPagesRead(uint8(0xc0+slot), uint8(0xc0+slot), c.rom) } + if slot != 0 && c.romExtra != nil { + c.romExtra.base = uint16(0xc800) + a.mmu.prepareCardExtraRom(slot, c.romExtra) + } + for i := 0; i < 0x10; i++ { a.io.addSoftSwitchR(uint8(0xC80+slot*0x10+i), c.ssr[i]) a.io.addSoftSwitchW(uint8(0xC80+slot*0x10+i), c.ssw[i]) diff --git a/cardThunderClockPlus.go b/cardThunderClockPlus.go new file mode 100644 index 0000000..882ab5a --- /dev/null +++ b/cardThunderClockPlus.go @@ -0,0 +1,49 @@ +package apple2 + +/* +ThunderClock`, real time clock card. + +See: + https://ia800706.us.archive.org/22/items/ThunderClock_Plus/ThunderClock_Plus.pdf + https://prodos8.com/docs/technote/01/ + https://www.semiee.com/file/backup/NEC-D1990.pdf + + +uPD1990AC hookup: + bit 0 = data in + bit 1 = CLK + bit 2 = STB + bit 3 = C0 + bit 4 = C1 + bit 5 = C2 + bit 7 = data out +*/ + +type cardThunderClockPlus struct { + microPD1990ac + cardBase +} + +func (c *cardThunderClockPlus) assign(a *Apple2, slot int) { + c.ssr[0] = func(*ioC0Page) uint8 { + bit := c.microPD1990ac.out() + // Get the next data bit from uPD1990AC on the MSB + if bit { + return 0x80 + } + return 0 + } + + c.ssw[0] = func(_ *ioC0Page, value uint8) { + dataIn := (value & 0x01) == 1 + clock := ((value >> 1) & 0x01) == 1 + strobe := ((value >> 2) & 0x01) == 1 + command := (value >> 3) & 0x07 + /* fmt.Printf("[cardThunderClock] dataIn %v, clock %v, strobe %v, command %v.\n", + dataIn, clock, strobe, command) */ + + c.microPD1990ac.in(clock, strobe, command, dataIn) + } + + c.cardBase.assign(a, slot) +} diff --git a/memoryManager.go b/memoryManager.go index 12b525b..cc15c57 100644 --- a/memoryManager.go +++ b/memoryManager.go @@ -1,6 +1,7 @@ package apple2 import ( + "encoding/binary" "io" ) @@ -14,9 +15,13 @@ type memoryManager struct { activeMemoryWrite [256]memoryHandler // Pages prepared to be paged in and out - physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb - physicalROM *memoryRange // 0xd000 to 0xffff, 12 Kb - physicalROMe *memoryRange // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e + physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb + physicalROM memoryHandler // 0xd000 to 0xffff, 12 Kb + physicalROMe memoryHandler // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e + + // Pages prapared for optional card ROM banks + activeSlot uint8 + cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. for each card } type memoryHandler interface { @@ -28,14 +33,28 @@ const ( ioC8Off uint16 = 0xCFFF ) -// Peek returns the data on the given address -func (mmu *memoryManager) Peek(address uint16) uint8 { +func (mmu *memoryManager) access(address uint16, activeMemory [256]memoryHandler) memoryHandler { if address == ioC8Off { mmu.resetSlotExpansionRoms() } hi := uint8(address >> 8) - mh := mmu.activeMemoryRead[hi] + if hi >= 0xC1 && hi <= 0xC7 { + slot := hi - 0xC0 + if slot != mmu.activeSlot { + mmu.activateCardRomExtra(slot) + } + } + mh := activeMemory[hi] + if mh == nil { + return nil + } + return mh +} + +// Peek returns the data on the given address +func (mmu *memoryManager) Peek(address uint16) uint8 { + mh := mmu.access(address, mmu.activeMemoryRead) if mh == nil { return 0xf4 // Or some random number } @@ -44,11 +63,7 @@ func (mmu *memoryManager) Peek(address uint16) uint8 { // Poke sets the data at the given address func (mmu *memoryManager) Poke(address uint16, value uint8) { - if address == ioC8Off { - mmu.resetSlotExpansionRoms() - } - hi := uint8(address >> 8) - mh := mmu.activeMemoryWrite[hi] + mh := mmu.access(address, mmu.activeMemoryWrite) if mh == nil { return } @@ -82,15 +97,29 @@ func (mmu *memoryManager) setPagesWrite(begin uint8, end uint8, mh memoryHandler } } +func (mmu *memoryManager) prepareCardExtraRom(slot int, mh memoryHandler) { + mmu.cardsROMExtra[slot] = mh +} + // When 0xcfff is accessed the card expansion rom is unassigned func (mmu *memoryManager) resetSlotExpansionRoms() { if mmu.apple2.io.isSoftSwitchActive(ioFlagIntCxRom) { // Ignore if the Apple2 shadow ROM is active return } + mmu.activeSlot = 0 mmu.setPagesRead(0xc8, 0xcf, nil) } +// When a card base ROM is accesed the extra rom is assigned if available +func (mmu *memoryManager) activateCardRomExtra(slot uint8) { + //fmt.Printf("Activate slot %v\n", slot) + if mmu.cardsROMExtra[slot] != nil { + mmu.setPagesRead(0xC8, 0xCF, mmu.cardsROMExtra[slot]) + } + mmu.activeSlot = slot +} + func (mmu *memoryManager) resetRomPaging() { // Assign the first 12kb of ROM from 0xd000 to 0xffff mmu.setPagesRead(0xd0, 0xff, mmu.physicalROM) @@ -114,9 +143,14 @@ func newMemoryManager(a *Apple2) *memoryManager { func (mmu *memoryManager) save(w io.Writer) { mmu.physicalMainRAM.save(w) + binary.Write(w, binary.BigEndian, mmu.activeSlot) + } func (mmu *memoryManager) load(r io.Reader) { mmu.physicalMainRAM.load(r) + binary.Read(r, binary.BigEndian, &mmu.activeSlot) + mmu.activateCardRomExtra(mmu.activeSlot) + mmu.resetBaseRamPaging() } diff --git a/microPD1990ac.go b/microPD1990ac.go new file mode 100644 index 0000000..0e9f733 --- /dev/null +++ b/microPD1990ac.go @@ -0,0 +1,119 @@ +package apple2 + +import ( + "fmt" + "time" +) + +/* + microPD1990ac Serial I/O Calendar Clock IC + See: + https://www.semiee.com/file/backup/NEC-D1990.pdf + + Used by the ThunderClock+ real time clock card. + + The 40 bit register has 5 bytes (10 nibbles): + byte 4: + month, binary from 1 to 12 + day of week, BCD 0 to 6 + byte 3: day of month, BCD 1 to 31 + byte 2: hour, BCD 0 to 23 + byte 1: minute, BCD 0 to 59 + byte 0: seconds, BCD 0 to 59 + +*/ + +type microPD1990ac struct { + clock bool // CLK state + strobe bool // STB state + command uint8 // C0, C1, C2 command. From 0 to 7 + register uint64 // 40 bit shift register +} + +const ( + mpd1990commandRegHold = 0 + mpd1990commandRegShift = 1 + mpd1990commandTimeSet = 2 + mpd1990commandTimeRead = 3 +) + +func (m *microPD1990ac) in(clock bool, strobe bool, command uint8, dataIn bool) { + // Detect signal raise + clockRaise := clock && !m.clock + strobeRaise := strobe && !m.strobe + + // Update signal status + m.clock = clock + m.strobe = strobe + + // On strobe raise, update command and execute if needed + if strobeRaise { + m.command = command + + switch m.command { + case mpd1990commandRegShift: + // Nothing to do + case mpd1990commandTimeRead: + m.loadTime() + default: + panic(fmt.Sprintf("PD1990ac command %v not implemented.", m.command)) + } + } + + // On clock raise, with shift enable, shift the register + if clockRaise && m.command == mpd1990commandRegShift { + // Rotate right the 40 bits of the shift register + lsb := m.register & 1 + m.register >>= 1 + m.register += lsb << 39 + } +} + +func (m *microPD1990ac) out() bool { + if m.command == mpd1990commandRegHold { + panic("Output on RegHold should be a 1Hz signal. Not implemented.") + } + + if m.command == mpd1990commandTimeRead { + panic("Output on RegHold should be a 512Hz signal with LSB. Not implemented.") + } + + // Return the LSB of the register shift + return (m.register & 1) == 1 +} + +func (m *microPD1990ac) loadTime() { + now := time.Now() + + var register uint64 + + register = uint64(now.Month()) + register <<= 4 + register += uint64(now.Weekday()) + + day := uint64(now.Day()) + register <<= 4 + register += day / 10 + register <<= 4 + register += day % 10 + + hour := uint64(now.Hour()) + register <<= 4 + register += hour / 10 + register <<= 4 + register += hour % 10 + + minute := uint64(now.Minute()) + register <<= 4 + register += minute / 10 + register <<= 4 + register += minute % 10 + + second := uint64(now.Second()) + register <<= 4 + register += second / 10 + register <<= 4 + register += second % 10 + + m.register = register +} diff --git a/romdumps/generate/generate.go b/romdumps/generate/generate.go index b935d84..9a98dae 100644 --- a/romdumps/generate/generate.go +++ b/romdumps/generate/generate.go @@ -1,4 +1,4 @@ -// To generate the resources put the files on a "files" subdirectory and run main +// To generate the resources put the files on a "files" subdirectory and run "go run generate.go" package main diff --git a/romdumps/romdumps_vfsdata.go b/romdumps/romdumps_vfsdata.go index 09c736a..492a9ae 100644 --- a/romdumps/romdumps_vfsdata.go +++ b/romdumps/romdumps_vfsdata.go @@ -19,7 +19,7 @@ var Assets = func() http.FileSystem { fs := vfsgen۰FS{ "/": &vfsgen۰DirInfo{ name: "/", - modTime: time.Date(2019, 6, 9, 16, 41, 30, 66545749, time.UTC), + modTime: time.Date(2019, 9, 24, 22, 1, 11, 324155917, time.UTC), }, "/Apple2_Plus.rom": &vfsgen۰CompressedFileInfo{ name: "Apple2_Plus.rom", @@ -91,6 +91,13 @@ var Assets = func() http.FileSystem { compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5a\xa4\xb0\x80\x61\x11\x73\x9b\x4d\x17\x97\x8a\xcd\x07\x01\x56\x1b\xcf\xff\x9a\x75\x1b\x38\xbc\x2e\xfc\x9e\x31\x37\x8c\xf9\xc4\x0b\x81\xa7\x0a\x11\xff\x77\xed\x65\x60\xe4\xe2\xe2\xe2\x6a\xd5\x5e\xb5\xb7\xef\xc0\xde\x9e\x03\x7b\xbb\x0e\xec\xed\x3c\xb0\x20\x60\x6f\xc3\x81\x19\x9a\xcc\x5c\xac\xda\xab\xf6\x36\x1e\x58\x19\xa6\xb0\xe2\x4f\x87\xc0\xeb\x56\xb5\x56\xdb\x56\xc7\x95\x1c\xad\xea\x12\x1c\x7b\x7b\x0e\x08\xfc\xf6\xbc\x7a\xe1\x3b\x98\x71\x72\xd5\x85\xcf\xaf\x20\xac\x69\x1f\x38\x35\x26\xdc\xf7\x5c\xfb\x41\xf5\xc2\xcd\x05\xcc\xad\x0e\x60\x51\xad\x56\x1b\x30\xad\x6a\xd3\x71\xe1\x8d\xc6\x51\xdb\x0b\xfb\x96\x3a\x1c\x75\xbc\xb0\x63\xc3\xf6\x05\x61\x2d\x36\x7b\x40\x52\x91\xd7\x98\x96\xd8\x74\xcc\x64\x60\xbe\xf0\x0e\x59\x64\xa2\xda\x89\x0b\xef\x61\xdc\x0b\xed\x0b\x18\x16\x85\x9d\x32\xf8\xbd\x51\x2d\x8e\x81\x59\x0b\x84\x41\xf2\xef\x9e\xa9\x3f\xb3\x5d\x6a\x7b\x96\x81\x63\x99\xf6\x84\xdb\x3e\x8c\x1c\x0c\x20\x00\x08\x00\x00\xff\xff\xf6\x44\x71\xce\x00\x01\x00\x00"), }, + "/ThunderclockPlusROM.bin": &vfsgen۰CompressedFileInfo{ + name: "ThunderclockPlusROM.bin", + modTime: time.Date(2019, 9, 24, 21, 55, 17, 674069636, time.UTC), + uncompressedSize: 2048, + + 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"), + }, "/dos33.dsk": &vfsgen۰CompressedFileInfo{ name: "dos33.dsk", modTime: time.Date(2019, 6, 7, 16, 50, 59, 550488426, time.UTC), @@ -110,6 +117,7 @@ var Assets = func() http.FileSystem { fs["/BASE64A_F8.BIN"].(os.FileInfo), fs["/BASE64A_ROM7_CharGen.BIN"].(os.FileInfo), fs["/DISK2.rom"].(os.FileInfo), + fs["/ThunderclockPlusROM.bin"].(os.FileInfo), fs["/dos33.dsk"].(os.FileInfo), } @@ -134,7 +142,7 @@ func (fs vfsgen۰FS) Open(path string) (http.File, error) { } return &vfsgen۰CompressedFile{ vfsgen۰CompressedFileInfo: f, - gr: gr, + gr: gr, }, nil case *vfsgen۰DirInfo: return &vfsgen۰Dir{