• Welcome to Smashboards, the world's largest Super Smash Brothers community! Over 250,000 Smash Bros. fans from around the world have come to discuss these great games in over 19 million posts!

    You are currently viewing our boards as a visitor. Click here to sign up right now and start on your path in the Smash community!

Break the Targets Randomizer (& Custom Stage Handler)

djwang88

Smash Cadet
Joined
Mar 31, 2004
Messages
49
A few weeks ago, the Break the Targets community had the idea for a target coordinate modifier, and after doing some searching in this forum, I contacted Punkline for advice, on the basis that he seemed to have experience with stage modifications. Since then, Punkline wrote several versions of this code, culminating in a fully-featured Break the Targets custom stage handler, and it escalated into me harnessing it (at a high level) to build a Break the Targets randomizer with randomized target positions (with rules to prevent impossible targets), spawn positions, and allowing an arbitrary number of targets.

Generate randomizer Gecko codes here: bttrandomizer.com

Introduction video:

Custom stage handler code:
Rich (BB code):
-==-
 
BTT Custom Stage Handler 0.2.1
 
Installs custom Break the Targets stages
[Punkline]

NTSC 1.02 ----- 801c4228 --- 93c10018 -> branch
# BTT spawner is about to begin, loop isn't started yet
# at this moment, r3...r12 and r30 are free to use
# - r31 = rStage   -- base of global stage data
# - 0x8(sp) and 0xC(sp) should be free to use as padding left over in the stack frame
#
# --- Stack Symbols:
# These are stored in the runtime stack frame padding, where the next injection can make use of them
sp.xHead  = 0x8  # if a matching stage is found, then the sub-table header is copied here
sp.xArray = 0xC  # the current address within an interating array

# --- Custom Data Symbols:
# This is the header struct of each sub-table in private in-line data, included with code
data.xQR      = 0x0  # HWORD - data is put directly into QR7 load params, for compression type
data.xKey     = 0x2  # BYTE - identifies a stage, or terminates the array of sub-tables
data.xCount   = 0x3  # BYTE - counts the number of targets (allows for TSeak to be included)
data.xArrayXY = 0x4  # array of 'Count' elements of size 'QR.Type' (float, hword, or byte alignment)
data.mScale        = 0x3F000000 # 6-bit mask for QR scale
data.mCustomSpawn  = 0x00100000 # bool for including a custom spawn XY
data.mType         = 0x00070000 # 3-bit mask for QR type
data.mCount        = 0x0000FF00 # byte for counting targets
data.mKey          = 0x000000FF # byte for selecting a stage
QR.float = 0  # data type IDs
QR.byte  = 6  # signed fixed point byte
QR.hword = 7  # signed fixed point hword


# --- Stage IDs:

# DBG STAGES
stage.TEST = 0x01 # TEST stage

# STANDARD
stage.Izumi    = 0x02  # Fountain of Dreams (Izumi)
stage.Pstadium = 0x03  # Pokémon Stadium (Pstadium)
stage.Castle   = 0x04  # Princess Peach's Castle (Castle)
stage.Kongo    = 0x05  # Kongo Jungle (Kongo)
stage.Zebes    = 0x06  # Brinstar (Zebes)
stage.Corneria = 0x07  # Corneria
stage.Story    = 0x08  # Yoshi's Story (Story)
stage.Onett    = 0x09  # Onett
stage.MuteCity = 0x0A  # Mute City
stage.RCruise  = 0x0B  # Rainbow Cruise (RCruise)
stage.Garden   = 0x0C  # Jungle Japes (Garden)
stage.GreatBay = 0x0D  # Great Bay
stage.Shrine   = 0x0E  # Hyrule Temple (Shrine)
stage.Kraid    = 0x0F  # Brinstar Depths (Kraid)
stage.Yoster   = 0x10  # Yoshi's Island (Yoster)
stage.Greens   = 0x11  # Green Greens (Greens)
stage.Fourside = 0x12  # Fourside
stage.Inishie1 = 0x13  # Mushroom Kingdom I (Inishie1)
stage.Inishie2 = 0x14  # Mushroom Kingdom II (Inishie2)
stage.Akaneia  = 0x15  # Akaneia (Deleted Stage)
stage.Venom    = 0x16  # Venom
stage.Pura     = 0x17  # Poké Floats (Pura)
stage.BigBlue  = 0x18  # Big Blue
stage.Icemt    = 0x19  # Icicle Mountain (Icemt)
stage.Icetop   = 0x1A  # Icetop
stage.FlatZone = 0x1B  # Flat Zone
stage.ppp      = 0x1C  # Dream Land N64 (old ppp)
stage.yosh     = 0x1D  # Yoshi's Island N64 (old yosh)
stage.kong     = 0x1E  # Kongo Jungle N64 (old kong)
stage.battle   = 0x1F  # Battlefield (battle)
stage.last     = 0x20  # Final Destination (last)

