• Welcome to Final Fantasy Hacktics. Please login or sign up.
 
June 01, 2024, 07:59:00 pm

News:

Don't be hasty to start your own mod; all our FFT modding projects are greatly understaffed! Find out how you can help in the Recruitment section or our Discord!


It is possible...! Distribute JP after battle

Started by Eldiran, May 15, 2013, 08:57:39 pm

Eldiran

May 15, 2013, 08:57:39 pm Last Edit: May 19, 2013, 07:57:40 pm by Eldiran
Hey, occasional lurker here --  I am loving all the fantastic hacks that come out of here, but one change in particular is something I have always dreamed about.

Is it possible to distribute JP to all the active units after (or at the start of) a battle?  Ideally the JP would be proportional to the number and strength of the enemies, but a flat rate would also suffice.

Example:
Player brings 5 units in to a random encounter.
The encounter consists of 4 enemies.
At the end of the encounter, 4 * 50 JP is distributed amongst the players' units (into their respective current class).


I will gladly (attempt to) learn basic ASM hacking to add this if it's doable.  What do you all think?

Thanks!

Choto

entirely possible. You would just have to code the routine into the Require.Out file, nothing too crazy. Require.out is where all the routines that store modified stats, equipment, gil, etc. after battle.

Eldiran

Excellent, thank you!  Would basing it off of number/stats of the enemy units be possible?  I can't imagine their data would be stored or used after the battle...

Do you have any tips on where is best to start learning for this particular task? (Besides the general tutorials linked in the forums) I imagine I'm in for quite a ride trying to pull it off. Seems like I need to do the following things:


1. Make a temporary variable X for JP rewarded (based off of enemies?)
2. Divide X by number of player chars in the battle
3. Increment each character's JP (of their current class) by X


Or I could just do the calculations in 1 and 2 for every assignment in 3 but I dunno if that'll work in ASM.

Seems like 3 is going to be the hard part, trying to get at the correct JP count for each class.

Thank you very much for the help!

And as an aside, if anyone suitably knowledgable happens to be interested in implementing this kind of patch, I'd love to not have to learn all this... : P

Choto

hehe.. the number of general hacks that are produced these days has dropped dramatically... so your best bet may be to learn. Scour the tutorials and the important links section. I made a little package for beginning ASM that I think is in the important links or tutorials section.. It'll take a bit of effort but people on here will always help when possible. First you have to learn where data is stored, which is mostly in the Data Locations.txt. You'll have a much better idea of what to do once you spend some time learning

Eldiran

Aw, dang :p  Thank you again for the help.  Time to go delve in, I suppose... though I'm sure it's only a matter of time until I post in the Help section again.

Choto

The nice thing is that once you know your way around, its much easier to do unique things that you want. You don't have to rely on somebody else to make your hacks for you. it just takes some time and alot of effort, that's all.

Eldiran

True indeed.  This stuff is pretty crazy and terrifying to get into, but I think I'm getting the hang of it... a bit...

But now I'm stuck!  I found this particular routine on the wiki: http://ffhacktics.com/wiki/Initialize_Unit%27s_Battle_Data

I'm attempting to insert code at line 0005b260.  Using FFT OrgASM XML Generator, I've made this:

<Patch name="JP+100 On Load Battle">
    <Description>Describe Your Patch Here</Description>
    <Location file="SCUS_942_21" offset="0005b260">
      26250064
    </Location>
  </Patch>


Then using FFTOrgASM I've applied it.  (FFTOrgASM is definitely working, because I also applied "No MP on Battle Start" alongside it and I can confirm that's working.)

However I'm not getting any results for JP going up.

