Skip to content

Instantly share code, notes, and snippets.

@CatTrinket
Last active August 7, 2018 07:14
Show Gist options
  • Save CatTrinket/a3a236c73b32bd14fc35 to your computer and use it in GitHub Desktop.
Save CatTrinket/a3a236c73b32bd14fc35 to your computer and use it in GitHub Desktop.
Scripts to rip the face sprites from Pokémon Super Mystery Dungeon in C and Python
/*
Rip the face sprites from Pokémon Super Mystery Dungeon.
The sprites are in romfs/face_graphic.bin. They're all in a seemingly-random
order and I haven't figured out how to map them to Pokémon yet.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIDTH 64
#define HEIGHT 64
struct Pixel {
// This is the channel order in face_graphic.bin
uint8_t blue;
uint8_t green;
uint8_t red;
};
int translate_pixel(int from_index) {
/* Translate a pixel index in the raw, tiled sprite into a pixel index in a
regular rows-and-columns image */
// Pixels are arranged in 8×8-pixel tiles
int tile = from_index / 64;
int inner_index = from_index % 64;
int tile_y = tile / (WIDTH / 8);
int tile_x = tile % (WIDTH / 8);
/* And the pixels within each tile are arranged in a funky iterative
Z-shaped pattern, which we'll unscramble with some fancy bit-math:
https://en.wikipedia.org/wiki/Z-order_curve#Coordinate_values
(This is a common 3DS sprite thing. No, I don't know why.) */
int inner_y =
(inner_index >> 1 & 1 << 0) |
(inner_index >> 2 & 1 << 1) |
(inner_index >> 3 & 1 << 2);
int inner_x =
(inner_index >> 0 & 1 << 0) |
(inner_index >> 1 & 1 << 1) |
(inner_index >> 2 & 1 << 2);
// These sprites in particular are upside-down for some reason
int y = HEIGHT - 1 - (tile_y * 8 + inner_y);
int x = tile_x * 8 + inner_x;
return y * WIDTH + x;
}
void make_pixel_map(int to_indices[WIDTH * HEIGHT]) {
/* Prepare an array mapping pre-untiling pixel indices to post-untiling
indices */
for (int from_index = 0; from_index < WIDTH * HEIGHT; from_index++) {
to_indices[translate_pixel(from_index)] = from_index;
}
}
void untile(struct Pixel old[WIDTH * HEIGHT], struct Pixel new[WIDTH * HEIGHT],
int indices[WIDTH * HEIGHT]) {
/* Untile a sprite using the coordinate array from make_pixel_map */
for (int pixel = 0; pixel < WIDTH * HEIGHT; pixel++) {
new[pixel] = old[indices[pixel]];
}
}
void write_pixel(struct Pixel pixel, FILE* out_file) {
/* Write out a pixel in RGB order */
fputc(pixel.red, out_file);
fputc(pixel.green, out_file);
fputc(pixel.blue, out_file);
}
void save_sprite(struct Pixel pixels[WIDTH * HEIGHT], char* out_dir,
int sprite_num) {
/* Save a sprite, using out_dir and sprite_num for the filename */
char filename[strlen(out_dir) + strlen("/12345.ppm") + 1];
sprintf(filename, "%s/%d.ppm", out_dir, sprite_num);
FILE* out_file = fopen(filename, "wb");
if (!out_file) {
fprintf(stderr, "opening %s failed for whatever reason\n", filename);
exit(EXIT_FAILURE);
}
// Write a PPM, because I haven't gotten around to checking out libpng yet
fputs("P6\n64 64\n255\n", out_file);
for (int pixel = 0; pixel < WIDTH * HEIGHT; pixel++) {
write_pixel(pixels[pixel], out_file);
}
fclose(out_file);
}
void rip_sprites(char* face_path, char* out_dir) {
/* Rip all the sprites from face_path (face_graphic.bin) to out_dir */
FILE* face_file = fopen(face_path, "rb");
if (!face_file) {
fprintf(stderr, "opening %s failed for whatever reason\n", face_path);
exit(EXIT_FAILURE);
}
// Skip FARC header and SIR0 block, which don't seem to contain anything
// useful
fseek(face_file, 0x31900, SEEK_SET);
// Precalculate all the coordinates for unscrambling each sprite
int coordinates[WIDTH * HEIGHT];
make_pixel_map(coordinates);
// Rip all the sprites
struct Pixel tiled_pixels[WIDTH * HEIGHT];
struct Pixel untiled_pixels[WIDTH * HEIGHT];
for (int sprite = 0; sprite < 16898; sprite++) {
int count = fread(tiled_pixels, sizeof(struct Pixel), WIDTH * HEIGHT,
face_file);
if (count != WIDTH * HEIGHT) {
fprintf(stderr, "only got %d pixels reading sprite %d\n", count,
sprite);
exit(EXIT_FAILURE);
}
untile(tiled_pixels, untiled_pixels, coordinates);
save_sprite(untiled_pixels, out_dir, sprite);
}
fclose(face_file);
}
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage: %s romfs/face_graphic.bin out_dir\n", argv[0]);
exit(EXIT_FAILURE);
}
rip_sprites(argv[1], argv[2]);
}
#!/usr/bin/env python3
"""Rip the face sprites from Pokémon Super Mystery Dungeon.
The sprites are in romfs/face_graphic.bin. They're all in a seemingly-random
order and I haven't figured out how to map them to Pokémon yet.
"""
import os
import sys
WIDTH = 64
HEIGHT = 64
def translate_pixel(from_index):
"""Translate a pixel index in the raw, tiled sprite into a pixel index in a
regular rows-and-columns image.
"""
# Pixels are arranged in 8×8-pixel tiles
(tile, inner_index) = divmod(from_index, 64)
(tile_y, tile_x) = divmod(tile, WIDTH // 8)
# ... And the pixels within each tile are arranged in a funky iterative
# Z-shaped pattern, which we'll unscramble with some fancy bit-math:
# https://en.wikipedia.org/wiki/Z-order_curve#Coordinate_values
# (This is a common 3DS sprite thing. No, I don't know why.)
inner_y = (
(inner_index & 0b000010) >> 1 |
(inner_index & 0b001000) >> 2 |
(inner_index & 0b100000) >> 3
)
inner_x = (
(inner_index & 0b000001) |
(inner_index & 0b000100) >> 1 |
(inner_index & 0b010000) >> 2
)
# These sprites in particular are upside-down for some reason
y = HEIGHT - 1 - (tile_y * 8 + inner_y)
x = tile_x * 8 + inner_x
return y * WIDTH + x
def pixel_slices():
"""Prepare a list of slices to unscramble each sprite.
Specifically, each sprite is a flat bytes([b, g, r, b, g, r, ...]) with all
the pixels out of order (see translate_pixel), and this function returns a
list of slices where slices[n] will pull out the correct bytes([r, g, b])
for the nth pixel.
"""
slices = [None] * (WIDTH * HEIGHT)
for from_index in range(WIDTH * HEIGHT):
to_index = translate_pixel(from_index)
from_index *= 3
slices[to_index] = slice(
from_index + 2,
from_index - 1 if from_index > 0 else None,
-1
)
return slices
def untile(pixels, slices=pixel_slices()):
"""Untile a sprite using the slice list from pixel_slices, and return the
untiled sprite as a flat bytearray.
"""
sprite = bytearray()
extend = sprite.extend
for pixel_slice in slices:
extend(pixels[pixel_slice])
return sprite
def rip_sprites(face_graphic_path, output_dir):
"""Rip all the sprites."""
with open(face_graphic_path, 'rb') as face_graphic:
# Skip FARC header and SIR0 block, which don't seem to contain anything
# useful
face_graphic.seek(0x31900)
# Rip all the sprites
for n in range(16898):
sprite = untile(face_graphic.read(0x3000))
# Write a PPM, because I'm lazy
filename = os.path.join(output_dir, '{0}.ppm'.format(n))
with open(filename, 'wb') as ppm:
ppm.write(b'P6\n64 64\n255\n')
ppm.write(sprite)
# Parse args
if len(sys.argv) != 3:
print('Usage: {0} romfs/face_graphic.bin out_dir'.format(sys.argv[0]))
exit(1)
rip_sprites(sys.argv[1], sys.argv[2])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment