News:

Don't forget to visit the main site! There's lots of helpful docs, patches, and more!

Main Menu

NEStroid tweaks and hexedits

Started by Stinktier, July 18, 2014, 02:27:26 PM

Previous topic - Next topic

Stinktier

Since i actually came up with a hex tweak that might be really useful to other level designers, i thought it would be great to have a thread (separete from all those SM tweaks) to collect and discuss changes that can be easily done with any hex editor. I hope this proposition is ok with you guys and gals.

==============
Name: Collision bug fix when unmorphing
Short description: makes it impossible to "morph through" a single row of solid tiles.
Known side effects: None so far and there shouldn't be.
String to find: 2DAD0103186908
Value to change: 08 to 00.
Link to further info, ASM and pictures: http://forum.metroidconstruction.com/index.php/topic,3171.0.html
Link to a video demonstrating before/after: http://instagram.com/p/qmfQMdyqZG/

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

EDIT: I tried to add this to the wiki, but couldn't find the 'modify page' link. Does editing the the wiki require a certain account status, or did somehow just miss it?

Grimlock

Sounds like a good idea for a thread.

Here's a previous thread that gets into detail on how to edit the title screen through HEX manipulation (making the game load the tiles you want and in which location).

http://forum.metroidconstruction.com/index.php/topic,2095.0.html

Zhs2

#2
Quote from: StinktierEDIT: I tried to add this to the wiki, but couldn't find the 'modify page' link. Does editing the the wiki require a certain account status, or did somehow just miss it?
You do because of spambots, and also because of my laziness in finding better ways to disallow spambots. I'll get right on elevating you. EDIT: Done.

Stinktier

Thanks Zhs2!

I've added the fix. I think it turned out a lot more collected and better explained than in the original thread in engine works, but i might have looked over some minor mistake or unclearness due to me speaking swenglish. The fix itself is so simple to do, but I think there needs to be some explanation as to why (or if) it should be fixed and what it affects. I hope the article didn't get too lenghty.

and thanks Grimlock for starting that topic. It will prove very handy when the time comes for title editing.  :^_^:

Grimlock

Is anyone aware of a hack or hex edit to trick the game into thinking the doors are already open? It would be great if the blue doors could be targeted specifically, and even better if by area. It would be great to eliminate the bubble doors in some areas and just have the room scroll when you move through the "door" opening tiles. I have some pretty creative uses for a modification like that.

snarfblam

This can be done with the built-in assembler. First, create a project under the project menu if you haven't already. Then open the general code file and add this:

    StopRightScroll:
        ldy #$02        ; Pretend there is a door on right

        .db $2C         ; BIT instruction, causes following LDY to be ignored!
       
    StopLeftScroll:
        ldy #$01        ; Pretend there is a door on left
   
        ; Set X to current nametable
        lda CartRAMPtr + 1
        lsr
        lsr
        eor #$01
        and #$01
        tax
       
        tya
        ora DoorOnNametable3,X
        sta DoorOnNametable3,X
        rts


You can call these two routines on screen load to stop scrolling as if there were a door bubble present. You can then add doorway tiles to create a capless door[1]. This is also good if you want to have a horizontal room that stops scrolling at the end without a blank map square next to it. This helps make better use of map space[2].

[spoiler=examples][/spoiler]

Keep in mind that the screen load routine is attached to the screen layout and not a map location, so it will run for every instance of a screen. So for example 1 above (screen 2F), select the screen in the editor, then select "Edit Screen Load Code" in the project menu, and add this:

; Brinstar Screen 2F
    jsr StopRightScroll

; Remember, kids, don't forget to RTS!
    rts


Also, if you have a door bubble on one side and not the other, it will not animate when you come out the side with the door bubble.

Grimlock

Thanks, that looks like a great solution.  How would you go about applying it to vertical scrolling area?  Would you place the doorway tiles in the appropriate location(s) and just skip adding the blue doors in the editor (no routine required)?

snarfblam

Yup. If I understand what you're after correctly, then this pretty much comes down to a scrolling issue more than a doorway issue, and since there is no scrolling issue in the vertical rooms, all you need to do is remove the door caps.

