Last active
August 7, 2018 07:14
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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]); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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