Qhimm.com Forums
Miscellaneous Forums => Scripting and Reverse Engineering => Topic started by: myst6re on 2016-04-14 20:28:28
-
Hello everyone! I need your help this time: In dat files, monster stats are represented by 4 numbers, which are used in an equation with monster level. Does anyone know the right equation for each monster attribute?
What we know (from Ifrit editor):
HP = Math.floor(number_0 * level * level / 20) + number_1 * level + number_2
EXP = number_0 * (5 * (level - level_party_avg)/level_party_avg + 4))
EXP_LAST = Math.floor(number_1 * (5 * (level - level_party_last)/level_party_last + 4))
# If the result > 255, STR = 255
STR = level * number_0 / 10 + level / number_1 - level / number_3 + number_2) / 4
MAG = Math.floor(level * number_0) + Math.floor(level/number_1) - Math.floor(Math.floor(Math.floor(level*level/2)/number_2)/4))
VIT = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
SPR = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
SPD = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
EVA = level * number_0 + Math.floor(level/number_1) + number_2 - Math.floor(level/number_3)
I think HP and EXP are good. For the rest...
-
Edit: Okay, so I'll preface this by saying sometimes they match the Ultimania stats (which give stats at Lv1, every ten levels, and the max level), sometimes they are way off. I do know that some changes were made from JP>EN so this could be a product of that. Also I'm suspicious of my math.floors, I may have added them at random to see what gave me the right results.
Here's the formulas I used (in Lua) for the FFWiki, probably with more brackets than I need. You can see the results of these and judge whether they are correct on the individual enemy articles on the wiki: example (http://finalfantasy.wikia.com/wiki/Iron_Giant_%28Final_Fantasy_VIII%29).
STR, MAG:
local calc = math.floor(((lv*a/10)+(lv/b)-math.floor((lv^2)/(2*d))+c)/4)
return math.min(calc, 255)
VIT, SPR, SPD, EVA:
local calc = math.floor(lv/b)-math.floor(lv/d)+(lv*a)+c
return math.min(calc, 255)
-
I noticed when poking around the memory at the actual values that some of the values in Ifrit were 1 off but it's probably a rounding issue, I could take a look in the code and see where it pushes the value from.
EDIT:
Function that gets the values in FF8_EN.exe (Steam version) is at 0x48C3F0 and takes 3 parameters:
param 1: level
param 2: pointer to section 7 of a dat file
param 3: integer (0 = str, 1 = mag (think this actually vit), 2 = vit (think this is actually mag), 3 = spr, 4 = spd, 5 = eva)
Is the information on http://wiki.qhimm.com/view/FF8/FileFormat_DAT#Section_7:_Informations_.26_stats (http://wiki.qhimm.com/view/FF8/FileFormat_DAT#Section_7:_Informations_.26_stats) in the correct order?
Since I'd expect the formulas for str and mag to be the same rather than str and vit - I'm guessing mag and vit should be swapped since the order they usually appear in is str, vit, mag, spr, spd, luck, eva (although monsters have no luck).
For the following sections:
ESI = pointer to the 4 bytes needed, so ESI = 1st byte, ESI+1 = 2nd byte, ESI+2 = 3rd byte, ESI+3 = 4th byte
arg.1 = level
result ends up in EAX
ok, here is the ASM for the mag, spr, spd and eva cases (it gets capped to 255 after this but I've omitted that part) - I think this is actually for vit, spr, spd and eva, see note above:
0048C447 |. 8B7C24 10 MOV EDI,DWORD PTR SS:[ARG.1] ; default case of switch FF8_EN.48C43D
0048C44B |. 33C9 XOR ECX,ECX
0048C44D |. 8A4E 03 MOV CL,BYTE PTR DS:[ESI+3]
0048C450 |. 8BC7 MOV EAX,EDI
0048C452 |. 99 CDQ
0048C453 |. F7F9 IDIV ECX
0048C455 |. 33DB XOR EBX,EBX
0048C457 |. 8A5E 01 MOV BL,BYTE PTR DS:[ESI+1]
0048C45A |. 8BC8 MOV ECX,EAX
0048C45C |. 8BC7 MOV EAX,EDI
0048C45E |. 99 CDQ
0048C45F |. F7FB IDIV EBX
0048C461 |. 33D2 XOR EDX,EDX
0048C463 |. 8A16 MOV DL,BYTE PTR DS:[ESI]
0048C465 |. 0FAFD7 IMUL EDX,EDI
0048C468 |. 2BC1 SUB EAX,ECX
0048C46A |. 33C9 XOR ECX,ECX
0048C46C |. 8A4E 02 MOV CL,BYTE PTR DS:[ESI+2]
0048C46F |. 03C2 ADD EAX,EDX
0048C471 |. 03C1 ADD EAX,ECX
if you're not using integer arithmetic, this is the same as: floor(lvl/b) - floor(lvl/d) + a*lvl + c - which is the same as JBedford128's formula above
ASM for str and vit (it gets capped to 255 after this but I've omitted that part): - I think this is actually for str and mag, see note above
0048C480 |. 8B7C24 10 MOV EDI,DWORD PTR SS:[ARG.1] ; cases 0, 2 of switch FF8_EN.48C43D
0048C484 |. 33C9 XOR ECX,ECX //ecx = 0
0048C486 |. 8BC7 MOV EAX,EDI //eax = lvl
0048C488 |. 8A4E 03 MOV CL,BYTE PTR DS:[ESI+3] //cl = d
0048C48B |. 0FAFC7 IMUL EAX,EDI EAX //edx:eax = lvl^2
0048C48E |. 99 CDQ //sign extend eax into edx
0048C48F |. F7F9 IDIV ECX //eax = floor(lvl^2/d)
0048C491 |. 33DB XOR EBX,EBX //ebx = 0
0048C493 |. 8A5E 01 MOV BL,BYTE PTR DS:[ESI+1] bl = b
0048C496 |. 99 CDQ //sign extend eax into edx
0048C497 |. 2BC2 SUB EAX,EDX //not sure about this one edx will either be 0xFFFFFFFF or 0x00000000 eax ends up as eax or eax + 1
0048C499 |. 8BC8 MOV ECX,EAX //ecx = floor(lvl^2/d)
0048C49B |. 8BC7 MOV EAX,EDI //eax = lvl
0048C49D |. 99 CDQ //sign extend eax into edx
0048C49E |. F7FB IDIV EBX //eax = floor(lvl/b)
0048C4A0 |. D1F9 SAR ECX,1 //ecx = floor(floor(lvl^2/d)/2)
0048C4A2 |. 8BD8 MOV EBX,EAX //ebx = floor(lvl/b)
0048C4A4 |. B8 67666666 MOV EAX,0x66666667 //eax = 0x66666667
0048C4A9 |. 2BD9 SUB EBX,ECX //ebx = floor(lvl/b) - floor(floor(lvl^2/d)/2)
0048C4AB |. 33C9 XOR ECX,ECX //ecx = 0
0048C4AD |. 8A0E MOV CL,BYTE PTR DS:[ESI] cl = a
0048C4AF |. 0FAFCF IMUL ECX,EDI //edx:ecx = lvl*a
0048C4B2 |. F7E9 IMUL ECX //edx:eax = lvl*a*0x66666667
0048C4B4 |. C1FA 02 SAR EDX,2 //edx = floor(lvl*a*0x66666667/2^34)
0048C4B7 |. 8BC2 MOV EAX,EDX //eax = floor(lvl*a*0x66666667/2^34)
0048C4B9 |. 03D3 ADD EDX,EBX //edx = floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2)
0048C4BB |. C1E8 1F SHR EAX,1F //eax = sign bit of eax
0048C4BE |. 03C2 ADD EAX,EDX //not sure what effect this has
0048C4C0 |. 33D2 XOR EDX,EDX //edx = 0
0048C4C2 |. 8A56 02 MOV DL,BYTE PTR DS:[ESI+2] dl = c
0048C4C5 |. 03C2 ADD EAX,EDX //eax = floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c
0048C4C7 |. 99 CDQ //sign extend eax into edx
0048C4C8 |. 83E2 03 AND EDX,0x00000003 //edx = either 0x00000000%4 or 0xFFFFFFFF%4, so 0 or 3 depending on sign of eax
0048C4CB |. 03C2 ADD EAX,EDX //eax = floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c
0048C4CD |. C1F8 02 SAR EAX,2//eax = floor((floor(lvl*a*0x66666667/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c)/4)
So something like:
floor((floor((lvl*a*0x66666667)/2^34) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c)/4)
as a rough guess.
There are a couple of things I'm not sure about above though and probably some mistakes in the annotations above. Someone that's better at signed arithmetic would probably help :P.
The formula actually looks fairly close to what JBedford128 had (note that 0x66666667/2^34 is ~1/10 - it's so close that you could probably just use divide by 10 and it probably was in the original code - I suspect it's a compiler optimization for division by 10) - the only main difference is the flooring.
I'd say that:
floor((floor(lvl*a/10) + floor(lvl/b) - floor(floor(lvl^2/d)/2) + c)/4) probably works.
or if you're using integer arithmetic:
((lvl*a)/10 + lvl/b - (lvl^2/d)/2 + c)/4
I do wonder if this differs from the PSX code though.
-
Edit: Okay, so I'll preface this by saying sometimes they match the Ultimania stats (which give stats at Lv1, every ten levels, and the max level), sometimes they are way off. I do know that some changes were made from JP>EN so this could be a product of that. Also I'm suspicious of my math.floors, I may have added them at random to see what gave me the right results.
I compared french PS and japanese version, here are the differences (in hex):
Tri-Point
Mag (or vit?) | JP: 01 14 64 02 | FR: 01 14 23 02
Left Orb (or Right?)
Spd | JP: 00 01 14 0a | FR: 00 01 05 03
Right Orb (or Left?)
Spd | JP: 00 01 28 0a | FR: 00 01 0a 03
Jumbo Cactuar
HP | JP: 00 00 3c 00 | FR: 00 00 1e 1e
Anakronox
HP | JP: 00 00 04 1e | FR: 00 00 03 1e
Left Probe / Right Probe
Spd | JP: 00 03 32 06 | FR: 00 03 1e 06
Trauma
Spd | JP: 00 01 24 01 | FR: 00 01 18 01
Fujin (119)
Str | JP: 46 05 55 82 | FR: 41 05 3c 82
Raijin (120)
Str | JP: 50 05 78 82 | FR: 50 05 6e 82
Thanks for your answers JWP and JBedford128 ! that is really helpful
-
Ok, I can confirm STR/MAG formula:
result = floor((floor(level * a/10) + floor(level / b) - floor((level * level) / (d * 2)) + c)/4)
result = min(result, 255)
And VIT/SPR/SPD/EVA formula:
result = floor(level / b) - floor(level / d) + a * level + c
result = min(result, 255)
My EXP formula seems correct too (where "a" the value from the DAT file):
EXP = floor(a * (5 * (level - level_party_avg) / level_party_avg + 4))
EXP_LAST = floor(a * (5 * (level - level_party_last) / level_party_last + 4))
Note: values on the Ultimania guide seems to be EXP / 4 and EXP_LAST / 4, for level 1 members.
But still, the HP formula is sometimes wrong, like JWP says, in Iron Giant for example. Now I use (I was wrong in my first post):
HP = floor(a * lvl * lvl/20) + (a + c * 100) * lvl + b * 10 + d * 1000
Someone know more about this? JWP?
-
looking at the code:
0048BD33 |. 8A57 1B MOV DL,BYTE PTR DS:[EDI+1B] //dl = d
0048BD36 |. 8886 CB7BD201 MOV BYTE PTR DS:[ESI+1D27BCB],AL //line not used in calculation
0048BD3C |. 33C0 XOR EAX,EAX //eax = 0
0048BD3E |. 33C9 XOR ECX,ECX //ecx = 0
0048BD40 |. 8D1492 LEA EDX,[EDX*4+EDX] //edx = d*5
0048BD43 |. 8A47 18 MOV AL,BYTE PTR DS:[EDI+18] al = a
0048BD46 |. 8A8E CC7BD201 MOV CL,BYTE PTR DS:[ESI+1D27BCC] //cl = lvl
0048BD4C |. 8D2C92 LEA EBP,[EDX*4+EDX] //ebp = d*25
0048BD4F |. 33D2 XOR EDX,EDX //edx = 0
0048BD51 |. 8A57 19 MOV DL,BYTE PTR DS:[EDI+19] //dl = b
0048BD54 |. 8D14AA LEA EDX,[EBP*4+EDX] //edx = 100*d + b
0048BD57 |. 8D2C92 LEA EBP,[EDX*4+EDX] //ebp = 500*d + 5*b
0048BD5A |. 33D2 XOR EDX,EDX //edx = 0
0048BD5C |. 8A57 1A MOV DL,BYTE PTR DS:[EDI+1A] //dl = c
0048BD5F |. 8D1492 LEA EDX,[EDX*4+EDX] //edx = c*5
0048BD62 |. 8D1492 LEA EDX,[EDX*4+EDX] //edx = c*25
0048BD65 |. 8D1490 LEA EDX,[EDX*4+EAX] //edx = c*100 + a
0048BD68 |. 0FAFC1 IMUL EAX,ECX //eax = a*lvl
0048BD6B |. 0FAFC1 IMUL EAX,ECX //eax = a*lvl*lvl
0048BD6E |. 0FAFD1 IMUL EDX,ECX //edx = (c*100 + a)*lvl
0048BD71 |. 8BC8 MOV ECX,EAX //ecx = a*lvl*lvl
0048BD73 |. B8 67666666 MOV EAX,66666667 //eax = 0x66666667
0048BD78 |. 8D2C6A LEA EBP,[EBP*2+EDX] //ebp = 1000*d + 10*b + (c*100 + a)*lvl
0048BD7B |. F7E9 IMUL ECX //edx:eax = (a*lvl*lvl)*0x66666667
0048BD7D |. C1FA 03 SAR EDX,3 //edx = floor((a*lvl*lvl)*0x66666667/2^35)
0048BD80 |. 8BC2 MOV EAX,EDX //eax = floor((a*lvl*lvl)*0x66666667/2^35)
0048BD82 |. 03D5 ADD EDX,EBP //edx = floor((a*lvl*lvl)*0x66666667/2^35) + 1000*d + 10*b + (c*100 + a)*lvl
0048BD84 |. C1E8 1F SHR EAX,1F //add 1 if the result was negative (this isn't going to happen, it's probably due to them putting the result in a signed int)
0048BD87 |. 03C2 ADD EAX,EDX
Think this gives:
HP = floor(a*lvl*lvl/20) + 1000*d + 10*b + (c*100 + a)*lvl
or if you're using integers:
(a*lvl*lvl)/20 + 1000*d + 10*b + (c*100 + a)*lvl
This looks like it matches with what you have and also the formula I gave several years ago in this post: http://forums.qhimm.com/index.php?topic=6961.msg86799#msg86799 (http://forums.qhimm.com/index.php?topic=6961.msg86799#msg86799)
BYTE HP[4]; //HP = (HP[0]*x*x)/20 + (HP[0] + 100*HP[2])*x + 10*HP[1] + 1000*HP[3], where x = monster lvl
You mentioned MAG twice in your post, did you mean VIT/SPR/SPD/EVA?
-
You mentioned MAG twice in your post, did you mean VIT/SPR/SPD/EVA?
Yes, sorry for the confusion, and I fixed this on the wiki page yesterday.
So for the PC version, we're sure that is the right formula :) . It would be good to know if the PS version shares the same formulas.
-
Ah nice :), I should probably see about getting a wiki account rather than just dumping info all over the forum :-P.
I'm curious as to what project you're using the formulas for :-P.
I'd guess that the original PS game was written in C/C++ and they copied functions like that for the PC version but I could be wrong.
I'm tempted to take a look at the PS version to see if some of the data structures in memory are similar and it might be easier to find information with snapshotting.
In addition to this, seeing the same routines in 2 different ASM implementations might help with reverse engineering.
PSX looks like it has a pretty good R3000 debugger to do that sort of thing.
While looking through the forums for FF8 posts (to see if there was anything I didn't know), I came across this old post: http://forums.qhimm.com/index.php?topic=3723.msg51764#msg51764 (http://forums.qhimm.com/index.php?topic=3723.msg51764#msg51764)
It seems that people have looked through the section of code before - although I think their interpretation is slightly off since there aren't enough brackets - divisions end up taking priority, which causes different results when using integer division:
4*4/3 = 4 * floor(4/3) = 4
(4*4)/3 = floor(16/3) = 5
Note the division order doesn't actually make a difference due to the nested division property mentioned below:
floor(floor(x/y)/z) = floor(x/(y*z)) = floor(x/(z*y)) = floor(floor(x/z)/y)
So many things just seem to get lost in time on this forum :(.
btw, I noticed that the STR/MAG formula can be simplified slightly due to this property:
https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Nested_divisions (https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Nested_divisions)
it might be possible to simplify it further but I'm not too familiar with that sort of mathematics.
floor((floor(level * a/10) + floor(level / b) - floor(level * level / d / 2) + c)/4)
-
I'm curious as to what project you're using the formulas for :-P.
A french website that will become the FF8 reference guide.
I removed nested floors, it works well :)
For now, Iron Giant is the only monster I know with errors, according to the Ultimania, maybe the error isn't on our side...
Values : 30, 0, 6, 0
Level Ultimania Known formula
LV1 663 HP 631 HP
LV10 6900 HP 6450 HP
LV20 14400 HP 13200 HP
LV30 22500 HP 20250 HP
LV40 31200 HP 27600 HP
LV50 40500 HP 35250 HP
LV60 50400 HP 43200 HP
LV70 60900 HP 51450 HP
LV80 72000 HP 60000 HP
LV90 83700 HP 68850 HP
LV100 96000 HP 78000 HP
There is nothing special about Iron Giant HPs on IA scripts.
-
I forced a battle against a lvl 20 Iron Giant by using encounter code 248 (8420 when entering it) in the debug room and memory editing the level just after it loaded using a memory breakpoint.
It had 13200 HP so either it's an issue with the guide or the guide is based on a different version of the game.
-
There are a few enemies with different HPs in the Ultimania guide to what I calculated. These enemies are Torama, Elnoyle, Iron Giant, and Behemoth. I think that's it.
I believe I determined their (simplified) formula myself. On their FFWiki pages (http://finalfantasy.wikia.com/wiki/Behemoth_%28Final_Fantasy_VIII%29) I list them.
So I figured Behemoth's Japanese formula was 3*lv*lv + 260*lv + 35000. Which I woud assume is [60, 0, 2, 35] in the data. Unless the Ultimania is just plain wrong. It might be worth mentioning that the English guides also use the same HP values as the Ultimania, but then again, they also use images of red Gerogero so I assume they were just given the Japanese information when writing the guides.
-
Hi Mystere,
I try to calculate the characters Stats by formul but I don't have the right formul. By example for the Strenght of Squall at Level 7 (starting level in game) I have 6 in STR but in game this is 17.
Do you have the right formuls? STR, VIT, MAG,etc.
Thanks.