update everything

This commit is contained in:
Kaisaan 2025-04-09 00:12:30 -04:00
parent f8a9edd53b
commit 03e5289dfa
75 changed files with 11204 additions and 1555 deletions

View File

@ -1,12 +1,14 @@
# Ys V: Lost Kefin - Kingdom of Sand (PS2) English Translation
Currently using Sam Farron's translation from his [Translation Series](https://www.youtube.com/watch?v=LfZZPwIdhzg&list=PLoD4gkRCJkUcgfpU5puBqYy5DX-RJK--b) with permission
# Current Progress
Slowly reverse-engineering the script files so I can make tools.
**If you want to help with the translation please [contact me!](https://kaisaan.github.io/pages/contact) (Knowing Japanese is not needed)**
# Current Progress
I am replacing the usage of [armips](https://github.com/Kingcom/armips) with [acbde](https://www.romhacking.net/utilities/1392/) for script extraction and insertion. Currently I have raw dumps only as I need to figure out all the control codes to get a full pointer dump
# Building
- Copy the contents of the game's .iso into the `extracted` and `translated` folders
- Use [Apache3](https://www.psx-place.com/threads/apache.19171/) to extract the contents of the game's .iso file
- Copy the contents to the `extracted` and `translated` folders
- Run `build.bat` or manually run the commands
# Hacking Notes
@ -15,20 +17,33 @@ Slowly reverse-engineering the script files so I can make tools.
- Extracted .bin files with `_anm` in the filename are animation files with indexed 8BPP graphics and have the header `NAXA5010`
- Extracted .HGB files are texture files with 32BPP RGBA graphics
- Music files are `.hd` (header), `.bd` (header), and `.sq` (sequence) files
- See my notes file for more terribly organized notes
# Extracting the DATA.BIN Files
`extract.py` extracts all the files from DATA.BIN and its folders into a `DATA` folder but does not extract the files into their correct folders yet, a `logfile.txt` is also created for fixing issues with the script
# Font info
The game uses Shift-JIS encoding but I decided to make table files so I can include control codes
`font.py` extracts the fontmap from `SLPM_663.60` to create a quick `font.tbl` table file. Use `kefin.tbl` for corrected values and control codes (also used by `patch.asm`)
`font.py` extracts the fontmap from `SLPM_663.60` to create a quick `font.tbl` table file. Use `kefin.tbl` for corrected values and control codes
In `SLPM_663.60` the font is located at $1A3E90 as 4BPP graphics, its palette is stored at $25E4C0, and the fontmap is at $1A31F0
# Script Info
- Script files (named `stageXX.bin`) have the first $2000 bytes as a pointer table
- Each pointer first as an "index" number (4 bytes, little-endian) then the pointer value to the file (4 bytes, little-endian)
- Pointer value are calculated as `pointer + $2000`
- Changing the pointer seems to cause the game to freeze
Before running `script.py` copy the script files (as extracted from `DATA.BIN`) to the `scripts` folder. `script.py` will extract the pointer information for each file into a separate text file. The python script will also include info for control codes.
Before running `dump.bat` make sure perl is installed. On Windows I recommend isnstalling [Strawberry perl](https://www.lifebottle.org/#/./other/strawberry-perl/index)
The script can be dumped from `DATA.BIN` and into `scripts` after running `dump.bat`. Currently it is a raw dump because I have not figured out all the control codes, using a pointer dump will cause the scripts to be under-dumped.
# To do
- Updated extraction script to extract `DATA0.BIN`, `DATA1.BIN`, and `SLPM_663.60`
- Add more hacking notes (my notes.txt file is a mess so I haven't added it here)
- Figure out all the control codes properly
- Update the extraction script to extract `DATA0.BIN`, `DATA1.BIN`, and `SLPM_663.60`
- Continue inserting the English script
# Game Manual Translation
In the `manual` folder are scans for the game's manual. They were originally from [landofys.narod.ru](https://landofys.narod.ru/) which is now landofys.com.ru(http://landofys.com.ru/) was scanned by Dragon.
# Links to Stuff
- Translation of opening cutscene by [mziab](https://www.romhacking.net/forum/index.php?topic=28379.0) (I did not use this translation)
- Hacking attempt by [Hectavus](https://zenhax.com/viewtopic.php@t=15249.html) with only `SLPM_663.60` being translated

133
datacommands.txt Normal file
View File

@ -0,0 +1,133 @@
#GAME NAME:Ys V Lost Kefin
#BLOCK NAME:stage00.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94BC800
#SCRIPT STOP: $94CBD9F
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage01.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94CE000
#SCRIPT STOP: $94D4292
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage02.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94D6800
#SCRIPT STOP: $94DDD40
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage03.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94E0000
#SCRIPT STOP: $94E155E
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage04.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94E3800
#SCRIPT STOP: $94EB29C
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage05.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94ED800
#SCRIPT STOP: $94F2E37
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage06.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94F5000
#SCRIPT STOP: $94FC64B
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage07.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94FE800
#SCRIPT STOP: $95018AA
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage08.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $9504000
#SCRIPT STOP: $950E73C
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage09.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $9510800
#SCRIPT STOP: $951609F
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stagea0.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $9518800
#SCRIPT STOP: $95188FC
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stageb0.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $951B000
#SCRIPT STOP: $951C0F4
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK

3
dump.bat Normal file
View File

@ -0,0 +1,3 @@
perl scripts\abcde\abcde.pl -cm abcde::Cartographer "extracted\DATA.BIN" datacommands.txt "scripts\dump" -m
@pause

View File

@ -38,7 +38,7 @@ while fileptr < fileptrend:
filename = filename[:filename.find(terminator)]
filesectorstart = filesectorstart * sector
filesectorsize = filesectorstart * sector
filesectorsize = filesectorsize * sector
datafile.seek(filesectorstart)
filedata = datafile.read(filesize)

3146
kefin.tbl

File diff suppressed because it is too large Load Diff

BIN
manual/Ys5_-_0001.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
manual/Ys5_-_0002.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

BIN
manual/Ys5_-_0003.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
manual/Ys5_-_0004.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
manual/Ys5_-_0005.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

BIN
manual/Ys5_-_0006.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

BIN
manual/Ys5_-_0007.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
manual/Ys5_-_0008.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
manual/Ys5_-_0009.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
manual/Ys5_-_0010.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
manual/Ys5_-_0011.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
manual/Ys5_-_0012.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

338
notes.txt Normal file
View File

@ -0,0 +1,338 @@
Text seems to be loaded into $00F6A150 in memory
and also at $00EF6580
stage00.bin is loaded at $0139BA00 (data starts at $0139DA00)
stagea0.bin
$00015F91 $00000000
$00015F92 $00000024
$00015F93 $00000048
$00015F94 $00000075
$00015F95 $000000A2
$00015F96 $000000CF
the latter values map directly to the start of each string (pointer?)
$00EF6660 has the value $00EF65D0
$00EF65D0 has the value $00F69E00
$00F69E00 has the value $01B08660
loading text is at $0025FD30
$0A is newline
stage00.bin
5A 00 5B 14 01 3A 02 $6005
THE TEXT IS IN SHIFT JIS THANK YOU FALCOM BLESS FEENA REAH ELDEEN EVERYONE I LOVE YOU FALCOM
Filename table for SLPM_663.60
0x00246240 Start
0x00246258 + Base pointer -> 003461D8 file start
0x0024B690 Folder start -> 0034B610
from vo1010101.bd to vo4030202.bd
1829D0 -> 00282950 Start of file info
0x001875E0 Start of folder info
4 bytes: Pointer to folder name/location
4 bytes: File count start?
4 bytes: File count
3DDFD0 -> 2DE050
003461D8 + 1D4D30 -> 41 AF88 -> 31 B008
What is 1D4D30? (D4DB0)
At 2879b4(187A34) is a pointer to 0282950 (1829D0) which points to 246258 which points to
0x00187A40 has 0034be10 (24BE90) which points the filename of stuff in DATA.BIN
at 0x00187A28 is 00287560 (1875E0)
what is 003DF4F0 -> 2DF570 ?
filename for bmg01.hd is checked at 0012ED74
bgm01.hd
filename location is at 0x002462D8 -> 346258
data file is 0000 09d0
file start is 11BD
file stop is 02
bgm01.sq info: 0x00182F50 -> 282ED0
filename location is at 0x002467D8 -> 346758
data file is 1120
file start is 3EDF
file stop is 03
file length is 10F2
file data start is at 0x01F6F800
file data stop is at 0x01F71000
file data length is 1800
ys3bgm01.sq
filename location is at 246A58 -> 3469D8
data file is 1120
file start is 3FE6
file stop is 03
file length is 1F1E
file data start is at 0x01FF4800
file data stop is at 0x01FF6800
file data length is 2000
first seq file is at 1F6F800
001342E8 in Ghidra:
At 4 breakpoints, first character is printed
At 8 breakpoints, second
at 12, third
at 14, v0 is 8000
at 15, v0 is C000
at 16, fourth
17, fifth
18, 14000
lw v1,0x4(v1)
slt at,v0,v1
bne at,zero,0x001342FC
nop
subu a2,v1,a1
When the bne is false
00014000 is v0
000115B0 is v1
002879C0 is a0
000115B0 is a1
00002A50 is a2
00402FF4 is a3
z_un_00134100
In DATA.BIN
0x0024BE80 File names
0x0024BE90 + FFF80 -> 0034BE10 -> 10BE3400
0x00187A40 + FFF80 -> 002879C0 start
0x0018E7CF -> 0028e74f end
003d2808
0x00187A40 is the file table the format is
4 bytes: Pointer to file name (add base pointer) (in little endian)
4 bytes:
4 bytes: Start of file offset
4 bytes: File length
vo1010101.bd
file length is 500
data file is 0115B0
file data start is at 0x001D73F0
B0150100 -> 000115B0 + FFF80
003DF4F0
0x01F6F800 to 0x01F70900 is 1100
Main text set is 094BC800, so with pointer it's 0139DA00
The text after the opening cutscene is at $094BCE12
The text at the opening cutscene is at $094BC862 or $094BC85E or $094BC85A or $094BC858
0x094BC800
094BC89B
095BC7DA -> DAC75B09
094BC85E - 0139DA5E = 0811EE00
Ghidra pointer is 187A40 or 1879C0 002879C0
Base pointer is $FFF80
Before the text is the expected string length then $80
This is loaded from the routine at $001971E8
opening cutscene text in hex:
82A082F182BD814182A0
location in memory (eeMemory.bin)
EF6588
F6A158
139DA62
true start is at 139DA5A
look into 0019DCF0
from 0x094BC800 to 0x094CBDA0 -> F5A0
text routine
0012D23C:
lbu v0,0x0(a1)
addiu a2,a2,-0x1
addiu a1,a1,0x1
sb v0,0x0(v1)
addiu v1,v1,0x1
bne a2,a0,0x0012D23C
nop
jr ra
first string is 55 dec or $37 (true $36)
second string is 83 dec or $53 (true $52)
third is true $8E
v0 holds character value
v1 holds memory address
a0 holds 8080808080808080FFFFFFFFFFFFFFFF (negative 1)
a1 holds character address
a2 holds string length
ra holds 00197224
0012D23C:
lbu v0,0x0(a1) // The value at (a1+0x0) is stored at v0
addiu a2,a2,-0x1 // Decrement string length
addiu a1,a1,0x1 // Increment character address
sb v0,0x0(v1) // The value in v0 is stored at (v1+0x0)
addiu v1,v1,0x1 // Increment address of memory
bne a2,a0,0x0012D23C // If a2 != a0, then loop back, so stop looping when string length is done
nop
jr ra // Return
ghidra output:
for (; iVar5 != -1; iVar5 = iVar5 + -1) {
uVar1 = *(undefined *)param_2;
param_2 = (undefined8 *)((int)param_2 + 1);
*(undefined *)puVar6 = uVar1;
puVar6 = (undefined8 *)((int)puVar6 + 1);
}
return param_1;
The character address is stored at 003D2F00 in memory
GP is 003DA7F0?
Function at 001bf260
void FUN_001bf260(int searchvalue)
{
int *pointer;
int counter;
counter = 0;
uRam003d2ed8 = uRam00f6c054;
uRam003d2ed4 = uRam00f6c058;
uRam003d2ed0 = uRam00f6c068;
pointer = piRam003d2f04;
do {
if (searchvalue == *pointer) goto LAB_001bf2e0;
if ((counter != 0) && (*pointer == 0)) break;
counter = counter + 1;
pointer = pointer + 2;
} while (counter < 0x400);
FUN_0012e570(0x368bf0,uRam003d2edc);
counter = -1;
LAB_001bf2e0:
if (counter != -1) {
uRam003d2ef0 = 1;
FUN_001484a0(uRam00f53d94,0x61,0);
FUN_001484a0(uRam00f53d94,0x3a,0x80);
FUN_001bf0e0((int)piRam003d2f04 + piRam003d2f04[counter * 2 + 1] + 0x2000);
FUN_00103d70(0xf53cd0,0xf53cf0);
}
return;
}
counter is s0
searchvalue is v0
pointer is v1
when counter is equal to searchvalue ($01), it is true
003D2EDC is 00000000
003D2ED8 is 4016CBE4
003D2ED4 is 3ED67748
003D2ED0 is 44DD5FFB
00F53D94 is 0118CB90
003D2F04 is 0139BA00
003d2f04 has $0139BA00 (start of stage00.bin)
00368bf0
f53cd0
loading screen text is at 0x0025FD30 -> 0x0035FCB0 in exe
graphics file format
8h: Header starts with NAXA5010
4h: Usually $100 but sometimes $10
4h: Always $200
4h: pointer?
4h: pointer?
4h: idk
4h: nothing
the rest: idk
There's just raw graphics at 0x0023C180 in slpm
2 dimensional, 32bpp (ARGB)
anm.bin animation, indexed 8bpp
.hgb texture file, 32bpp rgba
.hgk 3d model file?
.hd music header
.sq music sequence
.bd music header 2
0x0019D000 -> 29 CF80 some pointer table
the font graphic is at $1A3E90 -> 2A3E10
font map is at 0x001A31F0 -> 2A3170
palette is at 0x0025E4C0
FUN_0014f370 is to search the index of a character
0035FE30 (conv) is No Ys 3 Data
0035FDF0 (conv) is Ys Data Found
00360AF0 (conv) is Ys 3 Data search
FUN_0017b900 checks for save data with link option
stage90.bin has the text for the titlescreen cutscene
cartographer stuff
#BLOCK NAME:stage00.bin
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $94BA804
#POINTER TABLE STOP: $94BB410
#POINTER SIZE: $04
#POINTER SPACE: $04
#ATLAS PTRS: Yes
#BASE POINTER: $94BC800
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK
#BLOCK NAME:stage00.bin RAW
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $94BC800
#SCRIPT STOP: $94CBD9F
#TABLE: kefin.tbl
#COMMENTS: No
#SHOW END ADDRESS: No
#TRIM TRAILING NEWLINES: No
#END BLOCK

71
script.py Normal file
View File

@ -0,0 +1,71 @@
def extract(stagefile, logfile, data):
script = open(f"scripts/{stagefile}", "rb")
log = open(f"scripts/{logfile}", "w")
log.write(f"{stagefile} header info\n")
currentread = 0x0
endcode = 0x0
endsize = 0x2
count = 0x0
val1 = 0x0
val2 = 0x0
trueoffset = 0x0
dataoffset = data
offset = 0x2000
known = [b'\x00\xff', b'\x32\xff', b'\x11\xff', b'\x02\xff', b'\x4E\xff', b'\x01\xff', b'\x64\xff', b'\x04\xff', b'\x55\xff', b'\x07\xff', b'\x06\xff', b'\x56\xff']
ignore = [b'\xa5\xff']
#known = [b'\x00\x00\x00\xff', b'\x02\x00\x00\xff', b'\x04\x00\x00\xff', b'\x00\x4E\x4E\xff']
#ignore = [b'\x68\x81\xa5\xff', b'\x49\x81\xa5\xff', b'\x42\x81\xa5\xff', b'\x48\x81\xa5\xff',]
isknown = ""
while count < 0x400:
script.seek(currentread)
val1 = script.read(4)
val2 = script.read(4)
val1 = int.from_bytes(val1, "little")
val2 = int.from_bytes(val2, "little")
trueoffset = val2 + offset
dataoffset = dataoffset + trueoffset
script.seek(trueoffset - endsize)
endcode = script.read(endsize)
if endcode in known:
isknown = "KNOWN"
elif endcode in ignore:
isknown = "ignore"
else:
isknown = "NEW"
if val1 == val2 == 0:
break
log.write(f"Entry Order {count:X}\tEntry Order Offset {currentread:X}\tEntry Number {val1:X}\tOffset {val2:X}\tTrue Offset {trueoffset:X}\tDATA.BIN Offset {dataoffset:X}\tEnd code {endcode}\t{isknown}\n")
count = count + 1
currentread = currentread + 0x08
extract("stage00.bin", "script00.txt", 0x94BA800)
extract("stage10.bin", "script10.txt", 0x94CC000)
extract("stage20.bin", "script20.txt", 0x94D4800)
extract("stage30.bin", "script30.txt", 0x94DE000)
extract("stage40.bin", "script40.txt", 0x94E1800)
extract("stage50.bin", "script50.txt", 0x94EB800)
extract("stage60.bin", "script60.txt", 0x94F3000)
extract("stage70.bin", "script70.txt", 0x94FC800)
extract("stage80.bin", "script80.txt", 0x9502000)
extract("stage90.bin", "script90.txt", 0x950E800)
extract("stagea0.bin", "scripta0.txt", 0x9516800)
extract("stageb0.bin", "scriptb0.txt", 0x9519000)

268
scripts/abcde/abcde.pl Normal file
View File

@ -0,0 +1,268 @@
#!/usr/bin/perl
# abw's basic custom data encoder
package abcde;
use strict;
use warnings;
my $usage = <<USAGE;
usage: $0 <options> [-cm <command module> [<command module options>]]
In its most basic form, this program reads data from STDIN, translates it according to the provided table files and
other general options, and writes it to STDOUT. This form is useful for integration into other programs (e.g. hex
editors, (dis)assemblers, etc.) and for examining the translation process in detail, but is unlikely to be of much
interest to the average user as its scope is limited to the translation of data that should be translated and makes
no attempt to e.g. update pointers or exclude portions of the input data from translation.
The average user is more likely to be interested in the command modules. Currently, varying levels of support for Atlas
and Cartographer command files have been implemented; see those modules' documentation for more details.
Available required options:
-m <mode> / --mode <mode>: select operation mode; <mode> value can be either 'bin2text', in which case binary data on
STDIN is translated to text on STDOUT, or 'text2bin', in which case text data on STDIN is translated to binary
data on STDOUT.
-t <table> / --table <table file>: specify the file name of a table file to use in translation. Include this option
once for each table file to be used in translation. Note that when provided on the command line, relative
paths are assumed to be anchored at the current directory.
Available optional options:
-g / --group: instead of normal output, print the output grouped by token (primarily used for debug, but also available
for interest's sake).
-h / --help: print this help text and exit; if used in conjunction with -cm <command module name>, instead print help
text for the specified command module and exit.
-brp / --base-relative-path <path>: sets the base relative path used by command modules to <path> instead of the
current directory; see command module documentation for details including any special <path> values.
-s / --stats: after normal output, print some statistics to STDERR.
--artificial-end-token <label>: <label> used to indicate the logical end of a fixed-length string during insertion.
--multi-table-files: allow multiple logical tables inside a single table file.
--no-unicode-normalization: if set, prevents the default canonical decomposition (NFD) of all text input and canonical
composition (NFC) of all text output
--start-table-file-name: specify the file name of the table to be used as the starting table for translation; if not
specified, this defaults to the first table provided on the command line with -t.
Available command modules:
abcde::Atlas: Atlas emulation; implies --mode text2bin
abcde::Cartographer: Cartographer emulation; implies --mode bin2text
Available command module options:
All options following the command module name are passed directly to the command module; see the documentation for the
selected command module for details.
USAGE
# TBD:
# also implement longest-leftmost insert (valid for single table + all table entries have same binary width + no normal table entry has > 2 characters + all characters used in normal entries have an entry with just that character)
# also implement dynamic programming insert (valid for single table + no table entry's binary is a prefix of any other table entry's binary)
# also implement insert algorithm selection, defaulting to the fastest valid algorithm
# add string format support (non-text prefixes, with special case for pascal string lengths)
# add more pointer support (ability to specify where individual bits of pointer should be read from)
# update Atlas/Cartographer modules to support bit-oriented operations
# add more/better debug output (especially for Atlas, which has a debug flag) and stats
# ability to import one table file into another (useful e.g. when the game uses dozens of table files that differ only on a couple of control codes)
# increase support for pointer arithmetic (e.g. games that store P in ROM and then access e.g. P/2)
# combining characters stored on line N lines above the characters they combine with, e.g. JP dakuten marks written above their kana in NES games
# add check for writing the same address twice
# convert Cartographer's exclusive addresses to inclusive to match Atlas' behaviour
# convert Cartographer's own output (//POINTER etc.) to use customizable output templating system, allowing e.g. non-EN languages, formatting according to user prefences, choosing dec. vs. hex. numbers, etc.)
# for multi-table-files, Cartographer outputs the same file name with multiple IDs
BEGIN {
require Time::HiRes;
$abcde::stats{totalTime} = Time::HiRes::gettimeofday();
$| = 1;
require File::Basename;
require File::Spec;
my $dir = File::Spec->catdir(File::Basename::dirname(File::Spec->rel2abs(__FILE__)), '.');
# make sure our pre-packaged CPAN modules and abcde::* are in @INC before anything else the user might have installed on their system
unshift(@INC, File::Spec->catdir($dir.'/cpan'), $dir);
binmode(STDERR, ':utf8');
}
{
# for debug only
no warnings qw(once);
require Data::Dumper;
$Data::Dumper::Sortkeys = 1;
}
use Encode ();
use abcde::Table::Table;
use abcde::Table::Token;
my @tableFileNames = ();
my $startTable;
my $startTableFileName;
my $printHelp = 0;
# default values
%abcde::tablesByFileName = ();
%abcde::tablesByID = ();
@abcde::allTables = ();
%abcde::internalTokens = ();
$abcde::commandFileType = '';
@abcde::commandFileArgs = ();
%abcde::options = (
allowMultipleTablesPerFile => 0,
artificial_end_token => undef,
baseRelativePath => File::Spec->curdir(),
group => 0,
mode => undef,
printStats => 0,
errorEOF => 0,
unicode_normalization => 1,
);
$abcde::reserved_chars = { open => '<', close => '>' }; # I might make this configurable later
# read options from command line
#@ARGV = map { Encode::decode_utf8($_, 1) } @ARGV;
if (!@ARGV) {
print $usage;
exit;
}
while (my $arg = shift(@ARGV)) {
if ($arg eq '--artificial-end-token') {
($abcde::options{artificial_end_token} = shift(@ARGV)) =~ s/(?:\\n)*$//;
if ($abcde::options{artificial_end_token} !~ /^$abcde::reserved_chars->{open}(?:)[^$abcde::reserved_chars->{open}$abcde::reserved_chars->{close}]+$abcde::reserved_chars->{close}$/) {
die("invalid artificial-end-token label");
}
} elsif ($arg eq '-cm') {
$abcde::commandFileType = shift(@ARGV);
@abcde::commandFileArgs = @ARGV;
last;
} elsif ($arg eq '-g' || $arg eq '--group') {
$abcde::options{group} = 1;
} elsif ($arg eq '-h' || $arg eq '--help') {
$printHelp = 1;
} elsif ($arg eq '-m' || $arg eq '--mode') {
$abcde::options{mode} = lc(shift(@ARGV));
} elsif ($arg eq '--multi-table-files') {
$abcde::options{allowMultipleTablesPerFile} = 1;
} elsif ($arg eq '--no-unicode-normalization') {
$abcde::options{unicode_normalization} = 0;
} elsif ($arg eq '-brp' || $arg eq '--base-relative-path') {
$abcde::options{baseRelativePath} = shift(@ARGV);
} elsif ($arg eq '-s' || $arg eq '--stats') {
$abcde::options{printStats} = 1;
} elsif ($arg eq '--start-table-file-name') {
$startTableFileName = shift(@ARGV);
} elsif ($arg eq '-t' || $arg eq '--table') {
push(@tableFileNames, shift(@ARGV));
} else {
die("unknown option '$arg'\n\n$usage");
}
}
# start validating command line args
if ($abcde::commandFileType) {
my @parts = split(/'|::/, $abcde::commandFileType);
foreach (@parts) {
die("invalid command module name") if ($_ !~ /^[A-Za-z_][A-Za-z0-9_]*$/);
}
eval "require $abcde::commandFileType" or die("error loading command module '$abcde::commandFileType': $@");
$abcde::commandFileType->updateOptions();
}
if ($printHelp) {
if ($abcde::commandFileType) {
$abcde::commandFileType->printHelp();
} else {
print STDOUT $usage;
}
exit;
}
if (!defined($abcde::options{mode}) || $abcde::options{mode} !~ /^(?:bin2text|text2bin)$/) {
die("missing or unknown mode!");
}
# TBD: are these actually useful anymore?
# set up internal tokens
# FB => no-match fallback token
# FC => expired count fallback token
# FF => forced fallback token
# FI => immediate fallback
foreach my $flag (qw(FB FC FF FI)) {
$abcde::internalTokens{$flag} = abcde::Table::Token->create(undef, {$flag => 1}, '', 0, '');
}
# set up tables
$abcde::minBinPerText = 0.25; # heuristic factor for multi-table insertion; initialized to 1 bit per 4 characters, which is what $abcde::rawTable produces
# $abcde::rawTable and $abcde::rawBitTable must exist before calling finalize() on any other tables
$abcde::rawTable = abcde::Table::Table->generateRawTable(); # contains both bit and byte entries
$abcde::rawBitTable = abcde::Table::Table->generateRawBitTable(); # contains only bit entries; used for bin2text
$abcde::rawTable->finalize();
$abcde::rawBitTable->finalize();
# add any tables passed on the command line so that they will be available for command modules too
foreach my $fileName (@tableFileNames) {
$fileName = File::Spec->rel2abs(File::Spec->catdir($abcde::options{baseRelativePath}, $fileName));
my $tables = abcde::Table::Table->addTableFile($fileName);
$startTable ||= $tables->[0];
}
if ($abcde::commandFileType) {
$abcde::commandFileType->handleCommandFile(\@abcde::commandFileArgs);
} else {
# do stuff that can't be done until we've finished parsing all of the table files
foreach my $table (@abcde::allTables) {
$table->finalize();
}
if (defined($startTableFileName)) {
if ($abcde::tablesByFileName{$startTableFileName}) {
$startTable = $abcde::tablesByFileName{$startTableFileName}->[0];
} else {
die("--start-table-file-name was given, but no table with that file name exists");
}
} else {
$startTable ||= $abcde::rawTable;
}
# read input, defaulting to translating all of STDIN
{
local $/ = undef;
if ($abcde::options{mode} eq 'bin2text') {
binmode(STDIN, ':raw');
binmode(STDOUT, ':utf8');
} elsif ($abcde::options{mode} eq 'text2bin') {
binmode(STDIN, ':utf8');
binmode(STDOUT, ':raw');
}
$abcde::data = <STDIN>;
if ($abcde::options{mode} eq 'bin2text') {
$abcde::data = unpack('B*', $abcde::data); # bloat input up to 1 character per byte cuz working with bits in perl is super annoying; TBD: this is probably the best place to start when optimizing RAM usage
} elsif ($abcde::options{mode} eq 'text2bin') {
$abcde::data =~ s/\R//g; # ignore newlines during text2bin
}
}
# create a String object, parse our input, print out the result
require abcde::String::BasicString;
my $string = abcde::String::BasicString->create($startTable);
$string->parse();
my $output = $string->output();
if ($abcde::options{mode} eq 'text2bin' && !$abcde::options{group}) {
$output = pack('B*', $output); # convert characters to binary
}
print STDOUT $output;
}
if ($abcde::options{printStats}) {
# print stats
$abcde::stats{totalTime} = (Time::HiRes::gettimeofday() - $abcde::stats{totalTime});
print STDERR "\n=== Stats ($abcde::options{mode}) ===\n";
print STDERR "\nBlock\tStrings\tBits\tTokens\tChars\tTime (s)\n";
foreach my $block_name (sort keys %{$abcde::stats{blocks}}) {
my $block = $abcde::stats{blocks}->{$block_name};
print STDERR "$block_name:\t$block->{strings}\t$block->{bits}\t$block->{tokens}\t$block->{chars}\t$block->{time} seconds\n";
}
print STDERR "Total\t$abcde::stats{totalStrings}\t$abcde::stats{totalBits}\t$abcde::stats{totalTokens}\t$abcde::stats{totalChars}\t".sprintf('%.3f', $abcde::stats{totalTime})." seconds\n";
my $other_stats = "";
foreach my $key (sort keys %abcde::stats) {
next if ($key =~ /^(?:blocks|totalStrings|totalBits|totalTokens|totalChars|times|totalTime)$/);
$other_stats .= "$key: $abcde::stats{$key}\n";
}
print STDERR "\nOther stats:\n$other_stats" if ($other_stats);
}

1253
scripts/abcde/abcde/Atlas.pm Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,632 @@
package abcde::Cartographer;
use strict;
use warnings;
use Encode ();
use List::Util ();
use Unicode::Normalize ();
use abcde::String::BasicString;
my $usage = "Cartographer Module Usage: perl $0 <general options> -cm abcde::Cartographer ROM.ext Command.txt Output.txt -s/-m";
sub printHelp {
my ($class) = @_;
print <<DONE;
$usage
See Cartographer's documentation (included for your convenience under abcde's docs/Cartographer directory; Cartographer
itself is available via e.g. http://www.romhacking.net/utilities/647/) for a description of its command file format.
This module provides support for Cartographer command files, with some caveats.
Any valid Cartographer command file should be valid here too, but our command file parsing rules, order of execution,
and final output are subtly different from Cartographer's.
The salient differences are as follows:
- Cartographer generally requires the commands within each block to be in a certain predefined (but unspecified) order
but will accept certain commands out-of-order.
-- Instead, we accept commands in any order (except that each block must still end with an "#END BLOCK" command).
- Cartographer handles repeated commands inconsistently, with new commands sometimes overriding old commands, sometimes
not.
-- Instead, we throw an error except on #SUB TABLE, which is a file-level command allowed to appear multiple times.
- Cartographer assumes relative paths inside the command file (i.e. #TABLE) are anchored at the user's current
directory.
-- Instead, we assume relative paths inside the command file are anchored at the directory containing the command file.
- Cartographer dumps each block immediately after parsing it.
-- Instead, we don't dump anything until after the entire command file and all referenced table files have been
validated.
- Several of Cartographer's commands appear to be broken in various ways, including #METHOD: POINTER_RELATIVE_PC and
pretty much everything related to fixed-width strings.
-- Where possible, we attempt to make these commands work as they appear to have been intended to work rather than as
they actually (don't) work.
-- In the case of the #METHOD: POINTER_RELATIVE_PC command, we use the #BASE POINTER: command as in
#METHOD: POINTER_RELATIVE instead of the #RELATIVE PC: and/or #RELATIVE BASE: commands from Cartographer's
(inconsistent) documentation.
-- In the case of the #TYPE: FIXED_STRING and #TYPE: FIXED_STRING && FIXED_LINE commands, if a STRING LENGTH (for
FIXED_STRING) or LINE LENGTH (for FIXED_STRING && FIXED_LINE) boundary is reached partway through a multi-byte
token, Cartographer will dump the entire multi-byte token instead of only the bytes requested and then continue
(assuming it does continue) dumping starting from the STRING/LINE LENGTH boundary inside the multi-byte token.
E.g. if the STRING/LINE LENGTH boundary occurs between B and C in the token ABCD, Cartographer will dump ABCD
and then continue with CD. In the above example, note that this means that not only are the bytes CD dumped
twice, they are dumped with different translations. Instead, we will dump AB and then continue with CD.
-- In the case of the #TYPE: FIXED_STRING && FIXED_LINE command, it's not clear how the #STRING LENGTH: and
#LINE LENGTH: commands are supposed to function; LINE LENGTH generally predominates, but different values for
the #STRING LENGTH: command can result in a string being prematurely terminated or end tokens being output
twice, once with a LINE CTRL and once with an END CTRL.
-- #STRING LENGTH now controls the total number of bytes that get dumped from the ROM, and #LINE LENGTH controls how
those bytes get chopped into groups for output.
-- In the case of the #LINE END command, Cartographer refuses to accept a value of "No" as per its documentation; we
do.
- Cartographer has some issues with the % character (i.e. U+0025 PERCENT SIGN) when used in the right-hand-side of a
table entry or in the #LINE CTRL: or #END CTRL: commands which result in the % character (and sometimes the
next few bytes/characters) not being included in its output.
- Each referenced table file must be a valid abcde table file. This may require updating older table files.
- Translating binary data into characters according to abcde's updated table file semantics may result in us generating
different output than Cartographer.
- The #TABLE and #SUB TABLE commands are affected by abcde's -brp / --base-relative-path <path> option.
For invalid Cartographer command files, our error messaging is likely to be different from Cartographer's.
Since Cartographer was designed to work with only one table file at a time, you will need to specify any additional
table files in the <general options> or the new #SUB TABLE command rather than relying on Cartographer's #TABLE
commands.
DONE
}
my $dec_or_hex = qr/^(?:-?[0-9]+|-?\$[0-9A-Fa-f]+|\$-?[0-9A-Fa-f]+)$/; # accept both -$hex and $-hex
my $non_negative_dec_or_hex = qr/^(?:[0-9]+|\$[0-9A-Fa-f]+)$/;
my $positive_dec_or_hex = qr/^(?:[0-9]*[1-9][0-9]*|\$[0-9A-Fa-f]*[1-9A-Fa-f][0-9A-Fa-f]*)$/;
my $boolean = qr/Yes|No/;
my $comments = qr/Yes|No|Both/;
my $endian = qr/BIG|LITTLE/;
my $method = qr/POINTER|POINTER_RELATIVE|POINTER_RELATIVE_PC|RAW/;
my $type = qr/NORMAL|FIXED_STRING|FIXED_STRING && FIXED_LINE/;
my %syntax = (
'GAME NAME' => {
value => qr/(.*)/,
required => 1,
},
'BLOCK NAME' => {
value => qr/(.*)/,
required => 1,
},
'TYPE' => {
value => qr/($type)/,
required => 1,
value_deps => {
'FIXED_STRING' => {
required => ['STRING LENGTH', 'STRING END'],
forbidden => ['LINE LENGTH', 'LINE END'],
},
'FIXED_STRING && FIXED_LINE' => {
required => ['STRING LENGTH', 'STRING END', 'LINE LENGTH', 'LINE END'],
},
'NORMAL' => {
forbidden => ['STRING LENGTH', 'STRING END', 'LINE LENGTH', 'LINE END'],
},
},
},
'STRING LENGTH' => { value => qr/($positive_dec_or_hex)/, },
'STRING END' => { value => qr/($boolean)/, },
'END CTRL' => {
value => qr/(.*)/,
default => '(END)',
},
'LINE LENGTH' => { value => qr/($positive_dec_or_hex)/, },
'LINE END' => { value => qr/($boolean)/, },
'LINE CTRL' => {
value => qr/(.*)/,
default => '(LINE)',
},
'METHOD' => {
value => qr/($method)/,
required => 1,
value_deps => {
'POINTER' => {
required => ['POINTER TABLE START', 'POINTER TABLE STOP', 'POINTER SIZE', 'POINTER SPACE', 'POINTER ENDIAN', 'ATLAS PTRS'],
forbidden => ['BASE POINTER', 'SCRIPT START'],
},
'POINTER_RELATIVE' => {
required => ['POINTER TABLE START', 'POINTER TABLE STOP', 'POINTER SIZE', 'POINTER SPACE', 'POINTER ENDIAN', 'ATLAS PTRS', 'BASE POINTER'],
forbidden => ['SCRIPT START'],
},
'POINTER_RELATIVE_PC' => {
required => ['POINTER TABLE START', 'POINTER TABLE STOP', 'POINTER SIZE', 'POINTER SPACE', 'POINTER ENDIAN', 'ATLAS PTRS', 'BASE POINTER'],
forbidden => ['SCRIPT START'],
},
'RAW' => {
required => ['SCRIPT START', 'SCRIPT STOP'],
forbidden => ['POINTER TABLE START', 'POINTER TABLE STOP', 'POINTER SIZE', 'POINTER SPACE', 'POINTER ENDIAN', 'BASE POINTER', 'STRINGS PER POINTER'],
},
}
},
'POINTER ENDIAN' => { value => qr/($endian)/, },
'POINTER TABLE START' => { value => qr/($non_negative_dec_or_hex)/, },
'POINTER TABLE STOP' => { value => qr/($non_negative_dec_or_hex)/, },
'POINTER SIZE' => { value => qr/($positive_dec_or_hex)/, },
'POINTER SPACE' => { value => qr/($non_negative_dec_or_hex)/, },
'ATLAS PTRS' => {
value => qr/($boolean)/,
default => 'No',
},
'BASE POINTER' => { value => qr/($dec_or_hex)/, },
'RELATIVE PC' => { value => qr/.*/, }, # broken Cartographer command ignored by abcde
'SCRIPT START' => { value => qr/($non_negative_dec_or_hex)/, },
'SCRIPT STOP' => { value => qr/($non_negative_dec_or_hex)/, },
'TABLE' => {
value => qr/(.*)/,
required => 1,
},
'COMMENTS' => {
value => qr/($comments)/,
required => 1,
},
'END BLOCK' => { value => qr//, },
'STRINGS PER POINTER' => {
value => qr/($non_negative_dec_or_hex)/,
default => 1,
},
'STRING END REALIGN MULTIPLE' => { value => qr/($positive_dec_or_hex)/, },
'STRING END REALIGN OFFSET' => { value => qr/($non_negative_dec_or_hex)/, },
'AUTO JUMP START' => {
value => qr/($non_negative_dec_or_hex)/,
deps => ['AUTO JUMP STOP'],
},
'AUTO JUMP STOP' => {
value => qr/($non_negative_dec_or_hex)/,
deps => ['AUTO JUMP START'],
},
'SHOW END ADDRESS' => {
value => qr/($boolean)/,
default => 'Yes',
},
'STRINGS END AT NEXT POINTER' => {
value => qr/($boolean)/,
default => 'No',
},
'SORT OUTPUT BY STRING ADDRESS' => {
value => qr/($boolean)/,
default => 'No',
},
'SUB TABLE' => { value => qr/(.*)/, },
'TABLE ID' => { value => qr/(.*)/, },
'TRIM TRAILING NEWLINES' => {
value => qr/($boolean)/,
default => 'Yes',
},
);
my $commands = join('|', keys %syntax);
$commands = qr/$commands/;
my $Encode = Encode::find_encoding('UTF-8');
sub updateOptions {
# Cartographer only works one way; force this in case the correct option was not provided on the command line; issue a warning if an incorrect option was provided
my $mode = 'bin2text';
if (!defined($abcde::options{mode})) {
$abcde::options{mode} = $mode;
} elsif ($abcde::options{mode} ne $mode) {
$abcde::options{mode} = $mode;
warn ("Incorrect mode detected; switching to $abcde::options{mode}!\n");
}
}
sub handleCommandFile {
my ($class, $args) = @_;
my ($rom_filename, $commands_filename, $output_base_filename, $output_switch) = @$args;
if (scalar(@$args) != 4 || $output_switch !~ /^-[sm]$/) {
die("$usage\n");
}
$abcde::options{errorEOF} = 1;
# parse $commands_filename into blocks, set up the tables used by those blocks, read the ROM file, and then output translated data
my %tableFileNames = ();
my ($blocks, $info) = $class->parseCommandFile($commands_filename, \%tableFileNames);
$class->parseTableFiles(\%tableFileNames, $commands_filename);
$class->readInputFile($rom_filename);
$class->processBlocks($blocks, $info, $output_base_filename, $output_switch);
}
sub parseCommandFile {
my ($class, $commands_filename, $tableFileNames) = @_;
my @blocks = ();
my $block = {};
my $info = {};
open(my $file, '<:raw', $commands_filename) or die("Can't open $commands_filename for read!\n");
while (my $line = <$file>) {
eval { $line = $Encode->decode($line, Encode::FB_CROAK); };
if ($@) {
(my $err = $@) =~ s/^utf8 | at .*//gs;
die("Input file encoding error: $err at $commands_filename line ".$file->input_line_number()."!\n");
}
$line = Unicode::Normalize::NFD($line) if ($abcde::options{unicode_normalization});
if ($file->input_line_number() == 1) {
$line =~ s/^\N{U+FEFF}//; # strip out Unicode BOM if present
}
$line =~ s/\R//; # strip trailing newline
$line =~ s|\s*//.*||; # ignore comments and any preceding whitespace
next if ($line =~ /^\s*$/);
# Cartographer enforces a loose order to commands (e.g. can't have TABLE before SCRIPT START, but SCRIPT START can appear either before or after SCRIPT STOP); we'll be more permissive
die("Unrecognized line '$line' at $commands_filename line ".$file->input_line_number()." in block #".scalar(@blocks)."!\n") unless ($line =~ /^#($commands)(?::\s*(.*))?$/);
my $command = $1;
my $value = $2 // ''; # note that leading whitespace was not included in $2; Cartographer reports a parse error on values with trailing whitespace
# check values, throw errors
if ($value !~ m/$syntax{$command}->{value}/) {
die("Unrecognized value '$value' for command '$command' at $commands_filename line ".$file->input_line_number()." in block #".scalar(@blocks)."!\n");
}
# when encountering duplicate commands, Cartographer exhibits some interesting behaviour:
# depending on the command, Cartographer may report a parse error or it may overwrite, preserve, or conditionally overwrite (e.g. FIXED_STRING overwrites NORMAL, but not the other way around) the old value
# there's no good reason for specifying a command multiple times in the same block, so we'll throw an error to get the user to clean up their command file
if (defined($block->{$command}) && $command ne 'SUB TABLE') {
die("Duplicate command '$command' ('$block->{$command}') at $commands_filename line ".$file->input_line_number()." in block #".scalar(@blocks)."!\n");
}
$block->{$command} = $value;
if ($command eq 'SUB TABLE') { # this is a file-level command not part of any specific block
$tableFileNames->{File::Spec->rel2abs(File::Spec->catdir($abcde::options{baseRelativePath}, $value))} = 1;
next;
} elsif ($line eq '#END BLOCK') {
# check for missing required commands and presence of mutually-exclusive commands
# GAME NAME for current block defaults to value of previous block
if ($blocks[-1]) {
$block->{$_} //= $blocks[-1]->{$_} foreach ('GAME NAME');
}
# check BLOCK NAME first so we can use its value in later error messages
if (!defined($block->{'BLOCK NAME'})) {
die("Missing the required command 'BLOCK NAME' in block #".scalar(@blocks)."!\n");
}
foreach my $command (sort keys %syntax) {
# required block commands
die("Missing the required command '$command' in block #".scalar(@blocks)." ($block->{'BLOCK NAME'})!\n") if ($syntax{$command}->{required} && !defined($block->{$command}));
next unless (defined($block->{$command}));
# required other commands based on presence of this command
if ($syntax{$command}->{deps}) {
foreach my $other_command (@{$syntax{$command}->{deps}}) {
die("When using $command, you must also include all of (".join(',', @{$syntax{$command}->{deps}}).") in block #".scalar(@blocks)." ($block->{'BLOCK NAME'})!\n") unless (defined($block->{$other_command}));
}
}
if ($syntax{$command}->{value_deps} && $syntax{$command}->{value_deps}->{$block->{$command}}) {
# required other commands based on this command's value
foreach my $other_command (@{$syntax{$command}->{value_deps}->{$block->{$command}}->{required}}) {
die("Missing the required command '$other_command' for $command: $block->{$command} in block #".scalar(@blocks)." ($block->{'BLOCK NAME'})!\n") unless (defined($block->{$other_command }));
}
# forbidden other commands based on this command's value
foreach my $other_command (@{$syntax{$command}->{value_deps}->{$block->{$command}}->{forbidden}}) {
die("The command '$other_command' is not valid for $command: $block->{$command} in block #".scalar(@blocks)." ($block->{'BLOCK NAME'})!\n") if (defined($block->{$other_command}));
}
}
}
# apply default values
foreach my $command (sort keys %syntax) {
if (!defined($block->{$command}) && defined($syntax{$command}->{default})) {
$block->{$command} = $syntax{$command}->{default};
}
}
if ($block->{'ATLAS PTRS'} eq 'Yes') {
$info->{some_block_has_Atlas_pointers} = 1;
}
if (defined('STRING END REALIGN MULTIPLE') && !defined($block->{'STRING END REALIGN OFFSET'})) {
$block->{'STRING END REALIGN OFFSET'} = 0;
}
# capture table file name
$block->{'TABLE'} = File::Spec->rel2abs(File::Spec->catdir($abcde::options{baseRelativePath}, $block->{'TABLE'}));
$tableFileNames->{$block->{'TABLE'}} = 1;
# convert hexadecimal strings to decimal numbers and bytes to bits
foreach my $command ('STRING LENGTH', 'LINE LENGTH', 'POINTER TABLE START', 'POINTER TABLE STOP', 'POINTER SIZE', 'POINTER SPACE', 'BASE POINTER', 'SCRIPT START', 'SCRIPT STOP', 'AUTO JUMP START', 'AUTO JUMP STOP', 'STRINGS PER POINTER', 'STRING END REALIGN MULTIPLE', 'STRING END REALIGN OFFSET') {
next unless (defined($block->{$command}));
my $negative = $block->{$command} =~ s/^(?:\$-|-\$)/\$/; # strip negative sign since hex() chokes on negative hex strings
if ($block->{$command} =~ s/^\$//) { # strip '$'
$block->{$command} = hex($block->{$command}); # convert hex string to decimal integer
} # else: already is a decimal integer
unless ($command eq 'STRINGS PER POINTER') { # STRINGS PER POINTER is a count of end tokens, not bytes
$block->{$command} = $block->{$command} * 8; # convert byte address to bit address
}
if ($negative) {
$block->{$command} *= -1; # reapply negative sign
}
}
# Cartographer actually dumps each block immediately, but we'll validate all the blocks before dumping anything
push(@blocks, $block);
$block = {};
}
}
if (!@blocks) {
die("No blocks to dump!\n");
}
if (scalar(keys(%$block))) {
die("The final block does not contain an 'END BLOCK' command!\n");
}
close($file);
return (\@blocks, $info);
}
sub parseTableFiles {
my ($class, $tableFileNames) = @_;
foreach my $fileName (sort keys %$tableFileNames) {
next if ($abcde::tablesByFileName{$fileName}); # if table was already processed from the main command line, don't re-parse it here
abcde::Table::Table->addTableFile($fileName);
}
# do stuff that can't be done until we've finished parsing all of the table files
foreach my $table_num (0..$#abcde::allTables) {
my $table = $abcde::allTables[$table_num];
$table->finalize();
$table->{AtlasID} ||= 'Table_'.$table_num;
}
}
sub readInputFile {
my ($class, $rom_filename) = @_;
local $/ = undef;
open(ROM, '<', $rom_filename) || die("Can't open $rom_filename for read!\n");
binmode(ROM, ':raw');
binmode(STDOUT, ':utf8');
$abcde::data = <ROM>;
$abcde::data = unpack('B*', $abcde::data); # mode is bin2text, so convert bytes to bits
close(ROM);
}
sub processBlocks {
my ($class, $blocks, $info, $output_base_filename, $output_switch) = @_;
my $OUTPUT;
my $embedded_pointer_num = 0;
if ($output_switch eq '-s') { # all blocks get written to a single file
open($OUTPUT, '>:encoding(UTF-8)', $output_base_filename.'.txt') || die("Can't open '$output_base_filename.txt' for write!\n");
}
# run each block's commands
foreach my $block_num (0..scalar(@$blocks) - 1) {
my $start = Time::HiRes::gettimeofday();
my $block = $blocks->[$block_num];
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
$abcde::stats{blocks}->{$block->{'BLOCK NAME'}} = {
bits => 0,
chars => 0,
strings => 0,
time => 0,
tokens => 0,
};
}
print STDOUT "Dumping BLOCK [$block->{'BLOCK NAME'}]...\n";
if (defined($block->{'TABLE ID'}) && !$abcde::tablesByID{$block->{'TABLE ID'}}) {
die("No such table ID '$block->{'TABLE ID'}' in block #block_num ($block->{'BLOCK NAME'})!\n");
}
my $formatted_block_num = sprintf('%03u', $block_num); # like Cartographer, start with a 3-digit number and expand to more digits as required
if ($output_switch eq '-m') { # each block gets written to its own file
open($OUTPUT, '>:encoding(UTF-8)', $output_base_filename.'_'.$formatted_block_num.'.txt') || die("Can't open '${output_base_filename}_$formatted_block_num.txt' for write!\n");
}
if ($block_num == 0 || $output_switch eq '-m') {
print $OUTPUT "//GAME NAME:\t\t".$block->{'GAME NAME'}."\n";
if ($info->{some_block_has_Atlas_pointers}) {
print $OUTPUT "\n// Define required TABLE variables and load the corresponding tables\n";
foreach my $table_num (0..$#abcde::allTables) {
my $table = $abcde::allTables[$table_num];
print $OUTPUT "#VAR($table->{AtlasID}, TABLE)\n";
print $OUTPUT "#ADDTBL(\"$table->{fileName}\", $table->{AtlasID})\n";
}
}
}
print $OUTPUT "\n//BLOCK #".sprintf('%03u', $block_num)." NAME:\t\t".$block->{'BLOCK NAME'};
$block->{pointers} = [];
if ($block->{'METHOD'} eq 'RAW') {
# we can treat this like a pointer with only a target address
push(@{$block->{pointers}}, {pointer_num => scalar(@{$block->{pointers}}), target => $block->{'SCRIPT START'}});
} else { # some kind of POINTER
my $step_size = $block->{'POINTER SIZE'} + $block->{'POINTER SPACE'};
my $pointer_num = 0;
# like Cartographer, POINTER TABLE STOP is actually the address *after* the pointer table stops
# like Cartographer, POINTER TABLE STOP values are equivalent anywhere between the beginning and end of the final pointer + its spacing
for (my $pointer_address = $block->{'POINTER TABLE START'}; $pointer_address < $block->{'POINTER TABLE STOP'}; $pointer_address += $step_size) {
my $stringAddress_bit = substr($abcde::data, $pointer_address, $block->{'POINTER SIZE'});
if ($block->{'POINTER ENDIAN'} eq 'LITTLE') {
my @bytes = $stringAddress_bit =~ m/(.{8})/g;
$stringAddress_bit = join('', reverse(@bytes));
} # else: bytes are already in the right order
$stringAddress_bit = oct('0b'.$stringAddress_bit) * 8; # convert from ROM's byte address to our bit address
if ($block->{'METHOD'} eq 'POINTER_RELATIVE') {
$stringAddress_bit += $block->{'BASE POINTER'};
} elsif ($block->{'METHOD'} eq 'POINTER_RELATIVE_PC') {
# Cartographer's #METHOD: POINTER_RELATIVE_PC appears to be completely broken:
# the documentation is self-contradictory and the code appears to reject any attempt at using POINTER_RELATIVE_PC with any of the documented commands.
# So instead we'll leverage the existing BASE POINTER command (which is maybe what RELATIVE BASE was supposed to be?)
$stringAddress_bit += $block->{'BASE POINTER'} + $pointer_address;
} # else: $block->{'METHOD'} eq 'POINTER' and no further address adjustments are necessary
push(@{$block->{pointers}}, {pointer_num => scalar(@{$block->{pointers}}), address => $pointer_address >> 3, target => $stringAddress_bit});
}
}
if ($block->{'SORT OUTPUT BY STRING ADDRESS'} eq 'Yes') {
$block->{pointers} = [sort {$a->{target} cmp $b->{target}} @{$block->{pointers}}];
}
my @pointer_output = ();
my ($jmp_start, $jmp_end);
foreach my $pointer_index (0..scalar(@{$block->{pointers}})-1) {
my $string = $class->setupString($block, $pointer_index);
my $output = $class->extract($block, $block->{pointers}->[$pointer_index], $string, 0, \$embedded_pointer_num);
if ($block->{'ATLAS PTRS'} eq 'Yes') {
$jmp_start //= $string->{realStartAddress};
$jmp_end //= $string->{realEndAddress};
$jmp_start = List::Util::min($jmp_start, $string->{startAddress});
$jmp_end = List::Util::max($jmp_end, $string->{realEndAddress});
}
$output = Unicode::Normalize::NFC($output) if ($abcde::options{unicode_normalization});
push(@pointer_output, $output);
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
$abcde::stats{blocks}->{$block->{'BLOCK NAME'}}->{strings} += 1;
}
}
if ($block->{'ATLAS PTRS'} eq 'Yes') {
my $table = (defined($block->{'TABLE ID'}) ? $abcde::tablesByID{$block->{'TABLE ID'}} : $abcde::tablesByFileName{$block->{'TABLE'}}->[0]);
print $OUTPUT "\n#ACTIVETBL(".(defined($block->{'TABLE ID'}) ? "@".$table->{id} : $table->{AtlasID}).") // Activate this block's starting TABLE\n";
print $OUTPUT "\n#JMP(\$".sprintf('%X', $jmp_start >> 3).", \$".sprintf('%X', ($jmp_end >> 3) - 1).") // Jump to insertion point\n";
if (defined($block->{'BASE POINTER'})) {
my $sign = ($block->{'BASE POINTER'} > 0 ? 1 : -1); # strip negative sign since hex() chokes on negative hex strings
my $hdr = ($sign == -1 ? '-' : '').sprintf('%X', ($sign * $block->{'BASE POINTER'}) >> 3);
print $OUTPUT "#HDR(\$$hdr) // Difference between ROM and RAM addresses for pointer value calculations\n";
}
}
print $OUTPUT join('', @pointer_output);
if ($output_switch eq '-m') {
close($OUTPUT);
}
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
$abcde::stats{blocks}->{$block->{'BLOCK NAME'}}->{time} = Time::HiRes::gettimeofday() - $start;
}
}
}
sub setupString {
my ($class, $block, $pointer_index) = @_;
my $table = (defined($block->{'TABLE ID'}) ? $abcde::tablesByID{$block->{'TABLE ID'}} : $abcde::tablesByFileName{$block->{'TABLE'}}->[0]);
my $string = abcde::String::BasicString->create($table);
if (defined($block->{'AUTO JUMP START'})) {
$string->{autoJumps} = [{start => $block->{'AUTO JUMP START'}, stop => $block->{'AUTO JUMP STOP'}}];
}
if ($block->{'STRING END REALIGN MULTIPLE'}) {
$string->{stringEndReAlignMultiple} = $block->{'STRING END REALIGN MULTIPLE'};
$string->{stringEndReAlignOffset} = $block->{'STRING END REALIGN OFFSET'};
}
if (defined($block->{'SCRIPT STOP'})) {
$string->{endAddress} = $block->{'SCRIPT STOP'};
}
if (defined($block->{'SCRIPT START'})) {
$string->{startAddress} = $block->{'SCRIPT START'};
}
if ($block->{'METHOD'} ne 'RAW') {
$string->{endTokenTerminated} = 1;
$string->{stringsPerPointer} = $block->{'STRINGS PER POINTER'};
$string->{startAddress} = $block->{pointers}->[$pointer_index]->{target};
}
if ($block->{'STRINGS END AT NEXT POINTER'} eq 'Yes' && $block->{pointers} && defined($pointer_index) && $block->{pointers}->[$pointer_index + 1] && defined($block->{pointers}->[$pointer_index + 1]->{target})) {
$string->{endTokenTerminated} = 0;
if (defined($string->{endAddress})) {
$string->{endAddress} = List::Util::min($string->{endAddress}, $block->{pointers}->[$pointer_index + 1]->{target});
} else {
$string->{endAddress} = $block->{pointers}->[$pointer_index + 1]->{target};
}
}
$string->{realStartAddress} = $string->{startAddress}; # FIXED_STRING && FIXED_LINE plays games with the string's startAddress, so remember what it originally was
if (!defined($string->{endAddress})) {
$table->canReachEndToken({});
if (!$table->{canReachEndToken}) {
warn("attempting to extract text without an end token or end address; extraction will run to the end of the file!\n");
}
}
return $string;
}
sub extract {
my ($class, $block, $pointer, $string, $is_embedded_pointer, $embedded_pointer_num_ref) = @_;
my ($output, $embedded_pointers) = ('', []);
# header
if ($block->{'METHOD'} eq 'RAW') {
$output .= "\n//Block Range: \$".sprintf('%X', $block->{'SCRIPT START'} >> 3)." - \$".sprintf('%X', $block->{'SCRIPT STOP'} >> 3)."\n"; # Cartographer reports the SCRIPT START/SCRIPT STOP addresses even though that's not what it dumps :/
} elsif ($is_embedded_pointer) {
$output .= "\n//EMBEDDED POINTER #$pointer->{pointer_num} @ \$".sprintf('%X', $pointer->{address})." - SUB-STRING #$pointer->{pointer_num} @ \$".sprintf('%X', int($string->{startAddress} / 8))."\n";
if ($block->{'ATLAS PTRS'} eq 'Yes') {
$output .= "#EMBWRITE($pointer->{pointer_num})\n";
}
} else { # normal pointer
$output .= "\n//POINTER #$pointer->{pointer_num} @ \$".sprintf('%X', $pointer->{address})." - STRING #$pointer->{pointer_num} @ \$".sprintf('%X', int($string->{startAddress} / 8))."\n";
if ($block->{'ATLAS PTRS'} eq 'Yes') {
# Atlas actually only accepts #W16, #W24, and #W32, but Cartographer will quite happily output #W8 too, so I guess we'll do the same
$output .= "#W".$block->{'POINTER SIZE'}."(\$".sprintf('%X', $pointer->{address}).")\n";
}
}
# string
if ($block->{'TYPE'} eq 'FIXED_STRING && FIXED_LINE') {
# loop through $abcde::data from $string->{startAddress} stepping by LINE LENGTH, translate each sub-string and optionally append LINE CTRL to each translated sub-string
my @substrings = ();
my $maxEndAddress = $string->{startAddress} + $block->{'STRING LENGTH'};
while (1) {
$string->{endAddress} = List::Util::min($string->{startAddress} + $block->{'LINE LENGTH'}, $maxEndAddress);
$string->{endAddress} = List::Util::min($block->{'SCRIPT STOP'}, $string->{endAddress}) if (defined($block->{'SCRIPT STOP'}));
last if ($string->{startAddress} >= $string->{endAddress});
$string->parse();
(my $sub_output, $embedded_pointers) = $class->generateOutput($block, $string, $pointer->{target}, $embedded_pointer_num_ref);
push(@substrings, $sub_output);
$string->{startAddress} = $string->{endAddress};
}
$output .= join(($block->{'LINE END'} eq 'Yes' ? $block->{'LINE CTRL'}."\n" : "\n"), @substrings);
} else {
if ($block->{'TYPE'} eq 'FIXED_STRING') {
$string->{endAddress} = $string->{startAddress} + $block->{'STRING LENGTH'};
$string->{endAddress} = List::Util::min($block->{'SCRIPT STOP'}, $string->{endAddress}) if (defined($block->{'SCRIPT STOP'}));
}
$string->parse();
(my $sub_output, $embedded_pointers) = $class->generateOutput($block, $string, $pointer->{target}, $embedded_pointer_num_ref);
$output .= $sub_output;
}
# footer
if (($block->{'TYPE'} eq 'FIXED_STRING' || $block->{'TYPE'} eq 'FIXED_STRING && FIXED_LINE') && $block->{'STRING END'} eq 'Yes') {
$output .= $block->{'END CTRL'}."\n\n";
}
$output =~ s/\n+$// if ($block->{'TRIM TRAILING NEWLINES'} eq 'Yes');
$output .= "\n// current address: \$".sprintf('%X', $string->{realEndAddress} >> 3)."\n" if ($block->{'SHOW END ADDRESS'} eq 'Yes');
foreach (@$embedded_pointers) {
$output .= $class->extract($block, $_, $_->{string}, 1, $embedded_pointer_num_ref);
}
return $output;
}
# override this function to handle game-specific control codes with functionality that cannot be represented in a table file
sub generateOutput {
my ($class, $block, $string, $pointer_address, $embedded_pointer_num_ref) = @_;
my $tokenList = $string->{tokenList} || [];
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
foreach my $token (@$tokenList) {
$abcde::stats{blocks}->{$block->{'BLOCK NAME'}}->{bits} += length($token->{bin});
$abcde::stats{blocks}->{$block->{'BLOCK NAME'}}->{chars} += length($token->{text});
$abcde::stats{blocks}->{$block->{'BLOCK NAME'}}->{tokens} += 1;
}
}
my $output = '';
if ($block->{'COMMENTS'} eq 'Both') {
my @strings = (''); # $tokenList might contain multiple end-token-terminated strings; if so, we want to comment and output each string individually rather than commenting and outputting all the strings together as a combined block
foreach my $token (@$tokenList) {
if ($abcde::options{group}) {
$strings[-1] .= $token->{line} || 'N/A';
$strings[-1] .= "\t".($token->{table} && defined($token->{table}->{fileName}) ? $token->{table}->{fileName} : $abcde::reserved_chars->{open}.'internal'.$abcde::reserved_chars->{close});
$strings[-1] .= "\t".(scalar(keys %{$token->{flags}}) ? $abcde::reserved_chars->{open}.join(',', sort keys %{$token->{flags}}).$abcde::reserved_chars->{close} : '')."\n";
} else {
$strings[-1] .= $token->{text};
}
if ($token->{flags}->{isEndToken} && $token->{text} =~ /\n$/) {
push(@strings, ''); # if we find an end token, start a new output string
}
# since we aren't calling $string->output(), keep track of stats like abcde::String::BasicString does
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
$abcde::stats{totalBits} += length($token->{bin});
$abcde::stats{totalChars} += length($token->{text});
$abcde::stats{totalTokens} += 1;
}
}
pop(@strings) if ($strings[-1] eq ''); # if $tokenList ended with an end token, we would have pushed a useless empty string, so pop that
foreach my $part (@strings) {
(my $new_output = '//'.$part);
$new_output =~ s/\n+$//g if ($block->{'TRIM TRAILING NEWLINES'} eq 'Yes');
$new_output =~ s/\n/\n\/\//g;
$output .= $new_output."\n".$part;
}
} else {
$output = $string->output($string->{tokenList}, $block);
if ($block->{'COMMENTS'} eq 'Yes' && $output ne '') {
($output = '//'.$output) =~ s|\n|\n//|g;
}
}
return ($output, []);
}
1;

View File

@ -0,0 +1,74 @@
package abcde::Cartographer::NES::Battle_of_Olympus;
use strict;
use warnings;
use parent qw(abcde::Cartographer);
# This doesn't cover every Atlas command you'll need in order to get a correct insertion script, but it covers most of the extra work.
# In particular, you'll still need to set ACTIVETBL appropriately when switching between writing function calls and Pascal text,
# and depending on how you're going to arrange the Atlas script, there may be a couple of places where an EMBWRITE happens before the corresponding EMBSET and picks up incorrect EMBTYPE settings.
sub generateOutput {
my ($class, $block, $string, $pointer_address, $embedded_pointer_num_ref) = @_;
my @tokenList = grep {$_->{bin} ne ''} @{$string->{tokenList}}; # strip out internal tokens and only deal with what the game sees
my $currentAddress = $string->{startAddress};
my ($output, $embedded_pointers) = ('', []);
$output = '//' if ($block->{'COMMENTS'} eq 'Yes');
for (my $i = 0; $i < scalar(@tokenList); $i++) {
my $token = $tokenList[$i];
my $tokenText = $token->{text};
$tokenText =~ s|\n|\n//|g if ($block->{'COMMENTS'} eq 'Yes');
$output .= $tokenText;
if ($token->{table} && $token->{table}->{id} && $token->{table}->{id} eq 'functions') {
if ($token->{bin} =~ /^0000(?:0001|0010|0011|0111|1010|1011|1100)$/) { # 0x0[1237ABC]
my $substring = abcde::String::BasicString->create();
foreach (keys %$string) { # copy whatever settings $string had to $substring
$substring->{$_} = $string->{$_};
}
if ($token->{bin} eq '00000011') { # 0x03
# @OneBytePasclaString from 2-byte little endian address given by next 2 tokens + 0xC010
$currentAddress += $tokenList[$i]->{binLength};
$i++;
$output .= $tokenList[$i]->{text}; # need to print the text so that text2bin can parse the token correctly
my $startAddress = 0xC010 + oct('0b'.$tokenList[$i]->{bin});
$currentAddress += $tokenList[$i]->{binLength};
$i++;
$output .= $tokenList[$i]->{text}; # need to print the text so that text2bin can parse the token correctly
$startAddress += 256 * oct('0b'.$tokenList[$i]->{bin});
$substring->{table} = $abcde::tablesByID{'OneBytePascalString'};
$substring->{startAddress} = $startAddress * 8;
if ($block->{'ATLAS PTRS'} eq 'Yes') {
# replace the 2-byte address with #EMBTYPE/#EMBSET
$output .= "\n#SKIP(-2)\n#EMBTYPE(\"LINEAR\", 16, \$0)\n#EMBSET($$embedded_pointer_num_ref)\n";
}
} else {
if ($token->{bin} eq '00000001' || $token->{bin} eq '00000111' || $token->{bin} eq '00001100') { # 0x0[17C]
# @functions from current address + # of bytes given by second next token
$currentAddress += $tokenList[$i]->{binLength};
$i++;
$output .= $tokenList[$i]->{text}; # need to print the text so that text2bin can parse the token correctly
} # else: @functions from current address + # of bytes given by next token
$currentAddress += $tokenList[$i]->{binLength};
$i++;
$output .= $tokenList[$i]->{text}; # need to print the text so that text2bin can parse the token correctly
$substring->{table} = (defined($block->{'TABLE ID'}) ? $abcde::tablesByID{$block->{'TABLE ID'}} : $abcde::tablesByFileName{$block->{'TABLE'}}->[0]);
$substring->{startAddress} = $currentAddress + oct('0b'.$tokenList[$i]->{bin}) * 8;
if ($block->{'ATLAS PTRS'} eq 'Yes') {
$output .= "\n#SKIP(-1)\n#EMBTYPE(\"POINTER_RELATIVE\", 8, \$0)\n#EMBSET($$embedded_pointer_num_ref)\n";
}
}
push(@$embedded_pointers, {address => $currentAddress >> 3, pointer_num => $$embedded_pointer_num_ref, string => $substring});
$$embedded_pointer_num_ref++;
} elsif ($token->{bin} eq '00001001') { # 0x09
# running repeated A* searches for the right Pascal string length is a bajillion times slower than using Atlas' PASCAL commands, so just write '[Dialogue:]' as raw hex
$output .= $abcde::rawTable->{tokensByBin}->{'00001001'}->{text}."\n#ACTIVETBL(upper)\n";
}
}
$currentAddress += $token->{binLength};
}
return ($output, $embedded_pointers);
}
1;

View File

@ -0,0 +1,77 @@
package abcde::String::BasicString;
use strict;
use warnings;
sub create {
my ($class, $table) = @_;
my $self = {
table => $table,
startAddress => 0,
};
bless ($self, $class);
return $self;
}
# parse input
# the default behaviour is to translate everything; override in child classes for strings with different behaviour (e.g. end tokens, fixed-length, data that shouldn't be translated, etc.)
sub parse {
my ($self) = @_;
if ($abcde::options{mode} eq 'bin2text') {
my $startAddress = $self->{startAddress};
my %options = (
autoJumps => $self->{autoJumps} || [],
endTokenTerminated => ($self->{endTokenTerminated} ? 1 : 0),
endAddress => $self->{endAddress},
stringEndReAlignMultiple => $self->{stringEndReAlignMultiple},
stringEndReAlignOffset => $self->{stringEndReAlignOffset},
);
if (defined($self->{stringsPerPointer}) && $self->{stringsPerPointer} > 1) {
$self->{tokenList} = ();
foreach (1..$self->{stringsPerPointer}) {
(my $tokens, $startAddress) = $self->{table}->bin2text(\$abcde::data, $startAddress, \%options);
push(@{$self->{tokenList}}, @$tokens);
}
} else {
($self->{tokenList}, $startAddress) = $self->{table}->bin2text(\$abcde::data, $startAddress, \%options);
}
$self->{realEndAddress} = $startAddress;
} else {
$self->{tokenList} = $self->{table}->text2bin(\$abcde::data);
}
}
sub output {
my ($self, $tokenList) = @_;
my $output = ($abcde::options{group} ? "line\ttable\tflags\n" : '');
$tokenList ||= $self->{tokenList} || [];
for (my $i = 0; $i < scalar(@$tokenList); $i++) {
my $token = $tokenList->[$i];
if ($abcde::options{group}) {
$output .= $token->{line} || 'N/A';
$output .= "\t".($token->{table} && defined($token->{table}->{fileName}) ? $token->{table}->{fileName} : $abcde::reserved_chars->{open}.'internal'.$abcde::reserved_chars->{close});
$output .= "\t".(scalar(keys %{$token->{flags}}) ? $abcde::reserved_chars->{open}.join(',', sort keys %{$token->{flags}}).$abcde::reserved_chars->{close} : '')."\n";
} elsif ($abcde::options{mode} eq 'text2bin') {
$output .= $token->{bin};
} else {
$output .= $token->{text};
}
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
$abcde::stats{totalBits} += length($token->{bin});
$abcde::stats{totalChars} += length($token->{text});
$abcde::stats{totalTokens} += 1;
}
}
if ($abcde::options{printStats}) { # no point collecting stats we aren't going to use
$abcde::stats{totalStrings} += 1;
}
return $output;
}
1;

View File

@ -0,0 +1,230 @@
package abcde::Table::AStarNode;
use strict;
use warnings;
# there are potentially infinitely many edges and vertices in our tree, so only add those that connect to the current vertex
sub discoverNodes {
my ($self) = @_;
my $tableStack = $self->{tableStack};
my $curStack = $tableStack->[0];
my $table = $curStack->{table};
my @newNodes = ();
# stack: table, requiredCount, currentCount, FFToken
#print STDERR "\n".__LINE__.":discoverNodes in ".(defined($table->{fileName}) ? $table->{fileName} : '[internal]').(defined($table->{id}) ? "[$table->{id}]" : "").", pos $self->{pos}, paidCost $self->{paidCost}: ".($self->{token} ? ($self->{token}->{table} && defined($self->{token}->{table}->{fileName}) ? "'$self->{token}->{table}->{fileName}'" : 'N/A').".$self->{token}->{line}/'$self->{token}->{text}' [".join(',', sort keys %{$self->{token}->{flags}})."]" : "No token").", stackStr:'$self->{stackStr}'\n";
#print STDERR __LINE__."\tcoming from: ".($self->{parent} ? ($self->{parent}->{token} ? ($self->{parent}->{token}->{table} && defined($self->{parent}->{token}->{table}->{fileName}) ? "'$self->{parent}->{token}->{table}->{fileName}'" : 'N/A').".$self->{parent}->{token}->{line}/'$self->{parent}->{token}->{text}' [".join(',', sort keys %{$self->{parent}->{token}->{flags}})."]" : "No parent token") : "No parent")."\n";
if ($table->{participatesInSwitching}) {
# if we're trying to match a specific number of tokens and we've matched that number, the only allowable next token is FC
# check the current stack entry first
if ($curStack->{requiredCount} && defined($curStack->{currentCount}) && $curStack->{requiredCount} == $curStack->{currentCount}) {
my @newStack = map {{%{$_}}} @{$tableStack};
shift(@newStack);
# create new Node at current pos with all but the top table on our stack and token = FC
#print STDERR __LINE__.":found FC\n";
return [abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $abcde::internalTokens{FC})];
}
# for other stack entries, FC only happens if the child table is also incrementing its parent's currentCount
foreach my $num (1..scalar(@{$tableStack}) - 1) {
next unless ($tableStack->[$num - 1]->{addNumTokens});
my $stack = $tableStack->[$num];
if ($stack->{requiredCount} && defined($stack->{currentCount}) && $stack->{requiredCount} == $stack->{currentCount}) {
my @newStack = map {{%{$_}}} @{$tableStack};
shift(@newStack);
# create new Node at current pos with all but the top table on our stack and token = FC
#print STDERR __LINE__.":found FC\n";
return [abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $abcde::internalTokens{FC})];
}
}
# otherwise, if we're at the beginning of a parameter in a control token with parameters, the only allowable next token is falling back (if requiredCount == -1) or switching to that parameter's table (everything else)
if (!defined($curStack->{currentCount})) {
my @newStack = map {{%{$_}}} @{$tableStack};
if ($newStack[0]->{requiredCount} eq '-1') {
if (scalar(@newStack) > 1) {
shift(@newStack); # shift the switch to paramStack
shift(@newStack); # shift the previous table
# create new Node at current pos with parent table's stack and token = FI
#print STDERR __LINE__.":found -1\n";
return [abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $abcde::internalTokens{FI})];
}
#print STDERR __LINE__.":found invalid -1\n";
return []; # can't pop the start table!
} else {
$newStack[0]->{currentCount} = 0;
# create new Node at current pos with stack's currentCount initialized to 0 and token = S
#print STDERR __LINE__.":found switch from '$self->{token}->{table}->{fileName}' to '$table->{fileName}'\n";
return [abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $curStack->{paramTable}->{switchTokens}->{$table})];
}
}
# if we got to the current table with a forced-fallback token, also try using that token to fall back to the previous table
if (scalar(@{$tableStack}) > 1 && $curStack->{FFToken}) {
my @newStack = map {{%{$_}}} @{$tableStack};
shift(@newStack);
# in order to stay in sync with the longest-leftmost extraction algorithm, we'll need to insert the closing binary for the toggle switch
# create new Node at current pos with all but the top table on our stack and token = forced fallback from current table to previous table
#print STDERR __LINE__.":found FF ".(defined($curStack->{FFToken}->{table}->{fileName}) ? "'$curStack->{FFToken}->{table}->{fileName}'" : 'N/A').".$curStack->{FFToken}->{line}/'$curStack->{FFToken}->{text}' [".join(',', sort keys %{$curStack->{FFToken}->{flags}})."]\n";
push(@newNodes, abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $curStack->{FFToken}));
}
# whether we can match another token or not, try each control code that has no label
foreach my $token (@{$table->{switchTokensByBin}}) {
#print STDERR __LINE__.":found ".(defined($token->{table}->{fileName}) ? "'$token->{table}->{fileName}'" : 'N/A').".$token->{line}/'$token->{text}' [".join(',', sort keys %{$token->{flags}})."], switches:[".join(',', map {($_->{table_id} // '').':'.$_->{requiredCount}} @{$token->{params}})."]\n";
next unless ($token->{text} eq '');
# don't consider any path that involves a cycle between real input characters, otherwise we end up searching an exponentially larger space with an extremely low probability of success
if (($self->{tablesSincePosChange} || 0) < scalar(@abcde::allTables)) {
next unless ($self->is_token_usable($token));
# create new Node at current pos with our stack + new table/count and token = switch token
#print STDERR __LINE__."\tand using it!\n";
my @newStack = map {{%{$_}}} @{$tableStack};
$newStack[0]->{currentCount} += $token->{numTokens};
# if the param that switched into this table indicated that sub-table matches should count towards parent table matches, also update its currentCount
foreach my $num (0..$#newStack) {
if ($newStack[$num + 1] && $newStack[$num]->{addNumTokens}) {
$newStack[$num + 1]->{currentCount} += $token->{numTokens};
} else {
last;
}
}
push(@newNodes, abcde::Table::AStarNode->create($self, $self->{pos}, [map {{%{$_}}} @{$token->{paramStack}}, @newStack], $token));
}
}
}
if (!defined($table->{tokensByPos}) || !$table->{tokensByPos}->[$self->{pos}]) {
# determine all tokens that are capable of consuming text from the current position
#print STDERR __LINE__.":setup tokensByPos for '$table->{fileName}' at pos $self->{pos}\n";
$table->{tokensByPos}->[$self->{pos}] = $table->getAllTextMatches($Table::text2bin_stringRef, $self->{pos});
}
if (substr($$Table::text2bin_stringRef, $self->{pos}, 1) eq $abcde::reserved_chars->{open} && (!defined($abcde::rawTable->{tokensByPos}) || !$abcde::rawTable->{tokensByPos}->[$self->{pos}])) {
# determine all tokens that are capable of consuming text from the current position
#print STDERR __LINE__.":setup tokensByPos for \$abcde::rawTable at pos $self->{pos}\n";
$abcde::rawTable->{tokensByPos}->[$self->{pos}] = $abcde::rawTable->getAllTextMatches($Table::text2bin_stringRef, $self->{pos});
}
# otherwise, if we are the bottom table on the stack and we can't match any tokens but $abcde::rawTable can, switch to $abcde::rawTable
if (scalar(@{$tableStack}) == 1 && $abcde::rawTable->{tokensByPos} && $abcde::rawTable->{tokensByPos}->[$self->{pos}] && scalar(@{$abcde::rawTable->{tokensByPos}->[$self->{pos}]})) {
my @newStack = ({table => $abcde::rawTable, requiredCount => 1, currentCount => 0, FFToken => undef}, map {{%{$_}}} @{$tableStack});
# create new Node at new pos with our stack + new table/count
#print STDERR __LINE__.":found switch to \$abcde::rawTable\n";
return [abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $table->{switchTokens}->{$abcde::rawTable})];
}
# otherwise, if we can match any input tokens in the current table, try each of them
if (scalar(@{$table->{tokensByPos}->[$self->{pos}]})) {
foreach my $token (@{$table->{tokensByPos}->[$self->{pos}]}) {
#print STDERR __LINE__.":found ".(defined($token->{table}->{fileName}) ? "'$token->{table}->{fileName}'" : 'N/A').".$token->{line}/'$token->{text}' [".join(',', sort keys %{$token->{flags}})."]\n";
if ($table->{participatesInSwitching} && !$table->{allTokensHaveSameBinLength}) {
next unless ($self->is_token_usable($token));
}
#print STDERR __LINE__."\tand using it!\n";
my @newStack = map {{%{$_}}} @{$tableStack};
$newStack[0]->{currentCount} += $token->{numTokens};
# if the param that switched into this table indicated that sub-table matches should count towards parent table matches, also update its currentCount
foreach my $num (0..$#newStack) {
if ($newStack[$num + 1] && $newStack[$num]->{addNumTokens}) {
$newStack[$num + 1]->{currentCount} += $token->{numTokens};
} else {
last;
}
}
if ($token->{params}) {
# create new Node at new pos with parameter's stack followed by our stack + 1 token
push(@newNodes, abcde::Table::AStarNode->create($self, $self->{pos} + $token->{textLength}, [map {{%{$_}}} @{$token->{paramStack}}, @newStack], $token));
} elsif ($token->{flags}->{isEndToken}) {
# encountering an end token should trigger fallback to the start table
@newStack = \%{$tableStack->[-1]};
my $parent = {%$self};
$parent->{tablesSincePosChange} = 0;
$parent->{unusableSuffixes} = {};
#print STDERR __LINE__.":found E, reset to start table\n";
return [abcde::Table::AStarNode->create($parent, $self->{pos} + $token->{textLength}, \@newStack, $token)];
} else {
# create new Node at new pos with our stack + 1 token
push(@newNodes, abcde::Table::AStarNode->create($self, $self->{pos} + $token->{textLength}, \@newStack, $token));
}
}
# otherwise, if we can't match any input tokens and we're not the bottom table on the stack, try falling back to the previous table
} elsif (scalar(@{$tableStack}) > 1) {
my @newStack = map {{%{$_}}} @{$tableStack};
shift(@newStack);
if (!$self->{token}->{flags}->{S}) { # if falling back is the first thing to happen after a switch, don't push anything
# create new Node at current pos with all but the top table on our stack and token = FB
#print STDERR __LINE__.":found FB\n";
#push(@newNodes, abcde::Table::AStarNode->create($self, $self->{pos}, \@newStack, $abcde::internalTokens{FB}));
} else {
#print STDERR __LINE__.":skipping FB\n";
}
} elsif (scalar(@{$tableStack}) <= 0) {
die("Congratulations, somebody made a code change that broke tableStack logic! Go fix whatever it was!");
}
return \@newNodes;
}
sub create {
my ($class, $parent, $pos, $stack, $token) = @_;
my $self = {
parent => $parent, # reference to previous Node; need this so we can retrace our path once we reach the goal
tableStack => $stack, # arrayref containing the stack of table switch requirements (current table + any tables that the parameters of previously encountered control codes have committed us to switching to) at time of Node's creation
pos => $pos, # string character index of last character covered by this Node's token
token => $token, # token to be used at string character index
paidCost => 0, # "paid cost" portion of A* cost heuristic calculation
tablesSincePosChange => 0, # number of tables we've been to since the last pos change; used to prevent infinite loops
stackStr => '', # scalar string for easy stack equivalency comparisons; format of each piece is "$matchTable,$requiredCount,$currentCount, $fallbackBin"
unusableSuffixes => {}, # list of bit sequences that would concatenate with token's binary to form a different token
};
foreach (@$stack) {
$self->{stackStr} .= (defined($_->{table}->{fileName}) ? $_->{table}->{fileName} : $abcde::reserved_chars->{open}.'internal'.$abcde::reserved_chars->{close}).','.$_->{requiredCount}.','
. (defined($_->{currentCount}) ? ($_->{requiredCount} > 0 ? $_->{currentCount} : '0') : 'undef').','
. ($_->{FFToken} ? $_->{FFToken}->{bin} : '').':';
}
if ($parent && $token) {
$self->{paidCost} = $parent->{paidCost} + $token->{binLength};
if ($pos == $parent->{pos}) {
$self->{tablesSincePosChange} = $parent->{tablesSincePosChange} + 1;
}
if ($token->{binLength}) {
foreach my $suffix (keys(%{$self->{parent}->{unusableSuffixes}})) {
if ($self->{token}->{bin} eq $suffix) {
# if the current token is one of our parent's unusableSuffixes, something went very wrong upstream
die("Congratulations, somebody made a code change that broke the unusable suffix logic! Go fix whatever it was!");
} elsif (substr($suffix, 0, $token->{binLength}) eq $token->{bin}) {
# if the current token is a proper prefix of one of our parent's unusableSuffixes, add the remainder of the unusable suffix to our unusableSuffixes
$self->{unusableSuffixes}->{substr($suffix, $token->{binLength})} = 1;
} # else: the current token is not an unusable suffix, so we're all good
}
} else {
# if we're not inserting any binary, then we simply inherit our parent's (very possibly empty) unusableSuffixes
$self->{unusableSuffixes} = $self->{parent}->{unusableSuffixes};
}
}
$self->{minCost} = $self->{paidCost} + $abcde::minBinPerText * ($Table::text2bin_stringRef_length - $pos); # A* cost heuristic
return bless ($self, $class);
}
# the binary encoding of a sequence of text tokens will be interpreted as a different sequence of binary tokens by a longest-prefix tokenizing algorithm iff
# token A is a prefix of token B and token A is followed by a sequence of tokens C whose binary encoding is B[an..bn]
# i.e. C starts with the binary sequence that is suffixed to A to form B
#
# e.g. if we want to insert binary that tokenizes as (ABC)(D) but we have a table entry for (ABCD), our binary will be tokenized as (ABCD) instead of (ABC)(D).
# e.g. if we want to insert binary that tokenizes as (ABC)(D)(EFGH) but we have a table entry for (ABCDEF), our binary will be tokenized as (ABCDEF)(GH) (with (GH) possibly also tokenized incorrectly) instead of (ABC)(D)(EFGH).
sub is_token_usable {
my ($self, $token) = @_;
if ($self->{parent} && $token->{binLength}) {
foreach my $suffix (keys(%{$self->{parent}->{unusableSuffixes}})) {
if (substr($token->{bin}, 0, length($suffix)) eq $suffix) {
# if one of our parent's unusableSuffixes is prefix of $token's binary, then $token is unusable
return 0;
} # else: $token's binary is not an unusable suffix (though it may still be a proper prefix of an unusable suffix, in which case we can't determine usability until we see the next token(s)), so we're all good for now
}
} # else: either our parent is root or we're not inserting any binary; either way, $token is usable
return 1;
}
1;

