diff --git a/.gitignore b/.gitignore index 05cf7a3..6378da1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ test-cpu cpu.prof mem.prof mos6502go.test +apple2e.rom diff --git a/cmd/test-apple-iie-boot.go b/cmd/test-apple-iie-boot.go new file mode 100644 index 0000000..5a4700d --- /dev/null +++ b/cmd/test-apple-iie-boot.go @@ -0,0 +1,28 @@ +package main + +import ( + "flag" + "mos6502go/cpu" + "mos6502go/mmu" +) + +func main() { + showInstructions := flag.Bool("show-instructions", false, "Show instructions code while running") + disableBell := flag.Bool("disable-bell", false, "Disable bell") + flag.Parse() + + cpu.InitDisasm() + memory := mmu.InitRAM() + mmu.InitApple2eROM(memory) + + var s cpu.State + s.Memory = memory + s.MemoryMap = &memory.MemoryMap + s.Init() + + bootVector := 0xfffc + lsb := (*s.MemoryMap)[uint8(bootVector>>8)][uint8(bootVector&0xff)] // TODO move readMemory to mmu + msb := (*s.MemoryMap)[uint8((bootVector+1)>>8)][uint8((bootVector+1)&0xff)] + s.PC = uint16(lsb) + uint16(msb)<<8 + cpu.Run(&s, *showInstructions, nil, *disableBell, 0) +} diff --git a/cmd/test-cpu.go b/cmd/test-cpu.go index 7e5515f..d06d3b5 100644 --- a/cmd/test-cpu.go +++ b/cmd/test-cpu.go @@ -5,18 +5,20 @@ import ( "flag" "fmt" "mos6502go/cpu" + "mos6502go/mmu" "mos6502go/utils" ) func main() { - cpu.InitDisasm() - showInstructions := flag.Bool("show-instructions", false, "Show instructions code while running") skipTest0 := flag.Bool("skip-functional-test", false, "Skip functional test") skipTest1 := flag.Bool("skip-interrupt-test", false, "Skip interrupt test") breakAddressString := flag.String("break", "", "Break on address") flag.Parse() + cpu.InitDisasm() + memory := mmu.InitRAM() + var Roms = []string{ "6502_functional_test.bin.gz", "6502_interrupt_test.bin.gz", @@ -32,8 +34,10 @@ func main() { } fmt.Printf("Running %s\n", rom) + var s cpu.State s.Init() + s.PC = 0x800 cpu.RunningTests = true if i == 0 { @@ -44,15 +48,28 @@ func main() { cpu.RunningInterruptTests = true } - bytes, err := utils.ReadMemoryFromFile(rom) + bytes, err := utils.ReadMemoryFromGzipFile(rom) if err != nil { panic(err) } - for i := 0; i < len(bytes); i++ { - s.Memory[i] = bytes[i] + // Copy main RAM area 0x0000-0xbfff + for i := 0; i < 0xc000; i++ { + memory.PhysicalMemory.MainMemory[i] = bytes[i] } + // Map writable RAM area in 0xc000-0xffff + var RomPretendingToBeRAM [0x4000]uint8 + for i := 0x0; i < 0x4000; i++ { + RomPretendingToBeRAM[i] = bytes[0xc000+i] + } + for i := 0x0; i < 0x40; i++ { + memory.MemoryMap[0xc0+uint8(i)] = RomPretendingToBeRAM[i*0x100 : i*0x100+0x100] + } + + s.Memory = memory + s.MemoryMap = &memory.MemoryMap + var breakAddress *uint16 if *breakAddressString != "" { breakAddressValue, err := hex.DecodeString(*breakAddressString) @@ -71,7 +88,7 @@ func main() { breakAddress = &foo } - cpu.Run(&s, *showInstructions, breakAddress) + cpu.Run(&s, *showInstructions, breakAddress, false, 0) fmt.Printf("Finished running %s\n\n", rom) } } diff --git a/cpu/cpu.go b/cpu/cpu.go index 6d4eef4..6d79b40 100644 --- a/cpu/cpu.go +++ b/cpu/cpu.go @@ -2,6 +2,7 @@ package cpu import ( "fmt" + "mos6502go/mmu" "os" ) @@ -23,7 +24,8 @@ var ( ) type State struct { - Memory [0x10000]uint8 + Memory *mmu.Memory + MemoryMap *mmu.MemoryMap // For easy access, this is a shortcut for Memory.MemoryMap pendingInterrupt bool pendingNMI bool A uint8 @@ -43,7 +45,6 @@ func (s *State) Init() { s.X = 0 s.Y = 0 s.P = CpuFlagR | CpuFlagB | CpuFlagZ - s.PC = 0x800 s.SP = 0xff s.pendingInterrupt = false s.pendingNMI = false @@ -102,14 +103,14 @@ func (s *State) isN() bool { } func push8(s *State, value uint8) { - s.Memory[0x100+uint16(s.SP)] = value + (*s.MemoryMap)[mmu.StackPage][s.SP] = value s.SP -= 1 s.SP &= 0xff } func push16(s *State, value uint16) { - s.Memory[0x100+uint16(s.SP)] = uint8(value >> 8) - s.Memory[0xff+uint16(s.SP)] = uint8(value & 0xff) + (*s.MemoryMap)[mmu.StackPage][s.SP] = uint8(value >> 8) + (*s.MemoryMap)[mmu.StackPage][s.SP-1] = uint8(value & 0xff) s.SP -= 2 s.SP &= 0xff } @@ -117,24 +118,29 @@ func push16(s *State, value uint16) { func pop8(s *State) uint8 { s.SP += 1 s.SP &= 0xff - return s.Memory[0x100+uint16(s.SP)] + return (*s.MemoryMap)[mmu.StackPage][s.SP] } func pop16(s *State) uint16 { s.SP += 2 s.SP &= 0xff - lsb := uint16(s.Memory[0xff+uint16(s.SP)]) - msb := uint16(s.Memory[0x100+uint16(s.SP)]) + msb := uint16((*s.MemoryMap)[mmu.StackPage][s.SP]) + lsb := uint16((*s.MemoryMap)[mmu.StackPage][s.SP-1]) return lsb + msb<<8 } func readMemory(s *State, address uint16) uint8 { - return s.Memory[address] + if (address >= 0xc000) && (address < 0xc100) { + fmt.Printf("TODO read %04x\n", address) + return 0 + } + + return (*s.MemoryMap)[uint8(address>>8)][uint8(address&0xff)] } // Handle a write to a magic test address that triggers an interrupt and/or an NMI func writeInterruptTestOpenCollector(s *State, address uint16, value uint8) { - oldValue := s.Memory[address] + oldValue := readMemory(s, address) oldInterrupt := (oldValue & 0x1) == 0x1 oldNMI := (oldValue & 0x2) == 0x2 @@ -150,7 +156,7 @@ func writeInterruptTestOpenCollector(s *State, address uint16, value uint8) { s.pendingNMI = NMI } - s.Memory[address] = value + (*s.MemoryMap)[uint8(address>>8)][uint8(address&0xff)] = value } func writeMemory(s *State, address uint16, value uint8) { @@ -159,20 +165,35 @@ func writeMemory(s *State, address uint16, value uint8) { return } - s.Memory[address] = value + if address >= 0xc000 { + if address == mmu.CLRCXROM { + mmu.MapFirstHalfOfIO(s.Memory) + } else if address == mmu.SETCXROM { + mmu.MapSecondHalfOfIO(s.Memory) + } else { + fmt.Printf("TODO write %04x\n", address) + } + return + } + + if address >= 0x400 && address < 0x800 { + fmt.Printf("Text page write %04x: %02x\n", address, value) + } + + (*s.MemoryMap)[uint8(address>>8)][uint8(address&0xff)] = value if RunningFunctionalTests && address == 0x200 { - testNumber := s.Memory[0x200] + testNumber := readMemory(s, 0x200) if testNumber == 0xf0 { fmt.Println("Opcode testing completed") } else { - fmt.Printf("Test %d OK\n", s.Memory[0x200]) + fmt.Printf("Test %d OK\n", readMemory(s, 0x200)) } } } func branch(s *State, cycles *int, instructionName string, doBranch bool) { - value := s.Memory[s.PC+1] + value := readMemory(s, s.PC+1) var relativeAddress uint16 if (value & 0x80) == 0 { @@ -203,28 +224,28 @@ func branch(s *State, cycles *int, instructionName string, doBranch bool) { func getAddressFromAddressMode(s *State, addressMode byte) (result uint16, pageBoundaryCrossed bool) { switch addressMode { case AmZeroPage: - result = uint16(s.Memory[s.PC+1]) + result = uint16(readMemory(s, s.PC+1)) case AmZeroPageX: - result = (uint16(s.Memory[s.PC+1]) + uint16(s.X)) & 0xff + result = (uint16(readMemory(s, s.PC+1)) + uint16(s.X)) & 0xff case AmZeroPageY: - result = (uint16(s.Memory[s.PC+1]) + uint16(s.Y)) & 0xff + result = (uint16(readMemory(s, s.PC+1)) + uint16(s.Y)) & 0xff case AmAbsolute: - result = uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 + result = uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 case AmAbsoluteX: - value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 + value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 pageBoundaryCrossed = (value & 0xff00) != ((value + uint16(s.X)) & 0xff00) result = value + uint16(s.X) case AmAbsoluteY: - value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 + value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 pageBoundaryCrossed = (value & 0xff00) != ((value + uint16(s.Y)) & 0xff00) result = value + uint16(s.Y) case AmIndirectX: - zeroPageAddress := (s.Memory[s.PC+1] + s.X) & 0xff - result = uint16(s.Memory[zeroPageAddress]) + uint16(s.Memory[zeroPageAddress+1])<<8 + zeroPageAddress := (readMemory(s, s.PC+1) + s.X) & 0xff + result = uint16(readMemory(s, uint16(zeroPageAddress))) + uint16(readMemory(s, uint16(zeroPageAddress)+1))<<8 case AmIndirectY: - address := s.Memory[s.PC+1] - lsb := uint16(s.Memory[address]) - msb := uint16(s.Memory[address+1]) + address := uint16(readMemory(s, s.PC+1)) + lsb := uint16(readMemory(s, address)) + msb := uint16(readMemory(s, address+1)) value := lsb + msb<<8 pageBoundaryCrossed = (value & 0xff00) != ((value + uint16(s.Y)) & 0xff00) result = value + uint16(s.Y) @@ -238,47 +259,47 @@ func getAddressFromAddressMode(s *State, addressMode byte) (result uint16, pageB func readMemoryWithAddressMode(s *State, addressMode byte) (result uint8, pageBoundaryCrossed bool) { switch addressMode { case AmImmediate: - result = s.Memory[s.PC+1] + result = readMemory(s, s.PC+1) s.PC += 2 case AmZeroPage: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 2 case AmZeroPageX: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 2 case AmZeroPageY: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 2 case AmAbsolute: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 3 case AmAbsoluteX: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 3 case AmAbsoluteY: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 3 case AmIndirectX: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 2 case AmIndirectY: var address uint16 address, pageBoundaryCrossed = getAddressFromAddressMode(s, addressMode) - result = s.Memory[address] + result = readMemory(s, address) s.PC += 2 default: result = 0 @@ -482,7 +503,7 @@ func sbc(s *State, cycles *int, addressMode byte) { } func bit(s *State, address uint16) { - value := s.Memory[address] + value := readMemory(s, address) s.setN(value) s.setV((value & 0x40) != 0) s.setZ(value & s.A) @@ -494,14 +515,14 @@ func preProcessShift(s *State, cycles *int, addressMode byte) (address uint16, v value = s.A } else { address, _ = getAddressFromAddressMode(s, addressMode) - value = s.Memory[address] + value = readMemory(s, address) } if addressMode == AmAccumulator { value = s.A } else { address, _ = getAddressFromAddressMode(s, addressMode) - value = s.Memory[address] + value = readMemory(s, address) } return @@ -559,7 +580,7 @@ func brk(s *State, cycles *int) { s.P |= CpuFlagB push8(s, s.P) s.P |= CpuFlagI - s.PC = uint16(s.Memory[0xffff])<<8 + uint16(s.Memory[0xfffe]) + s.PC = uint16(readMemory(s, 0xffff))<<8 + uint16(readMemory(s, 0xfffe)) *cycles += 7 } @@ -568,7 +589,7 @@ func irq(s *State, cycles *int) { s.P &= ^CpuFlagB push8(s, s.P) s.P |= CpuFlagI - s.PC = uint16(s.Memory[0xffff])<<8 + uint16(s.Memory[0xfffe]) + s.PC = uint16(readMemory(s, 0xffff))<<8 + uint16(readMemory(s, 0xfffe)) *cycles += 7 } @@ -577,14 +598,18 @@ func nmi(s *State, cycles *int) { s.P &= ^CpuFlagB push8(s, s.P) s.P |= CpuFlagI - s.PC = uint16(s.Memory[0xfffb])<<8 + uint16(s.Memory[0xfffa]) + s.PC = uint16(readMemory(s, 0xfffb))<<8 + uint16(readMemory(s, 0xfffa)) *cycles += 7 } -func Run(s *State, showInstructions bool, breakAddress *uint16) { +func Run(s *State, showInstructions bool, breakAddress *uint16, disableBell bool, wantedCycles int) { cycles := 0 for { + if (wantedCycles != 0) && (cycles >= wantedCycles) { + return + } + if RunningTests && (s.PC == 0x3869) { fmt.Println("Functional tests passed") return @@ -611,7 +636,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) { PrintInstruction(s) } - opcode := s.Memory[s.PC] + opcode := readMemory(s, s.PC) addressMode := OpCodes[opcode].AddressingMode.Mode if breakAddress != nil && s.PC == *breakAddress { @@ -622,7 +647,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) { switch opcode { case 0x4c: // JMP $0000 - value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 + value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 if RunningTests && s.PC == value { fmt.Printf("Trap at $%04x\n", value) os.Exit(0) @@ -630,15 +655,21 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) { s.PC = value cycles += 3 case 0x6c: // JMP ($0000) - value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 - s.PC = uint16(s.Memory[value]) + uint16(s.Memory[value+1])<<8 + value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 + s.PC = uint16(readMemory(s, value)) + uint16(readMemory(s, value+1))<<8 cycles += 5 case 0x20: // JSR $0000 - value := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 + value := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 + cycles += 6 + + if disableBell && value == 0xfca8 { + s.PC += 3 + continue + } + push16(s, s.PC+2) s.PC = value - cycles += 6 case 0x60: // RTS value := pop16(s) @@ -819,12 +850,12 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) { cycles += 6 case 0x24: // BIT $00 - address := s.Memory[s.PC+1] + address := readMemory(s, s.PC+1) bit(s, uint16(address)) s.PC += 2 cycles += 3 case 0x2C: // BIT $0000 - address := uint16(s.Memory[s.PC+1]) + uint16(s.Memory[s.PC+2])<<8 + address := uint16(readMemory(s, s.PC+1)) + uint16(readMemory(s, s.PC+2))<<8 bit(s, address) s.PC += 3 cycles += 4 @@ -869,7 +900,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) { case 0xe6, 0xf6, 0xee, 0xfe: // INC address, _ := getAddressFromAddressMode(s, addressMode) - value := s.Memory[address] + value := readMemory(s, address) value = (value + 1) & 0xff s.setZ(value) s.setN(value) @@ -878,7 +909,7 @@ func Run(s *State, showInstructions bool, breakAddress *uint16) { case 0xc6, 0xd6, 0xce, 0xde: // DEC address, _ := getAddressFromAddressMode(s, addressMode) - value := s.Memory[address] + value := readMemory(s, address) value = (value - 1) & 0xff s.setZ(value) s.setN(value) diff --git a/cpu/disasm.go b/cpu/disasm.go index 624ed5c..951e993 100644 --- a/cpu/disasm.go +++ b/cpu/disasm.go @@ -37,7 +37,7 @@ func printInstruction(s *State, instruction string) { } func PrintInstruction(s *State) { - opcodeValue := s.Memory[s.PC] + opcodeValue := (*s.MemoryMap)[uint8((s.PC)>>8)][uint8((s.PC)&0xff)] opcode := OpCodes[opcodeValue] mnemonic := opcode.Mnemonic size := opcode.AddressingMode.OperandSize @@ -53,7 +53,7 @@ func PrintInstruction(s *State) { var suffix string if opcode.AddressingMode.Mode == AmRelative { - value = uint16(s.Memory[s.PC+1]) + value = uint16((*s.MemoryMap)[uint8((s.PC+1)>>8)][uint8((s.PC+1)&0xff)]) var relativeAddress uint16 if (value & 0x80) == 0 { relativeAddress = s.PC + 2 + uint16(value) @@ -64,15 +64,15 @@ func PrintInstruction(s *State) { suffix = fmt.Sprintf(stringFormat, relativeAddress) opcodes = fmt.Sprintf("%02x %02x ", opcodeValue, value) } else if size == 1 { - value = uint16(s.Memory[s.PC+1]) + value = uint16((*s.MemoryMap)[uint8((s.PC+1)>>8)][uint8((s.PC+1)&0xff)]) suffix = fmt.Sprintf(stringFormat, value) opcodes = fmt.Sprintf("%02x %02x ", opcodeValue, value) } else if size == 2 { - lower := s.Memory[s.PC+1] - higher := s.Memory[s.PC+2] - value = uint16(lower) + uint16(higher)*0x100 + lsb := (*s.MemoryMap)[uint8((s.PC+1)>>8)][uint8((s.PC+1)&0xff)] + msb := (*s.MemoryMap)[uint8((s.PC+2)>>8)][uint8((s.PC+2)&0xff)] + value = uint16(lsb) + uint16(msb)*0x100 suffix = fmt.Sprintf(stringFormat, value) - opcodes = fmt.Sprintf("%02x %02x %02x ", opcodeValue, lower, higher) + opcodes = fmt.Sprintf("%02x %02x %02x ", opcodeValue, lsb, msb) } printInstruction(s, fmt.Sprintf("%s %s %s", opcodes, mnemonic, suffix)) @@ -90,7 +90,7 @@ func DumpMemory(s *State, offset uint16) { } fmt.Printf("%04x ", offset+i) } - fmt.Printf(" %02x", s.Memory[offset+i]) + fmt.Printf(" %02x", (*s.MemoryMap)[uint8((offset+i)>>8)][uint8((offset+i)&0xff)]) } fmt.Print("\n") } diff --git a/mmu/mmu.go b/mmu/mmu.go new file mode 100644 index 0000000..9864cae --- /dev/null +++ b/mmu/mmu.go @@ -0,0 +1,101 @@ +package mmu + +import ( + "fmt" + "io/ioutil" +) + +const RomPath = "apple2e.rom" + +const StackPage = 1 + +// https://mirrors.apple2.org.za/apple.cabi.net/Languages.Programming/MemoryMap.IIe.64K.128K.txt + +const ( + KEYBOARD = 0xC000 // keyboard data (latched) (RD-only) + CLR80COL = 0xC000 // use 80-column memory mapping (WR-only) + SET80COL = 0xC001 + CLRAUXRD = 0xC002 // read from auxilliary 48K + SETAUXRD = 0xC003 + CLRAUXWR = 0xC004 // write to auxilliary 48K + SETAUXWR = 0xC005 + CLRCXROM = 0xC006 // use external slot ROM + SETCXROM = 0xC007 + CLRAUXZP = 0xC008 // use auxilliary ZP, stack, & LC + SETAUXZP = 0xC009 + CLRC3ROM = 0xC00A // use external slot C3 ROM + SETC3ROM = 0xC00B + CLR80VID = 0xC00C // use 80-column display mode + SET80VID = 0xC00D + CLRALTCH = 0xC00E // use alternate character set ROM + SETALTCH = 0xC00F + STROBE = 0xC010 // strobe (unlatch) keyboard data +) + +type PhysicalMemory struct { + MainMemory [0xc000]uint8 + UpperROM [0x3000]uint8 + RomC1 [0x1000]uint8 + RomC2 [0x1000]uint8 +} + +type MemoryMap map[uint8][]uint8 + +type Memory struct { + MemoryMap MemoryMap + PhysicalMemory PhysicalMemory +} + +func MapFirstHalfOfIO(m *Memory) { + for i := 0x1; i < 0x10; i++ { + m.MemoryMap[uint8(i)+0xc0] = m.PhysicalMemory.RomC1[i*0x100 : i*0x100+0x100] + } +} + +func MapSecondHalfOfIO(m *Memory) { + for i := 0x1; i < 0x10; i++ { + m.MemoryMap[uint8(i)+0xc0] = m.PhysicalMemory.RomC2[i*0x100 : i*0x100+0x100] + } +} + +func readApple2eROM(m *Memory) { + bytes, err := ioutil.ReadFile(RomPath) + if err != nil { + panic(fmt.Sprintf("Unable to read ROM: %s", err)) + } + + // Copy both I/O areas over c000-cfff, including unused c000-c0ff + for i := 0x0000; i < 0x1000; i++ { + m.PhysicalMemory.RomC1[i] = bytes[i] + m.PhysicalMemory.RomC2[i] = bytes[i+0x4000] + } + + // Copy ROM over for 0xd000-0xffff area + for i := 0x0; i < 0x3000; i++ { + m.PhysicalMemory.UpperROM[i] = bytes[i+0x1000] + } +} + +func InitApple2eROM(m *Memory) { + readApple2eROM(m) + + // Map 0xc100-0xcfff + MapFirstHalfOfIO(m) + + // Map 0xd000-0xffff + for i := 0x0; i < 0x30; i++ { + m.MemoryMap[uint8(i)+0xd0] = m.PhysicalMemory.UpperROM[i*0x100 : i*0x100+0x100] + } +} + +func InitRAM() (memory *Memory) { + memory = new(Memory) + memory.MemoryMap = make(MemoryMap) + + // Map main RAM + for i := 0x0; i < 0xc0; i++ { + memory.MemoryMap[uint8(i)] = memory.PhysicalMemory.MainMemory[i*0x100 : i*0x100+0x100] + } + + return +} diff --git a/utils/utils.go b/utils/utils.go index abe3ea3..71dd8d7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,7 +6,7 @@ import ( "os" ) -func ReadMemoryFromFile(filename string) (data []byte, err error) { +func ReadMemoryFromGzipFile(filename string) (data []byte, err error) { f, err := os.Open(filename) if err != nil { return nil, err