{ "info": { "author": "Bryan Bishop", "author_email": "kanzure@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "Pok\u00e9mon Crystal utilities and extras\n==============================\n\n`crystal.py` parses the ROM and provides convenient classes to dump human-readable ASM with the global `to_asm()` method. This ASM can then be compiled back into the original ROM. Currently it parses map headers, \"second\" map headers, map event headers, map script headers, map triggers, map \"callbacks\", map blockdata, xy triggers, warps, people-events, texts and scripts.\n\n#### Simple ASM generation example\n\nNote: throughout these examples it is possible to use `reload(crystal)` instead of `import pokemontools.crystal`. Once the module is loaded a first time, it must be reloaded if the file changes and the updates are desired.\n\n```python\nimport pokemontools.crystal as crystal\n\n# parse the ROM\ncrystal.run_main()\n\n# create a new dump\nasm = crystal.Asm()\n\n# insert the first 10 maps\nx = 10\nasm.insert_with_dependencies(crystal.all_map_headers[:x])\n\n# dump to extras/output.txt\nasm.dump()\n```\n\nAfter running those lines, `cp extras/output.txt main.asm` and run `git diff main.asm` to confirm that changes to `main.asm` have occurred. To test whether or not the newly inserted ASM compiles into the same ROM, use: `make clean && make`. This will complain very loudly if something is broken.\n\n#### Testing\n\nUnit tests cover most of the classes.\n\n```bash\npython tests.py\n```\n\n#### Parsing a script at a known address\n\nHere is a demo of how to investigate a particular script, starting with only an address to a known script (0x58043). In this case, the script calls the `2writetext` command to show some dialog. This dialog will be shown at the end of the example.\n\n```python\nimport pokemontools.crystal as crystal\n\n# parse the script at 0x58043 from the map event header at 0x584c3\n# from the second map header at 0x958b8\n# from the map header at 0x941ae\n# for \"Ruins of Alph Outside\" (map_group=3 map_id=0x16)\nscript = Script(0x58043)\n\n# show the script\nprint script.to_asm()\n\n# what labels does it point to in the to_asm output?\n# these must be present in the final asm file for rgbasm to compile the file\nobjdeps = script.get_dependencies()\nprint str(objdeps)\n\n# the individual commands that make up the script\ncommands = script.commands\nprint str(commands)\n\n# the 3rd command is 2writetext and points to a text\nthirdcommand = script.commands[2]\nprint thirdcommand\n# \n\n# look at the command parameters\nparams = thirdcommand.params\nprint params\n# {0: }\n\n# 2writetext always has a single parameter\ndefinition_param_count = len(getattr(crystal, \"2writetextCommand\").param_types.keys())\ncurrent_param_count = len(params.keys())\nassert definition_param_count == current_param_count, \"this should never \" + \\\n \"happen: instance of a command has more parameters than the \" + \\\n \"definition of the command allows\"\n\n# get the first parameter (the text pointer)\nparam = params[0]\nprint param\n# \n\n# RawTextPointerLabelParam instances point to their text\ntext = param.text\nprint text\n# \n\n# now investigate this text appearing in this script in \"Ruins of Alph Outside\"\nprint text.to_asm()\n```\n\nThe final output will be the following text.\n\n```asm\ndb $0, \"Hm? That's a #-\", $4f\ndb \"DEX, isn't it?\", $55\n; ...\n```\n\nHowever, this is not how that `TextScript` object would appear in the final ASM. To see how it would appear in `main.asm` once inserted, you would run `print crystal.to_asm(text)` to get the following.\n\n```asm\nUnknownText_0x580c7: ; 0x580c7\n db $0, \"Hm? That's a #-\", $4f\n db \"DEX, isn't it?\", $55\n db \"May I see it?\", $51\n db \"There are so many\", $4f\n db \"kinds of #MON.\", $51\n db \"Hm? What's this?\", $51\n db \"What is this\", $4f\n db \"#MON?\", $51\n db \"It looks like the\", $4f\n db \"strange writing on\", $51\n db \"the walls of the\", $4f\n db \"RUINS.\", $51\n db \"If those drawings\", $4f\n db \"are really #-\", $55\n db \"MON, there should\", $55\n db \"be many more.\", $51\n db \"I know! Let me up-\", $4f\n db \"grade your #-\", $55\n db \"DEX. Follow me.\", $57\n; 0x581e5\n```\n\n#### Figuring out where a script appears based on a known address\n\nAnother approach is to parse the entire ROM, then check a script at a particular address. This has the advantage that the script object will have the `map_group` and `map_id` variables set.\n\n```python\nimport pokemontools.crystal as crystal\n\n# parse the ROM\ncrystal.run_main()\n\n# get the parsed script\nscript = crystal.script_parse_table[0x58043]\n\n# read its attributes to figure out map group / map id\nmap_group = script.map_group\nmap_id = script.map_id\n\n# MapHeader is not given all the info yet\n# in the mean time \"map_names\" contains some metadata\nmap_dict = crystal.map_names[map_group][map_id]\nmap_header = map_dict[\"header_new\"]\n\nprint map_dict[\"name\"]\n# Ruins of Alph Outside\n```\n\nWhile the above doesn't show this, it turns out that the script at 0x58043 is referenced in the `MapEventHeader` as a person-event.\n\n```python\nprint map_header.second_map_header.event_header.to_asm()\n```\n\nThis will show a structure roughly like:\n\n```asm\nperson_event $3c, 19, 15, $7, $0, 255, 255, $0, 0, UnknownScript_0x58043, $0703\n```\n\nwithin this:\n\n```asm\nMapEventHeader_0x584c3: ; 0x584c3\n ; filler\n db 0, 0\n\n ; warps\n db 11\n warp_def $11, $2, 1, GROUP_RUINS_OF_ALPH_HO_OH_CHAMBER, MAP_RUINS_OF_ALPH_HO_OH_CHAMBER\n warp_def $7, $e, 1, GROUP_RUINS_OF_ALPH_KABUTO_CHAMBER, MAP_RUINS_OF_ALPH_KABUTO_CHAMBER\n warp_def $1d, $2, 1, GROUP_RUINS_OF_ALPH_OMANYTE_CHAMBER, MAP_RUINS_OF_ALPH_OMANYTE_CHAMBER\n warp_def $21, $10, 1, GROUP_RUINS_OF_ALPH_AERODACTYL_CHAMBER, MAP_RUINS_OF_ALPH_AERODACTYL_CHAMBER\n warp_def $d, $a, 1, GROUP_RUINS_OF_ALPH_INNER_CHAMBER, MAP_RUINS_OF_ALPH_INNER_CHAMBER\n warp_def $b, $11, 1, GROUP_RUINS_OF_ALPH_RESEARCH_CENTER, MAP_RUINS_OF_ALPH_RESEARCH_CENTER\n warp_def $13, $6, 1, GROUP_UNION_CAVE_B1F, MAP_UNION_CAVE_B1F\n warp_def $1b, $6, 2, GROUP_UNION_CAVE_B1F, MAP_UNION_CAVE_B1F\n warp_def $5, $7, 3, GROUP_ROUTE_36_RUINS_OF_ALPH_GATE, MAP_ROUTE_36_RUINS_OF_ALPH_GATE\n warp_def $14, $d, 1, GROUP_ROUTE_32_RUINS_OF_ALPH_GATE, MAP_ROUTE_32_RUINS_OF_ALPH_GATE\n warp_def $15, $d, 2, GROUP_ROUTE_32_RUINS_OF_ALPH_GATE, MAP_ROUTE_32_RUINS_OF_ALPH_GATE\n\n ; xy triggers\n db 2\n xy_trigger 1, $e, $b, $0, UnknownScript_0x58031, $0, $0\n xy_trigger 1, $f, $a, $0, UnknownScript_0x5803a, $0, $0\n\n ; signposts\n db 3\n signpost 8, 16, $0, UnknownScript_0x580b1\n signpost 16, 12, $0, UnknownScript_0x580b4\n signpost 12, 18, $0, UnknownScript_0x580b7\n\n ; people-events\n db 5\n person_event $27, 24, 8, $6, $0, 255, 255, $2, 1, Trainer_0x58089, $ffff\n person_event $3c, 19, 15, $7, $0, 255, 255, $0, 0, UnknownScript_0x58043, $0703\n person_event $3a, 21, 17, $3, $0, 255, 255, $a0, 0, UnknownScript_0x58061, $078e\n person_event $27, 15, 18, $2, $11, 255, 255, $b0, 0, UnknownScript_0x58076, $078f\n person_event $27, 12, 16, $7, $0, 255, 255, $80, 0, UnknownScript_0x5807e, $078f\n; 0x58560\n```\n\n#### Helpful ROM investigation tools\n\n```python\nimport pokemontools.crystal as crystal\n\n# load the bytes\ncrystal.load_rom()\n\n# get a sequence of bytes\ncrystal.rom_interval(0x112116, 10)\n# ['0x48', '0x54', '0x54', '0x50', '0x2f', '0x31', '0x2e', '0x30', '0xd', '0xa']\ncrystal.rom_interval(0x112116, 10, strings=False)\n# [72, 84, 84, 80, 47, 49, 46, 48, 13, 10]\n\n# get bytes until a certain byte\ncrystal.rom_until(0x112116, 0x50, strings=False)\n# ['0x48', '0x54', '0x54']\n# [72, 84, 84]\n\n# or just look at the encoded characters directly\ncrystal.rom[0x112116:0x112116+10]\n# 'HTTP/1.0\\r\\n'\n\n# look at a text at 0x197186\ntext = crystal.parse_text_at2(0x197186, 601, debug=False)\nprint text\n```\n\nThat last text at 0x197186 will look like:\n\n```python\n\"\"\"\nOAK: Aha! So\nyou're !\nI'm OAK! A #MON\nresearcher.\nI was just visit-\ning my old friend\nMR.#MON.\nI heard you were\nrunning an errand\nfor PROF.ELM, so I\nwaited here.\nOh! What's this?\nA rare #MON!\n...\n\"\"\"\n```", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/kanzure/pokemon-reverse-engineering-tools", "keywords": null, "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "pokemontools", "package_url": "https://pypi.org/project/pokemontools/", "platform": "any", "project_url": "https://pypi.org/project/pokemontools/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/kanzure/pokemon-reverse-engineering-tools" }, "release_url": "https://pypi.org/project/pokemontools/1.6.0/", "requires_dist": null, "requires_python": null, "summary": "Tools for compiling and disassembling Pok\u00e9mon Red and Pok\u00e9mon Crystal.", "version": "1.6.0" }, "last_serial": 916539, "releases": { "1.0": [], "1.0.1": [ { "comment_text": "", "digests": { "md5": "d9dfccd993cd707e7c13e182f0537bb2", "sha256": "b5f49311a1d6dc182691af06e1d71dbeedca8e036f9e1472c5bda7065ae43b64" }, "downloads": -1, "filename": "pokemontools-1.0.1.tar.gz", "has_sig": false, "md5_digest": "d9dfccd993cd707e7c13e182f0537bb2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 134448, "upload_time": "2013-08-05T04:12:05", "url": "https://files.pythonhosted.org/packages/cf/ef/c2298d73f5520c84580b2a1f3623560c53792a0c5078bd17eb2716181bc1/pokemontools-1.0.1.tar.gz" } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "ee375b8d5858bbec92ce69eaf735f71f", "sha256": "1e4515b1798e8fafc8eb01b1497ec07b3ac5f311a283f2307d70bedfed34993a" }, "downloads": -1, "filename": "pokemontools-1.0.2.tar.gz", "has_sig": false, "md5_digest": "ee375b8d5858bbec92ce69eaf735f71f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 134483, "upload_time": "2013-08-05T04:34:33", "url": "https://files.pythonhosted.org/packages/7e/61/215196c787e91d6b793651eed1fb7e654e2fbe08213ce6933bd0767fdc24/pokemontools-1.0.2.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "c1e413b0f8f87eb02633dd51e5e26f3e", "sha256": "475288bd23a01623500296595fb9fda195a5d5d524ea5770044d99cf132c9144" }, "downloads": -1, "filename": "pokemontools-1.1.0.tar.gz", "has_sig": false, "md5_digest": "c1e413b0f8f87eb02633dd51e5e26f3e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 134657, "upload_time": "2013-08-27T15:57:18", "url": "https://files.pythonhosted.org/packages/23/5d/d1043e92cc87f12f4072298defc020dbfe46fac9e20a1787861c42bdb4f3/pokemontools-1.1.0.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "a825e464540579281c55c1940d2ff1d3", "sha256": "1a742e2eb6c861b8e743d1d9ff1b6918a91b723b625454db1c808c08191c8386" }, "downloads": -1, "filename": "pokemontools-1.2.0.tar.gz", "has_sig": false, "md5_digest": "a825e464540579281c55c1940d2ff1d3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 139304, "upload_time": "2013-08-31T17:59:57", "url": "https://files.pythonhosted.org/packages/77/b7/508d882c247a5cc9f81f3b826c5fac06032f36a2c8006099be849f80bb3e/pokemontools-1.2.0.tar.gz" } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "a2eec47dc59d42dae001b92e424e5f9f", "sha256": "d56608967ef44415000aedbf3bffbd914fb4c1b8d052c6487c4ac7066b246cda" }, "downloads": -1, "filename": "pokemontools-1.3.0.tar.gz", "has_sig": false, "md5_digest": "a2eec47dc59d42dae001b92e424e5f9f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 188791, "upload_time": "2013-09-01T07:16:59", "url": "https://files.pythonhosted.org/packages/83/01/fa31ac756cab9febbafb886d3dbbd51072be559b7ee6ba3748e4ce5d4dc7/pokemontools-1.3.0.tar.gz" } ], "1.4.0": [ { "comment_text": "", "digests": { "md5": "70f42fac989dbc5fb4440e3595aaccd3", "sha256": "04df4205482705e6d2642bc187c009e1157089fe4b4aa4b1cb7eefb26b4db35c" }, "downloads": -1, "filename": "pokemontools-1.4.0.tar.gz", "has_sig": false, "md5_digest": "70f42fac989dbc5fb4440e3595aaccd3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 3127310, "upload_time": "2013-09-22T02:38:19", "url": "https://files.pythonhosted.org/packages/f9/6e/60f5c787315af39f0b5e2b020e8e248b48f7ead88e2da3e2881ac80eb2f8/pokemontools-1.4.0.tar.gz" } ], "1.4.1": [ { "comment_text": "", "digests": { "md5": "52d6218e9143207a205d5969ade07b58", "sha256": "4bc084a1e2ac63b37d87be366333546cff2c54b7cc170868cfd7739320d5119e" }, "downloads": -1, "filename": "pokemontools-1.4.1.tar.gz", "has_sig": false, "md5_digest": "52d6218e9143207a205d5969ade07b58", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1303312, "upload_time": "2013-09-22T02:54:05", "url": "https://files.pythonhosted.org/packages/7f/2f/0d5ee7fcbcbef7f85b5204faa0d479c3aea75a67d79600400163ede47aa2/pokemontools-1.4.1.tar.gz" } ], "1.5.0": [ { "comment_text": "", "digests": { "md5": "ac47bc6bed008c2190c2c6bd0671919a", "sha256": "aed2a9af0e42fb73d5f85909e78000f7992befe977a4e13b7137c97d4a1ce16f" }, "downloads": -1, "filename": "pokemontools-1.5.0.tar.gz", "has_sig": false, "md5_digest": "ac47bc6bed008c2190c2c6bd0671919a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1308151, "upload_time": "2013-11-11T18:38:12", "url": "https://files.pythonhosted.org/packages/79/dc/0f0408e150a7e32c0b30cc60d3758f7926039b8d044a72136792aedf5d34/pokemontools-1.5.0.tar.gz" } ], "1.6.0": [ { "comment_text": "", "digests": { "md5": "f75ba15e8cdb5157834430aa9972002a", "sha256": "4ecdea109c2adc6226d1ed95ac90d752e544e0d3ccc71bfd72f40eb319e60426" }, "downloads": -1, "filename": "pokemontools-1.6.0.tar.gz", "has_sig": false, "md5_digest": "f75ba15e8cdb5157834430aa9972002a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1315677, "upload_time": "2013-11-11T18:39:50", "url": "https://files.pythonhosted.org/packages/92/01/bfa76c379c4b26b2bad442ab98955fb1e7fd54fa8eb3b9f08ac1e3b4aa38/pokemontools-1.6.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f75ba15e8cdb5157834430aa9972002a", "sha256": "4ecdea109c2adc6226d1ed95ac90d752e544e0d3ccc71bfd72f40eb319e60426" }, "downloads": -1, "filename": "pokemontools-1.6.0.tar.gz", "has_sig": false, "md5_digest": "f75ba15e8cdb5157834430aa9972002a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1315677, "upload_time": "2013-11-11T18:39:50", "url": "https://files.pythonhosted.org/packages/92/01/bfa76c379c4b26b2bad442ab98955fb1e7fd54fa8eb3b9f08ac1e3b4aa38/pokemontools-1.6.0.tar.gz" } ] }