Support the ThunderClock Plus card. Partial mulation of the microPD1990AC integrated circuit.

This commit is contained in:
Ivan Izaguirre 2019-09-28 13:37:42 +02:00
parent 3d21387d47
commit 00e4476e86
10 changed files with 261 additions and 24 deletions

View File

@ -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 "<internal>/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

View File

@ -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)

View File

@ -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, "<internal>/ThunderclockPlusROM.bin")
}
if *disk2Slot >= 0 {
a.AddDisk2(*disk2Slot, *disk2RomFile, *diskImage)
}

BIN
apple2sdl/apple2.state Normal file

Binary file not shown.

View File

@ -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])

49
cardThunderClockPlus.go Normal file
View File

@ -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)
}

View File

@ -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()
}

119
microPD1990ac.go Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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{