예제 #1
0
// Load a game
func (deck *ControlDeck) Load(filename string) (err string, success bool) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Sprintf("Failed to load %s: %s", filename, r)
            success = false
        }
    }()


    deck.InsertedCartridge = cartridge.LoadFile(filename)

    // Check ROM against known hashes, and set deck.InsertedCartridge.SHA1
    // TODO: maybe this should be in nesfile, or in controldeck?
    matches := cartdb.Identify(cartdb.Load(), deck.InsertedCartridge)
    fmt.Printf("Cartridge SHA-1 = %s\n", deck.InsertedCartridge.SHA1)
    if len(matches) != 0 {
        fmt.Printf("cartdb: found %d matching cartridges:\n", len(matches))
    }
    for i, match := range matches {
        fmt.Printf("%d. ", i)
        cartdb.DumpMatch(match)
    }
    // TODO: use info here, to display title/image, or use mapper


    deck.InsertedCartridge.LoadPRG(deck.CPU)
    deck.InsertedCartridge.LoadCHR(deck.PPU)

    // nestest.nes (TODO: better way to have local additions to cartdb!)
    if deck.InsertedCartridge.SHA1 == "4131307F0F69F2A5C54B7D438328C5B2A5ED0820" {
        // TODO: only do this when running in automated mode, still want GUI mode unaffected
        fmt.Printf("Identified nestest; mapping for automation\n")

        deck.CPU.MapOver(0xc66e, 
                // The known-correct log http://nickmass.com/images/nestest.log ends at $C66E, on RTS
                func(address uint16)(value uint8) { 
                    // http://nesdev.com/bbs/viewtopic.php?t=7130
                    result := deck.CPU.ReadFrom(2) << 8 | deck.CPU.ReadFrom(3)

                    if result == 0 {
                        fmt.Printf("Nestest automation: Pass\n")
                    } else {
                        fmt.Printf("Nestest automation: FAIL with code %.4x\n", result)
                    }
                    // Signal to stop CPU (KIL instruction)
                    // TODO: find a better way to quit the program. Could quit here, but
                    // then last instruction trace wouldn't match expected log.
                    deck.InternalRAM[0x0001] = 0x02

                    return 0x60 },
                func(address uint16, value uint8) { },
                "nestest-automation")
    }

    return err, true
}
예제 #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)
    }
}