# TARGET TEST
stage.TMario   = 0x21 # Mario (TMario)
stage.TCaptain = 0x22 # C. Falcon (TCaptain)
stage.TClink   = 0x23 # Young Link (TClink)
stage.TDonkey  = 0x24 # Donkey Kong (TDonkey)
stage.TDrmario = 0x25 # Dr. Mario (TDrmario)
stage.TFalco   = 0x26 # Falco (TFalco)
stage.TFox     = 0x27 # Fox (TFox)
stage.TIceclim = 0x28 # Ice Climbers (TIceclim)
stage.TKirby   = 0x29 # Kirby (TKirby)
stage.TKoopa   = 0x2A # Bowser (TKoopa)
stage.TLink    = 0x2B # Link (TLink)
stage.TLuigi   = 0x2C # Luigi (TLuigi)
stage.TMars    = 0x2D # Marth (TMars)
stage.TMewtwo  = 0x2E # Mewtwo (TMewtwo)
stage.TNess    = 0x2F # Ness (TNess)
stage.TPeach   = 0x30 # Peach (TPeach)
stage.TPichu   = 0x31 # Pichu (TPichu)
stage.TPikachu = 0x32 # Pikachu (TPikachu)
stage.TPurin   = 0x33 # Jigglypuff (TPurin)
stage.TSamus   = 0x34 # Samus (TSamus)
stage.TSeak    = 0x35 # Sheik (TSeak)
stage.TYoshi   = 0x36 # Yoshi (TYoshi)
stage.TZelda   = 0x37 # Zelda (TZelda)
stage.TGamewat = 0x38 # Mr. Game & Watch (TGamewat)
stage.TEmblem  = 0x39 # Roy (TEmblem)
stage.TGanon   = 0x3A # Ganondorf (TGanon)

# ADVENTURE MODE
stage.Kinoko  = 0x3B  # 1 Kinoko (Mushroom Kingdom Adventure)
stage.Meiktu  = 0x3F  # 3 Meiktu (Zelda Adventure[Underground Maze])
stage.Dassyut = 0x42  # 4 Dassyut (Escape from Brinstar Adventure)
stage.BRoute  = 0x49  # 8 B Route (F  //Zero Adventure[F  //Zero Grand Prix])

# BONUS STAGES
stage.Takisusume = 0x52 # Takisusume (Race to the Finish Classic)
stage.figureget  = 0x53 # Grab the Trophies (figureget)
stage.homerun    = 0x54 # Homerun Contest (homerun)
stage.Heart      = 0x55 # Heal (All-Star's Stage Inbetween Matches)

# --- Melee Data Symbols:
global.xStageID = -0x6CB8  # current stage ID -- see stage.* symbols for IDs


stw r30, 0x0018(sp) # original instruction

bl _get_data
# --- branch over inline data:
b _end_of_data; _get_data: blrl
  
  
  # --- Link:
  .byte 0, 0, 0, stage.TLink
  # scale = 0 (no scaling)
  # type  = 0 (floating points)
  # spawn = Default
  # count = 0 (becomes 10, to handle default)
  # key   = 0x2B (TLink)
  .float -20,30,   -10,30,   0,30,   10,30,   20,30
  .float -20,20,   -10,20,   0,20,   10,20,   20,20
  # 10 uncompressed floating point pairs
  
  
  # --- Fox:
  .byte 4, 7, 4, stage.TFox
  # scale = 4 (0x000.0)
  # type  = 7 (signed hwords)
  # spawn = Default
  # count = 4 (only 4 targets are set up)
  # key   = 0x27 (TFox)
  .hword -0x800,-0x800,   0x800,-0x800
  .hword -0x800, 0x800,   0x800, 0x800
  # 4 compressed hword pairs, using a scale of >>4
  
  
  # --- Mewtwo:
  .byte 0, 6, 15, stage.TMewtwo
  # type = 6 (signed bytes)
  .byte -20,-10,   -10,-10,     0,-10,    10,-10,    20,-10
  .byte -20,  0,   -10,  0,     0,  0,    10,  0,    20,  0
  .byte -20, 10,   -10, 10,     0, 10,    10, 10,    20, 10
  .align 2 # don't forget to align your small integer pairs
  
  
  # --- Game and Watch:
  .byte 0, 6, 45, stage.TGamewat
  # count = 50 (overflows icon display until 16 or fewer targets remain)
  
  y = -30
  .rept 5
    y = y + 10
    x = -40
    .rept 9
      x = x + 10
      .byte x, y
    .endr
  .endr
  .align 2  # assembler loop creates gird of 5 by 9 targets in GAW stage
  
  
  # --- null terminator
  .long 0
  
  