snarfblam


Grimlock

I had a thought (didn't know where to post this), with the Editroid bank swapping capabilities (currently used for animations) , would it be possible to have the game swap 2 banks for the animations for a portion of an area, then at some point through screen loading code swap to the next 2 banks.  The concept would be similar to a pallet swap room, instead of colors it would basically be a graphics swap room.  I know this would be useful because you could have a more diverse set of graphics available in an area as a whole.  You could change the look and feel halfway through the area.

snarfblam

DAMN IT STOP HAVING MY IDEAS!  :FURY:

Yes, this is easy to do.

$7801 - First CHR page
$7802 - Last CHR page
$7803 - Current CHR page
$7804 - Number of frames to display each CHR page (lower value = faster animation)

You'll want to update $7801-7803 any time you want to change the animation sequence (change all three to the same value to use a single, unanimated CHR bank). $7804 only needs to be updated if the animation rate needs to change.

So, for example, to change background animation to CHR banks $10-$11, you would do this:

lda #$11
sta $7802
lda #$10
sta $7801
sta $7803


Easy. :stern:

Grimlock

Sweet!  This is going to work great for Ridley, I already used up my tiles!

You better hurry up and get your hack finished so I can steal the rest of your ideas (telepathic extraction is too time consuming).  :lol:

Grimlock

Snarfblam, I'm planning on using the GFX bank swapping technique to change out my air tiles to text (at the end of areas).  The plan is to use the last set for in game text to implement the storyline.  My question is in regard to the CHR cycling order.  I can still do pretty much all of the animations I plan on doing with 3 banks if the swapping order is as follows : 1 2 3 2 - 1 2 3 2 - 1 2 3 2  and so forth.  That would leave "4" for where I need the text.  How would you recommend I achieve that order of swapping, or is it even possible?

snarfblam

This would involve changing the code that does the CHR cycling. Certainly doable. I can change it so there are four bytes that specify CHR pages to cycle, set up so you can load a table in a screen load routine. It might look something like this:

;CHR animation sequence: 5, 6, 7, 6
jsr LoadChrAnimationTable

.db $05, $06, $07, $06


Or, to show a single CHR page:

; CHR animation sequence: 8, 8, 8, 8
jsr LoadChrAnimationTable

.db $08, $08, $08, $08


You'll need to do this every place the player can enter a level: top and bottom of each elevator, and once on each end of a transition between normal cycling and the CHR page containing the text.

Grimlock

Snarfblam how monumental of a task would it be to copy a bank to create a new area.  Specifically Norfair, mainly to add a "Norfair" like area between Brinstar and Kraid.  Not so much for my current work in progress but for future works. 

I suppose Editroid wouldn't be compatible with that sort of modification to the Rom without being modified as well.

snarfblam

Frankly, I'm not sure. The necessary modifications to Editroid might be a bit tedious, but not complex. Off the top of my head, the modifications to the game would involve re-allocating CHR (very easy), allocating the remaining unused banks to the new level, probably involving some minor shuffling (certainly doable), adding in new elevator types to access the new area (doable), and adding some new area-initialization routines to the game engine bank (doable). They did a pretty good job of isolating level-specific code and data into a single bank, so that once you have a bank swapped in, most things will just work. Any code that operates based on area index (e.g. elevators, banks/CHR initialization) will need to be modified. There's always the possibility that I'm overlooking something significant.

So... it's probably doable, but it certainly wouldn't be worth the effort unless there is a good payoff. Personally, I've opted to go the "pseudo-area" route, where you can have a section of an area with different graphics, palettes, and even music. Of course, that doesn't help with the sort of memory constraint issues you've run into.

crazyal02

Is it impossible to increase the speed that the screen scrolls at when going through a door?

Grimlock

Quote from: crazyal02 on October 17, 2014, 08:47:54 PM
Is it impossible to increase the speed that the screen scrolls at when going through a door?

Absolutely, and it's a pretty substantial improvement (in my opinion).  Snarfblam worked up a scroll speed increase as part of the ASM modifications planned for Rogue Dawn.  I wouldn't be able to explain how he achieved it but I can tell you that it really does make a big difference in gameplay, especially when dealing with enemies near scroll transitions.

Stinktier

#18
New tweak: Minimizing the grating effect of the low health beep
One of the game's major annoyances. Well, not at least as much anymore following these steps.

1)Either find rom adress 03ce89 (for expanded roms) or search for the string 03CE80290FD00320F3CBA9 which should be unique.

