News:

11 March 2016 - Forum Rules

Main Menu

Final Fantasy VII (PSX). W-Item glitch.

Started by Ortew, June 15, 2018, 02:25:41 PM

Previous topic - Next topic

Ortew

Hello. I'm member of a team that is re-translating FF7 to Spanish. A long time ago we released some patches and we've been fixing things like misstranslations and original bugs from the game. One of the glitches that we want to fix is the W-Item which you can duplicate items.

The first Final Fantasy VII japanese release hasn't this glitch... but it has another error: You can't retrieve the item if you cancel the action after select the target.

It works like this:

In the international version and in all the subsequent ones to this one, they added a code to retrieve the item if you cancel it... but they added the code in a wrong "Cancel". This is what causes the glitch that we all know.


That code is in address 800E14A4 ➝ 800E1568 (0x414A4 ➝ 0x41568 in BATTLE.X file decoded). USA version.
000e14a4: ori r2,r0,0x0002
000e14a8: lui r3,0x800f
000e14ac: lbu r3,0x38a4(r3)// 800F38A4=02 ➝ Phase 2 of W-Item
000e14b0: nop
000e14b4: bne r3,r2,0x000e156c
000e14b8: ori r2,r0,0x000a
000e14bc: lui r3,0x800f
000e14c0: lbu r3,0x389d(r3)// 800F389D=0A ➝ W-Item Menu
000e14c4: nop
000e14c8: bne r3,r2,0x000e156c
000e14cc: nop
000e14d0: lui r2,0x800f
000e14d4: lhu r2,0x562c(r2)//800F562C ➝ Store position of first item used.
000e14d8: nop
000e14dc: sll r3,r2,0x01
000e14e0: addu r3,r3,r2
000e14e4: sll r3,r3,0x01
000e14e8: addu r3,r3,r19
000e14ec: lbu r2,0x0002(r3)
000e14f0: nop
000e14f4: bne r2,r0,0x000e150c
000e14f8: nop
000e14fc: lui r2,0x800f
000e1500: lhu r2,0x314e(r2) // 0x800F314E ➝ Store ID of first item used.
000e1504: nop
000e1508: sh r2,0x0000(r3)
000e150c: lui r3,0x800f
000e1510: lhu r3,0x562c(r3)
000e1514: nop
000e1518: sll r2,r3,0x01
000e151c: addu r2,r2,r3
000e1520: sll r2,r2,0x01
000e1524: addu r2,r2,r19
000e1528: lbu r3,0x0002(r2)
000e152c: nop
000e1530: addiu r3,r3,0x0001 // Add 1 item.
000e1534: sb r3,0x0002(r2)
000e1538: lui r3,0x800f
000e153c: lhu r3,0x562c(r3)
000e1540: nop
000e1544: sll r2,r3,0x01
000e1548: addu r2,r2,r3
000e154c: sll r2,r2,0x01
000e1550: addu r3,r2,r19
000e1554: lbu r2,0x0002(r3)
000e1558: nop
000e155c: sltiu r2,r2,0x0064
000e1560: bne r2,r0,0x000e156c
000e1564: ori r2,r0,0x0063 // Quanty limit (99)
000e1568: sb r2,0x0002(r3)



What I want to do is move this code to the correct "Cancel" in order to recreate this.

If I'm not wrong and seeing where they put the code, I think that to completely fix the glitch that code should be in address 800DF4E0 (0x3F4E0 in BATTLE.X file decoded).

My problem is that I am not very good with MIPS and all the attempts I have made to fix it end in error (it seems that there are some problems with the jumps that I created or maybe the solution to the problem is not as simple as I think). I don't know if there are many people in this forum familiar with the asm of this game but any advice or help will be grateful.

assassin

#1
i've never played nor hacked FF7, and last worked with MIPS 17 years ago.  (however, i was privy to some of the development of the FF6 "For What Ails Ya" bugfix, and have read its documentation, which almost balances the scales. ;) )  so i'll be of very limited help, unless you like being peppered with questions:

1) so if the game reduces inventory right after an item is confirmed and before it's actually used, what's to stop the item from being lost if the Item-using character dies before acting?  is there a temporary variable that holds the item and gets used to add it back?
2) will your algorithm address the Morph variant of the bug?  e.g. will cancelling before choosing the 2nd item not add the Morphed item to inventory an extra time?
3) on the later releases, if an intervening Morph is done between choosing the first item with W-Item and cancelling the second one, is the first one lost for good?

------

a more drastic approach to a fix might be to not deduct the first item from inventory until after you've confirmed BOTH items+targets.  as it currently stands, is there any good reason to deduct it aside from making sure you can't have W-Item use the same item twice when only 1 is left?  for example, are there other things (e.g. commands done by other characters) that can happen between W-Item instances that we WANT to operate on the lowered quantity?