_end_of_data:
# now we start the injection code

rStage = 31; rData  = 30; rHead = 29; rOffset = 28
rKey   = 4;  rArray = 5;  rBytes = 6; rTotal = 7; rQuery = 8; rHead = 9; rOffset = 10
# r31 is already written
# r30 and r5 will be overwritten before the next injection; but they are free now

mflr rData
li rOffset, 0
lwz rKey, global.xStageID(r13)
# Current stage ID is now in rKey
# rOffset is pointing to the next (first) header from 'rData'

_for_each_subtable:
  lwzux rHead, rData, rOffset  # -u = update rData;  -x = use another register for index
  andi. rQuery, rHead, data.mKey  # the '.' compares the result to 0 in cr0
  beq- _write_to_stack
  # terminate loop if a null stage key is found
    
    # --- get table pair size:
    li rBytes, 8
    rlwinm. r0, rHead, 16, data.mType>>16  # r0 = extracted 3-bit type
    cmpwi cr1, r0, 6
    # check if type is type 0, 6, or 7
    
    beq- 0f
      li rBytes, 2
      beq- cr1, 0f
        li rBytes, 4
        # select a data size accordingly
    0: rlwimi rHead, rBytes, 0, data.mKey
    # overwrite header key with size of table pair, for next injection to use
    
    # --- check if matching:
    cmpw cr1, rQuery, rKey
    rlwinm. rTotal, rHead, 24, data.mCount>>8
    bne+ 0f  # cr0 eq means count is empty
      li rTotal, 10
      # then assign default of 10 targets
    0:
    rlwimi rHead, rTotal, 8, data.mCount
    # update header word, in case it is saved for match
    
    beq- cr1, _write_to_stack
    # continue loop if no match is found by using rBytes to calculate offset of next header
    # else, make some modifications to header before saving it for next injection to use...
      
      _iterate:
      andis. r0, rHead, data.mCustomSpawn@h
      beq+ 0f
        addi rTotal, rTotal, 1
        # add 1 to count if custom spawn flag is true -- for finding next header
        
      0:
      mullw rOffset, rBytes, rTotal
      addi rOffset, rOffset, 4 + 3
      rlwinm rOffset, rOffset, 0, ~3
      # round offset up to nearest word, to catch any mis-aligned bytes
      
      b _for_each_subtable
      # get next subtable
      
_write_to_stack:
addi rArray, rData, 4
sub rArray, rArray, rBytes
stw rHead, sp.xHead(sp)
stw rArray, sp.xArray(sp)
# most of the work is now done before loop begins in the spawner function

_return_from_injection:
.long 0



NTSC 1.02 ----- 801c4244 --- 28050000 -> branch

# Injects into the middle of BTT target setup routine
# - the routine sets up targets by parsing a list of global pointers left by the stage file data
# - this list of pointers includes a bit over 20 spaces, where blanks are skipped over
# - BTT stages provide 10 JObjs that get listed here to produce a spawning point for target items

# The injection interrupts the check for whether or not these pointers are null
# - this gives it power over whether or not a target item is instantiated, and what joint is used

# The only volatile register in use right now is r5:
# - r3, r4, r6...r12 are free to read/write from/to
# - r5 is safe to read from, as the current JObj being loaded to create a reference for a new target
#   - overwriting r5 will affect if/what joint is used to load the next new target item

# saved registers are all in use:
# - r31 = rStage   -- base of stage
# - r30 = rTPoint  -- current index of array of target RefJObj pointers
# - r29 = rCount   -- the counter, for storing number of targets spawned -- we can edit this
# - r28 = rID      -- unknown ID that gets counted up with rCount, and is loop termination value
# - 0x8(sp) and 0xC(sp) should be free to use as padding left over in the stack frame

