Removed boomer code & skybox tiles

This commit is contained in:
NoHomoBoi 2020-09-06 21:14:13 -05:00
parent 17c0f0bae3
commit 89d89b1121
74 changed files with 57 additions and 8861 deletions

1
Jenkinsfile vendored
View File

@ -48,7 +48,6 @@ pipeline {
}
}
environment {
QEMU_IRIX = credentials('qemu-irix')
ROMS_DIR = credentials('roms')
}
}

View File

@ -518,11 +518,10 @@ endif
# TODO: Remove -DEXTERNAL_DATA
# Load external textures
CC_CHECK += -DEXTERNAL_DATA -DFS_BASEDIR="\"$(BASEDIR)\""
CFLAGS += -DEXTERNAL_DATA -DFS_BASEDIR="\"$(BASEDIR)\""
CC_CHECK += -DFS_BASEDIR="\"$(BASEDIR)\""
CFLAGS += -DFS_BASEDIR="\"$(BASEDIR)\""
# tell skyconv to write names instead of actual texture data and save the split tiles so we can use them later
SKYTILE_DIR := $(BUILD_DIR)/textures/skybox_tiles
SKYCONV_ARGS := --store-names --write-tiles "$(SKYTILE_DIR)"
ASFLAGS := -I include -I $(BUILD_DIR) $(VERSION_ASFLAGS)
@ -567,7 +566,6 @@ TEXTCONV = $(TOOLS_DIR)/textconv
AIFF_EXTRACT_CODEBOOK = $(TOOLS_DIR)/aiff_extract_codebook
VADPCM_ENC = $(TOOLS_DIR)/vadpcm_enc
EXTRACT_DATA_FOR_MIO = $(TOOLS_DIR)/extract_data_for_mio
SKYCONV = $(TOOLS_DIR)/skyconv
SHA1SUM = sha1sum
ZEROTERM = $(PYTHON) $(TOOLS_DIR)/zeroterm.py

View File

@ -1592,7 +1592,7 @@ static const Lights1 segment2_lights_unused = gdSPDefLights1(
);
// 0x02014470 - 0x020144B0
static const Mtx matrix_identity = {
const Mtx matrix_identity = {
{{1.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
@ -1704,16 +1704,6 @@ const Gfx dl_skybox_begin[] = {
gsSPEndDisplayList(),
};
// 0x02014738 - 0x02014768
const Gfx dl_skybox_tile_tex_settings[] = {
gsSPMatrix(&matrix_identity, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH),
gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON),
gsDPTileSync(),
gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 0, G_TX_RENDERTILE, 0, G_TX_CLAMP, 5, G_TX_NOLOD, G_TX_CLAMP, 5, G_TX_NOLOD),
gsDPSetTileSize(0, 0, 0, (32 - 1) << G_TEXTURE_IMAGE_FRAC, (32 - 1) << G_TEXTURE_IMAGE_FRAC),
gsSPEndDisplayList(),
};
// 0x02014768 - 0x02014790
const Gfx dl_skybox_end[] = {
gsDPPipeSync(),

View File

@ -223,7 +223,7 @@ typedef _W64 int ptrdiff_t;
/* SGI MIPSPro doesn't like stdint.h in C++ mode */
/* ID: 3376260 Solaris 9 has inttypes.h, but not stdint.h */
#if (defined(__sgi) || defined(__sun)) && !defined(__GNUC__)
#if (defined(__sun)) && !defined(__GNUC__)
#include <inttypes.h>
#else
#include <stdint.h>

View File

@ -2,41 +2,9 @@
#define STDARG_H
// When not building with IDO, use the builtin vaarg macros for portability.
#ifndef __sgi
#define va_list __builtin_va_list
#define va_start __builtin_va_start
#define va_arg __builtin_va_arg
#define va_end __builtin_va_end
#else
typedef char *va_list;
#define _FP 1
#define _INT 0
#define _STRUCT 2
#define _VA_FP_SAVE_AREA 0x10
#define _VA_ALIGN(p, a) (((unsigned int)(((char *)p) + ((a) > 4 ? (a) : 4) - 1)) & -((a) > 4 ? (a) : 4))
#define va_start(vp, parmN) (vp = ((va_list)&parmN + sizeof(parmN)))
#define __va_stack_arg(list, mode) \
( \
((list) = (char *)_VA_ALIGN(list, __builtin_alignof(mode)) + \
_VA_ALIGN(sizeof(mode), 4)), \
(((char *)list) - (_VA_ALIGN(sizeof(mode), 4) - sizeof(mode))))
#define __va_double_arg(list, mode) \
( \
(((long)list & 0x1) /* 1 byte aligned? */ \
? (list = (char *)((long)list + 7), (char *)((long)list - 6 - _VA_FP_SAVE_AREA)) \
: (((long)list & 0x2) /* 2 byte aligned? */ \
? (list = (char *)((long)list + 10), (char *)((long)list - 24 - _VA_FP_SAVE_AREA)) \
: __va_stack_arg(list, mode))))
#define va_arg(list, mode) ((mode *)(((__builtin_classof(mode) == _FP && \
__builtin_alignof(mode) == sizeof(double)) \
? __va_double_arg(list, mode) \
: __va_stack_arg(list, mode))))[-1]
#define va_end(__list)
#endif
#endif

View File

@ -3,15 +3,7 @@
#include "platform_info.h"
#ifndef __sgi
#define GLOBAL_ASM(...)
#endif
#if !defined(__sgi) && (!defined(NON_MATCHING) || !defined(AVOID_UB))
// asm-process isn't supported outside of IDO, and undefined behavior causes
// crashes.
#error Matching build is only possible on IDO; please build with NON_MATCHING=1.
#endif
#define ARRAY_COUNT(arr) (s32)(sizeof(arr) / sizeof(arr[0]))

View File