Am I on the right track at all?  Is it possible to edit routines like that?  Am I editing the wrong routine? (Seems like Initialize Unit's Battle Data happens prior to every battle, right?)  Does the command 26250064 actually addiu by 100, like I think it does?

I just need some confirmation that I'm using the tools correctly before I try different things. Thanks!

Eldiran

I have no idea what I'm doing.  T_T

Apparently my command 26250064 is big endian.  Which is wrong.  Although I see all the commands in big endian when I use the debugger... anyway.  So I tried it little endian but nothing changed.

And when I open up my address, 0x0005b260, in the debugger, I see it all as it is laid out on the wiki.  Except for the command I edited, which is marked as "illegal" and says it's "4c444444".

And either way, nothing has any effect.  Although a breakpoint set at 0x0005b260 triggers multiple times at the start at any battle, (~8 times before formation placement, 2-5 times whenever I L1 or R1 to the next character, and ~5 times after formation is set), even inserting nonsense 'illegal' commands apparently does nothing.  And given how often the routine is called I suspect I'm on the wrong track anyway.

Even if I could find a routine that was executed once per battle, and even if I could insert anything I wanted in there correctly, I have no idea how to get access to the player's unit's current job's JP.

I see the addresses on this page: http://ffhacktics.com/wiki/Battle_Stats

But I have no idea what to do with them.  And I wouldn't know how to modify only their current job anyway without some gigantic labyrinthian switch statement that I would have to repeat 5x.  And it would probably crash if the player used less than 5 characters.

Choto

May 18, 2013, 04:32:21 pm #8 Last Edit: May 18, 2013, 04:54:08 pm by Choto
I'm happy to see that you're putting effort into it! ok, there's a couple of things that you're missing.

First, "4c444444" is an execute breakpoint. When you set a breakpoint, it stores that command in memory.. and then stops execution of code upon reaching it. It's only temporary, so no worries there.

in your patch, your offset is "5b260". However, this is the offset in RAM. When you write a hack, you actually have to write to an offset in a file. Files are loaded to different portions of RAM. For example, the SCUS file actually starts at 0xF800 in memory, so you would actually have to write it to 0x5b260 - 0xf800 = 0x4ba60. Don't worry, everybody has made this mistake.

lastly and most importantly, your approach will not work. the line in the wiki is actually an address, not a JP total. What the routine is doing is loading the job levels and jp from "Party data" (data stored in SCUS for your formation screen) into Unit Battle Data (temporary data loaded FROM party data to be used and manipulated during battle). To accomplish this though, it loads the two locations to 2 different registers, and then transfers the data using an intermediate routine. Thats this:

0005b264: 0c017895 jal 0x 0005e254      Store X Byte into Y (JP/Total JP)

It was a good thought to do it at the beginning of the battle though, good idea! To accomplish this, we'll use a different routine: http://ffhacktics.com/wiki/Move/Jump_%2BX_Calculation. You can write a jump command that jumps from this routine to a custom one that does what you want. It should only be called once per unit, although there's always the possibility that this routine is skipped for monsters or something, since they can't equip Move +X abilities. You'd have to test that

Now the hard part. You will have to code a routine that does the following:

-Get units data
-Load units Job (0x03 in Unit Battle Data)
-subtract units Job by 0x4A (this is to get the number of the job starting from 0. 0 would be squire, 1 would be chemist, etc.)
-add this number to the unit data address
-load the value at THAT address + 0x00DC (http://ffhacktics.com/wiki/Battle_Stats - this is where current JP amounts are stored. so now our result will be the address of the units current job JP amount)
-add 100 to this value (which you already figured out how to do)
-store the new value
-add 0x28 to the address (this gives you the address to the jobs TOTAL JP amount)
-load THAT value
-add 100 to it
-store it

Yes... you picked a pretty hard hack to do for your first one lol. However, it is completely doable.

A better list of data locations is the "Data Locations.txt" that can be found in the ASM starter kit thread. It has a list of the Party data and Unit battle data, and wayy more other data that is irrelevant atm. For your reference:

Party data starts at 0x57f74, and each unit has 0x100 bytes worth of data (this means that to get for example the third units data, you would do 0x57f74 + (0x100*3) = 0x58274
Unit battle data starts at 0x1908cc (although PLAYER unit data starts at 0x1924cc), and each unit has 0x1c0 worth of data.

Mull it over and check some of the other threads. Sometimes seeing other examples of how people learned will allow you to make connections and learn.

Eldiran

May 18, 2013, 09:02:15 pm #9 Last Edit: May 18, 2013, 10:18:52 pm by Eldiran
Thank you so much! I never would have been able to figure out to change the offsets like that... or to use Move/Jump +X Calculation. I think I'm really starting to get it because of all your help.

I even was able to change the Base Class' JP!  But that only works when I overwrite large parts of Move/Jump +X Calculation.

Your list of tasks
Quote-Get units data
-Load units Job (0x03 in Unit Battle Data)
-subtract units Job by 0x4A (this is to get the number of the job starting from 0. 0 would be squire, 1 would be chemist, etc.)
-add this number to the unit data address
-load the value at THAT address + 0x00DC (http://ffhacktics.com/wiki/Battle_Stats - this is where current JP amounts are stored. so now our result will be the address of the units current job JP amount)
-add 100 to this value (which you already figured out how to do)
-store the new value
-add 0x28 to the address (this gives you the address to the jobs TOTAL JP amount)
-load THAT value
-add 100 to it
-store it

is also extremely useful. However, I noticed that Move/Jump +X Calculation already has access to the unit's data, so I skipped to just using the existing reference in r4, which works perfectly.  And thankfully, since Move/Jump +X Calculation is called once per unit, I won't have to loop it.

Anyway right now I'm trying, and failing, to create a separate routine.  Currently what I have looks like this:


<Location file="SCUS_942_21" offset="4D0F0">
877A0108 <!-- j 0x0005ea1c -->
    </Location>
    <Location file="SCUS_942_21" offset="4f21c">
DC008294 <!-- lhu r2,0x00DC(r4) -->
04018394 <!-- lhu r3,0x0104(r4) -->
64004224 <!-- addiu r2,r2,0x0064 -->
64006324 <!-- addiu r3,r3,0x0064 -->
DC0082A4 <!-- sh r2,0x00DC(r4) -->
040183A4 <!-- sh r3,0x0104(r4) -->
3E720108 <!-- j 0x0005c8f8 -->
    </Location>


Offset 4f21c (5ea1c) is a chunk of 13 nops according to my debugger.  What the above is doing is just jumping to 5ea1c, loading, adding 100, and saving to the unit's Base JP and Total Base JP, and then jumping back to 5c8f8.

But for some reason it freezes up! I figure there must be an infinite loop in there somewhere, but I can't see what the problem is.  It's all being properly assigned -- I can see it in the debugger -- but there's a glitch somewhere.  All the registers (such as r4) should still be the same from Move/Jump +X Calculation, too... Perhaps I'm using Jump wrong?

EDIT: What the heck?!?!?!?! It works UNLESS I hold down the "fast forward" button in pSX v1.13??  What is that even?

Okay, disregard the above, maybe?
  Is this a thing that happens normally??

EDIT2: Okay, I have no idea what's going on.  Whether it works or not appears to be random.  I'm just loading, crashing, reloading, and it works, I don't even know.  More testing.

EDIT3: It appears that it always works if I use debug mode and add a breakpoint at 5ea1c. And apparently it either fires many times prior to, during, and after formation setup, OR it fires ZERO times.  And either way it works perfectly.

Unless I don't have that breakpoint in which case pSX crashes.

Eldiran

I got it working!!!  Phew!

The problem was placing a jump command immediately after a bne.  Apparently those two did not get along, but somehow debugging makes them cooperate.

Anyway, my separate routine is now workable, and I can continue expanding it.  My only questions now is:

Would you say it's safe to overwrite "illegal" commands like 01010101, 02020101, etc?  I'm probably going to need the space...

Choto

Excellent job! Now there are a couple more tricks of the trade you need to know.

Even though a space can be full of 00's or nops, that location may be written to by the original game code at some point, which would overwrite your code. Also, "illegal" commands like the ones you suggested appear to be parts of a table of data. Not all hex is code, many places are filled with tables of data. You should certainly stay away from overwriting tables of data, not healthy.

So the solution? Thankfully square left a decently big section of free space called the "kanji table". There were just kanji characters stored here that seem to have no correspondence with anything in the game. You can see the offsets here: http://ffhacktics.com/wiki/Allocated_space

Now, the cause of MOST issues with existing ASM hacks is that space is hard to keep track of, and some hacks are written to the same space as others. The best way to insure that you're not overwriting space is to do the following.

-Open your iso in CDmage
-extract your Battle.bin file (the kanji table exists in this file)
-Open it in a hex editor (HxD is a good one)
-go to the kanji table offsets (The offsets on the wiki are file offsets, not RAM offsets, so no conversion is necessary)
-copy a block of hex values
-paste it into MasshexASM
-convert it to code, and see if it is code that makes sense. If it is, it's probably a hack, if it's not.. it's probably fair game to be used.

Once you have some free space set aside, its as simple as jumping there, doing your thang, and jumping back.

Other things to be aware of when inserting your code in the midst of vanillas:
-Is there an important value stored in a register that you overwrite? See if the original routine uses that value, or overwrites it.
-When you return to the original routine, is everything in a state that it won't mess up the original routines function?

One other thing you have to be careful of is return addresses. You'll notice that the Move/Jump + X routine doesn't have any jal commands. A jal command jumps to another routine, and saves the address of the next command so that it can return there once the other routine is done. The address is stored in r31. However, because all of code is a sequence of calling a subsequent routine.. there is always an important return address in r31. You may see code that looks like this:

in the beginning of a routine
0005c5c8: 27bdffe0 addiu r29,r29,0xffe0      
0005c5cc: afb10014 sw r17,0x0014(r29)      
0005c5d4: afb00010 sw r16,0x0010(r29)
0005c5dc: afb20018 sw r18,0x0018(r29)
0005c5e8: afbf001c sw r31,0x001c(r29)

at the end of a rooutine:
0005c8d0: 8fbf001c lw r31,0x001c(r29)
0005c8d4: 8fb20018 lw r18,0x0018(r29)
0005c8d8: 8fb10014 lw r17,0x0014(r29)
0005c8dc: 8fb00010 lw r16,0x0010(r29)
0005c8e0: 27bd0020 addiu r29,r29,0x0020
0005c8e4: 03e00008 jr r31
0005c8e8: 00000000 nop

What this is doing is saving the return address (r31) as well as other important data in other registers (r16, r17, r18) on the stack, and then loading it at the end so the routine it jumps back to can use it. jr r31 jumps to the address in r31, which is why we reloaded the return address in there.

Glain

You can check the standards sticky to get a grasp of some things that can cause problems when writing ASM and how to avoid them.  If you're having problems where something seems to work in the debugger but not while running normally, there's definitely something wrong with the code (though sometimes I find the debugger itself can crash in certain circumstances when deleting breakpoints).

Another thing to note is branch delay -- jumps/branches aren't actually taken until the instruction after the branch is executed. e.g.:

jal  some_routine  # some_routine to be executed after delay slot
add r4,r3,r2   # delay slot: r4 = r3 + r2
# now we jump to some_routine

Putting another jump/branch in the branch delay slot causes problems (pipeline hazard).
  • Modding version: Other/Unknown

Eldiran

I did it!!! 8D Works flawlessly regardless of job/monster! Here's hoping it works consistently as I play through...



  <Patch name="JP + 130 per Battle">
    <Description>Adds 130 JP to current job of all units at the start of battle. Exists in Move/Jump +X Calculation subroutine (see wiki).
Huge thanks to Choto!
</Description>
    <Location file="SCUS_942_21" offset="4D0F4">
877A0108 <!-- j 0x0005ea1c -->
    </Location>
    <Location file="SCUS_942_21" offset="4f218">
9D7A0108
00000000
03008290
4A000324
13000624
22104300
22184600
02006004
00000000
21100000
FEFF4004
20104200
DC004524
2128A400
0000A294
2800A394
82004224
82006324
0000A2A4
2800A3A4
2A008294
2E008394
3E720108
00000000

<!--
 
j 0x0005ea74 //jump past current subroutine
nops
lbu r2,0x0003(r4) //r2 = current job

addiu r3, r0, 0x004a //r3 = 4a
addiu r6, r0, 0x0013 //r6 = 13

sub r2, r2, r3 //r2 -= 4a
sub r3, r2, r6 //r3 = r2 - 13

bltz r3, 0x5 //if r3 < 0 skip to *
nop
addu r2, r0, r0 //r2 = 0

*
bltz r2, 0x5 //if r2 < 0 skip up 1
add r2, r2, r2 //r2 *= 2

addiu r5, r2, 0x00DC //r5 = r2 + 0x00DC
addu r5, r5, r4 //r5 += r4


lhu r2,0x0000(r5)
lhu r3,0x0028(r5)
addiu r2,r2,0x0064
addiu r3,r3,0x0064
sh r2,0x0000(r5)
sh r3,0x0028(r5)

lhu r2,0x002a(r4)
lhu r3,0x002e(r4)
j 0x0005c8f8
nop

-->
    </Location>
  </Patch>
 
 
  <Patch name="JP + 130 per Battle FFT1.3 Compatibility">
    <Description>Adds 130 JP to current job of all units at the start of battle. Exists in Move/Jump +X Calculation subroutine (see wiki).
Huge thanks to Choto!
Specially made compatible with FFT Content 1.3</Description>
    <Location file="SCUS_942_21" offset="4D0F4">
547A0108 <!-- j 0x0005e950 -->
    </Location>
    <Location file="SCUS_942_21" offset="4f14c">
00000000
00000000
03008290
4A000324
13000624
22104300
22184600
02006004
00000000
21100000
FEFF4004
20104200
DC004524
2128A400
0000A294
2800A394
64004224
64006324
0000A2A4
2800A3A4
2A008294
2E008394
3E720108
00000000
   </Location>
  </Patch>



I certainly hope I didn't overwrite anything important... since I'm working in SCUS, I didn't conflict with any of the allocated space, but I also don't have a kanji table.  I did have to create an alternate version located somewhere slightly different to make it compatible with 1.3 (they were using the same nops for something else).

Good tips on the return addresses too, I had some brief wrestling with jal before I realized it was a bad idea.  Thank goodness Move/Jump +X Calculation already had about 4 registers it was going to use, so I could just coopt those.

Thank you so much for all your help!  I seriously could not have even scratched the surface without all the feedback.  Now I'm going to play with this for a while, and if I don't find any problems, I'll post it in a proper thread.

Glain, that is a very useful sticky -- I didn't even realize load commands had delay.  Seems I lucked out by copying the format already in use by Move/Jump +X Calculation.

Choto

Like I said before, you need to use the kanji space. pick a location in that range of offsets I gave you, and write it there. You're overwriting the movement cost/geomancy skill/terrain status table. This is really gonna cause some strange and crippling side effects :P  Almost there though by the sound of it. Keep learning and more things will make sense to you.. Then you can really edit the way you want

Thanks for that note Glain, I forgot about that convention.

Eldiran

Oh!  For some weird reason I thought jumping from file to file wasn't possible, but I don't know where I got that notion.

In my initial testing I did notice certain tiles had suddenly become unwalkable, but I thought that was interaction between 1.3 and my code. :p Apparently my 1.3-compatible version isn't overwriting that stuff... maybe... but I'd better start delving into the kanji.  I guess I should start looking from 0x0006B90F onward, since that's the last reserved space in WORLD.BIN listed.

Thanks!

Xifanie

Files are just loaded into RAM. During gameplay, either BATTLE.BIN (events/battles) or WORLD.BIN (world map + formation) will be loaded and both have kanji space you can use. The SCUS is always loaded... as for other files, they are loaded depending of the situation, but REQUIRE.OUT is only ever loaded alongside BATTLE.BIN, so it's safe.
  • Modding version: PSX
Love what you're seeing? https://supportus.ffhacktics.com/ 💜 it's really appreciated

Anything is possible as long as it is within the hardware's limits. (ie. disc space, RAM, Video RAM, processor, etc.)
<R999> My target market is not FFT mod players
<Raijinili> remember that? it was awful

Choto

its pretty simple to look into the battle.bin file and see what's there. there should be either unknown/illegal commands, or stuff that just doesn't make sense at all. Copy-paste the hex into MasshexASM and it'll show you what's there.

Eldiran

Thanks guys!  I took a break for a bit, but now I'm having another crack at it.  Unfortunately my attempts aren't yielding much fruit... if I put my chunk of code into WORLD.BIN, it seizes up at the start of battle.  (As it should, since I assume WORLD.BIN is not loaded when Move/Jump +X Calculation fires.)

And if I put my code into BATTLE.BIN, it works in battle but crashes when I try to enter the Formation screen.  It seems that Move/Jump +X Calculation is called during the formation screen, but in the past my code apparently just happened to never do anything in that context. (Man was I lucky it ever worked in the first place!)  Naturally this would crash since BATTLE.BIN isn't loaded at that point.

Now I'm not quite sure what to do.

As an aside, it works perfectly when placed in SCUS because I lucked out and put it at 8005e950 (listed as unidentified by the wiki, and seems to do nothing).  I'd like to expand it to do more, but I'm out of space in SCUS.

Choto

Paste what you currently have, i'll take a look at it. Welcome to the world of hacking... shit never works perfectly lol. However crashes are not always a fatal omen. A good idea is to set a breakpoint at the start of the routine that you edited... and produce the situation that causes a crash. That way you can step through the commands one by one and try to figure out where in the code it's causing a crash and fix it. Could be something simple.