View File

@ -0,0 +1,493 @@
package abcde::Table::Table;
use strict;
use warnings;
use Encode ();
use POSIX ();
use Unicode::Normalize ();
use abcde::Table::AStarNode;
use abcde::Table::Token;
use Hash::PriorityQueue;
use List::Util ();
sub addTableFile {
my ($class, $fileName) = @_;
my $tables = $class->parseTableFile($fileName);
$abcde::tablesByFileName{$fileName} = $tables;
foreach my $table (@$tables) {
if (defined($table->{id})) {
if ($abcde::tablesByID{$table->{id}}) {
die("duplicate table id '$table->{id}' in '$table->{fileName}'!");
} else {
$abcde::tablesByID{$table->{id}} = $table;
}
}
push(@abcde::allTables, $table);
}
return $tables;
}
# Read table(s) from a file.
# Handling duplicate table IDs is the caller's responsibility!
#
# $fileName: the file name to read table(s) from
#
# Returns an array of table objects.
sub parseTableFile {
my ($class, $fileName) = @_;
my $table = bless ({
fileName => $fileName,
}, $class);
my @tables = ($table);
open(TABLE, '<', $fileName) || die("can't open TABLE '$fileName'");
while (my $line = <TABLE>) {
$line =~ s/\R\z//; # strip newline
if ($. == 1) {
# strip out Unicode BOM if present
$line =~ s/^\x{EF}\x{BB}\x{BF}//;
}
next if ($line eq '');
next if (substr($line, 0, 1) eq '#'); # ignore comments
$line = eval { Encode::decode('UTF-8', $line, 1); }; # check to see whether $line is valid UTF-8
if ($@) {
die("error: $@ when reading $fileName line $.\n");
}
$line = Unicode::Normalize::NFD($line) if ($abcde::options{unicode_normalization});
if ($line =~ /^@([^$abcde::reserved_chars->{open}$abcde::reserved_chars->{close}]+)$/) {
# table id: @tableId
my $id = $1;
if (defined($table->{id})) {
if ($abcde::options{allowMultipleTablesPerFile}) {
# start a new table
$table = bless ({
fileName => $fileName,
id => $id,
}, $class);
push(@tables, $table);
} else {
die("too many table IDs at '$fileName' line $.!\nIn order to use multi-table files, you need to add the switch --multi-table-files to your abcde command\n");
}
} else {
$table->{id} = $id;
}
} else {
my $token = abcde::Table::Token->parseLine($table, $line, $.);
if (defined($table->{tokensByBin}->{$token->{bin}})) {
# In cases like hiragana he vs. katakana he or normal kana vs. small kana, it might be useful to allow different text entries to have the same hex;
# this idea is currently filed under "more trouble than it's worth".
die("duplicate binary value at '$fileName' lines $table->{tokensByBin}->{$token->{bin}}->{lineNum} and $.!\n");
}
$table->{tokensByBin}->{$token->{bin}} = $token;
if ($token->{params}) {
push(@{$table->{switchTokensByBin}}, $token);
}
if ($token->{flags}->{isEndToken}) {
$table->{canReachEndToken} = 1;
}
}
}
close(TABLE);
return \@tables;
}
sub finalize {
my ($table) = @_;
return if ($table->{finalized});
$table->{participatesInSwitching} //= 0;
if ($table->{switchTokensByBin}) {
$table->{participatesInSwitching} = 1;
$table->{switchTokensByBin} = [sort {$a->{bin} cmp $b->{bin}} @{$table->{switchTokensByBin}}]; # sort to encourage determinism
foreach my $token (@{$table->{switchTokensByBin}}) {
my @paramStack = ();
foreach my $num (0..scalar(@{$token->{params}}) - 1) {
my $param = $token->{params}->[$num];
# add direct references to each parameter's table, defaulting parameters with no table ID to $abcde::rawTable
$param->{table} = (defined($param->{table_id}) ? $abcde::tablesByID{$param->{table_id}} : ($param->{rawType} eq 'bin' ? $abcde::rawBitTable : $abcde::rawTable));
if (!$param->{table}) {
die("table '$table->{fileName}' contains a control code table switch parameter to non-existant table '$param->{table_id}' (did you forget to load whatever file contains that table?)");
}
push(@{$table->{child_tables}}, $param->{table});
$param->{table}->{participatesInSwitching} = 1;
# setup stack requirements for insertion
push(@paramStack, {%$param});
$table->{switchTokens}->{$param->{table}} ||= abcde::Table::Token->create($table, {S => 1}, '', 0, ''); # token for switching from this table to $param->{table}
}
$token->{paramStack} = \@paramStack;
}
}
if ($table ne $abcde::rawTable && $table ne $abcde::rawBitTable) {
$table->{switchTokens}->{$abcde::rawTable} ||= abcde::Table::Token->create($table, {S => 1}, '', 0, ''); # token for switching from this table to $abcde::rawTable
$table->{rawTableSwitchToken} = abcde::Table::Token->create($table, {}, '', 0, '');
$table->{rawTableSwitchToken}->{params} = [{table => $abcde::rawTable, requiredCount => 1}]; # the actual switch parameter
}
# sort by descending length so that we try matching longer left-hand sides before shorter left-hand sides
$table->{binPat} = join('|', sort {length($b) <=> length($a)} keys %{$table->{tokensByBin}});
$table->{binPat} = ($table->{binPat} eq '' ? qr/^$/ : qr/$table->{binPat}/);
# determine which binary suffixes can be added to each of our tokens to turn them into one of our other tokens
# determine maxBinLength for use in bin2text and allTokensHaveSameBinLength for use in text2bin
$table->{maxBinLength} = 0;
$table->{allTokensHaveSameBinLength} = 1;
my $tableBinLength;
my @bins = sort keys %{$table->{tokensByBin}};
for (my $i = 0; $i <= $#bins; $i++) {
my $token = $table->{tokensByBin}->{$bins[$i]};
my $binLength = length($bins[$i]);
for (my $j = 1; $j <= $#bins - $i; $j++) {
if ($bins[$i] eq substr($bins[$i + $j], 0, $binLength)) {
$token->{unusableSuffixes}->{substr($bins[$i + $j], $binLength)} = $table->{tokensByBin}->{$bins[$i + $j]};
} else {
last;
}
}
if ($table->{allTokensHaveSameBinLength}) {
$tableBinLength //= $binLength;
if ($binLength != $tableBinLength) {
$table->{allTokensHaveSameBinLength} = 0;
}
}
$table->{maxBinLength} = List::Util::max($table->{maxBinLength}, $binLength);
next if ($token->{text} eq '');
push(@{$table->{tokensByFirstChar}->{substr($token->{text}, 0, 1)}}, $token);
$abcde::minBinPerText = List::Util::min($abcde::minBinPerText, $token->{binLength} / $token->{textLength}); # A* cost heuristic factor
}
$table->{finalized} = 1;
}
sub canReachEndToken {
my ($self, $seen) = @_;
$seen->{$self} = 1; # prevent infinite loop
foreach my $table (@{$self->{child_tables}}) {
if ($seen->{$table}) {
$self->{canReachEndToken} ||= $table->{canReachEndToken};
next;
}
$table->canReachEndToken($seen);
$self->{canReachEndToken} ||= $table->{canReachEndToken};
}
}
sub getAllTextMatches {
my ($self, $stringRef, $pos) = @_;
my @tokenList = ();
my $char = substr($$stringRef, $pos, 1);
foreach my $token (@{$self->{tokensByFirstChar}->{$char}}) {
if (substr($$stringRef, $pos, $token->{textLength}) eq $token->{text}) {
push(@tokenList, $token);
}
}
return \@tokenList;
}
# in order to save memory, all the raw bit/byte entries we use will be references to entries in this table
sub generateRawTable {
my ($class) = @_;
my $self = bless ({
participatesInSwitching => 1,
}, $class);
foreach my $num (0..255) {
my $token = abcde::Table::Token->create($self, {B => 1}, '%'.sprintf('%08b', $num), 1, $abcde::reserved_chars->{open}.'$'.sprintf('%02X', $num).$abcde::reserved_chars->{close});
$self->{tokensByBin}->{$token->{bin}} = $token;
}
foreach my $num (0..1) {
my $token = abcde::Table::Token->create($self, {B => 1}, "%$num", 1, $abcde::reserved_chars->{open}.'%'.$num.$abcde::reserved_chars->{close});
$self->{tokensByBin}->{$token->{bin}} = $token;
}
return $self;
}
sub generateRawBitTable {
my ($class) = @_;
my $self = bless ({
participatesInSwitching => 1,
}, $class);
foreach my $num (0..1) {
my $token = abcde::Table::Token->create($self, {B => 1}, "%$num", 1, $abcde::reserved_chars->{open}.'%'.$num.$abcde::reserved_chars->{close});
$self->{tokensByBin}->{$token->{bin}} = $token;
}
return $self;
}
# Translates a hex string to text based on the current table.
# In order to translate properly, we need to know which bits to translate and which bits to ignore.
# This function handles the most common case, where
# 1) the start address is known (usually via pointer logic)
# 2) the end address is one of
# a) a known number of bits after the start address
# b) a known number of tokens (either in the current table only or including sub-tables as per the table switch token's definition) after the start address
# c) the last bit of the first end token encountered
# 3) all of the bits in between the start and end addresses are intended to be translated
#
# $binRef: reference to raw binary to translate (copying extremely large scalars like $abcde::data around has definite performance impacts!)
# $startPos: bit offset from beginning of $binRef indicating where to begin translation
# $options: hashref containing other translation configurations:
# autoJumps: arrayref of hashrefs, where the hashrefs have keys "start" and "stop" with integer values and the arrayref is sorted by the hashrefs' "start" values; when position "start" has been read, the read position will be changed to "stop"
# endTokenTerminated: if true, translation will stop when an end token has been read
# endAddress: if >= 0, translation will stop when this address is reached
# numTokens: arrayref, if any value > 0, translation will stop when that many tokens have been read
# param: if defined, the parameter that triggered switching to this table
# stringEndReAlignMultiple: if defined, a positive integer that will be used along with stringEndReAlignOffset to update the current read position after matching an end token
# stringEndReAlignOffset: if defined, a positive integer that will be used along with stringEndReAlignMultiple to update the current read position after matching an end token
# $previousTable: if defined, the previous table on the stack
#
# Returns an array of tokens representing $$binRef between the calculated start and end addresses.
sub bin2text {
my ($self, $binRef, $startPos, $options, $previousTable) = @_;
my $curPos = $startPos;
my @tokenList = ();
my $endTokensSeen = 0;
my $numTokens = $options->{numTokens} || [];
my $debug = 0;
print STDERR "bin2text in ".($self->{fileName} || $abcde::reserved_chars->{open}.'internal table'.$abcde::reserved_chars->{close})." (startPos=$startPos, endAddress=".($options->{endAddress}||'').", numTokens=".$abcde::reserved_chars->{open}.join(',', @$numTokens).$abcde::reserved_chars->{close}.", endTokenTerminated=$options->{endTokenTerminated})\n" if ($debug);
TABLE:
while (1) {
my $prevPos = $curPos; # remember where we started from in case we need to revert an incomplete table switch
# figure out what string we're going to try matching against, considering potential autoJumps
my $matchBin = '';
my @autoJumps = ();
if ($options->{autoJumps} && scalar(@{$options->{autoJumps}})) {
my $matchPos = $curPos;
my $matchLength = $self->{maxBinLength};
foreach my $autoJump (@{$options->{autoJumps}}) {
if ($matchPos <= $autoJump->{start} && $autoJump->{start} < $matchPos + $matchLength) {
# in order to correctly match the next token, we're going to have to glue two sections of $$binRef together
my $initLength = $autoJump->{start} - $matchPos;
$matchBin .= substr($$binRef, $matchPos, $initLength);
$matchPos = $autoJump->{stop};
$matchLength -= $initLength;
push(@autoJumps, $autoJump);
}
}
$matchBin .= substr($$binRef, $matchPos, $matchLength);
} else {
my $readLength = $self->{maxBinLength} || 8; # || 8 in case table has no entries!
die("Attempt to read beyond end of ROM!") if ($abcde::options{errorEOF} && $curPos + $readLength > length($$binRef));
$matchBin = substr($$binRef, $curPos, $readLength); # || 8 in case table has no entries!
}
if (defined($options->{endAddress}) && $options->{endAddress} >= 0) {
$matchBin = substr($matchBin, 0, $options->{endAddress} - $curPos);
}
if (!defined($matchBin) || $matchBin eq '') {
# if there's nothing to match, we're all done with this table
last;
}
# try to match a token against that string
my $token;
if (length($matchBin) && defined($options->{param}) && defined($options->{param}->{FFToken}) && $matchBin =~ m/^($options->{param}->{FFToken}->{bin})/) {
$token = $options->{param}->{FFToken};
} elsif (length($matchBin) && $matchBin =~ m/^($self->{binPat})/) {
$token = $self->{tokensByBin}->{$1};
} else {
# couldn't match another token - either we we have reached the end of $$binRef, we have encountered an unparseable token, or we have no tokens in the first place
if ($curPos == length($$binRef)) {
# if we've reached the end of $$binRef, we're all done with this string
last;
# otherwise, it's not one of our tokens, so switch to $abcde::rawTable and continue matching there
} else {
warn ("unable to match another token in the current table at $curPos; are you sure you really understand how the text is encoded?") if ($debug);
$token = $self->{rawTableSwitchToken};
# TBD: unexpected raw token match should still count towards numTokens
}
}
push(@tokenList, $token);
# figure out where we ended up after crossing any autoJumps
if (scalar(@autoJumps)) {
my $matchLength = $token->{binLength};
foreach my $autoJump (@autoJumps) {
if ($curPos <= $autoJump->{start} && $autoJump->{start} < $curPos + $matchLength) {
$matchLength -= $autoJump->{start} - $curPos; # amount of token that was consumed before jumping
$curPos = $autoJump->{stop};
}
}
$curPos += $matchLength;
} else {
$curPos += $token->{binLength};
}
# update the status of some options
for (my $i = 0; $i < scalar(@$numTokens); $i++) {
$numTokens->[$i] -= $token->{numTokens};
}
# if we matched the forced fallback token, we're all done
if (defined($options->{param}) && defined($options->{param}->{FFToken}) && $token eq $options->{param}->{FFToken}) {
last;
}
# handle any table switches
if ($token->{params}) {
foreach my $param (@{$token->{params}}) {
if ($param->{requiredCount} eq '-1') {
# fallback to previous table
push(@tokenList, $abcde::internalTokens{FF});
last TABLE;
} elsif ($curPos == length($$binRef)) {
# we're trying to switch to another table, but the string has already ended, which means the control code token is invalid
warn("binary string ended before all control code parameters could be matched!");
last;
}
# switch tables then increment pos by however many bits the sub-table matched
my %newOptions = %$options; # most options apply to the entire string; update options that differ for the sub-table
$newOptions{numTokens} = ($param->{addNumTokens} ? $numTokens : []),
push(@{$newOptions{numTokens}}, $param->{requiredCount}) if ($param->{requiredCount});
$newOptions{param} = $param;
if (!$param->{FFToken}) {
if ($param->{table} eq $abcde::rawTable) {
$newOptions{endAddress} = $curPos + 8 * ($param->{requiredCount} // 1) + 1;
} elsif ($param->{table} eq $abcde::rawBitTable) {
$newOptions{endAddress} = $curPos + ($param->{requiredCount} // 1) + 1;
}
}
(my $subTokens, $curPos, my $subEndTokens) = $param->{table}->bin2text($binRef, $curPos, \%newOptions, $self);
$endTokensSeen += $subEndTokens;
push(@tokenList, $self->{switchTokens}->{$param->{table}}, @$subTokens);
}
} # else: nothing more to do here
if ($options->{endTokenTerminated} && $token->{flags}->{isEndToken}) {
$endTokensSeen++;
if (defined($options->{stringEndReAlignMultiple}) && $options->{stringEndReAlignMultiple} > 0) {
# re-align read position to the next multiple of $options->{stringEndReAlignMultiple} starting from $options->{stringEndReAlignOffset}
$curPos = $options->{stringEndReAlignMultiple} * POSIX::ceil(($curPos - $options->{stringEndReAlignOffset}) / $options->{stringEndReAlignMultiple}) + ($options->{stringEndReAlignOffset}||0);
}
}
if ($options->{endTokenTerminated} && $endTokensSeen) {
# if we've reached one of the end conditions, kick all the way out, which will fall back to parent table if we're a sub table
last;
}
for (my $i = 0; $i < scalar(@$numTokens); $i++) {
if ($numTokens->[$i] == 0) {
# push appropriate FC token
push(@tokenList, $abcde::internalTokens{FC});
last TABLE;
}
}
}
return (\@tokenList, $curPos, $endTokensSeen);
}
# determining end of $$stringRef is caller's responsibility
# caller is also responsible for newline stripping (newlines must be ignored for insertion purposes)
# TBD: pre-process string to condense substrings with only one possible tokenization into a single node to avoid re-computing unique paths from different starting states (e.g. Pascal string tables)
sub text2bin {
my ($startTable, $stringRef) = @_;
# text2bin implements an A* graph search algorithm with a couple of tweaks to avoid exploring redundant tokenizations;
# at a high level, the algorithm looks like this:
#
# create priority queue
# enqueue empty root node for ^
# loop:
# curNode = dequeue lowest priority node
# discover accessible nodes from curNode
# for each accessible node newNode loop:
# if newNode's tokenization is equivalent (same string position + same pending table switch conditions) to any previously examined node with lower or equal cost, discard newNode
# else if newNode is end of string and satisfies acceptability criteria (is an end token or has no pending table switch conditions)
# return list of tokens by following newNode's parent pointers all the way back to root node
# else
# enqueue newNode with priority = A* cost heuristic
# add newNode to list of previously examined nodes
# return error
# some things to watch out for:
# + infinite table switch loops
# + need to keep track of number of tokens per table for table switch tokens requiring a specific number of tokens in the destination table
# + need to keep track of table switch stack
# + sequences of tokens whose concatenated binary would be interpreted as a different sequence of tokens by the (longest-leftmost) extraction algorithm, e.g. (a)(b) when table contains a token for (ab)
# + branches that rejoin - e.g. assuming binary cost and table stack conditions are the same, tokenization (a)(bc) is equivalent to tokenization (ab)(c) and we don't want to repeat investigating paths from the end of c
$Table::text2bin_stringRef = $stringRef;
$Table::text2bin_stringRef_length = length($$stringRef);
my @nodesByPos = ();
$#nodesByPos = $Table::text2bin_stringRef_length + 1; # pre-extend array to avoid repetitive copying during automatic resizing
foreach my $table (@abcde::allTables) {
delete $table->{tokensByPos}; # make sure there's nothing left over from a previous invocation
}
delete $abcde::rawTable->{tokensByPos}; # make sure there's nothing left over from a previous invocation
delete $abcde::rawBitTable->{tokensByPos}; # make sure there's nothing left over from a previous invocation
my $root = abcde::Table::AStarNode->create(undef, 0, [{table => $startTable, requiredCount => 0, currentCount => 0, FFToken => undef}], undef); # ($parent, $pos, $stack, $token)
my $activeNodes = Hash::PriorityQueue->new();
$activeNodes->insert($root, $root->{minCost});
my $farthestNode = $root;
my @tokenization = ();
SEARCHING:
while (my $node = $activeNodes->pop()) {
my $newNodes = $node->discoverNodes();
foreach my $newNode (@$newNodes) {
#print STDERR __LINE__.": newNode: pos:$newNode->{pos}, stackStr:'$newNode->{stackStr}', nodesByPos:".(defined($nodesByPos[$newNode->{pos}]->{$newNode->{stackStr}})).", unusableSuffixes:".scalar(keys(%{$newNode->{unusableSuffixes}}))."\n";
next if ($nodesByPos[$newNode->{pos}]->{$newNode->{stackStr}} && scalar(keys(%{$newNode->{unusableSuffixes}})) == 0); # once we're sure that the existing tokenization is stable, skip nodes that are just equivalent tokenizations of things we've already seen
if ($farthestNode->{pos} < $newNode->{pos}) {
$farthestNode = $newNode;
#print STDERR "reached pos $newNode->{pos}\n";
}
if ($newNode->{pos} == $Table::text2bin_stringRef_length) {
# if we have reached the end of the string and the current token is an end token or there are no unfulfilled table stack conditions, we're done
my $unfulfilled = 0;
foreach my $stack (@{$newNode->{tableStack}}) {
if ($stack->{requiredCount} && (!$stack->{currentCount} || $stack->{requiredCount} > $stack->{currentCount})) {
$unfulfilled = 1;
}
}
if (!$unfulfilled || $newNode->{token}->{flags}->{isEndToken}) {
while ($newNode->{parent}) {
push(@tokenization, $newNode->{token});
$newNode = $newNode->{parent};
}
last SEARCHING;
}
}
$nodesByPos[$newNode->{pos}]->{$newNode->{stackStr}} = 1; # remember that we've seen a tokenization that reaches this pos with this stack
$activeNodes->insert($newNode, $newNode->{minCost});
}
}
if (!@tokenization) {
#TBD: die makes us not print stats :(
my $farthestPos = $farthestNode->{pos};
while ($farthestNode->{parent}) {
push(@tokenization, $farthestNode->{token});
$farthestNode = $farthestNode->{parent};
}
# print STDERR join("\n", map {"(".($_->{table}->{fileName} // '').",[".join(',', sort keys %{$_->{flags}})."]:$_->{bin}/$_->{text})"} reverse(@tokenization))."\n";
my $context = 20;
my $start = ($farthestPos > $context ? $farthestPos - $context : 0);
my $pre_context_length = $farthestPos - $start;
die("unable to tokenize; best attempt failed at input position $farthestPos at ^ indicator in\n"
.($pre_context_length == 0 ? " " : "")
.substr($$stringRef, $start, $pre_context_length + $context)."\n"
.substr($$stringRef, $start, $pre_context_length)."^\n"
."(does your table file contain a \"".substr($$stringRef, $farthestPos, 1)."\"?)\n");
}
#print STDERR join("\n", map {"($_->{table}->{fileName},"$abcde::reserved_chars->{open}.join(',', sort keys %{$_->{flags}}).$abcde::reserved_chars->{close}":$_->{bin}/$_->{text})"} reverse(@tokenization))."\n";
return [reverse(@tokenization)];
}
1;

View File

@ -0,0 +1,119 @@
package abcde::Table::Token;
use strict;
use warnings;
sub create {
my ($class, $table, $flags, $bin, $numTokens, $text, $line, $lineNum) = @_;
if (defined($text)) {
if ($abcde::options{mode} eq 'bin2text') {
$text =~ s/\\n/\n/g;
} elsif ($abcde::options{mode} eq 'text2bin') {
$text =~ s/\\n//g;
}
}
$bin = (substr($bin, 0, 1) eq '%' ? substr($bin, 1) : unpack('B'.(length($bin)*4), pack('H'.length($bin), $bin))); # if $bin has a leading %, it's already in binary form, otherwise it's in hexadecimal form
my $self = {
bin => $bin,
binLength => length($bin),
flags => $flags,
line => $line || $text,
lineNum => $lineNum || 'N/A',
numTokens => $numTokens,
table => $table,
text => $text,
textLength => length($text),
};
return bless ($self, $class);
}
sub parseLine {
my ($class, $table, $line, $lineNum) = @_;
pos($line) = 0;
my ($flags, $prefixes, $bin, $numTokens, $text) = ({}, '', '', 1, '');
if ($line =~ m/\G([!\/]+)/gc) { # check for ! and /
$prefixes = $1;
}
if ($prefixes =~ s/\///) {
$flags->{isEndToken} = 1;
}
if ($prefixes =~ s/!//) {
$flags->{hasSwitchToken} = 1; # the documentation refers to these as switch tokens, but the switching doesn't actually happen until the start of each parameter, so we'll name the variable to reflect that
}
if ($prefixes ne '') { # extra unrecognized prefixes
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (unrecognized prefix '$prefixes')\n");
}
if ($line =~ m/\G(%[01]+|[0-9A-Fa-f]+)/gc) { # binary or hex string
$bin = $1;
} else {
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (not a binary or hexadecimal string)\n");
}
if ($line =~ m/\G$abcde::reserved_chars->{open}(-?(?:0|[1-9][0-9]*))$abcde::reserved_chars->{close}/gc) { # <num> match count modifier
$numTokens = $1;
}
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (no '=' separator)\n") unless ($line =~ m/\G=/gc); # = separating lhs from rhs
my $token;
if ($flags->{hasSwitchToken}) {
my $label = '';
if ($line =~ m/\G$abcde::reserved_chars->{open}([^\$$abcde::reserved_chars->{open}$abcde::reserved_chars->{close}][^$abcde::reserved_chars->{open}$abcde::reserved_chars->{close}]*)$abcde::reserved_chars->{close}/gc) { # <label>
$label = $1;
}
$token = $class->create($table, $flags, $bin, $numTokens, $label, $line, $lineNum);
my @params = ();
while ($line =~ m/\G,/gc) {
my ($switch_table_id, $switch_count, $switchNumTokens, $addNumTokens, $switch_fallback);
my $raw_type = 'hex';
$switchNumTokens = 1; # default value
if ($line =~ m/\G$abcde::reserved_chars->{open}\@([^$abcde::reserved_chars->{open}$abcde::reserved_chars->{close}]+)$abcde::reserved_chars->{close}:/gc) { # <@tableID>:
$switch_table_id = $1;
$abcde::tablesByID{$switch_table_id} ||= undef; # keep track of which tables we're expecting to be loaded
} elsif ($line =~ m/\G$abcde::reserved_chars->{open}binary$abcde::reserved_chars->{close}:/gc) { # <binary>:
$raw_type = 'bin';
}
# count: if matching against rawTable, %1+ => match exactly 1+ bits
# otherwise... -1 => immediate fallback, 0 => match as much as possible, 1+ => match exactly 1+ times, $lhs => fallback when matching $lhs
if ($line =~ m/\G(-1)/gc) {
$switch_count = $1;
if ($switch_table_id) {
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (can't have -1 fallback with table ID!)\n");
}
} elsif ($line =~ m/\G(0|[1-9][0-9]*)/gc) {
$switch_count = $1;
} elsif ($line =~ m/\G(%[01]+|\$(?:[0-9A-Fa-f]{2})+)/gc) {
$switch_count = 0;
($switch_fallback = $1) =~ s/^\$//;
if ($line =~ m/\G$abcde::reserved_chars->{open}(-?(?:0|[1-9][0-9]*))$abcde::reserved_chars->{close}/gc) { # <fallback token match count modifier>
$switchNumTokens = $1;
}
# TBD: this should be a list of tokens
$switch_fallback = $class->create($table, {FF => 1}, $switch_fallback, $switchNumTokens, '', $line, $lineNum); # since this type of fallback requires a binary representation, store a reference to it for later use
} else {
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (invalid match type)\n");
}
if ($line =~ m/\G\+/gc) {
$addNumTokens = 1;
}
push(@params, {paramTable => $table, table_id => $switch_table_id, requiredCount => $switch_count, addNumTokens => $addNumTokens, FFToken => $switch_fallback, rawType => $raw_type});
last if ($switch_count eq '-1'); # immediate fallback => can't have any further switches on this token
# TBD: for dictionary entires that trigger table switches, we'll need to allow -1 in more than just the end of a switch list
}
if (!scalar(@params)) {
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (switch token with no switch parameters)\n");
}
$token->{params} = \@params;
} elsif ($line =~ m/\G([^$abcde::reserved_chars->{open}$abcde::reserved_chars->{close}]*)$/gc) {
$text = $1;
$token = $class->create($table, $flags, $bin, $numTokens, $text, $line, $lineNum);
}
if ($token && pos($line) == length($line)) {
return $token;
} else {
die("unrecognized table entry '$line' at '$table->{fileName}' line $lineNum (invalid token format)\n");
}
}
1;

View File

@ -0,0 +1,128 @@
package Hash::PriorityQueue;
our $VERSION = '0.01';
use strict;
use warnings;
use List::Util qw(min);
sub new {
return bless {
queue => {}, # payloads by prio
prios => {}, # prios by payload
min_key => undef,
}, shift();
}
sub delete {
my ($self, $payload) = @_;
my $op = $self->{prios}->{$payload};
if (defined($op)) {
$self->{queue}->{$op} = [ grep { $_ ne $payload } @{$self->{queue}->{$op}} ];
if (!@{$self->{queue}->{$op}}) {
delete($self->{queue}->{$op});
if ($self->{min_key} == $op) {
$self->{min_key} = min keys(%{$self->{queue}});
}
}
}
}
sub pop {
my ($self) = @_;
if (!defined($self->{min_key})) {
return undef;
}
my $elem = shift(@{$self->{queue}->{$self->{min_key}}});
if (!@{$self->{queue}->{$self->{min_key}}}) {
delete($self->{queue}->{$self->{min_key}});
$self->{min_key} = min keys(%{$self->{queue}});
}
delete($self->{prios}->{$elem});
return $elem;
}
sub update {
my ($self, $payload, $priority) = @_;
my $op = $self->{prios}->{$payload};
if (defined($op)) {
$self->{queue}->{$op} = [ grep { $_ ne $payload } @{$self->{queue}->{$op}} ];
if (!@{$self->{queue}->{$op}}) {
delete($self->{queue}->{$op});
}
}
$self->{prios}->{$payload} = $priority;
push(@{$self->{queue}->{$priority}}, $payload);
if (!defined($self->{min_key}) or $priority < $self->{min_key}) {
$self->{min_key} = $priority;
} elsif ($priority > $self->{min_key} and (defined($op) and !defined($self->{queue}->{$op}))) {
$self->{min_key} = min keys(%{$self->{queue}});
}
}
*insert = \&update;
1;
__END__
=head1 NAME
Hash::PriorityQueue - priority queue based on perl hashes
=head1 SYNOPSIS
my $prio = Hash::PriorityQueue->new();
$prio->insert("foo", 2);
$prio->insert("bar", 1);
$prio->insert("baz", 3);
my $next = $prio->pop(); # "bar"
# I decided that "foo" isn't as important anymore
$prio->update("foo", 99);
=head1 DESCRIPTION
This module implements a high-performance priority queue, based on a hash.
It's written in pure Perl.
Available functions are:
=head2 B<new>()
Obvious.
=head2 B<insert>(I<$payload>, I<$priority>)
=head2 B<update>(I<$payload>, I<$new_priority>)
Adds the specified payload (anything fitting into a scalar) to the priority
queue, using the specified priority. Smaller means more important.
If the item already exists in the queue, it is assigned the new priority.
These names are actually the same function. The alternative name is provided
so you can make clear which operation you intended to be executed.
=head2 B<pop>()
Removes the most important item (numerically lowest priority) from the queue
and returns it. If no element is there, returns I<undef>.
=head2 B<delete>(I<$payload>)
Deletes an item known by the specified payload from the queue.
=head1 BUGS
Maybe.
=head1 AUTHORS & COPYRIGHTS
Made 2010 by Lars Stoltenow.
Hash::PriorityQueue is free software; you may redistribute it and/or modify
it under the same terms as Perl itself.
=cut

Binary file not shown.

View File

@ -0,0 +1,108 @@
#GAME NAME: Final Fantasy 1 (NES)
#BLOCK NAME: Dialogue Block (DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $28010
#POINTER TABLE STOP: $28210
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $20010 //add $20010 to each pointer to get the string address
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Intro Screen (DTE)
#TYPE: NORMAL
#METHOD: RAW
#SCRIPT START: $37F30
#SCRIPT STOP: $3800F
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Menu (DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $38510
#POINTER TABLE STOP: $3858F
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $30010
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Stores (DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $38010
#POINTER TABLE STOP: $3805B
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $30010
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Bridge/End Block (DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $36810
#POINTER TABLE STOP: $36841
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $2C010
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Battle Text (No DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $2CF60
#POINTER TABLE STOP: $2CFFB
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $24010
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Enemy Names (No DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $2D4F0
#POINTER TABLE STOP: $2D5EF
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $24010
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Enemy Attacks (No DTE)
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $2B610
#POINTER TABLE STOP: $2B643
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $20010
#TABLE: ff1e.tbl
#COMMENTS: No
#END BLOCK

View File

@ -0,0 +1,290 @@
Cartographer
Version PR3
Intial Release: Dec. 29, 2008
by RedComet
redcomet@rpgclassics.com
--------------------------------------
1. Introduction
2. Usage
3. Commands
4. Table Files
5. Credits
--------------------------------------
1. Introduction
--------------------------------------
Cartographer is a program designed to extract the script(s) from a file. Cartographer
is intended to replace the now out-of-date romjuice by prez.
--------------------------------------
2. Usage
--------------------------------------
Cartographer rom.ext commands.txt script.txt -s/-m
rom.ext specifies the file to dump text from.
commands.txt contains the dumping commands (see below) that specify how to
dump the text.
script.txt is the base filename for the output file(s) to which the text is dumped.
-s/-m is a switch that specifies whether to dump all blocks contained in commands.txt
to a single file (-s) or to dump each block to a separate file (-m).
--------------------------------------
3. Commands
--------------------------------------
#GAME NAME: This specifies the name of the game text is being dumped from.
The name specifed here will be included in the output file. This
name is only output once, at the beginning of each file.
#BLOCK NAME: Each block of text is assigned the name specified. This name
is output at the beginning of each block. Multiple blocks can
share the same name, so it is the user's responsibility to keep
track of which block is which.
When multiple files are specified at the command line, each block
is dumped to a separate text file. The filename is created by combining
the output base filename given at the parameter in the command line
with a block number used by Cartographer internally to identify each
block.
#TYPE: This allows the user to specify one of three possible dump types:
NORMAL, FIXED_STRING, FIXED_STRING && FIXED_LINE
NORMAL:
The table entries control the output of text, i.e. line
breaks are only output where the table entries specify.
FIXED_STRING:
This is for games that use a fixed string length. Line
breaks will be output when the specified number of bytes
have been read.
See #STRING LENGTH below.
FIXED_STRING && FIXED_LINE:
This is for games that use a fixed string and a fixed line
length. Line breaks will be output when the specified
number of bytes have been read.
See #STRING LENGTH and #LINE LENGTH below.
#STRING LENGTH: This specifies the length of the string in bytes. Once the number
of bytes specified have been dumped, a line break will be output
to the script file.
Only include this command if TYPE has been specified to either
FIXED_STRING or FIXED_STRING && FIXED_LINE.
#STRING END: This allows the user to specify whether or not they want to
include an artificial control code at the end of the string.
Yes or No option only.
#END CTRL: If Yes was specified for #STRING END, this command allows the
user to specify the artificial control code they want output.
If Yes was specified for #STRING END and this command is omitted,
the default artificial control code (END) is used.
#LINE LENGTH: Similar to #STRING LENGTH, this command specifies the length of
a line in bytes. Once the number of bytes specifed have been
dumped, a line break will be output to the script file.
#LINE END: This allows the user to specify whether or not they want to
include an artificial control code at the end of the line.
Yes or No option only.
#LINE CTRL: If Yes was specified for #LINE END, this command allows the
user to specify the artificial control code they want output.
If Yes was specified for #LINE END and this command is omitted,
the default artificial control code (LINE) is used.
#METHOD: This allows the user to specify one of three ways to dump the text:
POINTER, POINTER_RELATIVE RAW
POINTER:
Dump the text based on a pointer table.
POINTER_RELATIVE:
Same as a pointer, but this allows the user to dump text
using a pointer table that contains relative pointers
instead of absolute pointers.
POINTER_RELATIVE_PC:
Similar to POINTER_RELATIVE, except the base value is a relative
value instead of an absolute value.
See #RELATIVE PC below.
RAW:
A simple start to stop dump, like romjuice.
#POINTER ENDIAN:
Specifies the endianess of the pointers: BIG or LITTLE
#POINTER TABLE START:
Specifies the beginning address of the pointer table.
#POINTER TABLE STOP:
Specifies the ending address of the pointer table.
#POINTER SIZE:
Specifies the size of the pointer in bytes, e.g. 16-bit pointers
have a size of 2, 32-bit pointers have a size of 4, etc.
#POINTER SPACE: Specifies the number of bytes between each pointer to ignore.
This is useful for games that embed additional information between
each pointer. If pointers occur one after the other, specify
a pointer space size of 0.
#ATLAS PTRS: SE allows the user the option of dumping an Atlas compatible
script. This option specifies whether to include Atlas style
pointer writes, e.g. #W16($12345), in the dump or not.
Yes or No option only.
#BASE POINTER: This specifies the value to add to every pointer when using
the POINTER_RELATIVE dump method.
#RELATIVE PC: This specifies whether to use the address of the pointer as the
base pointer address. I've only seen one game that does this so far,
but there are probably more.
Yes or No option only.
#SCRIPT START: Specifies the beginning address for a RAW dump.
#SCRIPT STOP: Specifies the ending address for a RAW dump.
#TABLE: The filename of the table to use for dumping the script. Each
block specifies its own table file, thus allowing the user to
dump multiple blocks that use different tables at once, e.g.
dialogue and menu options.
#COMMENTS: Used to determine if the user wants each line of text in the
script to begin with Atlas style (//) comments or not.
Yes or No option only.
#END BLOCK: This specifies the end of the current block commands. This option
must be included, otherwise the commands for the next block will
overwrite the commands for the current block.
------------
Dependencies:
------------
Not every one of the above commands need be included. In fact, it's impossible to
include every command. Following is a list of commands that are dependent upon
certain options being specified in the previous command(s):
----- -----------------
#TYPE Required Commands
----- -----------------
FIXED_STRING #STRING LENGTH, #STRING END
FIXED_STRING && FIXED_LINE #STRING LENGTH, #STRING END, #LINE LENGTH, #LINE END
------- -----------------
#METHOD Required Commands
------- -----------------
POINTER #POINTER TABLE START, #POINTER TABLE STOP,
#POINTER SIZE, #POINTER SPACE, #POINTER ENDIAN,
#ATLAS PTRS
Note: These commands may be specified in any
order, but they are all required.
POINTER_RELATIVE #BASE POINTER plus the commands required for
POINTER method above.
POINTER_RELATIVE_PC #RELATIVE BASE plus the commands required for
POINTER method above.
RAW #SCRIPT START, #SCRIPT STOP
--------
Required:
--------
There are also certain commands which must be included:
GAME NAME, BLOCK NAME, TYPE, METHOD, TABLE, COMMENTS.
Additionally every block must end with an END BLOCK command.
--------
Features:
--------
You can also embed comments in your command files. Everything on the line after a //
is simply ignored. This is really handy if you like to make notes to yourself.
--------------------------------------
4. Table File
--------------------------------------
Although I built Cartographer using Klarth's TableLib v1.0, there are a few changes I had
to make to accomplish my goals. Here's a list of features from Klarth's readme with the
removed or disabled features omitted:
TableLib supports a slightly different feature set than most other programs.
- Supports virtually unlimited hex and string lengths, as long as the hex strings are whole bytes.
0123456789=super long string here <-- This is fine, the hex string represents 5 whole bytes.
01234=another long string <-- Error, the hex string is 2.5 bytes.
- The same table can be used for both insertion and dumping, TableReader accomodates for the changes.
- Ability to add newline characters for dumping. They will be ignored during insertion.
/FF=<END>\n\n <-- This is how you should define your endtokens so the library functions properly
- The so-called "linked entry" from Thingy terminology, but implemented a bit better. This allows you to
dump control codes that have parameter bytes cleanly, without it being dumped as text.
$0500=<Color>,1 <-- Dumps 0500 as <Color> and dumping one parameter byte afterwards as a <$XX> hex entry.
End tokens specifed by beginning the entry with a '/' (like /FF=<END>\n\n above) are used
by the Pointer dumping methods to determine when the end of the string has been reached.
I also disabled the newline removal on end tokens.
Please note that specifying an end token when using the RAW dumping mode will result in
portions of the script being omitted. There's no simple fix short of rewriting Klarth's
code that I can come up with at this time. So, if your dump looks choppy and you're
using the RAW mode, make sure you don't have any end tokens defined in your table.
Control codes that can be added to table entries:
\r Line break with commenting (//) on the next line.
\n Line break with no commenting on the next line.
These can be used as many times as you want per entry to format your text.
If a table entry begins with a non-numeric character other than '/' or '$', the entry is
flagged as an error. However, a blank entry (e.g. FFFF=) is valid and is simply ignored.
--------------------------------------
5. Credits
--------------------------------------
Klarth - For creating Atlas and TableLib.
Kaioshin - For helping test Cartographer.
Gideon Zhi - For suggesting the name Cartographer.
prez - For creating romjuice.
And everyone else who has given me feedback on this program. If you find a bug, please
let me know!

View File

@ -0,0 +1,505 @@
abcde
Version 0.0.10
(a.k.a. proof-of-concept work-in-progress might-melt-your-cpu insert-disclaimer-here edition)
2024-05-25
Table of Contents:
1) About abcde
1.1) History
2) Dependencies
3) Synopsis of Table File Syntax
4) Cartographer Support
5) Atlas Support
6) Customization Support
6.1) Extracting Embedded Pointer Text
7) Improved Text-To-Binary Algorithm
8) A Note About UTF-8
9) Future Plans
10) Thanks
11) Contact
12) Version History
1) About abcde
Usage:
perl abcde.pl <general options> [-cm <command module name> [<command module options>]]
Run `perl abcde.pl` to get more detailed usage information.
In its most basic form, abcde is a table-file-based binary-to-text and text-to-binary translation utility intended to make ROM hacking just a little bit easier. Give it one or more table files, tell it whether you want to translate from binary to text or vice-versa, and then watch in amazement as the data you feed it on STDIN appears on STDOUT translated according to the provided table file(s). Or don't, because a utility like that is probably pretty useless all on its own unless you happen to be one of those few people who enjoy doing things like writing your own hex editor or assembler in your spare time, in which case this form of usage might be exactly what you want. For everybody else, however, abcde also supports (with a couple of caveats) both Cartographer and Atlas command files, giving it a high degree of backwards compatibility with what are arguably the most popular general-purpose script extraction and insertion utilities at the time of writing.
Given that utilities like Cartographer and Atlas already exist, the obvious question is: why make another one? The short answer is that abcde consolidates features from a variety of separate mutually-incompatible existing utilities into a single utility, improves on those features, and adds several completely new features, thus significantly expanding the range of script encodings which can be extracted and inserted without the end user having to create a game-specific text editor. To put it another way, abcde lowers the barrier to entry for games with technically complex script encodings (abcde isn't a magic bullet, though - in order to use it effectively on a complex script encoding, you still have to understand how that script encoding really works, which potentially involves quite a lot of work all on its own).
For a specific example of a game that uses some of the new features abcde adds, consider the Japanese NES game Dragon Quest IV: Michibikareshi Monotachi. Dragon Quest IV is a game whose text is largely inaccessible to most general-purpose script extraction or insertion utilities due to several reasons:
- The text is encoded as a sequence of binary strings (tokens) with lengths that are a multiple of 6 bits instead of the usual 8 bits (a.k.a. 1 byte).
- The text encoding is capable of changing mid-string; e.g. within a single string, the meaning of a 0b000001 token can vary between "い", "イ", "「", "です。", and "では " depending on which table switch tokens have preceded it, possibly many tokens earlier.
- Dictionary (multi-character) entries contain characters which cannot be represented individually; e.g. "" only appears in combination with other characters - there is no token that prints only "" and nothing else.
- Instead of the usual 1 string per pointer, pointers point to the beginning of a group of strings, and only the first string in a group is necessarily byte-aligned; strings after the first string typically begin on the bit immediately following the final bit of the previous string, but ...
- Dragon Quest IV reserves sections of RAM (or ROM that gets mapped to RAM, depending on your perspective) for other purposes; strings are tightly packed around these reserved sections of RAM and can even cross bank boundaries, e.g. after reading the final bit of RAM $BFD7, which may well occur partway through a token, Dragon Quest IV swaps a different ROM bank into RAM and jumps to a new RAM address to continue reading the rest of the string (in one case, this means that the first few bits of a 6-bit token can be located inside ROM 0x13FD7 but the remaining bits of the token are located inside ROM 0x6E500 - good luck finding that with a relative search :p).
How does abcde make text stored like that accessible? To start with, the table file format has been expanded to allow it to represent bit strings, not just byte strings. The table switching functionality present in some general-purpose extraction utilities (e.g. romjuice) has also been generalized to handle an arbitrary number of table files and control over how switching back and forth between different table files occurs has been improved. At the same time, abcde uses a much more powerful algorithm for deciding how to translate text back into the binary data required by a game, which allows it to insert virtually any text that it can extract. Furthermore, through its Cartographer- and Atlas-style command file interfaces, abcde adds some new extraction and insertion commands to those already established by Cartographer and Atlas.
1.1) History:
Sometime back in 2010, Nightcrawler (a man who needs no introduction), lamenting the interoperability issues caused by every utility author under the sun putting their own unique spin on how table files needed to be structured and what they could and could not be used to accomplish, decided to spearhead a campaign to standardize the table file format. It sounded like a pretty great idea at the time and things appeared to be moving along quite well until some argumentative little git (a.k.a. yours truly) showed up and started poking holes in and proposing amendments to the table file standard's assumptions conditions, and provisions, sufficiently disrupting whatever momentum the campaign had to the point that, eight years later, the end result of the table file standard's noble effort appears to have been a lot of talk but no action. Sorry about that :(
abcde had its genesis in those discussions, starting off as a full implementation of the then current version of the table file standard, and is partly an effort to make good on some of the claims made all those years ago. As is all too common with ROM hacking projects (or maybe it's just me?), abcde's development lifecycle has been intermittent at best, with gaps of weeks, months or even years between those rare and precious hours of spare time with enough desire and mental energy to make some attempt at productivity worthwhile; the rate of progress was probably not helped by having a few other projects to work on at the same time. As I've gone further down the rabbit hole of pitting abcde against the nastiest character encodings I could find that were used in commercially-released games, I've frequently had to go back to the drawing board and revise the assumptions abcde had inherited from the table file standard (some of which I was responsible for adding in the first place!); this is an ongoing process, so none of the features or details of those features that I'll be describing below should be regarded as completely stable at this point in time. As such, abcde is regrettably unable to claim conformance to that table file standard and instead does pretty much the exact opposite, putting its own unique spin on how table files need to be structured and what they can and cannot be used to accomplish. Due to the repeated fundamental design changes abcde has undergone, it's a bit messier than I would like and still has some rough edges that need smoothing out. Despite that, abcde is far enough along that I figure it might be useful to other people, so even though it isn't really finished yet, I'm making it publicly available now rather than continuing to keep it all to myself until it's "done".
2) Dependencies
abcde is written in Perl 5, so you'll need to have perl installed on your system in order to run it; instructions for doing so are beyond the scope of this readme (see e.g. https://www.perl.org/ for details), but if you're using pretty much any non-antique Unix-like system (Macs are Unix-like these days), you probably already have it installed. Windows testing was done using Strawberry Perl.
All other code utilized by abcde (which currently consists of only the Hash::PriorityQueue perl module) is included with abcde for convenience.
3) Synopsis of Table File Syntax
# Table file #1:
# Table files are encoded as UTF-8 text files, and the Byte Order Mark is optional. The text portion of table entries can contain pretty much any Unicode character(s), with a few exceptions:
# a) newlines (as defined by your operating system) are reserved since they're used to delimit table entries from each other;
# b) "<" and ">" are reserved for various purposes including representing untranslated data and delimiting sections of table entries that need to contain multiple pieces of encoding information.
# Any line beginning with "#" (like this one!) is ignored, so you can include your own comments inside the table file, e.g.:
# This is a table file for game X used during situation Y and here's a list of things you should know about when using it: ...
# You can give your table an ID (an ID isn't required if you only have 1 table, but when storing multiple logical tables in the same table file, each logical table must be preceded by an ID):
@main
# Note that providing abcde with multiple tables that have the same ID is considered an error, so don't do that.
# Normal text entries are just what you'd expect, with the left-hand side of the entry specifying a byte in hexadecimal format and the right-hand side specifying what text that byte represents:
04=4
10=あ
# Just in case you weren't expecting it, note that the left-hand side of a table entry is interpreted as big endian (most significant bit/byte to least significant bit/byte), so e.g. AB < BA.
# Table entries can have any positive number of bytes and any non-negative number of characters; bytes can be written in uppercase, lowercase, or any mixture of the two:
01=foo
02=bar
03=cat
80=
ABCD=mouse
0123456789AbCdeF=*
# Since newlines are reserved for delimiting table entries from each other, you can't split a single table entry across multiple lines, but you can use "\n" anywhere in the right-hand side of a table entry to represent a newline:
FE=new\nline
# 0xFE would then be translated to text as:
# new
# line
# Newlines are ignored when translating text into binary, so in the absence of any other usable table entries, each of these texts would be translated to 0xFE:
# ┌──────────┬──────────┬──────────┬──────────┐
# │a) │b) │c) │d) │
# │new │new │newline │ne │
# │line │ │ │wli │
# │ │ │ │ne │
# │ │line │ │ │
# └──────────┴──────────┴──────────┴──────────┘
# etc., etc.
# String end entries are also just what you'd expect, formed by prefixing the left-hand side of a table entry with "/":
/FF=[END]
# Table file entries are no longer limited to matching bytes; now you can match bits by setting the left-hand side of a table entry to a binary string prefixed with "%"
# (mnemonic: binary is made up of 0s and 1s, % sort of looks like 010):
%10=two
%00010=also two
%001001=nine
001001=four thousand ninety seven (note the absence of the % prefix)
%0110=Got Huffman strings? No problem!
/%1111111=[end]
# If you happen to want to match a multiple of 4 bits, you can now also use table entries with an odd number of hex digits:
F=fifteen (matches 4 bits)
abcde=awesome? (matches 20 bits)
# Note that as with hex entries, leading 0s are meaningful for binary entries: in the above example, %10 matches two bits of input while %00010 matches five bits of input.
# Table switch entries are new (and/or improved, depending on what you're already familiar with); this can get a bit complicated, so let's add some more table files and see some examples.
# The table switch entry format looks like this: !lhs=<label>,<@table ID>:matchType,<@table ID>:matchType,<@table ID>:matchType,...
# Where <label> is an optional label for the control code that, if provided, appears in text output,
# <@table ID>: is an optional table ID that will be used to continue matching; if you don't provide a table ID, one of two things will happen:
# * if you don't provide any value here, you get raw hexadecimal-encoded output;
# * if you provide the special value <binary> (note the lack of "@"), you get raw binary-encoded output;
# matchType says how many matches to make in the new table before falling back to the current table:
# * 0 => keep going as long as you can;
# * X => make exactly X matches in the new table, where X is any positive decimal integer;
# * -1 => fall back right away;
# * $hex or %bin => keep going until you match $hex or %bin again (the "$" or "%" are required here so that we can tell whether 10 means ten, sixteen, or two).
# Once the matching condition for the table that was switched into has been satisfied, translation will continue with the table that did the switching.
#
# For table entries that require exactly X matches, the default match counting behaviour is for each matched table entry in the new table to count as 1 match.
# However, different games can count matches in different ways, so abcde provides support for modifying the counting behaviour in a couple of ways:
# * you can change the number of matches a table entry counts as by suffixing the left-hand side of that entry with "<Y>" for any non-negative decimal integer Y; the table entry will then be counted as Y matches instead of 1 match;
# * you can do the same thing with matchType when using $hex or %bin;
# * matchType can be followed by a "+" to indicate that matches in the new table should also contribute towards the number of required matches in the current table.
# Table file #2:
@ItemNames
01=[Potion]
02=[HolyHandGrenadeOfAntioch]
03=[Sword]
# Table file #3:
@FontNames
!01=,<@ItemNames>:1+
02=[Green]
03<2>=[Batman]
# Matching 0xAB 0x01 0x02 0xAB 0x03 using the above tables, starting from @main
# ┌────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────────┐
# │If @main contains: │Then the output is: │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │a normal table entry │ │
# │AB=[line] │[line]foobar[line]cat │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │table switch with a label,switching to the default raw hex table│ │
# │!AB=<[Item Name:]>,1 │[Item Name:]<$01>bar[Item Name:]<$03> │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │table switch with a label, switching to a table with an ID │ │
# │!AB=<[Item Name:]>,<@ItemNames>:1 │[Item Name:][Potion]bar[Item Name:][Sword] │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │in this example, we run out of input before being able to match │ │
# │all of the second 0xAB │ │
# │!AB=<[Item Name/Font:]>,<@ItemNames>:1,<@FontNames>:1 │[Item Name/Font:][Potion][Green]<$AB>cat │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │the below two examples are equivalent since 1 + 1 + 1 = 3 │ │
# │!AB=<[Window, X/Y]>,3 │[Window, X/Y]<$01><$02><$AB>cat │
# │!AB=<[Window, X/Y]>,1,1,1 │[Window, X/Y]<$01><$02><$AB>cat │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │matching 0xAB in @ItemNames causes us to fall back to matching │ │
# │in the previous table (@main) │ │
# │!AB=<[page]>,<@ItemNames>:$AB │[page][Potion][HolyHandGrenadeOfAntioch]cat │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │%10101011 and $AB mean the exact same thing │ │
# │!AB=<[page]>,<@ItemNames>:%10101011 │[page][Potion][HolyHandGrenadeOfAntioch]cat │
# ├────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
# │due to the "+" in @Fontnames 0x01, the 0x02 in @ItemNames counts│ │
# │towards the required 2 matches in @FontNames; due to the "<2>" │ │
# │in @FontNames 0x03, the 0x03 counts as 2 matches all on its own │ │
# │!AB=,<@FontNames>:2 │[HolyHandGrenadeOfAntioch][Batman] │
# ├────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┤
# │note that table IDs are case sensitive, so this results in an error about there not being any table with an ID of "iTeMnAmEs" │
# │!AB=,<@iTeMnAmEs>:0 │ │
# ├────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┤
# │note that string end tokens only stop binary-to-text translation for pointer-based extractions, │
# │which is not abcde's default behaviour │ │
# │/AB=[end] │[end]foobar[line]cat │
# ├────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┤
# │with a suitable extraction command file, however, string end tokens work just fine │
# │/AB=[end] │[end] │
# ├────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┤
# │it's perfectly possible for a game to use a single control code to trigger multiple things at once, such as switching to another │
# │table and then ending the string on fallback (this example assumes a suitable extraction command file) │
# │/!%10101011=<[Item Name:]>,<@ItemNames>:1 │[Item Name:][Potion] │
# └────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────┘
# abcde includes support for storing multiple logical tables within the same table file, which is useful if you find yourself with many small translation tables cluttering up your filesystem. To store multiple tables inside the same file, simply ensure that every table is preceded by a table ID, like so:
# Multi-table file:
@table1
A=01
B=02
@table2
A=81
B=82
@table3
A=FACD
B=0000
# etc., etc.
# Some other points that might not be obvious:
# Using the same left-hand side multiple times in the same table file is considered an error, regardless of whether you specify the left-hand side in binary or hexadecimal and regardless of any modifiers like "/" or "!", e.g. if @main also contained:
# %10000000= (conflicts with 80= above since %10000000 and 80 are both the same thing even though they're written differently)
# abcd=mouse (conflicts with ABCD=mouse above since hexadecimal digits are not case sensitive)
# ABCD=mouse (conflicts with ABCD=mouse above even though both lines are completely identical; clean up your table files before giving them to abcde!)
# /0123456789AbCdeF=* (conflicts with 0123456789AbCdeF=* above even though one is a normal entry and one is a string end entry)
# Using the same left-hand side in different table files is okay, though, so any of these entries that generate conflicts in @main could appear in a different table file like @ItemNames.
# Table file #4:
03=[Sword]
02=[HolyHandGrenadeOfAntioch]
01=[Potion]
@ItemNames2
# The order of lines in a table file holds no special meaning for abcde; @ItemNames and @ItemNames2 do exactly the same thing.
# The textual representation of bytes that do not get translated according to any table file looks like <$42>, and untranslated bits look like <%0>.
4) Cartographer Support
Any valid Cartographer command file should also be valid for abcde, but abcde's command file parsing rules, order of execution, and final output are subtly different from Cartographer's; see `abcde -h -cm abcde::Cartographer` for more details.
In addition to fixing several bugs and making a few design decisions differently, abcde adds or extends the following commands:
#ATLAS PTRS: Further to Cartographer's usual behaviour, setting #ATLAS PTRS to "Yes" will cause abcde to generate Atlas commands for activating the table specified by the current block's #TABLE command, jumping the insertion point to the start of the extraction point, and updating the RAM-to-ROM offset value based on the current block's #BASE POINTER value, if any. Additionally, if #ATLAS PTRS is set to "Yes" in any block, abcde will output Atlas commands for loading all of the same table files that were loaded during extraction. Note that the #ATLAS PTRS command can be included even in blocks using #METHOD: RAW.
#AUTO JUMP START: Specifies the starting address of an auto-jump.
#AUTO JUMP STOP: Specifies the destination address of an auto-jump.
When dumping a block that contains an auto-jump, attempting to read the byte address specified by #AUTO JUMP START will instead read the address specified by #AUTO JUMP STOP and dumping will continue from that point. If either of these commands is present in a block, both commands must be present or else an error will be generated.
(This command is how you can deal with extracting discontinuous strings like in the Dragon Quest IV example where the data being read from ROM $13FD7 is continued at ROM $6E500.)
#COMMENTS: Both
An alternate output format where the script is output twice, once commented and once uncommented, like so, reflecting my personal preference:
//string 1 line 1[line]
//string 1 line 2[end]
string 1 line 1[line]
string 1 line 2[end]
//string 2 line 1[line]
//string 2 line 2[end]
string 2 line 1[line]
string 2 line 2[end]
#SCRIPT STOP: This option is now available for all #METHOD values, not just RAW. If set for one of the POINTER methods, string translation will stop when reaching the #SCRIPT STOP value or when an end token is encountered, whichever comes first.
(This is useful when you want to extract one or more strings together with their pointers but the ending conditions for each string are difficult or impossible to represent via other means; see Dragon Warrior II's menus for an example.)
#SHOW END ADDRESS: If set to Yes, abcde will include the current ROM address in a comment following each string.
Yes or No option only; defaults to Yes.
#SORT OUTPUT BY STRING ADDRESS: Normally Cartographer outputs strings sorted by their pointers' addresses; setting this option to Yes sorts the strings by the strings' addresses instead
Yes or No option only; defaults to No.
#STRING END REALIGN MULTIPLE:
#STRING END REALIGN OFFSET: Defaults to 0.
If #STRING END REALIGN MULTIPLE is set to a positive integer value, then whenever a string end token is encountered, the read address will be advanced to the nearest multiple of #STRING END REALIGN MULTIPLE bytes, skipping over any intervening data, so this is kind of like a mini-auto-jump. E.g. if the block contains #STRING END REALIGN MULTIPLE: 14, #STRING END REALIGN OFFSET 10, and a string end token is matched that terminates at address 0x12345, instead of the next read address being 0x12346 as usual, we instead jump ahead to 0x1234E (0x12346 - 10 = 0x12336; ceiling(0x12336 / 14) * 14 = 0x12344; 0x12344 + 10 = 0x1234E). If the string end token had instead terminated at 0x1234D, the next read address would have been 0x1234E anyway, so no further jumping occurs.
(This command is how you can deal with extracting strings from games that mix byte- and bit-oriented data structures within the same string like in the Battle of Olympus example below where there's a gap of several bits between the end of one 5-bit string and the start of the next byte-aligned function index.)
#STRINGS END AT NEXT POINTER: Normally Cartographer continues translating a string until it reaches an end token; setting this option to Yes causes string translation to continue until the next pointer in the current pointer table is reached. One notable consequence of this that still applies to end-token-terminated strings is that if two adjacent pointers point to the same string, the first pointer will have its string length calculated as 0, which effectively eliminates runs of duplicate strings from the output; this property synergizes well with #SORT OUTPUT BY STRING ADDRESS: Yes.
Yes or No option only; defaults to No.
#STRINGS PER POINTER: Specifies the number of end tokens that must be matched in one pointer's string before moving to the next pointer's string. Defaults to 1. Attempting to use this in combination with #METHOD: RAW makes no sense and will generate an error.
(This command is how you can deal with extracting strings that are part of a group of strings that only have a pointer to the first string in the group like in the Dragon Quest IV example.)
#SUB TABLE: The filename of an additional table to be available for use when dumping the script. Unlike other commands, #SUB TABLE commands apply to the entire command file, not just the current block. This command can be repeated as many times as necessary to load tables that are never the starting table (#TABLE) of any block but are the target of some table switch entry. This has the same effect as supplying the table filename in abcde's <general options> on the main command line.
#TABLE ID: Allows you to specify the starting table for the current block by ID instead of filename.
#TRIM TRAILING NEWLINES: If set to Yes, trailing newlines at the end of each string will not be output.
Yes or No option only; defaults to Yes.
5) Atlas Support
abcde expands Atlas' command line to allow passing multiple script files which will be processed independently in the order provided.
Most valid Atlas command files should also be valid for abcde, but abcde's command file parsing rules, order of execution, and final output are subtly different from Atlas'; see `abcde -h -cm abcde::Atlas` for more details.
The primary incompatibility with Atlas command files lies in abcde's lack of support for Atlas extensions; the extension commands themselves are recognized, but abcde isn't going to call your extension's code, and will instead emit a warning about extension commands being unsupported as a reminder not to rely on whatever functionality the extension was supposed to provide.
In addition to fixing several bugs and making a few design decisions differently, abcde adds or extends the following commands:
ADDTBL(string TblFileName, table TableId)
When storing multiple logical tables inside the same table file, only the first table inside TblFileName will be associated with TableId.
ACTIVETBL(string TableId)
Passing a string TableId will activate a table by its ID (as opposed to the existing ACTIVETBL(table TableId) form, which requires every table to be in its own file). E.g. #ACTIVETBL("@foo") will activate the @foo table.
AUTOCMD(number Address, string Command)
During text insertion, when the current position reaches the start of Address, the Atlas command specified by Command will be run prior to inserting the text.
Address - The address at which to execute the Command
Command - The command to execute when reaching Address
You can specify AUTOCMD multiple times for the same Address (they are executed in the order you specify them), and you can use any Atlas command including another AUTCMD (new AUTOCMD commands are executed after any previously specified AUTOCMD commands). After the AUTOCMD executes, it is removed from the list of active AUTOCMDs, so you don't even have to worry about creating an infinite JMP loop. This is pretty potent stuff.
(This command is how you can deal with inserting discontinuous strings like in the Dragon Quest IV example where the data being written to ROM $13FD7 must be continued at ROM $6E500.)
abcde introduces a new VAR type, CALCVAR. A calculated variable is an integer whose value is the result of evaluating a mathematical expression via the new CALC command. Every Atlas command that accepts a number has been expanded to treat a CALCVAR as a number. Furthermore, the W8, W16, W24, W32, WBB, WHB, WHW, WLB, WUB, and WRITE commands have all been expanded to accept a CALCVAR in place of a CUSTOMPOINTER and will use the CALCVAR's value as the value to write to the given address.
CALC(calcvar Var, string Expr)
Evaluates the mathematical expression Expr and saves the result in variable Var. Decimal ([0-9]), hexadecimal ("$" + [0-9A-Fa-f]), and binary ("%" + [01]) numeric values, basic arithmetic ([+-/*()]), bitwise ([&|^]), logical ("&&", "||"), comparison ("<", ">", "<=", ">=", "=="), and ternary if/then/else (<if> + "?" + <then> + ":" + <else>) operators are all supported. Additionally, any CalcVars defined at the time of expression evaluation (!Var) can have their current value interpolated into the expression. The following read-only variable is predefined and is automatically updated to always reflect the current insertion state:
CURRENT_ADDRESS: The file address of the current insertion position.
Note that all CalcVars used in Expr must be defined at the time of expression evaluation.
CONTINUOUSFILE(boolean Continuous)
When passing multiple script files on the command line, setting Continuous to TRUE will maintain the current file's insertion state when starting the next file. Continuous is automatically reset to FALSE before processing each file.
Values: "TRUE", "FALSE"
*** DEPRECATION NOTICE: the functionaly provided by COUNTERs has been superceded by the introduction of CALCVARs in abcde v0.0.9; COUNTER variables and the CREATECTR and INC commands will likely be removed in a future version.
abcde introduces a new VAR type, COUNTER. A counter is just an integer that you can increment via the new INC command, and this integer can be written to the pointer file just like a pointer (the VAR, W16, W24, W32, WBB, WHB, WHW, WLB, WUB, and WRITE commands have all been expanded to accept a COUNTER in place of a CUSTOMPOINTER and will use the CALCVAR's value as the value to write to the given address).
(In the Dragon Quest IV example, these commands in combination with AUTOCMD are how you can update the table that lets Dragon Quest IV know how many text pointers are in each ROM bank.)
CREATECTR(counter Ctr, number Size, number Value)
Creates a counter.
Ctr - Name of the counter to create
Size - Size of the counter in bits
Value - Initial value for the counter, must be an integer
INC(counter Ctr, number Amount)
Increments a counter.
Ctr - The name of the counter to increment
Amount - The amount to increment by, must be an integer
PASCALTYPE(string PascalType)
Controls how the length of Pascal strings is calculated. Default is BYTES.
PascalType - "BYTES" or "TOKENS"
Atlas normally determines the length of a Pascal string by counting the number of bytes in that string, but now you can count the number of tokens (actually the number of matches including match count alterations specified by suffixing the left-hand side of a table entry with "<Y>" for any non-negative decimal integer Y; see the Synopsis of Table File Syntax above) in that string instead.
(This command is how you can deal with games that count string lengths in tokens rather than bytes like in the Battle of Olympus example below.)
PRINT(calcvar Var, number Size)
Inserts the low Size bits of Var into the target file at the current address.
READ(calcvar Var, number Address)
Reads one byte of data (interpreted as an unsigned integer) from offset Address within the target file into Var.
SKIP(number Offsetting)
Changes the current position for text insertion.
Offsetting - Number of bytes to add to current file position
Like JMP, SKIP updates the current insert position, but instead of setting the new insert position to the given target file offset (which necessarily must be known beforehand), the new insert position becomes the old insert position + the value of the (possibly negative) Offsetting parameter. I guess it could be useful for a bunch of things, but it was added as a hack for the current incompatibility of the text-to-binary algorithm's need to match full tokens and the embedded pointer syntax's need to reserve space not included in any other token.
W8(optional custompointer Ptr, number Address)
Writes an 8bit pointer to Address. If a custompointer is provided, it will be used for calculation.
abcde also expands the list of available AddressType values (used by CREATEPTR, EMBTYPE, and SMA) to include the new POINTER_RELATIVE value. Unlike the existing AddressType values which all take the current insert position (or for EMBSET, the position defined by the corresponding EMBWRITE) and modify it based on hard-coded values (e.g. HIROM just adds 0xC00000), POINTER_RELATIVE takes the current insert position (or for EMBSET, the position defined by the corresponding EMBWRITE) and subtracts the address of the pointer being written.
(Embedded pointers using the POINTER_RELATIVE address type are how you can deal with "skip Y bytes" instructions like in the Battle of Olympus example below.)
6) Customization Support
While abcde is capable of handling some fairly advanced script encodings, there is a whole lot of crazy stuff running around out there in the wild, and even with the new features described above, abcde won't be able to handle all of it. If you find yourself dealing with something that abcde can't handle, you might still need to write your own custom game-specific code, but since abcde is open source and is really just a bunch of text files that get run through perl, if you can edit text files and run perl, then you can change how abcde works and use it as a base for whatever customizations you need to make.
6.1) Extracting Embedded Pointer Text
One of the situations that will require customization is extracting embedded pointers. For an example of a game that uses embedded pointers, let's take a look at the English NES game Battle of Olympus. As a quick overview of Battle of Olympus's script engine, the data that pointers point to isn't text, but is instead a series of indexes (or indices if you prefer) into a function jump table, with each function consuming a certain number of the following bytes of data as parameters. The function indexes themselves and most of the function parameters are interpreted as bytes, but displayable text is encoded as a Pascal string with one byte specifying the string length in number of tokens (NOT bytes) followed by a sequence of 5-bit tokens, with some tokens acting as switches between uppercase, lowercase, and numbers/punctuation tables. Only 4 of the 16 functions end up printing text to the screen; the other functions consist of various commands for testing variables, setting variables, exiting the script engine, and a form of branching via updating which address the script engine will read next - otherwise known as an embedded pointer. Here's a sample embedded pointer in action, using the tables provided in abcde's "eg/NES/Battle of Olympus" directory:
[NPC:][Old bald man with white beard and staff]
[if flag X is not set, skip Y bytes][flag: Zeus met][bytes: $16]
// display this if you've already talked to Zeus:
[Dialogue:]
[HERO],[LINE]
don't be discouraged!
// end of displayed text
// display this if you haven't talked to Zeus yet:
[Dialogue:]
[HERO], first you must[LINE]
go to the Temple of[LINE]
Arcadia to meet Zeus.
// end of displayed text
Generating text that represents the testing and setting of variables can be accomplished easily enough via table switching, but the branching required to follow an embedded pointer or to extract both the "if" part and the "else" part (or parts, in the case of more complex functions) of a conditional statement can't be handled using just a table file (at least not without incorporating a whole lot of other things into the concept of a table file, such as a memory model and syntax for interacting with that memory model; I don't know about you, but that's something I'd prefer to avoid). During insertion, embedded pointers can be handled via Atlas' existing embedded pointer commands, but an extraction utility with complementary functionality is harder to find. You'll need to write your own code to handle whatever embedded pointers your game uses, and directions on how to do so are beyond the scope of this readme, but abcde does ship with the abcde::Cartographer::NES::Battle_of_Olympus command module that might prove useful as an example to start from when doing so.
7) Improved Text-To-Binary Algorithm
Even if you aren't using any of the new table file or command file features, abcde implements a powerful text-to-binary translation algorithm which for the given table files and text input, produces the smallest possible binary that can be translated back into the original text input according to the provided table files (if your table files do not accurately represent the game's text encoding, all bets are off). Unlike the longest-prefix text-to-binary algorithm employed by many insertion utilities, abcde uses a variant on the A* graph search algorithm to find a lowest-binary-cost path representing the text input that satisfies the constraints of the longest-prefix binary-to-text translation algorithm. The good news is that (barring implementation bugs) the output of the text-to-binary translation will be the shortest possible for single-table or multi-table encodings, but the bad news is that A* is a moderately complicated breadth-first search algorithm, so it generally takes much longer to find an encoding than the dead simple (but potentially incorrect and/or sub-optimal) depth-first longest-prefix algorithm. Add to that that abcde is written as an interpreted perl script as opposed to a pre-compiled and heavily optimized executable and you might have to wait a little bit before you get your shiny binary to come out.
The amount your project will benefit from abcde's improved algorithm is highly variable, but in general the more table entries your game uses that represent more than two characters, the more likely you are to save space using abcde compared to another insertion utility that uses a longest-prefix text-to-binary algorithm. In some cases, abcde can even be used to re-encode text in a more compact form than the original developers used, so even if you aren't making visible text changes in your project, you might enjoy using any space abcde is able to free up for other things. As an example of this, abcde is able to re-encode Dragon Quest IV's main script using 522 fewer bytes than were required by the original ROM; you can do quite a lot with an extra half-kilobyte of free space :)
8) A Note About UTF-8
Note that any text input to abcde must be encoded as UTF-8, and any text output from abcde will be encoded as UTF-8. In addition to the command line, STDIN (during text-to-binary translation), STDOUT (during binary-to-text translation), and STDERR, this includes all table files and command files and all output files generated by abcde. Most of the time this should just work, but if your system and/or favourite text editor(s) are configured to expect text in some other encoding, you may need to manually specify UTF-8 when working with abcde (on Windows cmd, you can try "chcp 65001", on Unix-like systems you can try e.g. "export LC_ALL=en_US.UTF-8", on a Mac RDP connection to a Windows box running MinGW that ssh's to a Linux box running Notepad under wine, sorry but you're on your own there).
In case it isn't obvious, this does not include the ROM file, STDIN (during binary-to-text translation), or STDOUT (during text-to-binary translation), which are all treated as binary rather than text.
9) Future Plans
abcde has come a long way since its inception, but it still has a long way to go before it becomes the tool I want it to be. Items on the to-do list include (in no particular order):
- further expanding the table file switch entry syntax (e.g. removing limiting features like only being able to fall back on one token instead of a list of tokens);
- cleaner Cartographer dumps (e.g. condensing duplicate pointers instead of dumping the text they point to multiple times);
- adding the ability to sort Cartographer output by string address across blocks (in order to make it easier to test Atlas insertions by comparing the original and modified ROMs);
- adding better/more user-friendly debug info;
- multi-language support;
- code cleanup/optimization (I doubt I'll get ambitious enough to rewrite all this in C/C++, but the performance improvements would likely be substantial);
- and a bunch of other stuff that escapes my mind at the moment :p
10) Thanks
- to AWJ for providing a wealth of helpful information about Dragon Quest IV;
- to Chicken Knife for playing the role of guinea pig and providing useful feedback to help decrease the crappiness of my UI;
- to Klarth for developing and releasing Atlas (with source code!);
- to Nightcrawler for opening discussions about standardizing the table file format;
- to RedComet for developing and releasing Cartographer;
- to Tauwasser for suggesting the A* algorithm as the basis of a workable multi-table insertion algorithm;
- to the rhdn community (which probably includes you!) for many reasons.
11) Contact
Fan mail, questions, comments, insults, suggestions and the like can all be sent to tempestas.caput@gmail.com where they will almost certainly all be read (unless they get filtered as spam). That said, I have been known to go for months without checking my mail, so don't be upset if you don't receive a response in a timely manner :p
12) Version History
0.0.10 (2024-05-25):
Various bugfixes and improvements including but not limited to:
- adding a warning to abcde::Cartographer when trying to extract text without providing an end address or a table with an end token;
- fixing issue where switch tokens that fall back on $hex or %bin had their string length set incorrectly;
- fixing issue inherited from Atlas where the "GB" AddressType calculation was incorrect - thanks Nezz!
0.0.9 (2020-08-27):
Various bugfixes and improvements including but not limited to:
- allowing #ATLAS PTRS in abcde::Cartographer blocks with #METHOD: RAW;
- adding #CALC and friends to abcde::Atlas;
- deprecating COUNTER vars and the CREATECTR and INC abcde::Atlas commands;
- adding more abcde::Cartographer output formatting commands; in simple cases it is now sometimes possible to use the unaltered abcde::Cartographer output as a complete Atlas command file.
0.0.8 (2020-05-10):
Various bugfixes and improvements including but not limited to:
- adding support for odd-length hex table entries;
- fixing #AUTOWRITE issues when used in combination with #FIXEDLENGTH or other #AUTOWRITE commands.
0.0.7 (2020-04-12):
Various bugfixes and improvements including but not limited to:
- allowing abcde::Atlas to accept multiple command files at once;
- adding more abcde::Cartographer output formatting commands;
- adding options to affect resolution of relative paths in file names.
Breaking change: abcde::Atlas and abcde::Cartographer now default to using the current directory for relative paths just like Atlas and Cartographer.
0.0.6 (2020-01-28):
Various bugfixes and improvements including but not limited to:
- adding #W8 abcde::Atlas command;
- fixing abcde::Atlas requiring tables to be loaded on the command line;
- making abcde::Cartographer output more Atlas commands.
0.0.5 (2019-09-01):
Various bugfixes and improvements including but not limited to:
- defaulting to applying Unicode normalizations NFD/NFC to text input/output;
- adding support for multiple logical tables per table file;
- making it possible to specify whether you want to match against bits or bytes when switching to the default table.
0.0.4 (2019-04-19):
Various bugfixes and improvements including but not limited to:
- added #SUB TABLE abcde::Cartographer command;
- now accepting both -$hex and $-hex for negative hexadecimal values.
Breaking changes:
- switched representation of raw bit tokens from e.g. <$0> to <%0>;
- changed behaviour of abcde::Cartographer's #TYPE: FIXED_STRING && FIXED_LINE to hopefully make more sense.
0.0.3 (2019-01-07):
Various bugfixes and improvements.
Breaking change (internal): the abcde::Cartographer perl module got refactored a bit and some method signatures changed.
0.0.2 (2018-09-08):
Various bugfixes, optimizations, and improvements.
Breaking change: reserved characters changed from [] to <> for better compatibility with the raw hex representation used by Atlas and Cartographer.
0.0.1 (2018-03-10):
Initial release.

View File

@ -0,0 +1,9 @@
@copy /Y "Battle of Olympus, The (U) [!].nes" Atlas.nes > nul
@if errorlevel 1 (
echo ROM file \"Battle of Olympus, The (U) [!].nes\" not found; creating an empty file instead.
@echo > nul 2> Atlas.nes
)
perl ..\..\..\abcde.pl -cm abcde::Atlas Atlas.nes Atlas.txt
pause

View File

@ -0,0 +1,9 @@
#!/bin/sh
cp "Battle of Olympus, The (U) [!].nes" Atlas.nes 2> /dev/null;
if [ "$?" != "0" ]; then
echo "ROM file \"Battle of Olympus, The (U) [!].nes\" not found; creating an empty file instead.";
cat /dev/null > Atlas.nes;
fi
perl ../../../abcde.pl -cm abcde::Atlas Atlas.nes Atlas.txt;

View File

@ -0,0 +1,55 @@
//GAME NAME: Battle of Olympus, The (U) <!>.nes
// Define required TABLE variables and load the corresponding tables
#VAR(functions, TABLE)
#ADDTBL("functions.tbl", functions)
#VAR(Table_1, TABLE)
#ADDTBL("NPCs.tbl", Table_1)
#VAR(Table_2, TABLE)
#ADDTBL("flags.tbl", Table_2)
#VAR(Table_3, TABLE)
#ADDTBL("bytes.tbl", Table_3)
#VAR(Table_4, TABLE)
#ADDTBL("items.tbl", Table_4)
#VAR(Table_5, TABLE)
#ADDTBL("OneBytePascalString.tbl", Table_5)
#VAR(upper, TABLE)
#ADDTBL("upper.tbl", upper)
#VAR(Table_7, TABLE)
#ADDTBL("lower.tbl", Table_7)
#VAR(Table_8, TABLE)
#ADDTBL("symbols.tbl", Table_8)
// The Battle of Olympus' pascal strings use 1 byte to store their length and count by number of tokens, not number of bytes
#PASCALLEN(1)
#PASCALTYPE("TOKENS")
//BLOCK #000 NAME: Arcadia
#JMP($15E48, $15E90) // Jump to insertion point
#HDR($C010) // Difference between ROM and RAM addresses for pointer value calculations
//POINTER #0 @ $158F2 - STRING #0 @ $15E48
#W16($158F2)
#ACTIVETBL(functions)
#STRTYPE("ENDTERM")
[NPC:][Old bald man with white beard and staff]
[if flag X is not set, skip Y bytes][flag: Zeus met][bytes: $16]
#SKIP(-1)
#EMBTYPE("POINTER_RELATIVE", 8, $0)
#EMBSET(0)
<$09>
#ACTIVETBL(upper)
#STRTYPE("PASCAL")
[HERO],[LINE]
don't be discouraged!
// current address: $15E62
//EMBEDDED POINTER #0 @ $15E4C - SUB-STRING #0 @ $15E62
#EMBWRITE(0)
#STRTYPE("ENDTERM")
<$09>
#STRTYPE("PASCAL")
[HERO], first you must[LINE]
go to the Temple of[LINE]
Arcadia to meet Zeus.
// current address: $15E90

View File

@ -0,0 +1,9 @@
@if not exist "Battle of Olympus, The (U) [!].nes" (
echo ROM file "Battle of Olympus, The (U) [!].nes" not found; place one here before running this script.
@goto done
)
perl ..\..\..\abcde.pl -cm abcde::Cartographer::NES::Battle_of_Olympus "Battle of Olympus, The (U) [!].nes" Cartographer.txt Cartographer_out -s
:done
@pause

View File

@ -0,0 +1,8 @@
#!/bin/bash
if [ ! -e "Battle of Olympus, The (U) [!].nes" ]; then
echo "ROM file \"Battle of Olympus, The (U) [!].nes\" not found; place one here before running this script.";
exit 1;
fi;
perl ../../../abcde.pl -cm abcde::Cartographer::NES::Battle_of_Olympus "Battle of Olympus, The (U) [!].nes" Cartographer.txt Cartographer_out -s

View File

@ -0,0 +1,162 @@
#GAME NAME: Battle of Olympus, The (U) [!].nes
// general: remember there's a $10 header on the file
#SUB TABLE: NPCs.tbl
#SUB TABLE: flags.tbl
#SUB TABLE: bytes.tbl
#SUB TABLE: items.tbl
#SUB TABLE: OneBytePascalString.tbl
#SUB TABLE: upper.tbl
#SUB TABLE: lower.tbl
#SUB TABLE: symbols.tbl
#BLOCK NAME: Arcadia
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $158F2
#POINTER TABLE STOP: $15909
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Attica
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1590A
#POINTER TABLE STOP: $15927
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Argolis
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $15928
#POINTER TABLE STOP: $15947
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Peloponnesus
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $15948
#POINTER TABLE STOP: $1595D
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Laconia
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1595E
#POINTER TABLE STOP: $15973
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Phthia
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $15974
#POINTER TABLE STOP: $15981
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Crete
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $15982
#POINTER TABLE STOP: $15993
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Phrygia
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $15994
#POINTER TABLE STOP: $15999
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Tartarus
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1599A
#POINTER TABLE STOP: $159A5
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Hades' Temple
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $159A6
#POINTER TABLE STOP: $159B3
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#STRING END REALIGN MULTIPLE: 1
#TABLE: functions.tbl
#COMMENTS: No
#END BLOCK

View File

@ -0,0 +1,256 @@
@OneBytePascalString
/!01=,<@upper>:1
/!02=,<@upper>:2
/!03=,<@upper>:3
/!04=,<@upper>:4
/!05=,<@upper>:5
/!06=,<@upper>:6
/!07=,<@upper>:7
/!08=,<@upper>:8
/!09=,<@upper>:9
/!0A=,<@upper>:10
/!0B=,<@upper>:11
/!0C=,<@upper>:12
/!0D=,<@upper>:13
/!0E=,<@upper>:14
/!0F=,<@upper>:15
/!10=,<@upper>:16
/!11=,<@upper>:17
/!12=,<@upper>:18
/!13=,<@upper>:19
/!14=,<@upper>:20
/!15=,<@upper>:21
/!16=,<@upper>:22
/!17=,<@upper>:23
/!18=,<@upper>:24
/!19=,<@upper>:25
/!1A=,<@upper>:26
/!1B=,<@upper>:27
/!1C=,<@upper>:28
/!1D=,<@upper>:29
/!1E=,<@upper>:30
/!1F=,<@upper>:31
/!20=,<@upper>:32
/!21=,<@upper>:33
/!22=,<@upper>:34
/!23=,<@upper>:35
/!24=,<@upper>:36
/!25=,<@upper>:37
/!26=,<@upper>:38
/!27=,<@upper>:39
/!28=,<@upper>:40
/!29=,<@upper>:41
/!2A=,<@upper>:42
/!2B=,<@upper>:43
/!2C=,<@upper>:44
/!2D=,<@upper>:45
/!2E=,<@upper>:46
/!2F=,<@upper>:47
/!30=,<@upper>:48
/!31=,<@upper>:49
/!32=,<@upper>:50
/!33=,<@upper>:51
/!34=,<@upper>:52
/!35=,<@upper>:53
/!36=,<@upper>:54
/!37=,<@upper>:55
/!38=,<@upper>:56
/!39=,<@upper>:57
/!3A=,<@upper>:58
/!3B=,<@upper>:59
/!3C=,<@upper>:60
/!3D=,<@upper>:61
/!3E=,<@upper>:62
/!3F=,<@upper>:63
/!40=,<@upper>:64
/!41=,<@upper>:65
/!42=,<@upper>:66
/!43=,<@upper>:67
/!44=,<@upper>:68
/!45=,<@upper>:69
/!46=,<@upper>:70
/!47=,<@upper>:71
/!48=,<@upper>:72
/!49=,<@upper>:73
/!4A=,<@upper>:74
/!4B=,<@upper>:75
/!4C=,<@upper>:76
/!4D=,<@upper>:77
/!4E=,<@upper>:78
/!4F=,<@upper>:79
/!50=,<@upper>:80
/!51=,<@upper>:81
/!52=,<@upper>:82
/!53=,<@upper>:83
/!54=,<@upper>:84
/!55=,<@upper>:85
/!56=,<@upper>:86
/!57=,<@upper>:87
/!58=,<@upper>:88
/!59=,<@upper>:89
/!5A=,<@upper>:90
/!5B=,<@upper>:91
/!5C=,<@upper>:92
/!5D=,<@upper>:93
/!5E=,<@upper>:94
/!5F=,<@upper>:95
/!60=,<@upper>:96
/!61=,<@upper>:97
/!62=,<@upper>:98
/!63=,<@upper>:99
/!64=,<@upper>:100
/!65=,<@upper>:101
/!66=,<@upper>:102
/!67=,<@upper>:103
/!68=,<@upper>:104
/!69=,<@upper>:105
/!6A=,<@upper>:106
/!6B=,<@upper>:107
/!6C=,<@upper>:108
/!6D=,<@upper>:109
/!6E=,<@upper>:110
/!6F=,<@upper>:111
/!70=,<@upper>:112
/!71=,<@upper>:113
/!72=,<@upper>:114
/!73=,<@upper>:115
/!74=,<@upper>:116
/!75=,<@upper>:117
/!76=,<@upper>:118
/!77=,<@upper>:119
/!78=,<@upper>:120
/!79=,<@upper>:121
/!7A=,<@upper>:122
/!7B=,<@upper>:123
/!7C=,<@upper>:124
/!7D=,<@upper>:125
/!7E=,<@upper>:126
/!7F=,<@upper>:127
/!80=,<@upper>:128
/!81=,<@upper>:129
/!82=,<@upper>:130
/!83=,<@upper>:131
/!84=,<@upper>:132
/!85=,<@upper>:133
/!86=,<@upper>:134
/!87=,<@upper>:135
/!88=,<@upper>:136
/!89=,<@upper>:137
/!8A=,<@upper>:138
/!8B=,<@upper>:139
/!8C=,<@upper>:140
/!8D=,<@upper>:141
/!8E=,<@upper>:142
/!8F=,<@upper>:143
/!90=,<@upper>:144
/!91=,<@upper>:145
/!92=,<@upper>:146
/!93=,<@upper>:147
/!94=,<@upper>:148
/!95=,<@upper>:149
/!96=,<@upper>:150
/!97=,<@upper>:151
/!98=,<@upper>:152
/!99=,<@upper>:153
/!9A=,<@upper>:154
/!9B=,<@upper>:155
/!9C=,<@upper>:156
/!9D=,<@upper>:157
/!9E=,<@upper>:158
/!9F=,<@upper>:159
/!A0=,<@upper>:160
/!A1=,<@upper>:161
/!A2=,<@upper>:162
/!A3=,<@upper>:163
/!A4=,<@upper>:164
/!A5=,<@upper>:165
/!A6=,<@upper>:166
/!A7=,<@upper>:167
/!A8=,<@upper>:168
/!A9=,<@upper>:169
/!AA=,<@upper>:170
/!AB=,<@upper>:171
/!AC=,<@upper>:172
/!AD=,<@upper>:173
/!AE=,<@upper>:174
/!AF=,<@upper>:175
/!B0=,<@upper>:176
/!B1=,<@upper>:177
/!B2=,<@upper>:178
/!B3=,<@upper>:179
/!B4=,<@upper>:180
/!B5=,<@upper>:181
/!B6=,<@upper>:182
/!B7=,<@upper>:183
/!B8=,<@upper>:184
/!B9=,<@upper>:185
/!BA=,<@upper>:186
/!BB=,<@upper>:187
/!BC=,<@upper>:188
/!BD=,<@upper>:189
/!BE=,<@upper>:190
/!BF=,<@upper>:191
/!C0=,<@upper>:192
/!C1=,<@upper>:193
/!C2=,<@upper>:194
/!C3=,<@upper>:195
/!C4=,<@upper>:196
/!C5=,<@upper>:197
/!C6=,<@upper>:198
/!C7=,<@upper>:199
/!C8=,<@upper>:200
/!C9=,<@upper>:201
/!CA=,<@upper>:202
/!CB=,<@upper>:203
/!CC=,<@upper>:204
/!CD=,<@upper>:205
/!CE=,<@upper>:206
/!CF=,<@upper>:207
/!D0=,<@upper>:208
/!D1=,<@upper>:209
/!D2=,<@upper>:210
/!D3=,<@upper>:211
/!D4=,<@upper>:212
/!D5=,<@upper>:213
/!D6=,<@upper>:214
/!D7=,<@upper>:215
/!D8=,<@upper>:216
/!D9=,<@upper>:217
/!DA=,<@upper>:218
/!DB=,<@upper>:219
/!DC=,<@upper>:220
/!DD=,<@upper>:221
/!DE=,<@upper>:222
/!DF=,<@upper>:223
/!E0=,<@upper>:224
/!E1=,<@upper>:225
/!E2=,<@upper>:226
/!E3=,<@upper>:227
/!E4=,<@upper>:228
/!E5=,<@upper>:229
/!E6=,<@upper>:230
/!E7=,<@upper>:231
/!E8=,<@upper>:232
/!E9=,<@upper>:233
/!EA=,<@upper>:234
/!EB=,<@upper>:235
/!EC=,<@upper>:236
/!ED=,<@upper>:237
/!EE=,<@upper>:238
/!EF=,<@upper>:239
/!F0=,<@upper>:240
/!F1=,<@upper>:241
/!F2=,<@upper>:242
/!F3=,<@upper>:243
/!F4=,<@upper>:244
/!F5=,<@upper>:245
/!F6=,<@upper>:246
/!F7=,<@upper>:247
/!F8=,<@upper>:248
/!F9=,<@upper>:249
/!FA=,<@upper>:250
/!FB=,<@upper>:251
/!FC=,<@upper>:252
/!FD=,<@upper>:253
/!FE=,<@upper>:254
/!FF=,<@upper>:255

View File

@ -0,0 +1,258 @@
@bytes
# yeah, you could just leave them as <$XX>, but where's the fun in that?
00=[bytes: $00]
01=[bytes: $01]
02=[bytes: $02]
03=[bytes: $03]
04=[bytes: $04]
05=[bytes: $05]
06=[bytes: $06]
07=[bytes: $07]
08=[bytes: $08]
09=[bytes: $09]
0A=[bytes: $0A]
0B=[bytes: $0B]
0C=[bytes: $0C]
0D=[bytes: $0D]
0E=[bytes: $0E]
0F=[bytes: $0F]
10=[bytes: $10]
11=[bytes: $11]
12=[bytes: $12]
13=[bytes: $13]
14=[bytes: $14]
15=[bytes: $15]
16=[bytes: $16]
17=[bytes: $17]
18=[bytes: $18]
19=[bytes: $19]
1A=[bytes: $1A]
1B=[bytes: $1B]
1C=[bytes: $1C]
1D=[bytes: $1D]
1E=[bytes: $1E]
1F=[bytes: $1F]
20=[bytes: $20]
21=[bytes: $21]
22=[bytes: $22]
23=[bytes: $23]
24=[bytes: $24]
25=[bytes: $25]
26=[bytes: $26]
27=[bytes: $27]
28=[bytes: $28]
29=[bytes: $29]
2A=[bytes: $2A]
2B=[bytes: $2B]
2C=[bytes: $2C]
2D=[bytes: $2D]
2E=[bytes: $2E]
2F=[bytes: $2F]
30=[bytes: $30]
31=[bytes: $31]
32=[bytes: $32]
33=[bytes: $33]
34=[bytes: $34]
35=[bytes: $35]
36=[bytes: $36]
37=[bytes: $37]
38=[bytes: $38]
39=[bytes: $39]
3A=[bytes: $3A]
3B=[bytes: $3B]
3C=[bytes: $3C]
3D=[bytes: $3D]
3E=[bytes: $3E]
3F=[bytes: $3F]
40=[bytes: $40]
41=[bytes: $41]
42=[bytes: $42]
43=[bytes: $43]
44=[bytes: $44]
45=[bytes: $45]
46=[bytes: $46]
47=[bytes: $47]
48=[bytes: $48]
49=[bytes: $49]
4A=[bytes: $4A]
4B=[bytes: $4B]
4C=[bytes: $4C]
4D=[bytes: $4D]
4E=[bytes: $4E]
4F=[bytes: $4F]
50=[bytes: $50]
51=[bytes: $51]
52=[bytes: $52]
53=[bytes: $53]
54=[bytes: $54]
55=[bytes: $55]
56=[bytes: $56]
57=[bytes: $57]
58=[bytes: $58]
59=[bytes: $59]
5A=[bytes: $5A]
5B=[bytes: $5B]
5C=[bytes: $5C]
5D=[bytes: $5D]
5E=[bytes: $5E]
5F=[bytes: $5F]
60=[bytes: $60]
61=[bytes: $61]
62=[bytes: $62]
63=[bytes: $63]
64=[bytes: $64]
65=[bytes: $65]
66=[bytes: $66]
67=[bytes: $67]
68=[bytes: $68]
69=[bytes: $69]
6A=[bytes: $6A]
6B=[bytes: $6B]
6C=[bytes: $6C]
6D=[bytes: $6D]
6E=[bytes: $6E]
6F=[bytes: $6F]
70=[bytes: $70]
71=[bytes: $71]
72=[bytes: $72]
73=[bytes: $73]
74=[bytes: $74]
75=[bytes: $75]
76=[bytes: $76]
77=[bytes: $77]
78=[bytes: $78]
79=[bytes: $79]
7A=[bytes: $7A]
7B=[bytes: $7B]
7C=[bytes: $7C]
7D=[bytes: $7D]
7E=[bytes: $7E]
7F=[bytes: $7F]
80=[bytes: $80]
81=[bytes: $81]
82=[bytes: $82]
83=[bytes: $83]
84=[bytes: $84]
85=[bytes: $85]
86=[bytes: $86]
87=[bytes: $87]
88=[bytes: $88]
89=[bytes: $89]
8A=[bytes: $8A]
8B=[bytes: $8B]
8C=[bytes: $8C]
8D=[bytes: $8D]
8E=[bytes: $8E]
8F=[bytes: $8F]
90=[bytes: $90]
91=[bytes: $91]
92=[bytes: $92]
93=[bytes: $93]
94=[bytes: $94]
95=[bytes: $95]
96=[bytes: $96]
97=[bytes: $97]
98=[bytes: $98]
99=[bytes: $99]
9A=[bytes: $9A]
9B=[bytes: $9B]
9C=[bytes: $9C]
9D=[bytes: $9D]
9E=[bytes: $9E]
9F=[bytes: $9F]
A0=[bytes: $A0]
A1=[bytes: $A1]
A2=[bytes: $A2]
A3=[bytes: $A3]
A4=[bytes: $A4]
A5=[bytes: $A5]
A6=[bytes: $A6]
A7=[bytes: $A7]
A8=[bytes: $A8]
A9=[bytes: $A9]
AA=[bytes: $AA]
AB=[bytes: $AB]
AC=[bytes: $AC]
AD=[bytes: $AD]
AE=[bytes: $AE]
AF=[bytes: $AF]
B0=[bytes: $B0]
B1=[bytes: $B1]
B2=[bytes: $B2]
B3=[bytes: $B3]
B4=[bytes: $B4]
B5=[bytes: $B5]
B6=[bytes: $B6]
B7=[bytes: $B7]
B8=[bytes: $B8]
B9=[bytes: $B9]
BA=[bytes: $BA]
BB=[bytes: $BB]
BC=[bytes: $BC]
BD=[bytes: $BD]
BE=[bytes: $BE]
BF=[bytes: $BF]
C0=[bytes: $C0]
C1=[bytes: $C1]
C2=[bytes: $C2]
C3=[bytes: $C3]
C4=[bytes: $C4]
C5=[bytes: $C5]
C6=[bytes: $C6]
C7=[bytes: $C7]
C8=[bytes: $C8]
C9=[bytes: $C9]
CA=[bytes: $CA]
CB=[bytes: $CB]
CC=[bytes: $CC]
CD=[bytes: $CD]
CE=[bytes: $CE]
CF=[bytes: $CF]
D0=[bytes: $D0]
D1=[bytes: $D1]
D2=[bytes: $D2]
D3=[bytes: $D3]
D4=[bytes: $D4]
D5=[bytes: $D5]
D6=[bytes: $D6]
D7=[bytes: $D7]
D8=[bytes: $D8]
D9=[bytes: $D9]
DA=[bytes: $DA]
DB=[bytes: $DB]
DC=[bytes: $DC]
DD=[bytes: $DD]
DE=[bytes: $DE]
DF=[bytes: $DF]
E0=[bytes: $E0]
E1=[bytes: $E1]
E2=[bytes: $E2]
E3=[bytes: $E3]
E4=[bytes: $E4]
E5=[bytes: $E5]
E6=[bytes: $E6]
E7=[bytes: $E7]
E8=[bytes: $E8]
E9=[bytes: $E9]
EA=[bytes: $EA]
EB=[bytes: $EB]
EC=[bytes: $EC]
ED=[bytes: $ED]
EE=[bytes: $EE]
EF=[bytes: $EF]
F0=[bytes: $F0]
F1=[bytes: $F1]
F2=[bytes: $F2]
F3=[bytes: $F3]
F4=[bytes: $F4]
F5=[bytes: $F5]
F6=[bytes: $F6]
F7=[bytes: $F7]
F8=[bytes: $F8]
F9=[bytes: $F9]
FA=[bytes: $FA]
FB=[bytes: $FB]
FC=[bytes: $FC]
FD=[bytes: $FD]
FE=[bytes: $FE]
FF=[bytes: $FF]

View File

@ -0,0 +1,25 @@
@flags
01=[flag: have Staff of Fennel]
02=[flag: have Nymph Sword]
03=[flag: Hephaesots met]
04=[flag: Apollo met]
05=[flag: Poseidon met]
08=[flag: Artemis met]
09=[flag: Hermes met]
0A=[flag: Athena met]
0B=[flag: have Salamander Shield]
0C=[flag: Ares met]
0D=[flag: have Key]
0E=[flag: have Fragment of Love #1]
0F=[flag: have Fragment of Love #2]
10=[flag: have Fragment of Love #3]
12=[flag: Zeus met]
14=[flag: $14]
16=[flag: have Ambrosia #2]
17=[flag: have Ambrosia #1]
18=[flag: have Ambrosia #3]
1C=[flag: $1C]
1F=[flag: child rescued]
20=[flag: missed Hermes]
21=[flag: have upgraded Staff of Fennel]
22=[flag: Hydra dead]

View File

@ -0,0 +1,20 @@
@functions
# 00: technically this is an end token, but it occurs during room setup and activating the dialogue continues with the next byte
!00=<\n[NPC:]>,<@NPCs>:1
!01=<\n[if flag X is not set, skip Y bytes]>,<@flags>:1,<@bytes>:1
!02=<\n[skip X bytes]>,<@bytes>:1
!03=<\n[read string from address]>,2
/04=\n[exit script engine]
!05=<\n[set flag:]>,<@flags>:1
!06=<\n[unset flag]>,<@flags>:1
!07=<\n[if flag X is set, skip Y bytes]>,<@flags>:1,<@bytes>:1
!08=<\n[load/receive item]>,<@items>:1
# 09: technically this is an end token, but we've attached the ending to the tokens inside @OneBytePascalString
!09=<\n[Dialogue:]\n>,<@OneBytePascalString>:1
!0A=<\n[display yes/no; if no selected, skip X bytes]>,<@bytes>:1
!0B=<\n[if # salamander skins less than 20, skip X bytes]>,<@bytes>:1
!0C=<\n[if # olives less than X, skip Y bytes]>,1,<@bytes>:1
/0D=\n[generate and display password]
# looks like these ones are health related, possibly refilling and extending; I didn't bother investigating them
/0E=\n[function_0E]
/0F=\n[function_0F]

View File

@ -0,0 +1,18 @@
@items
01=[Staff of Fennel]
02=[Nymph Sword]
03=[Divine Sword]
04=[Apollo's Harp]
05=[Ocarina]
08=[Moon Orb]
09=[Hermes' Sandals]
0A=[Athena's Shield]
0B=[Salamander Shield]
0C=[Power Bracelet]
0D=[Key]
0E=[Fragment of Love #1]
0F=[Fragment of Love #2]
10=[Fragment of Love #3]
16=[Ambrosia #2]
17=[Ambrosia #1]
18=[Ambrosia #3]

View File

@ -0,0 +1,43 @@
@lower
# numbers and punctuation symbols are handled similarly, but numbers do not trigger fallback
!%00000=,<@numbers>:0+
%0000001011<2>=?
%0000001100<2>=!
%0000001101<2>=.
%0000001110<2>=,
%0000001111<2>='
%0000010000<2>=-
%00001=a
%00010=b
%00011=c
%00100=d
%00101=e
%00110=f
%00111=g
%01000=h
%01001=i
%01010=j
%01011=k
%01100=l
%01101=m
%01110=n
%01111=o
%10000=p
%10001=q
%10010=r
%10011=s
%10100=t
%10101=u
%10110=v
%10111=w
%11000=x
%11001=y
%11010=z
!%11011=,-1
%1110000000<2>=[HERO]
%1110000001<2>=[HEROINE]
%11101=
%11110=[LINE]\n
!%11111=<[PAGE]\n>,-1

View File

@ -0,0 +1,86 @@
@NPCs
00=[Young woman with long hair in green dress]
01=[Young woman with hair in a bun in green dress]
02=[Bearded man in green tunic]
03=[Old bald man with green beard and staff]
04=[Child with green clothes]
05=[Young man in green tunic]
06=[Old hairy man with green beard and staff]
07=[Old woman with green hair and staff]
08=[Young woman with long hair in pink dress]
09=[Young woman with hair in a bun in pink dress]
0A=[Bearded man in pink tunic]
0B=[Old bald man with pink beard and staff]
0C=[Child with pink clothes]
0D=[Young man in pink tunic]
0E=[Old hairy man with pink beard and staff]
0F=[Old woman with pink hair and staff]
10=[Young woman with long hair in orange dress]
11=[Young woman with hair in a bun in orange dress]
12=[Bearded man in orange tunic]
13=[Old bald man with orange beard and staff]
14=[Child with orange clothes]
15=[Young man in orange tunic]
16=[Old hairy man with orange beard and staff]
17=[Old woman with orange hair and staff]
18=[Young woman with long hair in red dress]
19=[Young woman with hair in a bun in red dress]
1A=[Bearded man in red tunic]
1B=[Old bald man with red beard and staff]
1C=[Child with red clothes]
1D=[Young man in red tunic]
1E=[Old hairy man with red beard and staff]
1F=[Old woman with red hair and staff]
20=[Young woman with long hair in grey dress]
21=[Young woman with hair in a bun in grey dress]
22=[Bearded man in grey tunic]
23=[Old bald man with grey beard and staff]
24=[Child with grey clothes]
25=[Young man in grey tunic]
26=[Old hairy man with grey beard and staff]
27=[Old woman with grey hair and staff]
28=[Young woman with long hair in blue dress]
29=[Young woman with hair in a bun in blue dress]
2A=[Bearded man in blue tunic]
2B=[Old bald man with blue beard and staff]
2C=[Child with blue clothes]
2D=[Young man in blue tunic]
2E=[Old hairy man with blue beard and staff]
2F=[Old woman with blue hair and staff]
30=[Young woman with long hair in white dress]
31=[Young woman with hair in a bun in white dress]
32=[Bearded man in white tunic]
33=[Old bald man with white beard and staff]
34=[Child with white clothes]
35=[Young man in white tunic]
36=[Old hairy man with white beard and staff]
37=[Old woman with white hair and staff]
38=[Young woman with long hair in black dress]
39=[Young woman with hair in a bun in black dress]
3A=[Bearded man in black tunic]
3B=[Old bald man with black beard and staff]
3C=[Child with black clothes]
3D=[Young man in black tunic]
3E=[Old hairy man with black beard and staff]
3F=[Old woman with black hair and staff]
40=[Zeus]
41=[Female goddess]
42=[Male bearded god]
43=[Male beardless god]
60=[Fairy in pink dress]
61=[Fairy in green dress]
62=[Fairy in purple dress]
63=[Fairy in blue dress]
80=[nobody!]
C0=[2-block wall]

View File

@ -0,0 +1,12 @@
@numbers
!%00000=,-1
%00001=0
%00010=1
%00011=2
%00100=3
%00101=4
%00110=5
%00111=6
%01000=7
%01001=8
%01010=9

View File

@ -0,0 +1,41 @@
@upper
# numbers and punctuation symbols are handled similarly, but numbers do not trigger fallback
!%00000=,<@numbers>:0+
%0000001011<2>=?
%0000001100<2>=!
%0000001101<2>=.
%0000001110<2>=,
%0000001111<2>='
%0000010000<2>=-
%00001=A
%00010=B
%00011=C
%00100=D
%00101=E
%00110=F
%00111=G
%01000=H
%01001=I
%01010=J
%01011=K
%01100=L
%01101=M
%01110=N
%01111=O
%10000=P
%10001=Q
%10010=R
%10011=S
%10100=T
%10101=U
%10110=V
%10111=W
%11000=X
%11001=Y
%11010=Z
!%11011=,<@lower>:0+
%1110000000<2>=[HERO]
%1110000001<2>=[HEROINE]
%11101=
%11110=[LINE]\n
%11111=[PAGE]\n

View File

@ -0,0 +1,9 @@
@copy /Y "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" Atlas.nes > nul
@if errorlevel 1 (
echo ROM file \"Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes\" not found; creating an empty file instead.
@echo > nul 2> Atlas.nes
)
perl ..\..\..\abcde.pl -cm abcde::Atlas Atlas.nes Atlas.txt
pause

View File

@ -0,0 +1,9 @@
#!/bin/sh
cp "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" Atlas.nes 2> /dev/null;
if [ "$?" != "0" ]; then
echo "ROM file \"Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes\" not found; creating an empty file instead.";
cat /dev/null > Atlas.nes;
fi
perl ../../../abcde.pl -cm abcde::Atlas Atlas.nes Atlas.txt

View File

@ -0,0 +1,137 @@
//GAME NAME: Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) <!>.nes
// Define required TABLE variables and load the corresponding tables
#VAR(Table_0, TABLE)
#ADDTBL("hira.tbl", Table_0)
#VAR(Table_1, TABLE)
#ADDTBL("kata.tbl", Table_1)
#VAR(Table_2, TABLE)
#ADDTBL("dict.tbl", Table_2)
#VAR(pointerNum, CALCVAR) // create a CALCVAR variable named pointerNum
#CALC(pointerNum, "0") // initialize pointerNum to 0
// auto-commands:
// jump over each $4000 bank's RAM $BFD8-$BFFF to the next bank's $8000 (or $A500 for the final jump)
// update the pointer offsets
// update the table that controls which pointers start a new bank
#AUTOCMD($3FE8, #JMP($4010))
#AUTOCMD($3FE8, #HDR($-3FF0))
#AUTOCMD($3FE8, #WLB(pointerNum, $58970))
#AUTOCMD($7FE8, #JMP($8010))
#AUTOCMD($7FE8, #HDR($10))
#AUTOCMD($7FE8, #WLB(pointerNum, $58971))
#AUTOCMD($BFE8, #JMP($C010))
#AUTOCMD($BFE8, #HDR($4010))
#AUTOCMD($BFE8, #WLB(pointerNum, $58972))
#AUTOCMD($FFE8, #JMP($10010))
#AUTOCMD($FFE8, #HDR($8010))
#AUTOCMD($FFE8, #WLB(pointerNum, $58973))
#AUTOCMD($13FE8, #JMP($6E510))
#AUTOCMD($13FE8, #HDR($64010))
#AUTOCMD($13FE8, #WLB(pointerNum, $58974))
//BLOCK #000 NAME: Script
#ACTIVETBL(Table_0) // Activate this block's starting TABLE
#JMP($10, $6FBA3) // Jump to insertion point
#HDR($-7FF0) // Difference between ROM and RAM addresses for pointer value calculations
//POINTER #0 @ $588C0 - STRING #0 @ $10
#W16($588C0)
#CALC(pointerNum, "!pointerNum + 1")
ばしゃがいない[end]
MPがたりない[end]
[F6]は どうぐを もっていない[end]
[F6]は のろわれていて つけかえができない[end]
しかし まわりこまれてしまった![end]
[F6]が あらわれた[end]
ミス![FC]
[F6]は ダメージをうけない[end]
ミス![FC]
[F6]に ダメージをあたえられない[FC]
[FF][end]
[F6]を やっつけた[end]
まもののむれを やっつけた[end]
[F6]は いなくなった[end]
まもののむれは いなくなった[end]
[F6]は[FC]
[F8]ポイントのけいけんちをかくとく[end]
それぞれ[FC]
[F8]ポイントのけいけんちをかくとく[end]
[F6]には じゅもんがとどかない![end]
じゅもんは ひかりのかべに はじかれた![end]
しかし じゅもんは ふうじこまれている[end]
[F6]は[FC]
からだが しびれて うごけない[end]
[F6]は めをさました。[end]
[F6]は ねむっている[end]
[F6]は こんらんしている[end]
[F6]は ちからつきて いきたえた[FC]
[FF][end]
[F6]に [F8]のダメージ![end]
[F6]は [F8]のダメージを うけた![end]
[F6]には きかなかった![end]
[F6]は しななかった![end]
しかし なにも おこらなかった![end]
[F6]は しんでしまった![end]
[F6]は くだけちった![end]
[F6]は にげだした![end]
[F6]たちは にげだした![end]
しかし [F6]は ねむっている![F4][end]
// current address: $182
//POINTER #1 @ $588C2 - STRING #1 @ $183
#W16($588C2)
#CALC(pointerNum, "!pointerNum + 1")
しかし [F6]は[FC]
からだが しびれて うごけない![F4][end]
しかし [F6]は こんらんしている[F4][end]
[F6]の からだから[FC]
あやしいひかりが ふりそそいだ![F4][end]
[F6]は ねがえりを うった![end]
[F6]は わらいころげている![end]
[F6]は ころんで もがいている![end]
[F6]は[FC]
あなから でられず もがいている![F4][end]
[F6]は[FC]
おどろき すくみあがっている![F4][end]
[F6]「ねーねー [F6]さん[FC]
コイン くださらない?[FC]
あら ここカジノじゃないの?[F4][F4][F4][end]
[F6]は ばしゃに かけこんだ![end]
[F6]は[FC]
[F6]の ひつぎを ばしゃに おしこんだ![F4][end]
[F6]が ばしゃから とびだした![end]
[F6]は[FC]
[F6]の ひつぎを ばしゃから ひきずりだした[F4][end]
[F6]は [F7]を てにとった![end]
[F6]は みをまもっている![end]
[F6]が[FC]
たすけに あらわれた![end]
[F6]は なかまを よんだ![end]
しかし たすけは こなかった![end]
[F6]は ようすをみている[end]
[F6]は まごまごしている[end]
[F6]は ちからをためている[end]
[F6]は[FC]
おおきく いきを すいこんだ![end]
[F6]は しずかに めいそうした。[F4][F4][end]
なんと [F6]の キズが[FC]
みるみる かいふくした![F4][end]
[F6]の かおいろが かわった![F4][end]
[F6]は ほんきを だしてきた![F4][end]
[F6]に おびえの いろが みえる![F4][end]
[F6]は[FC]
さいごの ちからを ふりしぼった![F4][end]
 [F6]たちが………[F4][end]
[F6]は[FC]
ふしぎな おどりを おどった![end]
[F6]のMPが [F8]さがった![end]
[F6]の ゆびから[FC]
いてつく はどうが ほとばしる![F4][end]
// current address: $37D

View File

@ -0,0 +1,9 @@
@if not exist "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" (
echo ROM file "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" not found; place one here before running this script.
@goto done
)
perl ..\..\..\abcde.pl -cm abcde::Cartographer "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" Cartographer.txt Cartographer_out -s
:done
@pause

View File

@ -0,0 +1,8 @@
#!/bin/bash
if [ ! -e "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" ]; then
echo "ROM file \"Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes\" not found; place one here before running this script.";
exit 1;
fi;
perl ../../../abcde.pl -cm abcde::Cartographer "Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes" Cartographer.txt Cartographer_out -s;

View File

@ -0,0 +1,205 @@
#GAME NAME: Dragon Quest IV - Michibikareshi Monotachi (J) (PRG1) [!].nes
// general: remember there's a $10 header on the file
#SUB TABLE: kata.tbl
#SUB TABLE: dict.tbl
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $588C0
#POINTER TABLE STOP: $588C7
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: -$7FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// only 11 strings per pointer
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $588C8
#POINTER TABLE STOP: $588C9
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 11
#ATLAS PTRS: Yes
#BASE POINTER: -$7FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// skip duplicate pointers from $588CA - $588CF
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $588D0
#POINTER TABLE STOP: $588DD
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: -$7FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// only 27 strings per pointer
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $588DE
#POINTER TABLE STOP: $588DF
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 27
#ATLAS PTRS: Yes
#BASE POINTER: -$7FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// bank swap
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $588E0
#POINTER TABLE STOP: $588F7
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#AUTO JUMP START: $3FE8
#AUTO JUMP STOP: $4010
#ATLAS PTRS: Yes
#BASE POINTER: -$7FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $588F8
#POINTER TABLE STOP: $5890D
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: -$3FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// skip duplicate pointers from $5890E - $5890F; bank swap
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $58910
#POINTER TABLE STOP: $58917
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#AUTO JUMP START: $7FE8
#AUTO JUMP STOP: $8010
#ATLAS PTRS: Yes
#BASE POINTER: -$3FF0
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// bank swap
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $58918
#POINTER TABLE STOP: $58933
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#AUTO JUMP START: $BFE8
#AUTO JUMP STOP: $C010
#ATLAS PTRS: Yes
#BASE POINTER: $10
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// bank swap
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $58934
#POINTER TABLE STOP: $5894D
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#AUTO JUMP START: $FFE8
#AUTO JUMP STOP: $10010
#ATLAS PTRS: Yes
#BASE POINTER: $4010
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// bank swap
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $5894E
#POINTER TABLE STOP: $58965
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#AUTO JUMP START: $13FE8
#AUTO JUMP STOP: $6E510
#ATLAS PTRS: Yes
#BASE POINTER: $8010
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $58966
#POINTER TABLE STOP: $5896D
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: $64010
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK
// only 3 strings per pointer
#BLOCK NAME: Script
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $5896E
#POINTER TABLE STOP: $5896F
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 3
#ATLAS PTRS: Yes
#BASE POINTER: $64010
#TABLE: hira.tbl
#COMMENTS: No
#END BLOCK

View File

@ -0,0 +1,195 @@
# this table appears to be accessible from both @hiragana and @katakana
# these are actually 12-bit entries, but we used the first 4 bits as table switches in @hiragana and @katakana
@dictionary
40=[FB]\n
41=「
42=
43=…
44=…
45=*「
46=なんと
47=せかい
48=じょう
49=います
4A=ます。
4B=っている
4C=った!
4D=………
4E=か……
4F=
50=
51=
52=
53=
54=
55=
56=
57=
58=
59=ところ
5A=さまの
5B=[EF]
5C=[EE]
5D=[ED]
5E=ございま
5F=[FF]
60=たんだ
61=のだ。
62=ゆうし
63=この 
64=ません
65=ている
66=じゃあ
67=かった
68=いって
69=のです
6A=ないか
6B=ことを
6C= しょう
6D=している
6E=です。[FB]\n
6F=トルネコ
70=やく 
71=え? 
72=おお! 
73=バルザック
74=まものたち
75=キングレオ
76=しんかのひほう
77=ゆうしゃ
78=なんでも 
79=
7A=くなった
7B= あなた
7C=けっこん
7D=しょう。
7E=[F6]の 
7F=エンドール
80=あなた
81=です。
82=そなた
83=おしろ
84=でも 
85=す。[FB]\n
86=せんし
87=はやく
88=ものを
89= もう
8A=すか?
8B=なくなっ
8C=だ。[F4]
8D=……。
8E=が……
8F=……。[FB]\n
90= この
91=ちゃん
92= その
93=こども
94=らしい
95=どこか
96=そんな
97=おまえ
98=じゃな
99=また 
9A=さまが
9B=いるの
9C=まに 
9D=ありがと
9E=ました。
9F=わたしは 
A0= たび
A1=ような
A2=には 
A3=おお 
A4=ものが
A5=しょう
A6=のまち
A7=ってい
A8=いった
A9=まった
AA= なに
AB=さまは
AC=ひめさま
AD=いました
AE=ネネ「
AF=ライアン
B0=ぶじゅつたいかい
B1=なんと
B2=どうか 
B3=ゆうしょう
B4=むにゃむにゃ
B5=ありがとうございました
B6=ても 
B7=れてい
B8=おうごんのうでわ
B9=
BA=でしょう
BB=おうさま
BC=たのです
BD=ましょう
BE=[F6]は 
BF=デスピサロ
C0=わたし
C1=では 
C2=あんた
C3=ここは
C4=まもの
C5=ました
C6=じゃ。
C7=ちから
C8=きょう
C9=もう 
CA=した!
CB=なかった
CC=た![F4]
CD=…………
CE=う……。
CF=……。[FB]\n
D0=という
D1=から 
D2=だろう
D3=なかま
D4=じゅつ
D5=むすめ
D6=おかね
D7=うわさ
D8= ある
D9=さあ 
DA= ここ
DB=ことが
DC= もって
DD= わたし
DE= おうさま
DF=てください
E0=ように
E1=だよ。
E2=さい。
E3=ですか
E4= ひと
E5= どう
E6=ってき
E7=のじゃ
E8=だった
E9=てくれ
EA=れます
EB=なかった
EC=しまった
ED=ください
EE=ゴールド
EF=アりーナ
F0=まで 
F1=いったい 
F2=まったく 
F3=ぐうぐう……。
F4=わっはっはっ
F5=じごくの ていおう
F6=まって
F7=サントハイム
F8=れんきんじゅつ
F9=
FA=てくださ
FB= おしろ
FC=のです。
FD=ここは 
FE=[F6]は 
FF=[F6]は [F7]を

View File

@ -0,0 +1,97 @@
# start table is @hiragana
# make sure you view this in a font that contains U+3099/U+309A
@hiragana
# normal characters
%000000=あ
%000001=い
%000010=う
%000011=え
%000100=お
%000101=か
%000110=き
%000111=く
%001000=け
%001001=こ
%001010=さ
%001011=し
%001100=す
%001101=せ
%001110=そ
%001111=た
%010000=ち
%010001=つ
%010010=て
%010011=と
%010100=な
%010101=に
%010110=ぬ
%010111=ね
%011000=の
%011001=は
%011010=ひ
%011011=ふ
%011100=へ
%011101=ほ
%011110=ま
%011111=み
%100000=む
%100001=め
%100010=も
%100011=や
%100100=ゆ
%100101=よ
%100110=ら
%100111=り
%101000=る
%101001=れ
%101010=ろ
%101011=わ
%101100=を
%101101=ん
%101110=っ
%101111=ゃ
%110000=ゅ
%110001=ょ
%110010=
%110011=。
%110100=
# normal + dakuten
%110101000010=ゔ
%110101000101=が
%110101000110=ぎ
%110101000111=ぐ
%110101001000=げ
%110101001001=ご
%110101001010=ざ
%110101001011=じ
%110101001100=ず
%110101001101=ぜ
%110101001110=ぞ
%110101001111=だ
%110101010000=ぢ
%110101010001=づ
%110101010010=で
%110101010011=ど
%110101011001=ば
%110101011010=び
%110101011011=ぶ
%110101011100=べ
%110101011101=ぼ
# normal + handakuten
%110110011001=ぱ
%110110011010=ぴ
%110110011011=ぷ
%110110011100=ぺ
%110110011101=ぽ
%110111=[FC]\n
%111000=[FB]\n
/%111001=[end]\n
%111010= 
%111011=[F1]
!%111100=,<@katakana>:0
!%1111=,<@dictionary>:1

View File

@ -0,0 +1,91 @@
# start table is @hiragana
# make sure you view this in a font that contains U+3099/U+309A
@katakana
# normal characters
%000000=ア
%000001=イ
%000010=エ
%000011=オ
%000100=カ
%000101=キ
%000110=ク
%000111=コ
%001000=サ
%001001=シ
%001010=ス
%001011=ソ
%001100=タ
%001101=テ
%001110=ト
%001111=ナ
%010000=ニ
%010001=ヌ
%010010=ネ
%010011=
%010100=ハ
%010101=ヒ
%010110=フ
%010111=ホ
%011000=マ
%011001=ミ
%011010=ム
%011011=メ
%011100=モ
%011101=ラ
%011110=ル
%011111=レ
%100000=ロ
%100001=ン
%100010=ッ
%100011=ャ
%100100=
%100101=ー
%100110=
%100111=
%101000=
%101001=
%101010=
%101011=
%101100=
%101101=
%101110=
%101111=
%110000=[F8]
%110001=[F6]
%110010=[F7]
%110011=[F5]
%110100=[F4]
# normal + dakuten
%110101000100=ガ
%110101000101=ギ
%110101000110=グ
%110101000111=ゴ
%110101001000=ザ
%110101001001=ジ
%110101001010=ズ
%110101001011=ゾ
%110101001100=ダ
%110101001101=デ
%110101001110=ド
%110101010100=バ
%110101010101=ビ
%110101010110=ブ
%110101010111=ボ
%110101100010=ヅ
# normal + handakuten
%110110010100=パ
%110110010101=ピ
%110110010110=プ
%110110010111=ポ
%110111=[FE]
%111000=[FD]
# strings actually can end in @katakana, but DQ4 always returns to @hiragana anyway
/%111001=[end]\n
%111010= 
%111011=[F0]
!%111100=,-1
!%1111=,<@dictionary>:1

View File

@ -0,0 +1,9 @@
@copy /Y "Dragon Warrior II (U) [!].nes" Atlas.nes > nul
@if errorlevel 1 (
echo ROM file \"Dragon Warrior II (U) [!].nes\" not found; creating an empty file instead.
@echo > nul 2> Atlas.nes
)
perl ..\..\..\abcde.pl -cm abcde::Atlas Atlas.nes Atlas.txt
pause

View File

@ -0,0 +1,9 @@
#!/bin/sh
cp "Dragon Warrior II (U) [!].nes" Atlas.nes 2> /dev/null;
if [ "$?" != "0" ]; then
echo "ROM file \"Dragon Warrior II (U) [!].nes\" not found; creating an empty file instead.";
cat /dev/null > Atlas.nes;
fi
perl ../../../abcde.pl -cm abcde::Atlas Atlas.nes Atlas.txt

View File

@ -0,0 +1,263 @@
//GAME NAME: Dragon Warrior II (U) [!].nes
// Define required TABLE variables and load the corresponding tables
#VAR(Table_0, TABLE)
#ADDTBL("credits.tbl", Table_0)
#VAR(Table_1, TABLE)
#ADDTBL("credits_spacer.tbl", Table_1)
#VAR(Table_2, TABLE)
#ADDTBL("cursor.tbl", Table_2)
#VAR(Table_3, TABLE)
#ADDTBL("dw2.tbl", Table_3)
#VAR(Table_4, TABLE)
#ADDTBL("dw2_script.tbl", Table_4)
#VAR(Table_5, TABLE)
#ADDTBL("heights.tbl", Table_5)
#VAR(Table_6, TABLE)
#ADDTBL("menu.tbl", Table_6)
#VAR(Table_7, TABLE)
#ADDTBL("menu_links.tbl", Table_7)
#VAR(Table_8, TABLE)
#ADDTBL("menu_params.tbl", Table_8)
#VAR(Table_9, TABLE)
#ADDTBL("pos.tbl", Table_9)
#VAR(Table_10, TABLE)
#ADDTBL("pos2.tbl", Table_10)
#VAR(Table_11, TABLE)
#ADDTBL("spacer.tbl", Table_11)
#VAR(Table_12, TABLE)
#ADDTBL("widths.tbl", Table_12)
#VAR(pointerNum, CALCVAR) // create a CALCVAR variable named pointerNum
#CALC(pointerNum, "0") // initialize pointerNum to 0
// Auto-commands for when DW2 does a mid-string bankswap and resets its read address.
// Note that DW2's text engine reads the first 2 bytes at the current pointer's target *before* the code for checking whether the read address has reached RAM $BFD7 runs, so if you're unlucky enough to have a pointer point to RAM $BFD6 or $BFD7, change these AUTOCMD addresses from $17FE7 to $17FE5 or $17FE6 respectively and try inserting again.
#AUTOCMD($17FE7, #HDR($10))
#AUTOCMD($17FE7, #JMP($B7C2, $BE0F))
// update the code that controls which pointer starts the next bank
#AUTOCMD($17FE7, #WLB(pointerNum, $3FA90))
//BLOCK #000 NAME: Main Script Part 1
#ACTIVETBL(Table_4) // Activate this block's starting TABLE
#JMP($14010) // Jump to insertion point
#HDR($C010) // Difference between ROM and RAM addresses for pointer value calculations
//POINTER #0 @ $B762 - STRING #0 @ $14010
#W16($B762)
#CALC(pointerNum, "!pointerNum + 1")
//[name] stood guard.[end-FC]
[name] stood guard.[end-FC]
//[cardinal #] [monster(s)],[end-FC]
[cardinal #] [monster(s)],[end-FC]
//But it wasn't real.[end-FC]
But it wasn't real.[end-FC]
//[name] attacked![end-FC]
[name] attacked![end-FC]
//Before [name] was set for battle.[end-FC]
Before [name] was set for battle.[end-FC]
//But the [name] did not see thee.[end-FC]
But the [name] did not see thee.[end-FC]
//[name] attacked![end-FC]
[name] attacked![end-FC]
//But missed![end-FC]
But missed![end-FC]
//[name] was wounded.[end-FC]
[name] was wounded.[end-FC]
//[name] was not hurt.[end-FC]
[name] was not hurt.[end-FC]
//[name] dodged the blow.[end-FC]
[name] dodged the blow.[end-FC]
//[name]'s HP is reduced by [number].[end-FC]
[name]'s HP is reduced by [number].[end-FC]
//[name]'s HP is reduced by [number].[end-FC]
[name]'s HP is reduced by [number].[end-FC]
//A tremendous blow![end-FC]
A tremendous blow![end-FC]
//A heroic attack![end-FC]
A heroic attack![end-FC]
//[name] broke away and ran.[end-FC]
[name] broke away and ran.[end-FC]
// current address: $140C2
//POINTER #1 @ $B764 - STRING #1 @ $140C3
#W16($B764)
#CALC(pointerNum, "!pointerNum + 1")
//But there was no escape.[end-FC]
But there was no escape.[end-FC]
//Thy MP is low.[end-FC]
Thy MP is low.[end-FC]
//But the spell was blocked.[end-FC]
But the spell was blocked.[end-FC]
//[name] called for help.[end-FC]
[name] called for help.[end-FC]
//But no help came.[end-FC]
But no help came.[end-FC]
//[name] came to help.[end-FC]
[name] came to help.[end-FC]
//The enemies was confused.[end-FC]
The enemies was confused.[end-FC]
//[name]'s wounds were healed.[end-FC]
[name]'s wounds were healed.[end-FC]
//But the spell had no effect on [name].[end-FC]
But the spell had no effect on [name].[end-FC]
//Thou hast defeated the [name].[end-FC]
Thou hast defeated the [name].[end-FC]
//[name] chanted the spell of [spell].[end-FC]
[name] chanted the spell of [spell].[end-FC]
//Alas, brave [name] hast died.[end-FC]
Alas, brave [name] hast died.[end-FC]
//[name] fell asleep.[end-FC]
[name] fell asleep.[end-FC]
//[name] did not fall asleep.[end-FC]
[name] did not fall asleep.[end-FC]
//[name] fell asleep.[end-FC]
[name] fell asleep.[end-FC]
//[name] is asleep.[end-FC]
[name] is asleep.[end-FC]
// current address: $14186
// etc., etc.
// due to the way the game's bank bridging code works, the first byte of the second script bank needs to be copied to the byte after the last byte of the first script bank in order to ensure consistency between banks
#VAR(copy, CALCVAR)
#READ(copy, $B7C2)
#JMP($17FE7)
#PRINT(copy, 8)
//BLOCK #002 NAME: Menus
#ACTIVETBL(Table_8) // Activate this block's starting TABLE
#JMP($76E6, $7FDF) // Jump to insertion point
#HDR($-3FF0) // Difference between ROM and RAM addresses for pointer value calculations
//POINTER #0 @ $7652 - STRING #0 @ $76E6
#W16($7652)
//[menu format 01: no cursor, not linked, double-spaced]
//[height:4 tiles]
//[width:18 tiles]
//[position:(10,22)]
//NAME═LV══HP══MP[border line]
//[Midenhall's short name] [Midenhall's level] [Midenhall's current HP] [Midenhall's current MP][line]
[menu format 01: no cursor, not linked, double-spaced]
[height:4 tiles]
[width:18 tiles]
[position:(10,22)]
NAME═LV══HP══MP[border line]
[Midenhall's short name] [Midenhall's level] [Midenhall's current HP] [Midenhall's current MP][line]
// current address: $7700
//POINTER #1 @ $7654 - STRING #1 @ $7700
#W16($7654)
//[menu format 01: no cursor, not linked, double-spaced]
//[height:4 tiles]
//[width:18 tiles]
//[position:(4,2)]
//NAME═LV══HP══MP[border line]
//[Midenhall's short name] [Midenhall's level] [Midenhall's current HP] [Midenhall's current MP][line]
[menu format 01: no cursor, not linked, double-spaced]
[height:4 tiles]
[width:18 tiles]
[position:(4,2)]
NAME═LV══HP══MP[border line]
[Midenhall's short name] [Midenhall's level] [Midenhall's current HP] [Midenhall's current MP][line]
// current address: $771A
// etc., etc.
//BLOCK #003 NAME: "ADVENTURE LOG", stored backwards
#ACTIVETBL(Table_3) // Activate this block's starting TABLE
#JMP($3EDCF, $3EDDB) // Jump to insertion point
#HDR($30011) // Difference between ROM and RAM addresses for pointer value calculations
//POINTER #0 @ $3EDC4 - STRING #0 @ $3EDCF
#W16($3EDC4)
//GOL ERUTNEVDA
GOL ERUTNEVDA
// current address: $3EDDC
//BLOCK #004 NAME: Prologue
#ACTIVETBL(Table_11) // Activate this block's starting TABLE
#JMP($1CAC2, $1CC22) // Jump to insertion point
#HDR($14010) // Difference between ROM and RAM addresses for pointer value calculations
//POINTER #0 @ $1CAB1 - STRING #0 @ $1CAC2
#W16($1CAB1)
//
// PROLOGUE
//[line]
//[line]
// Many years ago a young
// warrior who was of the
// line of the great
// Erdrick came to Alefgard
// and defeated the dreaded
// Dragonlord, restoring
// peace to the land.
//[line]
//[line]
// For many generations
// the descendants of that
// warrior ruled Alefgard
// and the surrounding
// lands, including the
// Kingdom of Moonbrooke
// across the eastern sea
// from Alefgard.[end]
PROLOGUE
[line]
[line]
Many years ago a young
warrior who was of the
line of the great
Erdrick came to Alefgard
and defeated the dreaded
Dragonlord, restoring
peace to the land.
[line]
[line]
For many generations
the descendants of that
warrior ruled Alefgard
and the surrounding
lands, including the
Kingdom of Moonbrooke
across the eastern sea
from Alefgard.[end]
// current address: $1CC23
// etc., etc.

View File

@ -0,0 +1,9 @@
@if not exist "Dragon Warrior II (U) [!].nes" (
echo ROM file "Dragon Warrior II (U) [!].nes" not found; place one here before running this script.
@goto done
)
perl ..\..\..\abcde.pl -cm abcde::Cartographer "Dragon Warrior II (U) [!].nes" Cartographer.txt Cartographer_out -s
:done
@pause

View File

@ -0,0 +1,8 @@
#!/bin/sh
if [ ! -e "Dragon Warrior II (U) [!].nes" ]; then
echo "ROM file \"Dragon Warrior II (U) [!].nes\" not found; place one here before running this script.";
exit 1;
fi;
perl ../../../abcde.pl -cm abcde::Cartographer "Dragon Warrior II (U) [!].nes" Cartographer.txt Cartographer_out -s

View File

@ -0,0 +1,370 @@
#GAME NAME: Dragon Warrior II (U) [!].nes
// list of additional table files switched into from some block's #TABLE:
#SUB TABLE: credits.tbl
#SUB TABLE: cursor.tbl
#SUB TABLE: heights.tbl
#SUB TABLE: menu.tbl
#SUB TABLE: menu_links.tbl
#SUB TABLE: pos.tbl
#SUB TABLE: pos2.tbl
#SUB TABLE: widths.tbl
// if you need to edit it, the dictionary for the main script is at $B49B-$B686, with the entry lengths stored at $B44B-$B49A
#BLOCK NAME: Main Script Part 1
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $B762
#POINTER TABLE STOP: $B7BD
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 16
#AUTO JUMP START: $17FE7
#AUTO JUMP STOP: $B7C2
#ATLAS PTRS: Yes
#BASE POINTER: $C010
#TABLE: dw2_script.tbl
#COMMENTS: Both
#END BLOCK
// bank swap
#BLOCK NAME: Main Script Part 2
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $B7BE
#POINTER TABLE STOP: $B7C1
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 16
#ATLAS PTRS: Yes
#BASE POINTER: $10
#TABLE: dw2_script.tbl
#COMMENTS: Both
#END BLOCK
// Note that the original game uses " DETOXICATE " instead of " DETOXICATE[line]" and abcde will combine DETOXICATE's trailing space with " UNCURSE[line]"'s leading space into a single " " token (<$82> instead of <$81><$81>), but DW2's menu code doesn't handle those the same way when they terminate a menu line, so you'll want to edit that line before re-inserting it.
#BLOCK NAME: Menus
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $7652
#POINTER TABLE STOP: $76E5
#POINTER SIZE: $02
#POINTER SPACE: $00
#SCRIPT STOP: $7F20
#SORT OUTPUT BY STRING ADDRESS: Yes
#STRINGS END AT NEXT POINTER: Yes
#ATLAS PTRS: Yes
#BASE POINTER: -$3FF0
#TABLE: menu_params.tbl
#COMMENTS: Both
#END BLOCK
// pointer points 1 byte before data, relying on X > 0; string length is set at $3EDBE
#BLOCK NAME: "ADVENTURE LOG", stored backwards
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $3EDC4
#POINTER TABLE STOP: $3EDC5
#POINTER SIZE: $02
#POINTER SPACE: $00
#SCRIPT STOP: $3EDDC
#ATLAS PTRS: Yes
#BASE POINTER: $30011
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Prologue
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1CAB1
#POINTER TABLE STOP: $1CAB2
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $14010
#TABLE: spacer.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: End Credits
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $242AE
#POINTER TABLE STOP: $242AF
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $1C010
//#SCRIPT STOP: $247D5
#TABLE: credits_spacer.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Monster Counts
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $BF0A
#POINTER TABLE STOP: $BF0B
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 8
#ATLAS PTRS: Yes
#BASE POINTER: $10
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Direction Names
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1925E
#POINTER TABLE STOP: $1925F
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 4
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Prince of Cannock Names
#TYPE: FIXED_STRING
#STRING LENGTH: 64
#STRING END: No
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1AD13
#POINTER TABLE STOP: $1AD14
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Princess of Moonbrooke Names
#TYPE: FIXED_STRING
#STRING LENGTH: 64
#STRING END: No
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1AD31
#POINTER TABLE STOP: $1AD32
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Items 1 Line 1
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18032
#POINTER TABLE STOP: $18033
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Items 2 Line 1
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18040
#POINTER TABLE STOP: $18041
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Items 1 Line 2
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18034
#POINTER TABLE STOP: $18035
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Items 2 Line 2
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18042
#POINTER TABLE STOP: $18043
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Spells
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18036
#POINTER TABLE STOP: $18037
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 32
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
// pluralization code: 0x01C805 - 0x01C8F1
// -ngo -> -ngo
// -f -> -ves (not used)
// -y -> -ies
// -i -> -ies
// -rus -> -rii, -s -> -ses
// -ch -> -ches, -sh -> -shes
// -man -> -men, -Man -> -Men
// -mouse -> -mice, -Mouse -> -Mice
// -dead -> -dead
// - -> -s
#BLOCK NAME: Monsters 1 Line 1
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18038
#POINTER TABLE STOP: $18039
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 50
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Monsters 2 Line 1
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18044
#POINTER TABLE STOP: $18045
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 33
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Monsters 1 Line 2
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1803A
#POINTER TABLE STOP: $1803B
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 50
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Monsters 2 Line 2
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $18046
#POINTER TABLE STOP: $18047
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 33
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
#BLOCK NAME: Crests
#TYPE: NORMAL
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $19C36
#POINTER TABLE STOP: $19C37
#POINTER SIZE: $02
#POINTER SPACE: $00
#STRINGS PER POINTER: 3
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
// stuff used for redrawing sections of the main COMMAND menu when switching between EQUIP windows
#BLOCK NAME: COMMAND menu partial redraw (the actually used version)
#TYPE: FIXED_STRING && FIXED_LINE
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $3F90D
#POINTER TABLE STOP: $3F90E
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $30010
#STRING LENGTH: $40
#STRING END: No
#LINE LENGTH: 4
#LINE END: No
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK
// same as above, but called during battle where there is no COMMAND menu; completely useless?
#BLOCK NAME: COMMAND menu partial redraw (the useless version)
#TYPE: FIXED_STRING && FIXED_LINE
#METHOD: POINTER_RELATIVE
#POINTER ENDIAN: LITTLE
#POINTER TABLE START: $1803C
#POINTER TABLE STOP: $1803D
#POINTER SIZE: $02
#POINTER SPACE: $00
#ATLAS PTRS: Yes
#BASE POINTER: $10010
#STRING LENGTH: $40
#STRING END: No
#LINE LENGTH: 4
#LINE END: No
#TABLE: dw2.tbl
#COMMENTS: Both
#END BLOCK

View File

@ -0,0 +1,60 @@
@credits
00=A
01=B
02=C
03=D
04=E
05=F
06=G
07=H
08=I
09=J
0A=K
0B=L
0C=M
0D=N
0E=O
0F=P
10=Q
11=R
12=S
13=T
14=U
15=V
16=W
17=X
18=Y
19=Z
1A=0
1B=1
1C=2
1D=3
1E=4
1F=5
20=6
21=7
22=8
23=9
24252627=[CHUN]
28=-
29=[right triangle]
2A=©
2B2C2D=[enix logo line 1]
2E2F3031=[enix logo line 2]
32333435=[enix logo line 3]
36373839=[enix logo line 4]
3A3B3C3D=[enix logo line 5]
5F=
6B=.
6C=&
6E=?
6F=!
!FF00=<[palette for preceeding line: green]>,-1
!FF01=<[palette for preceeding line: orange]>,-1
!FF02=<[palette for preceeding line: pink]>,-1
!FF03=<[palette for preceeding line: blue]>,-1
!FFFF=<[palette for preceeding line: specified by next 16 bytes:]>,16,-1

View File

@ -0,0 +1,27 @@
!01=<\n >,<@credits>:0
!02=<\n >,<@credits>:0
!03=<\n >,<@credits>:0
!04=<\n >,<@credits>:0
!05=<\n >,<@credits>:0
!06=<\n >,<@credits>:0
!07=<\n >,<@credits>:0
!08=<\n >,<@credits>:0
!09=<\n >,<@credits>:0
!0A=<\n >,<@credits>:0
!0B=<\n >,<@credits>:0
!0C=<\n >,<@credits>:0
!0D=<\n >,<@credits>:0
!0E=<\n >,<@credits>:0
!0F=<\n >,<@credits>:0
82=\n[line]\n[line]\n[line]
83=\n[line]\n[line]\n[line]\n[line]
84=\n[line]\n[line]\n[line]\n[line]\n[line]
85=\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]
86=\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]
87=\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]
88=\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]
/8EFF=[end]\n
D2=\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]\n[line]

View File

@ -0,0 +1,257 @@
@cursor
00=[cursor left offset:0 tiles]\n
01=[cursor left offset:1 tiles]\n
02=[cursor left offset:2 tiles]\n
03=[cursor left offset:3 tiles]\n
04=[cursor left offset:4 tiles]\n
05=[cursor left offset:5 tiles]\n
06=[cursor left offset:6 tiles]\n
07=[cursor left offset:7 tiles]\n
08=[cursor left offset:8 tiles]\n
09=[cursor left offset:9 tiles]\n
0A=[cursor left offset:10 tiles]\n
0B=[cursor left offset:11 tiles]\n
0C=[cursor left offset:12 tiles]\n
0D=[cursor left offset:13 tiles]\n
0E=[cursor left offset:14 tiles]\n
0F=[cursor left offset:15 tiles]\n
10=[cursor left offset:16 tiles]\n
11=[cursor left offset:17 tiles]\n
12=[cursor left offset:18 tiles]\n
13=[cursor left offset:19 tiles]\n
14=[cursor left offset:20 tiles]\n
15=[cursor left offset:21 tiles]\n
16=[cursor left offset:22 tiles]\n
17=[cursor left offset:23 tiles]\n
18=[cursor left offset:24 tiles]\n
19=[cursor left offset:25 tiles]\n
1A=[cursor left offset:26 tiles]\n
1B=[cursor left offset:27 tiles]\n
1C=[cursor left offset:28 tiles]\n
1D=[cursor left offset:29 tiles]\n
1E=[cursor left offset:30 tiles]\n
1F=[cursor left offset:31 tiles]\n
20=[cursor left offset:32 tiles]\n
21=[cursor left offset:33 tiles]\n
22=[cursor left offset:34 tiles]\n
23=[cursor left offset:35 tiles]\n
24=[cursor left offset:36 tiles]\n
25=[cursor left offset:37 tiles]\n
26=[cursor left offset:38 tiles]\n
27=[cursor left offset:39 tiles]\n
28=[cursor left offset:40 tiles]\n
29=[cursor left offset:41 tiles]\n
2A=[cursor left offset:42 tiles]\n
2B=[cursor left offset:43 tiles]\n
2C=[cursor left offset:44 tiles]\n
2D=[cursor left offset:45 tiles]\n
2E=[cursor left offset:46 tiles]\n
2F=[cursor left offset:47 tiles]\n
30=[cursor left offset:48 tiles]\n
31=[cursor left offset:49 tiles]\n
32=[cursor left offset:50 tiles]\n
33=[cursor left offset:51 tiles]\n
34=[cursor left offset:52 tiles]\n
35=[cursor left offset:53 tiles]\n
36=[cursor left offset:54 tiles]\n
37=[cursor left offset:55 tiles]\n
38=[cursor left offset:56 tiles]\n
39=[cursor left offset:57 tiles]\n
3A=[cursor left offset:58 tiles]\n
3B=[cursor left offset:59 tiles]\n
3C=[cursor left offset:60 tiles]\n
3D=[cursor left offset:61 tiles]\n
3E=[cursor left offset:62 tiles]\n
3F=[cursor left offset:63 tiles]\n
40=[cursor left offset:64 tiles]\n
41=[cursor left offset:65 tiles]\n
42=[cursor left offset:66 tiles]\n
43=[cursor left offset:67 tiles]\n
44=[cursor left offset:68 tiles]\n
45=[cursor left offset:69 tiles]\n
46=[cursor left offset:70 tiles]\n
47=[cursor left offset:71 tiles]\n
48=[cursor left offset:72 tiles]\n
49=[cursor left offset:73 tiles]\n
4A=[cursor left offset:74 tiles]\n
4B=[cursor left offset:75 tiles]\n
4C=[cursor left offset:76 tiles]\n
4D=[cursor left offset:77 tiles]\n
4E=[cursor left offset:78 tiles]\n
4F=[cursor left offset:79 tiles]\n
50=[cursor left offset:80 tiles]\n
51=[cursor left offset:81 tiles]\n
52=[cursor left offset:82 tiles]\n
53=[cursor left offset:83 tiles]\n
54=[cursor left offset:84 tiles]\n
55=[cursor left offset:85 tiles]\n
56=[cursor left offset:86 tiles]\n
57=[cursor left offset:87 tiles]\n
58=[cursor left offset:88 tiles]\n
59=[cursor left offset:89 tiles]\n
5A=[cursor left offset:90 tiles]\n
5B=[cursor left offset:91 tiles]\n
5C=[cursor left offset:92 tiles]\n
5D=[cursor left offset:93 tiles]\n
5E=[cursor left offset:94 tiles]\n
5F=[cursor left offset:95 tiles]\n
60=[cursor left offset:96 tiles]\n
61=[cursor left offset:97 tiles]\n
62=[cursor left offset:98 tiles]\n
63=[cursor left offset:99 tiles]\n
64=[cursor left offset:100 tiles]\n
65=[cursor left offset:101 tiles]\n
66=[cursor left offset:102 tiles]\n
67=[cursor left offset:103 tiles]\n
68=[cursor left offset:104 tiles]\n
69=[cursor left offset:105 tiles]\n
6A=[cursor left offset:106 tiles]\n
6B=[cursor left offset:107 tiles]\n
6C=[cursor left offset:108 tiles]\n
6D=[cursor left offset:109 tiles]\n
6E=[cursor left offset:110 tiles]\n
6F=[cursor left offset:111 tiles]\n
70=[cursor left offset:112 tiles]\n
71=[cursor left offset:113 tiles]\n
72=[cursor left offset:114 tiles]\n
73=[cursor left offset:115 tiles]\n
74=[cursor left offset:116 tiles]\n
75=[cursor left offset:117 tiles]\n
76=[cursor left offset:118 tiles]\n
77=[cursor left offset:119 tiles]\n
78=[cursor left offset:120 tiles]\n
79=[cursor left offset:121 tiles]\n
7A=[cursor left offset:122 tiles]\n
7B=[cursor left offset:123 tiles]\n
7C=[cursor left offset:124 tiles]\n
7D=[cursor left offset:125 tiles]\n
7E=[cursor left offset:126 tiles]\n
7F=[cursor left offset:127 tiles]\n
80=[cursor left offset:128 tiles]\n
81=[cursor left offset:129 tiles]\n
82=[cursor left offset:130 tiles]\n
83=[cursor left offset:131 tiles]\n
84=[cursor left offset:132 tiles]\n
85=[cursor left offset:133 tiles]\n
86=[cursor left offset:134 tiles]\n
87=[cursor left offset:135 tiles]\n
88=[cursor left offset:136 tiles]\n
89=[cursor left offset:137 tiles]\n
8A=[cursor left offset:138 tiles]\n
8B=[cursor left offset:139 tiles]\n
8C=[cursor left offset:140 tiles]\n
8D=[cursor left offset:141 tiles]\n
8E=[cursor left offset:142 tiles]\n
8F=[cursor left offset:143 tiles]\n
90=[cursor left offset:144 tiles]\n
91=[cursor left offset:145 tiles]\n
92=[cursor left offset:146 tiles]\n
93=[cursor left offset:147 tiles]\n
94=[cursor left offset:148 tiles]\n
95=[cursor left offset:149 tiles]\n
96=[cursor left offset:150 tiles]\n
97=[cursor left offset:151 tiles]\n
98=[cursor left offset:152 tiles]\n
99=[cursor left offset:153 tiles]\n
9A=[cursor left offset:154 tiles]\n
9B=[cursor left offset:155 tiles]\n
9C=[cursor left offset:156 tiles]\n
9D=[cursor left offset:157 tiles]\n
9E=[cursor left offset:158 tiles]\n
9F=[cursor left offset:159 tiles]\n
A0=[cursor left offset:160 tiles]\n
A1=[cursor left offset:161 tiles]\n
A2=[cursor left offset:162 tiles]\n
A3=[cursor left offset:163 tiles]\n
A4=[cursor left offset:164 tiles]\n
A5=[cursor left offset:165 tiles]\n
A6=[cursor left offset:166 tiles]\n
A7=[cursor left offset:167 tiles]\n
A8=[cursor left offset:168 tiles]\n
A9=[cursor left offset:169 tiles]\n
AA=[cursor left offset:170 tiles]\n
AB=[cursor left offset:171 tiles]\n
AC=[cursor left offset:172 tiles]\n
AD=[cursor left offset:173 tiles]\n
AE=[cursor left offset:174 tiles]\n
AF=[cursor left offset:175 tiles]\n
B0=[cursor left offset:176 tiles]\n
B1=[cursor left offset:177 tiles]\n
B2=[cursor left offset:178 tiles]\n
B3=[cursor left offset:179 tiles]\n
B4=[cursor left offset:180 tiles]\n
B5=[cursor left offset:181 tiles]\n
B6=[cursor left offset:182 tiles]\n
B7=[cursor left offset:183 tiles]\n
B8=[cursor left offset:184 tiles]\n
B9=[cursor left offset:185 tiles]\n
BA=[cursor left offset:186 tiles]\n
BB=[cursor left offset:187 tiles]\n
BC=[cursor left offset:188 tiles]\n
BD=[cursor left offset:189 tiles]\n
BE=[cursor left offset:190 tiles]\n
BF=[cursor left offset:191 tiles]\n
C0=[cursor left offset:192 tiles]\n
C1=[cursor left offset:193 tiles]\n
C2=[cursor left offset:194 tiles]\n
C3=[cursor left offset:195 tiles]\n
C4=[cursor left offset:196 tiles]\n
C5=[cursor left offset:197 tiles]\n
C6=[cursor left offset:198 tiles]\n
C7=[cursor left offset:199 tiles]\n
C8=[cursor left offset:200 tiles]\n
C9=[cursor left offset:201 tiles]\n
CA=[cursor left offset:202 tiles]\n
CB=[cursor left offset:203 tiles]\n
CC=[cursor left offset:204 tiles]\n
CD=[cursor left offset:205 tiles]\n
CE=[cursor left offset:206 tiles]\n
CF=[cursor left offset:207 tiles]\n
D0=[cursor left offset:208 tiles]\n
D1=[cursor left offset:209 tiles]\n
D2=[cursor left offset:210 tiles]\n
D3=[cursor left offset:211 tiles]\n
D4=[cursor left offset:212 tiles]\n
D5=[cursor left offset:213 tiles]\n
D6=[cursor left offset:214 tiles]\n
D7=[cursor left offset:215 tiles]\n
D8=[cursor left offset:216 tiles]\n
D9=[cursor left offset:217 tiles]\n
DA=[cursor left offset:218 tiles]\n
DB=[cursor left offset:219 tiles]\n
DC=[cursor left offset:220 tiles]\n
DD=[cursor left offset:221 tiles]\n
DE=[cursor left offset:222 tiles]\n
DF=[cursor left offset:223 tiles]\n
E0=[cursor left offset:224 tiles]\n
E1=[cursor left offset:225 tiles]\n
E2=[cursor left offset:226 tiles]\n
E3=[cursor left offset:227 tiles]\n
E4=[cursor left offset:228 tiles]\n
E5=[cursor left offset:229 tiles]\n
E6=[cursor left offset:230 tiles]\n
E7=[cursor left offset:231 tiles]\n
E8=[cursor left offset:232 tiles]\n
E9=[cursor left offset:233 tiles]\n
EA=[cursor left offset:234 tiles]\n
EB=[cursor left offset:235 tiles]\n
EC=[cursor left offset:236 tiles]\n
ED=[cursor left offset:237 tiles]\n
EE=[cursor left offset:238 tiles]\n
EF=[cursor left offset:239 tiles]\n
F0=[cursor left offset:240 tiles]\n
F1=[cursor left offset:241 tiles]\n
F2=[cursor left offset:242 tiles]\n
F3=[cursor left offset:243 tiles]\n
F4=[cursor left offset:244 tiles]\n
F5=[cursor left offset:245 tiles]\n
F6=[cursor left offset:246 tiles]\n
F7=[cursor left offset:247 tiles]\n
F8=[cursor left offset:248 tiles]\n
F9=[cursor left offset:249 tiles]\n
FA=[cursor left offset:250 tiles]\n
FB=[cursor left offset:251 tiles]\n
FC=[cursor left offset:252 tiles]\n
FD=[cursor left offset:253 tiles]\n
FE=[cursor left offset:254 tiles]\n
FF=[cursor left offset:255 tiles]\n

View File

@ -0,0 +1,149 @@
@main
00=0
01=1
02=2
03=3
04=4
05=5
06=6
07=7
08=8
09=9
0A=a
0B=b
0C=c
0D=d
0E=e
0F=f
10=g
11=h
12=i
13=j
14=k
15=l
16=m
17=n
18=o
19=p
1A=q
1B=r
1C=s
1D=t
1E=u
1F=v
20=w
21=x
22=y
23=z
24=A
25=B
26=C
27=D
28=E
29=F
2A=G
2B=H
2C=I
2D=J
2E=K
2F=L
30=M
31=N
32=O
33=P
34=Q
35=R
36=S
37=T
38=U
39=V
3A=W
3B=X
3C=Y
3D=Z
3E=/a
3F=/b
40=/c
41=/d
42=/e
43=/f
44=/g
45=/h
46=/i
47=/j
48=/k
49=/l
4A=/m
4B=/n
4C=/o
4D=/p
4E=/q
4F=/r
50=/s
51=/t
52=/u
53=/v
54=/w
55=/x
56=/y
57=/z
58=/A
#59=
5A=[sun]
5B=[star]
5C=[moon]
5D=[water]
5E=[heart]
5F=
60=[no voice]
#61=
62=”
63=[right arrow]
64=‟
65=
# 66 and 67 are visually identical, but 66 is used as a right single quotation mark while 67 is used as an apostrophe
66=
67='
68=[.]
69=,
6A=-
6B=.
6C=&
#6D=
6E=?
6F=!
70=;
71=[ ]
72=[right triangle]
73=[down triangle]
74=:
75=[..]
76=[left border]
77=[top border-77]
78=[top border-78]
79=[top-left border]
7A=[bottom-left border]
7B=[right border]
7C=[top-right border]
7D=[bottom border]
7E=[bottom-right border]
C0=[switch to C0 table]
C1=[switch to C1 table]
C2=[switch to C2 table]
C3=[switch to C3 table]
F2=[(s)]
F3=[monster(s)]
F4=[cardinal #]
F5=[number]
F6=[spell]
F7=[item]
F8=[name]
F9=[item-F9]
/FA=[end-FA]\n
FB=[wait]
/FC=[end-FC]\n
FD=[FD]
FE=[line]\n
/FF=[end-FF]\n

View File

@ -0,0 +1,173 @@
/%00000=[end-FC]\n\n
/%00001=.[end-FC]\n\n
/%00010=?[FD][FD][end-FC]\n\n
/%00011=[.][end-FC]\n\n
# 00100 is an intermediate end token, used to subdivide larger strings where the same function needs to be called multiple times with different values.
# E.g. in
# [name] [end-FF]threw away [name]'s [item] and gave [end-FF]the [item] to ghost of [name].[end-FC]
# the first [name] is the Prince of Midenhall's name, but the second and third [name] are the name of the dead party member whose items you are ransacking;
# similarly, the first [item] is the item you lose, but the second [item] is the item you gain.
/%00100=[end-FF]\n\n
%00101=y
%00110=c
%00111=o
%01000=d
%01001=e
%01010=f
%01011=g
%01100=h
%01101=i
%01110=j
%01111=
%10000=l
%10001=m
%10010=n
%10011=[line]\n
%10100=[.]
%10101=
%10110=r
%10111=s
%11000=t
%11001=u
%11010=a
%11011=w
%11100=[switch to C0 table]
%11101=[switch to C1 table]
%11110=[switch to C2 table]
%11111=[switch to C3 table]
# C0 table
%1110000000=A
%1110000001=B
%1110000010=Ca
%1110000011=D
%1110000100=E
%1110000101=F
%1110000110=G
%1110000111=H
%1110001000=I
%1110001001=J
%1110001010=King
%1110001011=L
%1110001100=Moonbrooke
%1110001101=N
%1110001110=O
%1110001111=[item]
%1110010000=The
%1110010001=Rhone
%1110010010=S
%1110010011=;
%1110010100=U
%1110010101=”
%1110010110=Water Flying Cl
%1110010111=C
%1110011000=Y
%1110011001=Z
%1110011010=x
%1110011011=Village
%1110011100=z
%1110011101=[item-F9]
%1110011110=‟
%1110011111=K
# C1 table
%1110100000=v
%1110100001=q
%1110100010=[wait][line]\n
%1110100011=R
%1110100100=.
%1110100101=[FD][FD]
%1110100110=P
%1110100111=b
%1110101000=T
%1110101001=!
%1110101010=[sun]
%1110101011=[star]
%1110101100=[moon]
%1110101101=W
%1110101110=k
%1110101111=p
%1110110000=?
%1110110001=,
%1110110010=[cardinal #]
%1110110011=[..][..]
%1110110100=:
%1110110101='
%1110110110=-
%1110110111=
%1110111000=[spell]
%1110111001=[monster(s)]
%1110111010=[no voice]
%1110111011=[wait]
%1110111100=M
%1110111101=[name]
%1110111110=[number]
%1110111111=[FD]
# C2 table
%1111000000=Thou hast
%1111000001=hest
%1111000010=Midenhall
%1111000011=hou
%1111000100= of
%1111000101= is
%1111000110= thou has
%1111000111= and
%1111001000=to th
%1111001001= thee
%1111001010=ast
%1111001011= do
%1111001100=hat
%1111001101= shall
%1111001110= was
%1111001111=hou has
%1111010000=d the
%1111010001= has
%1111010010=gon
%1111010011=.[wait][line]\n
%1111010100= have
%1111010101=come to
%1111010110=ing
%1111010111= hast
%1111011000=ost thou
%1111011001=this
%1111011010= of the
%1111011011=Hargon
%1111011100=in the
%1111011101=thing
%1111011110=he
%1111011111= with
# C3 table
%1111100000=reasure
%1111100001=Hast
%1111100010=Erdrick
%1111100011=come
%1111100100=ere is
%1111100101=Welcome
%1111100110=rince
%1111100111= great
%1111101000=arr
%1111101001= for th
%1111101010=piece[(s)] of gold
%1111101011=[.][wait][line]\n
%1111101100=But
%1111101101=here
%1111101110=can
%1111101111=ove
%1111110000=hee
%1111110001=not
%1111110010=for
%1111110011=one
%1111110100= any
%1111110101= to
%1111110110=descendant
%1111110111=Roge Fastfinger
%1111111000=all
%1111111001=thy
%1111111010=W
%1111111011=thank thee
%1111111100= it
%1111111101= tha
%1111111110= thou
%1111111111= the

View File

@ -0,0 +1,257 @@
@heights
00=[height:0 tiles]\n
01=[height:2 tiles]\n
02=[height:4 tiles]\n
03=[height:6 tiles]\n
04=[height:8 tiles]\n
05=[height:10 tiles]\n
06=[height:12 tiles]\n
07=[height:14 tiles]\n
08=[height:16 tiles]\n
09=[height:18 tiles]\n
0A=[height:20 tiles]\n
0B=[height:22 tiles]\n
0C=[height:24 tiles]\n
0D=[height:26 tiles]\n
0E=[height:28 tiles]\n
0F=[height:30 tiles]\n
10=[height:32 tiles]\n
11=[height:34 tiles]\n
12=[height:36 tiles]\n
13=[height:38 tiles]\n
14=[height:40 tiles]\n
15=[height:42 tiles]\n
16=[height:44 tiles]\n
17=[height:46 tiles]\n
18=[height:48 tiles]\n
19=[height:50 tiles]\n
1A=[height:52 tiles]\n
1B=[height:54 tiles]\n
1C=[height:56 tiles]\n
1D=[height:58 tiles]\n
1E=[height:60 tiles]\n
1F=[height:62 tiles]\n
20=[height:64 tiles]\n
21=[height:66 tiles]\n
22=[height:68 tiles]\n
23=[height:70 tiles]\n
24=[height:72 tiles]\n
25=[height:74 tiles]\n
26=[height:76 tiles]\n
27=[height:78 tiles]\n
28=[height:80 tiles]\n
29=[height:82 tiles]\n
2A=[height:84 tiles]\n
2B=[height:86 tiles]\n
2C=[height:88 tiles]\n
2D=[height:90 tiles]\n
2E=[height:92 tiles]\n
2F=[height:94 tiles]\n
30=[height:96 tiles]\n
31=[height:98 tiles]\n
32=[height:100 tiles]\n
33=[height:102 tiles]\n
34=[height:104 tiles]\n
35=[height:106 tiles]\n
36=[height:108 tiles]\n
37=[height:110 tiles]\n
38=[height:112 tiles]\n
39=[height:114 tiles]\n
3A=[height:116 tiles]\n
3B=[height:118 tiles]\n
3C=[height:120 tiles]\n
3D=[height:122 tiles]\n
3E=[height:124 tiles]\n
3F=[height:126 tiles]\n
40=[height:128 tiles]\n
41=[height:130 tiles]\n
42=[height:132 tiles]\n
43=[height:134 tiles]\n
44=[height:136 tiles]\n
45=[height:138 tiles]\n
46=[height:140 tiles]\n
47=[height:142 tiles]\n
48=[height:144 tiles]\n
49=[height:146 tiles]\n
4A=[height:148 tiles]\n
4B=[height:150 tiles]\n
4C=[height:152 tiles]\n
4D=[height:154 tiles]\n
4E=[height:156 tiles]\n
4F=[height:158 tiles]\n
50=[height:160 tiles]\n
51=[height:162 tiles]\n
52=[height:164 tiles]\n
53=[height:166 tiles]\n
54=[height:168 tiles]\n
55=[height:170 tiles]\n
56=[height:172 tiles]\n
57=[height:174 tiles]\n
58=[height:176 tiles]\n
59=[height:178 tiles]\n
5A=[height:180 tiles]\n
5B=[height:182 tiles]\n
5C=[height:184 tiles]\n
5D=[height:186 tiles]\n
5E=[height:188 tiles]\n
5F=[height:190 tiles]\n
60=[height:192 tiles]\n
61=[height:194 tiles]\n
62=[height:196 tiles]\n
63=[height:198 tiles]\n
64=[height:200 tiles]\n
65=[height:202 tiles]\n
66=[height:204 tiles]\n
67=[height:206 tiles]\n
68=[height:208 tiles]\n
69=[height:210 tiles]\n
6A=[height:212 tiles]\n
6B=[height:214 tiles]\n
6C=[height:216 tiles]\n
6D=[height:218 tiles]\n
6E=[height:220 tiles]\n
6F=[height:222 tiles]\n
70=[height:224 tiles]\n
71=[height:226 tiles]\n
72=[height:228 tiles]\n
73=[height:230 tiles]\n
74=[height:232 tiles]\n
75=[height:234 tiles]\n
76=[height:236 tiles]\n
77=[height:238 tiles]\n
78=[height:240 tiles]\n
79=[height:242 tiles]\n
7A=[height:244 tiles]\n
7B=[height:246 tiles]\n
7C=[height:248 tiles]\n
7D=[height:250 tiles]\n
7E=[height:252 tiles]\n
7F=[height:254 tiles]\n
80=[height:256 tiles]\n
81=[height:258 tiles]\n
82=[height:260 tiles]\n
83=[height:262 tiles]\n
84=[height:264 tiles]\n
85=[height:266 tiles]\n
86=[height:268 tiles]\n
87=[height:270 tiles]\n
88=[height:272 tiles]\n
89=[height:274 tiles]\n
8A=[height:276 tiles]\n
8B=[height:278 tiles]\n
8C=[height:280 tiles]\n
8D=[height:282 tiles]\n
8E=[height:284 tiles]\n
8F=[height:286 tiles]\n
90=[height:288 tiles]\n
91=[height:290 tiles]\n
92=[height:292 tiles]\n
93=[height:294 tiles]\n
94=[height:296 tiles]\n
95=[height:298 tiles]\n
96=[height:300 tiles]\n
97=[height:302 tiles]\n
98=[height:304 tiles]\n
99=[height:306 tiles]\n
9A=[height:308 tiles]\n
9B=[height:310 tiles]\n
9C=[height:312 tiles]\n
9D=[height:314 tiles]\n
9E=[height:316 tiles]\n
9F=[height:318 tiles]\n
A0=[height:320 tiles]\n
A1=[height:322 tiles]\n
A2=[height:324 tiles]\n
A3=[height:326 tiles]\n
A4=[height:328 tiles]\n
A5=[height:330 tiles]\n
A6=[height:332 tiles]\n
A7=[height:334 tiles]\n
A8=[height:336 tiles]\n
A9=[height:338 tiles]\n
AA=[height:340 tiles]\n
AB=[height:342 tiles]\n
AC=[height:344 tiles]\n
AD=[height:346 tiles]\n
AE=[height:348 tiles]\n
AF=[height:350 tiles]\n
B0=[height:352 tiles]\n
B1=[height:354 tiles]\n
B2=[height:356 tiles]\n
B3=[height:358 tiles]\n
B4=[height:360 tiles]\n
B5=[height:362 tiles]\n
B6=[height:364 tiles]\n
B7=[height:366 tiles]\n
B8=[height:368 tiles]\n
B9=[height:370 tiles]\n
BA=[height:372 tiles]\n
BB=[height:374 tiles]\n
BC=[height:376 tiles]\n
BD=[height:378 tiles]\n
BE=[height:380 tiles]\n
BF=[height:382 tiles]\n
C0=[height:384 tiles]\n
C1=[height:386 tiles]\n
C2=[height:388 tiles]\n
C3=[height:390 tiles]\n
C4=[height:392 tiles]\n
C5=[height:394 tiles]\n
C6=[height:396 tiles]\n
C7=[height:398 tiles]\n
C8=[height:400 tiles]\n
C9=[height:402 tiles]\n
CA=[height:404 tiles]\n
CB=[height:406 tiles]\n
CC=[height:408 tiles]\n
CD=[height:410 tiles]\n
CE=[height:412 tiles]\n
CF=[height:414 tiles]\n
D0=[height:416 tiles]\n
D1=[height:418 tiles]\n
D2=[height:420 tiles]\n
D3=[height:422 tiles]\n
D4=[height:424 tiles]\n
D5=[height:426 tiles]\n
D6=[height:428 tiles]\n
D7=[height:430 tiles]\n
D8=[height:432 tiles]\n
D9=[height:434 tiles]\n
DA=[height:436 tiles]\n
DB=[height:438 tiles]\n
DC=[height:440 tiles]\n
DD=[height:442 tiles]\n
DE=[height:444 tiles]\n
DF=[height:446 tiles]\n
E0=[height:448 tiles]\n
E1=[height:450 tiles]\n
E2=[height:452 tiles]\n
E3=[height:454 tiles]\n
E4=[height:456 tiles]\n
E5=[height:458 tiles]\n
E6=[height:460 tiles]\n
E7=[height:462 tiles]\n
E8=[height:464 tiles]\n
E9=[height:466 tiles]\n
EA=[height:468 tiles]\n
EB=[height:470 tiles]\n
EC=[height:472 tiles]\n
ED=[height:474 tiles]\n
EE=[height:476 tiles]\n
EF=[height:478 tiles]\n
F0=[height:480 tiles]\n
F1=[height:482 tiles]\n
F2=[height:484 tiles]\n
F3=[height:486 tiles]\n
F4=[height:488 tiles]\n
F5=[height:490 tiles]\n
F6=[height:492 tiles]\n
F7=[height:494 tiles]\n
F8=[height:496 tiles]\n
F9=[height:498 tiles]\n
FA=[height:500 tiles]\n
FB=[height:502 tiles]\n
FC=[height:504 tiles]\n
FD=[height:506 tiles]\n
FE=[height:508 tiles]\n
FF=[height:dynamic]\n

View File

@ -0,0 +1,194 @@
@menu
00=0
01=1
02=2
03=3
04=4
05=5
06=6
07=7
08=8
09=9
0A=a
0B=b
0C=c
0D=d
0E=e
0F=f
10=g
11=h
12=i
13=j
14=k
15=l
16=m
17=n
18=o
19=p
1A=q
1B=r
1C=s
1D=t
1E=u
1F=v
20=w
21=x
22=y
23=z
24=A
25=B
26=C
27=D
28=E
29=F
2A=G
2B=H
2C=I
2D=J
2E=K
2F=L
30=M
31=N
32=O
33=P
34=Q
35=R
36=S
37=T
38=U
39=V
3A=W
3B=X
3C=Y
3D=Z
3E=/a
3F=/b
40=/c
41=/d
42=/e
43=/f
44=/g
45=/h
46=/i
47=/j
48=/k
49=/l
4A=/m
4B=/n
4C=/o
4D=/p
4E=/q
4F=/r
50=/s
51=/t
52=/u
53=/v
54=/w
55=/x
56=/y
57=/z
58=/A
#59=
5A=[sun]
5B=[star]
5C=[moon]
5D=[water]
5E=[heart]
# the menus use 81 instead of 5F despite 5F being faster than 81
#5F=
#60=
#61=
62=”
63=[right arrow]
64=‟
65=
# 66 and 67 are visually identical, but 66 is used as a right single quotation mark while 67 is used as an apostrophe
66=
67='
68=[.]
69=,
6A=-
6B=.
6C=&
#6D=
6E=?
6F=!
70=;
71=[ ]
72=[right triangle]
73=[down triangle]
74=:
75=[..]
80=[line]\n
81=
82=
83=
84=
85=
86=
87=
88=[border line]\n
89=═
8A=══
8B=═══
8C=════
8D=═════
8E=══════
8F=═══════
90=[Midenhall's current HP]
91=[Cannock's current HP]
92=[Moonbrooke's current HP]
93=[current hero's current HP]
94=[Midenhall's current MP]
95=[Cannock's current MP]
96=[Moonbrooke's current MP]
97=[current hero's current MP]
98=[party's gold]
99=[party's crests]
9A=["ADVENTURE LOG"]
A0=[Midenhall's level]
A1=[Cannock's level]
A2=[Moonbrooke's level]
A4=[Midenhall's level, saved game menu]
A8=[Midenhall's EXP]
A9=[Cannock's EXP]
AA=[Moonbrooke's EXP]
AB=[current hero's EXP]
B0=[Midenhall's short name]
B1=[Cannock's short name]
B2=[Moonbrooke's short name]
B3=[current hero's short name]
B4=[current hero's long name]\n
B5=[Midenhall's short name, saved game 1]
B6=[Midenhall's short name, saved game 2]
B7=[Midenhall's short name, saved game 3]
B8=[equipped weapon name, partial]\n
B9=[equipped armour name, partial]\n
BA=[equipped shield name, partial]\n
BB=[equipped helmet name, partial]\n
BC=[item name, partial]
BD=[item name, partial, shop list]
C0=[battle spell]
C1=[out-of-battle spell]
C8=[item price]
D0=[start weapon list]\n
D1=[start armour list]\n
D2=[start shield list]\n
D3=[start helmet list]\n
D4=[start item list]\n
D5=[start shop item list]\n
D6=[start out-of-battle spell list]\n
D7=[start monster list]\n
D8=[strength]\n
D9=[agility]\n
DA=[attack]\n
DB=[defense]\n
DC=[maximum HP]\n
DD=[maximum MP]\n
E0=[monster name, partial]
E1=[monster count]
E8=[repeat list]\n
E9=[end list]\n
EA=[Midenhall's long name, saved game menu]

View File

@ -0,0 +1,257 @@
@menu_links
00=[linked to menu #$00]\n
01=[linked to menu #$01]\n
02=[linked to menu #$02]\n
03=[linked to menu #$03]\n
04=[linked to menu #$04]\n
05=[linked to menu #$05]\n
06=[linked to menu #$06]\n
07=[linked to menu #$07]\n
08=[linked to menu #$08]\n
09=[linked to menu #$09]\n
0A=[linked to menu #$0A]\n
0B=[linked to menu #$0B]\n
0C=[linked to menu #$0C]\n
0D=[linked to menu #$0D]\n
0E=[linked to menu #$0E]\n
0F=[linked to menu #$0F]\n
10=[linked to menu #$10]\n
11=[linked to menu #$11]\n
12=[linked to menu #$12]\n
13=[linked to menu #$13]\n
14=[linked to menu #$14]\n
15=[linked to menu #$15]\n
16=[linked to menu #$16]\n
17=[linked to menu #$17]\n
18=[linked to menu #$18]\n
19=[linked to menu #$19]\n
1A=[linked to menu #$1A]\n
1B=[linked to menu #$1B]\n
1C=[linked to menu #$1C]\n
1D=[linked to menu #$1D]\n
1E=[linked to menu #$1E]\n
1F=[linked to menu #$1F]\n
20=[linked to menu #$20]\n
21=[linked to menu #$21]\n
22=[linked to menu #$22]\n
23=[linked to menu #$23]\n
24=[linked to menu #$24]\n
25=[linked to menu #$25]\n
26=[linked to menu #$26]\n
27=[linked to menu #$27]\n
28=[linked to menu #$28]\n
29=[linked to menu #$29]\n
2A=[linked to menu #$2A]\n
2B=[linked to menu #$2B]\n
2C=[linked to menu #$2C]\n
2D=[linked to menu #$2D]\n
2E=[linked to menu #$2E]\n
2F=[linked to menu #$2F]\n
30=[linked to menu #$30]\n
31=[linked to menu #$31]\n
32=[linked to menu #$32]\n
33=[linked to menu #$33]\n
34=[linked to menu #$34]\n
35=[linked to menu #$35]\n
36=[linked to menu #$36]\n
37=[linked to menu #$37]\n
38=[linked to menu #$38]\n
39=[linked to menu #$39]\n
3A=[linked to menu #$3A]\n
3B=[linked to menu #$3B]\n
3C=[linked to menu #$3C]\n
3D=[linked to menu #$3D]\n
3E=[linked to menu #$3E]\n
3F=[linked to menu #$3F]\n
40=[linked to menu #$40]\n
41=[linked to menu #$41]\n
42=[linked to menu #$42]\n
43=[linked to menu #$43]\n
44=[linked to menu #$44]\n
45=[linked to menu #$45]\n
46=[linked to menu #$46]\n
47=[linked to menu #$47]\n
48=[linked to menu #$48]\n
49=[linked to menu #$49]\n
4A=[linked to menu #$4A]\n
4B=[linked to menu #$4B]\n
4C=[linked to menu #$4C]\n
4D=[linked to menu #$4D]\n
4E=[linked to menu #$4E]\n
4F=[linked to menu #$4F]\n
50=[linked to menu #$50]\n
51=[linked to menu #$51]\n
52=[linked to menu #$52]\n
53=[linked to menu #$53]\n
54=[linked to menu #$54]\n
55=[linked to menu #$55]\n
56=[linked to menu #$56]\n
57=[linked to menu #$57]\n
58=[linked to menu #$58]\n
59=[linked to menu #$59]\n
5A=[linked to menu #$5A]\n
5B=[linked to menu #$5B]\n
5C=[linked to menu #$5C]\n
5D=[linked to menu #$5D]\n
5E=[linked to menu #$5E]\n
5F=[linked to menu #$5F]\n
60=[linked to menu #$60]\n
61=[linked to menu #$61]\n
62=[linked to menu #$62]\n
63=[linked to menu #$63]\n
64=[linked to menu #$64]\n
65=[linked to menu #$65]\n
66=[linked to menu #$66]\n
67=[linked to menu #$67]\n
68=[linked to menu #$68]\n
69=[linked to menu #$69]\n
6A=[linked to menu #$6A]\n
6B=[linked to menu #$6B]\n
6C=[linked to menu #$6C]\n
6D=[linked to menu #$6D]\n
6E=[linked to menu #$6E]\n
6F=[linked to menu #$6F]\n
70=[linked to menu #$70]\n
71=[linked to menu #$71]\n
72=[linked to menu #$72]\n
73=[linked to menu #$73]\n
74=[linked to menu #$74]\n
75=[linked to menu #$75]\n
76=[linked to menu #$76]\n
77=[linked to menu #$77]\n
78=[linked to menu #$78]\n
79=[linked to menu #$79]\n
7A=[linked to menu #$7A]\n
7B=[linked to menu #$7B]\n
7C=[linked to menu #$7C]\n
7D=[linked to menu #$7D]\n
7E=[linked to menu #$7E]\n
7F=[linked to menu #$7F]\n
80=[linked to menu #$80]\n
81=[linked to menu #$81]\n
82=[linked to menu #$82]\n
83=[linked to menu #$83]\n
84=[linked to menu #$84]\n
85=[linked to menu #$85]\n
86=[linked to menu #$86]\n
87=[linked to menu #$87]\n
88=[linked to menu #$88]\n
89=[linked to menu #$89]\n
8A=[linked to menu #$8A]\n
8B=[linked to menu #$8B]\n
8C=[linked to menu #$8C]\n
8D=[linked to menu #$8D]\n
8E=[linked to menu #$8E]\n
8F=[linked to menu #$8F]\n
90=[linked to menu #$90]\n
91=[linked to menu #$91]\n
92=[linked to menu #$92]\n
93=[linked to menu #$93]\n
94=[linked to menu #$94]\n
95=[linked to menu #$95]\n
96=[linked to menu #$96]\n
97=[linked to menu #$97]\n
98=[linked to menu #$98]\n
99=[linked to menu #$99]\n
9A=[linked to menu #$9A]\n
9B=[linked to menu #$9B]\n
9C=[linked to menu #$9C]\n
9D=[linked to menu #$9D]\n
9E=[linked to menu #$9E]\n
9F=[linked to menu #$9F]\n
A0=[linked to menu #$A0]\n
A1=[linked to menu #$A1]\n
A2=[linked to menu #$A2]\n
A3=[linked to menu #$A3]\n
A4=[linked to menu #$A4]\n
A5=[linked to menu #$A5]\n
A6=[linked to menu #$A6]\n
A7=[linked to menu #$A7]\n
A8=[linked to menu #$A8]\n
A9=[linked to menu #$A9]\n
AA=[linked to menu #$AA]\n
AB=[linked to menu #$AB]\n
AC=[linked to menu #$AC]\n
AD=[linked to menu #$AD]\n
AE=[linked to menu #$AE]\n
AF=[linked to menu #$AF]\n
B0=[linked to menu #$B0]\n
B1=[linked to menu #$B1]\n
B2=[linked to menu #$B2]\n
B3=[linked to menu #$B3]\n
B4=[linked to menu #$B4]\n
B5=[linked to menu #$B5]\n
B6=[linked to menu #$B6]\n
B7=[linked to menu #$B7]\n
B8=[linked to menu #$B8]\n
B9=[linked to menu #$B9]\n
BA=[linked to menu #$BA]\n
BB=[linked to menu #$BB]\n
BC=[linked to menu #$BC]\n
BD=[linked to menu #$BD]\n
BE=[linked to menu #$BE]\n
BF=[linked to menu #$BF]\n
C0=[linked to menu #$C0]\n
C1=[linked to menu #$C1]\n
C2=[linked to menu #$C2]\n
C3=[linked to menu #$C3]\n
C4=[linked to menu #$C4]\n
C5=[linked to menu #$C5]\n
C6=[linked to menu #$C6]\n
C7=[linked to menu #$C7]\n
C8=[linked to menu #$C8]\n
C9=[linked to menu #$C9]\n
CA=[linked to menu #$CA]\n
CB=[linked to menu #$CB]\n
CC=[linked to menu #$CC]\n
CD=[linked to menu #$CD]\n
CE=[linked to menu #$CE]\n
CF=[linked to menu #$CF]\n
D0=[linked to menu #$D0]\n
D1=[linked to menu #$D1]\n
D2=[linked to menu #$D2]\n
D3=[linked to menu #$D3]\n
D4=[linked to menu #$D4]\n
D5=[linked to menu #$D5]\n
D6=[linked to menu #$D6]\n
D7=[linked to menu #$D7]\n
D8=[linked to menu #$D8]\n
D9=[linked to menu #$D9]\n
DA=[linked to menu #$DA]\n
DB=[linked to menu #$DB]\n
DC=[linked to menu #$DC]\n
DD=[linked to menu #$DD]\n
DE=[linked to menu #$DE]\n
DF=[linked to menu #$DF]\n
E0=[linked to menu #$E0]\n
E1=[linked to menu #$E1]\n
E2=[linked to menu #$E2]\n
E3=[linked to menu #$E3]\n
E4=[linked to menu #$E4]\n
E5=[linked to menu #$E5]\n
E6=[linked to menu #$E6]\n
E7=[linked to menu #$E7]\n
E8=[linked to menu #$E8]\n
E9=[linked to menu #$E9]\n
EA=[linked to menu #$EA]\n
EB=[linked to menu #$EB]\n
EC=[linked to menu #$EC]\n
ED=[linked to menu #$ED]\n
EE=[linked to menu #$EE]\n
EF=[linked to menu #$EF]\n
F0=[linked to menu #$F0]\n
F1=[linked to menu #$F1]\n
F2=[linked to menu #$F2]\n
F3=[linked to menu #$F3]\n
F4=[linked to menu #$F4]\n
F5=[linked to menu #$F5]\n
F6=[linked to menu #$F6]\n
F7=[linked to menu #$F7]\n
F8=[linked to menu #$F8]\n
F9=[linked to menu #$F9]\n
FA=[linked to menu #$FA]\n
FB=[linked to menu #$FB]\n
FC=[linked to menu #$FC]\n
FD=[linked to menu #$FD]\n
FE=[linked to menu #$FE]\n
FF=[linked to menu #$FF]\n

View File

@ -0,0 +1,256 @@
!00=<[menu format 00: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!01=<[menu format 01: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!02=<[menu format 02: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!03=<[menu format 03: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!04=<[menu format 04: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!05=<[menu format 05: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!06=<[menu format 06: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!07=<[menu format 07: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!08=<[menu format 08: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!09=<[menu format 09: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!0A=<[menu format 0A: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!0B=<[menu format 0B: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!0C=<[menu format 0C: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!0D=<[menu format 0D: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!0E=<[menu format 0E: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!0F=<[menu format 0F: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!10=<[menu format 10: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!11=<[menu format 11: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!12=<[menu format 12: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!13=<[menu format 13: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!14=<[menu format 14: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!15=<[menu format 15: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!16=<[menu format 16: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!17=<[menu format 17: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!18=<[menu format 18: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!19=<[menu format 19: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!1A=<[menu format 1A: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!1B=<[menu format 1B: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!1C=<[menu format 1C: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!1D=<[menu format 1D: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!1E=<[menu format 1E: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!1F=<[menu format 1F: no cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!20=<[menu format 20: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!21=<[menu format 21: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!22=<[menu format 22: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!23=<[menu format 23: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!24=<[menu format 24: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!25=<[menu format 25: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!26=<[menu format 26: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!27=<[menu format 27: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!28=<[menu format 28: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!29=<[menu format 29: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!2A=<[menu format 2A: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!2B=<[menu format 2B: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!2C=<[menu format 2C: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!2D=<[menu format 2D: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!2E=<[menu format 2E: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!2F=<[menu format 2F: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!30=<[menu format 30: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!31=<[menu format 31: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!32=<[menu format 32: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!33=<[menu format 33: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!34=<[menu format 34: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!35=<[menu format 35: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!36=<[menu format 36: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!37=<[menu format 37: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!38=<[menu format 38: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!39=<[menu format 39: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!3A=<[menu format 3A: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!3B=<[menu format 3B: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!3C=<[menu format 3C: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!3D=<[menu format 3D: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!3E=<[menu format 3E: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!3F=<[menu format 3F: no cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu>:0
!40=<[menu format 40: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!41=<[menu format 41: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!42=<[menu format 42: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!43=<[menu format 43: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!44=<[menu format 44: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!45=<[menu format 45: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!46=<[menu format 46: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!47=<[menu format 47: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!48=<[menu format 48: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!49=<[menu format 49: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!4A=<[menu format 4A: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!4B=<[menu format 4B: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!4C=<[menu format 4C: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!4D=<[menu format 4D: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!4E=<[menu format 4E: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!4F=<[menu format 4F: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!50=<[menu format 50: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!51=<[menu format 51: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!52=<[menu format 52: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!53=<[menu format 53: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!54=<[menu format 54: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!55=<[menu format 55: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!56=<[menu format 56: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!57=<[menu format 57: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!58=<[menu format 58: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!59=<[menu format 59: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!5A=<[menu format 5A: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!5B=<[menu format 5B: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!5C=<[menu format 5C: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!5D=<[menu format 5D: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!5E=<[menu format 5E: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!5F=<[menu format 5F: no cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!60=<[menu format 60: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!61=<[menu format 61: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!62=<[menu format 62: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!63=<[menu format 63: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!64=<[menu format 64: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!65=<[menu format 65: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!66=<[menu format 66: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!67=<[menu format 67: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!68=<[menu format 68: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!69=<[menu format 69: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!6A=<[menu format 6A: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!6B=<[menu format 6B: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!6C=<[menu format 6C: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!6D=<[menu format 6D: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!6E=<[menu format 6E: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!6F=<[menu format 6F: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!70=<[menu format 70: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!71=<[menu format 71: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!72=<[menu format 72: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!73=<[menu format 73: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!74=<[menu format 74: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!75=<[menu format 75: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!76=<[menu format 76: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!77=<[menu format 77: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!78=<[menu format 78: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!79=<[menu format 79: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!7A=<[menu format 7A: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!7B=<[menu format 7B: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!7C=<[menu format 7C: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!7D=<[menu format 7D: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!7E=<[menu format 7E: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!7F=<[menu format 7F: no cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@menu_links>:1,<@menu>:0
!80=<[menu format 80: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!81=<[menu format 81: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!82=<[menu format 82: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!83=<[menu format 83: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!84=<[menu format 84: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!85=<[menu format 85: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!86=<[menu format 86: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!87=<[menu format 87: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!88=<[menu format 88: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!89=<[menu format 89: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!8A=<[menu format 8A: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!8B=<[menu format 8B: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!8C=<[menu format 8C: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!8D=<[menu format 8D: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!8E=<[menu format 8E: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!8F=<[menu format 8F: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!90=<[menu format 90: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!91=<[menu format 91: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!92=<[menu format 92: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!93=<[menu format 93: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!94=<[menu format 94: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!95=<[menu format 95: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!96=<[menu format 96: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!97=<[menu format 97: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!98=<[menu format 98: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!99=<[menu format 99: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!9A=<[menu format 9A: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!9B=<[menu format 9B: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!9C=<[menu format 9C: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!9D=<[menu format 9D: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!9E=<[menu format 9E: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!9F=<[menu format 9F: cursor, not linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A0=<[menu format A0: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A1=<[menu format A1: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A2=<[menu format A2: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A3=<[menu format A3: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A4=<[menu format A4: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A5=<[menu format A5: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A6=<[menu format A6: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A7=<[menu format A7: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A8=<[menu format A8: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!A9=<[menu format A9: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!AA=<[menu format AA: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!AB=<[menu format AB: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!AC=<[menu format AC: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!AD=<[menu format AD: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!AE=<[menu format AE: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!AF=<[menu format AF: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B0=<[menu format B0: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B1=<[menu format B1: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B2=<[menu format B2: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B3=<[menu format B3: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B4=<[menu format B4: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B5=<[menu format B5: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B6=<[menu format B6: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B7=<[menu format B7: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B8=<[menu format B8: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!B9=<[menu format B9: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!BA=<[menu format BA: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!BB=<[menu format BB: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!BC=<[menu format BC: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!BD=<[menu format BD: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!BE=<[menu format BE: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!BF=<[menu format BF: cursor, not linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu>:0
!C0=<[menu format C0: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C1=<[menu format C1: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C2=<[menu format C2: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C3=<[menu format C3: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C4=<[menu format C4: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C5=<[menu format C5: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C6=<[menu format C6: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C7=<[menu format C7: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C8=<[menu format C8: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!C9=<[menu format C9: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!CA=<[menu format CA: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!CB=<[menu format CB: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!CC=<[menu format CC: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!CD=<[menu format CD: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!CE=<[menu format CE: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!CF=<[menu format CF: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D0=<[menu format D0: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D1=<[menu format D1: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D2=<[menu format D2: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D3=<[menu format D3: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D4=<[menu format D4: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D5=<[menu format D5: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D6=<[menu format D6: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D7=<[menu format D7: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D8=<[menu format D8: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!D9=<[menu format D9: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!DA=<[menu format DA: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!DB=<[menu format DB: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!DC=<[menu format DC: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!DD=<[menu format DD: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!DE=<[menu format DE: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!DF=<[menu format DF: cursor, linked, double-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E0=<[menu format E0: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E1=<[menu format E1: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E2=<[menu format E2: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E3=<[menu format E3: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E4=<[menu format E4: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E5=<[menu format E5: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E6=<[menu format E6: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E7=<[menu format E7: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E8=<[menu format E8: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!E9=<[menu format E9: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!EA=<[menu format EA: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!EB=<[menu format EB: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!EC=<[menu format EC: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!ED=<[menu format ED: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!EE=<[menu format EE: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!EF=<[menu format EF: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F0=<[menu format F0: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F1=<[menu format F1: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F2=<[menu format F2: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F3=<[menu format F3: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F4=<[menu format F4: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F5=<[menu format F5: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F6=<[menu format F6: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F7=<[menu format F7: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F8=<[menu format F8: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!F9=<[menu format F9: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!FA=<[menu format FA: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!FB=<[menu format FB: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!FC=<[menu format FC: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!FD=<[menu format FD: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!FE=<[menu format FE: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0
!FF=<[menu format FF: cursor, linked, single-spaced]\n>,<@heights>:1,<@widths>:1,<@position*2>:1,<@cursor>:1,<@position>:1,<@menu_links>:1,<@menu>:0

View File

@ -0,0 +1,257 @@
@position
00=[position:(0,0)]\n
01=[position:(1,0)]\n
02=[position:(2,0)]\n
03=[position:(3,0)]\n
04=[position:(4,0)]\n
05=[position:(5,0)]\n
06=[position:(6,0)]\n
07=[position:(7,0)]\n
08=[position:(8,0)]\n
09=[position:(9,0)]\n
0A=[position:(10,0)]\n
0B=[position:(11,0)]\n
0C=[position:(12,0)]\n
0D=[position:(13,0)]\n
0E=[position:(14,0)]\n
0F=[position:(15,0)]\n
10=[position:(0,1)]\n
11=[position:(1,1)]\n
12=[position:(2,1)]\n
13=[position:(3,1)]\n
14=[position:(4,1)]\n
15=[position:(5,1)]\n
16=[position:(6,1)]\n
17=[position:(7,1)]\n
18=[position:(8,1)]\n
19=[position:(9,1)]\n
1A=[position:(10,1)]\n
1B=[position:(11,1)]\n
1C=[position:(12,1)]\n
1D=[position:(13,1)]\n
1E=[position:(14,1)]\n
1F=[position:(15,1)]\n
20=[position:(0,2)]\n
21=[position:(1,2)]\n
22=[position:(2,2)]\n
23=[position:(3,2)]\n
24=[position:(4,2)]\n
25=[position:(5,2)]\n
26=[position:(6,2)]\n
27=[position:(7,2)]\n
28=[position:(8,2)]\n
29=[position:(9,2)]\n
2A=[position:(10,2)]\n
2B=[position:(11,2)]\n
2C=[position:(12,2)]\n
2D=[position:(13,2)]\n
2E=[position:(14,2)]\n
2F=[position:(15,2)]\n
30=[position:(0,3)]\n
31=[position:(1,3)]\n
32=[position:(2,3)]\n
33=[position:(3,3)]\n
34=[position:(4,3)]\n
35=[position:(5,3)]\n
36=[position:(6,3)]\n
37=[position:(7,3)]\n
38=[position:(8,3)]\n
39=[position:(9,3)]\n
3A=[position:(10,3)]\n
3B=[position:(11,3)]\n
3C=[position:(12,3)]\n
3D=[position:(13,3)]\n
3E=[position:(14,3)]\n
3F=[position:(15,3)]\n
40=[position:(0,4)]\n
41=[position:(1,4)]\n
42=[position:(2,4)]\n
43=[position:(3,4)]\n
44=[position:(4,4)]\n
45=[position:(5,4)]\n
46=[position:(6,4)]\n
47=[position:(7,4)]\n
48=[position:(8,4)]\n
49=[position:(9,4)]\n
4A=[position:(10,4)]\n
4B=[position:(11,4)]\n
4C=[position:(12,4)]\n
4D=[position:(13,4)]\n
4E=[position:(14,4)]\n
4F=[position:(15,4)]\n
50=[position:(0,5)]\n
51=[position:(1,5)]\n
52=[position:(2,5)]\n
53=[position:(3,5)]\n
54=[position:(4,5)]\n
55=[position:(5,5)]\n
56=[position:(6,5)]\n
57=[position:(7,5)]\n
58=[position:(8,5)]\n
59=[position:(9,5)]\n
5A=[position:(10,5)]\n
5B=[position:(11,5)]\n
5C=[position:(12,5)]\n
5D=[position:(13,5)]\n
5E=[position:(14,5)]\n
5F=[position:(15,5)]\n
60=[position:(0,6)]\n
61=[position:(1,6)]\n
62=[position:(2,6)]\n
63=[position:(3,6)]\n
64=[position:(4,6)]\n
65=[position:(5,6)]\n
66=[position:(6,6)]\n
67=[position:(7,6)]\n
68=[position:(8,6)]\n
69=[position:(9,6)]\n
6A=[position:(10,6)]\n
6B=[position:(11,6)]\n
6C=[position:(12,6)]\n
6D=[position:(13,6)]\n
6E=[position:(14,6)]\n
6F=[position:(15,6)]\n
70=[position:(0,7)]\n
71=[position:(1,7)]\n
72=[position:(2,7)]\n
73=[position:(3,7)]\n
74=[position:(4,7)]\n
75=[position:(5,7)]\n
76=[position:(6,7)]\n
77=[position:(7,7)]\n
78=[position:(8,7)]\n
79=[position:(9,7)]\n
7A=[position:(10,7)]\n
7B=[position:(11,7)]\n
7C=[position:(12,7)]\n
7D=[position:(13,7)]\n
7E=[position:(14,7)]\n
7F=[position:(15,7)]\n
80=[position:(0,8)]\n
81=[position:(1,8)]\n
82=[position:(2,8)]\n
83=[position:(3,8)]\n
84=[position:(4,8)]\n
85=[position:(5,8)]\n
86=[position:(6,8)]\n
87=[position:(7,8)]\n
88=[position:(8,8)]\n
89=[position:(9,8)]\n
8A=[position:(10,8)]\n
8B=[position:(11,8)]\n
8C=[position:(12,8)]\n
8D=[position:(13,8)]\n
8E=[position:(14,8)]\n
8F=[position:(15,8)]\n
90=[position:(0,9)]\n
91=[position:(1,9)]\n
92=[position:(2,9)]\n
93=[position:(3,9)]\n
94=[position:(4,9)]\n
95=[position:(5,9)]\n
96=[position:(6,9)]\n
97=[position:(7,9)]\n
98=[position:(8,9)]\n
99=[position:(9,9)]\n
9A=[position:(10,9)]\n
9B=[position:(11,9)]\n
9C=[position:(12,9)]\n
9D=[position:(13,9)]\n
9E=[position:(14,9)]\n
9F=[position:(15,9)]\n
A0=[position:(0,10)]\n
A1=[position:(1,10)]\n
A2=[position:(2,10)]\n
A3=[position:(3,10)]\n
A4=[position:(4,10)]\n
A5=[position:(5,10)]\n
A6=[position:(6,10)]\n
A7=[position:(7,10)]\n
A8=[position:(8,10)]\n
A9=[position:(9,10)]\n
AA=[position:(10,10)]\n
AB=[position:(11,10)]\n
AC=[position:(12,10)]\n
AD=[position:(13,10)]\n
AE=[position:(14,10)]\n
AF=[position:(15,10)]\n
B0=[position:(0,11)]\n
B1=[position:(1,11)]\n
B2=[position:(2,11)]\n
B3=[position:(3,11)]\n
B4=[position:(4,11)]\n
B5=[position:(5,11)]\n
B6=[position:(6,11)]\n
B7=[position:(7,11)]\n
B8=[position:(8,11)]\n
B9=[position:(9,11)]\n
BA=[position:(10,11)]\n
BB=[position:(11,11)]\n
BC=[position:(12,11)]\n
BD=[position:(13,11)]\n
BE=[position:(14,11)]\n
BF=[position:(15,11)]\n
C0=[position:(0,12)]\n
C1=[position:(1,12)]\n
C2=[position:(2,12)]\n
C3=[position:(3,12)]\n
C4=[position:(4,12)]\n
C5=[position:(5,12)]\n
C6=[position:(6,12)]\n
C7=[position:(7,12)]\n
C8=[position:(8,12)]\n
C9=[position:(9,12)]\n
CA=[position:(10,12)]\n
CB=[position:(11,12)]\n
CC=[position:(12,12)]\n
CD=[position:(13,12)]\n
CE=[position:(14,12)]\n
CF=[position:(15,12)]\n
D0=[position:(0,13)]\n
D1=[position:(1,13)]\n
D2=[position:(2,13)]\n
D3=[position:(3,13)]\n
D4=[position:(4,13)]\n
D5=[position:(5,13)]\n
D6=[position:(6,13)]\n
D7=[position:(7,13)]\n
D8=[position:(8,13)]\n
D9=[position:(9,13)]\n
DA=[position:(10,13)]\n
DB=[position:(11,13)]\n
DC=[position:(12,13)]\n
DD=[position:(13,13)]\n
DE=[position:(14,13)]\n
DF=[position:(15,13)]\n
E0=[position:(0,14)]\n
E1=[position:(1,14)]\n
E2=[position:(2,14)]\n
E3=[position:(3,14)]\n
E4=[position:(4,14)]\n
E5=[position:(5,14)]\n
E6=[position:(6,14)]\n
E7=[position:(7,14)]\n
E8=[position:(8,14)]\n
E9=[position:(9,14)]\n
EA=[position:(10,14)]\n
EB=[position:(11,14)]\n
EC=[position:(12,14)]\n
ED=[position:(13,14)]\n
EE=[position:(14,14)]\n
EF=[position:(15,14)]\n
F0=[position:(0,15)]\n
F1=[position:(1,15)]\n
F2=[position:(2,15)]\n
F3=[position:(3,15)]\n
F4=[position:(4,15)]\n
F5=[position:(5,15)]\n
F6=[position:(6,15)]\n
F7=[position:(7,15)]\n
F8=[position:(8,15)]\n
F9=[position:(9,15)]\n
FA=[position:(10,15)]\n
FB=[position:(11,15)]\n
FC=[position:(12,15)]\n
FD=[position:(13,15)]\n
FE=[position:(14,15)]\n
FF=[position:(15,15)]\n

View File

@ -0,0 +1,257 @@
@position*2
00=[position:(0,0)]\n
01=[position:(2,0)]\n
02=[position:(4,0)]\n
03=[position:(6,0)]\n
04=[position:(8,0)]\n
05=[position:(10,0)]\n
06=[position:(12,0)]\n
07=[position:(14,0)]\n
08=[position:(16,0)]\n
09=[position:(18,0)]\n
0A=[position:(20,0)]\n
0B=[position:(22,0)]\n
0C=[position:(24,0)]\n
0D=[position:(26,0)]\n
0E=[position:(28,0)]\n
0F=[position:(30,0)]\n
10=[position:(0,2)]\n
11=[position:(2,2)]\n
12=[position:(4,2)]\n
13=[position:(6,2)]\n
14=[position:(8,2)]\n
15=[position:(10,2)]\n
16=[position:(12,2)]\n
17=[position:(14,2)]\n
18=[position:(16,2)]\n
19=[position:(18,2)]\n
1A=[position:(20,2)]\n
1B=[position:(22,2)]\n
1C=[position:(24,2)]\n
1D=[position:(26,2)]\n
1E=[position:(28,2)]\n
1F=[position:(30,2)]\n
20=[position:(0,4)]\n
21=[position:(2,4)]\n
22=[position:(4,4)]\n
23=[position:(6,4)]\n
24=[position:(8,4)]\n
25=[position:(10,4)]\n
26=[position:(12,4)]\n
27=[position:(14,4)]\n
28=[position:(16,4)]\n
29=[position:(18,4)]\n
2A=[position:(20,4)]\n
2B=[position:(22,4)]\n
2C=[position:(24,4)]\n
2D=[position:(26,4)]\n
2E=[position:(28,4)]\n
2F=[position:(30,4)]\n
30=[position:(0,6)]\n
31=[position:(2,6)]\n
32=[position:(4,6)]\n
33=[position:(6,6)]\n
34=[position:(8,6)]\n
35=[position:(10,6)]\n
36=[position:(12,6)]\n
37=[position:(14,6)]\n
38=[position:(16,6)]\n
39=[position:(18,6)]\n
3A=[position:(20,6)]\n
3B=[position:(22,6)]\n
3C=[position:(24,6)]\n
3D=[position:(26,6)]\n
3E=[position:(28,6)]\n
3F=[position:(30,6)]\n
40=[position:(0,8)]\n
41=[position:(2,8)]\n
42=[position:(4,8)]\n
43=[position:(6,8)]\n
44=[position:(8,8)]\n
45=[position:(10,8)]\n
46=[position:(12,8)]\n
47=[position:(14,8)]\n
48=[position:(16,8)]\n
49=[position:(18,8)]\n
4A=[position:(20,8)]\n
4B=[position:(22,8)]\n
4C=[position:(24,8)]\n
4D=[position:(26,8)]\n
4E=[position:(28,8)]\n
4F=[position:(30,8)]\n
50=[position:(0,10)]\n
51=[position:(2,10)]\n
52=[position:(4,10)]\n
53=[position:(6,10)]\n
54=[position:(8,10)]\n
55=[position:(10,10)]\n
56=[position:(12,10)]\n
57=[position:(14,10)]\n
58=[position:(16,10)]\n
59=[position:(18,10)]\n
5A=[position:(20,10)]\n
5B=[position:(22,10)]\n
5C=[position:(24,10)]\n
5D=[position:(26,10)]\n
5E=[position:(28,10)]\n
5F=[position:(30,10)]\n
60=[position:(0,12)]\n
61=[position:(2,12)]\n
62=[position:(4,12)]\n
63=[position:(6,12)]\n
64=[position:(8,12)]\n
65=[position:(10,12)]\n
66=[position:(12,12)]\n
67=[position:(14,12)]\n
68=[position:(16,12)]\n
69=[position:(18,12)]\n
6A=[position:(20,12)]\n
6B=[position:(22,12)]\n
6C=[position:(24,12)]\n
6D=[position:(26,12)]\n
6E=[position:(28,12)]\n
6F=[position:(30,12)]\n
70=[position:(0,14)]\n
71=[position:(2,14)]\n
72=[position:(4,14)]\n
73=[position:(6,14)]\n
74=[position:(8,14)]\n
75=[position:(10,14)]\n
76=[position:(12,14)]\n
77=[position:(14,14)]\n
78=[position:(16,14)]\n
79=[position:(18,14)]\n
7A=[position:(20,14)]\n
7B=[position:(22,14)]\n
7C=[position:(24,14)]\n
7D=[position:(26,14)]\n
7E=[position:(28,14)]\n
7F=[position:(30,14)]\n
80=[position:(0,16)]\n
81=[position:(2,16)]\n
82=[position:(4,16)]\n
83=[position:(6,16)]\n
84=[position:(8,16)]\n
85=[position:(10,16)]\n
86=[position:(12,16)]\n
87=[position:(14,16)]\n
88=[position:(16,16)]\n
89=[position:(18,16)]\n
8A=[position:(20,16)]\n
8B=[position:(22,16)]\n
8C=[position:(24,16)]\n
8D=[position:(26,16)]\n
8E=[position:(28,16)]\n
8F=[position:(30,16)]\n
90=[position:(0,18)]\n
91=[position:(2,18)]\n
92=[position:(4,18)]\n
93=[position:(6,18)]\n
94=[position:(8,18)]\n
95=[position:(10,18)]\n
96=[position:(12,18)]\n
97=[position:(14,18)]\n
98=[position:(16,18)]\n
99=[position:(18,18)]\n
9A=[position:(20,18)]\n
9B=[position:(22,18)]\n
9C=[position:(24,18)]\n
9D=[position:(26,18)]\n
9E=[position:(28,18)]\n
9F=[position:(30,18)]\n
A0=[position:(0,20)]\n
A1=[position:(2,20)]\n
A2=[position:(4,20)]\n
A3=[position:(6,20)]\n
A4=[position:(8,20)]\n
A5=[position:(10,20)]\n
A6=[position:(12,20)]\n
A7=[position:(14,20)]\n
A8=[position:(16,20)]\n
A9=[position:(18,20)]\n
AA=[position:(20,20)]\n
AB=[position:(22,20)]\n
AC=[position:(24,20)]\n
AD=[position:(26,20)]\n
AE=[position:(28,20)]\n
AF=[position:(30,20)]\n
B0=[position:(0,22)]\n
B1=[position:(2,22)]\n
B2=[position:(4,22)]\n
B3=[position:(6,22)]\n
B4=[position:(8,22)]\n
B5=[position:(10,22)]\n
B6=[position:(12,22)]\n
B7=[position:(14,22)]\n
B8=[position:(16,22)]\n
B9=[position:(18,22)]\n
BA=[position:(20,22)]\n
BB=[position:(22,22)]\n
BC=[position:(24,22)]\n
BD=[position:(26,22)]\n
BE=[position:(28,22)]\n
BF=[position:(30,22)]\n
C0=[position:(0,24)]\n
C1=[position:(2,24)]\n
C2=[position:(4,24)]\n
C3=[position:(6,24)]\n
C4=[position:(8,24)]\n
C5=[position:(10,24)]\n
C6=[position:(12,24)]\n
C7=[position:(14,24)]\n
C8=[position:(16,24)]\n
C9=[position:(18,24)]\n
CA=[position:(20,24)]\n
CB=[position:(22,24)]\n
CC=[position:(24,24)]\n
CD=[position:(26,24)]\n
CE=[position:(28,24)]\n
CF=[position:(30,24)]\n
D0=[position:(0,26)]\n
D1=[position:(2,26)]\n
D2=[position:(4,26)]\n
D3=[position:(6,26)]\n
D4=[position:(8,26)]\n
D5=[position:(10,26)]\n
D6=[position:(12,26)]\n
D7=[position:(14,26)]\n
D8=[position:(16,26)]\n
D9=[position:(18,26)]\n
DA=[position:(20,26)]\n
DB=[position:(22,26)]\n
DC=[position:(24,26)]\n
DD=[position:(26,26)]\n
DE=[position:(28,26)]\n
DF=[position:(30,26)]\n
E0=[position:(0,28)]\n
E1=[position:(2,28)]\n
E2=[position:(4,28)]\n
E3=[position:(6,28)]\n
E4=[position:(8,28)]\n
E5=[position:(10,28)]\n
E6=[position:(12,28)]\n
E7=[position:(14,28)]\n
E8=[position:(16,28)]\n
E9=[position:(18,28)]\n
EA=[position:(20,28)]\n
EB=[position:(22,28)]\n
EC=[position:(24,28)]\n
ED=[position:(26,28)]\n
EE=[position:(28,28)]\n
EF=[position:(30,28)]\n
F0=[position:(0,30)]\n
F1=[position:(2,30)]\n
F2=[position:(4,30)]\n
F3=[position:(6,30)]\n
F4=[position:(8,30)]\n
F5=[position:(10,30)]\n
F6=[position:(12,30)]\n
F7=[position:(14,30)]\n
F8=[position:(16,30)]\n
F9=[position:(18,30)]\n
FA=[position:(20,30)]\n
FB=[position:(22,30)]\n
FC=[position:(24,30)]\n
FD=[position:(26,30)]\n
FE=[position:(28,30)]\n
FF=[position:(30,30)]\n

View File

@ -0,0 +1,18 @@
!01=<\n >,<@main>:$FF
!02=<\n >,<@main>:$FF
!03=<\n >,<@main>:$FF
!04=<\n >,<@main>:$FF
!05=<\n >,<@main>:$FF
!06=<\n >,<@main>:$FF
!07=<\n >,<@main>:$FF
!08=<\n >,<@main>:$FF
!09=<\n >,<@main>:$FF
!0A=<\n >,<@main>:$FF
!0B=<\n >,<@main>:$FF
!0C=<\n >,<@main>:$FF
!0D=<\n >,<@main>:$FF
!0E=<\n >,<@main>:$FF
!0F=<\n >,<@main>:$FF
82=\n[line]\n[line]
/8FFF=[end]\n

View File

@ -0,0 +1,257 @@
@widths
00=[width:0 tiles]\n
01=[width:1 tiles]\n
02=[width:2 tiles]\n
03=[width:3 tiles]\n
04=[width:4 tiles]\n
05=[width:5 tiles]\n
06=[width:6 tiles]\n
07=[width:7 tiles]\n
08=[width:8 tiles]\n
09=[width:9 tiles]\n
0A=[width:10 tiles]\n
0B=[width:11 tiles]\n
0C=[width:12 tiles]\n
0D=[width:13 tiles]\n
0E=[width:14 tiles]\n
0F=[width:15 tiles]\n
10=[width:16 tiles]\n
11=[width:17 tiles]\n
12=[width:18 tiles]\n
13=[width:19 tiles]\n
14=[width:20 tiles]\n
15=[width:21 tiles]\n
16=[width:22 tiles]\n
17=[width:23 tiles]\n
18=[width:24 tiles]\n
19=[width:25 tiles]\n
1A=[width:26 tiles]\n
1B=[width:27 tiles]\n
1C=[width:28 tiles]\n
1D=[width:29 tiles]\n
1E=[width:30 tiles]\n
1F=[width:31 tiles]\n
20=[width:32 tiles]\n
21=[width:33 tiles]\n
22=[width:34 tiles]\n
23=[width:35 tiles]\n
24=[width:36 tiles]\n
25=[width:37 tiles]\n
26=[width:38 tiles]\n
27=[width:39 tiles]\n
28=[width:40 tiles]\n
29=[width:41 tiles]\n
2A=[width:42 tiles]\n
2B=[width:43 tiles]\n
2C=[width:44 tiles]\n
2D=[width:45 tiles]\n
2E=[width:46 tiles]\n
2F=[width:47 tiles]\n
30=[width:48 tiles]\n
31=[width:49 tiles]\n
32=[width:50 tiles]\n
33=[width:51 tiles]\n
34=[width:52 tiles]\n
35=[width:53 tiles]\n
36=[width:54 tiles]\n
37=[width:55 tiles]\n
38=[width:56 tiles]\n
39=[width:57 tiles]\n
3A=[width:58 tiles]\n
3B=[width:59 tiles]\n
3C=[width:60 tiles]\n
3D=[width:61 tiles]\n
3E=[width:62 tiles]\n
3F=[width:63 tiles]\n
40=[width:64 tiles]\n
41=[width:65 tiles]\n
42=[width:66 tiles]\n
43=[width:67 tiles]\n
44=[width:68 tiles]\n
45=[width:69 tiles]\n
46=[width:70 tiles]\n
47=[width:71 tiles]\n
48=[width:72 tiles]\n
49=[width:73 tiles]\n
4A=[width:74 tiles]\n
4B=[width:75 tiles]\n
4C=[width:76 tiles]\n
4D=[width:77 tiles]\n
4E=[width:78 tiles]\n
4F=[width:79 tiles]\n
50=[width:80 tiles]\n
51=[width:81 tiles]\n
52=[width:82 tiles]\n
53=[width:83 tiles]\n
54=[width:84 tiles]\n
55=[width:85 tiles]\n
56=[width:86 tiles]\n
57=[width:87 tiles]\n
58=[width:88 tiles]\n
59=[width:89 tiles]\n
5A=[width:90 tiles]\n
5B=[width:91 tiles]\n
5C=[width:92 tiles]\n
5D=[width:93 tiles]\n
5E=[width:94 tiles]\n
5F=[width:95 tiles]\n
60=[width:96 tiles]\n
61=[width:97 tiles]\n
62=[width:98 tiles]\n
63=[width:99 tiles]\n
64=[width:100 tiles]\n
65=[width:101 tiles]\n
66=[width:102 tiles]\n
67=[width:103 tiles]\n
68=[width:104 tiles]\n
69=[width:105 tiles]\n
6A=[width:106 tiles]\n
6B=[width:107 tiles]\n
6C=[width:108 tiles]\n
6D=[width:109 tiles]\n
6E=[width:110 tiles]\n
6F=[width:111 tiles]\n
70=[width:112 tiles]\n
71=[width:113 tiles]\n
72=[width:114 tiles]\n
73=[width:115 tiles]\n
74=[width:116 tiles]\n
75=[width:117 tiles]\n
76=[width:118 tiles]\n
77=[width:119 tiles]\n
78=[width:120 tiles]\n
79=[width:121 tiles]\n
7A=[width:122 tiles]\n
7B=[width:123 tiles]\n
7C=[width:124 tiles]\n
7D=[width:125 tiles]\n
7E=[width:126 tiles]\n
7F=[width:127 tiles]\n
80=[width:128 tiles]\n
81=[width:129 tiles]\n
82=[width:130 tiles]\n
83=[width:131 tiles]\n
84=[width:132 tiles]\n
85=[width:133 tiles]\n
86=[width:134 tiles]\n
87=[width:135 tiles]\n
88=[width:136 tiles]\n
89=[width:137 tiles]\n
8A=[width:138 tiles]\n
8B=[width:139 tiles]\n
8C=[width:140 tiles]\n
8D=[width:141 tiles]\n
8E=[width:142 tiles]\n
8F=[width:143 tiles]\n
90=[width:144 tiles]\n
91=[width:145 tiles]\n
92=[width:146 tiles]\n
93=[width:147 tiles]\n
94=[width:148 tiles]\n
95=[width:149 tiles]\n
96=[width:150 tiles]\n
97=[width:151 tiles]\n
98=[width:152 tiles]\n
99=[width:153 tiles]\n
9A=[width:154 tiles]\n
9B=[width:155 tiles]\n
9C=[width:156 tiles]\n
9D=[width:157 tiles]\n
9E=[width:158 tiles]\n
9F=[width:159 tiles]\n
A0=[width:160 tiles]\n
A1=[width:161 tiles]\n
A2=[width:162 tiles]\n
A3=[width:163 tiles]\n
A4=[width:164 tiles]\n
A5=[width:165 tiles]\n
A6=[width:166 tiles]\n
A7=[width:167 tiles]\n
A8=[width:168 tiles]\n
A9=[width:169 tiles]\n
AA=[width:170 tiles]\n
AB=[width:171 tiles]\n
AC=[width:172 tiles]\n
AD=[width:173 tiles]\n
AE=[width:174 tiles]\n
AF=[width:175 tiles]\n
B0=[width:176 tiles]\n
B1=[width:177 tiles]\n
B2=[width:178 tiles]\n
B3=[width:179 tiles]\n
B4=[width:180 tiles]\n
B5=[width:181 tiles]\n
B6=[width:182 tiles]\n
B7=[width:183 tiles]\n
B8=[width:184 tiles]\n
B9=[width:185 tiles]\n
BA=[width:186 tiles]\n
BB=[width:187 tiles]\n
BC=[width:188 tiles]\n
BD=[width:189 tiles]\n
BE=[width:190 tiles]\n
BF=[width:191 tiles]\n
C0=[width:192 tiles]\n
C1=[width:193 tiles]\n
C2=[width:194 tiles]\n
C3=[width:195 tiles]\n
C4=[width:196 tiles]\n
C5=[width:197 tiles]\n
C6=[width:198 tiles]\n
C7=[width:199 tiles]\n
C8=[width:200 tiles]\n
C9=[width:201 tiles]\n
CA=[width:202 tiles]\n
CB=[width:203 tiles]\n
CC=[width:204 tiles]\n
CD=[width:205 tiles]\n
CE=[width:206 tiles]\n
CF=[width:207 tiles]\n
D0=[width:208 tiles]\n
D1=[width:209 tiles]\n
D2=[width:210 tiles]\n
D3=[width:211 tiles]\n
D4=[width:212 tiles]\n
D5=[width:213 tiles]\n
D6=[width:214 tiles]\n
D7=[width:215 tiles]\n
D8=[width:216 tiles]\n
D9=[width:217 tiles]\n
DA=[width:218 tiles]\n
DB=[width:219 tiles]\n
DC=[width:220 tiles]\n
DD=[width:221 tiles]\n
DE=[width:222 tiles]\n
DF=[width:223 tiles]\n
E0=[width:224 tiles]\n
E1=[width:225 tiles]\n
E2=[width:226 tiles]\n
E3=[width:227 tiles]\n
E4=[width:228 tiles]\n
E5=[width:229 tiles]\n
E6=[width:230 tiles]\n
E7=[width:231 tiles]\n
E8=[width:232 tiles]\n
E9=[width:233 tiles]\n
EA=[width:234 tiles]\n
EB=[width:235 tiles]\n
EC=[width:236 tiles]\n
ED=[width:237 tiles]\n
EE=[width:238 tiles]\n
EF=[width:239 tiles]\n
F0=[width:240 tiles]\n
F1=[width:241 tiles]\n
F2=[width:242 tiles]\n
F3=[width:243 tiles]\n
F4=[width:244 tiles]\n
F5=[width:245 tiles]\n
F6=[width:246 tiles]\n
F7=[width:247 tiles]\n
F8=[width:248 tiles]\n
F9=[width:249 tiles]\n
FA=[width:250 tiles]\n
FB=[width:251 tiles]\n
FC=[width:252 tiles]\n
FD=[width:253 tiles]\n
FE=[width:254 tiles]\n
FF=[width:255 tiles]\n