sm64pc/enhancements/RecordDemo.js

185 lines
5.8 KiB
JavaScript

/*
* This is a companion file for the record_demo.inc.c enhancement.
*
* You will need the PJ64 javascript API to get this to work, so
* you should download a nightly build from here (Windows only atm):
* https://www.pj64-emu.com/nightly-builds
*
* Place this .js file into the /Scripts/ folder in the PJ64 directory.
*
* In the Scripts window, double click on "RecordDemo" on the list on the left side.
* When this is done, it should turn green which lets you know that it has started.
*
* When your demo has been recorded, it will be dumped to the newly created
* /SM64_DEMOS/ folder within the PJ64 directory.
*/
var RAM_SIZE = 4 * 1048576 // 4 MB
// Get a copy of the first 4MB of memory.
var RAM = mem.getblock(0x80000000, RAM_SIZE)
// Create SM64_DEMOS Directory if it already doesn't exist.
fs.mkdir("SM64_DEMOS/");
// string "DEMORECVARS"
var pattern = [0x44, 0x45, 0x4D, 0x4F, 0x52, 0x45, 0x43, 0x56, 0x41, 0x52, 0x53, 0x00]
var matches = find_matches_fast(pattern)
if(matches.length > 1) {
console.log('Error: More than 1 instance of "DEMORECVARS" was found. Abort!')
} else if(matches.length < 1) {
console.log('Error: No instance of "DEMORECVARS" was found. Abort!')
} else {
console.clear()
var demoRecVarsLocation = 0x80000000 + matches[0] + 12
// Control variables addresses
var gRecordingStatus_vaddr = demoRecVarsLocation + 0
var gDoneDelay_vaddr = demoRecVarsLocation + 4
var gNumOfRecordedInputs_vaddr = demoRecVarsLocation + 8
var gRecordedInputsPtr_vaddr = demoRecVarsLocation + 12
console.log('Recording variables were found at address 0x' + demoRecVarsLocation.toString(16))
console.log('Initialization successful! Press L in-game to ready the demo recording before entering in a level.')
// This runs every frame that is drawn.
events.ondraw(function() {
var gRecordingStatus = mem.u32[gRecordingStatus_vaddr]
if(gRecordingStatus == 3) { // gRecordingStatus == DEMOREC_STATUS_STOPPING
var gNumOfRecordedInputs = mem.u32[gNumOfRecordedInputs_vaddr]
if(gNumOfRecordedInputs < 1) {
console.log('Error: No inputs could be recorded!')
} else {
var gRecordedInputsPtr = mem.u32[gRecordedInputsPtr_vaddr]
console.log('Recorded ' + gNumOfRecordedInputs + ' demo inputs.')
// Grab demo data from RAM.
var demo_data = mem.getblock(gRecordedInputsPtr, (gNumOfRecordedInputs + 1) * 4)
// Create filename with random id added onto it.
var filename = 'SM64_DEMOS/demo_' + get_random_int(0, 0xFFFFFFFF).toString(16) + '.bin'
// Dump demo data to file.
var file = fs.open(filename, 'wb');
fs.write(file, demo_data);
fs.close(file);
console.log('Dumped data to file ' + filename)
}
// Set status to DEMOREC_STATUS_DONE
mem.u32[gRecordingStatus_vaddr] = 4;
// Decomp memes
console.log('OK');
}
})
}
function get_random_int(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/*
* Finds a byte pattern that is 4-byte aligned.
*
* The javascript api is pretty slow when reading memory directly,
* so I made this to search a copy of RAM to make things a little faster.
*/
function find_matches_fast(pattern) {
var targetLength = pattern.length
var targetLengthMinusOne = targetLength - 1
var matches = []
var matching = 0
// Increments by 8 to speed things up.
for(var i = 0; i < RAM_SIZE; i += 8) {
if(RAM[i] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i - targetLengthMinusOne)
matching = 0
}
if(matching > 0) {
if(RAM[i + 1] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 1 - targetLengthMinusOne)
matching = 0
}
if(matching > 1) {
if(RAM[i + 2] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 2 - targetLengthMinusOne)
matching = 0
}
if(matching > 2) {
if(RAM[i + 3] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 3 - targetLengthMinusOne)
matching = 0
}
}
}
}
if(RAM[i + 4] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 4 - targetLengthMinusOne)
matching = 0
}
if(matching > 0) {
if(RAM[i + 5] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 5 - targetLengthMinusOne)
matching = 0
}
if(matching > 1) {
if(RAM[i + 6] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 6 - targetLengthMinusOne)
matching = 0
}
if(matching > 2) {
if(RAM[i + 7] == pattern[matching])
matching++
else
matching = 0
if(matching == targetLength) {
matches.push(i + 7 - targetLengthMinusOne)
matching = 0
}
}
}
}
}
return matches
}