the other half of said fix would involve saving the first-chosen item ID in a custom variable, and adding special W-Item code to prohibit you from selecting a 1-count item whose ID matches the saved one.

iow, we're putting off the deduction, and making the new count only visible within the W-Item command until then.

now, this could work only if the functions being edited are specific to W-Item.  if it's using more general code to pick the items and deduct them, then you probably can't alter them without breaking other things.

------

hopefully, you can make it work in your proposed way, and the above method will be moot.  however, the code comments are too sparse (although the flow charts are great) and my knowledge of the game too nonexistent to know what's going on here implementation-wise.  so figured i'd put an alternative out there.

back to your way: if the first W-Item instance uses general code and the second instance command-specific code, how can their scopes be aligned?  that is, if the intended incrementation is no longer being done "under the same roof" as the second item's selection/cancellation, how do we pass that intent along to the general code?

where does W-Item first get set to Phase 2?  is it in 800DF4E0?

-----

to elaborate some more on #2 (Morph variant) above: based off of this post:
http://www.neoseeker.com/forums/1169/t1676800-glitch-alert-duplicating-sources-with-item/#m31893473

any fix might need to be more direct in adding back an item; i.e. specify an Item ID to increment as opposed to an Item Slot.

Ortew

OMG, that's a lot of questions  :o

1) The item is lost forever, I think that happens even in others FF.
2) Nope.
3) Yes, lost forever. The ID and Item slot seem to be stored in the same address for W-Item and Morph.

I would like to fix the problem with Morph (also with Steal) but I think that with my knowledge it is too difficult for now. Maybe changing the addresses in which W-item stores the information of the items?

800DF4E0 is part of the "Cancel" code (From Item list to Command Menu). That's the reason why I want to put the code there... and after a couple of tries I managed to make it work (I just had to place a couple of jumps in the code).

assassin

#3
thanks for the answers.

glad you've got the main fix working.

if you were to do the chunk of code ending at 000e1503 earlier on (upon the first item's target confirmation), and found a more permanent variable to hold the first Item ID, that'd enable a more complete fix.  incrementing the quantity of an Item ID as opposed to simply an Item Slot would need more code space if written manually.  however, i wouldn't be surprised if the game already has an Add_Item_ID_to_Inventory function that you can call.  if there isn't or it's unknown, is there any free space where you could put a dumb loop to track down the first slot that holds a given item?

------------------------

EDIT:

okay, here's the above with denser commenting (mainly for my own reference, since you probably already know what it does from having made those charts):

000e14a4: ori r2,r0,0x0002
000e14a8: lui r3,0x800f
000e14ac: lbu r3,0x38a4(r3)     // 800F38A4=02 -> Phase 2 of W-Item
000e14b0: nop
000e14b4: bne r3,r2,0x000e156c  // branch if phase not 2?
000e14b8: ori r2,r0,0x000a
000e14bc: lui r3,0x800f
000e14c0: lbu r3,0x389d(r3)     // 800F389D=0A -> W-Item Menu
000e14c4: nop
000e14c8: bne r3,r2,0x000e156c  // branch if menu ID not 10?
000e14cc: nop
000e14d0: lui r2,0x800f
000e14d4: lhu r2,0x562c(r2)     // 800F562C -> Load position of first item used.
000e14d8: nop
000e14dc: sll r3,r2,0x01
000e14e0: addu r3,r3,r2
000e14e4: sll r3,r3,0x01
000e14e8: addu r3,r3,r19        // (position of first item * 6) + constant
000e14ec: lbu r2,0x0002(r3)     // get quantity of first used item
000e14f0: nop
000e14f4: bne r2,r0,0x000e150c  // branch if nonzero
000e14f8: nop
000e14fc: lui r2,0x800f
000e1500: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e1504: nop
000e1508: sh r2,0x0000(r3)
000e150c: lui r3,0x800f
000e1510: lhu r3,0x562c(r3)     // 800F562C -> Load position of first item used.
000e1514: nop
000e1518: sll r2,r3,0x01
000e151c: addu r2,r2,r3
000e1520: sll r2,r2,0x01
000e1524: addu r2,r2,r19        // (position of first item * 6) + constant
000e1528: lbu r3,0x0002(r2)     // get quantity of first used item
000e152c: nop
000e1530: addiu r3,r3,0x0001    // Add 1 to it
000e1534: sb r3,0x0002(r2)      // save new value
000e1538: lui r3,0x800f
000e153c: lhu r3,0x562c(r3)     // 800F562C -> Load position of first item used.
000e1540: nop
000e1544: sll r2,r3,0x01
000e1548: addu r2,r2,r3
000e154c: sll r2,r2,0x01
000e1550: addu r3,r2,r19        // (position of first item * 6) + constant
000e1554: lbu r2,0x0002(r3)     // get quantity of first used item
000e1558: nop
000e155c: sltiu r2,r2,0x0064    // is it less than 100?
000e1560: bne r2,r0,0x000e156c  // branch if so
000e1564: ori r2,r0,0x0063      // Quantity limit (99)
000e1568: sb r2,0x0002(r3)      // save capped quantity


000e1534 thru 000e155b is a big block o' repetition.  unless there's something i'm missing that can branch to the middle of that, skip all that crap.  we only need to load the quantity once, and we only need to save it once.

the "dumb loop" i mentioned above to track down a slot with matching Item ID can go further up.  is 800F562C normalized to the first item slot; that is, will it hold 0 when the first W-Item used was in Slot 0?  hopefully, that's the case, and the constant offset is confined to r19; that'll make the search loop easy to write.  but if not, and 800F562C includes some constant, then i need to know what that value is.

also, i'm hoping that 0x800F314E indeed holds the ID of first W-Item used all of the time, and isn't one of the things that's clobbered by an intervening Morph?  (i'd previously assumed the worst and discounted the utility of this variable despite your clear commenting of it.  but then i realized its loading is skipped over most of the time, so there's a good chance it's *not* culpable for incrementing the wrong item.)

