add new 60FPS patch by Emil; nuke old patches

This commit is contained in:
fgsfds 2020-07-07 20:52:51 +03:00
parent 442ef7665f
commit 796e884f04
10 changed files with 1962 additions and 1811 deletions

1955
enhancements/60fps_ex.patch Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
# L-trigger mapping
Some parts of the code might require the pressing of the L-trigger for testing reasons.
If you need that, alter `controller_sdl.c`.
In the following line:
```
if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) pad->button |= Z_TRIG;
```
Replace `Z_TRIG` with `L_TRIG`, save and rebuild.
On a DS4, this now means that Z-trigger will be mapped to L2 and the L-trigger to L1.

View file

@ -10,40 +10,16 @@ to the source code.
Likewise, to undo the changes from a patch you applied, run
`tools/revert_patch.sh` with the name of the .patch file you wish to undo.
To create your own enhancement patch, switch to the `master` Git
branch, make your changes to the code (but do not commit), then run `tools/create_patch.sh`. Your changes will be stored in the .patch file you specify.
To create your own enhancement patch, switch to the `nightly` Git
branch, make your changes to the code (but do not commit), then run `tools/create_patch.sh`.
Your changes will be stored in the .patch file you specify.
The following enhancements are included in this directory:
## Crash Screen - `crash.patch`
## 60 FPS - `60fps_ex.patch`
This enhancement provides a crash screen that is displayed when the code throws a hardware exception. This may be useful for diagnosing crashes in game code.
This allows the game to be rendered at 60 FPS instead of 30 FPS by interpolation (game logic still runs at 30 FPS).
## Debug Box - `debug_box.patch`
The Mario head intro is the only exception which is still rendered at 30 FPS.
This allows you to draw 3D boxes for debugging purposes.
Call the `debug_box` function whenever you want to draw one. `debug_box` by default takes two arguments: a center and bounds vec3f. This will draw a box starting from the point (center - bounds) to (center + bounds).
Use `debug_box_rot` to draw a box rotated in the xz-plane. If you want to draw a box by specifying min and max points, use `debug_box_pos` instead.
## iQue Player Support - `ique_support.patch`
This enhancement allows the same ROM to work on both the Nintendo 64 and the iQue Player.
## Memory Expansion Pak Error Screen - `mem_error_screen.patch`
Use this patch if your game requires over 4 MB of memory and requires the
Expansion Pak. If the Expansion Pak is not present, an error message will be
shown on startup.
## Demo Input Recorder - `record_demo.patch`
This patch allows you to record gameplay demos for the attract screen. It requires the latest nightly versions of Project64, and uses the Project64 JavaScript API to dump the demo input data from RAM and write it to a file.
Place the `enhancements/RecordDemo.js` file in the `/Scripts/` folder in the Project64 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 Project64 directory.
This is the 60fps patch from [sm64-port](https://github.com/sm64-port/sm64-port/tree/master/enhancements) adapted for sm64ex.

View file

@ -1,184 +0,0 @@
/*
* 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
}

View file

@ -1,486 +0,0 @@
diff --git a/asm/crash.s b/asm/crash.s
new file mode 100644
index 00000000..7c272050
--- /dev/null
+++ b/asm/crash.s
@@ -0,0 +1,153 @@
+# SM64 Crash Handler
+# See Readme below.
+
+.include "macros.inc"
+
+/* ---------------------------------------------------------------
+ * IMPORTANT README:
+ * ---------------------------------------------------------------
+ * Frame buffer emulation is required. To enable it in GlideN64,
+ * check "Emulate frame buffer" and "Render frame buffer to output"
+ * in the "Frame buffer" tab.
+ *
+ * Your emulator's CPU core style should be set to interpreter for best results.
+ *
+ * See the DEBUG_ASSERT macro on how to call the crash screen for
+ * detected exceptions.
+ *
+ */
+
+.set noat
+.set noreorder
+.set gp=64
+
+.set COP0_CAUSE, $13
+.set COP0_EPC, $14
+.set COP0_BADVADDR, $8
+
+glabel crashFont
+ .incbin "enhancements/crash_font.bin"
+ .align 4
+
+glabel exceptionRegContext
+ .fill 0x108
+
+glabel pAssertFile
+ .dword 0
+glabel nAssertLine
+ .dword 0
+glabel pAssertExpression
+ .dword 0
+glabel nAssertStopProgram
+ .dword 0
+
+glabel _n64_assert
+ lui $at, %hi(pAssertFile)
+ sw $a0, %lo(pAssertFile)($at)
+ lui $at, %hi(nAssertLine)
+ sw $a1, %lo(nAssertLine)($at)
+ lui $at, %hi(pAssertExpression)
+ sw $a2, %lo(pAssertExpression)($at)
+ lui $at, %hi(nAssertStopProgram)
+ sw $a3, %lo(nAssertStopProgram)($at)
+ beqz $a3, .end_2
+ nop
+ syscall # trigger crash screen
+.end_2:
+ jr $ra
+ nop
+
+glabel cop0_get_cause
+ jr $ra
+ mfc0 $v0, COP0_CAUSE
+
+glabel cop0_get_epc
+ jr $ra
+ mfc0 $v0, COP0_EPC
+
+glabel cop0_get_badvaddr
+ jr $ra
+ mfc0 $v0, COP0_BADVADDR
+
+# If the error code field of cop0's cause register is non-zero,
+# draw crash details to the screen and hang
+#
+# If there wasn't an error, continue to the original handler
+
+glabel __crash_handler_entry
+ mfc0 $k1, COP0_CAUSE
+ andi $k1, $k1, (0x1F << 2)
+ beqzl $k1, .end2 # exit if ExCode is 0
+ lui $k0, %hi(__osException)
+ la $k0, exceptionRegContext
+ sd $zero, 0x018 ($k0)
+ sd $at, 0x020 ($k0)
+ sd $v0, 0x028 ($k0)
+ sd $v1, 0x030 ($k0)
+ sd $a0, 0x038 ($k0)
+ sd $a1, 0x040 ($k0)
+ sd $a2, 0x048 ($k0)
+ sd $a3, 0x050 ($k0)
+ sd $t0, 0x058 ($k0)
+ sd $t1, 0x060 ($k0)
+ sd $t2, 0x068 ($k0)
+ sd $t3, 0x070 ($k0)
+ sd $t4, 0x078 ($k0)
+ sd $t5, 0x080 ($k0)
+ sd $t6, 0x088 ($k0)
+ sd $t7, 0x090 ($k0)
+ sd $s0, 0x098 ($k0)
+ sd $s1, 0x0A0 ($k0)
+ sd $s2, 0x0A8 ($k0)
+ sd $s3, 0x0B0 ($k0)
+ sd $s4, 0x0B8 ($k0)
+ sd $s5, 0x0C0 ($k0)
+ sd $s6, 0x0C8 ($k0)
+ sd $s7, 0x0D0 ($k0)
+ sd $t8, 0x0D8 ($k0)
+ sd $t9, 0x0E0 ($k0)
+ sd $gp, 0x0E8 ($k0)
+ sd $sp, 0x0F0 ($k0)
+ sd $fp, 0x0F8 ($k0)
+ sd $ra, 0x100 ($k0)
+ # cop unusable exception fired twice on startup so we'll ignore it for now
+ li $t0, (0x0B << 2)
+ beq $k1, $t0, .end
+ nop
+ jal show_crash_screen_and_hang
+ nop
+ .end:
+ ld $at, 0x020 ($k0)
+ ld $v0, 0x028 ($k0)
+ ld $v1, 0x030 ($k0)
+ ld $a0, 0x038 ($k0)
+ ld $a1, 0x040 ($k0)
+ ld $a2, 0x048 ($k0)
+ ld $a3, 0x050 ($k0)
+ ld $t0, 0x058 ($k0)
+ ld $t1, 0x060 ($k0)
+ ld $t2, 0x068 ($k0)
+ ld $t3, 0x070 ($k0)
+ ld $t4, 0x078 ($k0)
+ ld $t5, 0x080 ($k0)
+ ld $t6, 0x088 ($k0)
+ ld $t7, 0x090 ($k0)
+ ld $s0, 0x098 ($k0)
+ ld $s1, 0x0A0 ($k0)
+ ld $s2, 0x0A8 ($k0)
+ ld $s3, 0x0B0 ($k0)
+ ld $s4, 0x0B8 ($k0)
+ ld $s5, 0x0C0 ($k0)
+ ld $s6, 0x0C8 ($k0)
+ ld $s7, 0x0D0 ($k0)
+ ld $t8, 0x0D8 ($k0)
+ ld $t9, 0x0E0 ($k0)
+ ld $gp, 0x0E8 ($k0)
+ ld $sp, 0x0F0 ($k0)
+ ld $fp, 0x0F8 ($k0)
+ ld $ra, 0x100 ($k0)
+ lui $k0, %hi(__osException)
+ .end2:
+ addiu $k0, $k0, %lo(__osException)
+ jr $k0 # run the original handler
+ nop
diff --git a/lib/asm/__osExceptionPreamble.s b/lib/asm/__osExceptionPreamble.s
index 865273d9..f9ce7596 100644
--- a/lib/asm/__osExceptionPreamble.s
+++ b/lib/asm/__osExceptionPreamble.s
@@ -15,8 +15,8 @@
.endif
glabel __osExceptionPreamble
- lui $k0, %hi(__osException)
- addiu $k0, %lo(__osException)
+ lui $k0, %hi(__crash_handler_entry)
+ addiu $k0, %lo(__crash_handler_entry)
jr $k0
nop
diff --git a/sm64.ld b/sm64.ld
index e6f5c942..c0feb343
--- a/sm64.ld
+++ b/sm64.ld
@@ -116,6 +116,7 @@ SECTIONS
BUILD_DIR/src/game/rendering_graph_node.o(.text);
BUILD_DIR/src/game/profiler.o(.text);
BUILD_DIR/asm/decompress.o(.text);
+ BUILD_DIR/asm/crash.o(.text);
BUILD_DIR/src/game/camera.o(.text);
BUILD_DIR/src/game/debug_course.o(.text);
BUILD_DIR/src/game/object_list_processor.o(.text);
diff --git a/src/game/crash.c b/src/game/crash.c
new file mode 100644
index 00000000..716adfbd
--- /dev/null
+++ b/src/game/crash.c
@@ -0,0 +1,260 @@
+/* SM64 Crash Handler */
+
+#include <sm64.h>
+
+#include "crash.h"
+
+extern u32 exceptionRegContext[];
+
+extern char *pAssertFile;
+extern int nAssertLine;
+extern char *pAssertExpression;
+extern int nAssertStopProgram;
+
+u16 fbFillColor = 0xFFFF;
+u16 fbShadeColor = 0x0000;
+u16 *fbAddress = NULL;
+
+extern u8 crashFont[];
+
+const char *szErrCodes[] = {
+ "INTERRUPT",
+ "TLB MOD",
+ "UNMAPPED LOAD ADDR",
+ "UNMAPPED STORE ADDR",
+ "BAD LOAD ADDR",
+ "BAD STORE ADDR",
+ "BUS ERR ON INSTR FETCH",
+ "BUS ERR ON LOADSTORE",
+ "SYSCALL",
+ "BREAKPOINT",
+ "UNKNOWN INSTR",
+ "COP UNUSABLE",
+ "ARITHMETIC OVERFLOW",
+ "TRAP EXC",
+ "VIRTUAL COHERENCY INSTR",
+ "FLOAT EXC",
+};
+
+const char *szGPRegisters1[] = { "R0", "AT", "V0", "V1", "A0", "A1", "A2", "A3",
+ "T0", "T1", "T2", "T3", "T4", "T5", "T6", NULL };
+
+const char *szGPRegisters2[] = { "T7", "S0", "S1", "S2", "S3", "S4",
+ "S5", "S6", "S7", "T8", "T9", /*"K0", "K1",*/
+ "GP", "SP", "FP", "RA", NULL };
+
+int crash_strlen(char *str) {
+ int len = 0;
+ while (*str++) {
+ len++;
+ }
+ return len;
+}
+
+void show_crash_screen_and_hang(void) {
+ u32 cause;
+ u32 epc;
+ u8 errno;
+
+ fb_set_address((void *) (*(u32 *) 0xA4400004 | 0x80000000)); // replace me
+
+ cause = cop0_get_cause();
+ epc = cop0_get_epc();
+
+ errno = (cause >> 2) & 0x1F;
+
+ if (nAssertStopProgram == 0) {
+ fbFillColor = 0x6253;
+ fb_fill(10, 10, 300, 220);
+
+ fb_print_str(80, 20, "AN ERROR HAS OCCURRED!");
+ fb_print_int_hex(80, 30, errno, 8);
+ fb_print_str(95, 30, szErrCodes[errno]);
+
+ if (errno >= 2 && errno <= 5) {
+ /*
+ 2 UNMAPPED LOAD ADDR
+ 3 UNMAPPED STORE ADDR
+ 4 BAD LOAD ADDR
+ 5 BAD STORE ADDR
+ */
+ u32 badvaddr = cop0_get_badvaddr();
+
+ fb_print_str(145, 50, "VA");
+ fb_print_int_hex(160, 50, badvaddr, 32);
+ }
+ } else {
+ int afterFileX;
+ int exprBoxWidth;
+ fbFillColor = 0x5263;
+ fb_fill(10, 10, 300, 220);
+
+ fb_print_str(80, 20, "ASSERTION FAILED!");
+
+ afterFileX = fb_print_str(80, 30, pAssertFile);
+ fb_print_str(afterFileX, 30, ":");
+ fb_print_uint(afterFileX + 5, 30, nAssertLine);
+
+ exprBoxWidth = (crash_strlen(pAssertExpression) * 5) + 2;
+ fbFillColor = 0x0001;
+ fb_fill(80 - 1, 40 - 1, exprBoxWidth, 10);
+ fb_print_str(80, 40, pAssertExpression);
+ }
+
+ fb_print_str(80, 50, "PC");
+ fb_print_int_hex(95, 50, epc, 32);
+
+ fb_print_gpr_states(80, 70, szGPRegisters1, &exceptionRegContext[6 + 0]);
+ fb_print_gpr_states(145, 70, szGPRegisters2, &exceptionRegContext[6 + 15 * 2]);
+
+ fb_swap();
+ osWritebackDCacheAll();
+
+ while (1) // hang forever
+ {
+ UNUSED volatile int t = 0; // keep pj64 happy
+ }
+}
+
+u8 ascii_to_idx(char c) {
+ return c - 0x20;
+}
+
+void fb_set_address(void *address) {
+ fbAddress = (u16 *) address;
+}
+
+void fb_swap() {
+ // update VI frame buffer register
+ // todo other registers
+ *(u32 *) (0xA4400004) = (u32) fbAddress & 0x00FFFFFF;
+}
+
+void fb_fill(int baseX, int baseY, int width, int height) {
+ int y, x;
+
+ for (y = baseY; y < baseY + height; y++) {
+ for (x = baseX; x < baseX + width; x++) {
+ fbAddress[y * 320 + x] = fbFillColor;
+ }
+ }
+}
+
+void fb_draw_char(int x, int y, u8 idx) {
+ u16 *out = &fbAddress[y * 320 + x];
+ const u8 *in = &crashFont[idx * 3];
+ int nbyte;
+ int nrow;
+ int ncol;
+
+ for (nbyte = 0; nbyte < 3; nbyte++) {
+ u8 curbyte = in[nbyte];
+ for (nrow = 0; nrow < 2; nrow++) {
+ for (ncol = 0; ncol < 4; ncol++) {
+ u8 px = curbyte & (1 << (7 - (nrow * 4 + ncol)));
+ if (px != 0) {
+ out[ncol] = fbFillColor;
+ }
+ }
+ out += 320;
+ }
+ }
+}
+
+void fb_draw_char_shaded(int x, int y, u8 idx) {
+ fbFillColor = 0x0001;
+ fb_draw_char(x - 1, y + 1, idx);
+
+ fbFillColor = 0xFFFF;
+ fb_draw_char(x, y, idx);
+}
+
+int fb_print_str(int x, int y, const char *str) {
+ while (1) {
+ int yoffs = 0;
+ u8 idx;
+ char c = *str++;
+
+ if (c == '\0') {
+ break;
+ }
+
+ if (c == ' ') {
+ x += 5;
+ continue;
+ }
+
+ switch (c) {
+ case 'j':
+ case 'g':
+ case 'p':
+ case 'q':
+ case 'y':
+ case 'Q':
+ yoffs = 1;
+ break;
+ case ',':
+ yoffs = 2;
+ break;
+ }
+
+ idx = ascii_to_idx(c);
+ fb_draw_char_shaded(x, y + yoffs, idx);
+ x += 5;
+ }
+
+ return x;
+}
+
+void fb_print_int_hex(int x, int y, u32 value, int nbits) {
+ nbits -= 4;
+
+ while (nbits >= 0) {
+ int nib = ((value >> nbits) & 0xF);
+ u8 idx;
+
+ if (nib > 9) {
+ idx = ('A' - 0x20) + (nib - 0xa);
+ } else {
+ idx = ('0' - 0x20) + nib;
+ }
+
+ fb_draw_char_shaded(x, y, idx);
+ x += 5;
+
+ nbits -= 4;
+ }
+}
+
+int fb_print_uint(int x, int y, u32 value) {
+ int nchars = 0;
+
+ int v = value;
+ int i;
+ while (v /= 10) {
+ nchars++;
+ }
+
+ x += nchars * 5;
+
+ for (i = nchars; i >= 0; i--) {
+ fb_draw_char_shaded(x, y, ('0' - 0x20) + (value % 10));
+ value /= 10;
+ x -= 5;
+ }
+
+ return (x + nchars * 5);
+}
+
+void fb_print_gpr_states(int x, int y, const char *regNames[], u32 *regContext) {
+ int i;
+ for (i = 0;; i++) {
+ if (regNames[i] == NULL) {
+ break;
+ }
+
+ fb_print_str(x, y, regNames[i]);
+ fb_print_int_hex(x + 15, y, regContext[i * 2 + 1], 32);
+ y += 10;
+ }
+}
diff --git a/src/game/crash.h b/src/game/crash.h
new file mode 100644
index 00000000..1386930d
--- /dev/null
+++ b/src/game/crash.h
@@ -0,0 +1,28 @@
+#ifndef _CRASH_H_
+#define _CRASH_H_
+
+#include <types.h>
+
+#define CRASH_SCREEN_INCLUDED 1
+
+extern u32 cop0_get_cause(void);
+extern u32 cop0_get_epc(void);
+extern u32 cop0_get_badvaddr(void);
+
+extern void _n64_assert(const char* pFile, int nLine, const char *pExpression, int nStopProgram);
+
+extern u8 __crash_handler_entry[];
+
+void show_crash_screen_and_hang(void);
+u8 ascii_to_idx(char c);
+void fb_set_address(void *address);
+void fb_swap(void);
+void fb_fill(int baseX, int baseY, int width, int height);
+void fb_draw_char(int x, int y, u8 idx);
+void fb_draw_char_shaded(int x, int y, u8 idx);
+int fb_print_str(int x, int y, const char *str);
+int fb_print_uint(int x, int y, u32 value);
+void fb_print_int_hex(int x, int y, u32 value, int nbits);
+void fb_print_gpr_states(int x, int y, const char* regStrs[], u32 *regContext);
+
+#endif /* _CRASH_H_ */

Binary file not shown.

View file

@ -1,303 +0,0 @@
diff --git a/src/game/area.c b/src/game/area.c
index 240605d8..88c1a314 100644
--- a/src/game/area.c
+++ b/src/game/area.c
@@ -19,7 +19,8 @@
#include "level_update.h"
#include "engine/geo_layout.h"
#include "save_file.h"
#include "level_table.h"
+#include "debug_box.h"
struct SpawnInfo gPlayerSpawnInfos[1];
struct GraphNode *D_8033A160[0x100];
@@ -353,6 +354,8 @@ void render_game(void) {
if (gCurrentArea != NULL && !gWarpTransition.pauseRendering) {
geo_process_root(gCurrentArea->unk04, D_8032CE74, D_8032CE78, gFBSetColor);
+ render_debug_boxes();
+
gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&D_8032CF00));
gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH,
diff --git a/src/game/debug_box.c b/src/game/debug_box.c
new file mode 100644
index 00000000..0ee87ec7
--- /dev/null
+++ b/src/game/debug_box.c
@@ -0,0 +1,244 @@
+#include <ultra64.h>
+
+#include "sm64.h"
+#include "game/game_init.h"
+#include "game/geo_misc.h"
+#include "engine/math_util.h"
+
+#include "debug_box.h"
+
+/**
+ * @file debug_box.c
+ * Draws 3D boxes for debugging purposes.
+ *
+ * How to use:
+ *
+ * In render_game() in area.c, add a call to render_debug_boxes():
+ *
+ * void render_game(void) {
+ * if (gCurrentArea != NULL && !gWarpTransition.pauseRendering) {
+ * geo_process_root(...);
+ *
+ * render_debug_boxes(); // add here
+ *
+ * gSPViewport(...);
+ * gDPSetScissor(...);
+ * //...
+ *
+ * Now just call debug_box() whenever you want to draw one!
+ *
+ * debug_box by default takes two arguments: a center and bounds vec3f.
+ * This will draw a box starting from the point (center - bounds) to (center + bounds).
+ *
+ * Use debug_box_rot to draw a box rotated in the xz-plane.
+ *
+ * If you want to draw a box by specifying min and max points, use debug_box_pos() instead.
+ */
+
+/**
+ * Internal struct containing box info
+ */
+struct DebugBox {
+ Vec3s center;
+ Vec3s bounds;
+ s16 yaw;
+};
+
+struct DebugBox *sBoxes[MAX_DEBUG_BOXES];
+s16 sNumBoxes = 0;
+
+extern Mat4 gMatStack[32]; //XXX: Hack
+
+/**
+ * The debug boxes' transparency
+ */
+#define DBG_BOX_ALPHA 0x7F
+/**
+ * The debug boxes' color
+ */
+#define DBG_BOX_COL 0xFF, 0x00, 0x00, DBG_BOX_ALPHA
+
+/**
+ * Sets up the RCP for drawing the boxes
+ */
+static const Gfx dl_debug_box_begin[] = {
+ gsDPPipeSync(),
+#if DBG_BOX_ALPHA < 0xFF
+ gsDPSetRenderMode(G_RM_ZB_XLU_SURF, G_RM_NOOP2),
+#else
+ gsDPSetRenderMode(G_RM_ZB_OPA_SURF, G_RM_NOOP2),
+#endif
+ gsSPClearGeometryMode(G_LIGHTING | G_CULL_BACK),
+ gsSPSetGeometryMode(G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH),
+ gsSPTexture(0, 0, 0, 0, G_OFF),
+ gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE),
+ gsSPEndDisplayList(),
+};
+
+/**
+ * Actually draws the box
+ */
+static const Gfx dl_debug_draw_box[] = {
+ gsSP2Triangles( 0, 1, 2, 0x0, 2, 1, 3, 0x0),
+ gsSP2Triangles( 2, 3, 6, 0x0, 6, 3, 7, 0x0),
+
+ gsSP2Triangles( 4, 0, 2, 0x0, 2, 6, 4, 0x0),
+ gsSP2Triangles( 1, 5, 3, 0x0, 3, 5, 7, 0x0),
+
+ gsSP2Triangles( 1, 0, 4, 0x0, 1, 4, 5, 0x0),
+ gsSP2Triangles( 5, 4, 6, 0x0, 5, 6, 7, 0x0),
+
+ gsSPEndDisplayList(),
+};
+
+/**
+ * Adds a box to the list to be rendered this frame.
+ *
+ * If there are already MAX_DEBUG_BOXES boxes, does nothing.
+ */
+static void append_debug_box(Vec3f center, Vec3f bounds, s16 yaw)
+{
+ if (sNumBoxes >= MAX_DEBUG_BOXES ||
+ (sBoxes[sNumBoxes] = mem_pool_alloc(gEffectsMemoryPool, sizeof(struct DebugBox))) == NULL) {
+ return;
+ }
+
+ vec3f_to_vec3s(sBoxes[sNumBoxes]->center, center);
+ vec3f_to_vec3s(sBoxes[sNumBoxes]->bounds, bounds);
+
+ sBoxes[sNumBoxes]->yaw = yaw;
+
+ ++sNumBoxes;
+}
+
+/**
+ * Draws a debug box from (center - bounds) to (center + bounds)
+ * To draw a rotated box, use debug_box_rot()
+ *
+ * @see debug_box_rot()
+ */
+void debug_box(Vec3f center, Vec3f bounds)
+{
+ append_debug_box(center, bounds, 0);
+}
+
+/**
+ * Draws a debug box from (center - bounds) to (center + bounds), rotating it by `yaw`
+ */
+void debug_box_rot(Vec3f center, Vec3f bounds, s16 yaw)
+{
+ append_debug_box(center, bounds, yaw);
+}
+
+/**
+ * Draws a debug box from pMin to pMax
+ * To draw a rotated box this way, use debug_box_pos_rot()
+ *
+ * @see debug_box_pos_rot()
+ */
+void debug_box_pos(Vec3f pMin, Vec3f pMax)
+{
+ debug_box_pos_rot(pMin, pMax, 0);
+}
+
+/**
+ * Draws a debug box from pMin to pMax, rotating it in the xz-plane by `yaw`
+ */
+void debug_box_pos_rot(Vec3f pMin, Vec3f pMax, s16 yaw)
+{
+ Vec3f center, bounds;
+
+ bounds[0] = pMax[0] - pMin[0] / 2.0f;
+ bounds[1] = pMax[1] - pMin[1] / 2.0f;
+ bounds[2] = pMax[2] - pMin[2] / 2.0f;
+
+ center[0] = pMin[0] + bounds[0];
+ center[1] = pMin[1] + bounds[1];
+ center[2] = pMin[2] + bounds[2];
+
+ append_debug_box(center, bounds, yaw);
+}
+
+static void render_box(struct DebugBox *box)
+{
+ Vtx *verts = alloc_display_list(8 * sizeof(Vtx));
+ Mtx *translate;
+ Mtx *rotate;
+ Mtx *translateback;
+ s32 x0 = box->center[0],
+ y0 = box->center[1],
+ z0 = box->center[2];
+
+ s32 xb = box->bounds[0],
+ yb = box->bounds[1],
+ zb = box->bounds[2];
+
+ if (verts != NULL) {
+ if (box->yaw != 0) {
+ // Translate to the origin, rotate, then translate back, effectively rotating the box about
+ // its center
+ translate = alloc_display_list(sizeof(Mtx));
+ rotate = alloc_display_list(sizeof(Mtx));
+ translateback = alloc_display_list(sizeof(Mtx));
+
+ guTranslate(translate, box->center[0], box->center[1], box->center[2]);
+ guRotate(rotate, box->yaw / (float)0x10000 * 360.0f, 0, 1.0f, 0);
+ guTranslate(translateback, -box->center[0], -box->center[1], -box->center[2]);
+
+ gSPMatrix(gDisplayListHead++, translate, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH);
+ gSPMatrix(gDisplayListHead++, rotate, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
+ gSPMatrix(gDisplayListHead++, translateback, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
+ }
+
+#define DBG_BOX_VTX(i, x, y, z) make_vertex(verts, i, x, y, z, 0, 0, DBG_BOX_COL)
+ DBG_BOX_VTX(0, x0 - xb, y0 + yb, z0 - zb);
+ DBG_BOX_VTX(1, x0 + xb, y0 + yb, z0 - zb);
+ DBG_BOX_VTX(2, x0 - xb, y0 - yb, z0 - zb);
+ DBG_BOX_VTX(3, x0 + xb, y0 - yb, z0 - zb);
+ DBG_BOX_VTX(4, x0 - xb, y0 + yb, z0 + zb);
+ DBG_BOX_VTX(5, x0 + xb, y0 + yb, z0 + zb);
+ DBG_BOX_VTX(6, x0 - xb, y0 - yb, z0 + zb);
+ DBG_BOX_VTX(7, x0 + xb, y0 - yb, z0 + zb);
+#undef DBG_BOX_VTX
+
+ gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(verts), 8, 0);
+
+ gSPDisplayList(gDisplayListHead++, dl_debug_draw_box);
+
+ if (box->yaw != 0) {
+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
+ }
+ }
+}
+
+void render_debug_boxes(void)
+{
+ s32 i;
+ Mtx *mtx;
+
+ if (sNumBoxes == 0) {
+ return;
+ }
+
+ mtx = alloc_display_list(sizeof(Mtx));
+ if (mtx == NULL) {
+ for (i = 0; i < sNumBoxes; ++i) {
+ mem_pool_free(gEffectsMemoryPool, sBoxes[i]);
+ }
+ sNumBoxes = 0;
+ return;
+ }
+
+ //XXX: This is hacky. Ths camera's look-at matrix is stored in gMatStack[1], so this is a simple way
+ // of using it without reconstructing the matrix.
+ mtxf_to_mtx(mtx, gMatStack[1]);
+ gSPMatrix(gDisplayListHead++, mtx, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
+ gSPDisplayList(gDisplayListHead++, dl_debug_box_begin);
+
+ for (i = 0; i < sNumBoxes; ++i) {
+ render_box(sBoxes[i]);
+ mem_pool_free(gEffectsMemoryPool, sBoxes[i]);
+ }
+
+ sNumBoxes = 0;
+}
diff --git a/src/game/debug_box.h b/src/game/debug_box.h
new file mode 100644
index 00000000..cdb3dc9d
--- /dev/null
+++ b/src/game/debug_box.h
@@ -0,0 +1,25 @@
+#ifndef _DEBUG_DRAW_CUBE_H
+#define _DEBUG_DRAW_CUBE_H
+
+/**
+ * @file debug_box.h
+ * Draws debug boxes, see debug_box.inc.c for details
+ */
+
+#include "types.h"
+
+/**
+ * The max amount of debug boxes before debug_box() just returns.
+ * You can set this to something higher like 1000, but things like text will stop rendering.
+ */
+#define MAX_DEBUG_BOXES 100
+
+void debug_box(Vec3f center, Vec3f bounds);
+void debug_box_rot(Vec3f center, Vec3f bounds, s16 yaw);
+
+void debug_box_pos(Vec3f pMin, Vec3f pMax);
+void debug_box_pos_rot(Vec3f pMin, Vec3f pMax, s16 yaw);
+
+void render_debug_boxes(void);
+
+#endif /* _DEBUG_DRAW_CUBE_H */

View file

@ -1,312 +0,0 @@
diff --git a/include/PR/console_type.h b/include/PR/console_type.h
new file mode 100644
index 00000000..e60550ab
--- /dev/null
+++ b/include/PR/console_type.h
@@ -0,0 +1,7 @@
+enum ConsoleType {
+ CONSOLE_N64,
+ CONSOLE_IQUE
+};
+
+extern enum ConsoleType gConsoleType;
+extern enum ConsoleType get_console_type(void);
diff --git a/lib/asm/skGetId.s b/lib/asm/skGetId.s
new file mode 100644
index 00000000..8fb4c449
--- /dev/null
+++ b/lib/asm/skGetId.s
@@ -0,0 +1,18 @@
+# Code by stuckpixel
+
+.set noreorder
+.set gp=64
+
+.include "macros.inc"
+
+glabel skGetId
+ li $v0, 0
+ li $t0, 0xA4300014
+ lw $t1, 0x00($t0)
+ nop
+ jr $ra
+ nop
+ nop
+ nop
+ nop
+ nop
diff --git a/lib/src/__osViSwapContext.c b/lib/src/__osViSwapContext.c
index d7741994..9aced7cf 100644
--- a/lib/src/__osViSwapContext.c
+++ b/lib/src/__osViSwapContext.c
@@ -52,7 +52,9 @@ void __osViSwapContext() {
HW_REG(VI_INTR_REG, u32) = s0->fldRegs[field].vIntr;
HW_REG(VI_X_SCALE_REG, u32) = s1->unk20;
HW_REG(VI_Y_SCALE_REG, u32) = s1->unk2c;
- HW_REG(VI_CONTROL_REG, u32) = s1->features;
+ /* Make sure bit 13 is cleared. Otherwise, graphics will be corrupted on
+ * iQue Player. This has no effect on N64. */
+ HW_REG(VI_CONTROL_REG, u32) = s1->features & ~(1 << 13);
D_80334914 = D_80334910;
D_80334910 = s1;
*D_80334914 = *D_80334910;
diff --git a/lib/src/consoleType.c b/lib/src/consoleType.c
new file mode 100644
index 00000000..ef08d1ef
--- /dev/null
+++ b/lib/src/consoleType.c
@@ -0,0 +1,12 @@
+#include "libultra_internal.h"
+#include <PR/console_type.h>
+
+enum ConsoleType gConsoleType;
+
+void skGetId(u32 *out);
+
+enum ConsoleType get_console_type(void) {
+ u32 id = 0;
+ skGetId(&id);
+ return (id == 0) ? CONSOLE_N64 : CONSOLE_IQUE;
+}
diff --git a/lib/src/osEepromProbe.c b/lib/src/osEepromProbe.c
index d550b846..bbaf2bcc 100644
--- a/lib/src/osEepromProbe.c
+++ b/lib/src/osEepromProbe.c
@@ -1,4 +1,5 @@
#include "libultra_internal.h"
+#include <PR/console_type.h>
// TODO: merge with osEepromWrite
typedef struct {
@@ -13,11 +14,23 @@ s32 osEepromProbe(OSMesgQueue *mq) {
unkStruct sp18;
__osSiGetAccess();
- status = __osEepStatus(mq, &sp18);
- if (status == 0 && (sp18.unk00 & 0x8000) != 0) {
- status = 1;
- } else {
- status = 0;
+ if (gConsoleType == CONSOLE_N64) {
+ status = __osEepStatus(mq, &sp18);
+ if (status == 0 && (sp18.unk00 & 0x8000) != 0) {
+ status = 1;
+ } else {
+ status = 0;
+ }
+ } else if (gConsoleType == CONSOLE_IQUE) {
+ s32 __osBbEepromSize = * (s32*) 0x80000360;
+
+ if (__osBbEepromSize == 0x200) {
+ status = 1;
+ }
+
+ if (__osBbEepromSize == 0x800) {
+ status = 2;
+ }
}
__osSiRelAccess();
return status;
diff --git a/lib/src/osEepromRead.c b/lib/src/osEepromRead.c
index ea784b2c..116dae2d 100644
--- a/lib/src/osEepromRead.c
+++ b/lib/src/osEepromRead.c
@@ -1,4 +1,5 @@
#include "libultra_internal.h"
+#include <PR/console_type.h>
extern u8 _osLastSentSiCmd;
@@ -42,33 +43,44 @@ s32 osEepromRead(OSMesgQueue *mq, u8 address, u8 *buffer) {
return -1;
}
__osSiGetAccess();
- sp34 = __osEepStatus(mq, &sp28);
- if (sp34 != 0 || sp28.unk00 != 0x8000) {
+ if (gConsoleType == CONSOLE_N64) {
+ sp34 = __osEepStatus(mq, &sp28);
+ if (sp34 != 0 || sp28.unk00 != 0x8000) {
- return 8;
- }
- while (sp28.unk02 & 0x80) {
- __osEepStatus(mq, &sp28);
- }
- __osPackEepReadData(address);
- sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00);
- osRecvMesg(mq, NULL, OS_MESG_BLOCK);
- for (sp30 = 0; sp30 < 0x10; sp30++) {
- (D_80365E00)[sp30] = 255;
- }
- D_80365E3C = 0;
- sp34 = __osSiRawStartDma(OS_READ, D_80365E00);
- _osLastSentSiCmd = 4;
- osRecvMesg(mq, NULL, OS_MESG_BLOCK);
- for (sp30 = 0; sp30 < 4; sp30++) {
- sp2c++;
- }
- sp20 = *(unkStruct2 *) sp2c;
- sp34 = (sp20.unk01 & 0xc0) >> 4;
- if (sp34 == 0) {
- for (sp30 = 0; sp30 < 8; sp30++) {
- *buffer++ = ((u8 *) &sp20.unk04)[sp30];
+ return 8;
+ }
+ while (sp28.unk02 & 0x80) {
+ __osEepStatus(mq, &sp28);
+ }
+ __osPackEepReadData(address);
+ sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00);
+ osRecvMesg(mq, NULL, OS_MESG_BLOCK);
+ for (sp30 = 0; sp30 < 0x10; sp30++) {
+ (D_80365E00)[sp30] = 255;
}
+ D_80365E3C = 0;
+ sp34 = __osSiRawStartDma(OS_READ, D_80365E00);
+ _osLastSentSiCmd = 4;
+ osRecvMesg(mq, NULL, OS_MESG_BLOCK);
+ for (sp30 = 0; sp30 < 4; sp30++) {
+ sp2c++;
+ }
+ sp20 = *(unkStruct2 *) sp2c;
+ sp34 = (sp20.unk01 & 0xc0) >> 4;
+ if (sp34 == 0) {
+ for (sp30 = 0; sp30 < 8; sp30++) {
+ *buffer++ = ((u8 *) &sp20.unk04)[sp30];
+ }
+ }
+ } else if (gConsoleType == CONSOLE_IQUE) {
+ u8 *__osBbEepromAddress = * (u8**) 0x8000035C;
+ s32 i;
+
+ for (i = 0; i < 8; i++) {
+ buffer[i] = __osBbEepromAddress[(address << 3) + i];
+ }
+
+ sp34 = 0;
}
__osSiRelAccess();
return sp34;
diff --git a/lib/src/osEepromWrite.c b/lib/src/osEepromWrite.c
index 1a86477b..52a23e5e 100644
--- a/lib/src/osEepromWrite.c
+++ b/lib/src/osEepromWrite.c
@@ -1,5 +1,6 @@
#include "libultra_internal.h"
#include "osContInternal.h"
+#include <PR/console_type.h>
#ifndef AVOID_UB
ALIGNED8 u32 D_80365E00[15];
@@ -52,36 +53,47 @@ s32 osEepromWrite(OSMesgQueue *mq, u8 address, u8 *buffer) {
}
__osSiGetAccess();
- sp34 = __osEepStatus(mq, &sp1c);
+ if (gConsoleType == CONSOLE_N64) {
+ sp34 = __osEepStatus(mq, &sp1c);
- if (sp34 != 0 || sp1c.unk00 != 0x8000) {
- return 8;
- }
+ if (sp34 != 0 || sp1c.unk00 != 0x8000) {
+ return 8;
+ }
- while (sp1c.unk02 & 0x80) {
- __osEepStatus(mq, &sp1c);
- }
+ while (sp1c.unk02 & 0x80) {
+ __osEepStatus(mq, &sp1c);
+ }
- __osPackEepWriteData(address, buffer);
+ __osPackEepWriteData(address, buffer);
- sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00);
- osRecvMesg(mq, NULL, OS_MESG_BLOCK);
+ sp34 = __osSiRawStartDma(OS_WRITE, &D_80365E00);
+ osRecvMesg(mq, NULL, OS_MESG_BLOCK);
- for (sp30 = 0; sp30 < 0x10; sp30++) {
- (D_80365E00)[sp30] = 255;
- }
+ for (sp30 = 0; sp30 < 0x10; sp30++) {
+ (D_80365E00)[sp30] = 255;
+ }
- D_80365E3C = 0;
- sp34 = __osSiRawStartDma(OS_READ, D_80365E00);
- _osLastSentSiCmd = 5;
- osRecvMesg(mq, NULL, OS_MESG_BLOCK);
+ D_80365E3C = 0;
+ sp34 = __osSiRawStartDma(OS_READ, D_80365E00);
+ _osLastSentSiCmd = 5;
+ osRecvMesg(mq, NULL, OS_MESG_BLOCK);
- for (sp30 = 0; sp30 < 4; sp30++) {
- sp2c++;
- }
+ for (sp30 = 0; sp30 < 4; sp30++) {
+ sp2c++;
+ }
+
+ sp20 = *(unkStruct2 *) sp2c;
+ sp34 = (sp20.unk01 & 0xc0) >> 4;
+ } else if (gConsoleType == CONSOLE_N64) {
+ u8 *__osBbEepromAddress = * (u8**) 0x8000035C;
+ s32 i;
- sp20 = *(unkStruct2 *) sp2c;
- sp34 = (sp20.unk01 & 0xc0) >> 4;
+ for (i = 0; i < 8; i++) {
+ __osBbEepromAddress[(address << 3) + i] = buffer[i];
+ }
+
+ sp34 = 0;
+ }
__osSiRelAccess();
return sp34;
}
diff --git a/lib/src/osInitialize.c b/lib/src/osInitialize.c
index 3cbca8ea..20723b2f 100644
--- a/lib/src/osInitialize.c
+++ b/lib/src/osInitialize.c
@@ -1,6 +1,7 @@
#include "libultra_internal.h"
#include "hardware.h"
#include <macros.h>
+#include <PR/console_type.h>
#define PIF_ADDR_START (void *) 0x1FC007FC
@@ -46,6 +47,7 @@ void osInitialize(void) {
UNUSED u32 eu_sp30;
#endif
UNUSED u32 sp2c;
+ gConsoleType = get_console_type();
D_80365CD0 = TRUE;
__osSetSR(__osGetSR() | 0x20000000);
__osSetFpcCsr(0x01000800);
diff --git a/sm64.ld b/sm64.ld
index 3275819f..6f97698f 100755
--- a/sm64.ld
+++ b/sm64.ld
@@ -261,6 +261,8 @@ SECTIONS
BUILD_DIR/libultra.a:func_802F7140.o(.text)
BUILD_DIR/libultra.a:func_802F71A0.o(.text)
BUILD_DIR/libultra.a:func_802F71F0.o(.text)
+ BUILD_DIR/libultra.a:consoleType.o(.text)
+ BUILD_DIR/libultra.a:skGetId.o(.text)
BUILD_DIR/lib/rsp.o(.text);
#else
BUILD_DIR/src/game*.o(.text);
@@ -371,6 +373,8 @@ SECTIONS
BUILD_DIR/libultra.a:__osGetCause.o(.text);
BUILD_DIR/libultra.a:__osAtomicDec.o(.text);
BUILD_DIR/libultra.a:guLookAtRef.o(.text); /* Fast3DEX2 only */
+ BUILD_DIR/libultra.a:consoleType.o(.text);
+ BUILD_DIR/libultra.a:skGetId.o(.text);
BUILD_DIR/lib/rsp.o(.text);
#endif

View file

@ -1,298 +0,0 @@
diff --git a/Makefile b/Makefile
index 88c8dbbe..f60df04f 100644
--- a/Makefile
+++ b/Makefile
@@ -382,6 +382,7 @@ $(BUILD_DIR)/include/text_strings.h: $(BUILD_DIR)/include/text_menu_strings.h
$(BUILD_DIR)/src/menu/file_select.o: $(BUILD_DIR)/include/text_strings.h
$(BUILD_DIR)/src/menu/star_select.o: $(BUILD_DIR)/include/text_strings.h
$(BUILD_DIR)/src/game/ingame_menu.o: $(BUILD_DIR)/include/text_strings.h
+$(BUILD_DIR)/src/game/mem_error_screen.o: $(BUILD_DIR)/include/text_strings.h
################################################################
# TEXTURE GENERATION #
diff --git a/include/segments.h b/include/segments.h
index c98040a8..eeecb4f6 100644
--- a/include/segments.h
+++ b/include/segments.h
@@ -1,6 +1,9 @@
#ifndef _SEGMENTS_H
#define _SEGMENTS_H
+/* Use expansion pack RAM */
+#define USE_EXT_RAM 1
+
/*
* Memory addresses for segments. Ideally, this header file would not be
* needed, and the addresses would be defined in sm64.ld and linker-inserted
diff --git a/include/text_strings.h.in b/include/text_strings.h.in
index 4e36eb96..7aadf0cb 100644
--- a/include/text_strings.h.in
+++ b/include/text_strings.h.in
@@ -25,6 +25,11 @@
#define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses
#define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses
+// Memory Expansion Error Screen
+#define TEXT_CONSOLE_8MB _("If you're using an N64 console, then you will need to buy an\nExpansion Pak to play this ROM hack.")
+#define TEXT_PJ64 _("If you are using PJ64 1.6, go to:\nOptions > Settings > Rom Settings Tab > Memory Size\nthen select 8 MB from the drop-down box.")
+#define TEXT_PJ64_2 _("If you are using PJ64 2.X, go to:\nOptions > Settings > Config: > Memory Size, select 8 MB")
+
#if defined(VERSION_JP) || defined(VERSION_SH)
/**
diff --git a/levels/entry.c b/levels/entry.c
index 015eeb6b..cc010ca1 100644
--- a/levels/entry.c
+++ b/levels/entry.c
@@ -15,3 +15,12 @@ const LevelScript level_script_entry[] = {
EXECUTE(/*seg*/ 0x14, /*script*/ _introSegmentRomStart, /*scriptEnd*/ _introSegmentRomEnd, /*entry*/ level_intro_entry_1),
JUMP(/*target*/ level_script_entry),
};
+
+const LevelScript level_script_entry_error_screen[] = {
+ INIT_LEVEL(),
+ SLEEP(/*frames*/ 2),
+ BLACKOUT(/*active*/ FALSE),
+ SET_REG(/*value*/ 0),
+ EXECUTE(/*seg*/ 0x14, /*script*/ _introSegmentRomStart, /*scriptEnd*/ _introSegmentRomEnd, /*entry*/ level_intro_entry_error_screen),
+ JUMP(/*target*/ level_script_entry_error_screen),
+};
diff --git a/levels/intro/geo.c b/levels/intro/geo.c
index 8ac70024..72766f3f 100644
--- a/levels/intro/geo.c
+++ b/levels/intro/geo.c
@@ -15,6 +15,24 @@
#include "levels/intro/header.h"
+const GeoLayout intro_geo_error_screen[] = {
+ GEO_NODE_SCREEN_AREA(0, SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH/2, SCREEN_HEIGHT/2),
+ GEO_OPEN_NODE(),
+ GEO_ZBUFFER(0),
+ GEO_OPEN_NODE(),
+ GEO_NODE_ORTHO(100),
+ GEO_OPEN_NODE(),
+ GEO_BACKGROUND_COLOR(0x0001),
+ GEO_CLOSE_NODE(),
+ GEO_CLOSE_NODE(),
+ GEO_ZBUFFER(0),
+ GEO_OPEN_NODE(),
+ GEO_ASM(0, geo18_display_error_message),
+ GEO_CLOSE_NODE(),
+ GEO_CLOSE_NODE(),
+ GEO_END(),
+};
+
// 0x0E0002D0
const GeoLayout intro_geo_0002D0[] = {
GEO_NODE_SCREEN_AREA(0, SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH/2, SCREEN_HEIGHT/2),
diff --git a/levels/intro/header.h b/levels/intro/header.h
index e0f6292d..8f77fb26 100644
--- a/levels/intro/header.h
+++ b/levels/intro/header.h
@@ -26,4 +26,8 @@ extern const LevelScript script_intro_L3[];
extern const LevelScript script_intro_L4[];
extern const LevelScript script_intro_L5[];
+extern const GeoLayout intro_geo_error_screen[];
+extern const LevelScript level_intro_entry_error_screen[];
+extern Gfx *geo18_display_error_message(u32 run, UNUSED struct GraphNode *sp44, UNUSED u32 sp48);
+
#endif
diff --git a/levels/intro/script.c b/levels/intro/script.c
index 4975dbb2..5ee6c688 100644
--- a/levels/intro/script.c
+++ b/levels/intro/script.c
@@ -18,6 +18,21 @@
#include "make_const_nonconst.h"
#include "levels/intro/header.h"
+const LevelScript level_intro_entry_error_screen[] = {
+ INIT_LEVEL(),
+ FIXED_LOAD(/*loadAddr*/ _goddardSegmentStart, /*romStart*/ _goddardSegmentRomStart, /*romEnd*/ _goddardSegmentRomEnd),
+ LOAD_MIO0(/*seg*/ 0x07, _intro_segment_7SegmentRomStart, _intro_segment_7SegmentRomEnd),
+ ALLOC_LEVEL_POOL(),
+
+ AREA(/*index*/ 1, intro_geo_error_screen),
+ END_AREA(),
+
+ FREE_LEVEL_POOL(),
+ LOAD_AREA(/*area*/ 1),
+ SLEEP(/*frames*/ 32767),
+ EXIT_AND_EXECUTE(/*seg*/ 0x14, _introSegmentRomStart, _introSegmentRomEnd, level_intro_entry_error_screen),
+};
+
const LevelScript level_intro_entry_1[] = {
INIT_LEVEL(),
FIXED_LOAD(/*loadAddr*/ _goddardSegmentStart, /*romStart*/ _goddardSegmentRomStart, /*romEnd*/ _goddardSegmentRomEnd),
diff --git a/src/engine/level_script.h b/src/engine/level_script.h
index 89bfb4ed..0ea607c5 100644
--- a/src/engine/level_script.h
+++ b/src/engine/level_script.h
@@ -4,5 +4,6 @@
struct LevelCommand *level_script_execute(struct LevelCommand *cmd);
extern u8 level_script_entry[];
+extern u8 level_script_entry_error_screen[];
#endif /* _LEVEL_SCRIPT_H */
diff --git a/src/game/main.c b/src/game/main.c
index a3afffee..8b05fcf1 100644
--- a/src/game/main.c
+++ b/src/game/main.c
@@ -12,6 +12,7 @@
#include "segments.h"
#include "main.h"
#include "thread6.h"
+#include "mem_error_screen.h"
// Message IDs
#define MESG_SP_COMPLETE 100
@@ -125,6 +126,10 @@ void alloc_pool(void) {
void *start = (void *) SEG_POOL_START;
void *end = (void *) SEG_POOL_END;
+ // Detect memory size
+ if (does_pool_end_lie_out_of_bounds(end))
+ end = (void *)SEG_POOL_END_4MB;
+
main_pool_init(start, end);
gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT);
}
@@ -330,7 +335,10 @@ void thread3_main(UNUSED void *arg) {
create_thread(&gSoundThread, 4, thread4_sound, NULL, gThread4Stack + 0x2000, 20);
osStartThread(&gSoundThread);
- create_thread(&gGameLoopThread, 5, thread5_game_loop, NULL, gThread5Stack + 0x2000, 10);
+ if (!gNotEnoughMemory)
+ create_thread(&gGameLoopThread, 5, thread5_game_loop, NULL, gThread5Stack + 0x2000, 10);
+ else
+ create_thread(&gGameLoopThread, 5, thread5_mem_error_message_loop, NULL, gThread5Stack + 0x2000, 10);
osStartThread(&gGameLoopThread);
while (1) {
diff --git a/src/game/mem_error_screen.c b/src/game/mem_error_screen.c
new file mode 100644
index 00000000..81efaf91
--- /dev/null
+++ b/src/game/mem_error_screen.c
@@ -0,0 +1,104 @@
+/* clang-format off */
+/*
+ * mem_error_screen.inc.c
+ *
+ * This enhancement should be used for ROM hacks that require the expansion pak.
+ *
+ */
+/* clang-format on */
+
+#include <types.h>
+#include "segments.h"
+#include "text_strings.h"
+#include "game_init.h"
+#include "main.h"
+#include "print.h"
+#include "ingame_menu.h"
+#include "segment2.h"
+#include "../engine/level_script.h"
+
+// Ensure that USE_EXT_RAM is defined.
+#ifndef USE_EXT_RAM
+#error You have to define USE_EXT_RAM in 'include/segments.h'
+#endif
+
+// Require 8 MB of RAM, even if the pool doesn't go into extended memory.
+// Change the '8' to whatever MB limit you want.
+// Note: only special emulators allow for RAM sizes above 8 MB.
+#define REQUIRED_MIN_MEM_SIZE 1048576 * 8
+
+u8 gNotEnoughMemory = FALSE;
+u8 gDelayForErrorMessage = 0;
+
+u8 does_pool_end_lie_out_of_bounds(void *end) {
+ u32 endPhy = ((u32) end) & 0x1FFFFFFF;
+ u32 memSize = *((u32 *) 0x80000318);
+
+ if (endPhy > memSize) {
+ gNotEnoughMemory = TRUE;
+ return TRUE;
+ } else {
+ if (memSize < REQUIRED_MIN_MEM_SIZE) {
+ gNotEnoughMemory = TRUE;
+ }
+ return FALSE;
+ }
+}
+
+// If you're using an N64 console, then you will need to buy an\nexpansion pak to play this ROM hack.
+u8 text_console_8mb[] = { TEXT_CONSOLE_8MB };
+
+// If you are using PJ64 1.6, go to: Options ► Settings ► Rom Settings Tab ► Memory Size then select 8
+// MB from the drop-down box.
+u8 text_pj64[] = { TEXT_PJ64 };
+
+// If you are using PJ64 2.X, go to: Options ► Settings ► Config: ► Memory Size, select 8 MB
+u8 text_pj64_2[] = { TEXT_PJ64_2 };
+
+Gfx *geo18_display_error_message(u32 run, UNUSED struct GraphNode *sp44, UNUSED u32 sp48) {
+ if (run) {
+ if (gDelayForErrorMessage > 0) {
+ // Draw color text title.
+ print_text(10, 210, "ERROR Need more memory");
+
+ // Init generic text rendering
+ create_dl_ortho_matrix();
+ gSPDisplayList(gDisplayListHead++,
+ dl_ia_text_begin); // Init rendering stuff for generic text
+
+ // Set text color to white
+ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255);
+
+ print_generic_string(8, 170, text_console_8mb);
+ print_generic_string(8, 120, text_pj64);
+ print_generic_string(8, 54, text_pj64_2);
+
+ // Cleanup
+ gSPDisplayList(gDisplayListHead++,
+ dl_ia_text_end); // Reset back to default render settings.
+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
+ } else {
+ gDelayForErrorMessage += 1;
+ }
+ }
+
+ return 0;
+}
+
+// Basic main loop for the error screen. Note that controllers are not enabled here.
+void thread5_mem_error_message_loop(UNUSED void *arg) {
+ struct LevelCommand *addr;
+
+ setup_game_memory();
+ set_vblank_handler(2, &gGameVblankHandler, &gGameVblankQueue, (OSMesg) 1);
+
+ addr = segmented_to_virtual(level_script_entry_error_screen);
+
+ rendering_init();
+
+ while (1) {
+ config_gfx_pool();
+ addr = level_script_execute(addr);
+ display_and_vsync();
+ }
+}
\ No newline at end of file
diff --git a/src/game/mem_error_screen.h b/src/game/mem_error_screen.h
new file mode 100644
index 00000000..9fbff34c
--- /dev/null
+++ b/src/game/mem_error_screen.h
@@ -0,0 +1,8 @@
+#ifndef MEM_ERROR_SCREEN_H
+#define MEM_ERROR_SCREEN_H
+
+extern u8 gNotEnoughMemory;
+void thread5_mem_error_message_loop(UNUSED void *arg);
+u8 does_pool_end_lie_out_of_bounds(void *end);
+
+#endif

View file

@ -1,185 +0,0 @@
diff --git a/src/game/game_init.c b/src/game/game_init.c
index a4302124..5ffbf3ed 100644
--- a/src/game/game_init.c
+++ b/src/game/game_init.c
@@ -11,6 +11,7 @@
#include "game_init.h"
#include "main.h"
#include "memory.h"
+#include "object_list_processor.h"
#include "profiler.h"
#include "save_file.h"
#include "seq_ids.h"
@@ -335,6 +336,45 @@ void display_and_vsync(void) {
gGlobalTimer++;
}
+/*
+ * This enhancement allows you to record gameplay demos for the mario head screen.
+ *
+ * Note:
+ * This enhancement does require the lastest versions of PJ64 from the nightly builds,
+ * because it uses the javascript API to automatically dump the demo files from RAM
+ * once the demo is completed. See enhancements/RecordDemo.js for more info
+ *
+*/
+
+#include "../src/game/mario.h"
+
+#define DEMOREC_STATUS_NOT_RECORDING 0
+#define DEMOREC_STATUS_PREPARING 1
+#define DEMOREC_STATUS_RECORDING 2
+#define DEMOREC_STATUS_STOPPING 3
+#define DEMOREC_STATUS_DONE 4
+
+#define DEMOREC_PRINT_X 10
+#define DEMOREC_PRINT_Y 10
+
+#define DEMOREC_DONE_DELAY 60 // Show "DONE" message for 2 seconds.
+
+#define DEMOREC_MAX_INPUTS 1025 // Max number of recorded inputs.
+
+/*
+ DO NOT REMOVE, MODIFY, OR MAKE A COPY OF THIS EXACT STRING!
+ This is here so that the js dump script can find the control variables easily.
+*/
+char gDemoRecTag[] = "DEMORECVARS";
+
+// Control variables. It is easier if they are each 4 byte aligned, which is why they are u32.
+u32 gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING;
+u32 gDoneDelay = 0;
+u32 gNumOfRecordedInputs = 0;
+struct DemoInput gRecordedInputs[DEMOREC_MAX_INPUTS];
+struct DemoInput* gRecordedInputsPtr = (struct DemoInput*)gRecordedInputs;
+struct DemoInput gRecordedDemoInputCopy;
+
// this function records distinct inputs over a 255-frame interval to RAM locations and was likely
// used to record the demo sequences seen in the final game. This function is unused.
static void record_demo(void) {
@@ -368,6 +408,118 @@ static void record_demo(void) {
gRecordedDemoInput.timer++;
}
+void record_new_demo_input(void) {
+ if(gRecordedDemoInput.timer == 1 && gRecordedDemoInputCopy.timer > 0) {
+ gRecordedInputs[gNumOfRecordedInputs].timer = gRecordedDemoInputCopy.timer;
+ gRecordedInputs[gNumOfRecordedInputs + 1].timer = 0;
+ gRecordedInputs[gNumOfRecordedInputs].rawStickX = gRecordedDemoInputCopy.rawStickX;
+ gRecordedInputs[gNumOfRecordedInputs + 1].rawStickX = gRecordedDemoInputCopy.rawStickX;
+ gRecordedInputs[gNumOfRecordedInputs].rawStickY = gRecordedDemoInputCopy.rawStickY;
+ gRecordedInputs[gNumOfRecordedInputs + 1].rawStickY = gRecordedDemoInputCopy.rawStickY;
+ gRecordedInputs[gNumOfRecordedInputs].buttonMask = gRecordedDemoInputCopy.buttonMask;
+ gRecordedInputs[gNumOfRecordedInputs + 1].buttonMask = gRecordedDemoInputCopy.buttonMask;
+ gNumOfRecordedInputs++;
+ }
+}
+
+// Self explanitory
+void copy_gRecordedDemoInput(void) {
+ gRecordedDemoInputCopy.timer = gRecordedDemoInput.timer;
+ gRecordedDemoInputCopy.rawStickX = gRecordedDemoInput.rawStickX;
+ gRecordedDemoInputCopy.rawStickY = gRecordedDemoInput.rawStickY;
+ gRecordedDemoInputCopy.buttonMask = gRecordedDemoInput.buttonMask;
+}
+
+// Runs when the demo is recording.
+void recording(void) {
+
+ // Force-stop when someone makes too many inputs.
+ if(gNumOfRecordedInputs + 1 > DEMOREC_MAX_INPUTS) {
+ gRecordingStatus = DEMOREC_STATUS_STOPPING;
+ return;
+ }
+
+ copy_gRecordedDemoInput();
+ record_demo(); // Defined in game.c
+ record_new_demo_input();
+}
+
+// Makes sure the last demo input is zeroed out, to make it look more clean.
+void record_cleanup(void) {
+ gRecordedInputs[gNumOfRecordedInputs].timer = 0;
+ gRecordedInputs[gNumOfRecordedInputs].rawStickX = 0;
+ gRecordedInputs[gNumOfRecordedInputs].rawStickY = 0;
+ gRecordedInputs[gNumOfRecordedInputs].buttonMask = 0;
+
+ // Make sure the done delay is reset before moving to DONE status.
+ gDoneDelay = 0;
+}
+
+void record_run(void) {
+ switch(gRecordingStatus) {
+ case DEMOREC_STATUS_NOT_RECORDING:
+ break;
+ case DEMOREC_STATUS_PREPARING:
+ if(gMarioObject != NULL && gCurrLevelNum >= 5) { // If the game is in an active level
+ gRecordingStatus = DEMOREC_STATUS_RECORDING;
+
+ // A bit of a hack, but it works.
+ gNumOfRecordedInputs = 1;
+ gRecordedInputs[0].timer = gCurrLevelNum;
+ gRecordedInputs[0].rawStickX = 0;
+ gRecordedInputs[0].rawStickY = 0;
+ gRecordedInputs[0].buttonMask = 0;
+ }
+ break;
+ case DEMOREC_STATUS_RECORDING:
+ recording();
+ break;
+ case DEMOREC_STATUS_DONE:
+ if(gDoneDelay > DEMOREC_DONE_DELAY)
+ gRecordingStatus = DEMOREC_STATUS_NOT_RECORDING;
+ else
+ gDoneDelay++;
+ break;
+ }
+}
+
+// Prints the status on the bottom-left side of the screen in colorful text.
+void print_status(void) {
+ switch(gRecordingStatus) {
+ case DEMOREC_STATUS_PREPARING:
+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "READY");
+ break;
+ case DEMOREC_STATUS_RECORDING:
+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "REC");
+ break;
+ case DEMOREC_STATUS_STOPPING:
+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "WAIT");
+ break;
+ case DEMOREC_STATUS_DONE:
+ print_text(DEMOREC_PRINT_X, DEMOREC_PRINT_Y, "DONE");
+ break;
+ }
+}
+
+// Main function that should be called from thread5_game_loop()
+void recordingDemo(void) {
+ // Mario needs to enter directly into a level and not from a warp,
+ // so the debug level select is used for that.
+ gDebugLevelSelect = TRUE;
+
+ if(gPlayer1Controller->buttonPressed & L_TRIG) {
+ if(gRecordingStatus == DEMOREC_STATUS_NOT_RECORDING) {
+ gRecordingStatus = DEMOREC_STATUS_PREPARING;
+ } else if (gRecordingStatus == DEMOREC_STATUS_RECORDING) {
+ gRecordingStatus = DEMOREC_STATUS_STOPPING;
+ record_cleanup();
+ }
+ }
+
+ record_run();
+ print_status();
+}
+
// take the updated controller struct and calculate
// the new x, y, and distance floats.
void adjust_analog_stick(struct Controller *controller) {
@@ -623,6 +775,7 @@ void thread5_game_loop(UNUSED void *arg) {
audio_game_loop_tick();
config_gfx_pool();
read_controller_inputs();
+ recordingDemo();
addr = level_script_execute(addr);
display_and_vsync();