Support viewing some AOB files

This commit is contained in:
Garhoogin 2025-06-02 19:25:16 -05:00 committed by GitHub
parent 5a120b2f44
commit 43554ae349
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 214 additions and 1 deletions

View File

@ -7,6 +7,7 @@
#include "ncgr.h"
#include "ncer.h"
#include "nscr.h"
#include "nanr.h"
extern const wchar_t *gComboFormats[] = {
L"Invalid",
@ -16,6 +17,7 @@ extern const wchar_t *gComboFormats[] = {
L"5BG",
L"MBB",
L"BNCD",
L"AOB",
NULL
};
@ -115,6 +117,10 @@ int combo2dGetObjMinCount(int comboType, int objType) {
if (objType == FILE_TYPE_CHARACTER) return 1;
if (objType == FILE_TYPE_CELL) return 1;
return 0;
case COMBO2D_TYPE_AOB:
if (objType == FILE_TYPE_CELL) return 1;
if (objType == FILE_TYPE_NANR) return 1;
return 0;
case COMBO2D_TYPE_DATAFILE:
//no particular requirements
return 0;
@ -151,6 +157,10 @@ int combo2dGetObjMaxCount(int comboType, int objType) {
if (objType == FILE_TYPE_CHARACTER) return 1;
if (objType == FILE_TYPE_CELL) return 1;
return 0;
case COMBO2D_TYPE_AOB:
if (objType == FILE_TYPE_CELL) return 1;
if (objType == FILE_TYPE_NANR) return 1;
return 0;
case COMBO2D_TYPE_DATAFILE:
//no particular requirements
return INT_MAX;
@ -360,8 +370,21 @@ int combo2dIsValidBncd(const unsigned char *file, unsigned int size) {
return 1;
}
static int combo2dIsValidAob(const unsigned char *file, unsigned int size) {
if (!IscadIsValidFooter(file, size)) return 0;
unsigned int agbSize, animSize, grpSize;
const unsigned char *agb = IscadFindBlockBySignature(file, size, "AGB ", &agbSize);
const unsigned char *anim = IscadFindBlockBySignature(file, size, "ANIM", &animSize);
const unsigned char *grp = IscadFindBlockBySignature(file, size, "GRP ", &grpSize);
if (agb == NULL || anim == NULL || grp == NULL) return 0;
return 1;
}
int combo2dIsValid(const unsigned char *file, unsigned int size) {
if (combo2dIsValid5bg(file, size)) return COMBO2D_TYPE_5BG;
if (combo2dIsValidAob(file, size)) return COMBO2D_TYPE_AOB;
if (combo2dIsValidBncd(file, size)) return COMBO2D_TYPE_BNCD;
if (combo2dIsValidTimeAce(file, size)) return COMBO2D_TYPE_TIMEACE;
if (combo2dIsValidBanner(file, size)) return COMBO2D_TYPE_BANNER;
@ -612,6 +635,144 @@ int combo2dReadMbb(COMBO2D *combo, const unsigned char *buffer, unsigned int siz
return 0;
}
static int combo2dReadAob(COMBO2D *combo, const unsigned char *buffer, unsigned int size) {
unsigned int agbSize, animSize, achrSize, objSize, grpSize, cmntSize, anmcSize, p2dmSize, ccamSize, linkSize;
unsigned char *agb = IscadFindBlockBySignature(buffer, size, "AGB ", &agbSize); // read
unsigned char *anim = IscadFindBlockBySignature(buffer, size, "ANIM", &animSize);
unsigned char *achr = IscadFindBlockBySignature(buffer, size, "ACHR", &achrSize);
unsigned char *obj = IscadFindBlockBySignature(buffer, size, "OBJ ", &objSize);
unsigned char *grp = IscadFindBlockBySignature(buffer, size, "GRP ", &grpSize);
unsigned char *cmnt = IscadFindBlockBySignature(buffer, size, "CMNT", &cmntSize); // read
unsigned char *anmc = IscadFindBlockBySignature(buffer, size, "ANMC", &anmcSize); // read
unsigned char *p2dm = IscadFindBlockBySignature(buffer, size, "2DM ", &p2dmSize); // read
unsigned char *ccam = IscadFindBlockBySignature(buffer, size, "CCAM", &ccamSize);
unsigned char *link = IscadFindBlockBySignature(buffer, size, "LINK", &linkSize); // read
/*
TODO: handling character compressed animation mode
byte 0 of CCAM will be set nonzero, and an ACHR block will be present with 1D compressed character data
OBJ block may be present
*/
int mappingMode = GX_OBJVRAMMODE_CHAR_1D_32K;
if (p2dm != NULL && p2dmSize > 0 && *p2dm) mappingMode = GX_OBJVRAMMODE_CHAR_2D;
unsigned int nObjEntry = *(const unsigned char *) (agb + 0x0);
NCER *ncer = (NCER *) calloc(1, sizeof(NCER));
CellInit(ncer, NCER_TYPE_COMBO);
ncer->mappingMode = mappingMode;
ncer->nCells = nObjEntry;
ncer->cells = (NCER_CELL *) calloc(ncer->nCells, sizeof(NCER_CELL));
if (cmnt != NULL) {
unsigned int commentLength = cmnt[1];
ncer->header.comment = calloc(commentLength + 1, 1);
memcpy(ncer->header.comment, cmnt + 2, commentLength);
}
//read cell OBJ data
const unsigned char *pCell = agb + 1;
ANIM_DATA_SRT *trans = (ANIM_DATA_SRT *) calloc(nObjEntry, sizeof(ANIM_DATA_SRT));
for (unsigned int i = 0; i < nObjEntry; i++) {
trans[i].sx = trans[i].sy = 4096;
}
for (unsigned int curCellCount = 0; curCellCount < nObjEntry;) {
uint16_t nObj = *(const uint16_t *) (pCell + 0);
pCell += 2;
if (nObj & 0x8000) {
//msb=1: SRT data
nObj &= ~0x8000;
for (unsigned int j = 0; j < nObj; j++) {
int deg = *(const int16_t *) (pCell + 0);
int grad = (deg * 65536 + (deg >= 0 ? 180 : -180)) / 360;
//TODO: support multiple transforms per cell?
trans[curCellCount].rotZ = grad;
trans[curCellCount].sx = (*(const int16_t *) (pCell + 2)) * 16; // 8-bit fraction -> 12-bit fraction
trans[curCellCount].sy = (*(const int16_t *) (pCell + 4)) * 16; // 8-bit fraction -> 12-bit fraction
pCell += 0x6;
}
} else {
//msb=0: OBJ data
int cellI = curCellCount;
ncer->cells[cellI].nAttribs = nObj;
ncer->cells[cellI].attr = (uint16_t *) calloc(nObj, 6);
for (unsigned int j = 0; j < nObj; j++) {
memcpy(ncer->cells[cellI].attr + j * 3, pCell, 6);
pCell += 0x6;
}
curCellCount++;
}
}
NANR *nanr = (NANR *) calloc(1, sizeof(NANR));
AnmInit(nanr, NANR_TYPE_COMBO);
//read animation sequences
unsigned int nSeq = *(anim++);
nanr->nSequences = nSeq;
nanr->sequences = (NANR_SEQUENCE *) calloc(nanr->nSequences, sizeof(NANR_SEQUENCE));
for (unsigned int i = 0; i < nSeq; i++) {
//sequence properties
NANR_SEQUENCE *seq = &nanr->sequences[i];
int offsX = *(const int16_t *) (anim + 0x0);
int offsY = *(const int16_t *) (anim + 0x2);
unsigned int nFrame = *(const uint8_t *) (anim + 0x4);
anim += 0x5;
seq->startFrameIndex = 0;
seq->type = (NANR_SEQ_TYPE_CELL << 16) | NANR_SEQ_TYPE_INDEX_SRT;
seq->mode = NANR_SEQ_MODE_FORWARD_LOOP;
seq->nFrames = nFrame;
seq->frames = (FRAME_DATA *) calloc(seq->nFrames, sizeof(FRAME_DATA));
for (unsigned int j = 0; j < nFrame; j++) {
unsigned int cellno = *(const uint16_t *) (anim + 0x0);
unsigned int dur = *(const uint16_t *) (anim + 0x2);
seq->frames[j].nFrames = dur;
seq->frames[j].animationData = calloc(1, sizeof(ANIM_DATA_SRT));
ANIM_DATA_SRT *pSrt = (ANIM_DATA_SRT *) seq->frames[j].animationData;
pSrt->index = cellno;
pSrt->px = offsX;
pSrt->py = offsY;
pSrt->sx = trans[cellno].sx;
pSrt->sy = trans[cellno].sy;
pSrt->rotZ = trans[cellno].rotZ;
anim += 4;
}
}
free(trans);
if (anmc != NULL) {
unsigned int commentLength = anmc[1];
nanr->header.comment = calloc(commentLength + 1, 1);
memcpy(nanr->header.comment, anmc + 2, commentLength);
}
combo2dLink(combo, &ncer->header);
combo2dLink(combo, &nanr->header);
if (link != NULL) {
unsigned int linkLength = link[1];
combo->header.fileLink = calloc(linkLength + 1, 1);
memcpy(combo->header.fileLink, link + 2, linkLength);
}
return 0;
}
int combo2dRead(COMBO2D *combo, const unsigned char *buffer, unsigned int size) {
int format = combo2dIsValid(buffer, size);
if (format == COMBO2D_TYPE_INVALID) return 1;
@ -628,6 +789,8 @@ int combo2dRead(COMBO2D *combo, const unsigned char *buffer, unsigned int size)
return combo2dReadMbb(combo, buffer, size);
case COMBO2D_TYPE_BNCD:
return combo2dReadBncd(combo, buffer, size);
case COMBO2D_TYPE_AOB:
return combo2dReadAob(combo, buffer, size);
}
return 1;
}

View File

@ -8,7 +8,8 @@
#define COMBO2D_TYPE_5BG 4
#define COMBO2D_TYPE_MBB 5
#define COMBO2D_TYPE_BNCD 6
#define COMBO2D_TYPE_MAX 7 //max +1
#define COMBO2D_TYPE_AOB 7
#define COMBO2D_TYPE_MAX 8 // max +1
extern const wchar_t *gComboFormats[];

View File

@ -10,6 +10,7 @@
#define NANR_TYPE_INVALID 0
#define NANR_TYPE_NANR 1
#define NANR_TYPE_GHOSTTRICK 2
#define NANR_TYPE_COMBO 3
#define NANR_SEQ_TYPE_INDEX 0

View File

@ -993,6 +993,12 @@ VOID OpenFileByName(HWND hWnd, LPCWSTR path) {
h = CreateNcerViewerImmediate(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, data->hWndMdi, (NCER *) object);
copy = EditorGetObject(h);
break;
case FILE_TYPE_NANR:
object->combo = (void *) combo;
h = CreateNanrViewerImmediate(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, data->hWndMdi, (NANR *) object);
copy = EditorGetObject(h);
break;
}
//if we created a copy, free the original and keep the copy

View File

@ -695,6 +695,44 @@ void NnsStreamFree(NnsStream *stream) {
// ----- ISCAD functions
int IscadIsValidFooter(const unsigned char *footer, unsigned int size) {
//check blocks
unsigned int pos = 0;
while (pos < size) {
const unsigned char *hdr = footer + pos;
if ((size - pos) < 8) return 0;
if (!NnsiSigNameIsValid((const char *) hdr)) return 0;
//check block size
pos += 8;
unsigned int blockSize = *(const uint32_t *) (hdr + 4);
if ((size - pos) < blockSize) return 0;
pos += blockSize;
}
return 1;
}
unsigned char *IscadFindBlockBySignature(const unsigned char *buffer, unsigned int size, const char *signature, unsigned int *pSize) {
//check blocks
unsigned int pos = 0;
while (pos < size) {
const unsigned char *hdr = buffer + pos;
if (memcmp(hdr, signature, 4) == 0) {
//return block
*pSize = *(const uint32_t *) (hdr + 4);
return (unsigned char *) (hdr + 8);
}
//check block size
pos += 8;
unsigned int blockSize = *(const uint32_t *) (hdr + 4);
pos += blockSize;
}
return NULL;
}
void IscadStreamCreate(IscadStream *stream) {
stream->inFooter = 0;
bstreamCreate(&stream->stream, NULL, 0);

View File

@ -74,6 +74,10 @@ void NnsStreamFree(NnsStream *stream);
// ----- ISCAD stream functions
int IscadIsValidFooter(const unsigned char *footer, unsigned int size);
unsigned char *IscadFindBlockBySignature(const unsigned char *buffer, unsigned int size, const char *signature, unsigned int *pSize);
typedef struct IscadStream_ {
BSTREAM stream; // file stream
int inFooter; // in footer