diff --git a/INSTALL.md b/INSTALL.md index 0443412f0..68d5c09b8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,21 +1,112 @@ # Getting Started -Compiling requires a certain Pokemon Crystal ROM: +Compiling **pokecrystal.gbc** requires a certain **Pokemon Crystal** rom: ``` Pokemon - Crystal Version (UE) (V1.0) [C][!].gbc md5: 9f2922b235a5eeb78d65594e82ef5dde ``` -Save it as `baserom.gbc` in the repository. +Save it as **baserom.gbc** in the repository. -Feel free to ask us on nucleus.kafuka.org #skeetendo if something goes wrong (remember to tell where)! +Feel free to ask us on **[nucleus.kafuka.org #skeetendo](https://kiwiirc.com/client/irc.nolimitzone.com/?#skeetendo)** if something goes wrong! -Don't know how to use IRC? Try [mibbit](http://chat.mibbit.com/?server=nucleus.kafuka.org&channel=#skeetendo). +# Windows + +If you are on Windows and can't install Linux, **Cygwin** is a great alternative. + +## Installing Cygwin + +Cygwin provides a virtual Linux environment on Windows systems. Just get **setup.exe**. + +**http://cygwin.com/install.html** + +During the install: + +* Keep the defaults. + +* Most mirrors are molasses. Use **http://mirrors.kernel.org**. -## Linux +## Using Cygwin + +Launch the **Cygwin terminal**. Maybe you know your way around the Linux terminal, **bash**. If not, a crash course: +```bash +# list files in current directory +ls + +# show current directory +pwd + +# change directory +cd /away/we/go +``` + + +## Getting up and running + +We need a couple more things to be able to compile. + +If you're feeling lazy, just paste these commands into your terminal. + +```bash +apt-cyg install make git wget python python-setuptools +easy_install pip +``` + +**rgbds** will let you compile Game Boy roms. + +```bash +cd ~ + +# download rgbds binaries +wget http://diyhpl.us/~bryan/irc/pokered/rgbds/rgbds.zip +unzip rgbds.zip +rm rgbds.zip + +# make rgbds accessible for all time +export PATH=$PATH:`pwd`/rgbds +echo "export PATH=$PATH" >> ~/.bashrc +``` + +Set up the **pokecrystal** repository: + +```bash +git clone https://github.com/kanzure/pokecrystal +cd pokecrystal + +# install python requirements +pip install -r requirements.txt +``` + +## Don't forget baserom.gbc!! + +Make sure you downloaded a base rom. Name it **baserom.gbc**. + +Now you should be able to build **pokecrystal.gbc** for the first time. + +This compiles a new rom from the source code, with any patches filled in from the base rom. +```bash +make +``` + +This ought to take **between 3 and 15 seconds**, depending on your computer. + +If you see `cmp baserom.gbc pokecrystal.gbc` as the last line, the build was successful! + +Your first compile processes every source file at once. After that, **only modified source files have to be reprocessed**, so compiling again should be a few seconds faster. + +Other **make targets** that may come in handy: + +`make clean` deletes any preprocessed source files (.tx), rgbds object files and pokecrystal.gbc, in case something goes wrong. + +`make pngs` decompresses any **lz** files in gfx/ and then exports any graphics files to **png**. + +`make lzs` does the reverse. This is already part of the build process, so **modified pngs will automatically be converted to 2bpp and lz-compressed** without any additional work. + + +# Linux ```bash sudo apt-get install make gcc bison git python python-setuptools @@ -46,36 +137,21 @@ pip install -r requirements.txt git config diff.hex.textconv hexdump ``` -To compile the ROM from ASM source: -``` -make clean && make +To compile the rom from asm source: +```bash +make ``` -That will take between 3 and 15 seconds, depending on your computer. If you see -`cmp baserom.gbc pokecrystal.gbc` as the last line, the build was successful! Rejoice! +That will take between 3 and 15 seconds, depending on your computer. If you see `cmp baserom.gbc pokecrystal.gbc` as the last line, the build was successful! Rejoice! -## Windows +# Now what? -Set up [GitHub for Windows](http://windows.github.com/) and clone this repository. +**main.asm** is a good starting point. The structure of the source is laid out here. -If you haven't already, get [Python 2.7](http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi) ([64-bit](http://www.python.org/ftp/python/2.7.3/python-2.7.3.amd64.msi)). +* **Can't find something?** Anyone can add to the source. There's lots to be uncovered. -Extract the following files from the [RGBDS](https://github.com/downloads/bentley/rgbds/rgbds-0.0.1.zip) package into the repository: -`rgbasm.exe` -`rgbds.exe` -`rgbfix.exe` -`rgblink.exe` +* **Do your own thing!** The asm source is hack-friendly, and the supplementary scripts in extras/ can be used for other projects. -Install [make](http://gnuwin32.sourceforge.net/downlinks/make.php) for Windows. - -To compile the ROM from ASM source, run `pokecrystal.bat`. - -That will take between 3 and 15 seconds, depending on your computer. If you see -`FC: no differences encountered`, the build was successful! Rejoice! - -Now you may try messing around with `main.asm`, or just do whatever you wanted to. - - -# Contributions are welcome! +* We'll be happy to answer any **questions** on **[nucleus.kafuka.org #skeetendo](https://kiwiirc.com/client/irc.nolimitzone.com/?#skeetendo)**. diff --git a/Makefile b/Makefile index 15fa7f5c1..37e9aef4f 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,24 @@ .SUFFIXES: .asm .tx .o .gbc .png .2bpp .lz -TEXTFILES = \ - text/sweethoney.tx \ - text/phone/bill.tx \ - text/phone/elm.tx \ - text/phone/mom.tx \ - text/phone/trainers1.tx \ - text/common.tx \ - text/common_2.tx \ - text/common_3.tx \ - main.tx +TEXTFILES := $(shell find ./ -type f -name '*.asm' | grep -v pokecrystal.asm | grep -v constants.asm | grep -v gbhw.asm | grep -v hram.asm | grep -v constants | grep -v wram.asm) +TEXTQUEUE := -PNG_GFX = $(shell find gfx/ -type f -name '*.png') -LZ_GFX = $(shell find gfx/ -type f -name '*.lz') -TWOBPP_GFX = $(shell find gfx/ -type f -name '*.2bpp') +PNG_GFX := $(shell find gfx/ -type f -name '*.png') +LZ_GFX := $(shell find gfx/ -type f -name '*.lz') +TWOBPP_GFX := $(shell find gfx/ -type f -name '*.2bpp') all: pokecrystal.gbc cmp baserom.gbc $< clean: - rm -f main.tx pokecrystal.o pokecrystal.gbc ${TEXTFILES} -pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} lzs + rm -f pokecrystal.o pokecrystal.gbc + @echo 'rm -f $(TEXTFILES:.asm=.tx)' + @rm -f $(TEXTFILES:.asm=.tx) +pokecrystal.o: $(TEXTFILES:.asm=.tx) $(LZ_GFX) $(TWOBPP_GFX) + python prequeue.py $(TEXTQUEUE) rgbasm -o pokecrystal.o pokecrystal.asm - .asm.tx: - python preprocessor.py < $< > $@ + $(eval TEXTQUEUE := $(TEXTQUEUE) $<) + @rm $@ pokecrystal.gbc: pokecrystal.o rgblink -o $@ $< @@ -33,6 +28,7 @@ pngs: cd extras && python gfx.py mass-decompress && python gfx.py dump-pngs lzs: $(LZ_GFX) $(TWOBPP_GFX) + @: gfx/pics/%/front.lz: gfx/pics/%/front.png gfx/pics/%/tiles.2bpp python extras/gfx.py png-to-lz --front $^ diff --git a/README.md b/README.md index f8b59dddd..6adb65625 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The source code in this project successfully converts back into a ROM image. All ## Base ROM -The following ROM is required for compiling: +The following rom is required for compiling: ``` Pokemon - Crystal Version (UE) (V1.0) [C][!].gbc @@ -17,16 +17,18 @@ md5: 9f2922b235a5eeb78d65594e82ef5dde Eventually this will not be necessary. -## See also - -* disassembly of [Pokémon Red](http://bitbucket.org/iimarckus/pokered). - - -## Contributing - -* Hang out with us on IRC: `nucleus.kafuka.org #skeetendo` (for example, by -using [mibbit](http://chat.mibbit.com/)). +## What can I do? * Are we missing something? Make a pull request! Contributions are welcome. -* Tackle some [issues](https://github.com/kanzure/pokecrystal/issues)! +* Take a look at some of the disasm tools in **extras/**. Most of the scripts are generalized enough to take apart other Game Boy games. + +* Tackle some **[issues](https://github.com/kanzure/pokecrystal/issues)**! + + +## See also + +* Hang out with us on irc: **[nucleus.kafuka.org #skeetendo](https://kiwiirc.com/client/irc.nolimitzone.com/?#skeetendo)** + +* Disassembly of **[Pokémon Red](http://bitbucket.org/iimarckus/pokered)**. + diff --git a/preprocessor.py b/preprocessor.py index 18e96dff0..468ae41a5 100644 --- a/preprocessor.py +++ b/preprocessor.py @@ -310,24 +310,14 @@ def separate_comment(l): """ Separates asm and comments on a single line. """ - - asm = "" - comment = None in_quotes = False - - # token either belongs to the line or to the comment - for token in l: - if comment: - comment += token - else: - if not in_quotes: - if token == ";": - comment = ";" - continue - if token == "\"": - in_quotes = not in_quotes - asm += token - return asm, comment + for i in xrange(len(l)): + if not in_quotes: + if l[i] == ";": + break + if l[i] == "\"": + in_quotes = not in_quotes + return l[:i], l[i:] or None def quote_translator(asm): """ @@ -335,63 +325,50 @@ def quote_translator(asm): """ # split by quotes - asms = asm.split("\"") + asms = asm.split('"') # skip asm that actually does use ASCII in quotes - lowasm = asms[0].lower() - - if "section" in lowasm \ - or "incbin" in lowasm: - sys.stdout.write(asm) - return + if "SECTION" in asms[0]\ + or "INCBIN" in asms[0]\ + or "INCLUDE" in asms[0]: + return asm print_macro = False if asms[0].strip() == 'print': asms[0] = asms[0].replace('print','db 0,') print_macro = True - output = "" + output = '' even = False - i = 0 for token in asms: - i = i + 1 - if even: characters = [] # token is a string to convert to byte values while len(token): # read a single UTF-8 codepoint char = token[0] - if ord(char) >= 0xFC: - char = char + token[1:6] - token = token[6:] - elif ord(char) >= 0xF8: - char = char + token[1:5] - token = token[5:] - elif ord(char) >= 0xF0: - char = char + token[1:4] - token = token[4:] - elif ord(char) >= 0xE0: - char = char + token[1:3] - token = token[3:] - elif ord(char) >= 0xC0: + if ord(char) < 0xc0: + token = token[1:] + # certain apostrophe-letter pairs are considered a single character + if char == "'" and token: + if token[0] in 'dlmrstv': + char += token[0] + token = token[1:] + elif ord(char) < 0xe0: char = char + token[1:2] token = token[2:] + elif ord(char) < 0xf0: + char = char + token[1:3] + token = token[3:] + elif ord(char) < 0xf8: + char = char + token[1:4] + token = token[4:] + elif ord(char) < 0xfc: + char = char + token[1:5] + token = token[5:] else: - token = token[1:] - - # certain apostrophe-letter pairs are only a single byte - if char == "'" and len(token) > 0 and \ - (token[0] == "d" or \ - token[0] == "l" or \ - token[0] == "m" or \ - token[0] == "r" or \ - token[0] == "s" or \ - token[0] == "t" or \ - token[0] == "v"): - char = char + token[0] - token = token[1:] - + char = char + token[1:6] + token = token[6:] characters += [char] if print_macro: @@ -421,32 +398,26 @@ def quote_translator(asm): output += ", ".join(["${0:02X}".format(chars[char]) for char in characters]) - # if not even else: - output += (token) + output += token even = not even - sys.stdout.write(output) - - return + return output def extract_token(asm): - token = asm.split(" ")[0].replace("\t", "").replace("\n", "") - return token + return asm.split(" ")[0].strip() def make_macro_table(): - return dict([(macro.macro_name, macro) for macro in macros]) + return dict(((macro.macro_name, macro) for macro in macros)) macro_table = make_macro_table() def macro_test(asm): """ Returns a matching macro, or None/False. """ - # macros are determined by the first symbol on the line token = extract_token(asm) - # check against all names if token in macro_table: return (macro_table[token], token) @@ -600,64 +571,46 @@ def macro_translator(macro, token, line): sys.stdout.write(output) -def include_file(asm): - """This is more reliable than rgbasm/rgbds including files on its own.""" - - prefix = asm.split("INCLUDE \"")[0] + '\n' - filename = asm.split("\"")[1] - suffix = asm.split("\"")[2] - - read_line(prefix) - - lines = open(filename, "r").readlines() - - for line in lines: - read_line(line) - - read_line(suffix) - def read_line(l): """Preprocesses a given line of asm.""" - # strip and store any comment on this line - if ";" in l: - asm, comment = separate_comment(l) - else: - asm = l - comment = None + # strip comments from asm + asm, comment = separate_comment(l) - # handle INCLUDE as a special case - if "INCLUDE \"" in l: - include_file(asm) + # export all labels + if ':' in asm[:asm.find('"')]: + sys.stdout.write('GLOBAL ' + asm.split(':')[0] + '\n') + + # expect preprocessed .asm files + if "INCLUDE" in asm: + asm = asm.replace('.asm','.tx') + sys.stdout.write(asm) # ascii string macro preserves the bytes as ascii (skip the translator) - elif len(asm) > 6 and "\tascii " in [asm[:7], "\t" + asm[:6]]: + elif len(asm) > 6 and "ascii " == asm[:6] or "\tascii " == asm[:7]: asm = asm.replace("ascii", "db", 1) sys.stdout.write(asm) # convert text to bytes when a quote appears (not in a comment) elif "\"" in asm: - quote_translator(asm) + sys.stdout.write(quote_translator(asm)) # check against other preprocessor features else: macro, token = macro_test(asm) - if macro: macro_translator(macro, token, asm) else: sys.stdout.write(asm) - # show line comment - if comment != None: - sys.stdout.write(comment) + if comment: sys.stdout.write(comment) def preprocess(lines=None): """Main entry point for the preprocessor.""" if not lines: # read each line from stdin - lines = sys.stdin + lines = (sys.stdin.readlines()) elif not isinstance(lines, list): # split up the input into individual lines lines = lines.split("\n") diff --git a/prequeue.py b/prequeue.py new file mode 100644 index 000000000..156d3e942 --- /dev/null +++ b/prequeue.py @@ -0,0 +1,17 @@ +# coding: utf-8 + +# Starting a new python process to preprocess each source file +# creates too much overhead. Instead, a list of files to preprocess +# is fed into a script run from a single process. + +import os +import sys +import preprocessor + +if __name__ == '__main__': + for source in sys.argv[1:]: + dest = os.path.splitext(source)[0] + '.tx' + sys.stdin = open(source, 'r') + sys.stdout = open(dest, 'w') + preprocessor.preprocess() +