tex archives (#1072)

* extraction

* hmm

* eth cleanup

* tex skeleton

* building

* OK

* pal OK

* cleaned texture names

* moved tex code to separate file

* additional error checking

* proposed splits

* split a5dd0

* additional cleanup

* myoop

* wahoo

* lru

* fixules

* fixin agin

---------

Co-authored-by: HailSanta <Hail2Santa@gmail.com>
Co-authored-by: Ethan Roseman <ethteck@gmail.com>
This commit is contained in:
HailSanta 2023-06-29 08:06:23 -04:00 committed by GitHub
parent 8f0f0bd941
commit 6bb14ff8be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2981 additions and 2381 deletions

View File

@ -994,14 +994,13 @@ typedef struct BattleStatus {
/* 0x45C */ char unk_45C[4];
} BattleStatus; // size = 0x460
// alternative name: TileDescriptor
typedef struct TextureHeader {
/* 0x00 */ s8 name[32];
/* 0x20 */ u16 auxW;
/* 0x22 */ u16 mainW;
/* 0x24 */ u16 auxH;
/* 0x26 */ u16 mainH;
/* 0x28 */ u8 unk_28;
/* 0x28 */ u8 isVariant;
/* 0x29 */ u8 extraTiles; // 0 - none, 1 - mipmap, 2 - ?, 3 - use aux tile
/* 0x2A */ u8 colorCombineType : 6;
/* 0x2A */ u8 colorCombineSubType : 2;

View File

@ -113,7 +113,7 @@ void filemenu_choose_name_update(MenuPanel*);
void filemenu_choose_name_cleanup(MenuPanel*);
void filemenu_draw_message(u8*, s32, s32, s32, s32, u32);
void filemenu_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileDescriptor, s32 uls, s32 ult, s32 dsdx, s32 dtdy);
void filemenu_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileIdx, s32 uls, s32 ult, s32 dsdx, s32 dtdy);
extern WindowStyleCustom filemenu_windowStyles[];
extern u8 filemenu_createfile_gridData[];

View File

@ -1097,7 +1097,7 @@ void btl_update(void);
void update_item_entities(void);
void iterate_models(void);
void restore_map_collision_data(void);
void load_model_textures(struct ModelNode* model, s32 romOffset, s32 size);
void mdl_load_all_textures(struct ModelNode* model, s32 romOffset, s32 size);
void calculate_model_sizes(void);
#endif

View File

@ -179,7 +179,7 @@ void update_model_animator(s32);
void update_model_animator_with_transform(s32 animatorID, Mtx* mtx);
void set_mdl_custom_gfx_set(Model*, s32, u32);
ModelNodeProperty* get_model_property(ModelNode* node, ModelPropertyKeys key);
void func_80115498(u32 romOffset, s32 textureID, s32 baseOffset, s32 size);
void load_texture_variants(u32 romOffset, s32 textureID, s32 baseOffset, s32 size);
s32 step_model_animator(ModelAnimator* animator);
AnimatorNode* get_animator_node_for_tree_index(ModelAnimator* animator, s32 treeIndex);
AnimatorNode* get_animator_node_with_id(ModelAnimator* animator, s32 id);

View File

@ -146,7 +146,7 @@ extern u16 gCurrentDoorSounds;
extern UNK_TYPE D_800E92D8;
extern UNK_TYPE D_80147574;
extern s8 D_8014C248[];
extern b8 D_8014C248;
extern UNK_FUN_PTR(TalkNotificationCallback);
extern UNK_FUN_PTR(InteractNotificationCallback);

View File

@ -193,8 +193,8 @@ void btl_state_update_normal_start(void) {
s32 size;
UiStatus* uiStatus;
void* compressedAsset;
ModelNode* model;
s32 textureRom;
ModelNode* rootModel;
s32 texturesOffset;
Actor* actor;
Evt* script;
s32 enemyNotDone;
@ -226,10 +226,10 @@ void btl_state_update_normal_start(void) {
ASSERT(size <= 0x8000);
model = gMapShapeData.header.root;
textureRom = get_asset_offset(stage->texture, &size);
if (model != NULL) {
load_data_for_models(model, textureRom, size);
rootModel = gMapShapeData.header.root;
texturesOffset = get_asset_offset(stage->texture, &size);
if (rootModel != NULL) {
load_data_for_models(rootModel, texturesOffset, size);
}
load_battle_hit_asset(stage->hit);

1759
src/entity.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -181,14 +181,14 @@ BSS u16 D_802517E0[2][0x400] ALIGNED(16);
BSS u8 filemenu_glyphBuffer[20][0x80];
#endif
void filemenu_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileDescriptor, s32 uls, s32 ult, s32 dsdx, s32 dtdy) {
void filemenu_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileIdx, s32 uls, s32 ult, s32 dsdx, s32 dtdy) {
if (ulx <= -2688 || uly <= -2688 || lrx <= 0 || lry <= 0) {
return;
}
if (ulx >= 1280 || uly >= 960 || lrx >= 2688 || lry >= 2688) {
return;
}
gSPScisTextureRectangle(gMainGfxPos++, ulx, uly, lrx, lry, tileDescriptor, uls, ult, dsdx, dtdy);
gSPScisTextureRectangle(gMainGfxPos++, ulx, uly, lrx, lry, tileIdx, uls, ult, dsdx, dtdy);
}
void filemenu_set_selected(MenuPanel* menu, s32 col, s32 row) {

180
src/game_states.c Normal file
View File

@ -0,0 +1,180 @@
#include "common.h"
extern GameMode gMainGameState[2]; // TODO rename
void state_delegate_NOP(void) {
}
void clear_game_modes(void) {
GameMode* gameMode;
s32 i;
for (gameMode = gMainGameState, i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
gameMode->flags = 0;
}
}
GameMode* set_next_game_mode(GameMode* arg0) {
GameMode* gameMode;
s32 i;
for (gameMode = gMainGameState, i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
if (gameMode->flags == 0) {
break;
}
}
ASSERT(i < ARRAY_COUNT(gMainGameState));
gameMode->flags = 1 | 2;
gameMode->init = arg0->init;
gameMode->step = arg0->step;
gameMode->render = arg0->render;
gameMode->unk_0C = NULL;
if (gameMode->init == NULL) {
gameMode->init = state_delegate_NOP;
}
if (gameMode->step == NULL) {
gameMode->step = state_delegate_NOP;
}
if (gameMode->unk_0C == NULL) {
gameMode->unk_0C = state_delegate_NOP;
}
if (gameMode->render == NULL) {
gameMode->render = state_delegate_NOP;
}
gameMode->renderAux = state_delegate_NOP;
gameMode->init();
return gameMode;
}
GameMode* set_game_mode_slot(s32 i, GameMode* mode) {
GameMode* gameMode = &gMainGameState[i];
ASSERT(i < ARRAY_COUNT(gMainGameState));
gameMode->flags = 2 | 1;
gameMode->init = mode->init;
gameMode->step = mode->step;
gameMode->render = mode->render;
gameMode->unk_0C = NULL;
if (gameMode->init == NULL) gameMode->init = state_delegate_NOP;
if (gameMode->step == NULL) gameMode->step = state_delegate_NOP;
if (gameMode->unk_0C == NULL) gameMode->unk_0C = state_delegate_NOP;
if (gameMode->render == NULL) gameMode->render = state_delegate_NOP;
gameMode->renderAux = state_delegate_NOP;
gameMode->init();
return gameMode;
}
void game_mode_set_fpDrawAuxUI(s32 i, void (*fn)(void)) {
GameMode* gameMode = &gMainGameState[i];
ASSERT(i < ARRAY_COUNT(gMainGameState));
gameMode->renderAux = fn;
gameMode->flags |= 0x20;
if (fn == NULL) {
gameMode->renderAux = state_delegate_NOP;
}
}
void func_80112DD4(s32 i) {
gMainGameState[i].flags |= 4;
}
void func_80112DFC(s32 i) {
gMainGameState[i].flags |= 8;
}
void func_80112E24(s32 i) {
gMainGameState[i].flags &= ~0x1C;
}
void func_80112E4C(s32 i) {
gMainGameState[i].flags &= ~0x0C;
gMainGameState[i].flags |= 0x10;
}
void step_current_game_mode(void) {
GameMode* gameMode = gMainGameState;
s32 i;
for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
if (gameMode->flags != 0) {
if (!(gameMode->flags & 4)) {
if (!(gameMode->flags & 8)) {
gameMode->flags &= ~2;
gameMode->step();
}
}
}
}
}
void state_do_unk(void) {
GameMode* gameMode = gMainGameState;
s32 i;
for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
if (gameMode->flags != 0) {
if (!(gameMode->flags & 4)) {
if (!(gameMode->flags & 0x10)) {
gameMode->unk_0C();
}
}
}
}
}
void state_render_backUI(void) {
GameMode* gameMode = gMainGameState;
s32 i;
for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
if (gameMode->flags != 0) {
if (!(gameMode->flags & 4)) {
if (!(gameMode->flags & 0x10)) {
gameMode->render();
}
}
}
}
}
void state_render_frontUI(void) {
GameMode* gameMode = gMainGameState;
s32 i;
for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
if (gameMode->flags != 0) {
if (!(gameMode->flags & 4)) {
if (!(gameMode->flags & 2)) {
if (gameMode->flags & 0x20) {
gameMode->renderAux();
}
}
}
}
}
// re-initialization needed - evidence of inlining? or just copy/pasting?
gameMode = &gMainGameState[0];
for (i = 0; i < ARRAY_COUNT(gMainGameState); i++, gameMode++) {
if (gameMode->flags != 0) {
if (!(gameMode->flags & 4)) {
if (!(gameMode->flags & 2)) {
if (gameMode->flags & 0x10) {
gameMode->render();
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -126,7 +126,7 @@ void pause_set_cursor_pos_immediate(s32 windowID, s32 posX, s32 posY);
void pause_set_cursor_opacity(s32 val);
void pause_draw_menu_label(s32 index, s32 x, s32 y);
s32 pause_get_total_equipped_bp_cost(void);
void pause_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileDescriptor, s32 uls, s32 ult, s32 dsdx, s32 dtdy);
void pause_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileIdx, s32 uls, s32 ult, s32 dsdx, s32 dtdy);
s32 pause_get_menu_msg(s32 index);
s32 pause_interp_vertical_scroll(s32 deltaBefore);
void pause_sort_item_list(s16* arr, s32 len, s32 (*compare)(s16*, s16 *));

View File

@ -845,14 +845,14 @@ s32 pause_get_total_equipped_bp_cost(void) {
return totalCost;
}
void pause_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileDescriptor, s32 uls, s32 ult, s32 dsdx, s32 dtdy) {
void pause_draw_rect(s32 ulx, s32 uly, s32 lrx, s32 lry, s32 tileIdx, s32 uls, s32 ult, s32 dsdx, s32 dtdy) {
if (ulx <= -2688 || uly <= -2688 || lrx <= 0 || lry <= 0) {
return;
}
if (ulx >= 1280 || uly >= 960 || lrx >= 2688 || lry >= 2688) {
return;
}
gSPScisTextureRectangle(gMainGfxPos++, ulx, uly, lrx, lry, tileDescriptor, uls, ult, dsdx, dtdy);
gSPScisTextureRectangle(gMainGfxPos++, ulx, uly, lrx, lry, tileIdx, uls, ult, dsdx, dtdy);
}
void pause_sort_item_list(s16* arr, s32 len, s32 (*compare)(s16*, s16 *)) {

View File

@ -195,7 +195,7 @@ void state_step_end_battle(void) {
set_background_size(296, 200, 12, 20);
}
load_model_textures(mapSettings->modelTreeRoot, get_asset_offset(wMapTexName, &sizeTemp), sizeTemp);
mdl_load_all_textures(mapSettings->modelTreeRoot, get_asset_offset(wMapTexName, &sizeTemp), sizeTemp);
calculate_model_sizes();
npc_reload_all();

View File

@ -57,7 +57,7 @@ void state_init_file_select(void) {
general_heap_create();
hud_element_set_aux_cache(0, 0);
hud_element_clear_cache();
load_model_textures(0, 0, 0);
mdl_load_all_textures(NULL, 0, 0);
gCameras[CAM_DEFAULT].updateMode = CAM_UPDATE_MODE_6;
gCameras[CAM_DEFAULT].needsInit = TRUE;
gCameras[CAM_DEFAULT].nearClip = 16;

View File

@ -100,7 +100,7 @@ void state_init_title_screen(void) {
gOverrideFlags = 0;
timeFreezeMode = 0;
D_8014C248[0] = 1;
D_8014C248 = TRUE;
general_heap_create();
clear_printers();
sfx_set_reverb_mode(0);

View File

@ -1910,11 +1910,6 @@ EvtScript N(EVS_Intro_Main) = {
f32 N(AnimBowser_FlyOff_Time) = 0.0;
#if VERSION_PAL
API_CALLABLE(N(AnimBowser_FlyOff));
INCLUDE_ASM(ApiResult, "world/area_hos/hos_05/hos_05_5_intro", AnimBowser_FlyOff);
asm(".section .data");
#else
API_CALLABLE(N(AnimBowser_FlyOff)) {
Npc* bowserMain = resolve_npc(script, NPC_Bowser_Body);
Npc* bowserProp = resolve_npc(script, NPC_Bowser_Prop);
@ -1931,20 +1926,15 @@ API_CALLABLE(N(AnimBowser_FlyOff)) {
bowserMain->colliderPos.y = bowserMain->pos.y;
bowserProp->colliderPos.y = bowserProp->pos.y;
N(AnimBowser_FlyOff_Time)++;
if (N(AnimBowser_FlyOff_Time) > 40.0f) {
if (N(AnimBowser_FlyOff_Time) > (int)(40 * DT)) {
return ApiStatus_DONE1;
} else {
return ApiStatus_BLOCK;
}
}
#endif
f32 N(AnimKammy_FlyOff_Time) = 0.0;
#if VERSION_PAL
API_CALLABLE(N(AnimKammy_FlyOff));
INCLUDE_ASM(ApiResult, "world/area_hos/hos_05/hos_05_5_intro", AnimKammy_FlyOff);
#else
API_CALLABLE(N(AnimKammy_FlyOff)) {
Npc* kammy = resolve_npc(script, NPC_Kammy);
@ -1958,13 +1948,12 @@ API_CALLABLE(N(AnimKammy_FlyOff)) {
40.0f, &kammy->pos.y);
kammy->colliderPos.y = kammy->pos.y;
N(AnimKammy_FlyOff_Time)++;
if (N(AnimKammy_FlyOff_Time) > 40.0f) {
if (N(AnimKammy_FlyOff_Time) > (int)(40 * DT)) {
return ApiStatus_DONE1;
} else {
return ApiStatus_BLOCK;
}
}
#endif
API_CALLABLE(N(func_80244934_A2EB74)) {
if (isInitialCall) {

View File

@ -44,3 +44,7 @@ extern EvtScript N(EVS_Main);
extern EvtScript N(EVS_Dummy);
extern EvtScript N(EVS_InitializeMinigame);
extern NpcGroupList N(DefaultNPCs);
#if VERSION_PAL
extern s32 N(pal_variable);
#endif

View File

@ -1,7 +1,6 @@
#include "mgm_02.h"
#if VERSION_PAL
extern s32 N(pal_variable);
s32 N(get_tattle)(void) {
s32 msgID = MSG_MapTattle_mgm_02;
if (N(pal_variable) != 0) {

View File

@ -438,7 +438,7 @@ API_CALLABLE(N(RunMinigame)) {
gameFinished = FALSE;
hittingPeachBlock = FALSE;
data = get_enemy(0)->varTablePtr[0];
data = get_enemy(SCOREKEEPER_ENEMY_IDX)->varTablePtr[SMASH_DATA_VAR_IDX];
for (i = 0; i < NUM_BOXES; i++) {
if (data->box[i].npcID != -1) {

View File

@ -199,10 +199,10 @@ void load_map_by_IDs(s16 areaID, s16 mapID, s16 loadType) {
sfx_reset_door_sounds();
if (!skipLoadingAssets) {
s32 thing = get_asset_offset(wMapTexName, &decompressedSize);
s32 texturesOffset = get_asset_offset(wMapTexName, &decompressedSize);
if (mapSettings->modelTreeRoot != NULL) {
load_data_for_models(mapSettings->modelTreeRoot, thing, decompressedSize);
load_data_for_models(mapSettings->modelTreeRoot, texturesOffset, decompressedSize);
}
}

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from functools import cache
from functools import lru_cache
import os
import shutil
from typing import List, Dict, Optional, Set, Union
@ -21,6 +21,7 @@ YAY0_COMPRESS_TOOL = f"{BUILD_TOOLS}/yay0/Yay0compress"
CRC_TOOL = f"{BUILD_TOOLS}/rom/n64crc"
def exec_shell(command: List[str]) -> str:
ret = subprocess.run(command, stdout=subprocess.PIPE, text=True)
return ret.stdout
@ -238,6 +239,12 @@ def write_ninja_rules(
command=f"$python {BUILD_TOOLS}/mapfs/combine.py $version $out $in",
)
ninja.rule(
"tex",
description="tex $out",
command=f"$python {BUILD_TOOLS}/mapfs/tex.py $out $tex_dir",
)
ninja.rule(
"pack_title_data",
description="pack_title_data $out",
@ -400,7 +407,7 @@ class Configure:
return [str(v) for v in ret.values()]
@cache
@lru_cache(maxsize=None)
def resolve_asset_path(self, path: Path) -> Path:
parts = list(path.parts)
@ -887,7 +894,15 @@ class Configure:
)
elif name.endswith("_tex"):
compress = False
bin_path = path
tex_dir = path.parent / name
build(
bin_path,
[tex_dir],
"tex",
variables={
"tex_dir": str(tex_dir)
}
)
elif name.endswith("_shape"):
map_name = "_".join(name.split("_")[:-1])

View File

@ -6,6 +6,7 @@ from glob import glob
import png # type: ignore
def unpack_color(s):
r = (s >> 11) & 0x1F
g = (s >> 6) & 0x1F
@ -47,12 +48,11 @@ def reversed_if(iterator, cond):
class Converter:
def __init__(self, mode, infile, outfile, *argv):
def __init__(self, mode, infile, flip_x: bool = False, flip_y: bool = False):
self.mode = mode
self.infile = infile
self.outfile = outfile
self.flip_x = "--flip-x" in argv
self.flip_y = "--flip-y" in argv
self.flip_x = flip_x
self.flip_y = flip_y
assert self.flip_x == False, "flip_x is not supported"
@ -64,151 +64,150 @@ class Converter:
print(self.infile + ": warning: " + msg, file=stderr)
def convert(self):
out_bytes = bytearray()
out_width = 0
out_height = 0
img = png.Reader(self.infile)
if self.mode == "rgba32":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
f.write(row)
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
out_bytes += row
elif self.mode == "rgba16":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
for rgba in iter_in_groups(row, 4):
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
f.write(color.to_bytes(2, byteorder="big"))
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
elif self.mode == "ci8":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.read()[2], self.flip_y):
f.write(row)
(out_width, out_height, data, info) = img.read()
for row in reversed_if(data, self.flip_y):
out_bytes += row
elif self.mode == "ci4":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.read()[2], self.flip_y):
for a, b in iter_in_groups(row, 2):
byte = (a << 4) | b
byte = byte & 0xFF
f.write(byte.to_bytes(1, byteorder="big"))
(out_width, out_height, data, info) = img.read()
for row in reversed_if(data, self.flip_y):
for a, b in iter_in_groups(row, 2):
byte = (a << 4) | b
byte = byte & 0xFF
out_bytes += byte.to_bytes(1, byteorder="big")
elif self.mode == "palette":
img.preamble(True)
palette = img.palette(alpha="force")
with open(self.outfile, "wb") as f:
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
f.write(color.to_bytes(2, byteorder="big"))
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
elif self.mode == "ia4":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2):
i1 = rgb_to_intensity(*c1[:3])
a1 = c1[3]
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2):
i1 = rgb_to_intensity(*c1[:3])
a1 = c1[3]
i2 = rgb_to_intensity(*c2[:3])
a2 = c2[3]
i2 = rgb_to_intensity(*c2[:3])
a2 = c2[3]
i1 = i1 >> 5
i2 = i2 >> 5
i1 = i1 >> 5
i2 = i2 >> 5
if a1 not in (0, 0xFF) or a2 not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
if c1[0] != c1[1] != c1[2]:
self.warn("grayscale mode but image is not")
if c2[0] != c2[1] != c2[2]:
self.warn("grayscale mode but image is not")
if a1 not in (0, 0xFF) or a2 not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
if c1[0] != c1[1] != c1[2]:
self.warn("grayscale mode but image is not")
if c2[0] != c2[1] != c2[2]:
self.warn("grayscale mode but image is not")
a1 = 1 if a1 > 128 else 0
a2 = 1 if a2 > 128 else 0
a1 = 1 if a1 > 128 else 0
a2 = 1 if a2 > 128 else 0
h = (i1 << 1) | a1
l = (i2 << 1) | a2
h = (i1 << 1) | a1
l = (i2 << 1) | a2
byte = (h << 4) | l
f.write(byte.to_bytes(1, byteorder="big"))
byte = (h << 4) | l
out_bytes += byte.to_bytes(1, byteorder="big")
elif self.mode == "ia8":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
for rgba in iter_in_groups(row, 4):
i = rgb_to_intensity(*rgba[:3])
a = rgba[3]
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
i = rgb_to_intensity(*rgba[:3])
a = rgba[3]
i = floor(15 * (i / 0xFF))
a = floor(15 * (a / 0xFF))
i = floor(15 * (i / 0xFF))
a = floor(15 * (a / 0xFF))
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
byte = (i << 4) | a
f.write(byte.to_bytes(1, byteorder="big"))
byte = (i << 4) | a
out_bytes += byte.to_bytes(1, byteorder="big")
elif self.mode == "ia16":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
for rgba in iter_in_groups(row, 4):
i = rgb_to_intensity(*rgba[:3])
a = rgba[3]
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
i = rgb_to_intensity(*rgba[:3])
a = rgba[3]
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
f.write(bytes((i, a)))
out_bytes += bytes((i, a))
elif self.mode == "i4":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2):
if c1[3] != 0xFF or c2[3] != 0xFF:
self.warn("discarding alpha channel")
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for c1, c2 in iter_in_groups(iter_in_groups(row, 4), 2):
if c1[3] != 0xFF or c2[3] != 0xFF:
self.warn("discarding alpha channel")
i1 = rgb_to_intensity(*c1[:3])
i2 = rgb_to_intensity(*c2[:3])
i1 = rgb_to_intensity(*c1[:3])
i2 = rgb_to_intensity(*c2[:3])
i1 = floor(15 * (i1 / 0xFF))
i2 = floor(15 * (i2 / 0xFF))
i1 = floor(15 * (i1 / 0xFF))
i2 = floor(15 * (i2 / 0xFF))
if c1[0] != c1[1] != c1[2]:
self.warn("grayscale mode but image is not")
if c2[0] != c2[1] != c2[2]:
self.warn("grayscale mode but image is not")
if c1[0] != c1[1] != c1[2]:
self.warn("grayscale mode but image is not")
if c2[0] != c2[1] != c2[2]:
self.warn("grayscale mode but image is not")
byte = (i1 << 4) | i2
f.write(byte.to_bytes(1, byteorder="big"))
byte = (i1 << 4) | i2
out_bytes += byte.to_bytes(1, byteorder="big")
elif self.mode == "i8":
with open(self.outfile, "wb") as f:
for row in reversed_if(img.asRGBA()[2], self.flip_y):
for rgba in iter_in_groups(row, 4):
if rgba[3] != 0xFF or rgba[3] != 0xFF:
self.warn("discarding alpha channel")
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
(out_width, out_height, data, info) = img.asRGBA()
for row in reversed_if(data, self.flip_y):
for rgba in iter_in_groups(row, 4):
if rgba[3] != 0xFF or rgba[3] != 0xFF:
self.warn("discarding alpha channel")
if rgba[0] != rgba[1] != rgba[2]:
self.warn("grayscale mode but image is not")
i = rgb_to_intensity(*rgba[:3])
f.write(i.to_bytes(1, byteorder="big"))
i = rgb_to_intensity(*rgba[:3])
out_bytes += i.to_bytes(1, byteorder="big")
elif self.mode == "party":
data = img.read()[2]
(out_width, out_height, data, info) = img.read()
img.preamble(True)
palette = img.palette(alpha="force")
with open(self.outfile, "wb") as f:
# palette
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
# palette
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
f.write(color.to_bytes(2, byteorder="big"))
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
assert f.tell() == 0x200, "palette has wrong size"
# ci 8
for row in reversed_if(data, self.flip_y):
out_bytes += row
# ci 8
for row in reversed_if(data, self.flip_y):
f.write(row)
f.write(b"\0\0\0\0\0\0\0\0\0\0") # padding
out_bytes += b"\0\0\0\0\0\0\0\0\0\0" # padding
elif self.mode == "bg":
width, height, data, info = img.read()
(out_width, out_height, data, info) = img.read()
img.preamble(True)
palettes = [img.palette(alpha="force")]
@ -217,52 +216,56 @@ class Converter:
pal.preamble(True)
palettes.append(pal.palette(alpha="force"))
with open(self.outfile, "wb") as f:
baseaddr = 0x80200000 # gBackgroundImage
headers_len = 0x10 * len(palettes)
palettes_len = 0x200 * len(palettes)
baseaddr = 0x80200000 # gBackgroundImage
headers_len = 0x10 * len(palettes)
palettes_len = 0x200 * len(palettes)
# header (struct BackgroundHeader)
for i, palette in enumerate(palettes):
f.write(
(baseaddr + palettes_len + headers_len).to_bytes(
4, byteorder="big"
)
) # raster offset
f.write(
(baseaddr + headers_len + 0x200 * i).to_bytes(
4, byteorder="big"
)
) # palette offset
f.write((12).to_bytes(2, byteorder="big")) # startX
f.write((20).to_bytes(2, byteorder="big")) # startY
f.write((width).to_bytes(2, byteorder="big")) # width
f.write((height).to_bytes(2, byteorder="big")) # height
# header (struct BackgroundHeader)
for i, palette in enumerate(palettes):
out_bytes += (baseaddr + palettes_len + headers_len).to_bytes(
4, byteorder="big"
) # raster offset
out_bytes += (baseaddr + headers_len + 0x200 * i).to_bytes(
4, byteorder="big"
) # palette offset
out_bytes += (12).to_bytes(2, byteorder="big") # startX
out_bytes += (20).to_bytes(2, byteorder="big") # startY
out_bytes += (out_width).to_bytes(2, byteorder="big") # width
out_bytes += (out_height).to_bytes(2, byteorder="big") # height
assert f.tell() == headers_len
for palette in palettes:
# palette
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
for palette in palettes:
# palette
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
out_bytes += color.to_bytes(2, byteorder="big")
color = pack_color(*rgba)
f.write(color.to_bytes(2, byteorder="big"))
assert f.tell() == palettes_len + headers_len
# ci 8
for row in reversed_if(data, self.flip_y):
f.write(row)
# ci 8
for row in reversed_if(data, self.flip_y):
out_bytes += row
else:
print("unsupported mode", file=stderr)
exit(1)
return (out_bytes, out_width, out_height)
if __name__ == "__main__":
if len(argv) < 4:
print("usage: build.py MODE INFILE OUTFILE [--flip-x] [--flip-y]")
exit(1)
Converter(*argv[1:]).convert()
mode = argv[1]
infile = argv[2]
outfile = argv[3]
flip_x = "--flip-x" in argv
flip_y = "--flip-y" in argv
(out_bytes, out_width, out_height) = Converter(
mode, infile, flip_x, flip_y
).convert()
with open(argv[3], "wb") as f:
f.write(out_bytes)

19
tools/build/mapfs/tex.py Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
import argparse
from pathlib import Path
from sys import argv, path
path.append(str(Path(__file__).parent.parent.parent / "splat"))
path.append(str(Path(__file__).parent.parent.parent / "splat_ext"))
from tex_archives import TexArchive
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Texture archives")
parser.add_argument("bin_out", type=Path, help="Output binary file path")
parser.add_argument("tex_dir", type=Path, help="File path to input tex subdirectory")
parser.add_argument(
"--endian", choices=["big", "little"], default="big", help="Output endianness"
)
args = parser.parse_args()
TexArchive.build(args.bin_out, args.tex_dir, args.endian)

View File

@ -2,7 +2,7 @@
import argparse
from dataclasses import dataclass
from functools import cache
from functools import lru_cache
from pathlib import Path
import sys
from typing import List
@ -95,7 +95,7 @@ SPECIAL_RASTER_BYTES = (
)
@cache
@lru_cache(maxsize=None)
def get_asset_path(asset: str) -> Path:
for sdir in ASSET_STACK:
potential_path = ASSET_DIR / sdir / "sprite" / asset

View File

@ -17,3 +17,11 @@ def unpack_color(data):
b = ceil(0xFF * (b / 31))
return r, g, b, a
def pack_color(r, g, b, a):
r = r >> 3
g = g >> 3
b = b >> 3
a = a >> 7
return (r << 11) | (g << 6) | (b << 1) | a

View File

@ -1,5 +1,6 @@
import os
import os, sys
from pathlib import Path
from typing import List
from segtypes.n64.segment import N64Segment
from util.n64.Yay0decompress import Yay0Decompressor
from util.color import unpack_color
@ -9,6 +10,10 @@ import png # type: ignore
import yaml as yaml_loader
import n64img.image
SPLAT_EXT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(str(Path(SPLAT_EXT_DIR)))
from tex_archives import TexArchive
script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
@ -69,6 +74,8 @@ class N64SegPm_map_data(N64Segment):
self.files = yaml_loader.load(f.read(), Loader=yaml_loader.SafeLoader)
def split(self, rom_bytes):
assert isinstance(self.rom_start, int)
fs_dir = options.opts.asset_path / self.dir / self.name
(fs_dir / "title").mkdir(parents=True, exist_ok=True)
(fs_dir / "party").mkdir(parents=True, exist_ok=True)
@ -104,6 +111,7 @@ class N64SegPm_map_data(N64Segment):
bytes = Yay0Decompressor.decompress_python(bytes)
if name.startswith("party_"):
assert path is not None
with open(path, "wb") as f:
# CI-8
w = png.Writer(150, 105, palette=parse_palette(bytes[:0x200]))
@ -143,7 +151,7 @@ class N64SegPm_map_data(N64Segment):
w = 128
h = 32
img = n64img.image.CI4(
data=bytes[0x10 : 0x10 + w * h], width=w, height=h
data=bytes[0x10 : 0x10 + (w * h // 2)], width=w, height=h
)
img.palette = parse_palette(bytes[0x810:0x830])
img.write(fs_dir / "title/copyright.png")
@ -189,7 +197,10 @@ class N64SegPm_map_data(N64Segment):
write_bg_png(
bytes, fs_dir / "bg" / f"{name}.alt.png", header_offset=0x10
)
elif name.endswith("_tex"):
TexArchive.extract(bytes, fs_dir / "tex" / name)
else:
assert path is not None
with open(path, "wb") as f:
f.write(bytes)

View File

@ -0,0 +1,642 @@
from dataclasses import dataclass
import os
import struct
import json
from pathlib import Path
import png
import n64img.image
from util.color import unpack_color, pack_color
from segtypes.n64.palette import iter_in_groups
from sys import path
path.append(str(Path(__file__).parent.parent / "build"))
from img.build import Converter
def decode_null_terminated_ascii(data):
length = 0
for byte in data:
if byte == 0:
break
length += 1
return data[:length].decode("ascii")
def parse_palette(data):
palette = []
for a, b in iter_in_groups(data, 2):
palette.append(unpack_color([a, b]))
return palette
FMT_RGBA = 0
FMT_CI = 2
FMT_IA = 3
FMT_I = 4
DEPTH_4_BIT = 0
DEPTH_8_BIT = 1
DEPTH_16_BIT = 2
DEPTH_32_BIT = 3
# extra tile modes
TILES_BASIC = 0
TILES_MIPMAPS = 1
TILES_SHARED_AUX = 2
TILES_INDEPENDENT_AUX = 3
aux_combine_modes = {
0x00: "None", # multiply main * prim, ignore aux
0x08: "Multiply", # multiply main * aux * prim
0x0D: "ModulateAlpha", # use prim color, but multiply alpha by the difference between main and aux red channels
0x10: "LerpMainAux", # use prim alpha to lerp between main and aux color, use main alpha
}
aux_combine_modes_inv = {v: k for k, v in aux_combine_modes.items()}
wrap_modes = {
0: "Repeat",
1: "Mirror",
2: "Clamp",
}
wrap_modes_inv = {v: k for k, v in wrap_modes.items()}
# correspond to modes provided to gSetTextureFilter, only 0 and 2 are ever used
filter_modes = {
0: "Nearest",
2: "Bilerp",
3: "Average",
}
filter_modes_inv = {v: k for k, v in filter_modes.items()}
def get_format_name(fmt, depth):
# get image from bytes for valid combinations of fmt and bit depth
if fmt == FMT_RGBA:
if depth == DEPTH_16_BIT:
return "RGBA16"
if depth == DEPTH_32_BIT:
return "RGBA32"
elif fmt == FMT_CI:
if depth == DEPTH_4_BIT:
return "CI4"
elif depth == DEPTH_8_BIT:
return "CI8"
elif fmt == FMT_IA:
if depth == DEPTH_4_BIT:
return "IA4"
elif depth == DEPTH_8_BIT:
return "IA8"
elif depth == DEPTH_16_BIT:
return "IA16"
elif fmt == FMT_I:
if depth == DEPTH_4_BIT:
return "I4"
elif depth == DEPTH_8_BIT:
return "I8"
else:
raise Exception(f"Invalid format/depth pair: {fmt} and {depth}")
def get_format_code(name):
# get image from bytes for valid combinations of fmt and bit depth
if name == "RGBA16":
return (FMT_RGBA, DEPTH_16_BIT)
elif name == "RGBA32":
return (FMT_RGBA, DEPTH_32_BIT)
elif name == "CI4":
return (FMT_CI, DEPTH_4_BIT)
elif name == "CI8":
return (FMT_CI, DEPTH_8_BIT)
elif name == "IA4":
return (FMT_IA, DEPTH_4_BIT)
elif name == "IA8":
return (FMT_IA, DEPTH_8_BIT)
elif name == "IA16":
return (FMT_IA, DEPTH_16_BIT)
elif name == "I4":
return (FMT_I, DEPTH_4_BIT)
elif name == "I8":
return (FMT_I, DEPTH_8_BIT)
else:
raise Exception(f"Invalid format: {name}")
# class for reading a tex file buffer one chunk at a time
@dataclass
class TexBuffer:
data: bytes
pos: int = 0
@property
def capacity(self):
return len(self.data)
def get(self, count):
amt = int(min(count, self.capacity - self.pos))
ret = self.data[self.pos : self.pos + amt]
self.pos += amt
return ret
def remaining(self):
return self.capacity - self.pos
class TexImage:
# utility function for unpacking aux/main property pairs from a single byte
def split_byte(self, byte):
return (byte >> 4 & 0xF), (byte & 0xF)
# utility function for unpacking aux/main property pairs from a single byte
def pack_byte(self, aux, main):
return ((aux & 0xF) << 4) | (main & 0xF)
# get n64img object from the buffer
def get_n64_img(self, texbuf: TexBuffer, fmt, depth, w, h):
# calculate size for bit depth
if depth == DEPTH_4_BIT:
size = w * h // 2
elif depth == DEPTH_8_BIT:
size = w * h
elif depth == DEPTH_16_BIT:
size = w * h * 2
elif depth == DEPTH_32_BIT:
size = w * h * 4
else:
raise Exception(f"Invalid bit depth: {depth}")
bytes = texbuf.get(size)
# get image from bytes for valid combinations of fmt and bit depth
fmt_name = get_format_name(fmt, depth)
if fmt_name == "RGBA16":
img = n64img.image.RGBA16(data=bytes, width=w, height=h)
elif fmt_name == "RGBA32":
img = n64img.image.RGBA32(data=bytes, width=w, height=h)
elif fmt_name == "CI4":
img = n64img.image.CI4(data=bytes, width=w, height=h)
elif fmt_name == "CI8":
img = n64img.image.CI8(data=bytes, width=w, height=h)
elif fmt_name == "IA4":
img = n64img.image.IA4(data=bytes, width=w, height=h)
elif fmt_name == "IA8":
img = n64img.image.IA8(data=bytes, width=w, height=h)
elif fmt_name == "IA16":
img = n64img.image.IA16(data=bytes, width=w, height=h)
elif fmt_name == "I4":
img = n64img.image.I4(data=bytes, width=w, height=h)
elif fmt_name == "I8":
img = n64img.image.I8(data=bytes, width=w, height=h)
else:
raise Exception(f"Invalid format: {fmt_name}")
img.flip_v = True
return img
# get palette from the buffer
def get_n64_pal(self, texbuf, fmt, depth):
if fmt == FMT_CI:
if depth == DEPTH_4_BIT:
return parse_palette(texbuf.get(0x20))
elif depth == DEPTH_8_BIT:
return parse_palette(texbuf.get(0x200))
# extract texture properties and rasters from buffer
def from_bytes(self, texbuf: TexBuffer):
# strip area prefix and original extension suffix
raw_name = decode_null_terminated_ascii(texbuf.get(32))
self.img_name = raw_name[4:-3]
self.raw_ext = raw_name[-3:]
(
self.aux_width,
self.main_width,
self.aux_height,
self.main_height,
self.is_variant,
self.extra_tiles,
self.combine_mode,
fmts,
depths,
hwraps,
vwraps,
self.filter_mode,
) = struct.unpack(">HHHHBBBBBBBB", texbuf.get(16))
# unpack upper/lower nibbles for aux/main
(self.aux_fmt, self.main_fmt) = self.split_byte(fmts)
(self.aux_depth, self.main_depth) = self.split_byte(depths)
(self.aux_hwrap, self.main_hwrap) = self.split_byte(hwraps)
(self.aux_vwrap, self.main_vwrap) = self.split_byte(vwraps)
self.has_mipmaps = False
self.has_aux = False
# main img only
if self.extra_tiles == TILES_BASIC:
self.main_img = self.get_n64_img(
texbuf,
self.main_fmt,
self.main_depth,
self.main_width,
self.main_height,
)
if self.main_fmt == FMT_CI:
self.main_img.palette = self.get_n64_pal(
texbuf, self.main_fmt, self.main_depth
)
# main img + mipmaps
elif self.extra_tiles == TILES_MIPMAPS:
self.has_mipmaps = True
self.main_img = self.get_n64_img(
texbuf,
self.main_fmt,
self.main_depth,
self.main_width,
self.main_height,
)
# read mipmaps
self.mipmaps = []
divisor = 2
if self.main_width >= (32 >> self.main_depth):
while True:
if (self.main_width // divisor) <= 0:
break
mmw = self.main_width // divisor
mmh = self.main_height // divisor
mipmap = self.get_n64_img(
texbuf, self.main_fmt, self.main_depth, mmw, mmh
)
self.mipmaps.append(mipmap)
divisor = divisor * 2
if (self.main_width // divisor) < (16 >> self.main_depth):
break
# read palette and assign to all images
if self.main_fmt == FMT_CI:
shared_pal = self.get_n64_pal(texbuf, self.main_fmt, self.main_depth)
self.main_img.palette = shared_pal
for mipmap in self.mipmaps:
mipmap.palette = shared_pal
# main + aux (shared attributes)
elif self.extra_tiles == TILES_SHARED_AUX:
self.has_aux = True
self.main_img = self.get_n64_img(
texbuf,
self.main_fmt,
self.main_depth,
self.main_width,
self.main_height // 2,
)
self.aux_img = self.get_n64_img(
texbuf,
self.main_fmt,
self.main_depth,
self.main_width,
self.main_height // 2,
)
if self.main_fmt == FMT_CI:
shared_pal = self.get_n64_pal(texbuf, self.main_fmt, self.main_depth)
self.main_img.palette = shared_pal
self.aux_img.palette = shared_pal
# main + aux (independent attributes)
elif self.extra_tiles == TILES_INDEPENDENT_AUX:
self.has_aux = True
# read main
self.main_img = self.get_n64_img(
texbuf,
self.main_fmt,
self.main_depth,
self.main_width,
self.main_height,
)
if self.main_fmt == FMT_CI:
pal = self.get_n64_pal(texbuf, self.main_fmt, self.main_depth)
self.main_img.palette = pal
# read aux
self.aux_img = self.get_n64_img(
texbuf, self.aux_fmt, self.aux_depth, self.aux_width, self.aux_height
)
if self.aux_fmt == FMT_CI:
self.aux_img.palette = self.get_n64_pal(
texbuf, self.aux_fmt, self.aux_depth
)
# constructs a dictionary entry for the tex archive for this texture
def get_json_entry(self):
out = {}
out["name"] = self.img_name
# only a single texture in 'tst_tex' has 'rgb', otherwise this is always 'tif'
if self.raw_ext != "tif":
out["ext"] = self.raw_ext
out["main"] = {
"format": get_format_name(self.main_fmt, self.main_depth),
"hwrap": wrap_modes.get(self.main_hwrap),
"vwrap": wrap_modes.get(self.main_vwrap),
}
if self.has_aux:
if self.extra_tiles == TILES_SHARED_AUX:
out["aux"] = {
"format": "Shared",
"hwrap": wrap_modes.get(self.aux_hwrap),
"vwrap": wrap_modes.get(self.aux_vwrap),
}
else:
out["aux"] = {
"format": get_format_name(self.aux_fmt, self.aux_depth),
"hwrap": wrap_modes.get(self.aux_hwrap),
"vwrap": wrap_modes.get(self.aux_vwrap),
}
if self.has_mipmaps:
out["hasMipmaps"] = True
if self.filter_mode == 2:
out["filter"] = True
out["combine"] = aux_combine_modes.get(self.combine_mode)
if self.is_variant:
out["variant"] = True
return out
def save_images(self, tex_path):
self.main_img.write(tex_path / f"{self.img_name}.png")
if self.has_aux:
self.aux_img.write(tex_path / f"{self.img_name}_AUX.png")
if self.has_mipmaps:
for idx, mipmap in enumerate(self.mipmaps):
mipmap.write(tex_path / f"{self.img_name}_MM{idx + 1}.png")
def read_json_img(self, img_data, tile_name, img_name):
fmt_str = img_data.get("format")
if fmt_str == None:
raise Exception(f"Texture {img_name} is missing 'format' for '{tile_name}'")
hwrap_str = img_data.get("hwrap", "Missing")
hwrap = wrap_modes_inv.get(hwrap_str)
if hwrap == None:
raise Exception(f"Texture {img_name} has invalid 'hwrap' for '{tile_name}'")
vwrap_str = img_data.get("vwrap", "Missing")
vwrap = wrap_modes_inv.get(vwrap_str)
if vwrap == None:
raise Exception(f"Texture {img_name} has invalid 'vwrap' for '{tile_name}'")
return fmt_str, hwrap, vwrap
def get_img_file(self, fmt_str, img_file):
(out_img, out_w, out_h) = Converter(
mode=fmt_str.lower(), infile=img_file, flip_y=True
).convert()
out_pal = bytearray()
if fmt_str == "CI4" or fmt_str == "CI8":
img = png.Reader(img_file)
img.preamble(True)
palette = img.palette(alpha="force")
for rgba in palette:
if rgba[3] not in (0, 0xFF):
self.warn("alpha mask mode but translucent pixels used")
color = pack_color(*rgba)
out_pal += color.to_bytes(2, byteorder="big")
return (out_img, out_pal, out_w, out_h)
# read texture properties from dictionary and load images
def from_json(self, tex_path: Path, json_data):
self.img_name = json_data["name"]
if "ext" in json_data:
self.raw_ext = json_data["ext"]
else:
self.raw_ext = "tif"
# read data for main tile
main_data = json_data.get("main")
if main_data == None:
raise Exception(f"Texture {self.img_name} has no definition for 'main'")
(main_fmt_name, self.main_hwrap, self.main_vwrap) = self.read_json_img(
main_data, "main", self.img_name
)
(self.main_fmt, self.main_depth) = get_format_code(main_fmt_name)
# read main image
img_path = str(tex_path / f"{self.img_name}.png")
if not os.path.isfile(img_path):
raise Exception(f"Could not find main image for texture: {self.img_name}")
(
self.main_img,
self.main_pal,
self.main_width,
self.main_height,
) = self.get_img_file(main_fmt_name, img_path)
# read data for aux tile
self.has_aux = "aux" in json_data
if self.has_aux:
aux_data = json_data.get("aux")
(aux_fmt_name, self.aux_hwrap, self.aux_vwrap) = self.read_json_img(
aux_data, "aux", self.img_name
)
if aux_fmt_name == "Shared":
# aux tiles have blank attributes in SHARED mode
aux_fmt_name = main_fmt_name
self.aux_fmt = 0
self.aux_depth = 0
self.aux_hwrap = 0
self.aux_vwrap = 0
self.extra_tiles = TILES_SHARED_AUX
else:
(self.aux_fmt, self.aux_depth) = get_format_code(aux_fmt_name)
self.extra_tiles = TILES_INDEPENDENT_AUX
# read aux image
img_path = str(tex_path / f"{self.img_name}_AUX.png")
if not os.path.isfile(img_path):
raise Exception(
f"Could not find AUX image for texture: {self.img_name}"
)
(
self.aux_img,
self.aux_pal,
self.aux_width,
self.aux_height,
) = self.get_img_file(aux_fmt_name, img_path)
if self.extra_tiles == TILES_SHARED_AUX:
# aux tiles have blank sizes in SHARED mode
self.main_height *= 2
self.aux_width = 0
self.aux_height = 0
else:
self.aux_fmt = 0
self.aux_depth = 0
self.aux_hwrap = 0
self.aux_vwrap = 0
self.aux_width = 0
self.aux_height = 0
self.extra_tiles = TILES_BASIC
# read mipmaps
self.has_mipmaps = json_data.get("hasMipmaps", False)
if self.has_mipmaps:
self.mipmaps = []
mipmap_idx = 1
divisor = 2
if self.main_width >= (32 >> self.main_depth):
while True:
if (self.main_width // divisor) <= 0:
break
mmw = self.main_width // divisor
mmh = self.main_height // divisor
img_path = str(tex_path / f"{self.img_name}_MM{mipmap_idx}.png")
if not os.path.isfile(img_path):
raise Exception(
f"Texture {self.img_name} is missing mipmap level {mipmap_idx} (size = {mmw} x {mmh})"
)
(raster, pal, width, height) = self.get_img_file(
main_fmt_name, img_path
)
self.mipmaps.append(raster)
if width != mmw or height != mmh:
raise Exception(
f"Texture {self.img_name} has wrong size for mipmap level {mipmap_idx} \n"
+ f"MM{mipmap_idx} size = {width} x {height}, but should be = {mmw} x {mmh}"
)
divisor = divisor * 2
mipmap_idx += 1
if (self.main_width // divisor) < (16 >> self.main_depth):
break
self.extra_tiles = TILES_MIPMAPS
# read filter mode
if json_data.get("filter", False):
self.filter_mode = 2
else:
self.filter_mode = 0
# read tile combine mode
combine_str = json_data.get("combine", "Missing")
self.combine = aux_combine_modes_inv.get(combine_str)
if self.combine == None:
raise Exception(f"Texture {self.img_name} has invalid 'combine'")
self.is_variant = json_data.get("variant", False)
# write texture header and image raster/palettes to byte array
def add_bytes(self, tex_name: str, bytes: bytearray):
# form raw name and write to header
raw_name = tex_name[:4] + self.img_name + self.raw_ext
name_bytes = raw_name.encode("ascii")
bytes += name_bytes
# pad name out to 32 bytes
pad_len = 32 - len(name_bytes)
assert pad_len > 0
bytes += b"\0" * pad_len
# write header fields
bytes += struct.pack(
">HHHHBBBBBBBB",
self.aux_width,
self.main_width,
self.aux_height,
self.main_height,
self.is_variant,
self.extra_tiles,
self.combine,
self.pack_byte(self.aux_fmt, self.main_fmt),
self.pack_byte(self.aux_depth, self.main_depth),
self.pack_byte(self.aux_hwrap, self.main_hwrap),
self.pack_byte(self.aux_vwrap, self.main_vwrap),
self.filter_mode,
)
# write rasters and palettes
if self.extra_tiles == TILES_BASIC:
bytes += self.main_img
if self.main_fmt == FMT_CI:
bytes += self.main_pal
elif self.extra_tiles == TILES_MIPMAPS:
bytes += self.main_img
for mipmap in self.mipmaps:
bytes += mipmap
if self.main_fmt == FMT_CI:
bytes += self.main_pal
elif self.extra_tiles == TILES_SHARED_AUX:
bytes += self.main_img
bytes += self.aux_img
if self.main_fmt == FMT_CI:
bytes += self.main_pal
elif self.extra_tiles == TILES_INDEPENDENT_AUX:
bytes += self.main_img
if self.main_fmt == FMT_CI:
bytes += self.main_pal
bytes += self.aux_img
if self.aux_fmt == FMT_CI:
bytes += self.aux_pal
class TexArchive:
@staticmethod
def extract(bytes, tex_path: Path):
textures = []
texbuf = TexBuffer(bytes)
while texbuf.remaining() > 0:
img = TexImage()
img.from_bytes(texbuf)
textures.append(img)
tex_path.mkdir(parents=True, exist_ok=True)
out = []
for texture in textures:
texture.save_images(tex_path)
out.append(texture.get_json_entry())
json_out = json.dumps(out, sort_keys=False, indent=4)
json_fn = str(tex_path) + ".json"
with open(json_fn, "w") as f:
f.write(json_out)
@staticmethod
def build(out_path: Path, tex_path: Path, endian: str = "big"):
out_bytes = bytearray()
tex_name = os.path.basename(tex_path)
json_fn = str(tex_path) + ".json"
with open(json_fn, "r") as json_file:
json_str = json_file.read()
json_data = json.loads(json_str)
if len(json_data) > 128:
raise Exception(
f"Maximum number of textures (128) exceeded by {tex_name} ({len(json_data)})`"
)
for img_data in json_data:
img = TexImage()
img.from_json(tex_path, img_data)
img.add_bytes(tex_name, out_bytes)
with open(out_path, "wb") as out_bin:
out_bin.write(out_bytes)

View File

@ -106,7 +106,7 @@ dlabel entity_numShadows
dlabel gSpriteShadingProfile
.space 4
dlabel entity_area_specific_data_is_loaded
dlabel isAreaSpecificEntityDataLoaded
.space 4
dlabel entity_updateCounter
@ -178,7 +178,7 @@ dlabel D_801516FC
dlabel gMainGameState
.space 0x00000040
dlabel gCurrentTileDescriptor
dlabel gCurrentTextureHeader
.space 0x00000030
dlabel wModelList
@ -268,7 +268,7 @@ dlabel texPannerAuxU
dlabel texPannerAuxV
.space 0x00000040
dlabel mdl_nextTextureAddress
dlabel TextureHeapPos
.space 4
dlabel mdl_currentTransformGroupChildIndex

View File

@ -401,7 +401,9 @@ segments:
vram: 0x8010dab0
subsegments:
- [0xA4990, hasm, bss3]
- [0xA4990, c, a5dd0_len_114e0]
- [0xA4990, c, entity]
- [auto, c, game_states]
- [auto, c, model]
- [0xB3140, c, B4580]
- [0xB5E70, c, entity_model]
- [0xB8370, c, worker]
@ -419,7 +421,9 @@ segments:
- [0xDD6B0, c, sprite_shading]
- [0xDE8C0, c, audio/sfx]
- [0xDFAA0, c, audio/e0b30_len_b80]
- [0xE0620, .data, a5dd0_len_114e0]
- [0xE0620, .data, entity]
- [auto, .data, game_states]
- [auto, .data, model]
- [0xE18C0, .data, B4580]
- [0xE18D0, .data, entity_model]
- [0xE18F0, .data, msg]

View File

@ -176,7 +176,7 @@ game_mode_set_fpDrawAuxUI = 0x80117F94;
step_current_game_mode = 0x80118088;
state_render_backUI = 0x80118168;
state_render_frontUI = 0x801181D4;
load_model_textures = 0x8011AE34;
mdl_load_all_textures = 0x8011AE34;
clear_model_data = 0x8011AF54;
init_model_data = 0x8011B1F8;
calculate_model_sizes = 0x8011B33C;

View File

@ -1,70 +0,0 @@
.set noat /* allow manual use of $at */
.set noreorder /* don't insert nops after branches */
/* Generated by spimdisasm 1.11.1 */
glabel hos_05_AnimBowser_FlyOff
/* A9A8B4 80244774 27BDFFD0 */ addiu $sp, $sp, -0x30
/* A9A8B8 80244778 AFB00018 */ sw $s0, 0x18($sp)
/* A9A8BC 8024477C 0080802D */ daddu $s0, $a0, $zero
/* A9A8C0 80244780 AFB1001C */ sw $s1, 0x1C($sp)
/* A9A8C4 80244784 00A0882D */ daddu $s1, $a1, $zero
/* A9A8C8 80244788 24050007 */ addiu $a1, $zero, 0x7
/* A9A8CC 8024478C AFBF0028 */ sw $ra, 0x28($sp)
/* A9A8D0 80244790 AFB30024 */ sw $s3, 0x24($sp)
/* A9A8D4 80244794 0C0B42B0 */ jal resolve_npc
/* A9A8D8 80244798 AFB20020 */ sw $s2, 0x20($sp)
/* A9A8DC 8024479C 0200202D */ daddu $a0, $s0, $zero
/* A9A8E0 802447A0 24050008 */ addiu $a1, $zero, 0x8
/* A9A8E4 802447A4 0C0B42B0 */ jal resolve_npc
/* A9A8E8 802447A8 0040902D */ daddu $s2, $v0, $zero
/* A9A8EC 802447AC 12200004 */ beqz $s1, .LPAL_802447C0
/* A9A8F0 802447B0 0040982D */ daddu $s3, $v0, $zero
/* A9A8F4 802447B4 C640003C */ lwc1 $f0, 0x3C($s2)
/* A9A8F8 802447B8 3C018025 */ lui $at, %hi(hos_05_AnimBowser_FlyOff_InitialY)
/* A9A8FC 802447BC E420F314 */ swc1 $f0, %lo(hos_05_AnimBowser_FlyOff_InitialY)($at)
.LPAL_802447C0:
/* A9A900 802447C0 3C018025 */ lui $at, %hi(hos_05_AnimBowser_FlyOff_InitialY)
/* A9A904 802447C4 C420F314 */ lwc1 $f0, %lo(hos_05_AnimBowser_FlyOff_InitialY)($at)
/* A9A908 802447C8 24040003 */ addiu $a0, $zero, 0x3
/* A9A90C 802447CC 44050000 */ mfc1 $a1, $f0
/* A9A910 802447D0 3C0143FA */ lui $at, (0x43FA0000 >> 16)
/* A9A914 802447D4 44810000 */ mtc1 $at, $f0
/* A9A918 802447D8 44853000 */ mtc1 $a1, $f6
/* A9A91C 802447DC 3C108025 */ lui $s0, %hi(hos_05_AnimBowser_FlyOff_Time)
/* A9A920 802447E0 2610AE70 */ addiu $s0, $s0, %lo(hos_05_AnimBowser_FlyOff_Time)
/* A9A924 802447E4 46003180 */ add.s $f6, $f6, $f0
/* A9A928 802447E8 3C014220 */ lui $at, (0x42200000 >> 16)
/* A9A92C 802447EC 44811000 */ mtc1 $at, $f2
/* A9A930 802447F0 8E070000 */ lw $a3, 0x0($s0)
/* A9A934 802447F4 44063000 */ mfc1 $a2, $f6
/* A9A938 802447F8 2642003C */ addiu $v0, $s2, 0x3C
/* A9A93C 802447FC AFA20014 */ sw $v0, 0x14($sp)
/* A9A940 80244800 0C090439 */ jal interp_value_with_easing
/* A9A944 80244804 E7A20010 */ swc1 $f2, 0x10($sp)
/* A9A948 80244808 C640003C */ lwc1 $f0, 0x3C($s2)
/* A9A94C 8024480C 3C013F80 */ lui $at, (0x3F800000 >> 16)
/* A9A950 80244810 44811000 */ mtc1 $at, $f2
/* A9A954 80244814 E660003C */ swc1 $f0, 0x3C($s3)
/* A9A958 80244818 C6000000 */ lwc1 $f0, 0x0($s0)
/* A9A95C 8024481C C644003C */ lwc1 $f4, 0x3C($s2)
/* A9A960 80244820 46020000 */ add.s $f0, $f0, $f2
/* A9A964 80244824 E6440070 */ swc1 $f4, 0x70($s2)
/* A9A968 80244828 C662003C */ lwc1 $f2, 0x3C($s3)
/* A9A96C 8024482C 3C014204 */ lui $at, (0x42040000 >> 16)
/* A9A970 80244830 44812000 */ mtc1 $at, $f4
/* A9A974 80244834 24020001 */ addiu $v0, $zero, 0x1
/* A9A978 80244838 E6620070 */ swc1 $f2, 0x70($s3)
/* A9A97C 8024483C 4600203C */ c.lt.s $f4, $f0
/* A9A980 80244840 00000000 */ nop
/* A9A984 80244844 45010002 */ bc1t .LPAL_80244850
/* A9A988 80244848 E6000000 */ swc1 $f0, 0x0($s0)
/* A9A98C 8024484C 0000102D */ daddu $v0, $zero, $zero
.LPAL_80244850:
/* A9A990 80244850 8FBF0028 */ lw $ra, 0x28($sp)
/* A9A994 80244854 8FB30024 */ lw $s3, 0x24($sp)
/* A9A998 80244858 8FB20020 */ lw $s2, 0x20($sp)
/* A9A99C 8024485C 8FB1001C */ lw $s1, 0x1C($sp)
/* A9A9A0 80244860 8FB00018 */ lw $s0, 0x18($sp)
/* A9A9A4 80244864 03E00008 */ jr $ra
/* A9A9A8 80244868 27BD0030 */ addiu $sp, $sp, 0x30

View File

@ -1,57 +0,0 @@
.set noat /* allow manual use of $at */
.set noreorder /* don't insert nops after branches */
/* Generated by spimdisasm 1.11.1 */
glabel hos_05_AnimKammy_FlyOff
/* A9A9AC 8024486C 27BDFFD8 */ addiu $sp, $sp, -0x28
/* A9A9B0 80244870 AFB00018 */ sw $s0, 0x18($sp)
/* A9A9B4 80244874 00A0802D */ daddu $s0, $a1, $zero
/* A9A9B8 80244878 24050009 */ addiu $a1, $zero, 0x9
/* A9A9BC 8024487C AFBF0020 */ sw $ra, 0x20($sp)
/* A9A9C0 80244880 0C0B42B0 */ jal resolve_npc
/* A9A9C4 80244884 AFB1001C */ sw $s1, 0x1C($sp)
/* A9A9C8 80244888 12000004 */ beqz $s0, .LPAL_8024489C
/* A9A9CC 8024488C 0040882D */ daddu $s1, $v0, $zero
/* A9A9D0 80244890 C620003C */ lwc1 $f0, 0x3C($s1)
/* A9A9D4 80244894 3C018025 */ lui $at, %hi(hos_05_AnimKammy_FlyOff_InitialY)
/* A9A9D8 80244898 E420F318 */ swc1 $f0, %lo(hos_05_AnimKammy_FlyOff_InitialY)($at)
.LPAL_8024489C:
/* A9A9DC 8024489C 3C018025 */ lui $at, %hi(hos_05_AnimKammy_FlyOff_InitialY)
/* A9A9E0 802448A0 C420F318 */ lwc1 $f0, %lo(hos_05_AnimKammy_FlyOff_InitialY)($at)
/* A9A9E4 802448A4 24040003 */ addiu $a0, $zero, 0x3
/* A9A9E8 802448A8 44050000 */ mfc1 $a1, $f0
/* A9A9EC 802448AC 3C0143FA */ lui $at, (0x43FA0000 >> 16)
/* A9A9F0 802448B0 44810000 */ mtc1 $at, $f0
/* A9A9F4 802448B4 44853000 */ mtc1 $a1, $f6
/* A9A9F8 802448B8 3C108025 */ lui $s0, %hi(hos_05_AnimKammy_FlyOff_Time)
/* A9A9FC 802448BC 2610AE74 */ addiu $s0, $s0, %lo(hos_05_AnimKammy_FlyOff_Time)
/* A9AA00 802448C0 46003180 */ add.s $f6, $f6, $f0
/* A9AA04 802448C4 3C014220 */ lui $at, (0x42200000 >> 16)
/* A9AA08 802448C8 44811000 */ mtc1 $at, $f2
/* A9AA0C 802448CC 8E070000 */ lw $a3, 0x0($s0)
/* A9AA10 802448D0 44063000 */ mfc1 $a2, $f6
/* A9AA14 802448D4 2622003C */ addiu $v0, $s1, 0x3C
/* A9AA18 802448D8 AFA20014 */ sw $v0, 0x14($sp)
/* A9AA1C 802448DC 0C090439 */ jal interp_value_with_easing
/* A9AA20 802448E0 E7A20010 */ swc1 $f2, 0x10($sp)
/* A9AA24 802448E4 C6020000 */ lwc1 $f2, 0x0($s0)
/* A9AA28 802448E8 3C013F80 */ lui $at, (0x3F800000 >> 16)
/* A9AA2C 802448EC 44810000 */ mtc1 $at, $f0
/* A9AA30 802448F0 3C014204 */ lui $at, (0x42040000 >> 16)
/* A9AA34 802448F4 44812000 */ mtc1 $at, $f4
/* A9AA38 802448F8 46001080 */ add.s $f2, $f2, $f0
/* A9AA3C 802448FC C620003C */ lwc1 $f0, 0x3C($s1)
/* A9AA40 80244900 24020001 */ addiu $v0, $zero, 0x1
/* A9AA44 80244904 E6200070 */ swc1 $f0, 0x70($s1)
/* A9AA48 80244908 4602203C */ c.lt.s $f4, $f2
/* A9AA4C 8024490C 00000000 */ nop
/* A9AA50 80244910 45010002 */ bc1t .LPAL_8024491C
/* A9AA54 80244914 E6020000 */ swc1 $f2, 0x0($s0)
/* A9AA58 80244918 0000102D */ daddu $v0, $zero, $zero
.LPAL_8024491C:
/* A9AA5C 8024491C 8FBF0020 */ lw $ra, 0x20($sp)
/* A9AA60 80244920 8FB1001C */ lw $s1, 0x1C($sp)
/* A9AA64 80244924 8FB00018 */ lw $s0, 0x18($sp)
/* A9AA68 80244928 03E00008 */ jr $ra
/* A9AA6C 8024492C 27BD0028 */ addiu $sp, $sp, 0x28

View File

@ -106,7 +106,7 @@ dlabel entity_numShadows
dlabel gSpriteShadingProfile
.space 4
dlabel entity_area_specific_data_is_loaded
dlabel isAreaSpecificEntityDataLoaded
.space 4
dlabel entity_updateCounter
@ -178,7 +178,7 @@ dlabel D_801516FC
dlabel gMainGameState
.space 0x00000040
dlabel gCurrentTileDescriptor
dlabel gCurrentTextureHeader
.space 0x00000030
dlabel wModelList
@ -268,7 +268,7 @@ dlabel texPannerAuxU
dlabel texPannerAuxV
.space 0x00000040
dlabel mdl_nextTextureAddress
dlabel TextureHeapPos
.space 4
dlabel mdl_currentTransformGroupChildIndex

View File

@ -824,7 +824,9 @@ segments:
follows_vram: engine1
subsegments:
- [0xA5DD0, hasm, bss3]
- [0xA5DD0, c, a5dd0_len_114e0]
- [0xA5DD0, c, entity]
- [0xA9290, c, game_states]
- [0xA9790, c, model]
- [0xB4580, c, B4580]
- [0xB72B0, c, entity_model]
- [0xB97B0, c, worker]
@ -842,7 +844,9 @@ segments:
- [0xDE740, c, sprite_shading]
- [0xDF950, c, audio/sfx]
- [0xE0B30, c, audio/e0b30_len_b80]
- [0xE16B0, .data, a5dd0_len_114e0]
- [0xE16B0, .data, entity]
- [0xE16C0, .data, game_states]
- [0xE16C0, .data, model]
- [0xE2950, .data, B4580]
- [0xE2960, .data, entity_model]
- [0xE2980, .data, msg]

View File

@ -3998,12 +3998,12 @@ state_do_unk = 0x80112EEC; // type:func rom:0xA95EC
state_render_backUI = 0x80112F58; // type:func rom:0xA9658
state_render_frontUI = 0x80112FC4; // type:func rom:0xA96C4
appendGfx_model = 0x80113090; // type:func rom:0xA9790
func_80114B58 = 0x80114B58; // type:func rom:0xAB258
load_tile_header = 0x80114D6C; // type:func rom:0xAB46C
func_80115498 = 0x80115498; // type:func rom:0xABB98
load_texture_impl = 0x80114B58; // type:func rom:0xAB258
load_texture_by_name = 0x80114D6C; // type:func rom:0xAB46C
load_texture_variants = 0x80115498; // type:func rom:0xABB98
get_model_property = 0x80115B0C; // type:func rom:0xAC20C
_load_model_textures = 0x80115B44; // type:func rom:0xAC244
load_model_textures = 0x80115C24; // type:func rom:0xAC324
load_next_model_textures = 0x80115B44; // type:func rom:0xAC244
mdl_load_all_textures = 0x80115C24; // type:func rom:0xAC324
mdl_get_child_count = 0x80115CA8; // type:func rom:0xAC3A8
clear_model_data = 0x80115D44; // type:func rom:0xAC444
init_model_data = 0x80115FE8; // type:func rom:0xAC6E8
@ -4016,7 +4016,7 @@ appendGfx_model_group = 0x80117C94; // type:func rom:0xAE394
func_80117D00 = 0x80117D00; // type:func rom:0xAE400
render_transform_group_node = 0x80117E74; // type:func rom:0xAE574
render_transform_group = 0x8011800C; // type:func rom:0xAE70C
func_801180E8 = 0x801180E8; // type:func rom:0xAE7E8
make_texture_gfx = 0x801180E8; // type:func rom:0xAE7E8
get_model_from_list_index = 0x8011AD30; // type:func rom:0xB1430
load_data_for_models = 0x8011AD48; // type:func rom:0xB1448
load_model_transforms = 0x8011ADC8; // type:func rom:0xB14C8
@ -4493,7 +4493,7 @@ D_8014B0B8 = 0x8014B0B8; // rom:0xE17B8
D_8014B0BC = 0x8014B0BC; // rom:0xE17BC
D_8014B400 = 0x8014B400; // rom:0xE1B00
D_8014B404 = 0x8014B404; // rom:0xE1B04
mdl_textureBaseAddress = 0x8014B748; // rom:0xE1E48
TextureHeapBase = 0x8014B748; // rom:0xE1E48
mdl_bgMultiplyColorA = 0x8014B74C; // rom:0xE1E4C
mdl_bgMultiplyColorR = 0x8014B74D; // rom:0xE1E4D
mdl_bgMultiplyColorG = 0x8014B74E; // rom:0xE1E4E