Open Drahsid opened 2 months ago
Hmm not sure where I'd start on bindings. I've never really looked into input stuff much. Given this is a relatively undocumented engine (modified from FFXIV's), I'm not where we'd begin to look 🤔.
If it's anything like ffxiv, for kb/m there would be various nearly functions for checking the key state, which follow this enum, and a relatively undocumented keybind id enum.
Though the code generation for ffxvi is very different from ffxiv, so unfortunately I don't think it's likely we could even closely match any of the same sigs.
I suppose the best way to approach this would be to actually find the memory relating to the eikonic abilities, and to start reverse engineering upward from there. Maybe seeing where the game resets abilities' cooldowns would be a good start.
Now that the full release is out, I'm gonna start looking into this. Do you have analysis files you can share?
Now that the full release is out, I'm gonna start looking into this. Do you have analysis files you can share?
I don't have anything too useful. Just a CDC file from the Cheat Engine code dissection. Trying to load the game exe in IDA takes so bloody long to analyse that I gave up with that approach (thanks Denuvo).
The only other thing of note is FF16Tools which can be used to unpack game assets. In my own searching through game memory and the unpacked assets I haven't found anything related to keymaps or bindings unfortunately. I'll keep taking a look here and there though, sometimes this stuff just hits you when you've stepped away for a bit.
I've poked around with the key bindings menu and if you access the address in the cmp instruction here you get a list of keyboard/mouse keymappings when you attempt to bind a key.
ffxvi.exe+3597C4 - 48 8D 04 40 - lea rax,[rax+rax*2]
>> ffxvi.exe+3597C8 - 45 39 54 81 08 - cmp [r9+rax*4+08],r10d
ffxvi.exe+3597CD - 74 13 - je ffxvi.exe+3597E2
ffxvi.exe+3597CF - 41 FF C0 - inc r8d
ffxvi.exe+3597D2 - 41 8B C0 - mov eax,r8d
ffxvi.exe+3597D5 - 48 3B D0 - cmp rdx,rax
In a quick test, the B
key is 66 (0x42) and the G
key is 71 (0x47). That seems to map with the enum that you posted before.
Trying to load the game exe in IDA takes so bloody long to analyse that I gave up with that approach (thanks Denuvo).
Ah yeah it took like 12+ hours for me. I hate it.
I've poked around with the key bindings menu and if you access the address in the cmp instruction here you get a list of keyboard/mouse keymappings when you attempt to bind a key.
This looks like a solid start. I'll see what I can find with this.
Some quick pseudocode of that function:
typedef {
/* 0x00 */ int Unk_0x00;
/* 0x04 */ int Unk_0x04;
/* 0x08 */ int KeyCode;
} UnkInputEntry; /* sizeof = 0x0C */
typedef void (*UnkInputStruct_Struct0x10_Unk_0x30Pfn)(UnkInputStruct_Struct0x10* thisx, UnkInputStruct* input, int arg3, int keyCode, int index);
typedef {
/* 0x30 */ UnkInputStruct_Struct0x10_Unk_0x30Pfn* Unk_0x30;
} UnkInputStruct_Struct0x10; /* sizeof is at least 0x38 */
typedef {
/* 0x08 */ UnkInputEntry* UnkStart; // head/tail?
/* 0x10 */ UnkInputEntry* UnkEnd;
} UnkInputStruct_Struct0x138; /* sizeof is at least 0x18 */
typedef void (*UnkInputStruct_Unk_0x80Pfn)(UnkInputStruct_Struct0x10* thisx);
typedef {
/* 0x010 */ UnkInputStruct_Struct0x10** Unk_0x10;
/* 0x080 */ UnkInputStruct_Unk_0x80Pfn Unk_0x80;
/* 0x108 */ int Unk_0x108;
/* 0x138 */ UnkInputStruct_Struct0x138* Unk_0x138;
/* 0x140 */ int Unk_Index;
/* 0x25C */ unsigned char Unk_0x25C;
} UnkInputStruct; /* sizeof is at least 0x260 */
int Unk_GetKeybind(UnkInputStruct* arg1, int keyCode) {
UnkInputEntry* pStart = arg1->Unk_0x138->Unk_Start;
int count = (arg1->Unk_0x138->Unk_End - pStart) / sizeof(UnkInputEntry);
if (count == 0) {
Sub_140246938(); // "Invalid vector subscript", then int3
}
for (int index = 0; index < count; index++) {
if (pStart[index].KeyCode == keyCode) {
return index;
}
}
return -1;
}
Alright, following the trail, these two instructions set the value that is actually responsible for the cycle eikon input:
ffxvi.exe+32CF95 - C5F81184 CB 30 050000 - vmovups [rbx+rcx*8+00000530],xmm0
ffxvi.exe+32CF9E - 89 94 CB 30050000 - mov [rbx+rcx*8+00000530],edx
This is in Sub 14032CE8C, the sig of which is 48 89 4c 24 ?? 53 55 56 57 41 54 41 55 41 56 41 57 48 83 ec ?? 48 8b ?? ?? ?? ?? ?? 45 33 e4 48 8b b1
This looks pretty similar to some of the input stuff from FFXIV, so I think I'm on the right track here.
Another update. The following code checks if the Cycle Eikon key is in the pressed state, and subsequently performs the Cycle Eikon Behavior:
ffxvi.exe+77E6B1 - E8 E213B1FF - call ffxvi.exe+28FA98
ffxvi.exe+77E6B6 - 84 C0 - test al,al
...
ffxvi.exe+77E6BD - E8 8E0B0000 - call ffxvi.exe+77F250
The call ffxvi.exe+28FA98
is the function which checks if the key is pressed, which uses the data from before, and follows this structure:
typedef {
/* 0x00 */ union {
short KeyCodeShort;
int KeyCode; // sometimes accessed as an int
};
/* 0x04 */ int KeyPressedTime; // the time in which the key was last pressed. Does not reset to 0.
/* 0x08 */ unsigned char Unk_0x08; // flags?
/* 0x09 */ unsigned char Unk_0x09; // flags?
/* 0x0A */ unsigned char KeyPressed; // key was pressed this frame
/* 0x0B */ unsigned char KeyReleased; // key was released this frame
/* 0x0C */ unsigned char KeyDown; // key is currently held down
/* 0x0D */ unsigned char Unk_0x0D;
/* 0x0E */ unsigned char Unk_0x0E;
/* 0x0F */ unsigned char Unk_0x0F;
} KeyStateEntry; /* sizeof = 0x10 */
The subsequent function call to Sub 14077f250 (sig 48 89 5c 24 ?? 48 89 6c 24 ?? 48 89 74 24 ?? 57 48 83 ec ?? 48 8b ?? ? ?? ?? ?? 48 33 c4 48 89 ?? ?? ?? ?? ?? ?? ?? ?? ?? 33 db
is the function which cycles the eikon.
Here is some quick pseudocode of the Cycle Eikon function:
typedef struct {
/* 0x146A8 */ void* EquippedEikons[3];
/* 0x146C0 */ int CurrentEikon;
} UnkGameState;
extern unsigned long long DAT_141711048; /*0x00002B992DDFA232*/
// arg1 some unknown struct
void CycleEikon(unsigned long long arg1) {
uint validEikonCount;
uint nextEikon;
UnkGameState* gameState;
unsigned char stack_48[32];
uint eikonOrder[4];
unsigned long long stack_10;
stack_10 = DAT_141711048 ^ (unsigned long long)stack_48;
gameState = (UnkGameState*)((unsigned long long)(*(uint*)(arg1 + 0x16530) >> 2 & 1) * 0x20 + arg1);
for (uint slot = 0; slot < 3; slot++) {
uint nextEikon = (gameState->CurrentEikon + slot) % 3;
if (gameState->EquippedEikons[nextEikon] != 0) {
if (Sub_14077c434(gameState->EquippedEikons[nextEikon]) != 0) {
eikonOrder[validEikonCount] = nextEikon;
validEikonCount++;
}
}
}
if (validEikonCount < 2) {
eikonOrder[1] = gameState->CurrentEikon;
}
Sub_14077efb4(arg1, eikonOrder[1], 0);
Sub_140c4d5d0(stack_10 ^ stack_48);
return;
}
Just saw this today. I had actually been doing reverse engineering to find to framerate cap, looks like you beat me! Nice work.
Anyway, pertaining to this suggestion, by default to use your eikonic abilities you need to switch the currently equipped eikon, then press the left/right button which the skill is assigned to. Much like in DOOM Eternal, which had the player use a switch button for the different grenades, this feels awful. I think this could ideally become 6 separate binds (2 for each equipped eikon), and if you use an ability of an eikon which is not currently equipped, it would automatically switch to that eikon.
I haven't touched anything relating to input in my reverse engineering at the moment, so I don't have any info to jumpstart this, but I think it'd be a nice feature.