diff options
author | Bryan Bishop <kanzure@gmail.com> | 2013-09-08 11:06:13 -0500 |
---|---|---|
committer | Bryan Bishop <kanzure@gmail.com> | 2013-09-08 11:06:13 -0500 |
commit | 138637d8f367bd066a4ad5f3c592299c5cab35e4 (patch) | |
tree | 417d5a613cba041e7b115999c1fc51a444c2ef7c /vba/vba.py | |
parent | 15c04c9885c639bd9b52cd94f6dc5442df52fab0 (diff) |
move vba tools into vba/
Diffstat (limited to 'vba/vba.py')
-rw-r--r-- | vba/vba.py | 1181 |
1 files changed, 1181 insertions, 0 deletions
diff --git a/vba/vba.py b/vba/vba.py new file mode 100644 index 0000000..ea7bb2e --- /dev/null +++ b/vba/vba.py @@ -0,0 +1,1181 @@ +#!/usr/bin/jython +# -*- encoding: utf-8 -*- +""" +vba-clojure (but really it's jython/python/jvm) + +This is jython, not python. Use jython to run this file. Before running this +file, some of the dependencies need to be constructed. These can be obtained +from the vba-clojure project. + sudo apt-get install g++ libtool openjdk-6-jre openjdk-6-jdk libsdl1.2-dev mercurial ant autoconf jython + + export JAVA_INCLUDE_PATH=/usr/lib/jvm/java-6-openjdk-amd64/include/ + export JAVA_INCLUDE_PATH2=/usr/lib/jvm/java-6-openjdk-amd64/include/ + + hg clone http://hg.bortreb.com/vba-clojure + cd vba-clojure/ + ./dl-libs.sh + cd java/ + ant all + cd .. + autoreconf -i + ./configure + make + sudo make install + +Make sure vba-clojure bindings are in $CLASSPATH: + export CLASSPATH=$CLASSPATH:java/dist/gb-bindings.jar + +Make sure vba-clojure is available within "java.library.path": + sudo ln -s \ + $HOME/local/vba-clojure/vba-clojure/src/clojure/.libs/libvba.so.0.0.0 \ + /usr/lib/jni/libvba.so + +(In the above command, substitute the first path with the path of the vba-clojure +directory you made, if it is different.) + +Also make sure VisualBoyAdvance.cfg is somewhere in the $PATH for VBA to find. +A default configuration is provided in vba-clojure under src/. + +Usage (in jython, not python): + import vba + + # activate the laser beam + vba.load_rom("/path/to/baserom.gbc") + + # make the emulator eat some instructions + vba.nstep(300) + + # save the state because we're paranoid + copyrights = vba.get_state() + # or ... + vba.save_state("copyrights") + # >>> vba.load_state("copyrights") == copyrights + # True + + # play for a while, then press F12 + vba.run() + + # let's save the game again + vba.save_state("unknown-delete-me") + + # and let's go back to the other state + vba.set_state(copyrights) + + # or why not the other way around? + vba.set_state(vba.load_state("unknown-delete-me")) + + vba.get_memory_at(0xDCDA) + vba.set_memory_at(0xDCDB, 0xFF) + vba.get_memory_range(0xDCDA, 10) + +TOOD: + [ ] set a specific register + [ ] get a specific register + [ ] breakpoints + [ ] vgm stuff + [ ] gbz80disasm integration + [ ] pokecrystal.extras integration + +""" + +import os +import sys +import re +from array import array +import string +from copy import copy +import unittest + +# for converting bytes to readable text +from chars import chars + +from map_names import map_names + +# for _check_java_library_path +from java.lang import System + +# for passing states to the emulator +from java.nio import ByteBuffer + +# For getRegisters and other times we have to pass a java int array to a +# function. +import jarray + +# load in the vba-clojure bindings +import com.aurellem.gb.Gb as Gb + +# load the vba-clojure library +Gb.loadVBA() + +from vba_config import * + +try: + import vba_keyboard as keyboard +except ImportError: + print "Not loading the keyboard module (which uses networkx)." + +if not os.path.exists(rom_path): + raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path)) + +def _check_java_library_path(): + """ + Returns the value of java.library.path. + + The vba-clojure library must be compiled + and linked from this location. + """ + return System.getProperty("java.library.path") + +class RomList(list): + + """ + Simple wrapper to prevent a giant rom from being shown on screen. + """ + + def __init__(self, *args, **kwargs): + list.__init__(self, *args, **kwargs) + + def __repr__(self): + """ + Simplifies this object so that the output doesn't overflow stdout. + """ + return "RomList(too long)" + +button_masks = { + "a": 0x0001, + "b": 0x0002, + "r": 0x0010, + "l": 0x0020, + "u": 0x0040, + "d": 0x0080, + "select": 0x0004, + "start": 0x0008, + "restart": 0x0800, + "listen": -1, # what? +} + +# useful for directly stating what to press +a, b, r, l, u, d, select, start, restart = "a", "b", "r", "l", "u", "d", "select", "start", "restart" + +def button_combiner(buttons): + """ + Combines multiple button presses into an integer. + + This is used when sending a keypress to the emulator. + """ + result = 0 + + # String inputs need to be cleaned up so that "start" doesn't get + # recognized as "s" and "t" etc.. + if isinstance(buttons, str): + if "restart" in buttons: + buttons = buttons.replace("restart", "") + result |= button_masks["restart"] + if "start" in buttons: + buttons = buttons.replace("start", "") + result |= button_masks["start"] + if "select" in buttons: + buttons = buttons.replace("select", "") + result |= button_masks["select"] + + # allow for the "a, b" and "a b" formats + if ", " in buttons: + buttons = buttons.split(", ") + elif " " in buttons: + buttons = buttons.split(" ") + + if isinstance(buttons, list): + if len(buttons) > 9: + raise Exception("can't combine more than 9 buttons at a time") + + for each in buttons: + result |= button_masks[each] + + #print "button: " + str(result) + return result + +def load_rom(path=None): + """ + Starts the emulator with a certain ROM. + + Defaults to rom_path if no parameters are given. + """ + if path == None: + path = rom_path + try: + root = load_state("root") + except: + # "root.sav" is required because if you create it in the future, you + # will have to shutdown the emulator and possibly lose your state. Some + # functions require there to be at least one root state available to do + # computations between two states. + sys.stderr.write("ERROR: unable to read \"root.sav\", please run" + " generate_root() or get_root() to make an initial save.\n") + Gb.startEmulator(path) + +def shutdown(): + """ + Stops the emulator. Closes the window. + + The "opposite" of this is the load_rom function. + """ + Gb.shutdown() + +def step(): + """ + Advances the emulator forward by one step. + """ + Gb.step() + +def nstep(steplimit): + """ + Step the game forward by a certain number of instructions. + """ + for counter in range(0, steplimit): + Gb.step() + +def step_until_capture(): + """ + Loop step() until SDLK_F12 is detected. + """ + Gb.stepUntilCapture() + +# just some aliases for step_until_capture +run = go = step_until_capture + +def translate_chars(charz): + result = "" + for each in charz: + result += chars[each] + return result + +def _create_byte_buffer(data): + """ + Converts data into a ByteBuffer. + + This is useful for interfacing with the Gb class. + """ + buf = ByteBuffer.allocateDirect(len(data)) + if isinstance(data[0], int): + for byte in data: + buf.put(byte) + else: + for byte in data: + buf.put(ord(byte)) + return buf + +def set_state(state, do_step=False): + """ + Injects the given state into the emulator. + + Use do_step if you want to call step(), which also allows + SDL to render the latest frame. Note that the default is to + not step, and that the screen (if it is enabled) will appear + as if it still has the last state loaded. This is normal. + """ + Gb.loadState(_create_byte_buffer(state)) + if do_step: + step() + +def get_state(): + """ + Retrieves the current state of the emulator. + """ + buf = Gb.saveState() + state = [buf.get(x) for x in range(0, buf.capacity())] + arr = array("b") + arr.extend(state) + return arr.tostring() # instead of state + +def save_state(name, state=None, override=False): + """ + Saves the given state to save_state_path. + + The file format must be ".sav" + (and this will be appended to your string if necessary). + """ + if state == None: + state = get_state() + if len(name) < 4 or name[-4:] != ".sav": + name += ".sav" + save_path = os.path.join(save_state_path, name) + if not override and os.path.exists(save_path): + raise Exception("oops, save state path already exists: " + str(save_path)) + else: + # convert the state into a reasonable output + data = array('b') + data.extend(state) + output = data.tostring() + + file_handler = open(save_path, "wb") + file_handler.write(output) + file_handler.close() + +def load_state(name): + """ + Reads a state from file based on name. + + Looks in save_state_path for a file + with this name (".sav" is optional). + """ + save_path = os.path.join(save_state_path, name) + if not os.path.exists(save_path): + if len(name) < 4 or name[-4:] != ".sav": + name += ".sav" + save_path = os.path.join(save_state_path, name) + file_handler = open(save_path, "rb") + state = file_handler.read() + file_handler.close() + return state + +def generate_root(): + """ + Restarts the emulator and saves the initial state to "root.sav". + """ + shutdown() + load_rom() + root = get_state() + save_state("root", state=root, override=True) + return root + +def get_root(): + """ + Loads the root state. + + (Or restarts the emulator and creates a new root state.) + """ + try: + root = load_state("root") + except: + root = generate_root() + +def get_registers(): + """ + Returns a list of current register values. + """ + register_array = jarray.zeros(Gb.NUM_REGISTERS, "i") + Gb.getRegisters(register_array) + return list(register_array) + +def set_registers(registers): + """ + Applies the set of registers to the CPU. + """ + Gb.writeRegisters(registers) +write_registers = set_registers + +def get_rom(): + """ + Returns the ROM in bytes. + """ + rom_array = jarray.zeros(Gb.ROM_SIZE, "i") + Gb.getROM(rom_array) + return RomList(rom_array) + +def get_ram(): + """ + Returns the RAM in bytes. + """ + ram_array = jarray.zeros(Gb.RAM_SIZE, "i") + Gb.getRAM(ram_array) + return RomList(ram_array) + +def say_hello(): + """ + Test that the VBA/GB bindings are working. + """ + Gb.sayHello() + +def get_memory(): + """ + Returns memory in bytes. + """ + memory_size = 0x10000 + memory = jarray.zeros(memory_size, "i") + Gb.getMemory(memory) + return RomList(memory) + +def set_memory(memory): + """ + Sets memory in the emulator. + + Use get_memory() to retrieve the current state. + """ + Gb.writeMemory(memory) + +def get_pixels(): + """ + Returns a list of pixels on the screen display. + + Broken, probably. Use screenshot() instead. + """ + sys.stderr.write("ERROR: seems to be broken on VBA's end? Good luck. Use" + " screenshot() instead.\n") + size = Gb.DISPLAY_WIDTH * Gb.DISPLAY_HEIGHT + pixels = jarray.zeros(size, "i") + Gb.getPixels(pixels) + return RomList(pixels) + +def screenshot(filename, literal=False): + """ + Saves a PNG screenshot to the file at filename. + + Use literal if you want to store it in the current directory. + Default is to save it to screenshots/ under the project. + """ + screenshots_path = os.path.join(project_path, "screenshots/") + filename = os.path.join(screenshots_path, filename) + if len(filename) < 4 or filename[-4:] != ".png": + filename += ".png" + Gb.nwritePNG(filename) + print "Screenshot saved to: " + str(filename) +save_png = screenshot + +def read_memory(address): + """ + Read an integer at an address. + """ + return Gb.readMemory(address) +get_memory_at = read_memory + +def get_memory_range(start_address, byte_count): + """ + Returns a list of bytes. + + start_address - address to start reading at + byte_count - how many bytes (0 returns just 1 byte) + """ + bytez = [] + for counter in range(0, byte_count): + byte = get_memory_at(start_address + counter) + bytez.append(byte) + return bytez + +def set_memory_at(address, value): + """ + Sets a byte at a certain address in memory. + + This directly sets the memory instead of copying + the memory from the emulator. + """ + Gb.setMemoryAt(address, value) + +def press(buttons, holdsteps=1, aftersteps=1): + """ + Press a button. + + Use steplimit to say for how many steps you want to press + the button (try leaving it at the default, 1). + """ + if hasattr(buttons, "__len__"): + number = button_combiner(buttons) + elif isinstance(buttons, int): + number = buttons + else: + number = buttons + for step_counter in range(0, holdsteps): + Gb.step(number) + + # clear the button press + if aftersteps > 0: + for step_counter in range(0, aftersteps): + Gb.step(0) + +def get_buttons(): + """ + Returns the currentButtons[0] value + + (an integer with bits set for which + buttons are currently pressed). + """ + return Gb.getCurrentButtons() + +class State(RomList): + name = None + +class Recording: + def __init__(self): + self.frames = [] + self.states = {} + + def _get_frame_count(self): + return len(self.frames) + + frame_count = property(fget=_get_frame_count) + + def save(self, name=None): + """ + Saves the current state. + """ + state = State(get_state()) + state.name = name + self.states[self.frame_count] = state + + def load(self, name): + """ + Loads a state by name in the state list. + """ + for state in self.states.items(): + if state.name == name: + set_state(state) + return state + return False + + def step(self, stepcount=1, first_frame=0, replay=False): + """ + Records button presses for each frame. + """ + if replay: + stepcount = len(self.frames[first_name:]) + + for counter in range(first_frame, stepcount): + if replay: + press(self.frames[counter], steplimit=0) + else: + self.frames.append(get_buttons()) + nstep(1) + + def replay_from(self, thing): + """ + Replays based on a State or the name of a saved state. + """ + if isinstance(thing, State): + set_state(thing) + else: + thing = self.load(thing) + frame_id = self.states.index(thing) + self.step(first_frame=frame_id, replay=True) + +class Registers: + order = [ + "pc", + "sp", + "af", + "bc", + "de", + "hl", + "iff", + "div", + "tima", + "tma", + "tac", + "if", + "lcdc", + "stat", + "scy", + "scx", + "ly", + "lyc", + "dma", + "wy", + "wx", + "vbk", + "hdma1", + "hdma2", + "hdma3", + "hdma4", + "hdma5", + "svbk", + "ie", + ] + + def __setitem__(self, key, value): + current_registers = get_registers() + current_registers[Registers.order.index(key)] = value + set_registers(current_registers) + + def __getitem__(self, key): + current_registers = get_registers() + return current_registers[Registers.order.index(key)] + + def __list__(self): + return get_registers() + + def _get_register(id): + def constructed_func(self, id=copy(id)): + return get_registers()[id] + return constructed_func + + def _set_register(id): + def constructed_func(self, value, id=copy(id)): + current_registers = get_registers() + current_registers[id] = value + set_registers(current_registers) + return constructed_func + + pc = property(fget=_get_register(0), fset=_set_register(0)) + sp = property(fget=_get_register(1), fset=_set_register(1)) + af = property(fget=_get_register(2), fset=_set_register(2)) + bc = property(fget=_get_register(3), fset=_set_register(3)) + de = property(fget=_get_register(4), fset=_set_register(4)) + hl = property(fget=_get_register(5), fset=_set_register(5)) + iff = property(fget=_get_register(6), fset=_set_register(6)) + div = property(fget=_get_register(7), fset=_set_register(7)) + tima = property(fget=_get_register(8), fset=_set_register(8)) + tma = property(fget=_get_register(9), fset=_set_register(9)) + tac = property(fget=_get_register(10), fset=_set_register(10)) + _if = property(fget=_get_register(11), fset=_set_register(11)) + lcdc = property(fget=_get_register(12), fset=_set_register(12)) + stat = property(fget=_get_register(13), fset=_set_register(13)) + scy = property(fget=_get_register(14), fset=_set_register(14)) + scx = property(fget=_get_register(15), fset=_set_register(15)) + ly = property(fget=_get_register(16), fset=_set_register(16)) + lyc = property(fget=_get_register(17), fset=_set_register(17)) + dma = property(fget=_get_register(18), fset=_set_register(18)) + wy = property(fget=_get_register(19), fset=_set_register(19)) + wx = property(fget=_get_register(20), fset=_set_register(20)) + vbk = property(fget=_get_register(21), fset=_set_register(21)) + hdma1 = property(fget=_get_register(22), fset=_set_register(22)) + hdma2 = property(fget=_get_register(23), fset=_set_register(23)) + hdma3 = property(fget=_get_register(24), fset=_set_register(24)) + hdma4 = property(fget=_get_register(25), fset=_set_register(25)) + hdma5 = property(fget=_get_register(26), fset=_set_register(26)) + svbk = property(fget=_get_register(27), fset=_set_register(27)) + ie = property(fget=_get_register(28), fset=_set_register(28)) + + def __repr__(self): + spacing = "\t" + output = "Registers:\n" + for (id, each) in enumerate(self.order): + output += spacing + each + " = " + hex(get_registers()[id]) + #hex(self[each]) + output += "\n" + return output + +registers = Registers() + +def call(bank, address): + """ + Jumps into a function at a certain address. + + Go into the start menu, pause the game and try call(1, 0x1078) to see a + string printed to the screen. + """ + push = [ + registers.pc, + registers.hl, + registers.de, + registers.bc, + registers.af, + 0x3bb7, + ] + + for value in push: + registers.sp -= 2 + set_memory_at(registers.sp + 1, value >> 8) + set_memory_at(registers.sp, value & 0xFF) + if get_memory_range(registers.sp, 2) != [value & 0xFF, value >> 8]: + print "desired memory values: " + str([value & 0xFF, value >> 8] ) + print "actual memory values: " + str(get_memory_range(registers.sp , 2)) + print "wrong value at " + hex(registers.sp) + " expected " + hex(value) + " but got " + hex(get_memory_at(registers.sp)) + + if bank != 0: + registers["af"] = (bank << 8) | (registers["af"] & 0xFF) + registers["hl"] = address + registers["pc"] = 0x2d63 # FarJump + else: + registers["pc"] = address + +class cheats: + """ + Helpers to manage the cheating infrastructure. + + import vba; vba.load_rom(); vba.cheats.add_gameshark("0100CFCF", "text speedup 1"); vba.cheats.add_gameshark("0101CCCF", "text speedup 2"); vba.go() + """ + + @staticmethod + def enable(id): + """ + void gbCheatEnable(int i) + """ + Gb.cheatEnable(id) + + @staticmethod + def disable(id): + """ + void gbCheatDisable(int i) + """ + Gb.cheatDisable(id) + + @staticmethod + def load_file(filename): + """ + Loads a .clt file. By default each cheat is disabled. + """ + Gb.loadCheatsFromFile(filename) + + @staticmethod + def remove_all(): + """ + Removes all cheats from memory. + + void gbCheatRemoveAll() + """ + Gb.cheatRemoveAll() + + @staticmethod + def remove_cheat(id): + """ + Removes a specific cheat from memory by id. + + void gbCheatRemove(int i) + """ + Gb.cheatRemove(id) + + @staticmethod + def add_gamegenie(code, description=""): + """ + void gbAddGgCheat(const char *code, const char *desc) + """ + Gb.cheatAddGamegenie(code, description) + + @staticmethod + def add_gameshark(code, description=""): + """ + gbAddGsCheat(const char *code, const char *desc) + """ + Gb.cheatAddGameshark(code, description) + +def get_stack(): + """ + Return a list of functions on the stack. + """ + addresses = [] + sp = registers.sp + + for x in range(0, 11): + sp = sp - (2 * x) + hi = get_memory_at(sp + 1) + lo = get_memory_at(sp) + address = ((hi << 8) | lo) + addresses.append(address) + + return addresses + +class crystal: + """ + Just a simple namespace to store a bunch of functions for Pokémon Crystal. + """ + + @staticmethod + def text_wait(step_size=1, max_wait=200, sfx_limit=0, debug=False, callback=None): + """ + Presses the "A" button when text is done being drawn to screen. + + The `debug` parameter is only useful when debugging this function. It + enables the `max_wait` feature, which causes the function to exit + instead of hanging around. + + The `sfx_limit` parameter is useful for when the player is given an + item during the text. Set it to 1 to not treat the sound as the end of + text. The next loop around it will return to the normal behavior of the + function. + + :param step_size: number of steps per wait loop + :param max_wait: number of wait loops to perform + """ + while max_wait > 0: + hi = get_memory_at(registers.sp + 1) + lo = get_memory_at(registers.sp) + address = ((hi << 8) | lo) + + if address in range(0xa1b, 0xa46) + range(0xaaf, 0xaf5): # 0xaef: + print "pressing, then breaking.. address is: " + str(hex(address)) + + # set CurSFX + set_memory_at(0xc2bf, 0) + + press("a", holdsteps=10, aftersteps=1) + + # check if CurSFX is SFX_READ_TEXT_2 + if get_memory_at(0xc2bf) == 0x8: + print "cursfx is set to SFX_READ_TEXT_2, looping.." + return crystal.text_wait(step_size=step_size, max_wait=max_wait, debug=debug, callback=callback, sfx_limit=sfx_limit) + else: + if sfx_limit > 0: + sfx_limit = sfx_limit - 1 + print "decreasing sfx_limit" + else: + # probably the last textbox in a sequence + print "cursfx is not set to SFX_READ_TEXT_2, so: breaking" + + break + else: + stack = get_stack() + + # yes/no box or the name selection box + if address in range(0xa46, 0xaaf): + print "probably at a yes/no box.. exiting." + break + + # date/time box (day choice) + # 0x47ab is the one from the intro, 0x49ab is the one from mom. + elif 0x47ab in stack or 0x49ab in stack: # was any([x in stack for x in range(0x46EE, 0x47AB)]) + print "probably at a date/time box ? exiting." + break + + # "How many minutes?" selection box + elif 0x4826 in stack: + print "probably at a \"How many minutes?\" box ? exiting." + break + + else: + nstep(step_size) + + # if there is a callback, then call the callback and exit when the + # callback returns True. This is especially useful during the + # OakSpeech intro where textboxes are running constantly, and then + # suddenly the player can move around. One way to detect that is to + # set callback to a function that returns + # "vba.get_memory_at(0xcfb1) != 0". + if callback != None: + result = callback() + if result == True: + print "callback returned True, exiting" + return + + # only useful when debugging. When this is left on, text that + # takes a while to print to screen will cause this function to + # exit. + if debug == True: + max_wait = max_wait - 1 + + if max_wait == 0: + print "max_wait was hit" + + @staticmethod + def walk_through_walls_slow(): + memory = get_memory() + memory[0xC2FA] = 0 + memory[0xC2FB] = 0 + memory[0xC2FC] = 0 + memory[0xC2FD] = 0 + set_memory(memory) + + @staticmethod + def walk_through_walls(): + """ + Lets the player walk all over the map. + + These values are probably reset by some of the map/collision + functions when you move on to a new location, so this needs + to be executed each step/tick if continuous walk-through-walls + is desired. + """ + set_memory_at(0xC2FA, 0) + set_memory_at(0xC2FB, 0) + set_memory_at(0xC2FC, 0) + set_memory_at(0xC2FD, 0) + + #@staticmethod + #def set_enemy_level(level): + # set_memory_at(0xd213, level) + + @staticmethod + def nstep(steplimit=500): + """ + Steps the CPU forward and calls some functions in between each step. + + (For example, to manipulate memory.) This is pretty slow. + """ + for step_counter in range(0, steplimit): + crystal.walk_through_walls() + #call(0x1, 0x1078) + step() + + @staticmethod + def disable_triggers(): + set_memory_at(0x23c4, 0xAF) + set_memory_at(0x23d0, 0xAF); + + @staticmethod + def disable_callbacks(): + set_memory_at(0x23f2, 0xAF) + set_memory_at(0x23fe, 0xAF) + + @staticmethod + def get_map_group_id(): + """ + Returns the current map group. + """ + return get_memory_at(0xdcb5) + + @staticmethod + def get_map_id(): + """ + Returns the map number of the current map. + """ + return get_memory_at(0xdcb6) + + @staticmethod + def get_map_name(): + """ + Figures out the current map name. + """ + map_group_id = crystal.get_map_group_id() + map_id = crystal.get_map_id() + return map_names[map_group_id][map_id]["name"] + + @staticmethod + def get_xy(): + """ + (x, y) coordinates of player on map. + + Relative to top-left corner of map. + """ + x = get_memory_at(0xdcb8) + y = get_memory_at(0xdcb7) + return (x, y) + + @staticmethod + def menu_select(id=1): + """ + Sets the cursor to the given pokemon in the player's party. + + This is under Start -> PKMN. This is useful for selecting a + certain pokemon with fly or another skill. + + This probably works on other menus. + """ + set_memory_at(0xcfa9, id) + + @staticmethod + def is_in_battle(): + """ + Checks whether or not we're in a battle. + """ + return (get_memory_at(0xd22d) != 0) or crystal.is_in_link_battle() + + @staticmethod + def is_in_link_battle(): + return get_memory_at(0xc2dc) != 0 + + @staticmethod + def unlock_flypoints(): + """ + Unlocks different destinations for flying. + + Note: this might start at 0xDCA4 (minus one on all addresses), but not + sure. + """ + set_memory_at(0xDCA5, 0xFF) + set_memory_at(0xDCA6, 0xFF) + set_memory_at(0xDCA7, 0xFF) + set_memory_at(0xDCA8, 0xFF) + + @staticmethod + def get_gender(): + """ + Returns 'male' or 'female'. + """ + gender = get_memory_at(0xD472) + if gender == 0: + return "male" + elif gender == 1: + return "female" + else: + return gender + + @staticmethod + def get_player_name(): + """ + Returns the 7 characters making up the player's name. + """ + bytez = get_memory_range(0xD47D, 7) + name = translate_chars(bytez) + return name + + @staticmethod + def warp(map_group_id, map_id, x, y): + set_memory_at(0xdcb5, map_group_id) + set_memory_at(0xdcb6, map_id) + set_memory_at(0xdcb7, y) + set_memory_at(0xdcb8, x) + set_memory_at(0xd001, 0xFF) + set_memory_at(0xff9f, 0xF1) + set_memory_at(0xd432, 1) + set_memory_at(0xd434, 0 & 251) + + @staticmethod + def warp_pokecenter(): + crystal.warp(1, 1, 3, 3) + crystal.nstep(200) + + @staticmethod + def masterballs(): + # masterball + set_memory_at(0xd8d8, 1) + set_memory_at(0xd8d9, 99) + + # ultraball + set_memory_at(0xd8da, 2) + set_memory_at(0xd8db, 99) + + # pokeballs + set_memory_at(0xd8dc, 5) + set_memory_at(0xd8dd, 99) + + @staticmethod + def get_text(): + """ + Returns alphanumeric text on the screen. + + Other characters will not be shown. + """ + output = "" + tiles = get_memory_range(0xc4a0, 1000) + for each in tiles: + if each in chars.keys(): + thing = chars[each] + acceptable = False + + if len(thing) == 2: + portion = thing[1:] + else: + portion = thing + + if portion in string.printable: + acceptable = True + + if acceptable: + output += thing + + # remove extra whitespace + output = re.sub(" +", " ", output) + output = output.strip() + + return output + + @staticmethod + def keyboard_apply(button_sequence): + """ + Applies a sequence of buttons to the on-screen keyboard. + """ + for buttons in button_sequence: + press(buttons) + nstep(2) + press([]) + + @staticmethod + def write(something="TrAiNeR"): + """ + Types out a word. + + Uses a planning algorithm to do this in the most efficient way possible. + """ + button_sequence = keyboard.plan_typing(something) + crystal.keyboard_apply([[x] for x in button_sequence]) + + @staticmethod + def set_partymon2(): + """ + This causes corruption, so it's not working yet. + """ + memory = get_memory() + memory[0xdcd7] = 2 + memory[0xdcd9] = 0x7 + + memory[0xdd0f] = 0x7 + memory[0xdd10] = 0x1 + + # moves + memory[0xdd11] = 0x1 + memory[0xdd12] = 0x2 + memory[0xdd13] = 0x3 + memory[0xdd14] = 0x4 + + # id + memory[0xdd15] = 0x1 + memory[0xdd16] = 0x2 + + # experience + memory[0xdd17] = 0x2 + memory[0xdd18] = 0x3 + memory[0xdd19] = 0x4 + + # hp + memory[0xdd1a] = 0x5 + memory[0xdd1b] = 0x6 + + # current hp + memory[0xdd31] = 0x10 + memory[0xdd32] = 0x25 + + # max hp + memory[0xdd33] = 0x10 + memory[0xdd34] = 0x40 + + set_memory(memory) + + @staticmethod + def wait_for_script_running(debug=False, limit=1000): + """ + Wait until ScriptRunning isn't -1. + """ + while limit > 0: + if get_memory_at(0xd438) != 255: + print "script is done executing" + return + else: + step() + + if debug: + limit = limit - 1 + + if limit == 0: + print "limit ran out" + + @staticmethod + def move(cmd): + """ + Attempt to move the player. + """ + press(cmd, holdsteps=10, aftersteps=0) + press([]) + + memory = get_memory() + #while memory[0xd4e1] == 2 and memory[0xd042] != 0x3e: + while memory[0xd043] in [0, 1, 2, 3]: + #while memory[0xd043] in [0, 1, 2, 3] or memory[0xd042] != 0x3e: + nstep(10) + memory = get_memory() + +class TestEmulator(unittest.TestCase): + try: + state = load_state("cheating-12") + except: + if "__name__" == "__main__": + raise Exception("failed to setup unit tests because no save state found") + + def setUp(self): + load_rom() + set_state(self.state) + + def tearDown(self): + shutdown() + + def test_PlaceString(self): + call(0, 0x1078) + + # where to draw the text + registers["hl"] = 0xc4a0 + + # what text to read from + registers["de"] = 0x1276 + + nstep(10) + + text = crystal.get_text() + + self.assertTrue("TRAINER" in text) + +class TestWriter(unittest.TestCase): + def test_very_basic(self): + button_sequence = keyboard.plan_typing("an") + expected_result = ["select", "a", "d", "r", "r", "r", "r", "a"] + + self.assertEqual(len(expected_result), len(button_sequence)) + self.assertEqual(expected_result, button_sequence) + +if __name__ == "__main__": + unittest.main() |