[Technical][Scripts] Help Wanted for Dragonball Advance Adventure Randomizer

Started by Larsch, May 17, 2023, 06:38:14 AM

Previous topic - Next topic

Larsch

Hello!
I am looking for help for my Dragonball Advance Adventure Randomizer. The game has the possibility to change the playing character to someone else.
Based on that I used Ghidra to reverse Engineer the game and find how to Change the character for the story mode (normally it was just possible in an extra mode). I found it and a lot of different stuff that I can change now. As long as I can change an already existing function I'm fine but now I am at a point where I need to write my own function.
I want to change the character when loading the cutscene for a level. I know the memory address of the character and I know where the function for starting the cutscene is but I don't know how to write a function in Assembly. So I'm asking for help with that. The general plan would be to replace the Start_Cutscene function with something like that:

New_Function(int CutsceneID)
Start_Cutscene (int CutsceneID)
Switch(CutsceneID):
Case 0:
Character = 0x01
Break
Case 1:
Character = 0x02
Break
...

Anyone here who can help me? :)

Best Regards,
Lars

Colmines92

Please, share said addresses so someone can help you without losing time investigating first.

FAST6191

A very curious mental block there. I can understand if you maybe just inferred some things from a disassembly that this would be a leap but if you can follow along with code and edit it within a function/in place it is not a large leap at all.

I can make things complicated such that it reflects the possibilities within hardware but might as well go from scratch.

Running code from start to finish is OK if you are converting a file from one format to another but in the real world, and especially games which might literally be defined as a series of decisions and furthermore might lurch from one aspect of play to another from moment to moment, you are going to need to jump in and out of extras. Likewise replicating code in multiple places wastes both memory and makes things harder to change if you have 20 instances of it.
To this end we have functions. In higher level languages you call them, usually pass some data to them and get a result back as almost a black box. In assembly you get to handle the lot, as it is with most things in assembly.

Branches, return, push and pop. These are your four main aspects.
Branches are decisions. Different processors have different ones but if equal, if not equal, straight branch, less than, greater than, in range.... are the sorts of things you can expect. The GBA (in both ARM and THUMB modes, the ARM7 of the GBA having two operating modes for those just joining us) has less than some things (no particular floating point or the like after all unless you construct it with prior instructions).

https://problemkaputt.de/gbatek.htm#armopcodesbranchandbranchwithlinkbblbxblxswibkpt
https://problemkaputt.de/gbatek.htm#thumbopcodesjumpsandcalls
Such things will possibly be preceded by a compare where it sets a flag, many older systems will also be this. Compare and conditional branches are how you create loops (compare, branch if not equal is surely a classic IF ELSE, and with a counter becomes a FOR or WHILE pretty easily as well, though we also have interrupts to consider in those). SWI (software interrupt) is what others refer to as BIOS calls aka functions the BIOS provides normal code.

Returns are when you want the system itself to handle going back to the original location (or indeed next instruction location lest you end up in an infinite loop), in which case it (for some branch methods at least) takes over R14 (register 14, so you actually have even fewer registers for general operations than you think, R16 is usually the program counter aka PC aka where you are at give or take pipelines/timings, R15 is the stack pointer aka SP but more on that shortly)

Push and pop.
https://problemkaputt.de/gbatek.htm#thumbopcodesmemorymultipleloadstorepushpopandldmstm
As you have a sharply limited amount of registers and subroutines/functions are often their own little world you might want to get a new set for your function. Push the old/current set to memory and pop then back in when you are done.


All this is why you are strongly discouraged from calling a function from within a function in high level languages -- it has to handle now a list of return addresses, possibly sorting all the registers out and that eats into time, memory and useful instructions performed at best. Modern compilers are all about making this that bit easier (predicting how many registers are needed, seeing if straight jumps are doable, frontloading parts of the nested function within the base...) but GBA compilers were not that. There is also a way of programming for modern PCs call branch avoidance or branchless programming, this is as modern PC processors will try to predict the outcome of a branch long before it is taken and scrap it should it be proven wrong but the GBA does not have this (at best it has a read ahead for upcoming instructions).

Anyway have to run now but hopefully a bit of an overview.