mirror of
https://github.com/pret/pokemon-reverse-engineering-tools.git
synced 2026-08-21 17:54:08 -05:00
Merge pull request #21 from kanzure/vba-automation
Start moving away from jvm/jython.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
include README.md
|
||||
include vba_autoplayer.py vba_config.py vba_keyboard.py vba.py
|
||||
|
||||
global-exclude *.pyc
|
||||
global-exclude .gitignore
|
||||
|
||||
@@ -31,6 +31,14 @@ class Config(object):
|
||||
if "path" not in self._config:
|
||||
self._config["path"] = os.getcwd()
|
||||
|
||||
# vba save states go into ./save-states/
|
||||
if "save_state_path" not in self._config:
|
||||
self._config["save_state_path"] = os.path.join(self._config["path"], "save-states/")
|
||||
|
||||
# assume rom is at ./baserom.gbc
|
||||
if "rom" not in self._config:
|
||||
self._config["rom_path"] = os.path.join(self._config["path"], "baserom.gbc")
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""
|
||||
Grab the value from the class properties, then check the configuration,
|
||||
|
||||
7
pokemontools/vba/__init__.py
Normal file
7
pokemontools/vba/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
pokecrystal/pokered VBA automation module
|
||||
|
||||
dependencies:
|
||||
python-vba-wrapper (vba_wrapper)
|
||||
vba-linux
|
||||
"""
|
||||
@@ -1,15 +1,3 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
This file constructs a networkx.DiGraph object called graph, which can be used
|
||||
to find the shortest path of keypresses on the keyboard to type a word.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import networkx
|
||||
|
||||
graph = networkx.DiGraph()
|
||||
|
||||
graph_data = """
|
||||
A a select
|
||||
A B r
|
||||
A I l
|
||||
@@ -510,53 +498,4 @@ z Z select
|
||||
z q u
|
||||
z PK d
|
||||
z y l
|
||||
z space-lower-x8-y2 r"""
|
||||
|
||||
for line in graph_data.split("\n"):
|
||||
if line == "":
|
||||
continue
|
||||
elif line[0] == "#":
|
||||
continue
|
||||
|
||||
(node1, node2, edge_name) = line.split(" ")
|
||||
graph.add_edge(node1, node2, key=edge_name)
|
||||
|
||||
#print "Adding edge ("+edge_name+") "+node1+" -> "+node2
|
||||
|
||||
def shortest_path(node1, node2):
|
||||
"""
|
||||
Figures out the shortest list of button presses to move from one letter to
|
||||
another.
|
||||
"""
|
||||
buttons = []
|
||||
last = None
|
||||
path = networkx.shortest_path(graph, node1, node2)
|
||||
for each in path:
|
||||
if last != None:
|
||||
buttons.append(convert_nodes_to_button_press(last, each))
|
||||
last = each
|
||||
return buttons
|
||||
#return [convert_nodes_to_button_press(node3, node4) for (node3, node4) in zip(*(iter(networkx.shortest_path(graph, node1, node2)),) * 2)]
|
||||
|
||||
def convert_nodes_to_button_press(node1, node2):
|
||||
"""
|
||||
Determines the button necessary to switch from node1 to node2.
|
||||
"""
|
||||
print "getting button press for state transition: " + node1 + " -> " + node2
|
||||
return graph.get_edge_data(node1, node2)["key"]
|
||||
|
||||
def plan_typing(text, current="A"):
|
||||
"""
|
||||
Plans a sequence of button presses to spell out the given text.
|
||||
"""
|
||||
buttons = []
|
||||
for target in text:
|
||||
if target == current:
|
||||
buttons.append("a")
|
||||
else:
|
||||
print "Finding the shortest path between " + current + " and " + target
|
||||
more_buttons = shortest_path(current, target)
|
||||
buttons.extend(more_buttons)
|
||||
buttons.append("a")
|
||||
current = target
|
||||
return buttons
|
||||
z space-lower-x8-y2 r
|
||||
64
pokemontools/vba/keyboard.py
Normal file
64
pokemontools/vba/keyboard.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This file constructs a networkx.DiGraph object called graph, which can be used
|
||||
to find the shortest path of keypresses on the keyboard to type a word.
|
||||
"""
|
||||
|
||||
import os
|
||||
import itertools
|
||||
import networkx
|
||||
|
||||
graph = networkx.DiGraph()
|
||||
|
||||
# load graph data from file
|
||||
data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyboard.data")
|
||||
graph_data = open(data_path, "r").read()
|
||||
|
||||
for line in graph_data.split("\n"):
|
||||
if line == "":
|
||||
continue
|
||||
elif line[0] == "#":
|
||||
continue
|
||||
|
||||
(node1, node2, edge_name) = line.split(" ")
|
||||
graph.add_edge(node1, node2, key=edge_name)
|
||||
|
||||
#print "Adding edge ("+edge_name+") "+node1+" -> "+node2
|
||||
|
||||
def shortest_path(node1, node2):
|
||||
"""
|
||||
Figures out the shortest list of button presses to move from one letter to
|
||||
another.
|
||||
"""
|
||||
buttons = []
|
||||
last = None
|
||||
path = networkx.shortest_path(graph, node1, node2)
|
||||
for each in path:
|
||||
if last != None:
|
||||
buttons.append(convert_nodes_to_button_press(last, each))
|
||||
last = each
|
||||
return buttons
|
||||
#return [convert_nodes_to_button_press(node3, node4) for (node3, node4) in zip(*(iter(networkx.shortest_path(graph, node1, node2)),) * 2)]
|
||||
|
||||
def convert_nodes_to_button_press(node1, node2):
|
||||
"""
|
||||
Determines the button necessary to switch from node1 to node2.
|
||||
"""
|
||||
print "getting button press for state transition: " + node1 + " -> " + node2
|
||||
return graph.get_edge_data(node1, node2)["key"]
|
||||
|
||||
def plan_typing(text, current="A"):
|
||||
"""
|
||||
Plans a sequence of button presses to spell out the given text.
|
||||
"""
|
||||
buttons = []
|
||||
for target in text:
|
||||
if target == current:
|
||||
buttons.append("a")
|
||||
else:
|
||||
print "Finding the shortest path between " + current + " and " + target
|
||||
more_buttons = shortest_path(current, target)
|
||||
buttons.extend(more_buttons)
|
||||
buttons.append("a")
|
||||
current = target
|
||||
return buttons
|
||||
527
pokemontools/vba/vba.py
Normal file
527
pokemontools/vba/vba.py
Normal file
@@ -0,0 +1,527 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VBA automation
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import string
|
||||
from copy import copy
|
||||
|
||||
import unittest
|
||||
|
||||
# for converting bytes to readable text
|
||||
from pokemontools.chars import chars
|
||||
|
||||
from pokemontools.map_names import map_names
|
||||
|
||||
import keyboard
|
||||
|
||||
# just use a default config for now until the globals are removed completely
|
||||
import pokemontools.config as conf
|
||||
config = conf.Config()
|
||||
project_path = config.path
|
||||
save_state_path = config.save_state_path
|
||||
rom_path = config.rom_path
|
||||
|
||||
if not os.path.exists(rom_path):
|
||||
raise Exception("rom_path is not configured properly; edit vba_config.py? " + str(rom_path))
|
||||
|
||||
import vba_wrapper
|
||||
|
||||
vba = vba_wrapper.VBA(rom_path)
|
||||
registers = vba_wrapper.core.registers.Registers(vba)
|
||||
|
||||
button_masks = vba_wrapper.core.VBA.button_masks
|
||||
button_combiner = vba_wrapper.core.VBA.button_combine
|
||||
|
||||
def translate_chars(charz):
|
||||
result = ""
|
||||
for each in charz:
|
||||
result += chars[each]
|
||||
return result
|
||||
|
||||
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 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
|
||||
|
||||
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):
|
||||
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()
|
||||
@@ -2,3 +2,6 @@
|
||||
|
||||
# testing
|
||||
mock
|
||||
|
||||
# emulation
|
||||
vba_wrapper==0.0.2
|
||||
|
||||
2
setup.py
2
setup.py
@@ -18,6 +18,8 @@ if sys.argv[-1] == 'publish':
|
||||
# dependencies.
|
||||
requires = [
|
||||
"mock",
|
||||
"nose",
|
||||
"vba_wrapper==0.0.2",
|
||||
]
|
||||
|
||||
setup(
|
||||
|
||||
@@ -4,12 +4,12 @@ Tests for VBA automation tools
|
||||
|
||||
import unittest
|
||||
|
||||
import vba
|
||||
import pokemontools.vba.vba as vba
|
||||
|
||||
try:
|
||||
import vba_autoplayer
|
||||
import pokemontools.vba.vba_autoplayer
|
||||
except ImportError:
|
||||
import autoplayer as vba_autoplayer
|
||||
import pokemontools.vba.autoplayer as vba_autoplayer
|
||||
|
||||
vba_autoplayer.vba = vba
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/jython
|
||||
# -*- encoding: utf-8 -*-
|
||||
import os
|
||||
|
||||
# by default we assume the user has vba in pokecrystal/extras
|
||||
project_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..'))
|
||||
|
||||
# save states are in pokecrystal/save-states/
|
||||
save_state_path = os.path.join(project_path, "save-states")
|
||||
|
||||
# where is your rom?
|
||||
rom_path = os.path.join(project_path, "baserom.gbc")
|
||||
Reference in New Issue
Block a user