Tools‎ > ‎zzrtl‎ > ‎

zzrtl manual

zzrtl uses a very minimalistic C implementation.

The following C features are unsupported:
 - floating points          - for loops
 - the preprocessor         - goto
 - do-while                 - all variables must be
 - break                      declared at beginning
 - +=, -=, etc.               of function
 - unsigned types           - arrays
 - declare-anywhere         - assigning variables
 - switch-case                during declaration
 - the struct keyword is    - function prototypes
   reserved for built-in    - probably other stuff;
   types                      proceed with caution

Besides what you write yourself, the only functions available to you
are the ones listed below. A brief description is provided for each.
zzrtl's built-in error checking reports errors and ends execution if
anything goes wrong, though there are exceptions. If you find these
notes too cryptic, refer to the pre-made scripts. They are thoroughly
commented and show each function in action.


libc functions:
printf
sprintf
sscanf
malloc
free
fopen
fprintf
fclose
memset
memcmp
strcmp
strcasecmp
strcat
system
(most of these have had error checking added so the program will
 throw an error if an invalid pointer is used with them)


generic functions:
die(char *fmt, ...)     terminate with a custom error message
get32(void *)           get 32-bit (4 byte) value from raw data
get24(void *)           get 24-bit (3 byte) value from raw data
get16(void *)           get 16-bit (2 byte) value from raw data
get8(void *)            get 8-bit (1 byte) value from raw data
put32(void *, int)      put 32-bit (4 byte) value into raw data
put24(void *, int)      put 24-bit (3 byte) value into raw data
put16(void *, int)      put 16-bit (2 byte) value into raw data
put8(void *, int)       put 8-bit (1 byte) value into raw data
u8(char)                get unsigned equivalent of signed char
s8(int)                 get signed char equivalent of int
u16(int)                cast int to unsigned short
s16(int)                cast int to signed short
u32op(a, *op, b)        cast a and b to u32 and do operation;
                        example: u32op(apples, ">=", oranges)
                        the reason this exists is as a workaround for
                        situations where signedness may cause behavior
                        that is undesirable;
                        valid options for op:
                        "+"  "-"  "*"  "/" "&"  "|"  "<"  ">"  "<="
                        ">=" "==" "%"
substring(*list, idx)   get string at index `idx` in string `*list`;
                        returns 0 if anything goes awry
find_bytes              find ndl inside hay; ndl is a 0-term'd string
   (*hay, hay_sz        of hexadecimal characters (failure returns 0)
    , *ndl, only1)      NOTE: * can be used for wildcard bytes
                              example string "DEAD****BEEF"
                        only1 = 1 throws fatal error if more than one
                                occurrence is found
find_bytes_stride       same as find_bytes(), but allows you to specify
   (*hay, hay_sz, *ndl  stride; stride is the number of bytes to
    , stride, only1)    advance when searching
find_text               find ndl inside hay; ndl is a text string
   (*hay, hay_sz        (returns 0 on failure)
    , *ndl, only1)      example string "scubadiver"
                        only1 = 1 throws fatal error if more than one
                                occurrence is found
find_text_stride        same as find_text(), but allows you to specify
   (*hay, hay_sz, *ndl  stride; stride is the number of bytes to
   , stride, only1)     advance when searching
ovl_vram_sz             returns virtual ram size of overlay
   (void *, sz)
load_png                returns pointer to rgba8888 pixel data of png
   (fn, int *w, int *h) if it is successfully loaded; w and h are
                        propagated with its width and height as well
                        ex: pix = load_png("sky.png", &width, &height);
                        returns 0 if file doesn't exist
int_array(num, ...)     create an int array containing `num` elements
                        array32 = int_array(4, 10, 20, 30, 40);
                        array32[0] is now 10, [1] is 20, and so on
new_string(..., 0)      combines multiple strings and returns a pointer
                        to the result, which you can free() if you want
                        the list you provide must end with 0
                        ex:  new_string("build-", codec, ".z64", 0);
loadfile(               load a file, returning a pointer to its raw
   char *fn, int *sz    data, or 0 if it doesn't exist; if `optional`
    , bool optional)    is `true`, no error will be thrown if file does
                        not exist; `sz` will be set to size of file (in
                        bytes), or if you don't need that, pass 0
tsv_col_row(char *tsv   returns pointer to string inside tsv, beneath
   , char *col          column `col` and in row `row` (row 0 is first
   , int   row)         row where names are contained)


