Merge branch 'cogitokat/master' into master.

fixes #152
This commit is contained in:
Bryan Bishop 2013-06-25 23:47:22 -05:00
commit aee96a5cf7
7 changed files with 1429 additions and 1315 deletions

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Finds shared functions between red/crystal.
Find shared functions between red/crystal.
"""
from crystal import (
@ -18,13 +18,13 @@ from romstr import (
def load_rom(path):
"""
Loads a ROM file into an abbreviated RomStr object.
Load a ROM file into an abbreviated RomStr object.
"""
return direct_load_rom(filename=path)
def load_asm(path):
"""
Loads source ASM into an abbreviated AsmList object.
Load source ASM into an abbreviated AsmList object.
"""
return direct_load_asm(filename=path)
@ -63,7 +63,8 @@ found_blobs = []
class BinaryBlob(object):
"""
Stores a label, line number, and addresses of a function from Pokémon Red.
Store a label, line number, and addresses of a function from Pokémon Red.
These details can be used to determine whether or not the function was
copied into Pokémon Crystal.
"""
@ -128,7 +129,7 @@ class BinaryBlob(object):
def parse_from_red(self):
"""
Reads bytes from Pokémon Red and stores them.
Read bytes from Pokémon Red and stores them.
"""
self.bytes = redrom[self.start_address : self.end_address + 1]
@ -146,7 +147,7 @@ class BinaryBlob(object):
def find_in_crystal(self):
"""
Checks whether or not the bytes appear in Pokémon Crystal.
Check whether or not the bytes appear in Pokémon Crystal.
"""
finditer = findall_iter(self.bytes, cryrom)
@ -160,7 +161,7 @@ class BinaryBlob(object):
def find_by_first_bytes(self):
"""
Finds this blob in Crystal based on the first n bytes.
Find this blob in Crystal based on the first n bytes.
"""
# how many bytes to match
@ -194,7 +195,7 @@ redsrc = load_asm(pokered_src_path)
def scan_red_asm(bank_stop=3, debug=True):
"""
Scans the ASM from Pokémon Red. Finds labels and objects. Does things.
Scan the ASM from Pokémon Red. Finds labels and objects. Does things.
Uses get_label_from_line and get_address_from_line_comment.
"""

View File

@ -13,6 +13,11 @@ if not hasattr(json, "read"):
json.read = json.loads
def load_rom(filename="../baserom.gbc"):
"""
Load the specified rom.
If no rom is given, load "../baserom.gbc".
"""
global rom
rom = bytearray(open(filename,'rb').read())
return rom
@ -557,6 +562,11 @@ call_commands = [0xdc, 0xd4, 0xc4, 0xcc, 0xcd]
all_labels = {}
def load_labels(filename="labels.json"):
"""
Load labels from specified file.
If no filename is given, loads 'labels.json'.
"""
global all_labels
# don't re-load labels each time
@ -588,21 +598,28 @@ def find_label(local_address, bank_id=0):
return None
def asm_label(address):
# why using a random value when you can use the address?
"""
Return the ASM label using the address.
"""
return ".ASM_" + hex(address)[2:]
def output_bank_opcodes(original_offset, max_byte_count=0x4000, include_last_address=True, stop_at=[], debug=False):
#fs = current_address
#b = bank_byte
#in = input_data -- rom
#bank_size = byte_count
#i = offset
#ad = end_address
#a, oa = current_byte_number
"""
Output bank opcodes.
# stop_at can be used to supply a list of addresses to not disassemble
# over. This is useful if you know in advance that there are a lot of
# fall-throughs.
fs = current_address
b = bank_byte
in = input_data -- rom
bank_size = byte_count
i = offset
ad = end_address
a, oa = current_byte_number
stop_at can be used to supply a list of addresses to not disassemble
over. This is useful if you know in advance that there are a lot of
fall-throughs.
"""
load_labels()
load_rom()
@ -851,8 +868,9 @@ def output_bank_opcodes(original_offset, max_byte_count=0x4000, include_last_add
def has_outstanding_labels(byte_labels):
"""
If a label is used once in the asm output, then that means it has to be
called or specified later.
Check whether a label is used once in the asm output.
If so, then that means it has to be called or specified later.
"""
for label_line in byte_labels.keys():
real_line = byte_labels[label_line]

View File

@ -16,6 +16,9 @@ if __name__ != "__main__":
def mkdir_p(path):
"""
Make a directory at a given path.
"""
try:
os.makedirs(path)
except OSError as exc: # Python >2.5
@ -25,7 +28,9 @@ def mkdir_p(path):
def hex_dump(input, debug = True):
"""display hex dump in rows of 16 bytes"""
"""
Display hex dump in rows of 16 bytes.
"""
dump = ''
output = ''
@ -52,6 +57,7 @@ def hex_dump(input, debug = True):
# margin
for char in range(margin):
output += ' '
#
for byte in range(bytes_per_line):
output += hex(byte)[2:].zfill(2) + ' '
@ -71,7 +77,9 @@ def hex_dump(input, debug = True):
def get_tiles(image):
"""split a 2bpp image into 8x8 tiles"""
"""
Split a 2bpp image into 8x8 tiles.
"""
tiles = []
tile = []
bytes_per_tile = 16
@ -91,7 +99,9 @@ def get_tiles(image):
def connect(tiles):
"""combine 8x8 tiles into a 2bpp image"""
"""
Combine 8x8 tiles into a 2bpp image.
"""
out = []
for tile in tiles:
for byte in tile:
@ -100,7 +110,9 @@ def connect(tiles):
def transpose(tiles):
"""transpose a tile arrangement along line y=x"""
"""
Transpose a tile arrangement along line y=x.
"""
# horizontal <-> vertical
# 00 01 02 03 04 05 00 06 0c 12 18 1e
@ -184,10 +196,12 @@ lowmax = 1 << 5 # standard 5-bit param
class Compressed:
"""compress 2bpp data"""
"""
Compress 2bpp data.
"""
def __init__(self, image = None, mode = 'horiz', size = None):
assert image, 'need something to compress!'
image = list(image)
self.image = image
@ -231,7 +245,9 @@ class Compressed:
def compress(self):
"""incomplete, but outputs working compressed data"""
"""
Incomplete, but outputs working compressed data.
"""
self.address = 0
@ -308,10 +324,12 @@ class Compressed:
def scanRepeats(self):
"""works, but doesn't do flipped/reversed streams yet
"""
Works, but doesn't do flipped/reversed streams yet.
this takes up most of the compress time and only saves a few bytes
it might be more feasible to exclude it entirely"""
This takes up most of the compress time and only saves a few bytes
it might be more feasible to exclude it entirely.
"""
self.repeats = []
self.flips = []
@ -567,7 +585,8 @@ class Compressed:
class Decompressed:
"""parse compressed 2bpp data
"""
Parse compressed 2bpp data.
parameters:
[compressed 2bpp data]
@ -576,7 +595,8 @@ class Decompressed:
[start] (optional)
splits output into pic [size] and animation tiles if applicable
data can be fed in from rom if [start] is specified"""
data can be fed in from rom if [start] is specified
"""
def __init__(self, lz = None, mode = None, size = None, start = 0):
# todo: play nice with Compressed
@ -615,7 +635,9 @@ class Decompressed:
def decompress(self):
"""replica of crystal's decompression"""
"""
Replica of crystal's decompression.
"""
self.output = []
@ -674,19 +696,25 @@ class Decompressed:
self.getCurByte()
def doLiteral(self):
# copy 2bpp data directly
"""
Copy 2bpp data directly.
"""
for byte in range(self.length):
self.next()
self.output.append(self.byte)
def doIter(self):
# write one byte repeatedly
"""
Write one byte repeatedly.
"""
self.next()
for byte in range(self.length):
self.output.append(self.byte)
def doAlt(self):
# write alternating bytes
"""
Write alternating bytes.
"""
self.alts = []
self.next()
self.alts.append(self.byte)
@ -697,25 +725,34 @@ class Decompressed:
self.output.append(self.alts[byte&1])
def doZeros(self):
# write zeros
"""
Write zeros.
"""
for byte in range(self.length):
self.output.append(0x00)
def doFlip(self):
# repeat flipped bytes from 2bpp output
# eg 11100100 -> 00100111
# quat 3 2 1 0 -> 0 2 1 3
"""
Repeat flipped bytes from 2bpp output.
eg 11100100 -> 00100111
quat 3 2 1 0 -> 0 2 1 3
"""
for byte in range(self.length):
flipped = sum(1<<(7-i) for i in range(8) if self.output[self.displacement+byte]>>i&1)
self.output.append(flipped)
def doReverse(self):
# repeat reversed bytes from 2bpp output
"""
Repeat reversed bytes from 2bpp output.
"""
for byte in range(self.length):
self.output.append(self.output[self.displacement-byte])
def doRepeat(self):
# repeat bytes from 2bpp output
"""
Repeat bytes from 2bpp output.
"""
for byte in range(self.length):
self.output.append(self.output[self.displacement+byte])
@ -741,7 +778,9 @@ sizes = [
]
def make_sizes():
"""front pics have specified sizes"""
"""
Front pics have specified sizes.
"""
top = 251
base_stats = 0x51424
# print monster sizes
@ -955,7 +994,9 @@ def decompress_misc():
to_file(filename, gfx.output)
def decompress_all(debug = False):
"""decompress all known compressed data in baserom"""
"""
Decompress all known compressed data in baserom.
"""
if debug: print 'fronts'
decompress_monsters(front)
@ -988,7 +1029,9 @@ def decompress_all(debug = False):
def decompress_from_address(address, mode='horiz', filename = 'de.2bpp', size = None):
"""write decompressed data from an address to a 2bpp file"""
"""
Write decompressed data from an address to a 2bpp file.
"""
image = Decompressed(rom, mode, size, address)
to_file(filename, image.pic)
@ -1034,7 +1077,9 @@ def compress_monster_frontpic(id, fileout):
def get_uncompressed_gfx(start, num_tiles, filename):
"""grab tiles directly from rom and write to file"""
"""
Grab tiles directly from rom and write to file.
"""
bytes_per_tile = 0x10
length = num_tiles*bytes_per_tile
end = start + length
@ -1139,7 +1184,7 @@ def dump_trainer_pals():
def flatten(planar):
"""
Flattens planar 2bpp image data into a quaternary pixel map.
Flatten planar 2bpp image data into a quaternary pixel map.
"""
strips = []
for pair in range(len(planar)/2):
@ -1155,7 +1200,7 @@ def flatten(planar):
def to_lines(image, width):
"""
Converts a tiled quaternary pixel map to lines of quaternary pixels.
Convert a tiled quaternary pixel map to lines of quaternary pixels.
"""
tile = 8 * 8
@ -1208,7 +1253,7 @@ def png_pal(filename):
def to_png(filein, fileout=None, pal_file=None, height=None, width=None):
"""
Takes a planar 2bpp graphics file and converts it to png.
Take a planar 2bpp graphics file and converts it to png.
"""
if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.png'
@ -1296,7 +1341,7 @@ def to_png(filein, fileout=None, pal_file=None, height=None, width=None):
def to_2bpp(filein, fileout=None, palout=None):
"""
Takes a png and converts it to planar 2bpp.
Take a png and converts it to planar 2bpp.
"""
if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.2bpp'
@ -1500,7 +1545,7 @@ def append_terminator_to_lzs(directory):
def lz_to_png_by_file(filename):
"""
Converts a lz file to png. Dumps a 2bpp file too.
Convert a lz file to png. Dump a 2bpp file too.
"""
assert filename[-3:] == ".lz"
lz_data = open(filename, "rb").read()
@ -1511,8 +1556,9 @@ def lz_to_png_by_file(filename):
def dump_tileset_pngs():
"""
Converts .lz format tilesets into .png format tilesets. Also, leaves a
bunch of wonderful .2bpp files everywhere for your amusement.
Convert .lz format tilesets into .png format tilesets.
Also, leaves a bunch of wonderful .2bpp files everywhere for your amusement.
"""
for tileset_id in range(37):
tileset_filename = "../gfx/tilesets/" + str(tileset_id).zfill(2) + ".lz"

