Skip to content

Commit

Permalink
Added feature to store save files in JSON format rather than binary (…
Browse files Browse the repository at this point in the history
…with compression) - this might be useful if I ever consider implementing #19
  • Loading branch information
djhworld committed May 19, 2013
1 parent e1e6e88 commit 23d175a
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 100 deletions.
7 changes: 7 additions & 0 deletions settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Title":"gomeboycolor",
"ScreenSize":2,
"SkipBoot":true,
"SavesDir":"./saves",
"DisplayFPS":false
}
71 changes: 3 additions & 68 deletions src/cartridge/MBC.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
package cartridge

import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
"types"
)
import "types"

type MemoryBankController interface {
Write(addr types.Word, value byte)
Read(addr types.Word) byte
SaveRam(filename string) error
LoadRam(filename string) error
SaveRam(savesDir string, game string) error
LoadRam(savesDir string, game string) error
switchROMBank(bank int)
switchRAMBank(bank int)
}
Expand Down Expand Up @@ -41,61 +34,3 @@ func populateRAMBanks(noOfBanks int) [][]byte {

return ramBanks
}

func WriteRAMToDisk(path string, ramBanks [][]byte) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()

writer := bufio.NewWriter(file)
for i := 0; i < len(ramBanks); i++ {
writer.Write(ramBanks[i])
/*
var b bytes.Buffer
w := zlib.NewWriter(&b)
w.Write(ramBanks[i])
w.Close()
before := base64.StdEncoding.EncodeToString(b.Bytes())
fmt.Println("Before = ", len(b.Bytes()))
after,_ := base64.StdEncoding.DecodeString(before)
b = *bytes.NewBuffer(after)
r, err := zlib.NewReader(&b)
if err != nil {
panic(fmt.Sprintln("Error" , err))
}
out := bytes.NewBuffer(make([]byte,0))
io.Copy(out, r)
fmt.Println("After = ", len(out.Bytes()))
fmt.Println(bytes.Compare(ramBanks[i], out.Bytes()))
r.Close()
*/

}
writer.Flush()
return nil
}

func ReadRAMFromDisk(path string, chunkSize int, expectedSize int) ([][]byte, error) {
fileBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}

if len(fileBytes) != expectedSize {
return nil, errors.New(fmt.Sprintf("RAM file is not %d bytes!", expectedSize))
}

var chunk int = 0x0000
var noOfBanks int = expectedSize / chunkSize
var ramBanks [][]byte = make([][]byte, noOfBanks)

for i := 0; i < noOfBanks; i++ {
ramBanks[i] = fileBytes[chunk : chunk+chunkSize]
chunk += chunkSize
}

return ramBanks, nil
}
4 changes: 2 additions & 2 deletions src/cartridge/MBC0.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ func (m *MBC0) switchRAMBank(bank int) {
// not needed for MBC0
}

