Home

Awesome

Trick ;)

Trick is a library for GBA and NDS asset conversion in Nim.

It is used by the Natu project tool, providing an easy way to put images & maps into your GBA games.

It was also used for the PP20th translation project, thus is able to convert binary data back to PNG in some cases.

Features

Overview

Trick is intended to allow you to make your own command-line tools to wrangle assets for your homebrew projects.

Installation

$ nimble install trick

Examples

These examples use hardcoded settings and paths, but in practise you'll want to be looping over directories, reading from config files, etc.

PNG to binary

Convert your sprite sheets into raw binary formats used by GBA/NDS games:

import trick

var conf = GfxInfo(
  pal: @[clrEmpty],   # initial palette
  bpp: gfx4bpp,       # bit depth
  layout: gfxTiles,   # arrange into 8x8 tiles
)

# do the conversion
# `data` is now a string containing raw pixel data
# `conf.pal` will be populated with all the colors in the image
let data = pngToBin("mario.png", conf, buildPal=true)

# output to files
writeFile("mario.img.bin", data)
writePal("mario.pal.bin", conf.pal)

Binary to PNG

The inverse of the above process. This may be useful for rom hacking, validation etc.

let conf = GfxInfo(
  pal: readPal("mario.pal.bin"),
  bpp: gfx4bpp,       # bit depth
  layout: gfxTiles,   # unscramble from tile arrangement
)
let data = readFile("mario.img.bin")
let png = binToPng(data, conf)
writeFile("mario_out.png", png)

PNG to tileset + tilemap + palettes

The typical use case here is to take an image of your whole level and transform it into a tile map. Under the hood this involves both tileset reduction and palette reduction.

let bg4 = loadBg4("brinstar.png")
writeFile("brinstar.map.bin", toBytes(bg4.map))
writeFile("brinstar.img.bin", toBytes(bg4.img))
writeFile("brinstar.pal.bin", toBytes(joinPalettes(bg4.pals)))

Data to C

While the above examples use .bin files, the preferred way to embed read-only data in your Nim GBA games is by using extenal C files.

Example:

let data = readFile("mario.img.bin")

# convert binary data to a C string literal
let imgStringLit = makeCString(data)

# output C source code
writeFile("source/gfxdata.c", fmt"""
const char *marioImg = {imgStringLit};
""")

# output Nim source code
writeFile("source/gfxdata.nim", fmt"""
{{.compile: "gfxdata.c".}}
var marioImg* {{.importc, extern:"marioImg", codegenDecl:"extern const $# $#".}}: array[{data.len}, uint8]
""")

To explain the generated Nim code: the {.compile.} pragma ensures that gfxdata.c is compiled and linked into the final ROM. The {.importc.} and {.extern.} pragmas are used to make the C variable accessible to Nim. {.codegenDecl.} is just to help avoid compiler warnings.

It's worth mentioning that this Nim code could be written by hand, but I recommend this approach to maintain integrity between the assets and the game code. It will also save a lot of work once you modify your tool to support multiple images, by using a config file or by processing all images in a certain folder, etc.

Example of using data in your game code:

import natu, gfxdata

# copy the image data into VRAM
memcpy32(addr tileMemObj[0], addr marioImg, marioImg.len div sizeof(uint32))

Todo