sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

RecordDemo.js (5981B)


      1 /*
      2  * This is a companion file for the record_demo.inc.c enhancement.
      3  *
      4  * You will need the PJ64 javascript API to get this to work, so
      5  * you should download a nightly build from here (Windows only atm):
      6  * https://www.pj64-emu.com/nightly-builds
      7  *
      8  * Place this .js file into the /Scripts/ folder in the PJ64 directory.
      9  *
     10  * In the Scripts window, double click on "RecordDemo" on the list on the left side.
     11  * When this is done, it should turn green which lets you know that it has started.
     12  *
     13  * When your demo has been recorded, it will be dumped to the newly created
     14  * /SM64_DEMOS/ folder within the PJ64 directory.
     15  */
     16 
     17 var RAM_SIZE = 4 * 1048576 // 4 MB
     18 
     19 // Get a copy of the first 4MB of memory.
     20 var RAM = mem.getblock(0x80000000, RAM_SIZE)
     21 
     22 // Create SM64_DEMOS Directory if it already doesn't exist.
     23 fs.mkdir("SM64_DEMOS/");
     24 
     25 // string "DEMORECVARS"
     26 var pattern = [0x44, 0x45, 0x4D, 0x4F, 0x52, 0x45, 0x43, 0x56, 0x41, 0x52, 0x53, 0x00]
     27 
     28 var matches = find_matches_fast(pattern)
     29 
     30 if(matches.length > 1) {
     31     console.log('Error: More than 1 instance of "DEMORECVARS" was found. Abort!')
     32 } else if(matches.length < 1) {
     33     console.log('Error: No instance of "DEMORECVARS" was found. Abort!')
     34 } else {
     35     console.clear()
     36     var demoRecVarsLocation = 0x80000000 + matches[0] + 12
     37 
     38     // Control variables addresses
     39     var gRecordingStatus_vaddr = demoRecVarsLocation + 0
     40     var gDoneDelay_vaddr = demoRecVarsLocation + 4
     41     var gNumOfRecordedInputs_vaddr = demoRecVarsLocation + 8
     42     var gRecordedInputsPtr_vaddr = demoRecVarsLocation + 12
     43 
     44     console.log('Recording variables were found at address 0x' + demoRecVarsLocation.toString(16))
     45     console.log('Initialization successful! Press L in-game to ready the demo recording before entering in a level.')
     46 
     47     // This runs every frame that is drawn.
     48     events.ondraw(function() {
     49         var gRecordingStatus = mem.u32[gRecordingStatus_vaddr]
     50 
     51         if(gRecordingStatus == 3) { // gRecordingStatus == DEMOREC_STATUS_STOPPING
     52             var gNumOfRecordedInputs = mem.u32[gNumOfRecordedInputs_vaddr]
     53 
     54             if(gNumOfRecordedInputs < 1) {
     55                 console.log('Error: No inputs could be recorded!')
     56             } else {
     57                 var gRecordedInputsPtr = mem.u32[gRecordedInputsPtr_vaddr]
     58 
     59                 console.log('Recorded ' + gNumOfRecordedInputs + ' demo inputs.')
     60 
     61                 // Grab demo data from RAM.
     62                 var demo_data = mem.getblock(gRecordedInputsPtr, (gNumOfRecordedInputs + 1) * 4)
     63 
     64                 // Create filename with random id added onto it.
     65                 var filename = 'SM64_DEMOS/demo_' + get_random_int(0, 0xFFFFFFFF).toString(16) + '.bin'
     66 
     67                 // Dump demo data to file.
     68                 var file = fs.open(filename, 'wb');
     69                 fs.write(file, demo_data);
     70                 fs.close(file);
     71 
     72                 console.log('Dumped data to file ' + filename)
     73             }
     74 
     75             // Set status to DEMOREC_STATUS_DONE
     76             mem.u32[gRecordingStatus_vaddr] = 4;
     77 
     78             // Decomp memes
     79             console.log('OK');
     80         }
     81     })
     82 }
     83 
     84 function get_random_int(min, max) {
     85     min = Math.ceil(min);
     86     max = Math.floor(max);
     87     return Math.floor(Math.random() * (max - min + 1)) + min;
     88 }
     89 
     90 /*
     91  * Finds a byte pattern that is 4-byte aligned.
     92  *
     93  * The javascript api is pretty slow when reading memory directly,
     94  * so I made this to search a copy of RAM to make things a little faster.
     95  */
     96 function find_matches_fast(pattern) {
     97     var targetLength = pattern.length
     98     var targetLengthMinusOne = targetLength - 1
     99     var matches = []
    100     var matching = 0
    101 
    102   // Increments by 8 to speed things up.
    103     for(var i = 0; i < RAM_SIZE; i += 8) {
    104         if(RAM[i] == pattern[matching])
    105             matching++
    106         else
    107             matching = 0
    108         if(matching == targetLength) {
    109             matches.push(i - targetLengthMinusOne)
    110             matching = 0
    111         }
    112     if(matching > 0) {
    113         if(RAM[i + 1] == pattern[matching])
    114             matching++
    115         else
    116             matching = 0
    117         if(matching == targetLength) {
    118             matches.push(i + 1 - targetLengthMinusOne)
    119             matching = 0
    120         }
    121         if(matching > 1) {
    122             if(RAM[i + 2] == pattern[matching])
    123                 matching++
    124             else
    125                 matching = 0
    126             if(matching == targetLength) {
    127                 matches.push(i + 2 - targetLengthMinusOne)
    128                 matching = 0
    129             }
    130             if(matching > 2) {
    131                 if(RAM[i + 3] == pattern[matching])
    132                     matching++
    133                 else
    134                     matching = 0
    135                 if(matching == targetLength) {
    136                     matches.push(i + 3 - targetLengthMinusOne)
    137                     matching = 0
    138                 }
    139             }
    140         }
    141     }
    142 
    143     if(RAM[i + 4] == pattern[matching])
    144         matching++
    145     else
    146         matching = 0
    147     if(matching == targetLength) {
    148         matches.push(i + 4 - targetLengthMinusOne)
    149         matching = 0
    150     }
    151     if(matching > 0) {
    152         if(RAM[i + 5] == pattern[matching])
    153             matching++
    154         else
    155             matching = 0
    156         if(matching == targetLength) {
    157             matches.push(i + 5 - targetLengthMinusOne)
    158             matching = 0
    159         }
    160         if(matching > 1) {
    161             if(RAM[i + 6] == pattern[matching])
    162                 matching++
    163             else
    164                 matching = 0
    165             if(matching == targetLength) {
    166                 matches.push(i + 6 - targetLengthMinusOne)
    167                 matching = 0
    168             }
    169             if(matching > 2) {
    170                 if(RAM[i + 7] == pattern[matching])
    171                     matching++
    172                 else
    173                     matching = 0
    174                 if(matching == targetLength) {
    175                     matches.push(i + 7 - targetLengthMinusOne)
    176                     matching = 0
    177                 }
    178             }
    179         }
    180     }
    181   }
    182 
    183   return matches
    184 }