directory functions:
dir_exists(char *)      returns 1 if directory of given name exists,
                        returns 0 otherwise
dir_enter(char *)       enter a directory (folder)
                        NOTE: directory is created if it doesn't exist
dir_enter_file(char *)  enter the directory of provided file
dir_leave(void)         leave last-entered directory
dir_mkname              make a compliant directory name; str can be 0
   (int, *str)          if you want only a number for the folder name
dir_use_style(char *)   set style used by dir_mkname (default is "pre")
                        valid options:
                           "pre"   : uses form "%d - %s"
                           "preX"  : uses form "0x%X - %s"
                           "post"  : uses form "%s (%d)"
                           "postX" : uses form "%s (0x%X)"
file_exists(char *)     returns non-zero if file of given name exists,
                        returns 0 otherwise


struct rom functions:
.new(char *)            allocate a rom structure and load rom file
.free(void)             free a rom structure
.raw(ofs)               get pointer to raw data at offset `ofs` in rom
.save(char *)           save rom to disk using specified filename
.align(int)             align injected files such that their injection
                        offset is a multiple of the value you provide;
                        in retail roms, alignment is 0x1000 for every
                        file except the overlays, boot, dmadata, the
                        audio files, and link_animetion [sic] (it is
                        0x10 for all of these); the value you provide
                        must be a multiple of 16 (0x10); to maximize
                        space savings, inject files with smaller
                        alignment requirements first
.compress(fmt, mb)      compress rom using specified algorithm
                        valid options: "yaz", "lzo", "ucl", "aplib"
                        NOTE: to use another codec, patch your rom;
                        mb is the number of mb to cap the compressed
                        rom to; 32 is standard for OoT and MM
.seek(u32)              go to offset within rom
.seek_cur(adv)          go forward adv bytes in rom
                        NOTE: use a negative value to travel backwards
.tell(void)             get offset within rom
.size(void)             get size of rom
.inject(fn, comp)       inject file into rom
                        NOTE: if comp is non-zero, file is compressible
                        NOTE: returns pointer to injected data, or 0
                        NOTE: if name is formatted like "*.ext", it
                              will auto-detect a file by extension; for
                              example, "*.zobj" to inject whatever zobj
                              it can find
.inject_dma             inject file into rom, over known dma index
   (fn, comp, idx)      (file-size must match file being overwritten)
.inject_png             loads PNG, converts to N64 format, and injects
   (fn, fmt, bpp, comp) into rom fmt and bpp use the n64texconv enums
                        NOTE: if comp is non-zero, file is compressible
.inject_raw             inject raw data into rom
   (*raw, sz, comp)     NOTE: if comp is non-zero, file is compressible
                        NOTE: returns pointer to injected data, or 0
                        NOTE: sz is number of bytes, raw points to them
.inject_raw_dma         inject raw data into rom, over known dma index
   (*raw, sz, idx, cmp) (sz must match entry being overwritten)
.file_start(void)       get start offset of data injected with inject()
                        NOTE: will be 0 if inject() failed
.file_end(void)         get end offset of data injected with inject()
                        NOTE: will be 0 if inject() failed
.file(void)             get pointer to data of most recently injected;
                        NOTE: will be 0 if inject() failed
.file_sz(void)          get size of injected data
                        NOTE: will be 0 if inject() failed
.file_dma(void)         get dma index of data injected with inject()
                        NOTE: will be -1 if inject failed
.extract                extract raw data to a file
   (fn, start, end)
.extract_png            converts raw texture data to rgba8888, saves
   (fn, buf*, tex, pal  as PNG; buf can be a prealloc'd block that you
    w, h, fmt, bpp)     can guarantee is large enough to intermediately
                        store the converted texture, or 0 to tell the
                        function to alloc its own; fmt and bpp must be
                        as they are defined by the n64texconv enums
.dma(start, num)        specify start of dmadata, and number of entries
                      * every entry is by default marked as readonly,
                        and dma_queue() must be used to mark specific
                        entries as writable
                      * no entry is queued for compression at first,
                        and dma_compress() must be used to selectively
                        enable compression where it is desired
.dma_queue_one(idx)     mark one dma entry (by index) as writable;
.dma_queue              mark dma indices as writable, between start and
   (start, end)         end, inclusive (aka start <= idx <= end)
.dma_compress_one       set compression flag on dma entry (by index);
   (idx, comp)          if (comp == 1), file is marked for compression;
                        0 means no compression; other non-zero values
                        are reserved for internal use only
