185 lines
5.8 KiB
JavaScript
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
|
||
|
}
|