2)0F is the number of frames for the signal to repeat itself. at first it might seem intuitive to at least double this value (hex 20) but don't do it, you'll get blaring "backing truck" noises - That's even more annoying. I found these values of interest:

1F - about half the tempo with the shortest possible note duration
1B - about the same but with a short slapback dublette. can't say which i prefer. this one sounds sophisticated at first.
15 and 17 - these sound exactly like a morning alarm clock from the 80s. better than the original in my opinion but the notes have a slightly too long duration. 1F and 1B are better.

if you experiment with this value you will notice that it sometimes has effect on the "you're dead"-tune, creating weird glissandoes or vibratoes. While fun it just doesn't sound right. key is to find a slow tempo with as short note duration as possible without getting audible effects on this tune. One setting even distorted the ingame music when samus was hit. I cannot understand why. The ones listed before are safe to my ears and experience so far.

Silly instagram movie showing difference between original value and 1F: https://instagram.com/p/zhdeTByqUc/

Stinktier

#19
Regarding door scroll speed. I looked into it. When samus is inside a door, the engine calls the scrolling routines that are used for the 'camera' to 'follow' samuses movements in a room, so to speak. hex editing these routines therefore would overshoot scrolling overall. I think the solution lies in how often the 'see if samus is in a door' routine is called per second. Metroid engine calls this routine twice each cycle, right at the beginning of it. Thrice would be nice.

Solution: The opcode for calling the subroutine is 20F1E1 (jsr $E1F1). Place another one or two of them anywhere in the main game loop and it should speed up the door transition. I tested it and it works just fine, both with three and four calls, although i'd prefer a speed of 3.5 calls  :eyeroll: But it works! I just plainly replaced all NARPASWORD with one or two of these calls and EA:d the rest because i think that password function has played out its role anyway.

While we're at it, the opcodes for forcing scroll 1px at any given direction are as follows

20D2E6 (JSR $E6D2) - call subroutine to scroll 1px right
20A7E6 (JSR $E6A7) - call subroutine to scroll 1px left
2019E5 (JSR $E519) - call subroutine to scroll 1px down
20F1E4 (JSR $E4F1) - call subroutine to scroll 1px up
2052E2 (JSR $E252) - force a toggling between vertical/horizontal scrolling. With hit detection/coordinate flip flop logic this could possibly be used to toggle mid-room.

It is possible to create a if-samus-is-in-door scroll up or down and then center subroutine by swapping these calls in the original subroutine and thus create the base of up/down doors, but i'm a long way from knowing how to correctly implement it.

Note that all these are based on the disassembly of vanilla metroid. adresses may vary.

Stinktier

#20
Two new (EDIT: three) mini-tweaks of doubtable use:

Change amount of missiles gained per missile expansion:
Location: 3dc0a (expanded rom)
Or search for string: a00184794c73cb60f007a905
Original value to edit: 05. Ie less for a hard mode of sorts/changing when some red/yellow doors can be opened.

Set delay time for game-freeze when an item is picked up or a boss is defeated. Note that the pick up jingle will be cut to the point where the timer reaches 0, so you need to edit the jingle to be faster or shorter in order to make it sound right. One increment or decrement of the value represents 10 frames worth of time.

Location: 3c969 (expanded rom)
Or search for the string: a9008d08018d0901a918
Original value to edit: 18 (equals 240 frames)