@ -164,11 +164,7 @@ u8 audioString118__[] = "";
// No-op printf macro which leaves string literals in rodata in IDO. (IDO
// doesn't support variadic macros, so instead they let the parameter list
// expand to a no-op comma expression.) See also goddard/gd_main.h.
#ifdef __sgi
#define stubbed_printf
#else
#define stubbed_printf(...)
#endif
struct Sound {
s32 soundBits;

View File

@ -871,7 +871,6 @@ void load_sequence_internal(u32 player, u32 seqId, s32 loadAsync) {
seqPlayer->scriptState.pc = sequenceData;
}
#ifdef EXTERNAL_DATA
# define LOAD_DATA(x) load_sound_res((const char *)x)
# include <stdio.h>
# include <stdlib.h>
@ -882,9 +881,6 @@ static inline void *load_sound_res(const char *path) {
// can't free it immediately after in audio_init()
return data;
}
#else
# define LOAD_DATA(x) x
#endif
// (void) must be omitted from parameters
void audio_init() {

View File

@ -7,11 +7,7 @@
#ifdef VERSION_EU
#ifdef __sgi
#define stubbed_printf
#else
#define stubbed_printf(...)
#endif
#define SAMPLES_TO_OVERPRODUCE 0x10
#define EXTRA_BUFFERED_AI_SAMPLES_TARGET 0x40

View File

@ -162,14 +162,6 @@ void seq_channel_layer_process_script(struct SequenceChannelLayer *layer) {
// inlined copt var that gets pulled out to the rest of the function
unsigned char _Kqi6;
//! Copt: manually inline these functions in the scope of this routine
#ifdef __sgi
#pragma inline routine(m64_read_u8)
#pragma inline routine(m64_read_compressed_u16)
#pragma inline routine(m64_read_s16)
#pragma inline routine(get_instrument)
#endif
sameSound = TRUE;
if (layer->enabled == FALSE) {
return;

View File

@ -121,17 +121,6 @@ u8 gMenuHoldKeyIndex = 0;
u8 gMenuHoldKeyTimer = 0;
s32 gDialogResponse = 0;
#if !defined(EXTERNAL_DATA) && (defined(VERSION_JP) || defined(VERSION_SH) || defined(VERSION_EU))
#ifdef VERSION_EU
#define CHCACHE_BUFLEN (8 * 8) // EU only converts 8x8
#else
#define CHCACHE_BUFLEN (8 * 16) // JP only converts 8x16 or 16x8 characters
#endif
// stores char data unpacked from ia1 to ia8 or ia4
// so that it won't be reconverted every time a character is rendered
static struct CachedChar { u8 used; u8 data[CHCACHE_BUFLEN]; } charCache[256];
#endif // VERSION
void create_dl_identity_matrix(void) {
Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx));
@ -235,16 +224,7 @@ static inline void alloc_ia8_text_from_i1(u8 *out, u16 *in, s16 width, s16 heigh
}
static inline u8 *convert_ia8_char(u8 c, u16 *tex, s16 w, s16 h) {
#ifdef EXTERNAL_DATA
return (u8 *)tex; // the data's just a name
#else
if (!tex) return NULL;
if (!charCache[c].used) {
charCache[c].used = 1;
alloc_ia8_text_from_i1(charCache[c].data, tex, w, h);
}
return charCache[c].data;
#endif
}
#endif
@ -296,16 +276,7 @@ static void alloc_ia4_tex_from_i1(u8 *out, u8 *in, s16 width, s16 height) {
}
static u8 *convert_ia4_char(u8 c, u8 *tex, s16 w, s16 h) {
#ifdef EXTERNAL_DATA
return tex; // the data's just a name
#else
if (!tex) return NULL;
if (!charCache[c].used) {
charCache[c].used = 1;
alloc_ia4_tex_from_i1(charCache[c].data, tex, w, h);
}
return charCache[c].data;
#endif
return tex;
}
void render_generic_char_at_pos(s16 xPos, s16 yPos, u8 c) {

View File

@ -29,7 +29,7 @@ extern Gfx dl_shadow_9_verts[];
extern Gfx dl_shadow_4_verts[];
extern Gfx dl_shadow_end[];
extern Gfx dl_skybox_begin[];
extern Gfx dl_skybox_tile_tex_settings[];
extern const Mtx matrix_identity;
extern Gfx dl_skybox_end[];
extern Gfx dl_waterbox_ia16_begin[];
extern Gfx dl_waterbox_rgba16_begin[];

View File

@ -85,32 +85,29 @@ u8 sSkyboxColors[][3] = {
/**
* Constant used to scale the skybox horizontally to a multiple of the screen's width
*/
#define SKYBOX_WIDTH (2 * SCREEN_WIDTH)
#define SKYBOX_WIDTH (4 * SCREEN_WIDTH)
/**
* Constant used to scale the skybox vertically to a multiple of the screen's height
*/
#define SKYBOX_HEIGHT (2 * SCREEN_HEIGHT)
#define SKYBOX_HEIGHT (4 * SCREEN_HEIGHT)
/**
* The tile's width in world space.
* By default, two full tiles can fit in the screen.
*/
#define SKYBOX_TILE_WIDTH SKYBOX_WIDTH
#define SKYBOX_TILE_WIDTH (SCREEN_WIDTH / 2)
/**
* The tile's height in world space.
* By default, two full tiles can fit in the screen.
*/
#define SKYBOX_TILE_HEIGHT SKYBOX_HEIGHT
#define SKYBOX_TILE_HEIGHT (SCREEN_HEIGHT / 2)
/**
* The horizontal length of the skybox tilemap in tiles.
*/
#define SKYBOX_COLS 1
/**
* The vertical length of the skybox tilemap in tiles.
*/
#define SKYBOX_ROWS 1
#define SKYBOX_COLS (10)
#define SKYBOX_IMAGE_SIZE (248)
/**
* Convert the camera's yaw into an x position into the scaled skybox image.
@ -131,7 +128,7 @@ f32 calculate_skybox_scaled_x(s8 player, f32 fov) {
if (scaledX > SKYBOX_WIDTH) {
scaledX -= (s32) scaledX / SKYBOX_WIDTH * SKYBOX_WIDTH;
}
return 0;
return SKYBOX_WIDTH - scaledX;
}
/**
@ -149,7 +146,7 @@ f32 calculate_skybox_scaled_y(s8 player, UNUSED f32 fov) {
// Since pitch can be negative, and the tile grid starts 1 octant above the camera's focus, add
// 5 octants to the y position
f32 scaledY = degreesToScale + 5 * SKYBOX_TILE_HEIGHT / 4;
f32 scaledY = degreesToScale + 5 * SKYBOX_TILE_HEIGHT;
if (scaledY > SKYBOX_HEIGHT) {
scaledY = SKYBOX_HEIGHT;
@ -167,7 +164,7 @@ static int get_top_left_tile_idx(s8 player) {
s32 tileCol = sSkyBoxInfo[player].scaledX / SKYBOX_TILE_WIDTH;
s32 tileRow = (SKYBOX_HEIGHT - sSkyBoxInfo[player].scaledY) / SKYBOX_TILE_HEIGHT;
return 0;
return tileRow * SKYBOX_COLS + tileCol;
}
/**
@ -182,16 +179,16 @@ Vtx *make_skybox_rect(s32 tileIndex, s8 colorIndex) {
s16 x = tileIndex % SKYBOX_COLS * SKYBOX_TILE_WIDTH;
s16 y = SKYBOX_HEIGHT - tileIndex / SKYBOX_COLS * SKYBOX_TILE_HEIGHT;
s16 w = SKYBOX_IMAGE_SIZE / (SKYBOX_COLS - 2);
s16 h = SKYBOX_IMAGE_SIZE / (SKYBOX_COLS - 2);
s16 tx = ((tileIndex % SKYBOX_COLS) * w) % SKYBOX_IMAGE_SIZE;
s16 ty = ((tileIndex / SKYBOX_COLS) * h) % SKYBOX_IMAGE_SIZE;
if (verts != NULL) {
make_vertex(verts, 0, x, y, -1, 0, 0, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1],
sSkyboxColors[colorIndex][2], 255);
make_vertex(verts, 1, x, y - SKYBOX_TILE_HEIGHT, -1, 0, 31 << 5, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1],
sSkyboxColors[colorIndex][2], 255);
make_vertex(verts, 2, x + SKYBOX_TILE_WIDTH, y - SKYBOX_TILE_HEIGHT, -1, 31 << 5, 31 << 5, sSkyboxColors[colorIndex][0],
sSkyboxColors[colorIndex][1], sSkyboxColors[colorIndex][2], 255);
make_vertex(verts, 3, x + SKYBOX_TILE_WIDTH, y, -1, 31 << 5, 0, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1],
sSkyboxColors[colorIndex][2], 255);
} else {
make_vertex(verts, 0, x, y, -1, tx << 5, ty << 5, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1], sSkyboxColors[colorIndex][2], 255);
make_vertex(verts, 1, x, y - SKYBOX_TILE_HEIGHT, -1, tx << 5, (ty + w) << 5, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1], sSkyboxColors[colorIndex][2], 255);
make_vertex(verts, 2, x + SKYBOX_TILE_WIDTH, y - SKYBOX_TILE_HEIGHT, -1, (tx + w) << 5, (ty + h) << 5, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1], sSkyboxColors[colorIndex][2], 255);
make_vertex(verts, 3, x + SKYBOX_TILE_WIDTH, y, -1, (tx + w) << 5, ty << 5, sSkyboxColors[colorIndex][0], sSkyboxColors[colorIndex][1], sSkyboxColors[colorIndex][2], 255);
}
return verts;
}
@ -205,13 +202,21 @@ void draw_skybox_tile_grid(Gfx **dlist, s8 background, s8 player, s8 colorIndex)
s32 row;
s32 col;
for (row = 0; row < 3; row++) {
for (col = 0; col < 3; col++) {
s32 tileIndex = sSkyBoxInfo[player].upperLeftTile + row * SKYBOX_COLS + col;
const u8 *const texture = sSkyboxTextures[background];
Vtx *vertices = make_skybox_rect(0, colorIndex);
s16 x = tileIndex % SKYBOX_COLS * SKYBOX_TILE_WIDTH;
s16 y = SKYBOX_HEIGHT - tileIndex / SKYBOX_COLS * SKYBOX_TILE_HEIGHT;
gLoadBlockTexture((*dlist)++, 32, 32, G_IM_FMT_RGBA, texture);
gLoadBlockTexture((*dlist)++, SKYBOX_IMAGE_SIZE, SKYBOX_IMAGE_SIZE, G_IM_FMT_RGBA, texture);
Vtx *vertices = make_skybox_rect(tileIndex, colorIndex);
gSPVertex((*dlist)++, VIRTUAL_TO_PHYSICAL(vertices), 4, 0);
gSPDisplayList((*dlist)++, dl_draw_quad_verts_0123);
}
}
}
void *create_skybox_ortho_matrix(s8 player) {
f32 left = sSkyBoxInfo[player].scaledX;
@ -240,7 +245,7 @@ void *create_skybox_ortho_matrix(s8 player) {
* Creates the skybox's display list, then draws the 3x3 grid of tiles.
*/
Gfx *init_skybox_display_list(s8 player, s8 background, s8 colorIndex) {
s32 dlCommandCount = 5 + (3 * 3) * 7; // 5 for the start and end, plus 9 skybox tiles
s32 dlCommandCount = 7 + (3 * 3) * 7; // 5 for the start and end, plus 9 skybox tiles
void *skybox = alloc_display_list(dlCommandCount * sizeof(Gfx));
Gfx *dlist = skybox;
@ -251,7 +256,13 @@ Gfx *init_skybox_display_list(s8 player, s8 background, s8 colorIndex) {
gSPDisplayList(dlist++, dl_skybox_begin);
gSPMatrix(dlist++, VIRTUAL_TO_PHYSICAL(ortho), G_MTX_PROJECTION | G_MTX_MUL | G_MTX_NOPUSH);
gSPDisplayList(dlist++, dl_skybox_tile_tex_settings);
gSPMatrix(dlist++, &matrix_identity, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
gSPTexture(dlist++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
gDPTileSync(dlist++);
gDPSetTile(dlist++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 0, G_TX_RENDERTILE, 0, G_TX_CLAMP, 5, G_TX_NOLOD, G_TX_CLAMP, 5, G_TX_NOLOD);
gDPSetTileSize(dlist++, 0, 0, 0, (SKYBOX_IMAGE_SIZE - 1) << G_TEXTURE_IMAGE_FRAC, (SKYBOX_IMAGE_SIZE - 1) << G_TEXTURE_IMAGE_FRAC);
draw_skybox_tile_grid(&dlist, background, player, colorIndex);
gSPDisplayList(dlist++, dl_skybox_end);
gSPEndDisplayList(dlist);

View File

@ -72,9 +72,7 @@ unsigned int configKeyStickLeft[MAX_BINDS] = { 0x001E, VK_INVALID, VK_INVALID
unsigned int configKeyStickRight[MAX_BINDS] = { 0x0020, VK_INVALID, VK_INVALID };
unsigned int configStickDeadzone = 16; // 16*DEADZONE_STEP=4960 (the original default deadzone)
unsigned int configRumbleStrength = 50;
#ifdef EXTERNAL_DATA
bool configPrecacheRes = true;
#endif
#ifdef BETTERCAMERA
// BetterCamera settings
unsigned int configCameraXSens = 50;
@ -122,9 +120,7 @@ static const struct ConfigOption options[] = {
{.name = "key_stickright", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickRight},
{.name = "stick_deadzone", .type = CONFIG_TYPE_UINT, .uintValue = &configStickDeadzone},
{.name = "rumble_strength", .type = CONFIG_TYPE_UINT, .uintValue = &configRumbleStrength},
#ifdef EXTERNAL_DATA
{.name = "precache", .type = CONFIG_TYPE_BOOL, .boolValue = &configPrecacheRes},
#endif
#ifdef BETTERCAMERA
{.name = "bettercam_enable", .type = CONFIG_TYPE_BOOL, .boolValue = &configEnableCamera},
{.name = "bettercam_analog", .type = CONFIG_TYPE_BOOL, .boolValue = &configCameraAnalog},

View File

@ -39,9 +39,7 @@ extern unsigned int configKeyStickLeft[];
extern unsigned int configKeyStickRight[];
extern unsigned int configStickDeadzone;
extern unsigned int configRumbleStrength;
#ifdef EXTERNAL_DATA
extern bool configPrecacheRes;
#endif
#ifdef BETTERCAMERA
extern unsigned int configCameraXSens;
extern unsigned int configCameraYSens;

View File

@ -6,10 +6,8 @@
#include <stdbool.h>
#include <assert.h>
#ifdef EXTERNAL_DATA
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#endif
#ifndef _LANGUAGE_C
#define _LANGUAGE_C
@ -50,13 +48,8 @@
#define MAX_LIGHTS 2
#define MAX_VERTICES 64
#ifdef EXTERNAL_DATA
# define MAX_CACHED_TEXTURES 4096 // for preloading purposes
# define HASH_SHIFT 0
#else
# define MAX_CACHED_TEXTURES 512
# define HASH_SHIFT 5
#endif
#define HASHMAP_LEN (MAX_CACHED_TEXTURES * 2)
#define HASH_MASK (HASHMAP_LEN - 1)
@ -186,14 +179,12 @@ static const uint8_t missing_texture[MISSING_W * MISSING_H * 4] = {
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF,
};
#ifdef EXTERNAL_DATA
static inline size_t string_hash(const uint8_t *str) {
size_t h = 0;
for (const uint8_t *p = str; *p; p++)
h = 31 * h + *p;
return h;
}
#endif
static unsigned long get_time(void) {
return 0;
@ -288,13 +279,8 @@ static struct ColorCombiner *gfx_lookup_or_create_color_combiner(uint32_t cc_id)
}
static bool gfx_texture_cache_lookup(int tile, struct TextureHashmapNode **n, const uint8_t *orig_addr, uint32_t fmt, uint32_t siz) {
#ifdef EXTERNAL_DATA // hash and compare the data (i.e. the texture name) itself
size_t hash = string_hash(orig_addr);
#define CMPADDR(x, y) (x && !sys_strcasecmp((const char *)x, (const char *)y))
#else // hash and compare the address
size_t hash = (uintptr_t)orig_addr;
#define CMPADDR(x, y) x == y
#endif
hash = (hash >> HASH_SHIFT) & HASH_MASK;
@ -331,182 +317,6 @@ static bool gfx_texture_cache_lookup(int tile, struct TextureHashmapNode **n, co
#undef CMPADDR
}
#ifndef EXTERNAL_DATA
static void import_texture_rgba32(int tile) {
uint32_t width = rdp.texture_tile.line_size_bytes / 2;
uint32_t height = (rdp.loaded_texture[tile].size_bytes / 2) / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rdp.loaded_texture[tile].addr, width, height);
}
static void import_texture_rgba16(int tile) {
uint8_t rgba32_buf[8192];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes / 2; i++) {
uint16_t col16 = (rdp.loaded_texture[tile].addr[2 * i] << 8) | rdp.loaded_texture[tile].addr[2 * i + 1];
uint8_t a = col16 & 1;
uint8_t r = col16 >> 11;
uint8_t g = (col16 >> 6) & 0x1f;
uint8_t b = (col16 >> 1) & 0x1f;
rgba32_buf[4*i + 0] = SCALE_5_8(r);
rgba32_buf[4*i + 1] = SCALE_5_8(g);
rgba32_buf[4*i + 2] = SCALE_5_8(b);
rgba32_buf[4*i + 3] = a ? 255 : 0;
}
uint32_t width = rdp.texture_tile.line_size_bytes / 2;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_ia4(int tile) {
uint8_t rgba32_buf[32768];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes * 2; i++) {
uint8_t byte = rdp.loaded_texture[tile].addr[i / 2];
uint8_t part = (byte >> (4 - (i % 2) * 4)) & 0xf;
uint8_t intensity = part >> 1;
uint8_t alpha = part & 1;
uint8_t r = intensity;
uint8_t g = intensity;
uint8_t b = intensity;
rgba32_buf[4*i + 0] = SCALE_3_8(r);
rgba32_buf[4*i + 1] = SCALE_3_8(g);
rgba32_buf[4*i + 2] = SCALE_3_8(b);
rgba32_buf[4*i + 3] = alpha ? 255 : 0;
}
uint32_t width = rdp.texture_tile.line_size_bytes * 2;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_ia8(int tile) {
uint8_t rgba32_buf[16384];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes; i++) {
uint8_t intensity = rdp.loaded_texture[tile].addr[i] >> 4;
uint8_t alpha = rdp.loaded_texture[tile].addr[i] & 0xf;
uint8_t r = intensity;
uint8_t g = intensity;
uint8_t b = intensity;
rgba32_buf[4*i + 0] = SCALE_4_8(r);
rgba32_buf[4*i + 1] = SCALE_4_8(g);
rgba32_buf[4*i + 2] = SCALE_4_8(b);
rgba32_buf[4*i + 3] = SCALE_4_8(alpha);
}
uint32_t width = rdp.texture_tile.line_size_bytes;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_ia16(int tile) {
uint8_t rgba32_buf[8192];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes / 2; i++) {
uint8_t intensity = rdp.loaded_texture[tile].addr[2 * i];
uint8_t alpha = rdp.loaded_texture[tile].addr[2 * i + 1];
uint8_t r = intensity;
uint8_t g = intensity;
uint8_t b = intensity;
rgba32_buf[4*i + 0] = r;
rgba32_buf[4*i + 1] = g;
rgba32_buf[4*i + 2] = b;
rgba32_buf[4*i + 3] = alpha;
}
uint32_t width = rdp.texture_tile.line_size_bytes / 2;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_i4(int tile) {
uint8_t rgba32_buf[32768];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes * 2; i++) {
uint8_t byte = rdp.loaded_texture[tile].addr[i / 2];
uint8_t intensity = (byte >> (4 - (i % 2) * 4)) & 0xf;
rgba32_buf[4*i + 0] = SCALE_4_8(intensity);
rgba32_buf[4*i + 1] = SCALE_4_8(intensity);
rgba32_buf[4*i + 2] = SCALE_4_8(intensity);
rgba32_buf[4*i + 3] = 255;
}
uint32_t width = rdp.texture_tile.line_size_bytes * 2;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_i8(int tile) {
uint8_t rgba32_buf[16384];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes; i++) {
uint8_t intensity = rdp.loaded_texture[tile].addr[i];
rgba32_buf[4*i + 0] = intensity;
rgba32_buf[4*i + 1] = intensity;
rgba32_buf[4*i + 2] = intensity;
rgba32_buf[4*i + 3] = 255;
}
uint32_t width = rdp.texture_tile.line_size_bytes;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_ci4(int tile) {
uint8_t rgba32_buf[32768];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes * 2; i++) {
uint8_t byte = rdp.loaded_texture[tile].addr[i / 2];
uint8_t idx = (byte >> (4 - (i % 2) * 4)) & 0xf;
uint16_t col16 = (rdp.palette[idx * 2] << 8) | rdp.palette[idx * 2 + 1]; // Big endian load
uint8_t a = col16 & 1;
uint8_t r = col16 >> 11;
uint8_t g = (col16 >> 6) & 0x1f;
uint8_t b = (col16 >> 1) & 0x1f;
rgba32_buf[4*i + 0] = SCALE_5_8(r);
rgba32_buf[4*i + 1] = SCALE_5_8(g);
rgba32_buf[4*i + 2] = SCALE_5_8(b);
rgba32_buf[4*i + 3] = a ? 255 : 0;
}
uint32_t width = rdp.texture_tile.line_size_bytes * 2;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
static void import_texture_ci8(int tile) {
uint8_t rgba32_buf[16384];
for (uint32_t i = 0; i < rdp.loaded_texture[tile].size_bytes; i++) {
uint8_t idx = rdp.loaded_texture[tile].addr[i];
uint16_t col16 = (rdp.palette[idx * 2] << 8) | rdp.palette[idx * 2 + 1]; // Big endian load
uint8_t a = col16 & 1;
uint8_t r = col16 >> 11;
uint8_t g = (col16 >> 6) & 0x1f;
uint8_t b = (col16 >> 1) & 0x1f;
rgba32_buf[4*i + 0] = SCALE_5_8(r);
rgba32_buf[4*i + 1] = SCALE_5_8(g);
rgba32_buf[4*i + 2] = SCALE_5_8(b);
rgba32_buf[4*i + 3] = a ? 255 : 0;
}
uint32_t width = rdp.texture_tile.line_size_bytes;
uint32_t height = rdp.loaded_texture[tile].size_bytes / rdp.texture_tile.line_size_bytes;
gfx_rapi->upload_texture(rgba32_buf, width, height);
}
#else // EXTERNAL_DATA
static inline void load_texture(const char *fullpath) {
int w, h;
u64 imgsize = 0;
@ -595,8 +405,6 @@ static bool preload_texture(void *user, const char *path) {
return true;
}
#endif // EXTERNAL_DATA
static void import_texture(int tile) {
uint8_t fmt = rdp.texture_tile.fmt;
uint8_t siz = rdp.texture_tile.siz;
@ -611,56 +419,11 @@ static void import_texture(int tile) {
return;
}
#ifdef EXTERNAL_DATA
// the "texture data" is actually a C string with the path to our texture in it
// load it from an external image in our data path
char texname[SYS_MAX_PATH];
snprintf(texname, sizeof(texname), FS_TEXTUREDIR "/%s.png", (const char*)rdp.loaded_texture[tile].addr);
load_texture(texname);
#else
// the texture data is actual texture data
int t0 = get_time();
if (fmt == G_IM_FMT_RGBA) {
if (siz == G_IM_SIZ_32b) {
import_texture_rgba32(tile);
}
else if (siz == G_IM_SIZ_16b) {
import_texture_rgba16(tile);
} else {
sys_fatal("unsupported RGBA texture size: %u", siz);
}
} else if (fmt == G_IM_FMT_IA) {
if (siz == G_IM_SIZ_4b) {
import_texture_ia4(tile);
} else if (siz == G_IM_SIZ_8b) {
import_texture_ia8(tile);
} else if (siz == G_IM_SIZ_16b) {
import_texture_ia16(tile);
} else {
sys_fatal("unsupported IA texture size: %u", siz);
}
} else if (fmt == G_IM_FMT_CI) {
if (siz == G_IM_SIZ_4b) {
import_texture_ci4(tile);
} else if (siz == G_IM_SIZ_8b) {
import_texture_ci8(tile);
} else {
sys_fatal("unsupported CI texture size: %u", siz);
}
} else if (fmt == G_IM_FMT_I) {
if (siz == G_IM_SIZ_4b) {
import_texture_i4(tile);
} else if (siz == G_IM_SIZ_8b) {
import_texture_i8(tile);
} else {
sys_fatal("unsupported I texture size: %u", siz);
}
} else {
sys_fatal("unsupported texture format: %u", fmt);
}
int t1 = get_time();
//printf("Time diff: %d\n", t1 - t0);
#endif
}
static void gfx_normalize_vector(float v[3]) {
@ -1759,12 +1522,10 @@ void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, co
gfx_lookup_or_create_shader_program(precomp_shaders[i]);
}
#ifdef EXTERNAL_DATA
void gfx_precache_textures(void) {
// preload all textures
fs_walk(FS_TEXTUREDIR, preload_texture, NULL, true);
}
#endif
struct GfxRenderingAPI *gfx_get_current_rendering_api(void) {
return gfx_rapi;

View File

@ -240,14 +240,12 @@ void main_func(void) {
inited = true;
#ifdef EXTERNAL_DATA
// precache data if needed
if (configPrecacheRes) {
fprintf(stdout, "precaching data\n");
fflush(stdout);
gfx_precache_textures();
}
#endif
#ifdef DISCORDRPC
discord_init();

View File

@ -51,7 +51,7 @@ char* read_file(char* name){
void preloadTexts(){
char * file = ENGLISH;
char * file = SPANISH;
#ifndef WIN32
char * language_file = realpath(file, NULL);

View File

@ -2,23 +2,11 @@ CC := gcc
CXX := g++
CFLAGS := -I../include -I. -Wall -Wextra -Wno-unused-parameter -pedantic -std=c99 -O2 -s
LDFLAGS := -lm
PROGRAMS := n64graphics n64graphics_ci mio0 n64cksum textconv patch_libultra_math aifc_decode aiff_extract_codebook vadpcm_enc tabledesign extract_data_for_mio skyconv
PROGRAMS := textconv aifc_decode aiff_extract_codebook vadpcm_enc tabledesign
default: all
n64graphics_SOURCES := n64graphics.c utils.c
n64graphics_CFLAGS := -DN64GRAPHICS_STANDALONE
n64graphics_ci_SOURCES := n64graphics_ci_dir/n64graphics_ci.c n64graphics_ci_dir/exoquant/exoquant.c n64graphics_ci_dir/utils.c
mio0_SOURCES := libmio0.c
mio0_CFLAGS := -DMIO0_STANDALONE
n64cksum_SOURCES := n64cksum.c utils.c
n64cksum_CFLAGS := -DN64CKSUM_STANDALONE
textconv_SOURCES := textconv.c utf8.c hashtable.c
patch_libultra_math_SOURCES := patch_libultra_math.c
aifc_decode_SOURCES := aifc_decode.c
@ -31,10 +19,6 @@ tabledesign_LDFLAGS := -Laudiofile -laudiofile -lstdc++ -lm
vadpcm_enc_SOURCES := sdk-tools/adpcm/vadpcm_enc.c sdk-tools/adpcm/vpredictor.c sdk-tools/adpcm/quant.c sdk-tools/adpcm/util.c sdk-tools/adpcm/vencode.c
vadpcm_enc_CFLAGS := -Wno-unused-result -Wno-uninitialized -Wno-sign-compare -Wno-absolute-value
extract_data_for_mio_SOURCES := extract_data_for_mio.c
skyconv_SOURCES := skyconv.c n64graphics.c utils.c
LIBAUDIOFILE := audiofile/libaudiofile.a
$(LIBAUDIOFILE):

View File

@ -1,868 +0,0 @@
#!/usr/bin/env python3
import argparse
import tempfile
import struct
import copy
import sys
import re
import os
MAX_FN_SIZE = 100
EI_NIDENT = 16
EI_CLASS = 4
EI_DATA = 5
EI_VERSION = 6
EI_OSABI = 7
EI_ABIVERSION = 8
STN_UNDEF = 0
SHN_UNDEF = 0
SHN_ABS = 0xfff1
SHN_COMMON = 0xfff2
SHN_XINDEX = 0xffff
SHN_LORESERVE = 0xff00
STT_NOTYPE = 0
STT_OBJECT = 1
STT_FUNC = 2
STT_SECTION = 3
STT_FILE = 4
STT_COMMON = 5
STT_TLS = 6
STB_LOCAL = 0
STB_GLOBAL = 1
STB_WEAK = 2
STV_DEFAULT = 0
STV_INTERNAL = 1
STV_HIDDEN = 2
STV_PROTECTED = 3
SHT_NULL = 0
SHT_PROGBITS = 1
SHT_SYMTAB = 2
SHT_STRTAB = 3
SHT_RELA = 4
SHT_HASH = 5
SHT_DYNAMIC = 6
SHT_NOTE = 7
SHT_NOBITS = 8
SHT_REL = 9
SHT_SHLIB = 10
SHT_DYNSYM = 11
SHT_INIT_ARRAY = 14
SHT_FINI_ARRAY = 15
SHT_PREINIT_ARRAY = 16
SHT_GROUP = 17
SHT_SYMTAB_SHNDX = 18
SHT_MIPS_GPTAB = 0x70000003
SHT_MIPS_DEBUG = 0x70000005
SHT_MIPS_REGINFO = 0x70000006
SHT_MIPS_OPTIONS = 0x7000000d
SHF_WRITE = 0x1
SHF_ALLOC = 0x2
SHF_EXECINSTR = 0x4
SHF_MERGE = 0x10
SHF_STRINGS = 0x20
SHF_INFO_LINK = 0x40
SHF_LINK_ORDER = 0x80
SHF_OS_NONCONFORMING = 0x100
SHF_GROUP = 0x200
SHF_TLS = 0x400
R_MIPS_32 = 2
R_MIPS_26 = 4
R_MIPS_HI16 = 5
R_MIPS_LO16 = 6
class ElfHeader:
"""
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
"""
def __init__(self, data):
self.e_ident = data[:EI_NIDENT]
self.e_type, self.e_machine, self.e_version, self.e_entry, self.e_phoff, self.e_shoff, self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum, self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack('>HHIIIIIHHHHHH', data[EI_NIDENT:])
assert self.e_ident[EI_CLASS] == 1 # 32-bit
assert self.e_ident[EI_DATA] == 2 # big-endian
assert self.e_type == 1 # relocatable
assert self.e_machine == 8 # MIPS I Architecture
assert self.e_phoff == 0 # no program header
assert self.e_shoff != 0 # section header
assert self.e_shstrndx != SHN_UNDEF
def to_bin(self):
return self.e_ident + struct.pack('>HHIIIIIHHHHHH', self.e_type,
self.e_machine, self.e_version, self.e_entry, self.e_phoff,
self.e_shoff, self.e_flags, self.e_ehsize, self.e_phentsize,
self.e_phnum, self.e_shentsize, self.e_shnum, self.e_shstrndx)
class Symbol:
"""
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
"""
def __init__(self, data, strtab):
self.st_name, self.st_value, self.st_size, st_info, self.st_other, self.st_shndx = struct.unpack('>IIIBBH', data)
assert self.st_shndx != SHN_XINDEX, "too many sections (SHN_XINDEX not supported)"
self.bind = st_info >> 4
self.type = st_info & 15
self.name = strtab.lookup_str(self.st_name)
self.visibility = self.st_other & 3
def to_bin(self):
st_info = (self.bind << 4) | self.type
return struct.pack('>IIIBBH', self.st_name, self.st_value, self.st_size, st_info, self.st_other, self.st_shndx)
class Relocation:
def __init__(self, data, sh_type):
self.sh_type = sh_type
if sh_type == SHT_REL:
self.r_offset, self.r_info = struct.unpack('>II', data)
else:
self.r_offset, self.r_info, self.r_addend = struct.unpack('>III', data)
self.sym_index = self.r_info >> 8
self.rel_type = self.r_info & 0xff
def to_bin(self):
self.r_info = (self.sym_index << 8) | self.rel_type
if self.sh_type == SHT_REL:
return struct.pack('>II', self.r_offset, self.r_info)
else:
return struct.pack('>III', self.r_offset, self.r_info, self.r_addend)
class Section:
"""
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
"""
def __init__(self, header, data, index):
self.sh_name, self.sh_type, self.sh_flags, self.sh_addr, self.sh_offset, self.sh_size, self.sh_link, self.sh_info, self.sh_addralign, self.sh_entsize = struct.unpack('>IIIIIIIIII', header)
assert not self.sh_flags & SHF_LINK_ORDER
if self.sh_entsize != 0:
assert self.sh_size % self.sh_entsize == 0
if self.sh_type == SHT_NOBITS:
self.data = ''
else:
self.data = data[self.sh_offset:self.sh_offset + self.sh_size]
self.index = index
self.relocated_by = []
@staticmethod
def from_parts(sh_name, sh_type, sh_flags, sh_link, sh_info, sh_addralign, sh_entsize, data, index):
header = struct.pack('>IIIIIIIIII', sh_name, sh_type, sh_flags, 0, 0, len(data), sh_link, sh_info, sh_addralign, sh_entsize)
return Section(header, data, index)
def lookup_str(self, index):
assert self.sh_type == SHT_STRTAB
to = self.data.find(b'\0', index)
assert to != -1
return self.data[index:to].decode('utf-8')
def add_str(self, string):
assert self.sh_type == SHT_STRTAB
ret = len(self.data)
self.data += bytes(string, 'utf-8') + b'\0'
return ret
def is_rel(self):
return self.sh_type == SHT_REL or self.sh_type == SHT_RELA
def header_to_bin(self):
if self.sh_type != SHT_NOBITS:
self.sh_size = len(self.data)
return struct.pack('>IIIIIIIIII', self.sh_name, self.sh_type, self.sh_flags, self.sh_addr, self.sh_offset, self.sh_size, self.sh_link, self.sh_info, self.sh_addralign, self.sh_entsize)
def late_init(self, sections):
if self.sh_type == SHT_SYMTAB:
self.init_symbols(sections)
elif self.is_rel():
self.rel_target = sections[self.sh_info]
self.rel_target.relocated_by.append(self)
self.init_relocs()
def find_symbol(self, name):
assert self.sh_type == SHT_SYMTAB
for s in self.symbol_entries:
if s.name == name:
return (s.st_shndx, s.st_value)
return None
def init_symbols(self, sections):
assert self.sh_type == SHT_SYMTAB
assert self.sh_entsize == 16
self.strtab = sections[self.sh_link]
entries = []
for i in range(0, self.sh_size, self.sh_entsize):
entries.append(Symbol(self.data[i:i+self.sh_entsize], self.strtab))
self.symbol_entries = entries
def init_relocs(self):
assert self.is_rel()
entries = []
for i in range(0, self.sh_size, self.sh_entsize):
entries.append(Relocation(self.data[i:i+self.sh_entsize], self.sh_type))
self.relocations = entries
def local_symbols(self):
assert self.sh_type == SHT_SYMTAB
return self.symbol_entries[:self.sh_info]
def global_symbols(self):
assert self.sh_type == SHT_SYMTAB
return self.symbol_entries[self.sh_info:]
class ElfFile:
def __init__(self, data):
self.data = data
assert data[:4] == b'\x7fELF', "not an ELF file"
self.elf_header = ElfHeader(data[0:52])
offset, size = self.elf_header.e_shoff, self.elf_header.e_shentsize
null_section = Section(data[offset:offset + size], data, 0)
num_sections = self.elf_header.e_shnum or null_section.sh_size
self.sections = [null_section]
for i in range(1, num_sections):
ind = offset + i * size
self.sections.append(Section(data[ind:ind + size], data, i))
symtab = None
for s in self.sections:
if s.sh_type == SHT_SYMTAB:
assert not symtab
symtab = s
assert symtab is not None
self.symtab = symtab
shstr = self.sections[self.elf_header.e_shstrndx]
for s in self.sections:
s.name = shstr.lookup_str(s.sh_name)
s.late_init(self.sections)
def find_section(self, name):
for s in self.sections:
if s.name == name:
return s
return None
def add_section(self, name, sh_type, sh_flags, sh_link, sh_info, sh_addralign, sh_entsize, data):
shstr = self.sections[self.elf_header.e_shstrndx]
sh_name = shstr.add_str(name)
s = Section.from_parts(sh_name=sh_name, sh_type=sh_type,
sh_flags=sh_flags, sh_link=sh_link, sh_info=sh_info,
sh_addralign=sh_addralign, sh_entsize=sh_entsize, data=data,
index=len(self.sections))
self.sections.append(s)
s.name = name
s.late_init(self.sections)
return s
def drop_irrelevant_sections(self):
# We can only drop sections at the end, since otherwise section
# references might be wrong. Luckily, these sections typically are.
while self.sections[-1].sh_type in [SHT_MIPS_DEBUG, SHT_MIPS_GPTAB]:
self.sections.pop()
def write(self, filename):
outfile = open(filename, 'wb')
outidx = 0
def write_out(data):
nonlocal outidx
outfile.write(data)
outidx += len(data)
def pad_out(align):
if align and outidx % align:
write_out(b'\0' * (align - outidx % align))
self.elf_header.e_shnum = len(self.sections)
write_out(self.elf_header.to_bin())
for s in self.sections:
if s.sh_type != SHT_NOBITS and s.sh_type != SHT_NULL:
pad_out(s.sh_addralign)
s.sh_offset = outidx
write_out(s.data)
pad_out(4)
self.elf_header.e_shoff = outidx
for s in self.sections:
write_out(s.header_to_bin())
outfile.seek(0)
outfile.write(self.elf_header.to_bin())
outfile.close()
def is_temp_name(name):
return name.startswith('_asmpp_')
class GlobalState:
def __init__(self, min_instr_count, skip_instr_count):
# A value that hopefully never appears as a 32-bit rodata constant (or we
# miscompile late rodata). Increases by 1 in each step.
self.late_rodata_hex = 0xE0123456
self.namectr = 0
self.min_instr_count = min_instr_count
self.skip_instr_count = skip_instr_count
def make_name(self, cat):
self.namectr += 1
return '_asmpp_{}{}'.format(cat, self.namectr)
class GlobalAsmBlock:
def __init__(self):
self.cur_section = '.text'
self.asm_conts = []
self.late_rodata_asm_conts = []
self.late_rodata_alignment = 0
self.text_glabels = []
self.fn_section_sizes = {
'.text': 0,
'.data': 0,
'.bss': 0,
'.rodata': 0,
'.late_rodata': 0,
}
self.fn_ins_inds = []
self.num_lines = 0
def add_sized(self, size, line):
if self.cur_section in ['.text', '.late_rodata']:
assert size % 4 == 0, "size must be a multiple of 4 on line: " + line
assert size >= 0
self.fn_section_sizes[self.cur_section] += size
if self.cur_section == '.text':
assert self.text_glabels, ".text block without an initial glabel"
self.fn_ins_inds.append((self.num_lines, size // 4))
def process_line(self, line):
line = re.sub(r'/\*.*?\*/', '', line)
line = re.sub(r'#.*', '', line)
line = line.strip()
changed_section = False
if line.startswith('glabel ') and self.cur_section == '.text':
self.text_glabels.append(line.split()[1])
if not line:
pass # empty line
elif line.startswith('glabel ') or (' ' not in line and line.endswith(':')):
pass # label
elif line.startswith('.section') or line in ['.text', '.data', '.rdata', '.rodata', '.bss', '.late_rodata']:
# section change
self.cur_section = '.rodata' if line == '.rdata' else line.split(',')[0].split()[-1]
assert self.cur_section in ['.data', '.text', '.rodata', '.late_rodata', '.bss'], \
"unrecognized .section directive"
changed_section = True
elif line.startswith('.late_rodata_alignment'):
assert self.cur_section == '.late_rodata'
self.late_rodata_alignment = int(line.split()[1])
assert self.late_rodata_alignment in [4, 8]
changed_section = True
elif line.startswith('.incbin'):
self.add_sized(int(line.split(',')[-1].strip(), 0), line)
elif line.startswith('.word') or line.startswith('.float'):
self.add_sized(4 * len(line.split(',')), line)
elif line.startswith('.double'):
self.add_sized(8 * len(line.split(',')), line)
elif line.startswith('.space'):
self.add_sized(int(line.split()[1], 0), line)
elif line.startswith('.'):
# .macro, .ascii, .asciiz, .balign, .align, ...
assert False, 'not supported yet: ' + line
else:
# Unfortunately, macros are hard to support for .rodata --
# we don't know how how space they will expand to before
# running the assembler, but we need that information to
# construct the C code. So if we need that we'll either
# need to run the assembler twice (at least in some rare
# cases), or change how this program is invoked.
# Similarly, we can't currently deal with pseudo-instructions
# that expand to several real instructions.
assert self.cur_section == '.text', "instruction or macro call in non-.text section? not supported: " + line
self.add_sized(4, line)
if self.cur_section == '.late_rodata':
if not changed_section:
self.late_rodata_asm_conts.append(line)
else:
self.asm_conts.append(line)
self.num_lines += 1
def finish(self, state):
src = [''] * (self.num_lines + 1)
late_rodata = []
late_rodata_fn_output = []
if self.fn_section_sizes['.late_rodata'] > 0:
# Generate late rodata by emitting unique float constants.
# This requires 3 instructions for each 4 bytes of rodata.
# If we know alignment, we can use doubles, which give 3
# instructions for 8 bytes of rodata.
size = self.fn_section_sizes['.late_rodata'] // 4
skip_next = False
for i in range(size):
if skip_next:
skip_next = False
continue
if (state.late_rodata_hex & 0xffff) == 0:
# Avoid lui
state.late_rodata_hex += 1
dummy_bytes = struct.pack('>I', state.late_rodata_hex)
state.late_rodata_hex += 1
late_rodata.append(dummy_bytes)
if self.late_rodata_alignment == 4 * ((i + 1) % 2 + 1) and i + 1 < size:
late_rodata.append(dummy_bytes)
fval, = struct.unpack('>d', dummy_bytes * 2)
late_rodata_fn_output.append('*(volatile double*)0 = {};'.format(fval))
skip_next = True
else:
fval, = struct.unpack('>f', dummy_bytes)
late_rodata_fn_output.append('*(volatile float*)0 = {}f;'.format(fval))
late_rodata_fn_output.append('')
late_rodata_fn_output.append('')
text_name = None
if self.fn_section_sizes['.text'] > 0 or late_rodata_fn_output:
text_name = state.make_name('func')
src[0] = 'void {}(void) {{'.format(text_name)
src[self.num_lines] = '}'
instr_count = self.fn_section_sizes['.text'] // 4
assert instr_count >= state.min_instr_count, "too short .text block"
tot_emitted = 0
tot_skipped = 0
fn_emitted = 0
fn_skipped = 0
rodata_stack = late_rodata_fn_output[::-1]
for (line, count) in self.fn_ins_inds:
for _ in range(count):
if (fn_emitted > MAX_FN_SIZE and instr_count - tot_emitted > state.min_instr_count and
(not rodata_stack or rodata_stack[-1])):
# Don't let functions become too large. When a function reaches 284
# instructions, and -O2 -framepointer flags are passed, the IRIX
# compiler decides it is a great idea to start optimizing more.
fn_emitted = 0
fn_skipped = 0
src[line] += ' }} void {}(void) {{ '.format(state.make_name('large_func'))
if fn_skipped < state.skip_instr_count:
fn_skipped += 1
tot_skipped += 1
elif rodata_stack:
src[line] += rodata_stack.pop()
else:
src[line] += '*(volatile int*)0 = 0;'
tot_emitted += 1
fn_emitted += 1
if rodata_stack:
size = len(late_rodata_fn_output) // 3
available = instr_count - tot_skipped
print("late rodata to text ratio is too high: {} / {} must be <= 1/3"
.format(size, available), file=sys.stderr)
print("add a .late_rodata_alignment (4|8) to the .late_rodata "
"block to double the allowed ratio.", file=sys.stderr)
exit(1)
rodata_name = None
if self.fn_section_sizes['.rodata'] > 0:
rodata_name = state.make_name('rodata')
output_line += ' const char {}[{}] = {{1}};'.format(rodata_name, self.fn_section_sizes['.rodata'])
data_name = None
if self.fn_section_sizes['.data'] > 0:
data_name = state.make_name('data')
output_line += ' char {}[{}] = {{1}};'.format(data_name, self.fn_section_sizes['.data'])
bss_name = None
if self.fn_section_sizes['.bss'] > 0:
bss_name = state.make_name('bss')
output_line += ' char {}[{}];'.format(bss_name, self.fn_section_sizes['.bss'])
fn = (self.text_glabels, self.asm_conts, late_rodata, self.late_rodata_asm_conts,
{
'.text': (text_name, self.fn_section_sizes['.text']),
'.data': (data_name, self.fn_section_sizes['.data']),
'.rodata': (rodata_name, self.fn_section_sizes['.rodata']),
'.bss': (bss_name, self.fn_section_sizes['.bss']),
})
return src, fn
def parse_source(f, print_source, opt, framepointer):
if opt == 'O2':
if framepointer:
min_instr_count = 6
skip_instr_count = 5
else:
min_instr_count = 2
skip_instr_count = 1
elif opt == 'g':
if framepointer:
min_instr_count = 7
skip_instr_count = 7
else:
min_instr_count = 4
skip_instr_count = 4
else:
assert opt == 'g3'
if framepointer:
min_instr_count = 4
skip_instr_count = 4
else:
min_instr_count = 2
skip_instr_count = 2
state = GlobalState(min_instr_count, skip_instr_count)
global_asm = None
asm_functions = []
output_lines = []
for raw_line in f:
raw_line = raw_line.rstrip()
line = raw_line.lstrip()
# Print exactly one output line per source line, to make compiler
# errors have correct line numbers. These will be overridden with
# reasonable content further down.
output_lines.append('')
if global_asm is not None:
if line.startswith(')'):
src, fn = global_asm.finish(state)
for i, line2 in enumerate(src):
output_lines[start_index + i] = line2
asm_functions.append(fn)
global_asm = None
else:
global_asm.process_line(line)
else:
if line == 'GLOBAL_ASM(':
global_asm = GlobalAsmBlock()
start_index = len(output_lines)
elif line.startswith('GLOBAL_ASM("') and line.endswith('")'):
global_asm = GlobalAsmBlock()
fname = line[len('GLOBAL_ASM') + 2 : -2]
with open(fname) as f:
for line2 in f:
global_asm.process_line(line2)
src, fn = global_asm.finish(state)
output_lines[-1] = ''.join(src)
asm_functions.append(fn)
global_asm = None
else:
output_lines[-1] = raw_line
if print_source:
for line in output_lines:
print(line)
return asm_functions
def fixup_objfile(objfile_name, functions, asm_prelude, assembler):
SECTIONS = ['.data', '.text', '.rodata', '.bss']
with open(objfile_name, 'rb') as f:
objfile = ElfFile(f.read())
prev_locs = {
'.text': 0,
'.data': 0,
'.rodata': 0,
'.bss': 0,
}
to_copy = {
'.text': [],
'.data': [],
'.rodata': [],
}
asm = []
late_rodata = []
late_rodata_asm = []
late_rodata_source_name = None
# Generate an assembly file with all the assembly we need to fill in. For
# simplicity we pad with nops/.space so that addresses match exactly, so we
# don't have to fix up relocations/symbol references.
all_text_glabels = set()
for (text_glabels, body, fn_late_rodata, fn_late_rodata_body, data) in functions:
ifdefed = False
for sectype, (temp_name, size) in data.items():
if temp_name is None:
continue
assert size > 0
loc = objfile.symtab.find_symbol(temp_name)
if loc is None:
ifdefed = True
break
loc = loc[1]
prev_loc = prev_locs[sectype]
assert loc >= prev_loc, sectype
if loc != prev_loc:
asm.append('.section ' + sectype)
if sectype == '.text':
for i in range((loc - prev_loc) // 4):
asm.append('nop')
else:
asm.append('.space {}'.format(loc - prev_loc))
if sectype != '.bss':
to_copy[sectype].append((loc, size))
prev_locs[sectype] = loc + size
if not ifdefed:
all_text_glabels.update(text_glabels)
late_rodata.extend(fn_late_rodata)
late_rodata_asm.extend(fn_late_rodata_body)
asm.append('.text')
for line in body:
asm.append(line)
if late_rodata_asm:
late_rodata_source_name = '_asmpp_late_rodata'
asm.append('.rdata')
asm.append('glabel {}'.format(late_rodata_source_name))
asm.extend(late_rodata_asm)
o_file = tempfile.NamedTemporaryFile(prefix='asm-processor', suffix='.o', delete=False)
o_name = o_file.name
o_file.close()
s_file = tempfile.NamedTemporaryFile(prefix='asm-processor', suffix='.s', delete=False)
s_name = s_file.name
try:
s_file.write(asm_prelude + b'\n')
for line in asm:
s_file.write(line.encode('utf-8') + b'\n')
s_file.close()
ret = os.system(assembler + " " + s_name + " -o " + o_name)
if ret != 0:
raise Exception("failed to assemble")
with open(o_name, 'rb') as f:
asm_objfile = ElfFile(f.read())
# Remove some clutter from objdump output
objfile.drop_irrelevant_sections()
# Unify reginfo sections
target_reginfo = objfile.find_section('.reginfo')
source_reginfo_data = list(asm_objfile.find_section('.reginfo').data)
data = list(target_reginfo.data)
for i in range(20):
data[i] |= source_reginfo_data[i]
target_reginfo.data = bytes(data)
# Move over section contents
modified_text_positions = set()
last_rodata_pos = 0
for sectype in SECTIONS:
if sectype == '.bss':
continue
source = asm_objfile.find_section(sectype)
target = objfile.find_section(sectype)
if source is None or not to_copy[sectype]:
continue
assert target is not None, "must have a section to overwrite: " + sectype
data = list(target.data)
for (pos, count) in to_copy[sectype]:
data[pos:pos + count] = source.data[pos:pos + count]
if sectype == '.text':
assert count % 4 == 0
assert pos % 4 == 0
for i in range(count // 4):
modified_text_positions.add(pos + 4 * i)
elif sectype == '.rodata':
last_rodata_pos = pos + count
target.data = bytes(data)
# Move over late rodata. This is heuristic, sadly, since I can't think
# of another way of doing it.
moved_late_rodata = {}
if late_rodata:
source = asm_objfile.find_section('.rodata')
target = objfile.find_section('.rodata')
source_pos = asm_objfile.symtab.find_symbol(late_rodata_source_name)
assert source_pos is not None and source_pos[0] == source.index
source_pos = source_pos[1]
new_data = list(target.data)
for dummy_bytes in late_rodata:
pos = target.data.index(dummy_bytes, last_rodata_pos)
new_data[pos:pos+4] = source.data[source_pos:source_pos+4]
moved_late_rodata[source_pos] = pos
last_rodata_pos = pos + 4
source_pos += 4
target.data = bytes(new_data)
# Merge strtab data.
strtab_adj = len(objfile.symtab.strtab.data)
objfile.symtab.strtab.data += asm_objfile.symtab.strtab.data
# Find relocated symbols
relocated_symbols = set()
for sectype in SECTIONS:
for obj in [asm_objfile, objfile]:
sec = obj.find_section(sectype)
if sec is None:
continue
for reltab in sec.relocated_by:
for rel in reltab.relocations:
relocated_symbols.add(obj.symtab.symbol_entries[rel.sym_index])
# Move over symbols, deleting the temporary function labels.
# Sometimes this naive procedure results in duplicate symbols, or UNDEF
# symbols that are also defined the same .o file. Hopefully that's fine.
# Skip over local symbols that aren't used relocated against, to avoid
# conflicts.
new_local_syms = [s for s in objfile.symtab.local_symbols() if not is_temp_name(s.name)]
new_global_syms = [s for s in objfile.symtab.global_symbols() if not is_temp_name(s.name)]
for i, s in enumerate(asm_objfile.symtab.symbol_entries):
is_local = (i < asm_objfile.symtab.sh_info)
if is_local and s not in relocated_symbols:
continue
if is_temp_name(s.name):
continue
if s.st_shndx not in [SHN_UNDEF, SHN_ABS]:
section_name = asm_objfile.sections[s.st_shndx].name
assert section_name in SECTIONS, "Generated assembly .o must only have symbols for .text, .data, .rodata, ABS and UNDEF, but found {}".format(section_name)
s.st_shndx = objfile.find_section(section_name).index
# glabel's aren't marked as functions, making objdump output confusing. Fix that.
if s.name in all_text_glabels:
s.type = STT_FUNC
if objfile.sections[s.st_shndx].name == '.rodata' and s.st_value in moved_late_rodata:
s.st_value = moved_late_rodata[s.st_value]
s.st_name += strtab_adj
if is_local:
new_local_syms.append(s)
else:
new_global_syms.append(s)
new_syms = new_local_syms + new_global_syms
for i, s in enumerate(new_syms):
s.new_index = i
objfile.symtab.data = b''.join(s.to_bin() for s in new_syms)
objfile.symtab.sh_info = len(new_local_syms)
# Move over relocations
for sectype in SECTIONS:
source = asm_objfile.find_section(sectype)
target = objfile.find_section(sectype)
if target is not None:
# fixup relocation symbol indices, since we butchered them above
for reltab in target.relocated_by:
nrels = []
for rel in reltab.relocations:
if sectype == '.text' and rel.r_offset in modified_text_positions:
# don't include relocations for late_rodata dummy code
continue
# hopefully we don't have relocations for local or
# temporary symbols, so new_index exists
rel.sym_index = objfile.symtab.symbol_entries[rel.sym_index].new_index
nrels.append(rel)
reltab.relocations = nrels
reltab.data = b''.join(rel.to_bin() for rel in nrels)
if not source:
continue
target_reltab = objfile.find_section('.rel' + sectype)
target_reltaba = objfile.find_section('.rela' + sectype)
for reltab in source.relocated_by:
for rel in reltab.relocations:
rel.sym_index = asm_objfile.symtab.symbol_entries[rel.sym_index].new_index
if sectype == '.rodata' and rel.r_offset in moved_late_rodata:
rel.r_offset = moved_late_rodata[rel.r_offset]
new_data = b''.join(rel.to_bin() for rel in reltab.relocations)
if reltab.sh_type == SHT_REL:
if not target_reltab:
target_reltab = objfile.add_section('.rel' + sectype,
sh_type=SHT_REL, sh_flags=0,
sh_link=objfile.symtab.index, sh_info=target.index,
sh_addralign=4, sh_entsize=8, data=b'')
target_reltab.data += new_data
else:
if not target_reltaba:
target_reltaba = objfile.add_section('.rela' + sectype,
sh_type=SHT_RELA, sh_flags=0,
sh_link=objfile.symtab.index, sh_info=target.index,
sh_addralign=4, sh_entsize=12, data=b'')
target_reltaba.data += new_data
objfile.write(objfile_name)
finally:
s_file.close()
os.remove(s_name)
try:
os.remove(o_name)
except:
pass
def main():
parser = argparse.ArgumentParser(description="Pre-process .c files and post-process .o files to enable embedding assembly into C.")
parser.add_argument('filename', help="path to .c code")
parser.add_argument('--post-process', dest='objfile', help="path to .o file to post-process")
parser.add_argument('--assembler', dest='assembler', help="assembler command (e.g. \"mips-linux-gnu-as -march=vr4300 -mabi=32\")")
parser.add_argument('--asm-prelude', dest='asm_prelude', help="path to a file containing a prelude to the assembly file (with .set and .macro directives, e.g.)")
parser.add_argument('-framepointer', dest='framepointer', action='store_true')
parser.add_argument('-g3', dest='g3', action='store_true')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-O2', dest='o2', action='store_true')
group.add_argument('-g', dest='o2', action='store_false')
args = parser.parse_args()
opt = 'O2' if args.o2 else 'g'
if args.g3:
if opt != 'O2':
print("-g3 is only supported together with -O2", file=sys.stderr)
exit(1)
opt = 'g3'
if args.objfile is None:
with open(args.filename) as f:
parse_source(f, print_source=True, opt=opt, framepointer=args.framepointer)
else:
assert args.assembler is not None, "must pass assembler command"
with open(args.filename) as f:
functions = parse_source(f, print_source=False, opt=opt, framepointer=args.framepointer)
if not functions:
return
asm_prelude = b''
if args.asm_prelude:
with open(args.asm_prelude, 'rb') as f:
asm_prelude = f.read()
fixup_objfile(args.objfile, functions, asm_prelude, args.assembler)
if __name__ == "__main__":
main()

View File

@ -1,36 +0,0 @@
#!/usr/bin/env python3
import sys
import os
import shlex
import subprocess
import tempfile
dir_path = os.path.dirname(os.path.realpath(__file__))
asm_processor = ['python3', os.path.join(dir_path, "asm-processor.py")]
prelude = os.path.join(dir_path, "prelude.inc")
all_args = sys.argv[1:]
sep1 = all_args.index('--')
sep2 = all_args.index('--', sep1+1)
compiler = all_args[:sep1]
assembler = all_args[sep1+1:sep2]
assembler_sh = ' '.join(shlex.quote(x) for x in assembler)
compile_args = all_args[sep2+1:]
in_file = compile_args[-1]
out_ind = compile_args.index('-o')
out_file = compile_args[out_ind + 1]
del compile_args[-1]
del compile_args[out_ind + 1]
del compile_args[out_ind]
in_dir = os.path.split(os.path.realpath(in_file))[0]
opt_flags = [x for x in compile_args if x in ['-g', '-O2', '-framepointer']]
preprocessed_file = tempfile.NamedTemporaryFile(prefix='preprocessed', suffix='.c')
subprocess.check_call(asm_processor + opt_flags + [in_file], stdout=preprocessed_file)
subprocess.check_call(compiler + compile_args + ['-I', in_dir, '-o', out_file, preprocessed_file.name])
subprocess.check_call(asm_processor + opt_flags + [in_file, '--post-process', out_file, '--assembler', assembler_sh, '--asm-prelude', prelude])

View File

@ -1,5 +0,0 @@
.set noat
.set noreorder
.set gp=64
.include "macros.inc"

View File

@ -915,7 +915,6 @@ typedef void (*AFerrfunc)(long, const char *);
defined(__NetBSD__) || \
defined(__OpenBSD__) || \
defined(__APPLE__) || \
defined(__sgi) || \
(defined(__linux__) && defined(__LP64__))
// BSD and IRIX systems define off_t as a 64-bit signed integer.
// Linux defines off_t as a 64-bit signed integer in LP64 mode.

View File

@ -57,7 +57,6 @@ typedef void (*AFerrfunc)(long, const char *);
defined(__NetBSD__) || \
defined(__OpenBSD__) || \
defined(__APPLE__) || \
defined(__sgi) || \
(defined(__linux__) && defined(__LP64__))
// BSD and IRIX systems define off_t as a 64-bit signed integer.
// Linux defines off_t as a 64-bit signed integer in LP64 mode.

View File

@ -13,11 +13,6 @@ if [[ $# = 0 ]]; then
exit 1
fi
if [ -z "$QEMU_IRIX" ]; then
echo "env variable QEMU_IRIX should point to the qemu-mips binary" >&2
exit 1
fi
if [ -z "$CROSS" ]; then
CROSS=mips-linux-gnu-
fi
@ -37,9 +32,6 @@ done
echo "char measurement;" >> $TEMPC
$QEMU_IRIX -silent -L $IRIX_ROOT $IRIX_ROOT/usr/bin/cc -c -non_shared -G 0 \
-g -Xcpluscomm -mips2 -I $(pwd)/include/ $TEMPC -o $TEMPO
LINE=$(${CROSS}objdump -t $TEMPO | grep measurement | cut -d' ' -f1)
NUM=$((0x$LINE - 1))
echo "bss index: $NUM"

View File

@ -1,42 +0,0 @@
#!/usr/bin/env python3
import sys
import os
import glob
if len(sys.argv) < 4:
print("usage: cleancrcmap <in_map> <out_map> <searchdir>")
sys.exit(1)
# load and check the old map
searchpath = sys.argv[3]
inmap = list()
with open(sys.argv[1], 'r') as f:
for line in f:
line = line.strip()
if line == '' or line[0] == '#':
continue
tok = line.split(',')
crcstr = tok[0].strip()
if crcstr.startswith('0x'):
crc = int(crcstr[2:], 16)
else:
crc = int(crcstr)
tok[1] = tok[1].strip()
[fname, fext] = os.path.splitext(tok[1])
[fname, ffmt] = os.path.splitext(fname)
fname = fname + ffmt[:-1] + '*'
matches = glob.glob(os.path.join(searchpath, fname))
if len(matches) == 0:
print("warning: texture '{0}' does not match anything in '{1}'".format(fname, searchpath))
else:
for s in matches:
tup = (crc, os.path.relpath(s, searchpath))
if not (tup in inmap):
inmap.append(tup)
# save cleaned up version to the new one
with open(sys.argv[2], 'w') as f:
for (crc, fpath) in inmap:
f.write("0x{0:08x}, {1}\n".format(crc, fpath))

View File

@ -1,308 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#define EI_DATA 5
#define EI_NIDENT 16
#define STT_NOTYPE 0
#define STT_OBJECT 1
#define STT_FUNC 2
#define STT_SECTION 3
#define STT_FILE 4
#define STT_COMMON 5
#define STT_TLS 6
#define ELF_ST_TYPE(x) (((unsigned int) x) & 0xf)
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Off;
typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
} Elf32_Ehdr;
typedef struct {
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
} Elf32_Shdr;
typedef struct {
uint32_t st_name;
Elf32_Addr st_value;
uint32_t st_size;
unsigned char st_info;
unsigned char st_other;
uint16_t st_shndx;
} Elf32_Sym;
typedef struct {
uint16_t magic; //To verify validity of the table
uint16_t vstamp; //Version stamp
uint32_t ilineMax; //Number of line number entries
uint32_t cbLine; //Number of bytes for line number entries
uint32_t cbLineOffset; //Index to start of line numbers
uint32_t idnMax; //Max index into dense numbers
uint32_t cbDnOffset; //Index to start dense numbers
uint32_t ipdMax; //Number of procedures
uint32_t cbPdOffset; //Index to procedure descriptors
uint32_t isymMax; //Number of local symbols
uint32_t cbSymOffset; //Index to start of local symbols
uint32_t ioptMax; //Maximum index into optimization entries
uint32_t cbOptOffset; //Index to start of optimization entries
uint32_t iauxMax; //Number of auxiliary symbols
uint32_t cbAuxOffset; //Index to the start of auxiliary symbols
uint32_t issMax; //Max index into local strings
uint32_t cbSsOffset; //Index to start of local strings
uint32_t issExtMax; //Max index into external strings
uint32_t cbSsExtOffset; //Index to the start of external strings
uint32_t ifdMax; //Number of file descriptors
uint32_t cbFdOffset; //Index to file descriptor
uint32_t crfd; //Number of relative file descriptors
uint32_t cbRfdOffset; //Index to relative file descriptors
uint32_t iextMax; //Maximum index into external symbols
uint32_t cbExtOffset; //Index to the start of external symbols.
} SymbolicHeader;
typedef struct {
uint32_t adr; // Memory address of start of file
uint32_t rss; // Source file name
uint32_t issBase; // Start of local strings
uint32_t cbSs; // Number of bytes in local strings
uint32_t isymBase; // Start of local symbol entries
uint32_t csym; // Count of local symbol entries
uint32_t ilineBase; // Start of line number entries
uint32_t cline; // Count of line number entries
uint32_t ioptBase; // Start of optimization symbol entries
uint32_t copt; // Count of optimization symbol entries
uint16_t ipdFirst; // Start of procedure descriptor table
uint16_t cpd; // Count of procedures descriptors
uint32_t iauxBase; // Start of auxiliary symbol entries
uint32_t caux; // Count of auxiliary symbol entries
uint32_t rfdBase; // Index into relative file descriptors
uint32_t crfd; // Relative file descriptor count
uint32_t flags;
uint32_t cbLineOffset; // Byte offset from header or file ln's
uint32_t cbLine;
} FileDescriptorTable;
typedef struct {
uint32_t iss;
uint32_t value;
uint32_t st_sc_index;
} LocalSymbolsEntry;
typedef enum {
stNil,
stGlobal,
stStatic,
stParam,
stLocal,
stLabel,
stProc,
stBlock,
stEnd,
stMember,
stTypedef,
stFile,
stStaticProc,
stConstant
} StConstants;
uint32_t u32be(uint32_t val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return __builtin_bswap32(val);
#else
return val;
#endif
}
uint16_t u16be(uint16_t val) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return __builtin_bswap16(val);
#else
return val;
#endif
}
static bool elf_get_section_range(uint8_t *file, const char *searched_name, uint32_t *address, uint32_t *offset, uint32_t *size, uint32_t *section_index) {
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)file;
for (int i = 0; i < u16be(ehdr->e_shnum); i++) {
if (memcmp("\x7f" "ELF", ehdr->e_ident, 4) != 0) {
fprintf(stderr, "Missing ELF magic\n");
exit(1);
}
if (ehdr->e_ident[EI_DATA] != 2) {
fprintf(stderr, "ELF file is not big-endian\n");
exit(1);
}
Elf32_Shdr *shdr = (Elf32_Shdr *)(file + u32be(ehdr->e_shoff) + i * u16be(ehdr->e_shentsize));
if (u16be(ehdr->e_shstrndx) >= u16be(ehdr->e_shnum)) {
fprintf(stderr, "Invalid ELF file\n");
exit(1);
}
Elf32_Shdr *str_shdr = (Elf32_Shdr *)(file + u32be(ehdr->e_shoff) + u16be(ehdr->e_shstrndx) * u16be(ehdr->e_shentsize));
char *name = (char *)(file + u32be(str_shdr->sh_offset) + u32be(shdr->sh_name));
if (memcmp(name, searched_name, strlen(searched_name)) == 0) {
*address = u32be(shdr->sh_addr);
*offset = u32be(shdr->sh_offset);
*size = u32be(shdr->sh_size);
*section_index = i;
return true;
}
}
return false;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s INFILE OUTFILE\n", argv[0]);
return 1;
}
FILE *in = fopen(argv[1], "rb");
if (in == NULL) {
perror("fopen couldn't open input file");
exit(1);
}
fseek(in, 0, SEEK_END);
size_t file_size = ftell(in);
fseek(in, 0, SEEK_SET);
uint8_t *file = malloc(file_size);
if (fread(file, 1, file_size, in) != file_size) {
fclose(in);
fprintf(stderr, "Failed to read file: %s\n", argv[1]);
exit(1);
}
fclose(in);
uint32_t data_address, data_offset, data_size, data_index;
if (!elf_get_section_range(file, ".data", &data_address, &data_offset, &data_size, &data_index)) {
fprintf(stderr, "section .data not found\n");
exit(1);
}
uint32_t rodata_address, rodata_offset, rodata_size, rodata_index;
if (elf_get_section_range(file, ".rodata", &rodata_address, &rodata_offset, &rodata_size, &rodata_index)) {
fprintf(stderr, ".rodata section found, please put everything in .data instead (non-const variables)\n");
exit(1);
}
uint32_t symtab_address, symtab_offset, symtab_size, symtab_index;
if (!elf_get_section_range(file, ".symtab", &symtab_address, &symtab_offset, &symtab_size, &symtab_index)) {
fprintf(stderr, "section .symtab not found\n");
exit(1);
}
uint32_t strtab_address, strtab_offset, strtab_size, strtab_index;
if (!elf_get_section_range(file, ".strtab", &strtab_address, &strtab_offset, &strtab_size, &strtab_index)) {
fprintf(stderr, "section .strtab not found\n");
exit(1);
}
// IDO might pad the section to the nearest 16 byte boundary,
// but the mio0 data should not include that. Therefore find
// the "real" end by finding where the last symbol ends.
uint32_t last_symbol_end = 0;
for (uint32_t i = 0; i < symtab_size / sizeof(Elf32_Sym); i++) {
Elf32_Sym *symbol = (Elf32_Sym *)(file + symtab_offset + i * sizeof(Elf32_Sym));
#if DEBUG
const char *name = "(null)";
if (symbol->st_name != 0U) {
name = (const char*)file + strtab_offset + u32be(symbol->st_name);
}
printf("%08x\t%08x\t%02x\t%02x\t%02x\t%s\n", u32be(symbol->st_value), u32be(symbol->st_size), symbol->st_info, symbol->st_other, u16be(symbol->st_shndx), name);
#endif
if (ELF_ST_TYPE(symbol->st_info) == STT_OBJECT && u16be(symbol->st_shndx) == data_index) {
uint32_t symbol_end = u32be(symbol->st_value) + u32be(symbol->st_size);
if (symbol_end > last_symbol_end) {
last_symbol_end = symbol_end;
}
}
}
uint32_t mdebug_address, mdebug_offset, mdebug_size, mdebug_index;
if (elf_get_section_range(file, ".mdebug", &mdebug_address, &mdebug_offset, &mdebug_size, &mdebug_index)) {
SymbolicHeader *symbolic_header = (SymbolicHeader *)(file + mdebug_offset);
for (uint32_t i = 0; i < u32be(symbolic_header->ifdMax); i++) {
FileDescriptorTable *fdt = (FileDescriptorTable *)(file + u32be(symbolic_header->cbFdOffset) + i * sizeof(FileDescriptorTable));
for (uint32_t j = 0; j < u32be(fdt->csym); j++) {
LocalSymbolsEntry lse;
memcpy(&lse, file + u32be(symbolic_header->cbSymOffset) + (u32be(fdt->isymBase) + j) * sizeof(LocalSymbolsEntry), sizeof(LocalSymbolsEntry));
uint32_t value = u32be(lse.value);
uint32_t st_sc_index = u32be(lse.st_sc_index);
uint32_t st = (st_sc_index >> 26);
#ifdef DEBUG
uint32_t sc = (st_sc_index >> 21) & 0x1f;
uint32_t index = st_sc_index & 0xfffff;
uint32_t iss = u32be(lse.iss);
const char *symbol_name = file + u32be(symbolic_header->cbSsOffset) + iss;
printf("%s %08x\n", symbol_name, value);
#endif
if (st == stStatic || st == stGlobal) {
// Right now just assume length 8 since it's quite much work to extract the real size
uint32_t symbol_end = value + 8;
if (symbol_end > last_symbol_end) {
last_symbol_end = symbol_end;
}
}
}
}
}
#ifdef DEBUG
printf("Last symbol end: %08x\n", last_symbol_end);
#endif
size_t new_size = last_symbol_end - data_address;
if (new_size + 16 <= data_size) {
// There seems to be more than 16 bytes padding or non-identified data, so abort and take the original size
new_size = data_size;
} else {
// Make sure we don't cut off non-zero bytes
for (size_t i = new_size; i < data_size; i++) {
if (file[data_offset + i] != 0) {
// Must be some symbol missing, so abort and take the original size
new_size = data_size;
break;
}
}
}
FILE *out = fopen(argv[2], "wb");
fwrite(file + data_offset, 1, new_size, out);
fclose(out);
free(file);
return 0;
}

View File

@ -1,18 +0,0 @@
# Silicon Graphics Freeware Legal Notice
## Copyright 1995, Silicon Graphics, Inc. -- ALL RIGHTS RESERVED
You may copy, modify, use and distribute this software, (i) provided that you include the entirety of this reservation of rights notice in all such copies, and (ii) you comply with any additional or different obligations and/or use restrictions specified by any third party owner or supplier of the software in other notices that may be included with the software.
**SGI DISCLAIMS ALL WARRANTIES WITH RESPECT TO THIS SOFTWARE, EXPRESS, IMPLIED, OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ALL WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT. SGI SHALL NOT BE LIABLE FOR ANY SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING, WITHOUT LIMITATION, LOST REVENUES, LOST PROFITS, OR LOSS OF PROSPECTIVE ECONOMIC ADVANTAGE, RESULTING FROM THE USE OR MISUSE OF THIS SOFTWARE.**
**U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND:**
Use, duplication or disclosure by the Government is subject to restrictions as set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software clause at DFARS 252.227-7013 and/or in similar or successor clauses in the FAR, or the DOD or NASA FAR Supplement. Unpublished - rights reserved under the Copyright Laws of United States. Contractor/manufacturer is Silicon Graphics, Inc., 2011 N. Shoreline Blvd. Mountain View, CA 94039-7311.
## Product Support
Freeware products are not supported by Silicon Graphics or any of its support providers. The software contained in this package is made available through the generous efforts of their authors. Although they are interested in your feedback, they are under no obligation to address bugs, enhancements, or answer questions.
----
**NOTE:** This license was copied verbatim from https://web.archive.org/web/19991008090202/http://toolbox.sgi.com/TasteOfDT/public/freeware1.0/legal_notice.html .

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -57,9 +57,8 @@ typedef void (*AFerrfunc)(long, const char *);
defined(__NetBSD__) || \
defined(__OpenBSD__) || \
defined(__APPLE__) || \
defined(__sgi) || \
(defined(__linux__) && defined(__LP64__))
// BSD and IRIX systems define off_t as a 64-bit signed integer.
// Linux defines off_t as a 64-bit signed integer in LP64 mode.
typedef off_t AFframecount;
typedef off_t AFfileoffset;

View File

@ -1,142 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include "n64cksum.h"
#include "utils.h"
#define N64CKSUM_VERSION "0.1"
// compute N64 ROM checksums
// buf: buffer with extended SM64 data
// cksum: two element array to write CRC1 and CRC2 to
void n64cksum_calc_6102(unsigned char *buf, unsigned int cksum[]) {
uint32_t t2, t3, t4, t6, t7, t8, s0;
uint32_t a0, a1, a2, a3;
uint32_t v0, v1;
uint32_t seed, end_offset, cur_offset, buf_offset;
// derived from the SM64 boot code
seed = 0xF8CA4DDB; // 0x3f * 0x5d588b65;
end_offset = 0x100000;
cur_offset = 0;
buf_offset = 0x1000;
seed++;
a3 = seed;
t2 = seed;
t3 = seed;
s0 = seed;
a2 = seed;
t4 = seed;
do {
v0 = read_u32_be(&buf[buf_offset]);
v1 = a3 + v0;
a1 = v1;
if (v1 < a3) {
t2++;
}
v1 = v0 & 0x1F;
t7 = 32 - v1;
t8 = v0 >> t7;
t6 = v0 << v1;
a0 = t6 | t8;
a3 = a1;
t3 ^= v0;
s0 += a0;
if (a2 < v0) {
a2 ^= a3 ^ v0;
} else {
a2 ^= a0;
}
cur_offset += 4;
t7 = v0 ^ s0;
buf_offset += 4;
t4 += t7;
} while (cur_offset != end_offset);
cksum[0] = (a3 ^ t2) ^ t3;
cksum[1] = (s0 ^ a2) ^ t4;
}
void n64cksum_update_checksums(uint8_t *buf)
{
unsigned int cksum_offsets[] = {0x10, 0x14};
uint32_t read_cksum[2];
uint32_t calc_cksum[2];
int i;
// assume CIC-NUS-6102
INFO("BootChip: CIC-NUS-6102\n");
// calculate new N64 header checksum
n64cksum_calc_6102(buf, calc_cksum);
// mimic the n64sums output
for (i = 0; i < 2; i++) {
read_cksum[i] = read_u32_be(&buf[cksum_offsets[i]]);
INFO("CRC%d: 0x%08X ", i+1, read_cksum[i]);
INFO("Calculated: 0x%08X ", calc_cksum[i]);
if (calc_cksum[i] == read_cksum[i]) {
INFO("(Good)\n");
} else {
INFO("(Bad)\n");
}
}
// write checksums into header
INFO("Writing back calculated Checksum\n");
write_u32_be(&buf[cksum_offsets[0]], calc_cksum[0]);
write_u32_be(&buf[cksum_offsets[1]], calc_cksum[1]);
}
#ifdef N64CKSUM_STANDALONE
static void print_usage(void)
{
ERROR("Usage: n64cksum ROM [ROM_OUT]\n"
"\n"
"n64cksum v" N64CKSUM_VERSION ": N64 ROM checksum calculator\n"
"\n"
"File arguments:\n"
" ROM input ROM file\n"
" ROM_OUT output ROM file (default: overwrites input ROM)\n");
}
int main(int argc, char *argv[])
{
unsigned char *rom_data;
char *file_in;
char *file_out;
long length;
long write_length;
if (argc < 2) {
print_usage();
return EXIT_FAILURE;
}
file_in = argv[1];
if (argc > 2) {
file_out = argv[2];
} else {
file_out = argv[1];
}
length = read_file(file_in, &rom_data);
if (length < 0) {
ERROR("Error reading input file \"%s\"\n", file_in);
return EXIT_FAILURE;
}
n64cksum_update_checksums(rom_data);
write_length = write_file(file_out, rom_data, length);
free(rom_data);
if (write_length != length) {
ERROR("Error writing to output file \"%s\"\n", file_out);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#endif // N64CKSUM_STANDALONE

View File

@ -1,16 +0,0 @@
#ifndef N64CKSUM_H_
#define N64CKSUM_H_
#include <stdint.h>
// compute N64 ROM checksums
// buf: buffer with extended SM64 data
// cksum: two element array to write CRC1 and CRC2 to
void n64cksum_calc_6102(unsigned char *buf, unsigned int cksum[]);
// update N64 header checksums
// buf: buffer containing ROM data
// checksums are written into the buffer
void n64cksum_update_checksums(uint8_t *buf);
#endif // N64CKSUM_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,100 +0,0 @@
#ifndef N64GRAPHICS_H_
#define N64GRAPHICS_H_
#include <stdint.h>
// intermediate formats
typedef struct _rgba
{
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
} rgba;
typedef struct _ia
{
uint8_t intensity;
uint8_t alpha;
} ia;
// CI palette
typedef struct
{
uint16_t data[256];
int max; // max number of entries
int used; // number of entries used
} palette_t;
//---------------------------------------------------------
// N64 RGBA/IA/I/CI -> intermediate RGBA/IA
//---------------------------------------------------------
// N64 raw RGBA16/RGBA32 -> intermediate RGBA
rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth);
// N64 raw IA1/IA4/IA8/IA16 -> intermediate IA
ia *raw2ia(const uint8_t *raw, int width, int height, int depth);
// N64 raw I4/I8 -> intermediate IA
ia *raw2i(const uint8_t *raw, int width, int height, int depth);
//---------------------------------------------------------
// intermediate RGBA/IA -> N64 RGBA/IA/I/CI
// returns length written to 'raw' used or -1 on error
//---------------------------------------------------------
// intermediate RGBA -> N64 raw RGBA16/RGBA32
int rgba2raw(uint8_t *raw, const rgba *img, int width, int height, int depth);
// intermediate IA -> N64 raw IA1/IA4/IA8/IA16
int ia2raw(uint8_t *raw, const ia *img, int width, int height, int depth);
// intermediate IA -> N64 raw I4/I8
int i2raw(uint8_t *raw, const ia *img, int width, int height, int depth);
//---------------------------------------------------------
// N64 CI <-> N64 RGBA16/IA16
//---------------------------------------------------------
// N64 CI raw data and palette to raw data (either RGBA16 or IA16)
uint8_t *ci2raw(const uint8_t *rawci, const uint8_t *palette, int width, int height, int ci_depth);
// convert from raw (RGBA16 or IA16) format to CI + palette
int raw2ci(uint8_t *rawci, palette_t *pal, const uint8_t *raw, int raw_len, int ci_depth);
//---------------------------------------------------------
// intermediate RGBA/IA -> PNG
//---------------------------------------------------------
// intermediate RGBA write to PNG file
int rgba2png(const char *png_filename, const rgba *img, int width, int height);
// intermediate IA write to grayscale PNG file
int ia2png(const char *png_filename, const ia *img, int width, int height);
//---------------------------------------------------------
// PNG -> intermediate RGBA/IA
//---------------------------------------------------------
// PNG file -> intermediate RGBA
rgba *png2rgba(const char *png_filename, int *width, int *height);
// PNG file -> intermediate IA
ia *png2ia(const char *png_filename, int *width, int *height);
//---------------------------------------------------------
// version
//---------------------------------------------------------
// get version of underlying graphics reading library
const char *n64graphics_get_read_version(void);
// get version of underlying graphics writing library
const char *n64graphics_get_write_version(void);
#endif // N64GRAPHICS_H_

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 David Benepe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,29 +0,0 @@
# n64graphics_ci
Allows you to convert PNG image files to/from N64 CI format. This is temporary until queueRAM adds CI support into his official n64graphics tool. This tool does not process RGBA, IA, or I textures, use n64graphics for those.
CI4 textures will always assume a 16 color palette is used, and CI8 textures a 256 palette is used. The palette will be generated as a seperate file. The palette file will be named after the CI filename, but postpended with `.pal`.
## Libraries Used (All MIT licensed)
* **Exoquant** by Dennis Ranke, for color reduction. https://github.com/exoticorn/exoquant
* **stb** by Sean Barrett, for loading PNG images. https://github.com/nothings/stb
## PNG -> CI4 + Palette
`./n64graphics_ci -i image.ci4 -g image.png -f ci4`
## CI4 + Palette -> 32x32 PNG
`./n64graphics_ci -e image.ci4 -g image.ci4.png -f ci4 -w 32 -h 32`
## PNG -> CI8 + Palette
`./n64graphics_ci -i image.ci8 -g image.png -f ci8`
## CI8 + Palette -> 32x32 PNG
`./n64graphics_ci -e image.ci8 -g image.ci8.png -f ci8 -w 32 -h 32`
## Comparision
![alt text](https://i.imgur.com/r3PhZp0.png)

View File

@ -1,712 +0,0 @@
/*
ExoQuant v0.7
Copyright (c) 2004 Dennis Ranke
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "exoquant.h"
#ifndef OSX_BUILD // OSX build cannot have malloc defined
#include <malloc.h>
#endif
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#ifndef NULL
#define NULL (0)
#endif
#define SCALE_R 1.0f
#define SCALE_G 1.2f
#define SCALE_B 0.8f
#define SCALE_A 1.0f
exq_data *exq_init()
{
int i;
exq_data *pExq;
pExq = (exq_data*)malloc(sizeof(exq_data));
for(i = 0; i < EXQ_HASH_SIZE; i++)
pExq->pHash[i] = NULL;
pExq->numColors = 0;
pExq->optimized = 0;
pExq->transparency = 1;
pExq->numBitsPerChannel = 8;
return pExq;
}
void exq_no_transparency(exq_data *pExq)
{
pExq->transparency = 0;
}
void exq_free(exq_data *pExq)
{
int i;
exq_histogram *pCur, *pNext;
for(i = 0; i < EXQ_HASH_SIZE; i++)
for(pCur = pExq->pHash[i]; pCur != NULL; pCur = pNext)
{
pNext = pCur->pNextInHash;
free(pCur);
}
free(pExq);
}
static unsigned int exq_make_hash(unsigned int rgba)
{
rgba -= (rgba >> 13) | (rgba << 19);
rgba -= (rgba >> 13) | (rgba << 19);
rgba -= (rgba >> 13) | (rgba << 19);
rgba -= (rgba >> 13) | (rgba << 19);
rgba -= (rgba >> 13) | (rgba << 19);
rgba &= EXQ_HASH_SIZE - 1;
return rgba;
}
void exq_feed(exq_data *pExq, unsigned char *pData, int nPixels)
{
int i;
unsigned int hash;
unsigned char r, g, b, a;
exq_histogram *pCur;
unsigned char channelMask = 0xff00 >> pExq->numBitsPerChannel;
for(i = 0; i < nPixels; i++)
{
r = *pData++; g = *pData++; b = *pData++; a = *pData++;
hash = exq_make_hash(((unsigned int)r) | (((unsigned int)g) << 8) | (((unsigned int)b) << 16) | (((unsigned int)a) << 24));
pCur = pExq->pHash[hash];
while(pCur != NULL && (pCur->ored != r || pCur->ogreen != g ||
pCur->oblue != b || pCur->oalpha != a))
pCur = pCur->pNextInHash;
if(pCur != NULL)
pCur->num++;
else
{
pCur = (exq_histogram*)malloc(sizeof(exq_histogram));
pCur->pNextInHash = pExq->pHash[hash];
pExq->pHash[hash] = pCur;
pCur->ored = r; pCur->ogreen = g; pCur->oblue = b; pCur->oalpha = a;
r &= channelMask; g &= channelMask; b &= channelMask;
pCur->color.r = r / 255.0f * SCALE_R;
pCur->color.g = g / 255.0f * SCALE_G;
pCur->color.b = b / 255.0f * SCALE_B;
pCur->color.a = a / 255.0f * SCALE_A;
if(pExq->transparency)
{
pCur->color.r *= pCur->color.a;
pCur->color.g *= pCur->color.a;
pCur->color.b *= pCur->color.a;
}
pCur->num = 1;
pCur->palIndex = -1;
pCur->ditherScale.r = pCur->ditherScale.g = pCur->ditherScale.b =
pCur->ditherScale.a = -1;
pCur->ditherIndex[0] = pCur->ditherIndex[1] = pCur->ditherIndex[2] =
pCur->ditherIndex[3] = -1;
}
}
}
void exq_quantize(exq_data *pExq, int nColors)
{
exq_quantize_ex(pExq, nColors, 0);
}
void exq_quantize_hq(exq_data *pExq, int nColors)
{
exq_quantize_ex(pExq, nColors, 1);
}
void exq_quantize_ex(exq_data *pExq, int nColors, int hq)
{
int besti;
exq_float beste;
exq_histogram *pCur, *pNext;
int i, j;
if(nColors > 256)
nColors = 256;
if(pExq->numColors == 0)
{
pExq->node[0].pHistogram = NULL;
for(i = 0; i < EXQ_HASH_SIZE; i++)
for(pCur = pExq->pHash[i]; pCur != NULL; pCur = pCur->pNextInHash)
{
pCur->pNext = pExq->node[0].pHistogram;
pExq->node[0].pHistogram = pCur;
}
exq_sum_node(&pExq->node[0]);
pExq->numColors = 1;
}
for(i = pExq->numColors; i < nColors; i++)
{
beste = 0;
besti = 0;
for(j = 0; j < i; j++)
if(pExq->node[j].vdif >= beste)
{
beste = pExq->node[j].vdif;
besti = j;
}
// printf("node %d: %d, %f\n", besti, pExq->node[besti].num, beste);
pCur = pExq->node[besti].pHistogram;
pExq->node[besti].pHistogram = NULL;
pExq->node[i].pHistogram = NULL;
while(pCur != NULL && pCur != pExq->node[besti].pSplit)
{
pNext = pCur->pNext;
pCur->pNext = pExq->node[i].pHistogram;
pExq->node[i].pHistogram = pCur;
pCur = pNext;
}
while(pCur != NULL)
{
pNext = pCur->pNext;
pCur->pNext = pExq->node[besti].pHistogram;
pExq->node[besti].pHistogram = pCur;
pCur = pNext;
}
exq_sum_node(&pExq->node[besti]);
exq_sum_node(&pExq->node[i]);
pExq->numColors = i + 1;
if(hq)
exq_optimize_palette(pExq, 1);
}
pExq->optimized = 0;
}
exq_float exq_get_mean_error(exq_data *pExq)
{
int i, n;
exq_float err;
n = 0;
err = 0;
for(i = 0; i < pExq->numColors; i++)
{
n += pExq->node[i].num;
err += pExq->node[i].err;
}
return sqrt(err / n) * 256;
}
void exq_get_palette(exq_data *pExq, unsigned char *pPal, int nColors)
{
int i, j;
exq_float r, g, b, a;
unsigned char channelMask = 0xff00 >> pExq->numBitsPerChannel;
if(nColors > pExq->numColors)
nColors = pExq->numColors;
if(!pExq->optimized)
exq_optimize_palette(pExq, 4);
for(i = 0; i < nColors; i++)
{
r = pExq->node[i].avg.r;
g = pExq->node[i].avg.g;
b = pExq->node[i].avg.b;
a = pExq->node[i].avg.a;
if(pExq->transparency == 1 && a != 0)
{
r /= a; g/= a; b/= a;
}
pPal[0] = (unsigned char)(r / SCALE_R * 255.9f);
pPal[1] = (unsigned char)(g / SCALE_G * 255.9f);
pPal[2] = (unsigned char)(b / SCALE_B * 255.9f);
pPal[3] = (unsigned char)(a / SCALE_A * 255.9f);
for(j = 0; j < 3; j++)
pPal[j] = (pPal[j] + (1 << (8 - pExq->numBitsPerChannel)) / 2) & channelMask;
pPal += 4;
}
}
void exq_set_palette(exq_data *pExq, unsigned char *pPal, int nColors)
{
int i;
pExq->numColors = nColors;
for(i = 0; i < nColors; i++)
{
pExq->node[i].avg.r = *pPal++ * SCALE_R / 255.9f;
pExq->node[i].avg.g = *pPal++ * SCALE_G / 255.9f;
pExq->node[i].avg.b = *pPal++ * SCALE_B / 255.9f;
pExq->node[i].avg.a = *pPal++ * SCALE_A / 255.9f;
}
pExq->optimized = 1;
}
void exq_sum_node(exq_node *pNode)
{
int n, n2;
exq_color fsum, fsum2, vc, tmp, tmp2, sum, sum2;
exq_histogram *pCur;
exq_float isqrt, nv, v;
n = 0;
fsum.r = fsum.g = fsum.b = fsum.a = 0;
fsum2.r = fsum2.g = fsum2.b = fsum2.a = 0;
for(pCur = pNode->pHistogram; pCur != NULL; pCur = pCur->pNext)
{
n += pCur->num;
fsum.r += pCur->color.r * pCur->num;
fsum.g += pCur->color.g * pCur->num;
fsum.b += pCur->color.b * pCur->num;
fsum.a += pCur->color.a * pCur->num;
fsum2.r += pCur->color.r * pCur->color.r * pCur->num;
fsum2.g += pCur->color.g * pCur->color.g * pCur->num;
fsum2.b += pCur->color.b * pCur->color.b * pCur->num;
fsum2.a += pCur->color.a * pCur->color.a * pCur->num;
}
pNode->num = n;
if(n == 0)
{
pNode->vdif = 0;
pNode->err = 0;
return;
}
pNode->avg.r = fsum.r / n;
pNode->avg.g = fsum.g / n;
pNode->avg.b = fsum.b / n;
pNode->avg.a = fsum.a / n;
vc.r = fsum2.r - fsum.r * pNode->avg.r;
vc.g = fsum2.g - fsum.g * pNode->avg.g;
vc.b = fsum2.b - fsum.b * pNode->avg.b;
vc.a = fsum2.a - fsum.a * pNode->avg.a;
v = vc.r + vc.g + vc.b + vc.a;
pNode->err = v;
pNode->vdif = -v;
if(vc.r > vc.g && vc.r > vc.b && vc.r > vc.a)
exq_sort(&pNode->pHistogram, exq_sort_by_r);
else if(vc.g > vc.b && vc.g > vc.a)
exq_sort(&pNode->pHistogram, exq_sort_by_g);
else if(vc.b > vc.a)
exq_sort(&pNode->pHistogram, exq_sort_by_b);
else
exq_sort(&pNode->pHistogram, exq_sort_by_a);
pNode->dir.r = pNode->dir.g = pNode->dir.b = pNode->dir.a = 0;
for(pCur = pNode->pHistogram; pCur != NULL; pCur = pCur->pNext)
{
tmp.r = (pCur->color.r - pNode->avg.r) * pCur->num;
tmp.g = (pCur->color.g - pNode->avg.g) * pCur->num;
tmp.b = (pCur->color.b - pNode->avg.b) * pCur->num;
tmp.a = (pCur->color.a - pNode->avg.a) * pCur->num;
if(tmp.r * pNode->dir.r + tmp.g * pNode->dir.g +
tmp.b * pNode->dir.b + tmp.a * pNode->dir.a < 0)
{
tmp.r = -tmp.r;
tmp.g = -tmp.g;
tmp.b = -tmp.b;
tmp.a = -tmp.a;
}
pNode->dir.r += tmp.r;
pNode->dir.g += tmp.g;
pNode->dir.b += tmp.b;
pNode->dir.a += tmp.a;
}
isqrt = 1 / sqrt(pNode->dir.r * pNode->dir.r +
pNode->dir.g * pNode->dir.g + pNode->dir.b * pNode->dir.b +
pNode->dir.a * pNode->dir.a);
pNode->dir.r *= isqrt;
pNode->dir.g *= isqrt;
pNode->dir.b *= isqrt;
pNode->dir.a *= isqrt;
exq_sort_dir = pNode->dir;
exq_sort(&pNode->pHistogram, exq_sort_by_dir);
sum.r = sum.g = sum.b = sum.a = 0;
sum2.r = sum2.g = sum2.b = sum2.a = 0;
n2 = 0;
pNode->pSplit = pNode->pHistogram;
for(pCur = pNode->pHistogram; pCur != NULL; pCur = pCur->pNext)
{
if(pNode->pSplit == NULL)
pNode->pSplit = pCur;
n2 += pCur->num;
sum.r += pCur->color.r * pCur->num;
sum.g += pCur->color.g * pCur->num;
sum.b += pCur->color.b * pCur->num;
sum.a += pCur->color.a * pCur->num;
sum2.r += pCur->color.r * pCur->color.r * pCur->num;
sum2.g += pCur->color.g * pCur->color.g * pCur->num;
sum2.b += pCur->color.b * pCur->color.b * pCur->num;
sum2.a += pCur->color.a * pCur->color.a * pCur->num;
if(n == n2)
break;
tmp.r = sum2.r - sum.r*sum.r / n2;
tmp.g = sum2.g - sum.g*sum.g / n2;
tmp.b = sum2.b - sum.b*sum.b / n2;
tmp.a = sum2.a - sum.a*sum.a / n2;
tmp2.r = (fsum2.r - sum2.r) - (fsum.r-sum.r)*(fsum.r-sum.r) / (n - n2);
tmp2.g = (fsum2.g - sum2.g) - (fsum.g-sum.g)*(fsum.g-sum.g) / (n - n2);
tmp2.b = (fsum2.b - sum2.b) - (fsum.b-sum.b)*(fsum.b-sum.b) / (n - n2);
tmp2.a = (fsum2.a - sum2.a) - (fsum.a-sum.a)*(fsum.a-sum.a) / (n - n2);
nv = tmp.r + tmp.g + tmp.b + tmp.a + tmp2.r + tmp2.g + tmp2.b + tmp2.a;
if(-nv > pNode->vdif)
{
pNode->vdif = -nv;
pNode->pSplit = NULL;
}
}
if(pNode->pSplit == pNode->pHistogram)
pNode->pSplit = pNode->pSplit->pNext;
pNode->vdif += v;
// printf("error sum: %f, vdif: %f\n", pNode->err, pNode->vdif);
}
void exq_optimize_palette(exq_data *pExq, int iter)
{
int n, i, j;
exq_histogram *pCur;
pExq->optimized = 1;
for(n = 0; n < iter; n++)
{
for(i = 0; i < pExq->numColors; i++)
pExq->node[i].pHistogram = NULL;
for(i = 0; i < EXQ_HASH_SIZE; i++)
for(pCur = pExq->pHash[i]; pCur != NULL; pCur = pCur->pNextInHash)
{
j = exq_find_nearest_color(pExq, &pCur->color);
pCur->pNext = pExq->node[j].pHistogram;
pExq->node[j].pHistogram = pCur;
}
for(i = 0; i < pExq->numColors; i++)
exq_sum_node(&pExq->node[i]);
}
}
void exq_map_image(exq_data *pExq, int nPixels, unsigned char *pIn,
unsigned char *pOut)
{
int i;
exq_color c;
exq_histogram *pHist;
if(!pExq->optimized)
exq_optimize_palette(pExq, 4);
for(i = 0; i < nPixels; i++)
{
pHist = exq_find_histogram(pExq, pIn);
if(pHist != NULL && pHist->palIndex != -1)
{
*pOut++ = (unsigned char)pHist->palIndex;
pIn += 4;
}
else
{
c.r = *pIn++ / 255.0f * SCALE_R;
c.g = *pIn++ / 255.0f * SCALE_G;
c.b = *pIn++ / 255.0f * SCALE_B;
c.a = *pIn++ / 255.0f * SCALE_A;
if(pExq->transparency)
{
c.r *= c.a; c.g *= c.a; c.b *= c.a;
}
*pOut = exq_find_nearest_color(pExq, &c);
if(pHist != NULL)
pHist->palIndex = *pOut;
pOut++;
}
}
}
void exq_map_image_ordered(exq_data *pExq, int width, int height,
unsigned char *pIn, unsigned char *pOut)
{
exq_map_image_dither(pExq, width, height, pIn, pOut, 1);
}
void exq_map_image_random(exq_data *pExq, int nPixels,
unsigned char *pIn, unsigned char *pOut)
{
exq_map_image_dither(pExq, nPixels, 1, pIn, pOut, 0);
}
void exq_map_image_dither(exq_data *pExq, int width, int height,
unsigned char *pIn, unsigned char *pOut, int ordered)
{
int x, y, i, j, d;
exq_color p, scale, tmp;
exq_histogram *pHist;
const exq_float dither_matrix[4] = { -0.375, 0.125, 0.375, -0.125 };
if(!pExq->optimized)
exq_optimize_palette(pExq, 4);
for(y = 0; y < height; y++)
for(x = 0; x < width; x++)
{
if(ordered)
d = (x & 1) + (y & 1) * 2;
else
d = rand() & 3;
pHist = exq_find_histogram(pExq, pIn);
p.r = *pIn++ / 255.0f * SCALE_R;
p.g = *pIn++ / 255.0f * SCALE_G;
p.b = *pIn++ / 255.0f * SCALE_B;
p.a = *pIn++ / 255.0f * SCALE_A;
if(pExq->transparency)
{
p.r *= p.a; p.g *= p.a; p.b *= p.a;
}
if(pHist == NULL || pHist->ditherScale.r < 0)
{
i = exq_find_nearest_color(pExq, &p);
scale.r = pExq->node[i].avg.r - p.r;
scale.g = pExq->node[i].avg.g - p.g;
scale.b = pExq->node[i].avg.b - p.b;
scale.a = pExq->node[i].avg.a - p.a;
tmp.r = p.r - scale.r / 3;
tmp.g = p.g - scale.g / 3;
tmp.b = p.b - scale.b / 3;
tmp.a = p.a - scale.a / 3;
j = exq_find_nearest_color(pExq, &tmp);
if(i == j)
{
tmp.r = p.r - scale.r * 3;
tmp.g = p.g - scale.g * 3;
tmp.b = p.b - scale.b * 3;
tmp.a = p.a - scale.a * 3;
j = exq_find_nearest_color(pExq, &tmp);
}
if(i != j)
{
scale.r = (pExq->node[j].avg.r - pExq->node[i].avg.r) * 0.8f;
scale.g = (pExq->node[j].avg.g - pExq->node[i].avg.g) * 0.8f;
scale.b = (pExq->node[j].avg.b - pExq->node[i].avg.b) * 0.8f;
scale.a = (pExq->node[j].avg.a - pExq->node[i].avg.a) * 0.8f;
if(scale.r < 0) scale.r = -scale.r;
if(scale.g < 0) scale.g = -scale.g;
if(scale.b < 0) scale.b = -scale.b;
if(scale.a < 0) scale.a = -scale.a;
}
else
scale.r = scale.g = scale.b = scale.a = 0;
if(pHist != NULL)
{
pHist->ditherScale.r = scale.r;
pHist->ditherScale.g = scale.g;
pHist->ditherScale.b = scale.b;
pHist->ditherScale.a = scale.a;
}
}
else
{
scale.r = pHist->ditherScale.r;
scale.g = pHist->ditherScale.g;
scale.b = pHist->ditherScale.b;
scale.a = pHist->ditherScale.a;
}
if(pHist != NULL && pHist->ditherIndex[d] >= 0)
*pOut++ = (unsigned char)pHist->ditherIndex[d];
else
{
tmp.r = p.r + scale.r * dither_matrix[d];
tmp.g = p.g + scale.g * dither_matrix[d];
tmp.b = p.b + scale.b * dither_matrix[d];
tmp.a = p.a + scale.a * dither_matrix[d];
*pOut = exq_find_nearest_color(pExq, &tmp);
if(pHist != NULL)
pHist->ditherIndex[d] = *pOut;
pOut++;
}
}
}
exq_histogram *exq_find_histogram(exq_data *pExq, unsigned char *pCol)
{
unsigned int hash;
int r, g, b, a;
exq_histogram *pCur;
r = *pCol++; g = *pCol++; b = *pCol++; a = *pCol++;
hash = exq_make_hash(((unsigned int)r) | (((unsigned int)g) << 8) | (((unsigned int)b) << 16) | (((unsigned int)a) << 24));
pCur = pExq->pHash[hash];
while(pCur != NULL && (pCur->ored != r || pCur->ogreen != g ||
pCur->oblue != b || pCur->oalpha != a))
pCur = pCur->pNextInHash;
return pCur;
}
unsigned char exq_find_nearest_color(exq_data *pExq, exq_color *pColor)
{
exq_float bestv;
int besti, i;
exq_color dif;
bestv = 16;
besti = 0;
for(i = 0; i < pExq->numColors; i++)
{
dif.r = pColor->r - pExq->node[i].avg.r;
dif.g = pColor->g - pExq->node[i].avg.g;
dif.b = pColor->b - pExq->node[i].avg.b;
dif.a = pColor->a - pExq->node[i].avg.a;
if(dif.r*dif.r + dif.g*dif.g + dif.b*dif.b + dif.a*dif.a < bestv)
{
bestv = dif.r*dif.r + dif.g*dif.g + dif.b*dif.b + dif.a*dif.a;
besti = i;
}
}
return (unsigned char)besti;
}
void exq_sort(exq_histogram **ppHist, exq_float (*sortfunc)(const exq_histogram *pHist))
{
exq_histogram *pLow, *pHigh, *pCur, *pNext;
int n = 0;
exq_float sum = 0;
for(pCur = *ppHist; pCur != NULL; pCur = pCur->pNext)
{
n++;
sum += sortfunc(pCur);
}
if(n < 2)
return;
sum /= n;
pLow = pHigh = NULL;
for(pCur = *ppHist; pCur != NULL; pCur = pNext)
{
pNext = pCur->pNext;
if(sortfunc(pCur) < sum)
{
pCur->pNext = pLow;
pLow = pCur;
}
else
{
pCur->pNext = pHigh;
pHigh = pCur;
}
}
if(pLow == NULL)
{
*ppHist = pHigh;
return;
}
if(pHigh == NULL)
{
*ppHist = pLow;
return;
}
exq_sort(&pLow, sortfunc);
exq_sort(&pHigh, sortfunc);
*ppHist = pLow;
while(pLow->pNext != NULL)
pLow = pLow->pNext;
pLow->pNext = pHigh;
}
exq_float exq_sort_by_r(const exq_histogram *pHist)
{
return pHist->color.r;
}
exq_float exq_sort_by_g(const exq_histogram *pHist)
{
return pHist->color.g;
}
exq_float exq_sort_by_b(const exq_histogram *pHist)
{
return pHist->color.b;
}
exq_float exq_sort_by_a(const exq_histogram *pHist)
{
return pHist->color.a;
}
exq_color exq_sort_dir;
exq_float exq_sort_by_dir(const exq_histogram *pHist)
{
return pHist->color.r * exq_sort_dir.r +
pHist->color.g * exq_sort_dir.g +
pHist->color.b * exq_sort_dir.b +
pHist->color.a * exq_sort_dir.a;
}

View File

@ -1,150 +0,0 @@
/*
ExoQuant v0.7
Copyright (c) 2004 Dennis Ranke
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/******************************************************************************
* Usage:
* ------
*
* exq_data *pExq = exq_init(); // init quantizer (per image)
* exq_feed(pExq, <ptr to image>, <num of pixels); // feed pixel data (32bpp)
* exq_quantize(pExq, <num of colors>); // find palette
* exq_get_palette(pExq, <ptr to buffer>, <num of colors>); // get palette
* exq_map_image(pExq, <num of pixels>, <ptr to input>, <ptr to output>);
* or:
* exq_map_image_ordered(pExq, <width>, <height>, <input>, <output>);
* // map image to palette
* exq_free(pExq); // free memory again
*
* Notes:
* ------
*
* All 32bpp data (input data and palette data) is considered a byte stream
* of the format:
* R0 G0 B0 A0 R1 G1 B1 A1 ...
* If you want to use a different order, the easiest way to do this is to
* change the SCALE_x constants in expquant.h, as those are the only differences
* between the channels.
*
******************************************************************************/
#ifndef __EXOQUANT_H
#define __EXOQUANT_H
#ifdef __cplusplus
extern "C" {
#endif
/* type definitions */
typedef double exq_float;
typedef struct _exq_color
{
exq_float r, g, b, a;
} exq_color;
typedef struct _exq_histogram
{
exq_color color;
unsigned char ored, ogreen, oblue, oalpha;
int palIndex;
exq_color ditherScale;
int ditherIndex[4];
int num;
struct _exq_histogram *pNext;
struct _exq_histogram *pNextInHash;
} exq_histogram;
typedef struct _exq_node
{
exq_color dir, avg;
exq_float vdif;
exq_float err;
int num;
exq_histogram *pHistogram;
exq_histogram *pSplit;
} exq_node;
#define EXQ_HASH_BITS 16
#define EXQ_HASH_SIZE (1 << (EXQ_HASH_BITS))
typedef struct _exq_data
{
exq_histogram *pHash[EXQ_HASH_SIZE];
exq_node node[256];
int numColors;
int numBitsPerChannel;
int optimized;
int transparency;
} exq_data;
/* interface */
exq_data *exq_init();
void exq_no_transparency(exq_data *pExq);
void exq_free(exq_data *pExq);
void exq_feed(exq_data *pExq, unsigned char *pData,
int nPixels);
void exq_quantize(exq_data *pExq, int nColors);
void exq_quantize_hq(exq_data *pExq, int nColors);
void exq_quantize_ex(exq_data *pExq, int nColors, int hq);
exq_float exq_get_mean_error(exq_data *pExq);
void exq_get_palette(exq_data *pExq, unsigned char *pPal,
int nColors);
void exq_set_palette(exq_data *pExq, unsigned char *pPal,
int nColors);
void exq_map_image(exq_data *pExq, int nPixels,
unsigned char *pIn, unsigned char *pOut);
void exq_map_image_ordered(exq_data *pExq, int width,
int height, unsigned char *pIn,
unsigned char *pOut);
void exq_map_image_random(exq_data *pExq, int nPixels,
unsigned char *pIn, unsigned char *pOut);
/* internal functions */
void exq_map_image_dither(exq_data *pExq, int width,
int height, unsigned char *pIn,
unsigned char *pOut, int ordered);
void exq_sum_node(exq_node *pNode);
void exq_optimize_palette(exq_data *pExp, int iter);
unsigned char exq_find_nearest_color(exq_data *pExp, exq_color *pColor);
exq_histogram *exq_find_histogram(exq_data *pExp, unsigned char *pCol);
void exq_sort(exq_histogram **ppHist,
exq_float (*sortfunc)(const exq_histogram *pHist));
exq_float exq_sort_by_r(const exq_histogram *pHist);
exq_float exq_sort_by_g(const exq_histogram *pHist);
exq_float exq_sort_by_b(const exq_histogram *pHist);
exq_float exq_sort_by_a(const exq_histogram *pHist);
exq_float exq_sort_by_dir(const exq_histogram *pHist);
extern exq_color exq_sort_dir;
#ifdef __cplusplus
}
#endif
#endif // __EXOQUANT_H

View File

@ -1,573 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#define STBI_NO_LINEAR
#define STBI_NO_HDR
#define STBI_NO_TGA
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_image_write.h>
#include "exoquant/exoquant.h"
#include "n64graphics_ci.h"
#include "utils.h"
// SCALE_M_N: upscale/downscale M-bit integer to N-bit
#define SCALE_5_8(VAL_) (((VAL_) * 0xFF) / 0x1F)
#define SCALE_8_5(VAL_) ((((VAL_) + 4) * 0x1F) / 0xFF)
#define SCALE_4_8(VAL_) ((VAL_) * 0x11)
#define SCALE_8_4(VAL_) ((VAL_) / 0x11)
#define SCALE_3_8(VAL_) ((VAL_) * 0x24)
#define SCALE_8_3(VAL_) ((VAL_) / 0x24)
typedef enum
{
IMG_FORMAT_CI
} img_format;
rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth)
{
rgba *img;
int img_size;
img_size = width * height * sizeof(*img);
img = malloc(img_size);
if (!img) {
ERROR("Error allocating %d bytes\n", img_size);
return NULL;
}
if (depth == 16) {
for (int i = 0; i < width * height; i++) {
img[i].red = SCALE_5_8((raw[i * 2] & 0xF8) >> 3);
img[i].green = SCALE_5_8(((raw[i * 2] & 0x07) << 2) | ((raw[i * 2 + 1] & 0xC0) >> 6));
img[i].blue = SCALE_5_8((raw[i * 2 + 1] & 0x3E) >> 1);
img[i].alpha = (raw[i * 2 + 1] & 0x01) ? 0xFF : 0x00;
}
}
else if (depth == 32) {
for (int i = 0; i < width * height; i++) {
img[i].red = raw[i * 4];
img[i].green = raw[i * 4 + 1];
img[i].blue = raw[i * 4 + 2];
img[i].alpha = raw[i * 4 + 3];
}
}
return img;
}
// extract RGBA from CI raw data and palette
rgba *rawci2rgba(const uint8_t *rawci, const uint8_t *palette, int width, int height, int depth)
{
uint8_t *raw_rgba;
rgba *img = NULL;
int raw_size;
int pal_index_1;
int pal_index_2;
// first convert to raw RGBA
raw_size = 2 * width * height;
raw_rgba = malloc(raw_size);
if (!raw_rgba) {
ERROR("Error allocating %u bytes\n", raw_size);
return NULL;
}
if (depth == 4) { // CI4
for (int i = 0; i < (width * height) / 2; i++) {
pal_index_1 = rawci[i] >> 4;
pal_index_2 = rawci[i] & 0xF;
raw_rgba[i * 4 + 0] = palette[pal_index_1 * 2 + 0];
raw_rgba[i * 4 + 1] = palette[pal_index_1 * 2 + 1];
raw_rgba[i * 4 + 2] = palette[pal_index_2 * 2 + 0];
raw_rgba[i * 4 + 3] = palette[pal_index_2 * 2 + 1];
}
} else { // CI8
for (int i = 0; i < width * height; i++) {
pal_index_1 = rawci[i];
raw_rgba[i * 2 + 0] = palette[pal_index_1 * 2 + 0];
raw_rgba[i * 2 + 1] = palette[pal_index_1 * 2 + 1];
}
}
// then convert to RGBA image data
img = raw2rgba(raw_rgba, width, height, 16);
free(raw_rgba);
return img;
}
int rgba2rawci(uint8_t *raw, uint8_t *out_palette, int *pal_len, const rgba *img, int width, int height, int depth)
{
int size = width * height * depth / 8;
int num_colors = *pal_len / 2;
INFO("Converting RGBA %dx%d to raw CI%d\n", width, height, depth);
uint8_t *rgba32_palette = malloc(num_colors * 4);
uint8_t *ci8_raw = malloc(width * height);
// Use ExoQuant to convert the RGBA32 data buffer to an CI8 output
exq_data *pExq = exq_init();
exq_feed(pExq, (uint8_t*)img, width * height);
exq_quantize_hq(pExq, num_colors);
exq_get_palette(pExq, rgba32_palette, num_colors);
exq_map_image_ordered(pExq, width, height, (uint8_t*)img, ci8_raw);
exq_free(pExq);
if (depth == 4) {
// Convert CI8 image to CI4 image
for (int i = 0; i < size; i++) {
raw[i] = (ci8_raw[i * 2 + 0] << 4) | ci8_raw[i * 2 + 1];
}
}
else {
memcpy(raw, ci8_raw, size);
}
// Convert RGBA32 palette to RGBA16
for (int i = 0; i < num_colors; i++) {
unsigned char red = (rgba32_palette[i * 4 + 0] / 8) & 0x1F;
unsigned char green = (rgba32_palette[i * 4 + 1] / 8) & 0x1F;
unsigned char blue = (rgba32_palette[i * 4 + 2] / 8) & 0x1F;
unsigned char alpha = rgba32_palette[i * 4 + 3] > 0 ? 1 : 0; // 1 bit alpha
out_palette[i * 2 + 0] = (red << 3) | (green >> 2);
out_palette[i * 2 + 1] = ((green & 3) << 6) | (blue << 1) | alpha;
}
free(rgba32_palette);
free(ci8_raw);
return size;
}
rgba *png2rgba(const char *png_filename, int *width, int *height)
{
rgba *img = NULL;
int w = 0;
int h = 0;
int channels = 0;
int img_size;
stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);
if (!data || w <= 0 || h <= 0) {
ERROR("Error loading png file \"%s\"\n", png_filename);
return NULL;
}
INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);
img_size = w * h * sizeof(*img);
img = malloc(img_size);
if (!img) {
ERROR("Error allocating %u bytes\n", img_size);
return NULL;
}
switch (channels) {
case 3: // red, green, blue
case 4: // red, green, blue, alpha
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
int idx = j*w + i;
img[idx].red = data[channels*idx];
img[idx].green = data[channels*idx + 1];
img[idx].blue = data[channels*idx + 2];
if (channels == 4) {
img[idx].alpha = data[channels*idx + 3];
}
else {
img[idx].alpha = 0xFF;
}
}
}
break;
case 2: // grey, alpha
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
int idx = j*w + i;
img[idx].red = data[2 * idx];
img[idx].green = data[2 * idx];
img[idx].blue = data[2 * idx];
img[idx].alpha = data[2 * idx + 1];
}
}
break;
default:
ERROR("Don't know how to read channels: %d\n", channels);
free(img);
img = NULL;
}
// cleanup
stbi_image_free(data);
*width = w;
*height = h;
return img;
}
int rgba2png(const char *png_filename, const rgba *img, int width, int height)
{
int ret = 0;
INFO("Saving RGBA %dx%d to \"%s\"\n", width, height, png_filename);
// convert to format stb_image_write expects
uint8_t *data = malloc(4 * width*height);
if (data) {
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
int idx = j*width + i;
data[4 * idx] = img[idx].red;
data[4 * idx + 1] = img[idx].green;
data[4 * idx + 2] = img[idx].blue;
data[4 * idx + 3] = img[idx].alpha;
}
}
ret = stbi_write_png(png_filename, width, height, 4, data, 0);
free(data);
}
return ret;
}
const char *n64graphics_get_read_version(void)
{
return "stb_image 2.19";
}
const char *n64graphics_get_write_version(void)
{
return "stb_image_write 1.09";
}
/***************************************************************/
#define N64GRAPHICS_VERSION "0.4 - CI Only Branch"
#include <string.h>
typedef enum
{
MODE_EXPORT,
MODE_IMPORT,
} tool_mode;
typedef struct
{
char *img_filename;
char *bin_filename;
tool_mode mode;
unsigned int offset;
img_format format;
int depth;
int width;
int height;
int truncate;
} graphics_config;
static const graphics_config default_config =
{
.img_filename = NULL,
.bin_filename = NULL,
.mode = MODE_EXPORT,
.offset = 0,
.format = IMG_FORMAT_CI,
.depth = 8,
.width = 32,
.height = 32,
.truncate = 1,
};
typedef struct
{
const char *name;
img_format format;
int depth;
} format_entry;
static const format_entry format_table[] =
{
{ "ci4", IMG_FORMAT_CI, 4 },
{ "ci8", IMG_FORMAT_CI, 8 },
};
static const char *format2str(img_format format, int depth)
{
for (unsigned i = 0; i < DIM(format_table); i++) {
if (format == format_table[i].format && depth == format_table[i].depth) {
return format_table[i].name;
}
}
return "unknown";
}
static int parse_format(graphics_config *config, const char *str)
{
for (unsigned i = 0; i < DIM(format_table); i++) {
if (!strcasecmp(str, format_table[i].name)) {
config->format = format_table[i].format;
config->depth = format_table[i].depth;
return 1;
}
}
return 0;
}
static void print_usage(void)
{
ERROR("Usage: n64graphics_ci -e/-i BIN_FILE -g PNG_FILE [-o offset] [-f FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n"
"\n"
"n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n"
"\n"
"Required arguments:\n"
" -e BIN_FILE export from BIN_FILE to PNG_FILE\n"
" -i BIN_FILE import from PNG_FILE to BIN_FILE\n"
" -g PNG_FILE graphics file to import/export (.png)\n"
"Optional arguments:\n"
" -o OFFSET starting offset in BIN_FILE (prevents truncation during import)\n"
" -f FORMAT texture format: ci4, ci8 (default: %s)\n"
" -w WIDTH export texture width (default: %d)\n"
" -h HEIGHT export texture height (default: %d)\n"
" -v verbose logging\n"
" -V print version information\n",
format2str(default_config.format, default_config.depth),
default_config.width,
default_config.height);
}
static void print_version(void)
{
ERROR("n64graphics v" N64GRAPHICS_VERSION ", using:\n"
" %s\n"
" %s\n",
n64graphics_get_read_version(), n64graphics_get_write_version());
}
// parse command line arguments
static int parse_arguments(int argc, char *argv[], graphics_config *config)
{
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
switch (argv[i][1]) {
case 'e':
if (++i >= argc) return 0;
config->bin_filename = argv[i];
config->mode = MODE_EXPORT;
break;
case 'f':
if (++i >= argc) return 0;
if (!parse_format(config, argv[i])) {
return 0;
}
break;
case 'i':
if (++i >= argc) return 0;
config->bin_filename = argv[i];
config->mode = MODE_IMPORT;
break;
case 'g':
if (++i >= argc) return 0;
config->img_filename = argv[i];
break;
case 'h':
if (++i >= argc) return 0;
config->height = strtoul(argv[i], NULL, 0);
break;
case 'o':
if (++i >= argc) return 0;
config->offset = strtoul(argv[i], NULL, 0);
config->truncate = 0;
break;
case 'w':
if (++i >= argc) return 0;
config->width = strtoul(argv[i], NULL, 0);
break;
case 'v':
g_verbosity = 1;
break;
case 'V':
print_version();
exit(0);
break;
default:
return 0;
break;
}
}
else {
return 0;
}
}
return 1;
}
char* getPaletteFilename(char* ci_filename)
{
int bin_filename_len;
int extension_len;
char* pal_bin_filename;
char* extension_loc;
char* extension;
// Write Palette file
bin_filename_len = strlen(ci_filename);
extension_loc = strrchr(ci_filename, '.');
extension_len = (int)(extension_loc - ci_filename);
extension = malloc(extension_len);
memcpy(extension, extension_loc, extension_len);
pal_bin_filename = malloc(bin_filename_len + 4); // +4 to include ".pal"
if (!pal_bin_filename) {
ERROR("Error allocating bytes for palette filename\n");
}
memcpy(pal_bin_filename, ci_filename, bin_filename_len);
strcpy(pal_bin_filename + bin_filename_len, ".pal");
//strcpy(pal_bin_filename + bin_filename_len, extension);
free(extension);
return pal_bin_filename;
}
int main(int argc, char* argv[])
{
graphics_config config = default_config;
rgba *imgr;
FILE *fp;
uint8_t *raw;
int raw_size;
int length = 0;
int flength;
int res;
FILE *fp_pal; // for ci palette
uint8_t *pal;
int pal_len;
char* pal_bin_filename;
int valid = parse_arguments(argc, argv, &config);
if (!valid || !config.bin_filename || !config.bin_filename || !config.img_filename) {
print_usage();
exit(EXIT_FAILURE);
}
if (config.mode == MODE_IMPORT) {
printf("%s\n", config.bin_filename);
if (config.truncate) {
fp = fopen(config.bin_filename, "wb");
}
else {
fp = fopen(config.bin_filename, "r+b");
}
if (!fp) {
ERROR("Error opening binary file \"%s\"\n", config.bin_filename);
return -1;
}
if (!config.truncate) {
fseek(fp, config.offset, SEEK_SET);
}
switch (config.format) {
case IMG_FORMAT_CI:
imgr = png2rgba(config.img_filename, &config.width, &config.height);
raw_size = config.width * config.height * config.depth / 8;
raw = malloc(raw_size);
if (!raw) {
ERROR("Error allocating %u bytes\n", raw_size);
}
if (config.depth == 4) {
pal_len = 16 * 2; // CI4
}
else {
pal_len = 256 * 2; // CI8
}
pal = malloc(pal_len);
if (!pal) {
ERROR("Error allocating %u bytes for palette\n", pal_len);
}
length = rgba2rawci(raw, pal, &pal_len, imgr, config.width, config.height, config.depth);
//length = rgba2raw(raw, imgr, config.width, config.height, config.depth);
break;
}
if (length <= 0) {
ERROR("Error converting to raw format\n");
return EXIT_FAILURE;
}
INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", length, config.offset, config.bin_filename);
flength = fwrite(raw, 1, length, fp);
if (flength != length) {
ERROR("Error writing %d bytes to \"%s\"\n", length, config.bin_filename);
}
fclose(fp);
if (config.format == IMG_FORMAT_CI) {
pal_bin_filename = getPaletteFilename(config.bin_filename);
fp_pal = fopen(pal_bin_filename, "wb");
INFO("Writing 0x%X bytes to palette file \"%s\"\n", pal_len, pal_bin_filename);
flength = fwrite(pal, 1, pal_len, fp_pal);
if (flength != pal_len) {
ERROR("Error writing %d bytes to \"%s\"\n", pal_len, pal_bin_filename);
}
fclose(fp_pal);
free(pal_bin_filename);
}
} else { // Export
if (config.width <= 0 || config.height <= 0 || config.depth <= 0) {
ERROR("Error: must set position width and height for export\n");
return EXIT_FAILURE;
}
fp = fopen(config.bin_filename, "rb");
if (!fp) {
ERROR("Error opening \"%s\"\n", config.bin_filename);
return -1;
}
raw_size = config.width * config.height * config.depth / 8;
raw = malloc(raw_size);
if (config.offset > 0) {
fseek(fp, config.offset, SEEK_SET);
}
flength = fread(raw, 1, raw_size, fp);
if (flength != raw_size) {
ERROR("Error reading %d bytes from \"%s\"\n", raw_size, config.bin_filename);
}
switch (config.format) {
case IMG_FORMAT_CI:
// Read Palette file
pal_bin_filename = getPaletteFilename(config.bin_filename);
fp_pal = fopen(pal_bin_filename, "rb");
if (!fp_pal) {
ERROR("Error opening \"%s\"\n", pal_bin_filename);
return -1;
}
if (config.depth == 4) {
pal_len = 16 * 2; // CI4
}
else {
pal_len = 256 * 2; // CI8
}
pal = malloc(pal_len);
if (config.offset > 0) {
fseek(fp, config.offset, SEEK_SET);
}
flength = fread(pal, 1, pal_len, fp_pal);
imgr = rawci2rgba(raw, pal, config.width, config.height, config.depth);
res = rgba2png(config.img_filename, imgr, config.width, config.height);
free(pal_bin_filename);
break;
default:
return EXIT_FAILURE;
}
if (!res) {
ERROR("Error writing to \"%s\"\n", config.img_filename);
}
}
return 0;
}

View File

@ -1,42 +0,0 @@
#ifndef N64GRAPHICS_CI_H_
#define N64GRAPHICS_CI_H_
#include <stdint.h>
// intermediate formats
typedef struct _rgba
{
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
} rgba;
typedef struct _ia
{
uint8_t intensity;
uint8_t alpha;
} ia;
// N64 raw RGBA16/RGBA32 -> intermediate RGBA
rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth);
// N64 raw CI + palette -> intermediate RGBA
rgba *rawci2rgba(const uint8_t *rawci, const uint8_t *palette, int width, int height, int depth);
// intermediate RGBA -> N64 raw CI + palette
int rgba2rawci(uint8_t *raw, uint8_t *out_palette, int *pal_len, const rgba *img, int width, int height, int depth);
// PNG file -> intermediate RGBA
rgba *png2rgba(const char *png_filename, int *width, int *height);
// intermediate RGBA write to PNG file
int rgba2png(const char *png_filename, const rgba *img, int width, int height);
// get version of underlying graphics reading library
const char *n64graphics_get_read_version(void);
// get version of underlying graphics writing library
const char *n64graphics_get_write_version(void);
#endif

View File

@ -1,276 +0,0 @@
#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#if defined(_MSC_VER) || defined(__MINGW32__)
#include <io.h>
#include <sys/utime.h>
#else
#include <unistd.h>
#include <utime.h>
#endif
#include "utils.h"
// global verbosity setting
int g_verbosity = 0;
int read_s16_be(unsigned char *buf)
{
unsigned tmp = read_u16_be(buf);
int ret;
if (tmp > 0x7FFF) {
ret = -((int)0x10000 - (int)tmp);
} else {
ret = (int)tmp;
}
return ret;
}
float read_f32_be(unsigned char *buf)
{
union {uint32_t i; float f;} ret;
ret.i = read_u32_be(buf);
return ret.f;
}
int is_power2(unsigned int val)
{
while (((val & 1) == 0) && (val > 1)) {
val >>= 1;
}
return (val == 1);
}
void fprint_hex(FILE *fp, const unsigned char *buf, int length)
{
int i;
for (i = 0; i < length; i++) {
fprint_byte(fp, buf[i]);
fputc(' ', fp);
}
}
void fprint_hex_source(FILE *fp, const unsigned char *buf, int length)
{
int i;
for (i = 0; i < length; i++) {
if (i > 0) fputs(", ", fp);
fputs("0x", fp);
fprint_byte(fp, buf[i]);
}
}
void print_hex(const unsigned char *buf, int length)
{
fprint_hex(stdout, buf, length);
}
void swap_bytes(unsigned char *data, long length)
{
long i;
unsigned char tmp;
for (i = 0; i < length; i += 2) {
tmp = data[i];
data[i] = data[i+1];
data[i+1] = tmp;
}
}
void reverse_endian(unsigned char *data, long length)
{
long i;
unsigned char tmp;
for (i = 0; i < length; i += 4) {
tmp = data[i];
data[i] = data[i+3];
data[i+3] = tmp;
tmp = data[i+1];
data[i+1] = data[i+2];
data[i+2] = tmp;
}
}
long filesize(const char *filename)
{
struct stat st;
if (stat(filename, &st) == 0) {
return st.st_size;
}
return -1;
}
void touch_file(const char *filename)
{
int fd;
//fd = open(filename, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666);
fd = open(filename, O_WRONLY|O_CREAT, 0666);
if (fd >= 0) {
utime(filename, NULL);
close(fd);
}
}
long read_file(const char *file_name, unsigned char **data)
{
FILE *in;
unsigned char *in_buf = NULL;
long file_size;
long bytes_read;
in = fopen(file_name, "rb");
if (in == NULL) {
return -1;
}
// allocate buffer to read from offset to end of file
fseek(in, 0, SEEK_END);
file_size = ftell(in);
// sanity check
if (file_size > 256*MB) {
return -2;
}
in_buf = malloc(file_size);
fseek(in, 0, SEEK_SET);
// read bytes
bytes_read = fread(in_buf, 1, file_size, in);
if (bytes_read != file_size) {
return -3;
}
fclose(in);
*data = in_buf;
return bytes_read;
}
long write_file(const char *file_name, unsigned char *data, long length)
{
FILE *out;
long bytes_written;
// open output file
out = fopen(file_name, "wb");
if (out == NULL) {
perror(file_name);
return -1;
}
bytes_written = fwrite(data, 1, length, out);
fclose(out);
return bytes_written;
}
void generate_filename(const char *in_name, char *out_name, char *extension)
{
char tmp_name[FILENAME_MAX];
int len;
int i;
strcpy(tmp_name, in_name);
len = strlen(tmp_name);
for (i = len - 1; i > 0; i--) {
if (tmp_name[i] == '.') {
break;
}
}
if (i <= 0) {
i = len;
}
tmp_name[i] = '\0';
sprintf(out_name, "%s.%s", tmp_name, extension);
}
char *basename(const char *name)
{
const char *base = name;
while (*name) {
if (*name++ == '/') {
base = name;
}
}
return (char *)base;
}
void make_dir(const char *dir_name)
{
struct stat st = {0};
if (stat(dir_name, &st) == -1) {
mkdir(dir_name, 0755);
}
}
long copy_file(const char *src_name, const char *dst_name)
{
unsigned char *buf;
long bytes_written;
long bytes_read;
bytes_read = read_file(src_name, &buf);
if (bytes_read > 0) {
bytes_written = write_file(dst_name, buf, bytes_read);
if (bytes_written != bytes_read) {
bytes_read = -1;
}
free(buf);
}
return bytes_read;
}
void dir_list_ext(const char *dir, const char *extension, dir_list *list)
{
char *pool;
char *pool_ptr;
struct dirent *entry;
DIR *dfd;
int idx;
dfd = opendir(dir);
if (dfd == NULL) {
ERROR("Can't open '%s'\n", dir);
exit(1);
}
pool = malloc(FILENAME_MAX * MAX_DIR_FILES);
pool_ptr = pool;
idx = 0;
while ((entry = readdir(dfd)) != NULL && idx < MAX_DIR_FILES) {
if (!extension || str_ends_with(entry->d_name, extension)) {
sprintf(pool_ptr, "%s/%s", dir, entry->d_name);
list->files[idx] = pool_ptr;
pool_ptr += strlen(pool_ptr) + 1;
idx++;
}
}
list->count = idx;
closedir(dfd);
}
void dir_list_free(dir_list *list)
{
// assume first entry in array is allocated
if (list->files[0]) {
free(list->files[0]);
list->files[0] = NULL;
}
}
int str_ends_with(const char *str, const char *suffix)
{
if (!str || !suffix) {
return 0;
}
size_t len_str = strlen(str);
size_t len_suffix = strlen(suffix);
if (len_suffix > len_str) {
return 0;
}
return (0 == strncmp(str + len_str - len_suffix, suffix, len_suffix));
}

View File

@ -1,153 +0,0 @@
#ifndef UTILS_H_
#define UTILS_H_
#include <stdio.h>
// defines
// printing size_t varies by compiler
#if defined(_MSC_VER) || defined(__MINGW32__)
#define SIZE_T_FORMAT "%Iu"
#else
#define SIZE_T_FORMAT "%zu"
#endif
#define KB 1024
#define MB (1024 * KB)
// number of elements in statically declared array
#define DIM(S_ARR_) (sizeof(S_ARR_) / sizeof(S_ARR_[0]))
#define MIN(A_, B_) ((A_) < (B_) ? (A_) : (B_))
#define MAX(A_, B_) ((A_) > (B_) ? (A_) : (B_))
// align value to N-byte boundary
#define ALIGN(VAL_, ALIGNMENT_) (((VAL_) + ((ALIGNMENT_) - 1)) & ~((ALIGNMENT_) - 1))
// read/write u32/16 big/little endian
#define read_u32_be(buf) (unsigned int)(((buf)[0] << 24) + ((buf)[1] << 16) + ((buf)[2] << 8) + ((buf)[3]))
#define read_u32_le(buf) (unsigned int)(((buf)[1] << 24) + ((buf)[0] << 16) + ((buf)[3] << 8) + ((buf)[2]))
#define write_u32_be(buf, val) do { \
(buf)[0] = ((val) >> 24) & 0xFF; \
(buf)[1] = ((val) >> 16) & 0xFF; \
(buf)[2] = ((val) >> 8) & 0xFF; \
(buf)[3] = (val) & 0xFF; \
} while(0)
#define read_u16_be(buf) (((buf)[0] << 8) + ((buf)[1]))
#define write_u16_be(buf, val) do { \
(buf)[0] = ((val) >> 8) & 0xFF; \
(buf)[1] = ((val)) & 0xFF; \
} while(0)
// print nibbles and bytes
#define fprint_nibble(FP, NIB_) fputc((NIB_) < 10 ? ('0' + (NIB_)) : ('A' + (NIB_) - 0xA), FP)
#define fprint_byte(FP, BYTE_) do { \
fprint_nibble(FP, (BYTE_) >> 4); \
fprint_nibble(FP, (BYTE_) & 0x0F); \
} while(0)
#define print_nibble(NIB_) fprint_nibble(stdout, NIB_)
#define print_byte(BYTE_) fprint_byte(stdout, BYTE_)
// Windows compatibility
#if defined(_MSC_VER) || defined(__MINGW32__)
#include <direct.h>
#define mkdir(DIR_, PERM_) _mkdir(DIR_)
#ifndef strcasecmp
#define strcasecmp(A, B) stricmp(A, B)
#endif
#endif
// typedefs
#define MAX_DIR_FILES 128
typedef struct
{
char *files[MAX_DIR_FILES];
int count;
} dir_list;
// global verbosity setting
extern int g_verbosity;
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
#define INFO(...) if (g_verbosity) printf(__VA_ARGS__)
#define INFO_HEX(...) if (g_verbosity) print_hex(__VA_ARGS__)
// functions
// convert two bytes in big-endian to signed int
int read_s16_be(unsigned char *buf);
// convert four bytes in big-endian to float
float read_f32_be(unsigned char *buf);
// determine if value is power of 2
// returns 1 if val is power of 2, 0 otherwise
int is_power2(unsigned int val);
// print buffer as hex bytes
// fp: file pointer
// buf: buffer to read bytes from
// length: length of buffer to print
void fprint_hex(FILE *fp, const unsigned char *buf, int length);
void fprint_hex_source(FILE *fp, const unsigned char *buf, int length);
void print_hex(const unsigned char *buf, int length);
// perform byteswapping to convert from v64 to z64 ordering
void swap_bytes(unsigned char *data, long length);
// reverse endian to convert from n64 to z64 ordering
void reverse_endian(unsigned char *data, long length);
// get size of file without opening it;
// returns file size or negative on error
long filesize(const char *file_name);
// update file timestamp to now, creating it if it doesn't exist
void touch_file(const char *filename);
// read entire contents of file into buffer
// returns file size or negative on error
long read_file(const char *file_name, unsigned char **data);
// write buffer to file
// returns number of bytes written out or -1 on failure
long write_file(const char *file_name, unsigned char *data, long length);
// generate an output file name from input name by replacing file extension
// in_name: input file name
// out_name: buffer to write output name in
// extension: new file extension to use
void generate_filename(const char *in_name, char *out_name, char *extension);
// extract base filename from file path
// name: path to file
// returns just the file name after the last '/'
char *basename(const char *name);
// make a directory if it doesn't exist
// dir_name: name of the directory
void make_dir(const char *dir_name);
// copy a file from src_name to dst_name. will not make directories
// src_name: source file name
// dst_name: destination file name
long copy_file(const char *src_name, const char *dst_name);
// list a directory, optionally filtering files by extension
// dir: directory to list files in
// extension: extension to filter files by (NULL if no filtering)
// list: output list and count
void dir_list_ext(const char *dir, const char *extension, dir_list *list);
// free associated date from a directory list
// list: directory list filled in by dir_list_ext() call
void dir_list_free(dir_list *list);
// determine if a string ends with another string
// str: string to check if ends with 'suffix'
// suffix: string to see if 'str' ends with
// returns 1 if 'str' ends with 'suffix'
int str_ends_with(const char *str, const char *suffix);
#endif // UTILS_H_

View File

@ -1,126 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* from elf.h */
/* Type for a 16-bit quantity. */
typedef uint16_t Elf32_Half;
/* Types for signed and unsigned 32-bit quantities. */
typedef uint32_t Elf32_Word;
/* Type of addresses. */
typedef uint32_t Elf32_Addr;
/* Type of file offsets. */
typedef uint32_t Elf32_Off;
/* The ELF file header. This appears at the start of every ELF file. */
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
/* Conglomeration of the identification bytes, for easy testing as a word. */
#define ELFMAG "\177ELF"
#define SELFMAG 4
#define EI_CLASS 4 /* File class byte index */
#define ELFCLASS32 1 /* 32-bit objects */
#define EI_DATA 5 /* Data encoding byte index */
#define ELFDATA2MSB 2 /* 2's complement, big endian */
/* end from elf.h */
// This file will find all mips3 object files in an ar archive and set the ABI flags to O32
// this allows gcc to link them with the mips2 object files.
// Irix CC doesn't set the elf e_flags properly.
// the AR file is structured as followed
//"!<arch>" followed by 0x0A (linefeed) 8 characters
// then a file header that follows the following structure
// everything is represented using space padded characters
// the last two characters are alwos 0x60 0x0A
// then come the file contents
// you can find the location of the next header by adding file_size_in_bytes (after parsing)
// all file headers start at an even offset so if the file size in bytes is odd you have to add 1
// the first two "files" are special. One is a symbol table with a pointer to the header of the file
// contaning the symbol the other is an extended list of filenames
struct ar_header {
char identifier[16];
char file_modification_timestamp[12];
char owner_id[6];
char group_id[6];
char file_mode[8];
char file_size_in_bytes[10];
char ending[2];
};
//These constants found by inspecting output of objdump
#define FLAGS_MIPS3 0x20
#define FLAGS_O32ABI 0x100000
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "r+");
if (f == NULL) {
printf("Failed to open file! %s\n", argv[1]);
return -1;
}
struct ar_header current_header;
fseek(f, 0x8, SEEK_SET); // skip header, this is safe enough given that we check to make sure the
// file header is valid
while (1 == fread(&current_header, sizeof(current_header), 1, f)) {
if (current_header.ending[0] != 0x60 && current_header.ending[1] != 0x0A) {
printf("Expected file header\n");
return -1;
}
size_t filesize = atoi(current_header.file_size_in_bytes);
Elf32_Ehdr hdr;
if (filesize < sizeof(hdr) || (1 != fread(&hdr, sizeof(hdr), 1, f))) {
printf("Failed to read ELF header\n");
return -1;
}
if (strncmp((const char *) hdr.e_ident, ELFMAG, SELFMAG) == 0) {
// found an ELF file.
if (hdr.e_ident[EI_CLASS] != ELFCLASS32 || hdr.e_ident[EI_DATA] != ELFDATA2MSB) {
printf("Expected 32bit big endian object files\n");
return -1;
}
if ((hdr.e_flags & 0xFF) == FLAGS_MIPS3 && (hdr.e_flags & FLAGS_O32ABI) == 0) {
hdr.e_flags |= FLAGS_O32ABI;
fseek(f, -sizeof(hdr), SEEK_CUR);
if (1 != fwrite(&hdr, sizeof(hdr), 1, f)) {
printf("Failed to write back ELF header after patching.\n");
return -1;
}
}
}
if (filesize % 2 == 1)
filesize++;
fseek(f, filesize - sizeof(hdr), SEEK_CUR);
}
fclose(f);
return 0;
}

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python
import argparse
import re
import sys
def read_file(filepath):
with open(filepath) as f:
lines = f.readlines()
split_lines = [re.split(r'[ ,]+', l.strip().replace('$', '')) for l in lines]
return split_lines
# jumps and branches with named targets
jumps = ['jal', 'j']
branches = ['beq', 'bgez', 'bgtz', 'blez', 'bltz', 'bne']
jump_branches = jumps + branches
# jumps and branches with delay slots
has_delay_slot = jump_branches + ['jr']
def decode_references(instructions):
refs = []
for ins in instructions:
if ins[3] in jump_branches:
target = int(ins[-1], 0)
if target not in refs:
refs.append(target)
return refs
def reassemble(args, instructions, refs):
print('.rsp')
print('\n.create DATA_FILE, 0x%04X' % 0x0000)
print('\n.close // DATA_FILE\n')
print('.create CODE_FILE, 0x%08X\n' % args.base)
delay_slot = False
for ins in instructions:
addr = int(ins[0], 0)
if (addr & 0xFFFF) in refs:
print('%s_%08x:' % (args.name, addr))
sys.stdout.write(' ' * args.indent)
if delay_slot:
sys.stdout.write(' ')
delay_slot = False
if ins[3] in jumps:
target = int(ins[-1], 0) | (args.base & 0xFFFF0000)
ins[-1] = '%s_%08x' % (args.name, target)
elif ins[3] in branches:
if ins[3][-1] =='z' and ins[5] == 'zero':
del ins[5] # remove 'zero' operand from branch
target = (int(ins[-1], 0) & 0x1FFF) + (args.base & 0xFFFF0000)
ins[-1] = '%s_%08x' % (args.name, target)
elif ins[3] == 'vsar': # fixup last operand of vsar
reg_map = {'ACC_H': 0, 'ACC_M': 1, 'ACC_L': 2}
reg = ins[4].split(r'[')[0]
num = reg_map[ins[-1]]
ins[-1] = '%s[%d]' % (reg, num)
if ins[3] in has_delay_slot:
delay_slot = True
if len(ins) > 4: # with args
print('%-5s %s' % (ins[3], ', '.join(ins[4:])))
else:
print('%s' % ins[3])
print('\n.close // CODE_FILE')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('input_file', help="input assembly file generated from `rasm2 -D -e -a rsp -B -o 0x04001000 -f`")
parser.add_argument('-b', type=int, help="base address of file", dest='base', default=0x04001000)
parser.add_argument('-i', type=int, help="amount of indentation", dest='indent', default=4)
parser.add_argument('-n', help="name to prefex labels with", dest='name', default='f3d')
args = parser.parse_args()
lines = read_file(args.input_file)
refs = decode_references(lines)
reassemble(args, lines, refs)
main()

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,6 @@
*.aiff
*.aifc
*.table
/vadpcm_dec_irix
/vadpcm_enc_irix
/vadpcm_dec_native
/vadpcm_enc_native
/vadpcm_dec_orig

View File

@ -1,34 +1,13 @@
# Makefile for building vadpcm_enc and vadpcm_dec for either IRIX or natively.
# For an IRIX build, the env variable IRIX_ROOT should point to the root of an
# IRIX filesystem, and QEMU_IRIX should point to the qemu-irix binary.
IRIX_CC := $(QEMU_IRIX) -silent -L $(IRIX_ROOT) $(IRIX_ROOT)/usr/bin/cc
IRIX_CFLAGS := -fullwarn -woff 515,608,658,799 -Wab,-r4300_mul -g -Xcpluscomm -mips1 -o32
NATIVE_CC := gcc
NATIVE_CFLAGS := -g -Wall -O2 -Wno-unused-result -Wno-uninitialized
default: native
all: irix native
all: native
irix: vadpcm_dec_irix vadpcm_enc_irix
native: vadpcm_dec_native vadpcm_enc_native
clean:
$(RM) *.o vadpcm_dec_irix vadpcm_enc_irix vadpcm_dec_native vadpcm_enc_native
%.o: %.c
$(IRIX_CC) -c $(IRIX_CFLAGS) $< -o $@
vadpcm_dec_irix: SHELL := /usr/bin/env bash
vadpcm_dec_irix: vadpcm_dec.o vpredictor.o sampleio.o vdecode.o util.o
$(IRIX_CC) $^ -o $@ -lm
@dd status=none iflag=skip_bytes,count_bytes skip=$$((0x120)) count=$$((0x5000 - 0x120)) if=$@ | sha256sum | diff -q - <(echo 'ffaf4d0e4a5d13279d8de8e1eb4ba0f6350e7e3940ad1339f665036bf74f81c1 -') >/dev/null && echo $@: OK || echo $@: FAILED
vadpcm_enc_irix: SHELL := /usr/bin/env bash
vadpcm_enc_irix: vadpcm_enc.o vpredictor.o quant.o util.o vencode.o
$(IRIX_CC) $^ -o $@ -lm
@dd status=none iflag=skip_bytes,count_bytes skip=$$((0x120)) count=$$((0x6000 - 0x120)) if=$@ | sha256sum | diff -q - <(echo '803be21f985c520eafdde0ff2d0ad2d6dd0db364f146c6d5f5763251f4c1796b -') >/dev/null && echo $@: OK || echo $@: FAILED
$(RM) *.o vadpcm_dec_native vadpcm_enc_native
vadpcm_dec_native: vadpcm_dec.c vpredictor.c sampleio.c vdecode.c util.c
$(NATIVE_CC) $(NATIVE_CFLAGS) $^ -o $@ -lm
@ -36,4 +15,4 @@ vadpcm_dec_native: vadpcm_dec.c vpredictor.c sampleio.c vdecode.c util.c
vadpcm_enc_native: vadpcm_enc.c vpredictor.c quant.c util.c vencode.c
$(NATIVE_CC) $(NATIVE_CFLAGS) $^ -o $@ -lm
.PHONY: default all irix native clean
.PHONY: default all native clean

View File

@ -14,7 +14,7 @@ typedef unsigned long long u64;
typedef float f32;
typedef double f64;
#if defined(__sgi) || (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
# define BSWAP16(x)
# define BSWAP32(x)
# define BSWAP16_MANY(x, n)
@ -24,13 +24,8 @@ typedef double f64;
# define BSWAP16_MANY(x, n) { s32 _i; for (_i = 0; _i < n; _i++) BSWAP16((x)[_i]) }
#endif
#ifdef __sgi
# define MODE_READ "r"
# define MODE_WRITE "w"
#else
# define MODE_READ "rb"
# define MODE_WRITE "wb"
#endif
typedef struct {
u32 ckID;

View File

@ -19,18 +19,6 @@ static void int_handler(s32 sig)
static char usage[] = "bitfile";
#ifdef __sgi
// Declaring a sigaction like this is wildly unportable; you're supposed to
// assign members one by one in code. We do that in the non-SGI case.
static struct sigaction int_act = {
/* sa_flags = */ SA_RESTART,
/* sa_handler = */ &int_handler,
/* sa_mask = */ 0,
};
#endif
s32 main(s32 argc, char **argv)
{
s32 c;
@ -66,9 +54,7 @@ s32 main(s32 argc, char **argv)
FILE *ifile;
char *progname = argv[0];
#ifndef __sgi
nloops = 0;
#endif
if (argc < 2)
{
@ -163,9 +149,6 @@ s32 main(s32 argc, char **argv)
BSWAP32(SndDChunk.blockSize)
// The assert error messages specify line numbers 165/166. Match
// that using a #line directive.
#ifdef __sgi
# line 164
#endif
assert(SndDChunk.offset == 0);
assert(SndDChunk.blockSize == 0);
soundPointer = ftell(ifile);
@ -226,12 +209,10 @@ s32 main(s32 argc, char **argv)
fseek(ifile, soundPointer, SEEK_SET);
if (doloop && nloops > 0)
{
#ifndef __sgi
struct sigaction int_act;
int_act.sa_flags = SA_RESTART;
int_act.sa_handler = int_handler;
sigemptyset(&int_act.sa_mask);
#endif
sigaction(SIGINT, &int_act, NULL);
flags = fcntl(STDIN_FILENO, F_GETFL, 0);

View File

@ -124,11 +124,9 @@ int main(int argc, char **argv)
state[i] = 0;
}
#ifndef __sgi
// If there is no instrument chunk, make sure to output zeroes instead of
// garbage. (This matches how the IRIX -g-compiled version behaves.)
memset(&InstChunk, 0, sizeof(InstChunk));
#endif
inBuffer = malloc(16 * sizeof(s16));
@ -203,9 +201,6 @@ int main(int argc, char **argv)
BSWAP32(SndDChunk.blockSize)
// The assert error messages specify line numbers 219/220. Match
// that using a #line directive.
#ifdef __sgi
# line 218
#endif
assert(SndDChunk.offset == 0);
assert(SndDChunk.blockSize == 0);
soundPointer = ftell(ifile);

View File

@ -4,6 +4,5 @@
*.aiff
*.aifc
*.table
/tabledesign_irix
/tabledesign_native
/tabledesign_orig

View File

@ -1,31 +1,9 @@
# Makefile for building tabledesign for either IRIX or natively.
# For an IRIX build, the env variable IRIX_ROOT should point to the root of an
# IRIX filesystem, and QEMU_IRIX should point to the qemu-irix binary.
IRIX_CC := $(QEMU_IRIX) -silent -L $(IRIX_ROOT) $(IRIX_ROOT)/usr/bin/cc
IRIX_CFLAGS := -fullwarn -Wab,-r4300_mul -Xcpluscomm -mips1 -O2
NATIVE_CC := gcc
NATIVE_CFLAGS := -Wall -Wno-uninitialized -O2
LDFLAGS := -lm -laudiofile
default: native
all: irix native
all: native
irix: tabledesign_irix
native: tabledesign_native
clean:
$(RM) *.o tabledesign_irix tabledesign_native
%.o: %.c
$(IRIX_CC) -c $(IRIX_CFLAGS) $< -o $@
tabledesign_irix: tabledesign.o codebook.o estimate.o print.o
$(IRIX_CC) $^ -o $@ $(LDFLAGS)
tabledesign_native: tabledesign.c codebook.c estimate.c print.c
$(NATIVE_CC) $(NATIVE_CFLAGS) $^ -o $@ $(LDFLAGS)
.PHONY: default all irix native clean
.PHONY: default all native clean

View File

@ -5,14 +5,6 @@
#include <audiofile.h>
#include "tabledesign.h"
#ifdef __sgi
typedef long SampleFormat;
#define MODE_READ "r"
#else
// The modern implementation of SGI's audiofile library which is in Ubuntu
// (https://github.com/mpruett/audiofile/) has renamed some of the functions,
// and changed some data types.
@ -28,8 +20,6 @@ typedef int SampleFormat;
#define MODE_READ "rb"
#endif
char usage[80] = "[-o order -s bits -t thresh -i refine_iter -f frame_size] aifcfile";
int main(int argc, char **argv)

View File

@ -1,680 +0,0 @@
/* skybox generator */
#define _GNU_SOURCE
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include "n64graphics.h"
#include "utils.h"
typedef struct {
rgba *px;
bool useless;
unsigned int pos;
} TextureTile;
typedef enum {
InvalidType = -1,
Skybox,
Cake,
CakeEU,
ImageType_MAX
} ImageType;
typedef enum {
InvalidMode = -1,
Combine,
Split
} OperationMode;
typedef struct {
int imageWidth, imageHeight;
int tileWidth, tileHeight;
int numCols, numRows;
bool wrapX;
bool optimizePositions;
} ImageProps;
static const ImageProps IMAGE_PROPERTIES[ImageType_MAX][2] = {
[Skybox] = {
{248, 248, 31, 31, 8, 8, true, true},
{256, 256, 32, 32, 8, 8, true, true},
},
[Cake] = {
{316, 228, 79, 19, 4, 12, false, false},
{320, 240, 80, 20, 4, 12, false, false},
},
[CakeEU] = {
{320, 224, 64, 32, 5, 7, false, false},
{320, 224, 64, 32, 5, 7, false, false},
},
};
typedef struct {
int cols, rows;
} TableDimension;
static const TableDimension TABLE_DIMENSIONS[ImageType_MAX] = {
[Skybox] = {8, 10},
[Cake] = {4, 12},
[CakeEU] = {5, 7},
};
TextureTile *tiles;
ImageType type = InvalidType;
OperationMode mode = InvalidMode;
char *programName;
char *input, *output;
char *writeDir;
char skyboxName[256];
bool expanded = false;
bool writeTiles;
bool storeNamesOnly = false;
static void allocate_tiles() {
const ImageProps props = IMAGE_PROPERTIES[type][true];
int tileWidth = props.tileWidth;
int tileHeight = props.tileHeight;
int numRows = props.numRows;
int numCols = props.numCols;
int tileSize = tileWidth * tileHeight * sizeof(rgba);
int totalSize = numRows * numCols * tileSize;
tiles = calloc(1, numRows * numCols * sizeof(TextureTile));
rgba *tileData = calloc(1, totalSize);
for (int row = 0; row < numRows; ++row) {
for (int col = 0; col < numCols; ++col) {
tiles[row * numCols + col].px = (tileData + (row * numCols + col) * (tileWidth * tileHeight));
}
}
}
static void free_tiles() {
free(tiles->px);
free(tiles);
}
static void split_tile(int col, int row, rgba *image, bool expanded) {
const ImageProps props = IMAGE_PROPERTIES[type][expanded];
int tileWidth = props.tileWidth;
int tileHeight = props.tileHeight;
int imageWidth = props.imageWidth;
int numCols = props.numCols;
int expandedWidth = IMAGE_PROPERTIES[type][true].tileWidth;
for (int y = 0; y < tileHeight; y++) {
for (int x = 0; x < tileWidth; x++) {
int ny = row * tileHeight + y;
int nx = col * tileWidth + x;
tiles[row * numCols + col].px[y * expandedWidth + x] = image[(ny * imageWidth + nx)];
}
}
}
static void expand_tiles(ImageType imageType) {
const ImageProps props = IMAGE_PROPERTIES[imageType][true];
int numRows = props.numRows;
int numCols = props.numCols;
int tileWidth = props.tileWidth;
int tileHeight = props.tileHeight;
// If the image type wraps,
// Copy each tile's left edge to the previous tile's right edge
// Each tile's height is still tileHeight - 1
if (props.wrapX) {
for (int row = 0; row < numRows; ++row) {
for (int col = 0; col < numCols; ++col) {
int nextCol = (col + 1) % numCols;
for (int y = 0; y < (tileHeight - 1); ++y) {
tiles[row * numCols + col].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + nextCol].px[y * tileWidth];
}
}
}
} else {
// Don't wrap, copy the second to last column instead.
for (int row = 0; row < numRows; ++row) {
for (int col = 0; col < numCols - 1; ++col) {
int nextCol = (col + 1) % numCols;
for (int y = 0; y < (tileHeight - 1); ++y) {
tiles[row * numCols + col].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + nextCol].px[y * tileWidth];
}
}
for (int y = 0; y < (tileHeight - 1); ++y) {
tiles[row * numCols + (numCols - 1)].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + (numCols - 1)].px[(tileWidth - 2) + y * tileWidth];
}
}
}
// Copy each tile's top edge to the previous tile's bottom edge, EXCEPT for the bottom row, which
// just duplicates its second-to-last row
for (int row = 0; row < numRows; ++row) {
if (row < numRows - 1) {
for (int col = 0; col < numCols; ++col) {
int nextRow = (row + 1) % numRows;
for (int x = 0; x < tileWidth; ++x) {
tiles[row * numCols + col].px[x + (tileHeight - 1) * tileWidth] = tiles[nextRow * numCols + col].px[x];
}
}
}
// For the last row of tiles, duplicate each one's second to last row
else {
for (int col = 0; col < numCols; ++col) {
for (int x = 0; x < tileWidth; ++x) {
tiles[row * numCols + col].px[x + (tileHeight - 1) * tileWidth] = tiles[row * numCols + col].px[x + (tileHeight - 2) * tileWidth];
}
}
}
}
}
static void init_tiles(rgba *image, bool expanded) {
const ImageProps props = IMAGE_PROPERTIES[type][expanded];
for (int row = 0; row < props.numRows; row++) {
for (int col = 0; col < props.numCols; col++) {
split_tile(col, row, image, expanded);
}
}
// Expand the tiles to their full size
if (!expanded) {
expand_tiles(type);
}
}
static void assign_tile_positions() {
const ImageProps props = IMAGE_PROPERTIES[type][true];
const size_t TILE_SIZE = props.tileWidth * props.tileHeight * sizeof(rgba);
unsigned int newPos = 0;
for (int i = 0; i < props.numRows * props.numCols; i++) {
if (props.optimizePositions) {
for (int j = 0; j < i; j++) {
if (!tiles[j].useless && memcmp(tiles[j].px, tiles[i].px, TILE_SIZE) == 0) {
tiles[i].useless = 1;
tiles[i].pos = j;
break;
}
}
}
if (!tiles[i].useless) {
tiles[i].pos = newPos;
newPos++;
}
}
}
// Provide a replacement for realpath on Windows
#ifdef _WIN32
#define realpath(path, resolved_path) _fullpath(resolved_path, path, PATH_MAX)
#endif
/* write pngs to disc */
void write_tiles() {
const ImageProps props = IMAGE_PROPERTIES[type][true];
char buffer[PATH_MAX];
if (realpath(writeDir, buffer) == NULL) {
fprintf(stderr, "err: Could not find find img dir %s", writeDir);
exit(EXIT_FAILURE);
}
strcat(buffer, "/");
switch(type) {
case Skybox:
strcat(buffer, skyboxName);
break;
case Cake:
strcat(buffer, "cake");
break;
case CakeEU:
strcat(buffer, "cake_eu");
break;
default:
exit(EXIT_FAILURE);
break;
}
int dirLength = strlen(buffer);
char *filename = buffer + dirLength;
for (int i = 0; i < props.numRows * props.numCols; i++) {
if (!tiles[i].useless) {
*filename = 0;
snprintf(filename, PATH_MAX, ".%d.rgba16.png", tiles[i].pos);
rgba2png(buffer, tiles[i].px, props.tileWidth, props.tileHeight);
}
}
}
static unsigned int get_index(TextureTile *t, unsigned int i) {
if (t[i].useless) {
i = t[i].pos;
}
return t[i].pos;
}
static void print_raw_data(FILE *cFile, TextureTile *tile) {
ImageProps props = IMAGE_PROPERTIES[type][true];
uint8_t *raw = malloc(props.tileWidth * props.tileHeight * 2);
int size = rgba2raw(raw, tile->px, props.tileWidth, props.tileHeight, 16);
for (int i = 0; i < size; ++i) {
fprintf(cFile, "0x%hhX,", raw[i]);
}
free(raw);
}
static void write_skybox_c() { /* write c data to disc */
const ImageProps props = IMAGE_PROPERTIES[type][true];
char fBuffer[PATH_MAX] = "";
FILE *cFile;
if (realpath(output, fBuffer) == NULL) {
fprintf(stderr, "err: Could not find find src dir %s", output);
exit(EXIT_FAILURE);
}
sprintf(fBuffer, "%s/%s_skybox.c", output, skyboxName);
cFile = fopen(fBuffer, "w"); /* reset file */
/* setup C file */
if (cFile == NULL) {
fprintf(stderr, "err: Could not open %s\n", fBuffer);
}
fprintf(cFile, "#include \"sm64.h\"\n\n#include \"make_const_nonconst.h\"\n\n");
for (int i = 0; i < props.numRows * props.numCols; i++) {
if (!tiles[i].useless) {
if (storeNamesOnly) {
fprintf(
cFile,
"ALIGNED8 static const u8 %s_skybox_texture_%05X[] = "
"\"textures/skybox_tiles/%s.%d.rgba16\";\n\n",
skyboxName, tiles[i].pos, skyboxName, tiles[i].pos
);
} else {
fprintf(cFile, "ALIGNED8 static const u8 %s_skybox_texture_%05X[] = {\n", skyboxName, tiles[i].pos);
print_raw_data(cFile, &tiles[i]);
fputs("};\n\n", cFile);
}
}
}
fprintf(cFile, "const u8 *const %s_skybox_ptrlist[] = {\n", skyboxName);
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 10; col++) {
fprintf(cFile, "%s_skybox_texture_%05X,\n", skyboxName, get_index(tiles, row * 8 + (col % 8)));
}
}
fputs("};\n\n", cFile);
fclose(cFile);
}
static void write_cake_c() {
char buffer[PATH_MAX] = "";
if (realpath(output, buffer) == NULL) {
fprintf(stderr, "err: Could not find find src dir %s", output);
exit(EXIT_FAILURE);
}
if (type == CakeEU) {
strcat(buffer, "/cake_eu.inc.c");
}
else {
strcat(buffer, "/cake.inc.c");
}
FILE *cFile = fopen(buffer, "w");
const char *euSuffx = "";
if (type == CakeEU) {
euSuffx = "eu_";
}
int numTiles = TABLE_DIMENSIONS[type].cols * TABLE_DIMENSIONS[type].rows;
for (int i = 0; i < numTiles; ++i) {
if (storeNamesOnly) {
fprintf(
cFile,
"ALIGNED8 static const u8 cake_end_texture_%s%d[] = "
"\"textures/skybox_tiles/cake%s.%d.rgba16\";\n\n",
euSuffx, i, *euSuffx ? "_eu" : "", tiles[i].pos
);
} else {
fprintf(cFile, "ALIGNED8 static const u8 cake_end_texture_%s%d[] = {\n", euSuffx, i);
print_raw_data(cFile, &tiles[i]);
fputs("};\n\n", cFile);
}
}
fclose(cFile);
}
// input: the skybox tiles + the table = up to 64 32x32 images (rgba16) + 80 pointers (u32)
// some pointers point to duplicate entries
void combine_skybox(const char *input, const char *output) {
enum { W = 10, H = 8, W2 = 8 };
FILE *file = fopen(input, "rb");
if (!file) goto fail;
if (fseek(file, 0, SEEK_END)) goto fail;
ssize_t fileSize = ftell(file);
if (fileSize < 8*10*4) goto fail;
rewind(file);
size_t tableIndex = fileSize - 8*10*4;
if (tableIndex % (32*32*2) != 0) goto fail;
// there are at most 64 tiles before the table
rgba *tiles[8*8];
size_t tileIndex = 0;
for (size_t pos = 0; pos < tableIndex; pos += 32*32*2) {
uint8_t buf[32*32*2];
if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
tiles[tileIndex] = raw2rgba(buf, 32, 32, 16);
tileIndex++;
}
uint32_t table[W*H];
if (fread(table, sizeof(table), 1, file) != 1) goto fail;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
reverse_endian((unsigned char *) table, W*H*4);
#endif
uint32_t base = table[0];
for (int i = 0; i < W*H; i++) {
table[i] -= base;
}
// Convert the 256x256 skybox to an editable 248x248 image by skipping the duplicated rows and columns
// every 32nd column is a repeat of the 33rd, and
// every 32nd row is a repeat of the 33rd, EXCEPT for the last row, but that only matters when
// expanding the tiles
rgba combined[31*H * 31*W2];
for (int i = 0; i < H; i++) {
for (int j = 0; j < W2; j++) {
int index = table[i*W+j] / 0x800;
for (int y = 0; y < 31; y++) {
for (int x = 0; x < 31; x++) {
combined[(i*31 + y) * (31*W2) + (j*31 + x)] = tiles[index][y*32 + x];
}
}
}
}
if (!rgba2png(output, combined, 31*W2, 31*H)) {
fprintf(stderr, "Failed to write skybox image.\n");
exit(1);
}
return;
fail:
fprintf(stderr, "Failed to read skybox binary.\n");
exit(1);
}
void combine_cakeimg(const char *input, const char *output, bool eu) {
int W, H, SMALLH, SMALLW;
if (eu) {
W = 5;
H = 7;
SMALLH = 32;
SMALLW = 64;
} else {
W = 4;
H = 12;
SMALLH = 20;
SMALLW = 80;
}
FILE *file = fopen(input, "rb");
if (!file) goto fail;
rgba *combined;
if (!eu) {
combined = malloc((SMALLH-1)*H * (SMALLW-1)*W * sizeof(rgba));
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
//Read the full tile
uint8_t buf[SMALLH * SMALLW * 2];
if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
rgba *tile = raw2rgba(buf, SMALLH, SMALLW, 16);
//Only write the unique parts of each tile
for (int y = 0; y < SMALLH - 1; y++) {
for (int x = 0; x < SMALLW - 1; x++) {
combined[(i*(SMALLH-1) + y) * (SMALLW-1)*W + (j*(SMALLW-1) + x)] = tile[y*(SMALLW) + x];
}
}
}
}
if (!rgba2png(output, combined, (SMALLW-1)*W, (SMALLH-1)*H)) {
fprintf(stderr, "Failed to write cake image.\n");
exit(1);
}
}
else {
combined = malloc(SMALLH*H * SMALLW*W * sizeof(rgba));
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
uint8_t buf[SMALLH * SMALLW * 2];
if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
rgba *tile = raw2rgba(buf, SMALLH, SMALLW, 16);
for (int y = 0; y < SMALLH; y++) {
for (int x = 0; x < SMALLW; x++) {
combined[(i*SMALLH + y) * SMALLW*W + (j*SMALLW + x)] = tile[y*SMALLW + x];
}
}
}
}
if (!rgba2png(output, combined, SMALLW*W, SMALLH*H)) {
fprintf(stderr, "Failed to write cake image.\n");
exit(1);
}
}
return;
fail:
fprintf(stderr, "Failed to read cake binary.\n");
exit(1);
}
// Modified from n64split
static void usage() {
fprintf(stderr,
"Usage: %s --type sky|cake|cake_eu {--combine INPUT OUTPUT | --split INPUT OUTPUT}\n"
"\n"
"Optional arguments:\n"
" --write-tiles OUTDIR Also create the individual tiles' PNG files\n"
" --store-names Store texture file names instead of actual data\n", programName);
}
// Modified from n64split
static int parse_arguments(int argc, char *argv[]) {
programName = argv[0];
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--combine") == 0) {
if (++i >= argc || mode != InvalidMode) {
goto invalid;
}
mode = Combine;
input = argv[i];
if (++i >= argc) {
goto invalid;
}
output = argv[i];
}
if (strcmp(argv[i], "--split") == 0) {
if (++i >= argc || mode != InvalidMode) {
goto invalid;
}
mode = Split;
input = argv[i];
if (++i >= argc) {
goto invalid;
}
output = argv[i];
}
if (strcmp(argv[i], "--type") == 0) {
if (++i >= argc || type != InvalidType) {
goto invalid;
}
if (strcmp(argv[i], "sky") == 0) {
type = Skybox;
} else if(strcmp(argv[i], "cake-eu") == 0) {
type = CakeEU;
} else if(strcmp(argv[i], "cake") == 0) {
type = Cake;
}
}
if (strcmp(argv[i], "--write-tiles") == 0) {
if (++i >= argc || argv[i][0] == '-') {
goto invalid;
}
writeTiles = true;
writeDir = argv[i];
}
if (strcmp(argv[i], "--store-names") == 0) {
storeNamesOnly = true;
}
}
return 1;
invalid:
usage();
return 0;
}
bool imageMatchesDimensions(int width, int height) {
bool matchesDimensions = false;
for (int expand = false; expand <= true; ++expand) {
if (width == IMAGE_PROPERTIES[type][expand].imageWidth &&
height == IMAGE_PROPERTIES[type][expand].imageHeight) {
matchesDimensions = true;
expanded = expand;
break;
}
}
if (!matchesDimensions) {
if (type != CakeEU) {
fprintf(stderr, "err: That type of image must be either %d x %d or %d x %d. Yours is %d x %d.\n",
IMAGE_PROPERTIES[type][false].imageWidth, IMAGE_PROPERTIES[type][false].imageHeight,
IMAGE_PROPERTIES[type][true].imageWidth, IMAGE_PROPERTIES[type][true].imageHeight,
width, height);
}
else {
fprintf(stderr, "err: That type of image must be %d x %d. Yours is %d x %d.\n",
IMAGE_PROPERTIES[type][true].imageWidth, IMAGE_PROPERTIES[type][true].imageHeight,
width, height);
}
return false;
}
if (type == CakeEU) {
expanded = true;
}
return true;
}
int main(int argc, char *argv[]) {
if (parse_arguments(argc, argv) == false) {
return EXIT_FAILURE;
}
if (type == Skybox && mode == Split) {
// Extract the skybox's name (ie: bbh, bidw) from the input png
char *base = basename(input);
strcpy(skyboxName, base);
char *extension = strrchr(skyboxName, '.');
if (extension) *extension = '\0';
}
switch (mode) {
case Combine:
switch (type) {
case Skybox:
combine_skybox(input, output);
break;
case Cake:
combine_cakeimg(input, output, 0);
break;
case CakeEU:
combine_cakeimg(input, output, 1);
break;
default:
usage();
return EXIT_FAILURE;
break;
}
break;
case Split: {
int width, height;
rgba *image = png2rgba(input, &width, &height);
if (image == NULL) {
fprintf(stderr, "err: Could not load image %s\n", argv[1]);
return EXIT_FAILURE;
}
if (!imageMatchesDimensions(width, height)) {
return EXIT_FAILURE;
}
allocate_tiles();
init_tiles(image, expanded);
switch (type) {
case Skybox:
assign_tile_positions();
write_skybox_c();
break;
case Cake:
case CakeEU:
assign_tile_positions();
write_cake_c();
break;
default:
fprintf(stderr, "err: Unknown image type.\n");
return EXIT_FAILURE;
break;
}
if (writeTiles) {
write_tiles();
}
free_tiles();
free(image);
} break;
default:
usage();
return EXIT_FAILURE;
break;
}
return EXIT_SUCCESS;
}