.dma_compress           set compression flag on indices between start
   (start, end, comp)   and end, inclusive (aka start <= idx <= end)
.dma_ready(void)        call this when you're finished with the dma
                        stuff; must be called before you inject data
.write(void *, sz)      write and advance `sz` bytes within rom
.write32(u32)           write and advance four  bytes within rom
.write24(int)           write and advance three bytes within rom
.write16(int)           write and advance two   bytes within rom
.write8(int)            write and advance one   byte  within rom
.read32(void)           read and advance four  bytes within rom
.read24(void)           read and advance three bytes within rom
.read16(void)           read and advance two   bytes within rom
.read8(void)            read and advance one   byte  within rom
.cloudpatch(ofs, fn)    patch a rom with a cloudpatch (.txt patch);
                        ofs is the value to add to all the offsets in
                        the patch, which is useful for applying patches
                        to individual files as they are injected;
                        otherwise, just use 0
.rearchive              this function is for MM only; it re-encodes
   (start, end, old,    the contents of an archive (a file containing
    new, repack)        multiple compressed files); `old` refers to
                        the old encoding (only "yaz" is supported for
                        now), and `new` refers to the new encoding,
                        like "ucl"; `repack` should always be true (1)
                        unless you run out of space and need the lossy
                        packing hack (use false (0) in that case)
                        (it is lossy in that it zeroes data at the end of
                        the texture until it fits, so it is recommended
                        that you optimize your textures manually)
.rearchive_one          re-encode one archive, by dma index
   (idx, old, new,      (MM only; see rearchive for more details)
    repack)


struct folder functions:
.new(*ext)              allocate a folder structure and parse the
                        current directory; contents can be named any
                      . way you like, as long as they contain a number
                         > "0 - gameplay_keep"
                         > "gameplay_keep (0)"
                         > "room_0.zmap"
                         > etc.
                      . folder/file names starting with a '.' or '_'
                        are not processed
                         > .trash
                         > _src
                         > etc.
                      . items are accessed in the order specified by
                        the numerical part, which can be hexadecimal as
                        long as it is preceded by "0x"
                      . no two folder/file names are allowed to contain
                        the same number (a fatal error will be thrown)
                      . if ext is 0, it creates a folder list
                      . if ext is non-zero, it creates a list of files
                        of the requested extension, such as "zmap"
                      . it first tries to find a number at the very
                        beginning (it is not allowed to be surrounded
                        by quotes, parenthesis, preceded by a space,
                        etc); if that fails, it uses the last number it
                        detects in the name
                         -> "1 - spot04"        yields index 1
                         -> "En_Torch2 (51)"    yields index 51
                         -> "object_link_boy"   throws fatal error
.free(void)             free a folder structure
.name(void)             get current folder name
.index(void)            get numerical part of current folder name
.next(void)             go to next folder in list;
                        returns 0 when end of list is reached
.count(void)            get number of items in list
.remaining(void)        get number of items between current and end
.max(void)              get highest index detected in list


struct conf functions:
.new(*fn, *fmt)         allocate a conf structure and parse file;
                        fmt should be either "table" or "list";
                      * a fatal error occurs if the file doesn't exist
                      * see conf section for more details
.free(void)             free a conf structure
.exists(*name)          returns non-zero if name exists
.get_int(*name)         get integer value associated with name;
                      * this function throws a fatal error if the name
                        does not exist; if you are handling optional
                        names, use .exists(name) to confirm it exists
                        before calling this function
                      * passing 0 to this function will cause it to
                        return the current list item's value as an int
.get_str(*name)         get pointer to string associated with name;
                      * the contents of the string are read only; do
                        not modify them or you will cause undefined
                        behavior
                      * this function throws a fatal error if the name
                        does not exist; if you are handling optional
                        names, use .exists(name) to confirm it exists
                        before calling this function
.next(void)             in the case of a table, go to next row;
                        in a list, go to next item
                        returns 0 when there is no next line
.remaining(void)        returns non-zero if there are rows (in table),
                        items (in list), remaining, 0 otherwise
.name(void)             returns string of selected item name
                        returns 0 at end of list
                        (for use in list types only)
.value(void)            returns string of selected item value
                        returns 0 at end of list
                        (for use in list types only)


conf section

conf can load files of two formats: "table" and "list"

the following information applies to both formats:

you can use // to comment out the remainder of a line,
or /* to comment out a specific block of text */
the number of tabs or spaces between names/values does
not matter, as all contiguous blocks of whitespace are
used to determine where one name or value ends and the
next begins; the names and values can contain anything
but whitespace (you can get around this limitation by
putting quotes around them); lastly, keep in mind that
the names are NOT cAsE-sEnSiTiVe, and as such, "vram"
and "VRAM" are the same

the source code provided for each can be executed using
zzrtl; just save as .rtl and open in zzrtl; don't forget
to make sure their dependencies ("table.txt", "list.txt")
are in the same directory as their respective .rtls

table notes

here is a table that you may load from a file

table.txt
scene   card   music   notes              fadein
0x02     off    stop   "diligent work"         2
0x03      on      go   "getting ocarina"       3

the first row contains the names that are used to look
up the values using the .get_x() functions; the rows
that follow contain the values themselves

the code for parsing it would look like

/****************************************
 * <z64.me> zzrtl table parsing example *
 ****************************************/

/* table.txt (save in same directory as example)
scene   card   music   notes              fadein
0x02     off    stop   "diligent work"         2
0x03      on      go   "getting ocarina"       3
*/

int
main(int argc, char **argv)
{
    struct conf *table;
    char *card;
    char *music;
    char *notes;
    int   scene;
    int   fadein;

    /* load the table */
    table = table.new("table.txt", "table");

    /* now parse all the rows */
    while (table.remaining())
    {
        /* retrieve variables */
        scene  = table.get_int("scene");
        card   = table.get_str("card");
        music  = table.get_str("music");
        fadein = table.get_int("fadein");
        notes  = table.get_str("notes");
       
        /* do something with the variables here */
        printf("scene %d settings:\n", scene);
        printf(" > card: %s\n", card);
        printf(" > music: %s\n", music);
        printf(" > fadein: %d\n", fadein);
        printf(" > notes: %s\n", notes);
       
        /* on the first pass, each variable will be whatever the  *
         * first value row says it should be; on the second pass, *
         * it pulls from the next row; there is no third pass     */
       
        /* go to next row */
        table.next();
    }

    /* cleanup */
    table.free();
    return 0;
}


list notes

here is a list that you may load from a file

list.txt
vram          0x80800000
unknown       0x01000000
"please do"   "work diligently"
//optional      "output changes if you uncomment this line"

each row contains a names is used to look up the value
to its right using the .get_x() functions;

the code for parsing it would look like

/***************************************
 * <z64.me> zzrtl list parsing example *
 ***************************************/

/* list.txt (save in same directory as example)
vram          0x80800000
unknown       0x01000000
"please do"   "work diligently"
//optional      "output changes if you uncomment this line"
*/

int
main(int argc, char **argv)
{
    struct conf *list;
    char *optional;
    char *pleasedo;
    int   vram;
    int   unknown;

    /* load the list */
    list = list.new("list.txt", "list");

    /* retrieve variables */
    vram      = list.get_int("vram");
    unknown   = list.get_int("unknown");
    pleasedo  = list.get_str("please do");
   
    /* how to do optional variables */
    if (list.exists("optional"))
        optional = list.get_str("optional");
    else
        optional = 0;

    /* do something with the variables here */
    printf("vram:          0x%X\n", vram);
    printf("unknown:       0x%08X\n", unknown);
    printf("'please do':  '%s'\n", pleasedo);
    if (optional)
        printf("optional:     '%s'\n", optional);

    /* cleanup */
    list.free();
    return 0;
}


migrating a zzromtool project tree to zzrtl

first off, if you are taking advantage of zzromtool's
built-in table expansion, you are going to need to either:
 (a) make everything fit within the original
     limits for each table
 (b) create your own table expansion mod and
     update the sample build script as needed

misc, system, shader, patch, repoint.tsv
 > this stuff is all deprecated, in favor of modifying
   it in the rom directly; should you wish to reimplement
   it in your own build script, the option is there
 > externalizing some files, like Link's overlay, does
   have its benefits; it was left out of the example
   build script for the sake of simplicity, but here is
   a guide to adding this particular functionality to
   the sample build script:
   https://github.com/z64me/zzrtl/compare/master...oot-link-cat


route.txt
 > remove the '#' character from the first row
 > any remaining '#' comments should be changed to //

scene
in the sample build script...
 > change "unk_a" to "unk-a:"
 > change "unk_b" to "unk-b:"
 > change "shader" to "shader:"
note:
 > save and restrict are not used, but you could add
   support for them with some assembly editing and by
   updating the build script

If you read this far, chances are you don't even need a manual. ;)