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 }