mirror of https://github.com/sm64pc/sm64pc.git
Removed boomer code & skybox tiles
This commit is contained in:
parent
17c0f0bae3
commit
89d89b1121
|
@ -48,7 +48,6 @@ pipeline {
|
|||
}
|
||||
}
|
||||
environment {
|
||||
QEMU_IRIX = credentials('qemu-irix')
|
||||
ROMS_DIR = credentials('roms')
|
||||
}
|
||||
}
|
||||
|
|
6
Makefile
6
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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,12 +202,20 @@ void draw_skybox_tile_grid(Gfx **dlist, s8 background, s8 player, s8 colorIndex)
|
|||
s32 row;
|
||||
s32 col;
|
||||
|
||||
const u8 *const texture = sSkyboxTextures[background];
|
||||
Vtx *vertices = make_skybox_rect(0, colorIndex);
|
||||
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];
|
||||
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);
|
||||
gSPVertex((*dlist)++, VIRTUAL_TO_PHYSICAL(vertices), 4, 0);
|
||||
gSPDisplayList((*dlist)++, dl_draw_quad_verts_0123);
|
||||
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) {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
// 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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
|
@ -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])
|
|
@ -1,5 +0,0 @@
|
|||
.set noat
|
||||
.set noreorder
|
||||
.set gp=64
|
||||
.include "macros.inc"
|
||||
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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;
|
||||
|
|
142
tools/n64cksum.c
142
tools/n64cksum.c
|
@ -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
|
|
@ -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_
|
1021
tools/n64graphics.c
1021
tools/n64graphics.c
File diff suppressed because it is too large
Load Diff
|
@ -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_
|
|
@ -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.
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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));
|
||||
}
|
|
@ -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_
|
|
@ -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(¤t_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;
|
||||
}
|
|
@ -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
|
@ -4,8 +4,6 @@
|
|||
*.aiff
|
||||
*.aifc
|
||||
*.table
|
||||
/vadpcm_dec_irix
|
||||
/vadpcm_enc_irix
|
||||
/vadpcm_dec_native
|
||||
/vadpcm_enc_native
|
||||
/vadpcm_dec_orig
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -4,6 +4,5 @@
|
|||
*.aiff
|
||||
*.aifc
|
||||
*.table
|
||||
/tabledesign_irix
|
||||
/tabledesign_native
|
||||
/tabledesign_orig
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
680
tools/skyconv.c
680
tools/skyconv.c
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue