Author Topic: Can we add new animations?  (Read 419 times)

james_c

  • Fast newbie
  • *
  • Posts: 9
  • Karma: 1
    • View Profile
Can we add new animations?
« on: 2018-12-27 01:16:37 »
Does anyone know if it's possible to add a whole new animation into the game (with kimara?). I wanted to give Cid a jump like a dragoon but would need new animations for it.

quantumpencil

  • Fast newbie
  • *
  • Posts: 37
  • Karma: 5
    • View Profile
Re: Can we add new animations?
« Reply #1 on: 2018-12-27 01:27:07 »
Of course it's possible, this is just a program, and we can inject an arbitrary amount of Turing complete code into it so in theory it's possible =p.

I think it's practical as well. I recently hacked the game using DLL-injection to run a new animation script under specific conditions for my burn effect. (Simplest possible script, E8 EC FE =p)

There's really only two steps beyond that needed to do what you're talking about:

1: Someone needs to reverse engineer the structure of the animations themselves in the **da files so that a new "animation" can be created.
2: Someone needs to figure out how the game "looks up" a given animation that appears in an animation script (Where the **da files get loaded and where the pointers to the correct indexes get stored) which is just a matter of doing some more reversing work on the function that runs the animation scripts.

Then you just need to malloc a new struct to holding the custom animation you want (the *da clone data) and rewrite the opcode handler (The animation script doesn't actually think of low indexes as opcodes, so the "byte" of the specific model animation) to look up that index at your memory address instead of where it usually goes.

Depending on generated x86 that could either be super easy (If the opcode/handler for animations is a different function, you can just replace/redirect the function, which is the easiest way) or more difficult (might have to patch the raw ASM to make space for a call to a new function that does what you want).
« Last Edit: 2018-12-27 01:34:29 by quantumpencil »

james_c

  • Fast newbie
  • *
  • Posts: 9
  • Karma: 1
    • View Profile
Re: Can we add new animations?
« Reply #2 on: 2018-12-27 01:42:14 »
Seriously? That's awesome.

I didnt get everything you said, but it sounds dope. If I made an animation for jump, could you/would you be able to help me get it working in the game?

quantumpencil

  • Fast newbie
  • *
  • Posts: 37
  • Karma: 5
    • View Profile
Re: Can we add new animations?
« Reply #3 on: 2018-12-27 01:58:36 »
Yes, I could probably do that.

You'll need more than one animation though. I would suggest the following at a minimum:

1: Take the animation where Cid changes into the back row as a base (03). Edit that animation in Kimera to look like a "jump" animation and make sure that he jumps really high off screen when it is used.
2: Take the animation where Cid changes into the front row as a base (04). Edit that animation to start up in the sky and "jump" back down to the ground.

That's all you need in theory. We can use a script like the change/row script with the opcodes that change/return model location(don't remember which ones, but they're in my disassembly) to have Cid jump off screen. Then we can flag him as not able to be targeted, and set a jump bit somewhere in his AI battle data. Whenever that bit is set, his just need to write a main-script that enqueues a battle command which uses Throw animation to have him rain down some lances on people by setting the data we want prior to the execution of the command. We can run the return to ground script after a certain number of C or V-Timer ticks (I'm doing this for burn already)

I wouldn't know how to create an entirely new command (I could replace one you don't care about and rename it "jump" on the menu) -- that might be possible too but I haven't looked into doing something like that yet.

Tsunamix

  • Freak
  • *
  • Posts: 742
  • Karma: 16
  • Check out my work - https://goo.gl/m4NFlm
    • View Profile
    • The home of Tsunamods
Re: Can we add new animations?
« Reply #4 on: 2018-12-27 16:18:32 »
If you can actually add a brand new animation in then may i suggest if you ever time to add a real Death animation?
So that when HP hits 0 they dont just teleport to the ground but actually fall. Might need 2 of them realism sake, 1 for going from good HP to 0 (Standing) and one from low HP to 0 (Crouching)

I have kind of done this myself by just altering the existing death animation but the issue is when you escape from battle they drop again :D  along with a few other instances. Would add a nice realism affect to the game

quantumpencil

  • Fast newbie
  • *
  • Posts: 37
  • Karma: 5
    • View Profile
Re: Can we add new animations?
« Reply #5 on: 2018-12-28 08:07:24 »
Would definitely be cool. I'm going to go ahead and try this in the coming weeks, my first test-case will by my float status effect for my upcoming tactical elements mod.

Ideally I'll be able to play the characters idle animations but transpose them up by a certain amount by adding a new animation per character of them "floating" up and "floating" down which is essentially the same thing the OP asked for but I'm not gonna do a bunch of work on the animation aspect (Animation will literally just be them moving straight up in idle).

If I get that working I will of course post and update and share my findings, any of these ideas are possible at that point.

Tsunamix

  • Freak
  • *
  • Posts: 742
  • Karma: 16
  • Check out my work - https://goo.gl/m4NFlm
    • View Profile
    • The home of Tsunamods
Re: Can we add new animations?
« Reply #6 on: 2018-12-28 11:42:04 »
Oh man if you can figure out how to do this stuff it'll be awesome. I've wanted to be able to do it forever now

quantumpencil

  • Fast newbie
  • *
  • Posts: 37
  • Karma: 5
    • View Profile
Re: Can we add new animations?
« Reply #7 on: 2018-12-28 18:01:51 »
Hardest part is going to be figuring out the animation structure and mapping it, I think. Kimara must already know this so its source code should help, but I need to figure out what chunk of **da data is getting copied/indexed by FF7. If the process is dynamic or based on metadata in the other model files, then we might be able to just extend Kimera to create new animations instead of having to malloc new memory specifically for a given animation -- and the game will just load them and convert

This would really be preferable, because the game dynamically loads/indexes model animations in battle based on the models present. From what I've seen so far, I think it can load a dynamic number of animations per model based on the **da file structures (which I don't understand yet).

Once they're loaded with a new index, we're done =p.

Kaldarasha

  • No life
  • *
  • Posts: 2281
  • Karma: 137
  • Prince of Model Editing
    • View Profile
Re: Can we add new animations?
« Reply #8 on: 2018-12-28 20:39:03 »
The **ba file is the second info file of a model. I accidentally had overwritten the Scorpion model with the one of Tifa. Was funny to fight against her.
The **ba file has probably the info about the id for the animations in the da file.

ergonomy_joe

  • Fast newbie
  • *
  • Posts: 17
  • Karma: 6
    • View Profile
Re: Can we add new animations?
« Reply #9 on: 2019-01-07 05:53:50 »
Hi there, I'm trying to undersand the **DA and **BA naming.

concerning the former, it seems that the file name is obfuscated from ".Axx" (xx being a number) filenames.
For example, "CLOUD.A00" is obfuscated into "RTDA".
The A stands for "amp" I guess (I got that from the source code debug info: amptoanm.cpp).
I found the code that parses those files but it is hard to read. I may post some info on the format later.

As for the **BA files I'm not sure because I could not find a runtime example.
They look very close to Polygon files, and there is an obfuscation for ".Pxx" files, but they do not end as **BA.
example: "CLOUD.P01" becomes "RTAN"

Here is my decompiled version of the obfuscation function at 0x005E2460:
Code: [Select]
//obfuscate name?
void C_005E2460(int dwCount/*bp08*/, struct t_battle_obfuscateInfo *bp0c, const char *pFName/*bp10*/, char *pObfuscated/*bp14*/) {
struct {
int dwDotIndex;//bp_ec
int dwLen;//bp_e8
int bp_e4;
int bp_e0;
int dwIndex;//bp_dc
int bp_d8;
int dwBase;//bp_d4
int i;//bp_d0
int dwFoundInfo;//bp_cc
char bp_c8[200];
}lolo;

//-- init --
if(bp0c[1].dwStartIndex == 0)
C_005E23F6(dwCount, bp0c);//compute dwStartIndex?
//-- --
lolo.dwFoundInfo = 0;
for(lolo.i = 0; lolo.i < dwCount; lolo.i ++) {
if(*(int *)bp0c->pPrefix == *(int *)pFName) {//compare 4 first characters
if(bp0c->dwFullName) {//else 005E258C
C_00682C91(pFName, lolo.bp_c8);//file:remove file extension
if(strcmp(bp0c->pPrefix, lolo.bp_c8) == 0) {
lolo.dwFoundInfo = 1;
break;
}
} else {
lolo.dwFoundInfo = 1;
break;
}
}
bp0c ++;
}//end for
//-- --
if(lolo.dwFoundInfo) {//else 005E28D0
//-- --
lolo.dwIndex = bp0c->dwStartIndex;
if(bp0c->dwCodeLength > 0) {//else 005E2688
lolo.bp_d8 = 0;
for(lolo.i = 0; lolo.i < bp0c->dwCodeLength; lolo.i ++) {
lolo.bp_e4 = pFName[bp0c->dwFirstChar + bp0c->dwCodeLength - (lolo.i + 1)] - '0';
if(lolo.i == 0) {
lolo.bp_d8 = lolo.bp_e4;
lolo.bp_e0 = 10;
} else {
lolo.bp_d8 += lolo.bp_e4 * lolo.bp_e0;
lolo.bp_e0 *= 10;
}
}//end for
lolo.dwIndex += lolo.bp_d8;
}
//-- --
lolo.dwBase = 0;
lolo.dwDotIndex = 0;
lolo.dwLen = strlen(pFName);
if(lolo.dwLen > 0) {//else 005E26F8
for(lolo.dwDotIndex = lolo.dwLen - 1; lolo.dwDotIndex >= 0; lolo.dwDotIndex --) {
if(pFName[lolo.dwDotIndex] == '.')
break;
}//end for
}
switch(pFName[lolo.dwDotIndex + 1]) {
case 'D':
lolo.dwBase = 0;
break;
case 'B':
lolo.dwBase = 1;
break;
case 'T':
lolo.dwBase = 2;
lolo.dwBase += (pFName[lolo.dwDotIndex + 2] - '0') * 10;
lolo.dwBase += pFName[lolo.dwDotIndex + 3] - '0';
break;
case 'P':
lolo.dwBase = 12;
lolo.dwBase += (pFName[lolo.dwDotIndex + 2] - '0') * 10;
lolo.dwBase += pFName[lolo.dwDotIndex + 3] - '0';
break;
case 'W':
lolo.dwBase = 62;
lolo.dwBase += (pFName[lolo.dwDotIndex + 2] - '0') * 10;
lolo.dwBase += pFName[lolo.dwDotIndex + 3] - '0';
break;
case 'A':
lolo.dwBase = 78;
break;
default:
sprintf(lolo.bp_c8, /*008FF9D4*/"ERROR: EXTENSION NOT SUPPORTED %s \n", pFName);
C_00664E30(lolo.bp_c8);//dx_dbg:kind of puts?
}//end switch
//-- --
pObfuscated[0] = (lolo.dwIndex / 26) + 'A';
pObfuscated[1] = (lolo.dwIndex % 26) + 'A';
pObfuscated[2] = (lolo.dwBase / 26) + 'A';
pObfuscated[3] = (lolo.dwBase % 26) + 'A';
pObfuscated[4] = 0;
}
}

the argument passed as "bp0c" is the array at 0x008FF2F0:
Code: [Select]
struct t_battle_obfuscateInfo {//size 0x18
/*00*/const char *pPrefix;
/*04*/int dwStartIndex;
/*08*/int dwCodeLength;
/*0c*/int dwFirstChar;
/*10*/int dwElementCount;
/*14*/int dwFullName;
};

struct t_battle_obfuscateInfo D_008FF2F0[0x17] = {
{/*008FF900*/"ENEMY",   0,3,5,0x172,0},
{/*008FF908*/"STAGE",   0,2,5,0x5a,0},
{/*008FF910*/"FROG",    0,0,0,1,0},
{/*008FF918*/"CLOUD",   0,0,0,1,0},
{/*008FF920*/"TIFA",    0,0,0,1,0},
{/*008FF928*/"EARITH",  0,0,0,1,0},
{/*008FF930*/"RED13",   0,0,0,1,0},
{/*008FF938*/"YUFI",    0,0,0,1,0},
{/*008FF940*/"KETCY",   0,0,0,1,0},
{/*008FF948*/"CID1",    0,0,0,1,0},
{/*008FF950*/"SEFIROS", 0,0,0,1,0},
{/*008FF958*/"BARRETT", 0,0,0,1,1},
{/*008FF960*/"BARRETT2",0,0,0,1,1},
{/*008FF96C*/"BARRETT3",0,0,0,1,1},
{/*008FF978*/"BARRETT4",0,0,0,1,0},
{/*008FF984*/"VINSENT", 0,0,0,1,1},
{/*008FF98C*/"VINSENT2",0,0,0,1,1},
{/*008FF998*/"VINSENT3",0,0,0,1,0},
{/*008FF9A4*/"HICLOUD", 0,0,0,1,0},
{/*008FF9AC*/"GALL",    0,0,0,1,0},
{/*008FF9B4*/"DEATHGIG",0,0,0,1,0},
{/*008FF9C0*/"HELLMASK",0,0,0,1,0},
{/*008FF9CC*/"CHAOS",   0,0,0,1,0}
};
« Last Edit: 2019-01-08 00:01:44 by ergonomy_joe »

ergonomy_joe

  • Fast newbie
  • *
  • Posts: 17
  • Karma: 6
    • View Profile
Re: Can we add new animations?
« Reply #10 on: 2019-01-08 00:17:12 »
Here is a first draft of the format of the "amp" files:
Code: [Select]
u32 animationCount;and then, for each animation:
Code: [Select]
u32 boneCount;
u32 frameCount;
u32 dataSize;
Then follows the data (which size is dataSize, you use this size to skip to the next animation):
The data starts with a 5 bytes header:
Code: [Select]
u16 frameCount_bis;//save as frameCount
u16 size;//= dataSize +7
u8 rotValueShift;//can also be understood as "number of 0s"
What follows is the actual data, but the unit is not byte, it is bit. It's a little hard to explain so here it is:
Code: [Select]
//format for the first frame:
s16 posX,posY,posZ;//since data is a bitstream, there are to be read as BigEndian
sMax12 bone0_rotX,bone0_rotY,bone0_rotZ;
sMax12 bone1_rotX,bone1_rotY,bone1_rotZ;
//...
and the format for the remaining frames:
Code: [Select]
s7_16 transX,transY,transZ;
special bone0_rotX,bone0_rotY,bone0_rotZ;
special bone1_rotX,bone1_rotY,bone1_rotZ;
//...

"sMax12" means that the data is "up to 12 bits long". The actual size (in bits) is "12 - rotValueShift". And the read data must be shifted left by rotValueShift bits.
"s7_16" is a little trickier. You read one bit. If its value is 0, you read the next 7 bits. If it is 1, you read the next 16 bits.
"special" is beyond any possible explanation for me so here is the code that parses it (note that the next 3 bits are read and used to determine the type of data that follows):
Code: [Select]
//read "special" value from buffer?
short C_005E7CE4(unsigned char *bp08, int *pBitIndex/*bp0c*/, int dwRotShift/*bp10*/) {
struct {
int bp_08;
int dwValue;//bp_04
}lolo;

if(C_005E7C40(bp08, pBitIndex, 1) == 0)//read value from buffer?
return 0;
lolo.bp_08 = C_005E7C40(bp08, pBitIndex, 3) & 7;//read value from buffer?
switch(lolo.bp_08) {
case 0:
return 0xffffffff << dwRotShift;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
lolo.dwValue = C_005E7C40(bp08, pBitIndex, lolo.bp_08);//read value from buffer?
if(lolo.dwValue >= 0)
lolo.dwValue += BIT(lolo.bp_08 - 1);
else
lolo.dwValue -= BIT(lolo.bp_08 - 1);
return lolo.dwValue << dwRotShift;
case 7:
lolo.dwValue = C_005E7C40(bp08, pBitIndex, 12 - dwRotShift);//read value from buffer?
return lolo.dwValue << dwRotShift;
}//end switch

return 0;
}
"bp08" is a pointer to the data just after the "5 byte long" header.
"pBitIndex" is a pointer to the current index (in bit) in the data.