anyway, assuming the above two questions have the pleasant answers, then it should be fairly easy.  we start at Item Slot 0, and check for an Item ID that matches our first chosen one.  if it matches, we increment quantity there.  if not, we proceed to the next slot by advancing a counter by 6, etc.

----

EDIT 2: actually, as a compromise, we should check for an Item ID match at the 800F562C slot before embarking on the possibly slow loop.  takes more space than dropping that 000e14d0 code, but probably worth the speed savings it has 99+% of the time.

Ortew

#4
Quoteis 800F562C normalized to the first item slot; that is, will it hold 0 when the first W-Item used was in Slot 0?
Yes, it doesn't include any constant. Slot 0 is 00, Slot 1 is 01... Slot 15 is 0F, etc...

Quotealso, i'm hoping that 0x800F314E indeed holds the ID of first W-Item used all of the time, and isn't one of the things that's clobbered by an intervening Morph?
Yes, I confirm it. Potion=00, S Potion=01, Ink=3A, etc...

I have done some tests and I also admit that using "Steal" or "Morph" does not write anything in the address 0x800F314E or 0x800F562C.

When using W-item, it stores the Item ID (only of first item used) in the address 0x0x800F314E but it seems that it doesn't use it.

Ah, from the beginning I moved the code to a free space that does not use the game.

assassin

#5
excellent on both answers!  this will keep things fairly simple.

also glad you found free space.  that should accommodate the monstrosity i post tomorrow. ;)

June 20, 2018, 03:47:23 AM - (Auto Merged - Double Posts are not allowed before 7 days.)

okay, here's my stab at making the item add-back robust for intervening Morph/etc.

it's written in the original, wrong function, because you never posted your fixed code.  so you'll have to adapt this to your other routine.  no clue whether this'll even assemble, let alone run.

000e14a4: ori r2,r0,0x0002
000e14a8: lui r3,0x800f
000e14ac: lbu r3,0x38a4(r3)     // 800F38A4=02 -> Phase 2 of W-Item
000e14b0: nop
000e14b4: bne r3,r2,noAddBack   // branch if phase not 2
000e14b8: ori r2,r0,0x000a
000e14bc: lui r3,0x800f
000e14c0: lbu r3,0x389d(r3)     // 800F389D=0A -> W-Item Menu
000e14c4: nop
000e14c8: bne r3,r2,noAddBack   // branch if menu ID not 10
000e14cc: nop

// Add cancelled first item for W-Item back to inventory, using one of three
// methods, in order of preference:
//
//   1) The slot from where the item was used, provided it still has at least 1
//      of that type.
//   2) The first slot that does hold at least 1 of that type.
//   3) The first slot with 0 item quantity (i.e. an empty slot).

000e14d0: lui r2,0x800f
000e14d4: lhu r2,0x562c(r2)     // 800F562C -> Load position of first item used.
000e14d8: nop
000e14dc: sll r3,r2,0x01
000e14e0: addu r3,r3,r2
000e14e4: sll r3,r3,0x01
000e14e8: addu r3,r3,r19        // (position of first item * 6) + constant
000e14ec: lbu r2,0x0002(r3)     // get quantity of item in slot we used from
000e14f0: nop
000e14f4: beq r2,r0,findMatch   // branch if zero, indicative of nothing there.
000e14f8: nop
000e14fc: lui r2,0x800f
000e1500: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e1504: nop
000e1508: lhu r3,0x0000(r3)     // get ID of item in slot we used from
000e150c: nop
000e1510: beq r2,r3,useCurrent  // branch if it indeed matches the actual item used.
                                //  intervening Morph/etc can cause mismatch.
000e1514: nop