EDIT:
===
Autoball. Only keeps samus in morph ball-position for as long as you either 1)Hold down button pressed, or 2)Are in a narrow passage. Or to put it differently; samus will automatically un-morph when outside a narrow passage or when you release the button. It is more fluent but also something metroid players aren't used to, so... It goes to the "tweaks with possible but doubtable use" department. Also, if you auto-fire the down button, samus gets elevated since the morphball drops down from a higher position (if the button pressing is faster than the gravitational acceleration, samus will elevate). For this tweak to work, you need also to rewrite from which position samus is turned into a ball. Thus, this tweak is not complete in itself, it just was something that showed up as a side effect when i tried to rule out possible causes for something completely else.

String to find: a5122908d00424121033
Replace 24121033 with EAEAEAEA.


Stinktier

#21
Sometimes, the code does things twice without it being needed, or dumps values in dark places where they are forgotten. Or simetimes it isn't just the most time efficient way to do it. This post is reserved for whatever i find to improve either available space in the engine page to make room for new subroutines or for improving the overall execution speed.

Update: New optimizing stuff. Making multiplication and division smoother-er.
Simple tweaks: [spoiler]For convenience (and possibly for saving some space for other stuff), the programmers made a math subroutine which by bit shifting can perform some multiplications and divisions (x2,x4,x8,x16 and x32). While tidy, it's not the most time efficient way to go.

For x8 or /8 operations, doing them without calling this subroutine takes exactly the same amount of data but is 12 machine cycles shorter/faster per time it is performed. In one example, this means (i think) 24 cycles shorter per projectile that is updated (two jsr @6 cycles, two rts @ 6 cycles). I don't know how much it takes to make a noticeable effect but it's so simple to update so why not? Every little stream leads to a big river.

There's only a few places to update here.
Search for string: 0420c0c2 and replace c20c0c2 with 4a4a4a (this the division is in the update bullet routine).
Search for string: 8a20c6c2 and replace c20c6c2 with 0a0a0a (this is the multiplication in the same).

You get the picture... there's a couple more x8's (c20c6c2) and one more /8 to be found and replaced with 0a0a0a or 4a4a4a but i haven't looked them up and tested them. Glancing at the code i don't think they are called that often to make a difference. Still, it's super easy if you'd want to.

[/spoiler]
Tutorial for a little more advanced tweaks: [spoiler]
For replacing x16 or /16 shift calls, the route is not as straight.

Method 1 is cramming another bit shift into the routine on-site, and since space is scarce, that isn't possible without moving the routine to a bigger space, or some major overall restructuring. A lot of effort for little juice.

Another way (often called strength reduction) is to increase the highest/lowest knowable number for the variable/s in focus and therefore reduce the need of x16 or /16, so you can go along with on-site x8 or /8 (which ideally would be 14  cycles faster). The variable/s shouldn't be used in multiple places, or else something may break.

A third way is to implement undocumented/illegal opcodes. This can be rewarding and is easy to implement where applicable. Be advised that some obsolete emulators won't like this.


It takes a bit more to look for these possibilities.  Here's two examples of how to achieve it in one place:

Original code snippet:
LE3E5:  lda SamusHorzSpeedMax
   jsr Amul16       ; * 16
   sta $00            ;temporary zero page storages
   sta $02
   lda SamusHorzSpeedMax  ;refresh accumulator
   jsr Adiv16       ; / 16
   sta $01
   sta $03

