okay! you just smash my head with a hammer! stop being so awesome and tell me what you did
well, dont stop being awesome and share some information of how you did! that might help alot!
Well, the whole process was rather lengthily, but I'll try to break it down the best I can.
It all starts in your target character's ctor function. For my case, I used
ftMario.ctor.
ftMario.ctor is located at file offset 0x13C. In one of my earlier posts I discussed how
ftCharacter.ctor is responsible for creating the whole character memory object as well as all the module objects associated with it. In particular, we are interested in
soGenerateArticleManageModuleImpl which is responsible for generating and manipulating character articles (
characters without any articles will not implement this modules). Most
ftCharacter.ctor functions generally take the same shape. This means that for most module files, the part of
ftCharacter.ctor that calls
soGenerateArticleManageModuleImpl.ctor is usually around file offset 0x850. As it turns out, the constructor call for Mario's article module is located at file offset 0x838. Following this call we find out that the
soGenerateArticleManageModuleImpl.ctor function is located at file offset 0x5A74 inside ft_mario.rel
Before I go into the
soGenerateArticleManageModuleImpl.ctor function, I want to explain how the
soGenerateArticleManageModuleImpl object is stored in memory. It is difficult to describe it using memory offsets, so I'll describe it as a basic C++ definition.
struct soGenerateArticleManageModuleImpl
{
soArrayVector<wnArticle *, N> (size: 0xC + N * 0x4)
soArrayVector<wnArticleEventObserver, N> (size: 0xC + N * 0x10)
struct soArticleMediatorImpl
{
soInstancePool<...>
soInstancePoolSub<...>[N]
}
}
It may not make a whole lot of sense as it's just a definition, but try to keep it in mind when reading about the constructor function.
For Mario, the constructor function performs the following actions at the associated file offsets:
0x5A94 soArrayVector<wnArticle *, 5>.ctor(this)
0x5AA4 soArrayVector<wnArticleEventObserver, 5>.ctor(this + 0x20)
0x5AB4 [this + 0x7C] = soArticleMediatorImpl.declaration
0x5AA8 var soArticleMediator_ptr = this + 0x7C
Notice that the offsets 0x20 and 0x7C are equal to the summed sizes of the previous elements. That is:
0xC + 5 * 0x4 = 0x20
0xC + 5 * 0x10 = 0x5C
0x20 + 0x5C = 0x7C
This is important as you must make sure all you objects are not overlapping with other objects in memory when it comes to adding your own articles.
After these 3 base actions have been performed, there's two ways the function can go about creating articles. The first is the most basic; the constructor function does this starting at 0x5AC4:
0x5AC0 var soInstancePool<...>_ptr = this + 0x84
0x5ACC [this + 0x84] = soInstancePool<...>.declaration
// [this + 0x84] == [soArticleMediator_ptr + 0x8]0x5AD8 [this + 0x88] = soInstancePoolSub<wnMarioHugeFlame, ...>.declaration
// [this + 0x88] == [soInstancePool<...>_ptr + 0x4]0x5ADC var wnInstanceHolder<...>_ptr = soArticleMediator + 0xC
0x5AE8 [this + 0x90] = wnInstanceHolder<wnMarioHugeFlame, ...>.declaration
// [this + 0x90] == [wnInstanceHolder<...>_ptr + 0x00]0x5B2C ftDataProvider.get_wnMarioHugeFlame_.pac_data(character_id = 0)
0x5B40 wnMarioHugeFlame.ctor(wnInstanceHolder<...>_ptr + 0x4, ...)
That's probably a lot of information to take in there, but suffice it to say, those 7 primary instructions are responsible for creating the wnMarioHugeFlame object - Mario's Final Smash. Whenever you want to create an article that only appears as a single instance onscreen, then this is the structure that gets used. Besides the offsets and function references, the structure mostly stays the same.
The second method for generating articles is used for articles that appear as multiple instances onscreen - such as Mario's fireballs. They get created starting at 0x5BBC:
// update the soInstancePool declaration with a new one containing the articles we're going to add.
0x5BC4 [this + 0x84] = soInstancePool<...>.declaration
0x5AC0 var temp = this + 0x20000
// [this + 0x84] == [soArticleMediator_ptr + 0x8]// temp is used due to limitations in PowerPC's adding capabilities0x5BD4 [temp - 0x5600] = soInstancePoolSub<wnMarioFireball, ...>.declaration
0x5BD8 var soInstancePools_ptr = temp - 0x5FFC
0x5BE4 [temp - 0x5FFC] = soInstancePoolSub<wnMarioFireball, ...>.declaration
0x5BF0 [temp - 0x5FF8] = soInstancePoolSub<wnMarioFireball, ...>.declaration
0x5C00 [temp - 0x5FF4] = soInstancePoolSub<wnMarioFireball, ...>.declaration
0x5C0C [temp - 0x5FF0] = soInstancePoolSub<wnMarioFireball, ...>.declaration
// These are written immediately following the previously created article.
0x5C18 Create_wnMarioFireball_holder_and_article(soInstancePools + 0xC, ...)
0x5C24 Create_wnMarioFireball_holder_and_article(soInstancePools + 0x1F54, ...)
0x5C30 Create_wnMarioFireball_holder_and_article(soInstancePools + 0x3EA4, ...)
0x5C3C Create_wnMarioFireball_holder_and_article(soInstancePools + 0x5DEC, ...)
0x5C48 Create_wnMarioFireball_holder_and_article(temp + 0x2738, ...)
// Once again, these offsets are immediately following the soInstancePoolSubs we just created.
The
Create_wnMarioFireball_holder_and_article is another function inside ft_mario.rel - it can be found at file offset 0x7AF4. The function does exactly what it says.
So to summarize, the first method for creating articles has you updating the instance pool; creating the instance pool sub and instance holder; obtaining the .pac data; and creating the article all in the main function. The second method involves updating the instance pool, creating the instance pool subs and then calling the helper function to actually create the instance holder, obtain the .pac data, and create the article.
When it came to me adding ROB's laser, I replaced
Create_wnMarioFireball_holder_and_article's wnMarioFireball.ctor and
ftDataProvider.get_wnMarioFireball_.pac_data calls with calls to
wnRobotBeam.ctor and
ftDataProvider.get_wnRobotBeam_.pac_data. This wasn't a problem because both articles are Kirby-Copy articles meaning these functions were stored in sora_melee. When replacing the get .pac data function, it is also necessary to change the id being passed to it (
or you can leave Mario's get .pac data function and end up with a weird mixture of both of them like I mentioned above).
I should also mention that the actual
wnInstanceHolder and
soInstancePoolSub declarations don't need to be changed as they are just primitive data structures that all have exactly the same shape and functionality.
Additionally, I removed all but the first call to Create_wnMarioFireball_holder_and_article as wnRobotBeam is actually larger in size than wnMarioFireball and - like I said ealier - you can't have objects overlapping in memory (
you can find the size of wnMarioFireball by subtracting the offsets that are passed to Create_wnMarioFireball_holder_and_article).
Finally, I had to change the article count references and the call to
ftMarioTransactor.instantiate_wnMarioFireball located inside
soArticleMediatorImpl. If these count values weren't changed, then the game will crash by trying to access the other 4 article instances which I removed.
I've listed the basic details on that in one of my earlier posts, but to find everything you'll need to know the function listing of
soArticleMediatorImpl:
soArticleMediator : soArticleGenerator, soArticleOperator
soArticleGenerator
Method[0][0] Finalizer
Method[0][1] GenerateArticle(r4=id, r5=soModuleAccessor)
soArticleOperator
Method[1][0] Method[0][0] Thunk
Method[1][1] Method[1][8] Thunk
Method[1][2] ClearInstances()
Method[1][3] ClearInstances(r4=soModuleAccessor, r5=id)
Method[1][4] GetInstanceCount(r4=soModuleAccessor, r5=id)
Method[1][5] GetInstanceCap(r4=id)
Method[1][6] GetArticleCount()
Method[1][7] SetEnabled(r4=val)
Method[1][8] PSA_Event_1001(r4=soModuleAccessor, r5=soArticle)
A lot of this was trial and error and there isn't really a consistent way of finding everything. Even after doing all that, there were still a couple of bugs which I had to resolve using WiiRD - and I'm not even sure what exactly was causing it.
Anyways, make of it what you will. Hopefully you'll be able to work something out with it. Like I said before: it's pretty complicated.
... Long post is long.