findMatch:
000e1518: or r3,r0,0            // start loop counter at item slot 0
findLoop1:                      // our original slot was claimed, so next, look for
                                //  a matching item anywhere in inventory.
000e151c: addu r3,r3,r19        // (position of current item * 6) + constant
000e1520: lbu r2,0x0002(r3)     // get quantity of item in current slot
000e1524: nop
000e1528: beq r2,r0,nextSlot    // branch if zero, indicative of nothing there.
000e152c: nop
000e1530: lui r2,0x800f
000e1534: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e1538: nop
000e153c: addiu sp,sp,-4        // can probably skip this and other SP instruction,
                                //  as they're used very close to each other, and
                                //  just save and load from -4(sp).  but in
                                //  off-chance something can interrupt these, i
                                //  want to be safe.
000e1540: sw r3,0(sp)           // save our loop counter to stack
000e1544: nop
000e1548: lbu r3,0x0000(r3)     // get ID of item in current slot
000e154c: nop
000e1550: xor r2,r3,r2          // does it match that of first item used?  save test
                                //  result in r2.
000e1554: lw r3,0(sp)           // retrieve our loop counter from stack
000e1558: nop
000e155c: addiu sp,sp,4
000e1560: beq r2,r0,useCurrent  // branch if current slot's item matches the first
                                //  item used; we've found somewhere to add it back.
000e1564: nop
nextSlot:
000e1568: subu r3,r3,r19        // temporarily convert loop counter to 0-based index
000e156c: addiu r3,r3,6         // advance to next item slot
000e1570: sltiu r2,r3,1536      // are we pointing past the 256th (0-based) slot?
000e1574: bne r2,r0,findLoop1   // loop and check next slot if not
000e1578: nop

findEmpty:
000e157c: or r3,r0,0            // start loop counter at item slot 0
findLoop2:                      // we found no matching contents, so next, look for
                                //  our first empty slot.
000e1580: addu r3,r3,r19        // (position of current item * 6) + constant
000e1584: lbu r2,0x0002(r3)     // get quantity of item in current slot
000e1588: nop
000e158c: beq r2,r0,useCurInit  // branch if zero, indicative of nothing there,
                                //  which is our desired home at this point.
000e1590: nop
000e1594: subu r3,r3,r19        // temporarily convert loop counter to 0-based index
000e1598: addiu r3,r3,6         // advance to next item slot
000e159c: sltiu r2,r3,1536      // are we pointing past the 256th (0-based) slot?
000e15a0: bne r2,r0,findLoop2   // loop and check next slot if not
000e15a4: nop

000e15a8: beq r0,r0,noAddBack   // all 3 methods couldn't find a suitable slot, so
                                //  don't add back item anywhere.
000e15ac: nop

useCurInit:
000e15b0: lui r2,0x800f
000e15b4: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e15b8: nop
000e15bc: sh r2,0x0000(r3)      // save Item ID in our chosen slot

useCurrent:
000e15c0: lbu r2,0x0002(r3)     // get quantity of item in chosen slot
000e15c4: nop
000e15c8: addiu r2,r2,0x0001    // Add 1 to it
000e15cc: sb r2,0x0002(r3)      // save new quantity
000e15d0: nop
000e15d4: sltiu r2,r2,0x0064    // is it less than 100?
000e15d8: bne r2,r0,keepVal     // branch if so
000e15dc: ori r2,r0,0x0063      // Quantity limit (99)
000e15e0: sb r2,0x0002(r3)      // save capped quantity
keepVal:
noAddBack:


some comments:

1) with the simplicity of the algorithm (just a couple dumb loops), i actually thought i had a chance of fitting into existing space after cutting Square's redundant code.  but instead, it's about 120 bytes over! :-[  while i may have ...overestimated... my abilities, it's more due to me forgetting how stupid MIPS is:
a. EVERY instruction, no matter how simple, is 4 bytes.  even the NOPs.
b. and you'll be needing plenty of those NOPs, to avoid errors due to pipelining.

2) i do use the stack briefly to save something.  hopefully, there's space.  and resorting to this ties into #3 some.

3) part of the reason MIPS instructions are 4 bytes is because they're flexible enough to let you use any of their 32 registers as operands.  however, that accomplishes jack squat when you don't know WHICH registers are free.  that disassembly only shows r2 and r3 being modified, so i stuck to that, often frustratingly.  my ignorance of this game definitely comes into play here.  it would be nice to:

a. save the first empty slot # during findLoop1, so findLoop2 doesn't need to happen.
b. save 0x800f314e, so i never have to reload using 8 bytes of instructions.
c. merge the initial item slot check based on 0x800f562c into findLoop1, using a flag to differentiate whether we're in that initial case or in the loop phase.
d. never use the stack, though 4 bytes stored briefly should be safe.

4) is r19 a constant value, or can it vary?  if the former, we can speed up the loops a little by dropping the (r3 - r19) subtraction, and not having to add it back each pass.