#
# --- Stack Symbols:
# These are stored in the runtime stack frame padding, where the next injection can make use of them
sp.xHead  = 0x8  # if a matchink stage is found, then the sub-table header is copied here
sp.xArray = 0xC  # the current address within an interating array

# --- Custom Data Symbols:
# This is the header struct of each sub-table in private in-line data, included with code
data.xQR      = 0x0  # HWORD - data is put directly into QR7 load params, for compression type
data.xKey     = 0x2  # BYTE - identifies a stage, or terminates the array of sub-tables
data.xCount   = 0x3  # BYTE - counts the number of targets (allows for TSeak to be included)
data.xArrayXY = 0x4  # array of 'Count' elements of size 'QR.Type' (float, hword, or byte alignment)
data.mScale        = 0x3F000000 # 6-bit mask for QR scale
data.mCustomSpawn  = 0x00100000 # bool for including a custom spawn XY
data.mType         = 0x00070000 # 3-bit mask for QR type
data.mCount        = 0x0000FF00 # byte for counting targets
data.mOffset       = 0x000000FF # byte for memorizing array element size
QR.float = 0  # data type IDs
QR.byte  = 6  # signed fixed point byte
QR.hword = 7  # signed fixed point hword

# --- Melee Data Symbols:
global.xStageID = -0x6CB8  # current stage ID -- see stage.* symbols for IDs

JObj.xFlags    = 0x14        # bools and small ints for JObj settings
JObj.xPosXY    = 0x38        # XY loaded/stored by a paired singles instruction
JObj.xPosX_MTX = 0x50        # absolute X used by camera MTX
JObj.xPosY_MTX = 0x60        # absolute Y used by camera MTX
JObj.xDesc     = 0x84        # point to JObjDesc for this JObj -- for instantiating new joints
JObj.mUSER_MTX = 0x00800000  # this mask can be ORed into a JObj bools to stop animated MTX updates

stage.xP1SpawnJObj = 0x280  # offset of pointer to live JObj used for player 1 spawner

func.HSD_JObjLoadJoint = 0x80370e44  # function for loading JObjs


# --- Register names:
rStage  = 31; rPoint = 30; rCount  = 29; rID    = 28
rOffset = 4;  rJObj  = 5;  rHead   = 6;  rArray = 7;  rSpawn = 8;  rTotal = 9;  rBackup = 10
# rJObj is the JObj we were modifying the JObj.xPosXY_MTX values of in previous concepts
# it has just been confirmed to be a valid pointer, so we KNOW this is a JObj belonging to target
# - we can just turn off MTX updates for this JObj to stop all animations from affecting it



lwz rHead, sp.xHead(sp)
andi. r0, rHead, data.mOffset
# if a matching key was found, it was replaced with an offset value in the stack frame
# - we can check it for 0 to see if this injection should mutate the target positions with rArray