Method #1 isn't applicable in this case as there's no room for it, lest you move it into another bank, but that's not optimizing.
Method #2: Strength reduction.
[spoiler]
pseudo: let samushorzspeedmax = x2 (that is, we've increased 12 to 24 and 18 to 30 where samushorzspeedmax is stored)
.Patch E3E5
ldaSamusHorzSpeedMax
asl asl asl         ;-14 cycles
sta $00
sta $02
lda SamusHorzSpeedmax
jsr Adiv32                      ;+2 cycles since you actually do a strength increase here
sta $01
sta $03
[/spoiler]

Method 3: Use of 'illegal' opcode. Easiest, most resoure efficient and most convenient way but won't work on every single bad or old emulator out there
[spoiler]
lax SamusHorzSpeedMax ;"illegal" opcode AF. Stores into A and X simultaneously. Saves 3 bytes for subsequent use
asl asl asl asl                ; one of the saved bytes is used here
sta $00
sta $02
txa                              ;transfers the register dublette to the accumulator. Another freed up byte is used here.
lsr lsr lsr lsr             ;and the third is used here. It saves a total of 23 cycles per procedure.
sta $01                            ;since you skip 2 calls and returns, but use one extra cycle for LAX.
sta $03
[/spoiler]
Conclusion: If modern and/or accurate emulators and real hardware are what people use the most, a conciderable amount of cycles can be saved in total by implementing a combination of these tweaks. All counted together they could possibly help against slowdowns. LAX/LXA is a pretty awesome quirk of the 6502 that can be used in even more places than exemplified above it is also one of the few stable unofficial operations.
[/spoiler]

====

Set Samus to Roll subroutine - saves 15 bytes for more instructions/small new subroutine.

[spoiler]Original code (with additional comments on where i cleaned some stuff):

SetSamusRoll:
LD0B5:  lda SamusGear
and #gr_MARUMARI
beq +    
lda SamusGravity ;as snarfblam commented, omitting this will make samus able to roll in to a ball if she already
bne +                 ;were a ball, rolled off a cliff, unmoporphed, and tries to morph again mid-air. i think it's okay.

;Turn Samus into ball
ldx SamusDir
lda #an_SamusRoll
sta AnimResetIndex
lda #an_SamusRunJump
sta AnimIndex
lda LCCC0,x
sta SamusHorzAccel
lda #$01                    ;stores value at a location which is never loaded again.
sta $0686                   ;it is safe to regard this a production leftover.
jmp SFX_SamusBall

*       lda #sa_Stand       ;these two lines seems to be omittable aswell. gives another 5 bytes.   
sta ObjAction         ;for details see my next post           
rts


Cleaned up code suggestion - frees up 15 bytes for new minisubroutine:

SetSamusRoll:
LD0B5:  lda SamusGear
and #gr_MARUMARI
beq +    ; branch if Samus doesn't have Maru Mari
ldx SamusDir
lda #an_SamusRoll
sta AnimResetIndex
lda #an_SamusRunJump
sta AnimIndex
lda LCCC0,x
sta SamusHorzAccel
jmp SFX_SamusBall
*      rts

[/spoiler]
A value is also loaded into the unused variable in the routine which handles jump, hi-jump and if samus is to screw attack, but cleaning that routine up only yields 5 bytes which is pretty pointless on its own.

snarfblam


LD0B5:  lda SamusGear
and #gr_MARUMARI
beq +    
lda SamusGravity ;presumably thought to be a safety condition so that samus can't morph in certain conditions
bne +                 ;but doesn't seem to affect anything in itself. I tried a lot of stuff.


If you convert this to pseudo-code, you get something like this:


-Check if Samus has marumari
-Exit if not obtained
-Check Samus' vertical speed
-Exit if non-zero


IIRC, the code is intended to keep the player from morphing while in the air while in a standing pose. Try rolling off a ledge, pressing up to unmorph, and pressing down again to remorph.

More importantly, if you're willing to wait for Editroid 4.0, an MMC3 ROM makes bank-switching easier, so you can put new code and data into a different bank. If you need to keep it in the game engine bank for performance reasons (e.g. you don't want to bank-swap inside a loop) you can relocate some existing code from the game engine bank to the new bank. In my case, I relocated the code that displays the HUD because it is pretty self-contained.

Of course, that doesn't address the performance issues that cause slowdown, but to fix that you would have to do more than cut out a few cycles here and there.

Stinktier

#23
Thanks! Admittedly, i overlooked doing that combination of moves. I figured out the gravity /= 0 -> branch part but couldn't find if it ever did any difference. But there it is: 'standing' samus in the air. Well then. It works for me as i'm almost done with a tweak that lets samus roll into a ball if she is somersaulting, horizontal keys buttons are let go during the course of the jump, and down is pressed, as sort of a 'secret' move. So my level design is adapted for a ruleset which in some conditions allows samus to morph mid-air for some minor sequence breaking.