View File

@ -1,6 +1,11 @@
import gfx
def rip_sprites_from_bank(bank, offset=0):
"""
Rips sprites from specified bank.
Sprites are 4x4.
"""
file_handler = open("../gfx/overworld/bank" + str(hex(bank))[2:] + ".asm", "w")
for sprite in range(0 + offset, 256 + offset):
filename = "../gfx/overworld/" + str(sprite).zfill(3) + ".2bpp"

View File

@ -50,7 +50,7 @@ class RomStr(str):
@classmethod
def load(cls, filename=None, crystal=True, red=False):
"""
Loads a ROM into a RomStr.
Load a ROM into a RomStr.
"""
if crystal and not red and not filename:
file_handler = open("../baserom.gbc", "r")
@ -66,8 +66,10 @@ class RomStr(str):
def load_labels(self, filename="labels.json"):
"""
Loads labels from labels.json, or parses the source code file and
generates new labels.
Loads labels from labels.json.
(Or parses the source code file and
generates new labels.)
"""
filename = os.path.join(os.path.dirname(__file__), filename)
@ -114,8 +116,9 @@ class RomStr(str):
def get_address_for(self, label):
"""
Returns the address of a label. This is slow and could be improved
dramatically.
Return the address of a label.
This is slow and could be improved dramatically.
"""
label = str(label)
for address in self.labels.keys():
@ -137,7 +140,7 @@ class RomStr(str):
def interval(self, offset, length, strings=True, debug=True):
"""
returns hex values for the rom starting at offset until offset+length
Return hex values for the rom starting at offset until offset+length.
"""
returnable = []
for byte in self[offset:offset+length]:
@ -149,16 +152,17 @@ class RomStr(str):
def until(self, offset, byte, strings=True, debug=False):
"""
Returns hex values from rom starting at offset until the given byte.
Return hex values from rom starting at offset until the given byte.
"""
return self.interval(offset, self.find(chr(byte), offset) - offset, strings=strings)
def to_asm(self, address, end_address=None, size=None, max_size=0x4000, debug=None):
"""
Disassembles ASM at some address. This will stop disassembling when
either the end_address or size is met. Also, there's a maximum size
that will be parsed, so that large patches of data aren't parsed as
code.
Disassemble ASM at some address.
This will stop disassembling when either the end_address or size is
met. Also, there's a maximum size that will be parsed, so that large
patches of data aren't parsed as code.
"""
if type(address) in [str, unicode] and "0x" in address:
address = int(address, 16)