beq- _return_from_injection
# if this stage is not keyed in custom data table, then ignore the code's mutation
# - else, mutate this iteration of the natural Target Spawner loop:
  
  # --- implement JObj limiter, for making fewer targets:
  rlwinm rTotal, rHead, 24, 0xFF
  cmpw cr1, rCount, rTotal
  # condition register 1 holds memory of comparison between rCount and rTotal
  
  blt cr1, _start_mutation
    
    # if rTotal >= rCount...
    li rJObj, 0
    b _return_from_injection
    # then nullify the JObj to prevent more from being spawned
    
  # else, begin the mutation of this joint...
  _start_mutation:
  lwz rArray, sp.xArray(sp)
  mr. rBackup, rJObj
  lwz rSpawn, stage.xP1SpawnJObj(rStage)
  # rSpawn keeps JObj of P1 spawn ready for edit and/or JObj cloning, for making dummy joints
  
  # --- implement custom spawn location:
  cmpwi rCount, 0
  bne+ _begin_check # if this is the first target...
    andis. r0, rHead, data.mCustomSpawn@h
    beq+ _begin_check # and mCustom Spawn is used...
      
      # then set up JObj spawner in r5:
      mr rJObj, rSpawn
      # this will create 2 passes in the first JObj write
      # - the first will be the spawn JObj write
      # - then, rBackup will be moved back to rJObj for a second pass -- to act on the first target
      
  # --- implement JObj spawner for making extra targets:
  _begin_check:
  cmpwi rJObj, 0
  blt+ _JObj_write  # if this stage target JObj pointer was blank...
    bgt+ cr1, _return_from_injection  # and target counter is > specified counter...
    # then return without creating more dummy joints
      
      subi rID, rID, 1  # ID is used as terminator check, so we stall it
      subi rPoint, rPoint, 4  # also stall pointer index, to prevent overflowing array
      
      # and generate a new JObj from the Spawner JObjDesc pointer:
      lwz r3, JObj.xDesc(rSpawn)
      lis r0, func.HSD_JObjLoadJoint@h
      ori r0, r0, func.HSD_JObjLoadJoint@l
      mtlr r0
      blrl  # this function will instantiate a new JObj, if given a JObjDesc
      mr rJObj, r3
      # rJObj now has a new dummy joint to make use of
      
  # --- implement XY translation and disabled MTX updates:
  _JObj_write:
  lwz rHead, sp.xHead(sp)
  lwz rArray, sp.xArray(sp)
  rlwinm rOffset, rHead, 0, data.mOffset
  andis. r0, rHead, data.mScale | data.mType@h
  # r0 = masked graphical quantization register data
  # - only the load portion (high end) of the register is used
  
  mtspr 919, r0
  # load masked data into QR7
  # spr 919 is the ID for GQR7
  # - 0...5 are used by the OS to create static scales/types for quick casting
  # - 6 and 7 can be used like volatile registers
  
  psq_lux f0, rArray, rOffset, 0, 7
  # paired singles quantized load -update -indexed
  # the ending '0, 7' means 'load pair, and use QR7'
  # - loads a pair using the type/scale defined in QR7
  # - uses rOffset to use size variable calculated in previous injection
  # - updates rArray += rOffset on load
  
  psq_st f0, JObj.xPosXY(rJObj), 0, 0
  # QR0 stores as uncompressed floating point singles
  # - JObj XY has been updated, for initialization of references made by other joints
  
  ps_merge10 f1, f0, f0
  # f0 pair = XY
  # f1 pair = YX -- swapped
  # - this allows us to store X and Y individually, for the MTX edit
  
  stfs f0, JObj.xPosX_MTX(rJObj)
  stfs f1, JObj.xPosY_MTX(rJObj)
  # manually overwrite un-paired X and Y from JObj MTX
  # - JObj will now always reference this position, since MTX updates are turned off with USER_MTX
  
  lwz r0, JObj.xFlags(rJObj)
  oris r0, r0, JObj.mUSER_MTX@h
  stw r0, JObj.xFlags(rJObj)
  # this flag will stop the MTX from updating from normal XYZ translation/scale/rotation properties
  # - joint will still update with direct edits to the MTX values
  # - this prevents animations from influencing targets, making them static
  
  stw rArray, sp.xArray(sp)
  # update array pointer for next iteration
  
  cmpw rSpawn, rJObj
  bne+ _return_from_injection
    mr rJObj, rBackup
    b _begin_check
  # rJObj will be == rSpawn if the spawner joint was just modified as part of a custom spawn
  # - in this case, a second pass is executed with the JObj held in rBackup
  # - else, in most cases it just returns from injection:
  
_return_from_injection:
cmpwi rJObj, 0  # original instruction
.long 0
Stage header specification:
Rich (BB code):
#+ 0xFF000000 = compression_scale value (signed)
#+ 0x00100000 = custom_spawn bool
#+ 0x00070000 = compression_type value (unsigned)
#+ 0x0000FF00 = target_count value (unsigned)
#+ 0x000000FF = stage_key ID (unsigned)
# - by leaving everything but the stage key as 0; default settings will be used
  
# if compression_type is 0, then each coordinate value in XY pair is 4 bytes
# if compression_type is 6, then each coordinate value in XY pair is 1 byte
# if compression_type is 7, then each coordinate value in XY pair is 2 bytes
# if custom_spawn is TRUE, then an extra XY coord pair is added after header to record location
# target_count determines the number of other XY coord pairs that follow
  
# -- if a stage_key ID is null, then it terminates the super-table (list of sub-tables)
# -- else, another sub-table follows this one
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Fantastic stuff, man! Glad to see this released!

It's really inspiring to see what extra utility you can get out of injection codes like these by wrapping a thoughtful GUI around it. Your webapp really makes this into a gem. I hope the BTT community has fun with this one!
 
Top Bottom