From d55fbd6ff4ce2dab93afe4a6894e07e63f335758 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sun, 20 Oct 2024 12:51:53 -0500 Subject: [PATCH 1/5] update NANR parser to handle resultType better and handle padding --- gfx.c | 125 +++++++++++++++++++++++++++++++++++++++++------------- json.c | 6 +-- options.h | 3 +- 3 files changed, 101 insertions(+), 33 deletions(-) diff --git a/gfx.c b/gfx.c index 0bc7150..c4db346 100644 --- a/gfx.c +++ b/gfx.c @@ -1327,8 +1327,8 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) { options->sequenceData[i]->frameCount = data[offset] | (data[offset + 1] << 8); options->sequenceData[i]->loopStartFrame = data[offset + 2] | (data[offset + 3] << 8); - options->sequenceData[i]->animationElement = data[offset + 4] | (data[offset + 5] << 8); - options->sequenceData[i]->animationType = data[offset + 6] | (data[offset + 7] << 8); + options->sequenceData[i]->animationType = data[offset + 4] | (data[offset + 5] << 8); + options->sequenceData[i]->animationType2 = data[offset + 6] | (data[offset + 7] << 8); options->sequenceData[i]->playbackMode = data[offset + 8] | (data[offset + 9] << 8) | (data[offset + 10] << 16) | (data[offset + 11] << 24); frameOffsets[i] = data[offset + 12] | (data[offset + 13] << 8) | (data[offset + 14] << 16) | (data[offset + 15] << 24); @@ -1358,6 +1358,7 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) { if (resultOffsets[k] == options->sequenceData[i]->frameData[j]->resultOffset) { + options->sequenceData[i]->frameData[j]->resultId = k; present = true; break; } @@ -1370,6 +1371,7 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) { if (resultOffsets[k] == -1) { + options->sequenceData[i]->frameData[j]->resultId = k; resultOffsets[k] = options->sequenceData[i]->frameData[j]->resultOffset; break; } @@ -1400,37 +1402,49 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) options->animationResults[i] = malloc(sizeof(struct AnimationResults)); } + // store the animationType of the corresponding sequence as this result's resultType + for (int i = 0; i < options->sequenceCount; i++) + { + for (int j = 0; j < options->sequenceData[i]->frameCount; j++) + { + options->animationResults[options->sequenceData[i]->frameData[j]->resultId]->resultType = options->sequenceData[i]->animationType; + } + } + int resultOffset = 0; + int lastSequence = 0; for (int i = 0; i < options->resultCount; i++) { - if (data[offset + 2] == 0xCC && data[offset + 3] == 0xCC) - { - options->animationResults[i]->resultType = 0; - } - else if (data[offset + 2] == 0xEF && data[offset + 3] == 0xBE) - { - options->animationResults[i]->resultType = 2; - } - else - { - options->animationResults[i]->resultType = 1; - } + // find the earliest sequence matching this animation result, + // and add padding if the sequence changes + the total offset is not 4-byte aligned. + bool found = false; for (int j = 0; j < options->sequenceCount; j++) { for (int k = 0; k < options->sequenceData[j]->frameCount; k++) { - if (options->sequenceData[j]->frameData[k]->resultOffset == resultOffset) + if (options->sequenceData[j]->frameData[k]->resultId == i) { - options->sequenceData[j]->frameData[k]->resultId = i; + if (lastSequence != j) + { + lastSequence = j; + if (resultOffset % 4 != 0) + { + resultOffset += 0x2; + offset += 0x2; + } + } + found = true; + break; } } + if (found) break; } switch (options->animationResults[i]->resultType) { case 0: //index options->animationResults[i]->index = data[offset] | (data[offset + 1] << 8); - resultOffset += 0x4; - offset += 0x4; + resultOffset += 0x2; + offset += 0x2; break; case 1: //SRT @@ -1454,6 +1468,9 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) } } + // add any missed padding from the final frame before processing labels + if (offset % 4 != 0) offset += 2; + if (options->labelEnabled) { options->labelCount = options->sequenceCount; //*should* be the same @@ -1479,17 +1496,60 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) unsigned int totalSize = 0x20 + options->sequenceCount * 0x10 + options->frameCount * 0x8; - //todo: check these for (int i = 0; i < options->resultCount; i++) { if (options->animationResults[i]->resultType == 0) - totalSize += 0x4; + totalSize += 0x2; else if (options->animationResults[i]->resultType == 1) totalSize += 0x10; else if (options->animationResults[i]->resultType == 2) totalSize += 0x8; } + // foreach sequence, need to check whether padding is applied for its results + // then add 0x02 to totalSize if padding exists. + // padding exists if the animation results for that sequence are not 4-byte aligned. + // also flag the last result for the sequence with `padded` to save having to redo this same step later. + int *usedResults = malloc(sizeof(int) * options->frameCount); + memset(usedResults, -1, sizeof(int) * options->frameCount); + for (int i = 0; i < options->sequenceCount; i++) + { + int sequenceLen = 0; + int resultIndex = 0; + for (int j = 0; j < options->sequenceData[i]->frameCount; j++) + { + // check if the result has already been used + for (resultIndex = 0; resultIndex < options->resultCount; resultIndex++) + { + if (usedResults[resultIndex] == options->sequenceData[i]->frameData[j]->resultId) + { + break; + } + + // if not already used, add it to the list + if (usedResults[resultIndex] == -1) + { + usedResults[resultIndex] = options->sequenceData[i]->frameData[j]->resultId; + break; + } + } + + // if not already used, add it to the result size for the sequence + if (options->animationResults[resultIndex]->resultType == 0) + sequenceLen += 0x2; + else if (options->animationResults[resultIndex]->resultType == 1) + sequenceLen += 0x10; + else if (options->animationResults[resultIndex]->resultType == 2) + sequenceLen += 0x8; + } + if (sequenceLen % 4 != 0) + { + totalSize += 0x02; + // mark the last animationResult for the sequence as padded, this saves needing to check this again later + options->animationResults[resultIndex]->padded = true; + } + } + unsigned int KNBASize = totalSize; if (options->labelEnabled) @@ -1547,10 +1607,10 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) KBNAContents[i + 1] = options->sequenceData[i / 0x10]->frameCount >> 8; KBNAContents[i + 2] = options->sequenceData[i / 0x10]->loopStartFrame & 0xff; KBNAContents[i + 3] = options->sequenceData[i / 0x10]->loopStartFrame >> 8; - KBNAContents[i + 4] = options->sequenceData[i / 0x10]->animationElement & 0xff; - KBNAContents[i + 5] = options->sequenceData[i / 0x10]->animationElement >> 8; - KBNAContents[i + 6] = options->sequenceData[i / 0x10]->animationType & 0xff; - KBNAContents[i + 7] = options->sequenceData[i / 0x10]->animationType >> 8; + KBNAContents[i + 4] = options->sequenceData[i / 0x10]->animationType & 0xff; + KBNAContents[i + 5] = (options->sequenceData[i / 0x10]->animationType >> 8) & 0xff; + KBNAContents[i + 6] = options->sequenceData[i / 0x10]->animationType2 & 0xff; + KBNAContents[i + 7] = (options->sequenceData[i / 0x10]->animationType2 >> 8) & 0xff; KBNAContents[i + 8] = options->sequenceData[i / 0x10]->playbackMode & 0xff; KBNAContents[i + 9] = (options->sequenceData[i / 0x10]->playbackMode >> 8) & 0xff; KBNAContents[i + 10] = (options->sequenceData[i / 0x10]->playbackMode >> 16) & 0xff; @@ -1570,11 +1630,13 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) int resPtr = 0; for (int l = 0; l < options->sequenceData[m]->frameData[k]->resultId; l++) { if (options->animationResults[l]->resultType == 0) - resPtr += 0x4; + resPtr += 0x2; else if (options->animationResults[l]->resultType == 1) resPtr += 0x10; else if (options->animationResults[l]->resultType == 2) resPtr += 0x8; + + if (options->animationResults[l]->padded) resPtr += 0x02; } KBNAContents[j + (k * 8)] = resPtr & 0xff; KBNAContents[j + (k * 8) + 1] = (resPtr >> 8) & 0xff; @@ -1588,7 +1650,6 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) j += options->sequenceData[m]->frameCount * 8; } - //todo: these are extrapolated, need confirming int resPtrCounter = j; for (int k = 0; k < options->resultCount; k++) { @@ -1597,9 +1658,7 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) case 0: KBNAContents[resPtrCounter] = options->animationResults[k]->index & 0xff; KBNAContents[resPtrCounter + 1] = options->animationResults[k]->index >> 8; - KBNAContents[resPtrCounter + 2] = 0xCC; - KBNAContents[resPtrCounter + 3] = 0xCC; - resPtrCounter += 0x4; + resPtrCounter += 0x2; break; case 1: @@ -1634,6 +1693,14 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) resPtrCounter += 0x8; break; } + + // use the `padded` flag which was stored earlier to inject padding + if (options->animationResults[k]->padded) + { + KBNAContents[resPtrCounter] = 0xCC; + KBNAContents[resPtrCounter + 1] = 0xCC; + resPtrCounter += 0x2; + } } fwrite(KBNAContents, 1, contentsSize, fp); diff --git a/json.c b/json.c index 12bc8a9..772bcc9 100644 --- a/json.c +++ b/json.c @@ -376,14 +376,14 @@ struct JsonToAnimationOptions *ParseNANRJson(char *path) cJSON *frameCount = cJSON_GetObjectItemCaseSensitive(sequence, "frameCount"); cJSON *loopStartFrame = cJSON_GetObjectItemCaseSensitive(sequence, "loopStartFrame"); - cJSON *animationElement = cJSON_GetObjectItemCaseSensitive(sequence, "animationElement"); cJSON *animationType = cJSON_GetObjectItemCaseSensitive(sequence, "animationType"); + cJSON *animationType2 = cJSON_GetObjectItemCaseSensitive(sequence, "animationType2"); cJSON *playbackMode = cJSON_GetObjectItemCaseSensitive(sequence, "playbackMode"); options->sequenceData[i]->frameCount = GetInt(frameCount); options->sequenceData[i]->loopStartFrame = GetInt(loopStartFrame); - options->sequenceData[i]->animationElement = GetInt(animationElement); options->sequenceData[i]->animationType = GetInt(animationType); + options->sequenceData[i]->animationType2 = GetInt(animationType2); options->sequenceData[i]->playbackMode = GetInt(playbackMode); options->sequenceData[i]->frameData = malloc(sizeof(struct FrameData *) * options->sequenceData[i]->frameCount); @@ -521,8 +521,8 @@ char *GetNANRJson(struct JsonToAnimationOptions *options) cJSON *sequence = cJSON_CreateObject(); cJSON_AddNumberToObject(sequence, "frameCount", options->sequenceData[i]->frameCount); cJSON_AddNumberToObject(sequence, "loopStartFrame", options->sequenceData[i]->loopStartFrame); - cJSON_AddNumberToObject(sequence, "animationElement", options->sequenceData[i]->animationElement); cJSON_AddNumberToObject(sequence, "animationType", options->sequenceData[i]->animationType); + cJSON_AddNumberToObject(sequence, "animationType2", options->sequenceData[i]->animationType2); cJSON_AddNumberToObject(sequence, "playbackMode", options->sequenceData[i]->playbackMode); cJSON *frameData = cJSON_AddArrayToObject(sequence, "frameData"); diff --git a/options.h b/options.h index 4304f1e..d0870ea 100644 --- a/options.h +++ b/options.h @@ -123,8 +123,8 @@ struct FrameData { struct SequenceData { short frameCount; short loopStartFrame; - short animationElement; short animationType; + short animationType2; int playbackMode; struct FrameData **frameData; }; @@ -147,6 +147,7 @@ struct AnimationDataT { struct AnimationResults { short resultType; + bool padded; union { short index; struct AnimationDataSRT dataSrt; From 4433b68feb08949018d6575e1803b7853f3131f3 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sun, 20 Oct 2024 13:20:07 -0500 Subject: [PATCH 2/5] missed checking whether result was already used before increasing sequenceLen --- gfx.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/gfx.c b/gfx.c index c4db346..f8589ca 100644 --- a/gfx.c +++ b/gfx.c @@ -1519,10 +1519,12 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) for (int j = 0; j < options->sequenceData[i]->frameCount; j++) { // check if the result has already been used + bool isUsed = false; for (resultIndex = 0; resultIndex < options->resultCount; resultIndex++) { if (usedResults[resultIndex] == options->sequenceData[i]->frameData[j]->resultId) { + isUsed = true; break; } @@ -1535,12 +1537,15 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) } // if not already used, add it to the result size for the sequence - if (options->animationResults[resultIndex]->resultType == 0) - sequenceLen += 0x2; - else if (options->animationResults[resultIndex]->resultType == 1) - sequenceLen += 0x10; - else if (options->animationResults[resultIndex]->resultType == 2) - sequenceLen += 0x8; + if (!isUsed) + { + if (options->animationResults[resultIndex]->resultType == 0) + sequenceLen += 0x2; + else if (options->animationResults[resultIndex]->resultType == 1) + sequenceLen += 0x10; + else if (options->animationResults[resultIndex]->resultType == 2) + sequenceLen += 0x8; + } } if (sequenceLen % 4 != 0) { From 4d1609732bcf8618f367cb43e9d29f49fec5516b Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sun, 27 Oct 2024 18:56:21 -0500 Subject: [PATCH 3/5] undo changes to animationElement/animationType fields --- gfx.c | 16 ++++++++-------- json.c | 6 +++--- options.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gfx.c b/gfx.c index f8589ca..99846e6 100644 --- a/gfx.c +++ b/gfx.c @@ -1327,8 +1327,8 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) { options->sequenceData[i]->frameCount = data[offset] | (data[offset + 1] << 8); options->sequenceData[i]->loopStartFrame = data[offset + 2] | (data[offset + 3] << 8); - options->sequenceData[i]->animationType = data[offset + 4] | (data[offset + 5] << 8); - options->sequenceData[i]->animationType2 = data[offset + 6] | (data[offset + 7] << 8); + options->sequenceData[i]->animationElement = data[offset + 4] | (data[offset + 5] << 8); + options->sequenceData[i]->animationType = data[offset + 6] | (data[offset + 7] << 8); options->sequenceData[i]->playbackMode = data[offset + 8] | (data[offset + 9] << 8) | (data[offset + 10] << 16) | (data[offset + 11] << 24); frameOffsets[i] = data[offset + 12] | (data[offset + 13] << 8) | (data[offset + 14] << 16) | (data[offset + 15] << 24); @@ -1402,12 +1402,12 @@ void ReadNtrAnimation(char *path, struct JsonToAnimationOptions *options) options->animationResults[i] = malloc(sizeof(struct AnimationResults)); } - // store the animationType of the corresponding sequence as this result's resultType + // store the animationElement of the corresponding sequence as this result's resultType for (int i = 0; i < options->sequenceCount; i++) { for (int j = 0; j < options->sequenceData[i]->frameCount; j++) { - options->animationResults[options->sequenceData[i]->frameData[j]->resultId]->resultType = options->sequenceData[i]->animationType; + options->animationResults[options->sequenceData[i]->frameData[j]->resultId]->resultType = options->sequenceData[i]->animationElement; } } @@ -1612,10 +1612,10 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) KBNAContents[i + 1] = options->sequenceData[i / 0x10]->frameCount >> 8; KBNAContents[i + 2] = options->sequenceData[i / 0x10]->loopStartFrame & 0xff; KBNAContents[i + 3] = options->sequenceData[i / 0x10]->loopStartFrame >> 8; - KBNAContents[i + 4] = options->sequenceData[i / 0x10]->animationType & 0xff; - KBNAContents[i + 5] = (options->sequenceData[i / 0x10]->animationType >> 8) & 0xff; - KBNAContents[i + 6] = options->sequenceData[i / 0x10]->animationType2 & 0xff; - KBNAContents[i + 7] = (options->sequenceData[i / 0x10]->animationType2 >> 8) & 0xff; + KBNAContents[i + 4] = options->sequenceData[i / 0x10]->animationElement & 0xff; + KBNAContents[i + 5] = (options->sequenceData[i / 0x10]->animationElement >> 8) & 0xff; + KBNAContents[i + 6] = options->sequenceData[i / 0x10]->animationType & 0xff; + KBNAContents[i + 7] = (options->sequenceData[i / 0x10]->animationType >> 8) & 0xff; KBNAContents[i + 8] = options->sequenceData[i / 0x10]->playbackMode & 0xff; KBNAContents[i + 9] = (options->sequenceData[i / 0x10]->playbackMode >> 8) & 0xff; KBNAContents[i + 10] = (options->sequenceData[i / 0x10]->playbackMode >> 16) & 0xff; diff --git a/json.c b/json.c index 772bcc9..12bc8a9 100644 --- a/json.c +++ b/json.c @@ -376,14 +376,14 @@ struct JsonToAnimationOptions *ParseNANRJson(char *path) cJSON *frameCount = cJSON_GetObjectItemCaseSensitive(sequence, "frameCount"); cJSON *loopStartFrame = cJSON_GetObjectItemCaseSensitive(sequence, "loopStartFrame"); + cJSON *animationElement = cJSON_GetObjectItemCaseSensitive(sequence, "animationElement"); cJSON *animationType = cJSON_GetObjectItemCaseSensitive(sequence, "animationType"); - cJSON *animationType2 = cJSON_GetObjectItemCaseSensitive(sequence, "animationType2"); cJSON *playbackMode = cJSON_GetObjectItemCaseSensitive(sequence, "playbackMode"); options->sequenceData[i]->frameCount = GetInt(frameCount); options->sequenceData[i]->loopStartFrame = GetInt(loopStartFrame); + options->sequenceData[i]->animationElement = GetInt(animationElement); options->sequenceData[i]->animationType = GetInt(animationType); - options->sequenceData[i]->animationType2 = GetInt(animationType2); options->sequenceData[i]->playbackMode = GetInt(playbackMode); options->sequenceData[i]->frameData = malloc(sizeof(struct FrameData *) * options->sequenceData[i]->frameCount); @@ -521,8 +521,8 @@ char *GetNANRJson(struct JsonToAnimationOptions *options) cJSON *sequence = cJSON_CreateObject(); cJSON_AddNumberToObject(sequence, "frameCount", options->sequenceData[i]->frameCount); cJSON_AddNumberToObject(sequence, "loopStartFrame", options->sequenceData[i]->loopStartFrame); + cJSON_AddNumberToObject(sequence, "animationElement", options->sequenceData[i]->animationElement); cJSON_AddNumberToObject(sequence, "animationType", options->sequenceData[i]->animationType); - cJSON_AddNumberToObject(sequence, "animationType2", options->sequenceData[i]->animationType2); cJSON_AddNumberToObject(sequence, "playbackMode", options->sequenceData[i]->playbackMode); cJSON *frameData = cJSON_AddArrayToObject(sequence, "frameData"); diff --git a/options.h b/options.h index d0870ea..867a204 100644 --- a/options.h +++ b/options.h @@ -123,8 +123,8 @@ struct FrameData { struct SequenceData { short frameCount; short loopStartFrame; + short animationElement; short animationType; - short animationType2; int playbackMode; struct FrameData **frameData; }; From 112fce2031caa20825d62d66de434e6a4214db96 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sun, 27 Oct 2024 19:49:22 -0500 Subject: [PATCH 4/5] add missing free for usedResults --- gfx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gfx.c b/gfx.c index 99846e6..73a8cce 100644 --- a/gfx.c +++ b/gfx.c @@ -1555,6 +1555,8 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) } } + free(usedResults); + unsigned int KNBASize = totalSize; if (options->labelEnabled) From 17cb8623f4d243c24afdd0aa6a6ed9209210806c Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Mon, 28 Oct 2024 12:57:13 -0500 Subject: [PATCH 5/5] apply padding to the correct animation result index when results are reused between sequences --- gfx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gfx.c b/gfx.c index 73a8cce..ccd7ca2 100644 --- a/gfx.c +++ b/gfx.c @@ -1516,6 +1516,7 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) { int sequenceLen = 0; int resultIndex = 0; + int lastNewResultIndex = -1; for (int j = 0; j < options->sequenceData[i]->frameCount; j++) { // check if the result has already been used @@ -1532,6 +1533,7 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) if (usedResults[resultIndex] == -1) { usedResults[resultIndex] = options->sequenceData[i]->frameData[j]->resultId; + lastNewResultIndex = options->sequenceData[i]->frameData[j]->resultId; break; } } @@ -1547,11 +1549,11 @@ void WriteNtrAnimation(char *path, struct JsonToAnimationOptions *options) sequenceLen += 0x8; } } - if (sequenceLen % 4 != 0) + if (sequenceLen % 4 != 0 && lastNewResultIndex != -1) { totalSize += 0x02; - // mark the last animationResult for the sequence as padded, this saves needing to check this again later - options->animationResults[resultIndex]->padded = true; + // mark the last new animationResult index for the sequence as padded, this saves needing to check this again later + options->animationResults[lastNewResultIndex]->padded = true; } }