View File

@ -6,13 +6,15 @@ 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 ant jython
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/java/
cd vba-clojure/
./dl-libs.sh
cd java/
ant all
cd ..
autoreconf -i
@ -28,6 +30,9 @@ Make sure vba-clojure is available within "java.library.path":
$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/.
@ -114,15 +119,19 @@ if not os.path.exists(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.
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)
@ -150,8 +159,9 @@ a, b, r, l, u, d, select, start, restart = "a", "b", "r", "l", "u", "d", "select
def button_combiner(buttons):
"""
Combines multiple button presses into an integer. This is used when sending
a keypress to the emulator.
Combines multiple button presses into an integer.
This is used when sending a keypress to the emulator.
"""
result = 0
@ -186,8 +196,9 @@ def button_combiner(buttons):
def load_rom(path=None):
"""
Starts the emulator with a certain ROM. Defaults to rom_path if no
parameters are given.
Starts the emulator with a certain ROM.
Defaults to rom_path if no parameters are given.
"""
if path == None:
path = rom_path
@ -204,8 +215,9 @@ def load_rom(path=None):
def shutdown():
"""
Stops the emulator. Closes the window. The "opposite" of this is the
load_rom function.
Stops the emulator. Closes the window.
The "opposite" of this is the load_rom function.
"""
Gb.shutdown()
@ -239,8 +251,9 @@ def translate_chars(charz):
def _create_byte_buffer(data):
"""
Converts data into a ByteBuffer. This is useful for interfacing with the Gb
class.
Converts data into a ByteBuffer.
This is useful for interfacing with the Gb class.
"""
buf = ByteBuffer.allocateDirect(len(data))
if isinstance(data[0], int):
@ -253,9 +266,11 @@ def _create_byte_buffer(data):
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
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))
@ -274,7 +289,9 @@ def get_state():
def save_state(name, state=None, override=False):
"""
Saves the given state to save_state_path. The file format must be ".sav"
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:
@ -296,7 +313,9 @@ def save_state(name, state=None, override=False):
def load_state(name):
"""
Reads a state from file based on name. Looks in save_state_path for a file
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)
@ -321,8 +340,9 @@ def generate_root():
def get_root():
"""
Loads the root state, or restarts the emulator and creates a new root
state.
Loads the root state.
(Or restarts the emulator and creates a new root state.)
"""
try:
root = load_state("root")
@ -377,15 +397,17 @@ def get_memory():
def set_memory(memory):
"""
Sets memory in the emulator. Use get_memory() to retrieve the current
state.
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.
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")
@ -396,9 +418,10 @@ def get_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.
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)
@ -430,14 +453,18 @@ def get_memory_range(start_address, byte_count):
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.
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
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__"):
@ -456,7 +483,9 @@ def press(buttons, holdsteps=1, aftersteps=1):
def get_buttons():
"""
Returns the currentButtons[0] value (an integer with bits set for which
Returns the currentButtons[0] value
(an integer with bits set for which
buttons are currently pressed).
"""
return Gb.getCurrentButtons()
@ -708,6 +737,7 @@ class cheats:
Gb.cheatAddGameshark(code, description)
class crystal:
"""
Just a simple namespace to store a bunch of functions for Pokémon Crystal.
"""
@ -715,8 +745,7 @@ class crystal:
@staticmethod
def text_wait(step_size=10, max_wait=500):
"""
Watches for a sign that text is done being drawn to screen, then
presses the "A" button.
Presses the "A" button when text is done being drawn to screen.
:param step_size: number of steps per wait loop
:param max_wait: number of wait loops to perform
@ -744,10 +773,12 @@ class crystal:
@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.
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)
@ -761,8 +792,9 @@ class crystal:
@staticmethod
def nstep(steplimit=500):
"""
Steps the CPU forward and calls some functions in between each step,
like to manipulate memory. This is pretty slow.
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()
@ -806,6 +838,7 @@ class crystal:
def get_xy():
"""
(x, y) coordinates of player on map.
Relative to top-left corner of map.
"""
x = get_memory_at(0xdcb8)
@ -815,9 +848,10 @@ class crystal:
@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.
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.
"""
@ -902,8 +936,9 @@ class crystal:
@staticmethod
def get_text():
"""
Returns alphanumeric text on the screen. Other characters will not be
shown.
Returns alphanumeric text on the screen.
Other characters will not be shown.
"""
output = ""
tiles = get_memory_range(0xc4a0, 1000)
@ -942,8 +977,9 @@ class crystal:
@staticmethod
def write(something="TrAiNeR"):
"""
Uses a planning algorithm to type out a word in the most efficient way
possible.
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])

View File

@ -26,8 +26,10 @@ def main():
def skippable(func):
"""
Makes a function skippable by saving the state before and after the
function runs. Pass "skip=True" to the function to load the previous save
Makes a function skippable.
Saves the state before and after the function runs.
Pass "skip=True" to the function to load the previous save
state from when the function finished.
"""
def wrapped_function(*args, **kwargs):
@ -433,6 +435,8 @@ def handle_elm(starter_choice):
@skippable
def new_bark_level_grind(level):
"""
Do level grinding in New Bark.
Starting just outside of Elm's Lab, do some level grinding until the first
partymon level is equal to the given value..
"""