Main issue right now is to cram in a branch where samus animation index is reset to somersault, lest shoot button (and, in my case, down button) is pressed which branches this. I got it to work by replacing one with the other, but of course i'd want both, so it requires some shuffling. The thing is enveloped into the SamusRun subroutine, which has some JSR:s in it in the end, but a long length of it is just loads, compares, branches and stores.

The MMC3 mapper is great news! Though, doesn't it divide banks in smaller segments? But more space for engine rom would be great. Now i just need to learn how to call something from a different page (not to mention bank), haha.



And yeah... if i looked up every single thing which stored stuff at locations which are never loaded again i could probably free up 20-40 bytes or compress the code to perform 20-40 cycles less per main game loop, but it's not like it will change much in terms of performance. Stuff like how often bit shifts/multiplication/division is called probably does a lot more for performance. I think the sweeping arc of enemy class rios/gerutas/holtz is one such issue, but i also think their specific behaviour is half the charm of nestroid (something i missed in SM), so i don't know what to make of it.

Edit:
I messed around a bit more for learning purposes. Right before the RTS in the setsamusroll, there's:

lda sa_stand
sta objAction


This assumes that if samus isn't able to morph, she is to be standing under all circumstances. However, testing around, that doesn't seem to be needed, because if the conditions decide the morph code part of the subroutine isn't to be executed, samus is already in a desired state (that is, standing, jumping, running). The game loop makes sure that samus is handled anyway. Haven't detected any bugs so far blanking this part out. Maybe that will surface when accessing the morph code from new, modded conditions. But that could save 5 more bytes. If one is willing to let samus unmorph and morph again during roll-off falls, that is a total of 15 bytes that can be easily placed in a row. Enough for a small new subroutine.

In opcode and data, that would be:

AD7868 ;loads samus have-gear 'table'.
2910     ;and-compares if samus has marumari
F015     ;branches the whole next segment if not. Since code is shortened, a change from 1f to 15 has been made.

a64d     ;loads samus direction into x
a916     ;loads samus roll anim into a
8d0503  ;stores a into animation reset index
a913     ;loads samus run-jump animation into a
6d0603  ;stores a into current animation 'frame'. It will show samus kneeling for one frame before turning into a morphball.
bdc0cc  ;loads lccc0, x into a (i think this is a load from another page? is this how it's done?)
8d1503  ;stores a to samuses horizontal acceleration

4cefcb   ;jumps to play morph SFX
60        ;return from subroutine. this is where the previous branch is to be set.
eaea
eaeaea ;the amount of bytes to overwrite with a new subroutine.
eaea    ;i use this to do my four calls to the door scrolling routine
eaeaea ;to save even more space for something else in the beginning
eaea    ;of the main game loop.
eaeaea
[/code

snarfblam

#24
Quote from: Stinktier on March 06, 2015, 03:18:41 AM
The MMC3 mapper is great news! Though, doesn't it divide banks in smaller segments? But more space for engine rom would be great. Now i just need to learn how to call something from a different page (not to mention bank), haha.

MMC3 divides banks into smaller sizes, yes. It lets you load two $2000-byte banks instead of one $4000-byte bank, which is actually very handy. For example, I put all the structures used to build an area's screens in one bank, and then I put the actual screen data in whatever banks have space. Being able to load two banks at once means I don't need to duplicate data across multiple banks and I have more freedom as far as where things go.

If you're using Editroid, you can call code in another bank like this. Just be careful not to pull the rug out from under your own feet.


; Call routine defined in another bank
    ldx #MMC3SLOT_PRG8000           ; Load bank $22 into $8000-$9FFF region
    ldy #$22                        ;
    jsr SelectMMC3Bank              ;
   
    jsr NewRoutine                  ; Call new code
   
    jsr RestoreLevelBank            ; Restore the normal bank configuration when you're done
   
; New routine in another bank
    .PATCH 11:8000                  ; Beginning of bank 22
    NewRoutine:
        ; blah
        ; blah
        ; do stuff
        ; blah
        rts


Edit: One confusing thing (which I actually messed up above and fixed just now) is that the assembler used by Editroid assumes $4000-byte banks. So when you want to place code into bank $22, as far as the assembler is concerned, that is bank $11.