I did not expect to ramble this much but I ran some tests and left with more knowledge than I had hoped for. Therefore, obligatory long-post-warning.
The above solution works as long as you don't plan on spawning objects with that ID. For instance, Julian's mirror images (run+D or DJA) might be broken this way; same with who you're facing against in stage 5-5 or survival. I'd guess it'll always spawn the last entry of data.txt with that ID (or the first one, no idea, really, would need to be tested), so you could exploit that in a way.
In terms of exe-editing (I'm assuming you have a rough idea about how assembler-code works, otherwise this will probably make zero sense; so in case you don't, please read a few tutorials on that matter first), there are several things that you'd need to copy to truly replicate all ID properties. Just had a look into the exe file; for Julian, there are a few things which might be of interest...
AI
If I'm not mistaken, the AI-check should be located at 00405D1Ah:
ASM-Code:
00405D14 MOV ECX,DWORD PTR DS:[EAX+368] ; load pointer of object or smth
00405D1A CMP DWORD PTR DS:[ECX+6F4],34 ; ID = Julian? (34h = 52d)
00405D21 JNE 00405FC2 ; if this is not the case, yeet out
|
It's
immensely helpful to use the
DLL Framework as opposed to making your edits directly inside the exe so that things become more easily maintainable. In that case, you could do something like
ASM-Code:
invoke JmpPatch, 00405D14h, addr julian_ai_stuff
NOT_JULIAN_AI dd 00405D27h
JULIAN_AI dd 0042FE52h
julian_ai_stuff proc
MOV ECX,DWORD PTR DS:[EAX+368] ; line over written
CMP DWORD PTR DS:[ECX+6F4],34
je short return
cmp ecx,id ; ENTER YOUR NEW ID HERE, i.e. 400 or 190h (RadASM/MASM is defaulted to decimal)
je short return
jmp dword ptr [NOT_JULIAN_AI]
return: jmp dword ptr [JULIAN_AI]
julian_ai_stuff endp
|
If you directly hardcode your stuff inside the exe, you'd have to find some empty space, add the code there and adjust the JMPs accordingly (also you could probably write the addresses directly instead of constants). For the sake of brevity, I'm not going to write down the exact formula.
ARMOR
The coding for armor works similarly to AI. Note that IDs 208 (Henry arrow2) and 214 (John biscuit) are treated special, those always break the armor. Changing one of them to any other ID gives them the insta-armor-break-property. Look at 0042E7CEh:
ASM-Code:
0042E7CE CMP DWORD PTR SS:[ESP+14],34 ; is ID 52?
0042E7D3 JNE SHORT 0042E80A ; if not, go somewhere else
0042E7D5 MOV EAX,DWORD PTR DS:[EDI*4+ESI+194] ;
0042E7DC CMP DWORD PTR DS:[EAX+0B8],0F ; something with shaking...
0042E7E3 JG SHORT 0042E80A ; ...char will take normal dmg if >15*
0042E7E5 MOV EAX,DWORD PTR DS:[EBX*4+ESI+194] ; get object-slot of the attacker
0042E7EC MOV EAX,DWORD PTR DS:[EAX+368] ;
0042E7F2 MOV EAX,DWORD PTR DS:[EAX+6F4] ;
0042E7F8 CMP EAX,0D6 ; D6h = 214d = john_biscuit
0042E7FD JE SHORT 0042E80A ; if ^, go to armor broken
0042E7FF CMP EAX,0D0 ; do the same with D0h = 208 = henry_arrow2
0042E804 JNE 0042FDF2 ; armor unbroken, proceed with Julian normal armor behavior
|
*The bdefend-values of itr's hitting the character are piling up in this "shaking"-slot (decreases back to 0 over time). Once the value indicated at 42e7dc is surpassed, the next hit will do the damage a normal character would receive. That means: any subsequent itr will also accumulate points in the "fall"-slot which will ultimately lead to a knock-down once 70 is reached (also counts down over time). For the record, I have adopted the terminology used in the
LF2 Info spreadsheet.
MP REGEN
Julian recovers his MP by taking the base MP regen rate (+1/TU) and adding stuff on top of that (+2/TU, so +3/TU in total). The check for the respective ID is located at 0041FAC9h (note that Firzen and Julian share the same MP recovery rate), so you would add an additional ID-check there:
ASM-Code:
0041FAC9 CMP EDX,33 ; 33h = 51d = Firzen
0041FACC JE SHORT 0041FAD3 ;
0041FACE CMP EDX,34 ; 34h = 52d = Julian
0041FAD1 JNE SHORT 0041FAD8 ; neither of those two. JMP out to normal MP regen
0041FAD3 CDQ ; magic, sets EDX to 0, ignore
0041FAD4 SUB EAX,EDX ; effectively does nothing, ignore
0041FAD6 SAR EAX,1 ; <- this causes the extra-MP regen, would be 0 otherwise*
0041FAD8 MOV EDX,1F4 ; rest of MP regen code
....
|
*after a bit of tinkering around (didn't bother trying to understand the whole code), the additional mp/TU can be approximately defined by writing
MOV EAX,val
at 41fad3 where
val=500/ExtraMP
(the "MOV r,imm"-command occupies all 3 lines -- i.e. CDQ, SUB, & SAR -- in the above code). So, if you want +2 (which is exactly what the above code does), you'd write
mov eax,fa
(FAh = 250d), for even faster regen, you go with lower values such as
mov eax,64
. Fun fact: it seems capped at +5 which is only obtained when EAX = 0. Some weird nonlinear magic, not willing to investigate any further...
STAGE MULTIPLIER
The multipliers are determined starting at 00437964h:
ASM-Code:
00437964 ADD EAX,1 ; x1 for normal chars
00437967 CMP ECX,33 ; Firzen?
0043796A JNE SHORT 0043796F ; nope, check for more two lines down
0043796C ADD EAX,1 ; it's Firzen! Add 1 to the multiplier (now x2)
0043796F CMP ECX,34 ; Julian?
00437972 JNE SHORT 00437977 ; nope, proceed with actual multiplier-magic
00437974 ADD EAX,2 ; it's Julian! Add 2 to the multiplier (now x3)
00437977 ...
|
Basically, the code iterates through all characters in the selection screen and increases this multiplier for each.
Example: You pick 4 characters in the selection screen, Davis (1), Template (1), Julian (3), Firzen (2). The total counter will be
1+1+3+2=7
, so when you end up in a phase of the stage which spawns Bandits with something like
ratio: 1.2
, you'll end up with
floor(1.2*7) = 8
Bandits on the enemy team. Fun-fact: this is capped to 40, by the way.
Well, all for now, old man's getting tired.