Ejemplo n.º 1
0
// Import GoNES XML cheat database
// TODO: read Nestopia's NstCheat files, like those found on http://www.mightymo.net/downloads.html
// TODO: read Nesticle .pat files, [+]<code> [<name>] per line, raw format. http://www.zophar.net/faq/nitrofaq/nesticle.readme.txt and some at http://jeff.tk:81/consoles/
func (db *Database) ImportXML(filename string) {
	fmt.Printf("Importing %s\n", filename)
	r, err := os.Open(filename, os.O_RDONLY, 0)
	if r == nil {
		panic(fmt.Sprintf("cannot open %s: %s", filename, err))
	}

	cheats := Cheats{}

	xml.Unmarshal(r, &cheats)

	db.exec("INSERT INTO user(fullname) VALUES(?)", "Galoob") // TODO: pass as argument, search for existing, and stop hardcoding
	userID := db.handle.LastInsertRowID()

	gamesInserted := 0
	cartsInserted := 0
	effectsInserted := 0
	codesInserted := 0

	// Insert into database
	for _, game := range cheats.Game {
		db.exec("INSERT INTO game(name,galoob_id,galoob_name) VALUES(?,?,?)", game.Name, game.GaloobID, game.GaloobName)
		// LastInsertedRowID() requires gosqlite patch at http://code.google.com/p/gosqlite/issues/detail?id=7
		// or for https://github.com/kless/go-sqlite, add to end of connection.go:
		/*
			func (c *Connection) LastInsertRowID() (int64) {
			    return int64(C.sqlite3_last_insert_rowid(c.db))
			}
		*/
		gameID := db.handle.LastInsertRowID()
		gamesInserted += 1

		cartName2ID := make(map[string]int64)

		for _, cart := range game.Cartridge {
			db.exec("INSERT INTO cart(game_id,sha1,name,filename) VALUES(?,?,?,?)", gameID, cart.SHA1, cart.Name, cart.Filename)
			cartName2ID[cart.Name] = db.handle.LastInsertRowID()
			cartsInserted += 1
		}

		for _, effect := range game.Effect {
			db.exec("INSERT INTO effect(game_id,number,source,title,hacker_id,create_date) VALUES(?,?,?,?,?,?)",
				gameID, effect.Number, effect.Source, effect.Title, userID, "1994")
			effectID := db.handle.LastInsertRowID()
			effectsInserted += 1

			for _, code := range effect.Code {
				func() {
					defer func() {
						r := recover()
						if r != nil {
							fmt.Printf("Bad code for %s: %s: %s\n", game.Name, code.Genie, r)
						}
					}()

					decoded := gamegenie.Decode(code.Genie)
					db.exec("INSERT INTO code(effect_id,cpu_address,value) VALUES(?,?,?)",
						effectID,
						decoded.CPUAddress(),
						decoded.Value)
					codeID := db.handle.LastInsertRowID()
					codesInserted += 1

					// Only set if has a compare value, otherwise leave NULL
					if decoded.HasKey {
						db.exec("UPDATE code SET compare=? WHERE id=?", decoded.Key, codeID)
					}

					// If code only applies to a specific cart, set it; otherwise, leave NULL to apply to all
					if code.Applies != "" {
						db.exec("UPDATE code SET cart_id=? WHERE id=?", cartName2ID[code.Applies], codeID)
					}
				}()
			}
		}
	}
	fmt.Printf("Imported %d games, %d carts, %d effects, and %d total codes\n",
		gamesInserted, cartsInserted, effectsInserted, codesInserted)
}
Ejemplo n.º 2
0
// Run a command to do something with the unit
func (deck *ControlDeck) RunCommand(cmd string) {
    // If given filename (no command), load and run it
    if strings.HasSuffix(cmd, ".nes") || strings.HasSuffix(cmd, ".NES") ||
       strings.HasSuffix(cmd, ".unf") || strings.HasSuffix(cmd, ".UNF") ||
       strings.HasSuffix(cmd, ".unif") || strings.HasSuffix(cmd, ".UNIF") {
        _, err := os.Stat(cmd)
        if err == nil {
            deck.Load(cmd)
            deck.Start()
        } // have to ignore non-existing files, since might be a command
    }

    // TODO: better cmd parsing. real scripting language?
    tokens := strings.Split(cmd, " ", -1)
    name := tokens[0]
    args := tokens[1:]
    switch name {
    // go (not to be confused with the programming language)
    case "g", "go": 
        if len(args) > 0 {
            // optional start address to override RESET vector
            startInt, err := strconv.Btoui64(args[0], 0)
            if err != nil {
                fmt.Fprintf(os.Stderr, "unable to read start address: %s: %s\n", args[0], err)
                os.Exit(-1)
            }

            deck.CPU.PC = uint16(startInt)
        }
        // TODO: number of instructions, cycles, pixels, or frames to execute
        // (2i, 2c, 2p, 2f)
        deck.Start()
    // load
    case "l", "load":
        if len(args) > 0 {
            deck.Load(strings.Join(args, " ")) // TODO: use cmd directly instead of rejoining
            // TODO: verbose load
        } else {
            fmt.Printf("usage: l <filename>\n")
        }
    case "load-dir":
        if len(args) > 0 {
            deck.LoadDir(strings.Join(args, " ")) // TODO: use cmd directly instead of rejoining
        }

    // hide GUI
    case "h", "hide": deck.ShowGui = false

    // interactive
    case "i", "shell": deck.Shell()
    // registers
    case "r", "registers": deck.CPU.DumpRegisters()


    case "m", "memory": deck.CPU.DumpMemoryMap()

    case "show-carts": cartdb.Dump(cartdb.Load())

    // TODO: search
    // TODO: unassemble

    // pattern table
    case "p", "show-pattern":
        if len(args) < 2 {
            fmt.Printf("usage: p <table-number> <tile-number>\n")
            return
        }
        // TODO: allow omitting to show all
        table, err := strconv.Btoui64(args[0], 0)
        if err == nil {
            table = 0
        }
        tile, err := strconv.Btoui64(args[1], 0) 
        if err == nil {
            tile = 0
        }
        if table == 0 {
            table = 0
        } else {
            table = 0x1000
        }
        deck.PPU.PrintPattern(deck.PPU.GetPattern(uint16(table), int(tile)))

    /* TODO: be able to interact with shell when GUI is open, so could do this
    case "draw-patterns":
        ppu.ShowPatterns()
    */

    // enter
    case "e", "enter":
        if len(args) < 2 {
            fmt.Printf("usage: e <address> <byte0> [<byte1> [<byteN...]]\n")
            return
        }

        baseAddress, err := strconv.Btoui64(args[0], 16)
        if err != nil {
            fmt.Printf("bad address: %s: %s\n", args[0], err)
            return
        }

        for offset, arg := range args[1:] {
            argInt, err := strconv.Btoui64(arg, 16)
            if err == nil {
                writeAddress := uint16(baseAddress) + uint16(offset)
                writeByte := uint8(argInt)

                fmt.Printf("%.4X = %X\n", writeAddress, writeByte)

                deck.CPU.WriteTo(writeAddress, writeByte)
            } else {
                fmt.Printf("bad value: %s: %s\n", arg, err)
            }
        }

    // assemble
    // TODO: get string -> Opcode/AddrMode working again
    /*
    case "a", "assemble":
        if len(args) != 2 {
            fmt.Printf("usage: a <opcode> <addrmode>\n")
            return
        }
        opcode := dis6502.Opcode(args[0])
        addrMode := dis6502.AddrMode(args[1])
        // TODO: show all matches
        fmt.Printf("%.2x\n", dis6502.OpcodeByteFor(opcode, addrMode))
        // TODO: read operands and actually assemble and write
    */
    // trace
    case "t", "trace":
        // TODO: optional argument of instructions to execute
        // TODO: Fix, really have to run PPU & CPU in sync..see 'g'
        deck.CPU.ExecuteInstruction()
        deck.CPU.DumpRegisters()
    case "import-cheats":
        db := cheatdb.Open()
        filename := strings.Join(args, " ") // TODO: use cmd directly instead of rejoining
        db.ImportXML(filename)

    case "analyze-cheats":
        db := cheatdb.Open()
        db.AllCarts(func(path string) (bool) { 
            err, ok := deck.Load(path)
            if !ok {        // Not everything is supported yet
                fmt.Printf("Skipping %s: %s", path, err)
                return false
            }
            return true
        },
      
        func(code gamegenie.GameGenieCode) (found bool, romAddress uint32, romChip string, romBefore string, romAfter string) {
             romAddress, romChip = deck.CPU.Address2ROM(code.CPUAddress())    

            // TODO: refactor 'code' command below that also checks key
            currentValue := deck.InsertedCartridge.Prg[romAddress]
            if code.HasKey && currentValue != code.Key {
                // Some other bank is loaded currently
                fmt.Printf("This is NOT the ROM address: %.6X, since it currently contains %.2X, but key is %.2X\n", romAddress, currentValue, code.Key)
            } else {
                // Read bytes before and after the patched address, for context
                for i := uint32(0); i < 16; i += 1 {
                    romAfter += fmt.Sprintf("%.2X ", deck.InsertedCartridge.Prg[romAddress + i])
                    romBefore = fmt.Sprintf("%.2X ", deck.InsertedCartridge.Prg[romAddress - i - 1]) + romBefore
                }

                //code.ROMBefore = romBefore
                //code.ROMAfter = romAfter

                // TODO: save it for jsdis, in patch table
                return true, romAddress, romChip, romBefore, romAfter
            }
            // Not found
            return false, romAddress, romChip, romBefore, romAfter
        })

    case "serve-cheats":
        db := cheatdb.Open()
        db.Serve() 

    // cheat code
    case "c", "code": 
        for _, code := range(args) {
            // TODO: Pro Action Replay/PAR codes (RAM patches), FCEU ("emulator PAR"; 001234 FF) and PAR format (1234 FF)
            // TODO: ROM patches, complete addresses in PRG chip to patch, more precise than GG
            //  since can specify entire address (XXXXX=XX?)
            // TODO: maybe CHR ROM patches too? (XXXXX=XX, but with higher address? PRG+CHR)
            // TODO: Pro Action Rocky (ROM patcher like Game Genie but for Famicom) - see http://www.disgruntleddesigner.com/chrisc/fcrocky.html - need more info

            patch := gamegenie.Decode(code)
            fmt.Printf("%s = %s\n", patch, patch.Encode())
            fmt.Printf("CPU address: %.4X\n", patch.CPUAddress())
            fmt.Printf("Value: %.2X\n", patch.Value)
            // TODO: apply code

            // Find out what the CPU address currently maps to in ROM
            // TODO: option to attempt to do this statically.. the code below will only find what is currently loaded.
            romAddress, romChip := deck.CPU.Address2ROM(patch.CPUAddress())

            currentValue := deck.CPU.ReadFrom(patch.CPUAddress())
            if !patch.HasKey || currentValue == patch.Key {
                fmt.Printf("ROM address found: %.6X\n", romAddress)
                fmt.Printf("ROM chip: %s\n", romChip)
                fmt.Printf("iNES offset: %.4X\n", romAddress + 0x10)
            } else {
                // The affected part of ROM is not currently loaded
                fmt.Printf("This is NOT the ROM address: %.6X, since it currently contains %.2X, but key is %.2X\n", romAddress, currentValue, patch.Key)
            }

            // TODO: should we also, after finding the ROM address, search for all CPU
            // addresses it affects, since it may be more than one? For example, 16 KB NROM
            // games (like Mario Bros.) have codes like SXTIEG = 5ce0:a5 = CPU address dce0,
            // which affects ROM address 1ce0, since CPU d000-dfff is mapped to ROM 1000-1fff;
            // however, paching ROM 1ce0 would ALSO affect CPU 9ce0, since CPU 8000-8fff is 
            // mapped there, too. Game Genie, since it uses CPU addresses, can affect each of
            // these mirrors independently. The code for the mirrored bank is 1ce0:a5=SXTPEG,
            // but it has no effect because the program in this case runs from c000-ffff not
            // the mirrored 8000-bfff. 
            // Point is, GG codes can not only be LESS specific than ROM patches (affecting multiple
            // addresses in the ROM), they can also be MORE specific, since they use the CPU address.
            // TODO: also should check compare value, to restrict banks

        }
        if len(args) == 0 {
            fmt.Printf("usage: c game-genie-code\n")
        }


    // verbose
    case "v", "show-cycles": deck.CPU.Verbose = true
    case "show-ppu": deck.PPU.Verbose = true
    case "b", "show-vblank": deck.PPU.Verbose = true
    case "V", "show-trace": deck.CPU.InstrTrace = true

    // TODO: breakpoints
    // TODO: watch
    default: fmt.Printf("unknown command: %s\n", cmd)
    }
}