5) is the in-battle item list 256 entries long?  if not, modify the two "sltiu r2,r3,1536" accordingly.

6) i don't entirely follow how the pipelining works, so a couple of my NOPs might be unnecessary.  but hopefully, none are missing.

Ortew

Thanks for posting your code.

The truth is that I have not modified anything of the original code, I have only moved it where it should be so that it works correctly.

I have some doubts (I am noob with MIPS code).

I can not enter instruction "or r3,r0,0"..., so I do not know if you really mean "or r3,r0,r0" or "ori r3,r0, 0x0000" or simply I don't know hoy to enter it.

Quote5) is the in-battle item list 256 entries long?  if not, modify the two "sltiu r2,r3,1536" accordingly.

I do not know why 0x1536 is related to 256.
Anyway, the last slot is 013F (319).

assassin

#7
you're welcome.

- oops. "ori r3,r0,0x0000" is what i had in mind, but either of those should work.
- 1536 decimal, not hex.  it's related because each item slot takes 6 bytes, and i thought there were likely 256 of them.
- thanks.  does the game expect any slot (e.g. the last one) to always be Empty or have a specific item, or can they all hold anything?  (FF6 will have the "Equip Anything" bug with Optimum if the last slot isn't empty, so just checking.)

-------

Quote from: mehowever, i wouldn't be surprised if the game already has an Add_Item_ID_to_Inventory function that you can call.

come to think of it, Morph or Steal probably call such a function.  if you know in advance the item type they're going to increment and where in the list it's located, can you set a data breakpoint to figure out where the inventory gets updated?  if i can follow the found routine enough (i.e. am reasonably confident calling it won't conflict with other things, and it's not using mystery variables or temporary item buffers), then we can call that instead of doing my two dumb loops, and save a lot of space.

but first, let's get the posted code running!

Ortew

At the moment I have not managed to make your code work. It does not give any error but does not cause it to return the item.

I made those breakpoints that you said... I wrote part of the code on google drive because it's too long.
Here is the link: https://docs.google.com/document/d/1crBWecggu6CnjJJ8MEoEdSr9SgXY9cPB8FEhf-McmNE/edit?usp=sharing

assassin

#9
argh, one problem was early on:

000e1508: lhu r3,0x0000(r3)     // get ID of item in slot we used from
000e150c: nop
000e1510: beq r2,r3,useCurrent  // branch if it indeed matches the actual item used.
                                //  intervening Morph/etc can cause mismatch.


unlike in the loop after it, i forgot to preserve and restore r3 before the branch.

28 bytes added to fix.  man, it'd be nice if we knew whether more than two registers were free...

then a typo using the wrong variable size:

000e1548: lbu r3,0x0000(r3)     // get ID of item in current slot

should be a half-word.

here's the fixed code:

000e14a4: ori r2,r0,0x0002
000e14a8: lui r3,0x800f
000e14ac: lbu r3,0x38a4(r3)     // 800F38A4=02 -> Phase 2 of W-Item
000e14b0: nop
000e14b4: bne r3,r2,noAddBack   // branch if phase not 2
000e14b8: ori r2,r0,0x000a
000e14bc: lui r3,0x800f
000e14c0: lbu r3,0x389d(r3)     // 800F389D=0A -> W-Item Menu
000e14c4: nop
000e14c8: bne r3,r2,noAddBack   // branch if menu ID not 10
000e14cc: nop

// Add cancelled first item for W-Item back to inventory, using one of three
// methods, in order of preference:
//
//   1) The slot from where the item was used, provided it still has at least 1
//      of that type.
//   2) The first slot that does hold at least 1 of that type.
//   3) The first slot with 0 item quantity (i.e. an empty slot).

000e14d0: lui r2,0x800f
000e14d4: lhu r2,0x562c(r2)     // 800F562C -> Load position of first item used.
000e14d8: nop
000e14dc: sll r3,r2,0x01
000e14e0: addu r3,r3,r2
000e14e4: sll r3,r3,0x01
000e14e8: addu r3,r3,r19        // (position of first item * 6) + constant
000e14ec: lbu r2,0x0002(r3)     // get quantity of item in slot we used from
000e14f0: nop
000e14f4: beq r2,r0,findMatch   // branch if zero, indicative of nothing there.
000e14f8: nop
000e14fc: lui r2,0x800f
000e1500: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e1504: nop
000e1508: addiu sp,sp,-4        // can probably skip this and other SP instruction,
                                //  as they're used very close to each other, and
                                //  just save and load from -4(sp).  but in
                                //  off-chance something can interrupt these, i
                                //  want to be safe.
000e150c: sw r3,0(sp)           // save our loop counter to stack
000e1510: nop
000e1514: lhu r3,0x0000(r3)     // get ID of item in current slot
000e1518: nop
000e151c: xor r2,r3,r2          // does it match that of first item used?  save test
                                //  result in r2.
000e1520: lw r3,0(sp)           // retrieve our loop counter from stack
000e1524: nop
000e1528: addiu sp,sp,4
000e152c: beq r2,r0,useCurrent  // branch if it indeed matches the actual item used.
                                //  intervening Morph/etc can cause mismatch.
000e1530: nop

findMatch:
000e1534: ori r3,r0,0x0000      // start loop counter at item slot 0
findLoop1:                      // our original slot was claimed, so next, look for
                                //  a matching item anywhere in inventory.
000e1538: addu r3,r3,r19        // (position of current item * 6) + constant
000e153c: lbu r2,0x0002(r3)     // get quantity of item in current slot
000e1540: nop
000e1544: beq r2,r0,nextSlot    // branch if zero, indicative of nothing there.
000e1548: nop
000e154c: lui r2,0x800f
000e1550: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e1554: nop
000e1558: addiu sp,sp,-4        // can probably skip this and other SP instruction,
                                //  as they're used very close to each other, and
                                //  just save and load from -4(sp).  but in
                                //  off-chance something can interrupt these, i
                                //  want to be safe.
000e155c: sw r3,0(sp)           // save our loop counter to stack
000e1560: nop
000e1564: lhu r3,0x0000(r3)     // get ID of item in current slot
000e1568: nop
000e156c: xor r2,r3,r2          // does it match that of first item used?  save test
                                //  result in r2.
000e1570: lw r3,0(sp)           // retrieve our loop counter from stack
000e1574: nop
000e1578: addiu sp,sp,4
000e157c: beq r2,r0,useCurrent  // branch if current slot's item matches the first
                                //  item used; we've found somewhere to add it back.
000e1580: nop
nextSlot:
000e1584: subu r3,r3,r19        // temporarily convert loop counter to 0-based index
000e1588: addiu r3,r3,6         // advance to next item slot
000e158c: sltiu r2,r3,0x0780    // are we pointing past the 320th (0-based) slot?
000e1590: bne r2,r0,findLoop1   // loop and check next slot if not
000e1594: nop

findEmpty:
000e1598: ori r3,r0,0x0000      // start loop counter at item slot 0
findLoop2:                      // we found no matching contents, so next, look for
                                //  our first empty slot.
000e159c: addu r3,r3,r19        // (position of current item * 6) + constant
000e15a0: lbu r2,0x0002(r3)     // get quantity of item in current slot
000e15a4: nop
000e15a8: beq r2,r0,useCurInit  // branch if zero, indicative of nothing there,
                                //  which is our desired home at this point.
000e15ac: nop
000e15b0: subu r3,r3,r19        // temporarily convert loop counter to 0-based index
000e15b4: addiu r3,r3,6         // advance to next item slot
000e15b8: sltiu r2,r3,0x0780    // are we pointing past the 320th (0-based) slot?
000e15bc: bne r2,r0,findLoop2   // loop and check next slot if not
000e15c0: nop

000e15c4: beq r0,r0,noAddBack   // all 3 methods couldn't find a suitable slot, so
                                //  don't add back item anywhere.
000e15c8: nop

useCurInit:
000e15cc: lui r2,0x800f
000e15d0: lhu r2,0x314e(r2)     // 0x800F314E -> Load ID of first item used.
000e15d4: nop
000e15d8: sh r2,0x0000(r3)      // save Item ID in our chosen slot

useCurrent:
000e15dc: lbu r2,0x0002(r3)     // get quantity of item in chosen slot
000e15e0: nop
000e15e4: addiu r2,r2,0x0001    // Add 1 to it
000e15e8: sb r2,0x0002(r3)      // save new quantity
000e15ec: nop
000e15f0: sltiu r2,r2,0x0064    // is it less than 100?
000e15f4: bne r2,r0,keepVal     // branch if so
000e15f8: ori r2,r0,0x0063      // Quantity limit (99)
000e15fc: sb r2,0x0002(r3)      // save capped quantity
keepVal:
noAddBack:


i also expanded the loops to operate on 320 item slots instead of 256.

do you need the address labels preceding every instruction (e.g. "000e15f4:")?  they're a pain in the ass to update constantly, so i'd prefer to avoid them if possible.

=======================================

as for the Steal/Morph code: thanks!  definitely helpful.

because that function does write to a buffer and use some mystery variables, i very likely won't be calling it.  however, i can definitely learn a lot from it.  for instance, i should probably modify my second "dumb loop" to also check for a null Item ID in a slot before assuming it's free, to be safe.  i'll either add that to the Quantity==0 check, or just replace it.

here's a more commented version in progress:
http://assassin17.brinkster.net/forum-posts/ff7-morph-steal-item-add-function-more-comments.txt

Ortew

#10
Your new code works perfectly. If you perform the action of the W-item in the middle of Steal or Morph, it returns the first item used in another slot.

But there is an unexpected error.  :banghead: When this happens the name of the item appears gray and can not be used again. Once the battle is over, the item can be used again normally.

I will investigate what value is what causes the item to appear gray.

PD: You don't need to write the address labels preceding every instruction.
PD2: How can I know if a register is not being used?

assassin

excellent.  but i'm still gonna add the Item ID == null check, to be safe.

weird.  my first theory would be that it's coming from failure to initialize 0x0003(item_list_offset) and 0x0004(item_list_offset).

in the W-Item code in 000e14a4, if Square encounters a 0 quantity (byte held at 0x0002(item_list_offset)) in the item slot they were gonna increment, they will also rewrite the Item ID (half-word held at 0x0000(item_list_offset)).  i used this as a model for my code, not having any idea what the other fields (of 6 bytes total per slot) do.

however, from since looking at the 000a5750 function to add Morph/Steal items, i can see Square also initializes 0x0003(item_list_offset) and 0x0004(item_list_offset).  i still have no clue what they're for (well, possibly for what you encountered :P), but should probably follow suit.  maybe Square was able to get away with not touching these in 000e14a4 because they were putting the item back in the same slot where it was used from, so the 0003 and 0004 variables might still hold the correct data.

now the difficulty is, 000a5750 can initialize them to many different things from different data structures, depending on the Item ID range: < 128, >= 128 and < 256, >= 256 and < 288, >= 288 and < 320, and >= 320 and < 384 (didn't know that last range existed).  reproducing this would take a TON of code.  hopefully, it can be limited.  which Item IDs are usable with W-Item?

normally, i'd be reluctant to look at hardcoded ID numbers, however much space it may save, because it's not robust, and wouldn't play nice with hacks that change item types.  however, since Square's already using the IDs in 000a5750 to decide which variables will be copied to 0x0003(item_list_offset) and 0x0004(item_list_offset), why should i argue? :D  for that matter, does the FF7 item data even support changing an item from one type to another?  FF6's does.  now, there are still some hardcoded checks that will bite you if ranges aren't expanded and/or exceptions aren't made, but item types aren't set in stone based on their IDs.

------

re PD: good!  that'll preserve some of my sanity.

re PD2: i'd look at what's physically before 000e14a4 and after 000e1568 (or really, what precedes+follows the section where you relocated this stuff to fix the main W-Item bug), and/or the code that's run in the callers of 000e14a4 and 000DF4E0 surrounding the calls.

if after our code completes, something writes to Register N before ever reading from it, that's a cue that it doesn't care what we did to its value.  as for stuff that runs before our code, if Register N can be assigned two totally unrelated things, depending on the path taken to reach that point, that's another cue the game won't care.

so yeah, surrounding code, both physically and chronologically (i.e. callers of the function we're editing).

Ortew

#12
I'm sorry to answer so late, but I've had a lot of work these days (in fact these nights). The good thing is that now, thanks to insomnia, I have time to look at these things (It's about 03:30 am in my country right now).

I have not said it before, but I wanted to thank you for taking your time working with this. I am learning a lot thanks to you.

------
About 6 bytes per slot in the item list:

- First two bytes are Item ID.

- Third byte is Item quanty.

- Forth byte indicates in whom the item can be used.

Example of values of this byte:
01: The item can be used on an ally or an enemy (The first selection is on an ally).
03: The item can be used on an enemy or an ally (The first selection is on an enemy).
05: The item can be used on all allies or all enemies (The first selection is on all allies).
07: The item can be used on all enemies or all allies (The first selection is on all enemies).
11: The item can only be used in only one ally.

This byte varies a lot with the items that can be used with W-item, since there are as many curative items as there are offensive items.

In your last code this byte was at "00" when you retrieved the item used with W-Item by performing the Steal/Morph glitch.

In the Steal/Morph code it is saved by the address 000a5838: sb r2,0x0003(r4).


- Fifth byte indicates the way in which this item can be used.

Example of values of this byte:

02 = Items that can be used in battle using Throw command.
08 = Items that can be used in battle using Item or W-Item command.
0A = Items that can not be used in battle.
0B = Items that can not be used in battle and that can not be stolen from you.

So all items retrieved with W-Item should have a value of "08" in this byte. In your last code they had a value of "0B" and for that reason they appeared with the name of gray color and they could not be used.

In the Steal/Morph code it is saved by the address 000a5918: sb r2,0x0004(r4).

- Sixth byte is always "00".

------

The items can be used by the W-Item command are those that have an ID from 00 to 45 (In hexadecimal).

------

In this link I have copied an extract of the code from the Battle file: https://docs.google.com/document/d/1RDMSyhtOtqI04q8svTZt7xLUZGRha8tSp9YCuEl7-hM/edit?usp=sharing


assassin

you're welcome.  it's fun to help tackle a longstanding bug.  and now that Slick's been down for over 2 months (seriously, wtf?!), i'm officially a displaced person.  filing the official paperwork for refugee status is tedious (if even possible in this climate...), so i instead divert myself with unfamiliar games. ;)  ff6hacking.com is always hospitable, but my weekly routine has still been thrown into disarray.

and thank you for answering my numerous questions.

-------

re: the 4th and 5th byte breakdowns:

great!  that'll help in completing the patch, and they've also been worked into the commented disassembly on my website.

"11: The item can only be used in only one ally."

to be sure: only one at a time, or is the targeting cursor unmovable?  also, i assume these values are in hex?  (they almost always are, but since i lack the game to test, i ask some normally silly questions.)

------

QuoteThe items can be used by the W-Item command are those that have an ID from 00 to 45 (In hexadecimal).

also very good news.  this should stop the patch from ballooning too much.  though i am still curious as to whether FF7's data allows changing the type of a given item, or if the types are firmly fixed by Item ID range.

------

as for the (Item ID == null) check i planned to add to or displace the (Item count == 0) check, i'm now leaning the latter.  Square's Morph/Steal add item code does not use count in determining emptiness.  and it's possible that if an item slot doesn't have all bytes reset when its contents are used up, i might falsely assume something still inhabits it.  a theoretical issue when adding to an otherwise full list, for instance.

------

re your link: also helpful.  a few things:

1) r1 looks like it'll be safe to use.  both for reading data to put in the fourth and fifth bytes of the item list, and as a general temporary register.  however, i'd like to see whether and how 0x000bb9b8 uses the register, to be sure.

2) what branches to or calls 000e14a0 or 000e14a4?  it doesn't matter much with your fix going elsewhere, but it's got me curious how that code is reached.

3) 000df4dc: 1040000b beq r2,r0,0x000df50c
000df4e0: 00000000 nop     ← This is where I relocate the code. The following code is similar to where it was originally. It seems that the Square programmer was wrong to place it.