func (m *MBC0) SaveRam(filename string) error {
func (m *MBC0) SaveRam(savesDir string, game string) error {
return nil
}

func (m *MBC0) LoadRam(filename string) error {
func (m *MBC0) LoadRam(savesDir string, game string) error {
return nil
}
23 changes: 10 additions & 13 deletions src/cartridge/MBC1.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,28 +134,25 @@ func (m *MBC1) switchRAMBank(bank int) {
m.selectedRAMBank = bank
}

func (m *MBC1) SaveRam(filename string) error {
func (m *MBC1) SaveRam(savesDir string, game string) error {
if m.hasRAM && m.hasBattery {
log.Println(m.Name+":", "Saving RAM to", filename)
err := WriteRAMToDisk(filename, m.ramBanks)
if err != nil {
return err
}
s := NewSaveFile(savesDir, game)
err := s.Save(m.ramBanks)
s = nil
return err
}

return nil
}

func (m *MBC1) LoadRam(filename string) error {
func (m *MBC1) LoadRam(savesDir string, game string) error {
if m.hasRAM && m.hasBattery {
log.Println(m.Name+":", "Loading RAM from", filename)
ramBanks, err := ReadRAMFromDisk(filename, 0x2000, 0x8000)
s := NewSaveFile(savesDir, game)
banks, err := s.Load(4)
if err != nil {
return err
}

m.ramBanks = ramBanks
m.ramBanks = banks
s = nil
}

return nil
}
23 changes: 10 additions & 13 deletions src/cartridge/MBC3.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,28 +115,25 @@ func (m *MBC3) switchRAMBank(bank int) {
m.selectedRAMBank = bank
}

func (m *MBC3) SaveRam(filename string) error {
func (m *MBC3) SaveRam(savesDir string, game string) error {
if m.hasRAM && m.hasBattery {
log.Println(m.Name+":", "Saving RAM to", filename)
err := WriteRAMToDisk(filename, m.ramBanks)
if err != nil {
return err
}
s := NewSaveFile(savesDir, game)
err := s.Save(m.ramBanks)
s = nil
return err
}

return nil
}

func (m *MBC3) LoadRam(filename string) error {
func (m *MBC3) LoadRam(savesDir string, game string) error {
if m.hasRAM && m.hasBattery {
log.Println(m.Name+":", "Loading RAM from", filename)
ramBanks, err := ReadRAMFromDisk(filename, 0x2000, 0x8000)
s := NewSaveFile(savesDir, game)
banks, err := s.Load(4)
if err != nil {
return err
}

m.ramBanks = ramBanks
m.ramBanks = banks
s = nil
}

return nil
}
160 changes: 160 additions & 0 deletions src/cartridge/SaveFile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package cartridge

import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"log"
"path/filepath"
"time"
)

type SaveFile struct {
Game string
Path string
NoOfBanks int
Banks []string
BankHashes []uint32
LastSaved string
}

func NewSaveFile(savesDir string, game string) *SaveFile {
var s *SaveFile = new(SaveFile)
s.Game = game
s.Path = filepath.Join(savesDir, game) + ".sav"
return s
}

func (s *SaveFile) Validate() error {
if s.NoOfBanks != len(s.Banks) {
return errors.New(fmt.Sprintf("No. of banks does (%d) NOT match number of actual banks (%d)", s.NoOfBanks, s.Banks))
}

return nil
}

//Takes a byte array, converts it to a base64 string and compresses it using ZLIB.
func (s *SaveFile) DeflateBank(bank []byte) (string, error) {
var outBuffer bytes.Buffer
zl := zlib.NewWriter(&outBuffer)
_, err := zl.Write(bank)
if err != nil {
return "", err
}
zl.Close()

compressedBankStr := base64.StdEncoding.EncodeToString(outBuffer.Bytes())
return compressedBankStr, nil
}

//Takes a base64 string and decompresses it using ZLIB into a byte array
func (s *SaveFile) InflateBank(bankStr string) ([]byte, error) {
compressedBank, err := base64.StdEncoding.DecodeString(bankStr)
if err != nil {
return nil, err
}

var inBuffer *bytes.Buffer = bytes.NewBuffer(compressedBank)
zl, err := zlib.NewReader(inBuffer)
if err != nil {
return nil, err
}

var outBuffer *bytes.Buffer = bytes.NewBuffer(make([]byte, 0))
io.Copy(outBuffer, zl)
zl.Close()

return outBuffer.Bytes(), nil
}

func (s *SaveFile) Load(noOfBanks int) ([][]byte, error) {
log.Println("Loading RAM from", s.Path)
file, err := ioutil.ReadFile(s.Path)
if err != nil {
return nil, err
}

//Now parse into struct
var saveFile SaveFile
err = json.Unmarshal(file, &saveFile)
if err != nil {
return nil, err
}

//ensure save file is valid
if err := saveFile.Validate(); err != nil {
return nil, err
}

s = &saveFile
log.Println("Game was last saved:", s.LastSaved)

if len(s.Banks) != noOfBanks {
return nil, errors.New(fmt.Sprintln("Error: Expected", noOfBanks, "banks but found", len(s.Banks)))
}

s.NoOfBanks = noOfBanks

var result [][]byte = make([][]byte, s.NoOfBanks)
for i, bank := range s.Banks {
log.Println("--> Loading bank", i)

//decompress into byte array
inflatedBank, err := s.InflateBank(bank)
if err != nil {
return nil, errors.New(fmt.Sprintln("Error attempting to parse and decompress bank %d (%v), save file could be corrupted!", i, err))
}

//check to ensure checksum is valid against what we decompressed
hash := crc32.ChecksumIEEE(inflatedBank)
if hash != s.BankHashes[i] {
return nil, errors.New(fmt.Sprintln("Hash error occured, ram save file is corrupted! (inflated bank", i, " does not match hash on disk!)"))
}

result[i] = inflatedBank
}

return result, nil
}

//compresses ram banks and stores as base64 strings.
//hashes are taken each bank
//information is stored on disk in JSON format
func (s *SaveFile) Save(data [][]byte) error {
s.NoOfBanks = len(data)
s.Banks = make([]string, s.NoOfBanks)
s.BankHashes = make([]uint32, s.NoOfBanks)
s.LastSaved = fmt.Sprint(time.Now().Format(time.UnixDate))

log.Println("Saving RAM to", s.Path)
for i, bank := range data {
//take crc32 hash of bank
s.BankHashes[i] = crc32.ChecksumIEEE(bank)

//compress
bankStr, err := s.DeflateBank(bank)
if err != nil {
return errors.New(fmt.Sprintln("Error attempting to compress bank %d (%v)", i, err))
}

log.Printf("--> Storing bank %d (Compression ratio: %.1f%%)", i, 100.00-((float32(len(bankStr))/float32(len(bank)))*100))
s.Banks[i] = bankStr
}

//serialize to JSON
js, err := json.Marshal(&s)
if err != nil {
return errors.New(fmt.Sprintln("Error attempting to parse into JSON", err))
}

log.Println("Save file", s.Path, "size is", len(js), "bytes")

//write to disk
return ioutil.WriteFile(s.Path, js, 0755)
}
6 changes: 2 additions & 4 deletions src/cartridge/cartridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,11 @@ func (c *Cartridge) Init(rom []byte) error {
}

func (c *Cartridge) SaveRam(savesDir string) error {
base := filepath.Base(c.Filename) + ".ramsave"
return c.MBC.SaveRam(filepath.Join(savesDir, base))
return c.MBC.SaveRam(savesDir, filepath.Base(c.Filename))
}

func (c *Cartridge) LoadRam(savesDir string) error {
base := filepath.Base(c.Filename) + ".ramsave"
return c.MBC.LoadRam(filepath.Join(savesDir, base))
return c.MBC.LoadRam(savesDir, filepath.Base(c.Filename))
}

func (c *Cartridge) String() string {
Expand Down

0 comments on commit 23d175a

Please sign in to comment.