Basic Questions and Answers Thread

Discussion in 'Discussion & Q&A' started by Malevolence, Jul 7, 2009.

  1. Tanner

    Tanner Newcomer Trialist

    Joined:
    Dec 23, 2016
    Messages:
    8
    This is exactly what I meant.
    Thank you for explaining that.
     
  2. Pacguy

    Pacguy *boop* Member

    Joined:
    Jul 5, 2014
    Messages:
    1,016
    Location:
    Stardust Speedway
    I've run into an odd issue with the Aquatic Ruin Zone boss in every major Sonic 2 hack I've ever worked on. It seems to happen naturally whenever code/data/etc. is shifted around enough, and it has happened in both the Xenowhirl and GitHub disassemblies. I've been unable to figure out where the problem lies for quite some time now, and it's really starting to get on my nerves...

    The issue is that the Arrows the boss should shoot never appear; the spawn code for them runs, and it appears to work as intended, but there's no arrow after the fact; "Obj89_Arrow" (the routine for the entire arrow object) never even runs.

    I figured out the source of the problem while writing this post, but it still leaves me somewhat confused. Here's the code in question:
    Code:
    ; ===========================================================================
    ; loc_309A8:
    Obj89_Pillar:
        moveq    #0,d0
        movea.l    obj89_pillar_parent(a0),a1 ; a1=object
    
    ;These two lines were added by me, and fix the problem
        cmp.b        #6,routine_secondary(a0)    ;are we an arrow?
        beq.s        Obj89_Pillar_Normal        ;if so, skip pillar only check
    
        cmpi.b    #8,boss_routine(a1)        ; has boss been defeated?
        blt.s    Obj89_Pillar_Normal        ; if not, branch
        move.b    #4,routine_secondary(a0)
    
    ; loc_309BC:
    Obj89_Pillar_Normal:
    
    The arrows run the pillar code by mistake because of the extra check that happens before the routine is run.

    Why the heck was this check programmed like this? And why does this code suddenly stop working when rebuilding? This issue has followed me over the years (It's the reason the Aquatic Ruin Zone boss doesn't appear in Robotnik Returns 2), so I'm really curious as to what's going on here...
     
    ProjectFM likes this.
  3. MarkeyJester

    MarkeyJester Blue hair? What a freak! Member

    Joined:
    Jun 27, 2009
    Messages:
    2,605
    The pillars have the parent object (Robotnik) stored in their object RAM slot at 2A - 2D (obj89_pillar_parent), a long-word address pointing to the Robotnik object. The arrows (and the eyes of the stone) do not get the parent's address put into that slot location, so the arrows (and stone eyes) have the parent object address set to offset: 000000 (ala, the beginning of the ROM).

    That code above which the pillar, the arrow, and the stone eyes, run down, reads the parent object address (which in the arrow's case is 000000), and then accesses the 26th (boss_routine) byte from it. The 26th byte from the beginning of the ROM is the 68k vector table, containing pointers to error handler routines:

    [​IMG]

    The error handlers all point to an error trap loop (the blue box addresses), however, the object will be reading from 26 of the start of ROM (that red box) which is 02, and according to signed logic it is lower than 8. Therefore it gets through the comparison without a hitch.

    If you happen to have installed a new error handler, or put the "Error Trap" routine somewhere else, or put something between it and the header information, you will change the address of those 00000200's. Perhaps you have installed either flamewing's or vladikcomper's error handler nearer to the end of the ROM where the address is much higher, and that particular byte happens to be larger than 8. And then, it's secondary routine byte will be changed from 6 to 4, and the 4th routine happens to be the pillar moving down.

    You could point the parent object to a specific address, where the 26th byte will always be lower than 8, probably something like:
    Code:
    DummyObj:	dc.w	$0000
    ...and then:
    Code:
    movea.l	#DummyObj-boss_routine,obj89_pillar_parent(a1)
    I would suggest putting the actual Robotnik object address in there, but that seems like bad practice.

    Your method seems cover the problem very well, so I wouldn't bother with the above, just stick with what you have.
     
  4. Pacguy

    Pacguy *boop* Member

    Joined:
    Jul 5, 2014
    Messages:
    1,016
    Location:
    Stardust Speedway
    Are their equivalents to cmp or the various branch instructions (bgt, ble, etc.) that don't factor in negative numbers? I keep having values higher then $7F break my byte checks, and I usually just work around them with excess checks, which just looks and feels messy.
     
  5. Novedicus

    Novedicus Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    772
    Yes, there are.

    bcc/bhs = bge
    bcs/blo = blt
    bhi = bgt
    bls = ble

    Difference being they handled numbers as unsigned.
     
  6. Pacguy

    Pacguy *boop* Member

    Joined:
    Jul 5, 2014
    Messages:
    1,016
    Location:
    Stardust Speedway
    Hm, bhs or bcc don't seem to work quite as I hoped. Here's the check in question:
    Code:
        cmpi.b    #$6A,(Current_Zone).w    ;are we in Zone $6A?
        bge.s        LoadSomethingElse        ;if we're in zone $6A or higher, load something different instead.
        ;Something loads here I guess
    
    bhs and bcc seem to work in place of bge when Current_Zone is $6A, but when its' $A0, they both don't branch. Even bhi doesn't branch when Current_Zone is $A0.
     
  7. vladikcomper

    vladikcomper Well-Known Member Member

    Joined:
    Dec 2, 2009
    Messages:
    378
    Location:
    Russia
    Pacguy, $6A is not higher or the same as $A0, which is why branches are not taken.

    Code:
        move.b   #$A0, (CurrentZone).w   ; CurrentZone = $A0
       ;<...>
       cmp.b   #$6A, (CurrentZone).w   ; compare $6A with $A0
       bhs.s   LoadSomethingElse       ; if $6A >= $A0 (FALSE), branch
    
    The CMP opcode compares the source (left) operand with the destination (right) operand, in that order.
    As a generalized rule, the subject to a certain operation (opcode) is always the destination and the object is always the source. In other words: MOVE X, Y moves X to Y, not Y to X; SUB X, Y subtracts X from Y, not Y from X. Similarly, CMP X, Y compares X with Y, not Y with X.

    Internally, CMP subtracts source operand from the destination and sets conditional flags (CCR register) accordingly. This operation is almost identical to the SUB opcode, except the result is not stored in the destination, only conditional flags are affected.
     
    ProjectFM, Novedicus and Natsumi like this.