what are you putting in the NOP slot?  are you aware of CPU pipelining, and that the instruction after 000df4dc will get run even when that branch is taken?  also, from what i've read, having jumps/branches in that "branch delay slot" (i.e. where the NOP originally is) is a no-no, that produces unpredictable or nonsensical behavior.

or phrased differently: do you want whatever's being put at 000df4e0 to run all the time, or just conditionally?  i'm too unfamiliar with 000df4dc and Function 000df2c8 in general to know what the goal is here.

Ortew

Quoteto be sure: only one at a time, or is the targeting cursor unmovable?  also, i assume these values are in hex?  (they almost always are, but since i lack the game to test, i ask some normally silly questions.)
You can move the cursor to select only one of the three allies.

Yes, the values are in hex.

Quote1) r1 looks like it'll be safe to use.  both for reading data to put in the fourth and fifth bytes of the item list, and as a general temporary register.  however, i'd like to see whether and how 0x000bb9b8 uses the register, to be sure.
000bb9b8: 27bdffe8 addiu r29,r29,0xffe8
000bb9bc: 3c02800f lui r2,0x800f
000bb9c0: 24424ad0 addiu r2,r2,0x4ad0
000bb9c4: 34030030 ori r3,r0,0x0030
000bb9c8: 3084ffff andi r4,r4,0xffff
000bb9cc: afbf0010 sw r31,0x0010(r29)
000bb9d0: a4430000 sh r3,0x0000(r2)
000bb9d4: 3c01800f lui r1,0x800f
000bb9d8: ac244ad4 sw r4,0x4ad4(r1)
000bb9dc: 3c01800f lui r1,0x800f
000bb9e0: ac244ad8 sw r4,0x4ad8(r1)
000bb9e4: 0c00b7e2 jal 0x0002df88
000bb9e8: 00402021 addu r4,r2,r0
000bb9ec: 8fbf0010 lw r31,0x0010(r29)
000bb9f0: 27bd0018 addiu r29,r29,0x0018
000bb9f4: 03e00008 jr r31


Quote2) what branches to or calls 000e14a0 or 000e14a4?  it doesn't matter much with your fix going elsewhere, but it's got me curious how that code is reached.
000e11e4: 30620020 andi r2,r3,0x0020 "Circle button"
000e11e8: 104000ad beq r2,r0,0x000e14a0
000e11ec: 30620040 andi r2,r3,0x0040 "X button"


Quote3)what are you putting in the NOP slot?  are you aware of CPU pipelining, and that the instruction after 000df4dc will get run even when that branch is taken?  also, from what i've read, having jumps/branches in that "branch delay slot" (i.e. where the NOP originally is) is a no-no, that produces unpredictable or nonsensical behavior.
I put the jump to the new code in 000df4e4.
At the end of the new code I put "jal 0x000bb9b8", followed by a NOP and the jump to 000df4e8.