diff --git a/enhancements/60fps_ex.patch b/enhancements/60fps_ex.patch new file mode 100644 index 00000000..01c17945 --- /dev/null +++ b/enhancements/60fps_ex.patch @@ -0,0 +1,1955 @@ +diff --git a/include/types.h b/include/types.h +index b3dc27e..c46bdf0 100644 +--- a/include/types.h ++++ b/include/types.h +@@ -118,6 +118,10 @@ struct GraphNodeObject_sub + /*0x0A 0x42*/ u16 animTimer; + /*0x0C 0x44*/ s32 animFrameAccelAssist; + /*0x10 0x48*/ s32 animAccel; ++ s16 prevAnimFrame; ++ s16 prevAnimID; ++ u32 prevAnimFrameTimestamp; ++ struct Animation *prevAnimPtr; + }; + + struct GraphNodeObject +@@ -128,11 +132,22 @@ struct GraphNodeObject + /*0x19*/ s8 unk19; + /*0x1A*/ Vec3s angle; + /*0x20*/ Vec3f pos; ++ Vec3s prevAngle; ++ Vec3f prevPos; ++ u32 prevTimestamp; ++ Vec3f prevShadowPos; ++ u32 prevShadowPosTimestamp; + /*0x2C*/ Vec3f scale; ++ Vec3f prevScale; ++ u32 prevScaleTimestamp; + /*0x38*/ struct GraphNodeObject_sub unk38; + /*0x4C*/ struct SpawnInfo *unk4C; + /*0x50*/ Mat4 *throwMatrix; // matrix ptr ++ Mat4 prevThrowMatrix; ++ u32 prevThrowMatrixTimestamp; ++ Mat4 *throwMatrixInterpolated; + /*0x54*/ Vec3f cameraToObject; ++ u32 skipInterpolationTimestamp; + }; + + struct ObjectNode +@@ -243,6 +258,10 @@ struct Surface + } normal; + /*0x28*/ f32 originOffset; + /*0x2C*/ struct Object *object; ++ Vec3s prevVertex1; ++ Vec3s prevVertex2; ++ Vec3s prevVertex3; ++ u32 modifiedTimestamp; + }; + + struct MarioBodyState +diff --git a/src/engine/graph_node.h b/src/engine/graph_node.h +index 802d97a..1b0d677 100644 +--- a/src/engine/graph_node.h ++++ b/src/engine/graph_node.h +@@ -110,6 +110,8 @@ struct GraphNodePerspective + /*0x1C*/ f32 fov; // horizontal field of view in degrees + /*0x20*/ s16 near; // near clipping plane + /*0x22*/ s16 far; // far clipping plane ++ f32 prevFov; ++ f32 prevTimestamp; + }; + + /** An entry in the master list. It is a linked list of display lists +@@ -118,7 +120,9 @@ struct GraphNodePerspective + struct DisplayListNode + { + Mtx *transform; ++ void *transformInterpolated; + void *displayList; ++ void *displayListInterpolated; + struct DisplayListNode *next; + }; + +@@ -185,7 +189,11 @@ struct GraphNodeCamera + } config; + /*0x1C*/ Vec3f pos; + /*0x28*/ Vec3f focus; ++ Vec3f prevPos; ++ Vec3f prevFocus; ++ u32 prevTimestamp; + /*0x34*/ Mat4 *matrixPtr; // pointer to look-at matrix of this camera as a Mat4 ++ Mat4 *matrixPtrInterpolated; + /*0x38*/ s16 roll; // roll in look at matrix. Doesn't account for light direction unlike rollScreen. + /*0x3A*/ s16 rollScreen; // rolls screen while keeping the light direction consistent + }; +@@ -226,7 +234,8 @@ struct GraphNodeRotation + /*0x00*/ struct GraphNode node; + /*0x14*/ void *displayList; + /*0x18*/ Vec3s rotation; +- u8 pad1E[2]; ++ Vec3s prevRotation; ++ u32 prevTimestamp; + }; + + /** GraphNode part that transforms itself and its children based on animation +@@ -323,6 +332,9 @@ struct GraphNodeBackground + /*0x00*/ struct FnGraphNode fnNode; + /*0x18*/ s32 unused; + /*0x1C*/ s32 background; // background ID, or rgba5551 color if fnNode.func is null ++ Vec3f prevCameraPos; ++ Vec3f prevCameraFocus; ++ u32 prevCameraTimestamp; + }; + + /** Renders the object that Mario is holding. +@@ -333,6 +345,8 @@ struct GraphNodeHeldObject + /*0x18*/ s32 playerIndex; + /*0x1C*/ struct Object *objNode; + /*0x20*/ Vec3s translation; ++ Vec3f prevShadowPos; ++ u32 prevShadowPosTimestamp; + }; + + /** A node that allows an object to specify a different culling radius than the +diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c +index 5b6775f..2c11e25 100644 +--- a/src/engine/surface_collision.c ++++ b/src/engine/surface_collision.c +@@ -8,6 +8,7 @@ + #include "surface_collision.h" + #include "surface_load.h" + #include "math_util.h" ++#include "game/game_init.h" + + /************************************************** + * WALLS * +@@ -394,26 +395,44 @@ f32 find_floor_height_and_data(f32 xPos, f32 yPos, f32 zPos, struct FloorGeometr + return floorHeight; + } + ++u8 gInterpolatingSurfaces; ++ + /** + * Iterate through the list of floors and find the first floor under a given point. + */ + static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 x, s32 y, s32 z, f32 *pheight) { + register struct Surface *surf; +- register s32 x1, z1, x2, z2, x3, z3; ++ register f32 x1, z1, x2, z2, x3, z3; + f32 nx, ny, nz; + f32 oo; + f32 height; + struct Surface *floor = NULL; ++ s32 interpolate; + + // Iterate through the list of floors until there are no more floors. + while (surfaceNode != NULL) { + surf = surfaceNode->surface; + surfaceNode = surfaceNode->next; ++ interpolate = gInterpolatingSurfaces && surf->modifiedTimestamp == gGlobalTimer; + + x1 = surf->vertex1[0]; + z1 = surf->vertex1[2]; + x2 = surf->vertex2[0]; + z2 = surf->vertex2[2]; ++ if (interpolate) { ++ f32 diff = (surf->prevVertex1[0] - x1) * (surf->prevVertex1[0] - x1); ++ diff += (surf->prevVertex1[1] - surf->vertex1[1]) * (surf->prevVertex1[1] - surf->vertex1[1]); ++ diff += (surf->prevVertex1[2] - z1) * (surf->prevVertex1[2] - z1); ++ //printf("%f\n", sqrtf(diff)); ++ if (diff > 10000) { ++ interpolate = FALSE; ++ } else { ++ x1 = (surf->prevVertex1[0] + x1) / 2; ++ z1 = (surf->prevVertex1[2] + z1) / 2; ++ x2 = (surf->prevVertex2[0] + x2) / 2; ++ z2 = (surf->prevVertex2[2] + z2) / 2; ++ } ++ } + + // Check that the point is within the triangle bounds. + if ((z1 - z) * (x2 - x1) - (x1 - x) * (z2 - z1) < 0) { +@@ -423,6 +442,10 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 + // To slightly save on computation time, set this later. + x3 = surf->vertex3[0]; + z3 = surf->vertex3[2]; ++ if (interpolate) { ++ x3 = (surf->prevVertex3[0] + x3) / 2; ++ z3 = (surf->prevVertex3[2] + z3) / 2; ++ } + + if ((z2 - z) * (x3 - x2) - (x2 - x) * (z3 - z2) < 0) { + continue; +@@ -442,10 +465,30 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 + continue; + } + +- nx = surf->normal.x; +- ny = surf->normal.y; +- nz = surf->normal.z; +- oo = surf->originOffset; ++ if (interpolate) { ++ f32 y1, y2, y3; ++ f32 mag; ++ y1 = (surf->prevVertex1[1] + surf->vertex1[1]) / 2; ++ y2 = (surf->prevVertex2[1] + surf->vertex2[1]) / 2; ++ y3 = (surf->prevVertex3[1] + surf->vertex3[1]) / 2; ++ nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2); ++ ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2); ++ nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2); ++ mag = sqrtf(nx * nx + ny * ny + nz * nz); ++ if (mag < 0.0001) { ++ continue; ++ } ++ mag = (f32)(1.0 / mag); ++ nx *= mag; ++ ny *= mag; ++ nz *= mag; ++ oo = -(nx * x1 + ny * y1 + nz * z1); ++ } else { ++ nx = surf->normal.x; ++ ny = surf->normal.y; ++ nz = surf->normal.z; ++ oo = surf->originOffset; ++ } + + // If a wall, ignore it. Likely a remnant, should never occur. + if (ny == 0.0f) { +@@ -460,6 +503,15 @@ static struct Surface *find_floor_from_list(struct SurfaceNode *surfaceNode, s32 + } + + *pheight = height; ++ if (interpolate) { ++ static struct Surface s; ++ s.type = surf->type; ++ s.normal.x = nx; ++ s.normal.y = ny; ++ s.normal.z = nz; ++ s.originOffset = oo; ++ return &s; ++ } + floor = surf; + break; + } +diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c +index ac2ee50..323b7d0 100644 +--- a/src/engine/surface_load.c ++++ b/src/engine/surface_load.c +@@ -14,6 +14,7 @@ + #include "game/mario.h" + #include "game/object_list_processor.h" + #include "surface_load.h" ++#include "game/game_init.h" + + s32 unused8038BE90; + +@@ -359,6 +360,11 @@ static struct Surface *read_surface_data(s16 *vertexData, s16 **vertexIndices) { + + surface = alloc_surface(); + ++ vec3s_copy(surface->prevVertex1, surface->vertex1); ++ vec3s_copy(surface->prevVertex2, surface->vertex2); ++ vec3s_copy(surface->prevVertex3, surface->vertex3); ++ surface->modifiedTimestamp = gGlobalTimer; ++ + surface->vertex1[0] = x1; + surface->vertex2[0] = x2; + surface->vertex3[0] = x3; +diff --git a/src/game/camera.c b/src/game/camera.c +index bde0662..9351dea 100644 +--- a/src/game/camera.c ++++ b/src/game/camera.c +@@ -484,6 +484,10 @@ CameraTransition sModeTransitions[] = { + extern u8 sDanceCutsceneIndexTable[][4]; + extern u8 sZoomOutAreaMasks[]; + ++static void skip_camera_interpolation(void) { ++ gLakituState.skipCameraInterpolationTimestamp = gGlobalTimer; ++} ++ + /** + * Starts a camera shake triggered by an interaction + */ +@@ -5552,6 +5556,7 @@ s32 set_camera_mode_fixed(struct Camera *c, s16 x, s16 y, s16 z) { + c->mode = CAMERA_MODE_FIXED; + vec3f_set(c->pos, sFixedModeBasePosition[0], sMarioCamState->pos[1], + sFixedModeBasePosition[2]); ++ skip_camera_interpolation(); + } + return basePosSet; + } +@@ -5714,6 +5719,7 @@ BAD_RETURN(s32) cam_rr_enter_building_side(struct Camera *c) { + if (c->mode != CAMERA_MODE_FIXED) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + c->mode = CAMERA_MODE_FIXED; ++ skip_camera_interpolation(); + } + } + +@@ -5909,6 +5915,7 @@ BAD_RETURN(s32) cam_castle_enter_lobby(struct Camera *c) { + sStatusFlags &= ~CAM_FLAG_SMOOTH_MOVEMENT; + set_fixed_cam_axis_sa_lobby(c->mode); + c->mode = CAMERA_MODE_FIXED; ++ skip_camera_interpolation(); + } + } + +@@ -7279,6 +7286,7 @@ BAD_RETURN(s32) cutscene_unused_loop(UNUSED struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_mario_fall_start(struct Camera *c) { + vec3f_set(c->focus, -26.f, 0.f, -137.f); + vec3f_set(c->pos, 165.f, 4725.f, 324.f); ++ skip_camera_interpolation(); + } + + /** +@@ -7311,6 +7319,7 @@ BAD_RETURN(s32) cutscene_ending_mario_fall(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { + vec3f_set(c->focus, 85.f, 826.f, 250.f); + vec3f_set(c->pos, -51.f, 988.f, -202.f); ++ skip_camera_interpolation(); + player2_rotate_cam(c, -0x2000, 0x2000, -0x2000, 0x2000); + } + +@@ -7320,6 +7329,7 @@ BAD_RETURN(s32) cutscene_ending_mario_land_closeup(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_reset_spline(UNUSED struct Camera *c) { + sCutsceneVars[9].point[0] = 0.f; + cutscene_reset_spline(); ++ skip_camera_interpolation(); + } + + /** +@@ -7355,6 +7365,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appear_closeup(struct Camera *c) { + vec3f_set(c->pos, 179.f, 2463.f, -1216.f); + c->pos[1] = gCutsceneFocus->oPosY + 35.f; + vec3f_set(c->focus, gCutsceneFocus->oPosX, gCutsceneFocus->oPosY + 125.f, gCutsceneFocus->oPosZ); ++ skip_camera_interpolation(); + } + + /** +@@ -7373,6 +7384,7 @@ BAD_RETURN(s32) cutscene_ending_peach_appears(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_peach_descends_start(UNUSED struct Camera *c) { + cutscene_reset_spline(); + sCutsceneVars[2].point[1] = 150.f; ++ skip_camera_interpolation(); + } + + /** +@@ -7459,6 +7471,7 @@ BAD_RETURN(s32) cutscene_ending_peach_wakeup(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_dialog(struct Camera *c) { + vec3f_set(c->focus, 11.f, 983.f, -1273.f); + vec3f_set(c->pos, -473.f, 970.f, -1152.f); ++ skip_camera_interpolation(); + player2_rotate_cam(c, -0x800, 0x2000, -0x2000, 0x2000); + } + +@@ -7469,6 +7482,7 @@ BAD_RETURN(s32) cutscene_ending_kiss_closeup(struct Camera *c) { + set_fov_function(CAM_FOV_SET_29); + vec3f_set(c->focus, 350.f, 1034.f, -1216.f); + vec3f_set(c->pos, -149.f, 1021.f, -1216.f); ++ skip_camera_interpolation(); + } + + /** +@@ -7504,6 +7518,7 @@ BAD_RETURN(s32) cutscene_ending_kiss(struct Camera *c) { + BAD_RETURN(s32) cutscene_ending_look_at_sky(struct Camera *c) { + move_point_along_spline(c->focus, sEndingLookAtSkyFocus, &sCutsceneSplineSegment, &sCutsceneSplineSegmentProgress); + vec3f_set(c->pos, 699.f, 1680.f, -703.f); ++ skip_camera_interpolation(); + } + + /** +@@ -10340,6 +10355,7 @@ BAD_RETURN(s32) cutscene_door_start(struct Camera *c) { + BAD_RETURN(s32) cutscene_door_fix_cam(struct Camera *c) { + vec3f_copy(c->pos, sCutsceneVars[0].point); + vec3f_copy(c->focus, sCutsceneVars[1].point); ++ skip_camera_interpolation(); + } + + /** +@@ -10373,6 +10389,7 @@ BAD_RETURN(s32) cutscene_door_move_behind_mario(struct Camera *c) { + } + + offset_rotated(c->pos, sMarioCamState->pos, camOffset, sCutsceneVars[0].angle); ++ skip_camera_interpolation(); + } + + /** +diff --git a/src/game/camera.h b/src/game/camera.h +index 173ab8a..b1abdc4 100644 +--- a/src/game/camera.h ++++ b/src/game/camera.h +@@ -657,6 +657,8 @@ struct LakituState + /// Mario's action from the previous frame. Only used to determine if Mario just finished a dive. + /*0xB8*/ u32 lastFrameAction; + /*0xBC*/ s16 unused; ++ ++ u32 skipCameraInterpolationTimestamp; + }; + + // bss order hack to not affect BSS order. if possible, remove me, but it will be hard to match otherwise +diff --git a/src/game/envfx_bubbles.c b/src/game/envfx_bubbles.c +index 16a9272..ee1b029 100644 +--- a/src/game/envfx_bubbles.c ++++ b/src/game/envfx_bubbles.c +@@ -35,6 +35,20 @@ Vtx_t gBubbleTempVtx[3] = { + { { 0, 0, 0 }, 0, { -498, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, + }; + ++static Gfx sGfxSaved[60 / 5]; ++static Gfx *sBubbleInterpolatedDisplayListPos[60 / 5]; ++static Vec3s sPrevBubblePositions[60]; ++ ++void patch_interpolated_bubble_particles(void) { ++ s32 i; ++ for (i = 0; i < 60 / 5; i++) { ++ if (sBubbleInterpolatedDisplayListPos[i] != NULL) { ++ *sBubbleInterpolatedDisplayListPos[i] = sGfxSaved[i]; ++ sBubbleInterpolatedDisplayListPos[i] = NULL; ++ } ++ } ++} ++ + /** + * Check whether the particle with the given index is + * laterally within distance of point (x, z). Used to +@@ -241,6 +255,7 @@ void envfx_update_whirlpool(void) { + (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY; + (gEnvFxBuffer + i)->unusedBubbleVar = 0; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + + envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos, + &(gEnvFxBuffer + i)->zPos); +@@ -299,6 +314,7 @@ void envfx_update_jetstream(void) { + + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; + (gEnvFxBuffer + i)->yPos = + gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f); ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } else { + (gEnvFxBuffer + i)->angleAndDist[1] += 10; + (gEnvFxBuffer + i)->xPos += sins((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f; +@@ -506,6 +522,12 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro + Vec3s vertex1; + Vec3s vertex2; + Vec3s vertex3; ++ Vec3s interpolatedVertices[3]; ++ ++ static Vec3s prevVertex1; ++ static Vec3s prevVertex2; ++ static Vec3s prevVertex3; ++ static u32 prevTimestamp; + + Gfx *gfxStart; + +@@ -521,18 +543,52 @@ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFro + envfx_bubbles_update_switch(mode, camTo, vertex1, vertex2, vertex3); + rotate_triangle_vertices(vertex1, vertex2, vertex3, pitch, yaw); + ++ if (gGlobalTimer == prevTimestamp + 1) { ++ interpolate_vectors_s16(interpolatedVertices[0], prevVertex1, vertex1); ++ interpolate_vectors_s16(interpolatedVertices[1], prevVertex2, vertex2); ++ interpolate_vectors_s16(interpolatedVertices[2], prevVertex3, vertex3); ++ } ++ vec3s_copy(prevVertex1, vertex1); ++ vec3s_copy(prevVertex2, vertex2); ++ vec3s_copy(prevVertex3, vertex3); ++ prevTimestamp = gGlobalTimer; ++ + gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D38); + + for (i = 0; i < sBubbleParticleMaxCount; i += 5) { ++ Vtx *interpolatedVertBuf = alloc_display_list(15 * sizeof(Vtx)); ++ s32 j, k; + gDPPipeSync(sGfxCursor++); + envfx_set_bubble_texture(mode, i); +- append_bubble_vertex_buffer(sGfxCursor++, i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); ++ sBubbleInterpolatedDisplayListPos[i / 5] = sGfxCursor; ++ for (j = 0; j < 5; j++) { ++ for (k = 0; k < 3; k++) { ++ Vtx *v = &interpolatedVertBuf[j * 3 + k]; ++ v->v = gBubbleTempVtx[k]; ++ if (gGlobalTimer != gEnvFxBuffer[i + j].spawnTimestamp && mode != ENVFX_LAVA_BUBBLES) { ++ v->v.ob[0] = (sPrevBubblePositions[i + j][0] + gEnvFxBuffer[i + j].xPos) / 2.0f + interpolatedVertices[k][0]; ++ v->v.ob[1] = (sPrevBubblePositions[i + j][1] + gEnvFxBuffer[i + j].yPos) / 2.0f + interpolatedVertices[k][1]; ++ v->v.ob[2] = (sPrevBubblePositions[i + j][2] + gEnvFxBuffer[i + j].zPos) / 2.0f + interpolatedVertices[k][2]; ++ } else { ++ v->v.ob[0] = gEnvFxBuffer[i + j].xPos + interpolatedVertices[k][0]; ++ v->v.ob[1] = gEnvFxBuffer[i + j].yPos + interpolatedVertices[k][1]; ++ v->v.ob[2] = gEnvFxBuffer[i + j].zPos + interpolatedVertices[k][2]; ++ } ++ } ++ } ++ gSPVertex(sGfxCursor++, VIRTUAL_TO_PHYSICAL(interpolatedVertBuf), 15, 0); ++ append_bubble_vertex_buffer(&sGfxSaved[i / 5], i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); + gSP1Triangle(sGfxCursor++, 0, 1, 2, 0); + gSP1Triangle(sGfxCursor++, 3, 4, 5, 0); + gSP1Triangle(sGfxCursor++, 6, 7, 8, 0); + gSP1Triangle(sGfxCursor++, 9, 10, 11, 0); + gSP1Triangle(sGfxCursor++, 12, 13, 14, 0); + } ++ for (i = 0; i < sBubbleParticleMaxCount; i++) { ++ sPrevBubblePositions[i][0] = gEnvFxBuffer[i].xPos; ++ sPrevBubblePositions[i][1] = gEnvFxBuffer[i].yPos; ++ sPrevBubblePositions[i][2] = gEnvFxBuffer[i].zPos; ++ } + + gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006AB0); + gSPEndDisplayList(sGfxCursor++); +diff --git a/src/game/envfx_snow.c b/src/game/envfx_snow.c +index c3c14a5..d2212ef 100644 +--- a/src/game/envfx_snow.c ++++ b/src/game/envfx_snow.c +@@ -54,6 +54,26 @@ extern void *tiny_bubble_dl_0B006AB0; + extern void *tiny_bubble_dl_0B006A50; + extern void *tiny_bubble_dl_0B006CD8; + ++static struct { ++ Gfx *pos; ++ Vtx vertices[15]; ++} sPrevSnowVertices[140 / 5]; ++static s16 sPrevSnowParticleCount; ++static u32 sPrevSnowTimestamp; ++ ++void patch_interpolated_snow_particles(void) { ++ int i; ++ ++ if (gGlobalTimer != sPrevSnowTimestamp + 1) { ++ return; ++ } ++ ++ for (i = 0; i < sPrevSnowParticleCount; i += 5) { ++ gSPVertex(sPrevSnowVertices[i / 5].pos, ++ VIRTUAL_TO_PHYSICAL(sPrevSnowVertices[i / 5].vertices), 15, 0); ++ } ++} ++ + /** + * Initialize snow particles by allocating a buffer for storing their state + * and setting a start amount. +@@ -217,6 +237,7 @@ void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCyli + 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); + (gEnvFxBuffer + i)->yPos = 200.0f * random_float() + snowCylinderY; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } else { + (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2); + (gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8); +@@ -251,6 +272,7 @@ void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCy + 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2); + (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } else { + (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f; + (gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8); +@@ -294,6 +316,7 @@ void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylin + (gEnvFxBuffer + i)->zPos = 400.0f * random_float() - 200.0f + snowCylinderZ; + (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY; + (gEnvFxBuffer + i)->isAlive = 1; ++ (gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer; + } + } + } +@@ -346,6 +369,8 @@ void rotate_triangle_vertices(Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, s16 p + void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) { + s32 i = 0; + Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); ++ Vtx *vertBufInterpolated = (Vtx *) alloc_display_list(15 * sizeof(Vtx)); ++ Vtx *v; + #ifdef VERSION_EU + Vtx *p; + #endif +@@ -395,7 +420,23 @@ void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s ve + #endif + } + +- gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0); ++ for (i = 0; i < 15; i++) { ++ v = &sPrevSnowVertices[index / 5].vertices[i]; ++ vertBufInterpolated[i] = gSnowTempVtx[i % 3]; ++ if (index < sPrevSnowParticleCount && gGlobalTimer == sPrevSnowTimestamp + 1 && ++ gGlobalTimer != gEnvFxBuffer[index + i / 3].spawnTimestamp) { ++ vertBufInterpolated[i].v.ob[0] = (v->v.ob[0] + vertBuf[i].v.ob[0]) / 2; ++ vertBufInterpolated[i].v.ob[1] = (v->v.ob[1] + vertBuf[i].v.ob[1]) / 2; ++ vertBufInterpolated[i].v.ob[2] = (v->v.ob[2] + vertBuf[i].v.ob[2]) / 2; ++ } else { ++ vertBufInterpolated[i].v.ob[0] = vertBuf[i].v.ob[0]; ++ vertBufInterpolated[i].v.ob[1] = vertBuf[i].v.ob[1]; ++ vertBufInterpolated[i].v.ob[2] = vertBuf[i].v.ob[2]; ++ } ++ *v = vertBuf[i]; ++ } ++ sPrevSnowVertices[index / 5].pos = gfx; ++ gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBufInterpolated), 15, 0); + } + + /** +@@ -479,6 +520,8 @@ Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) + gSP1Triangle(gfx++, 9, 10, 11, 0); + gSP1Triangle(gfx++, 12, 13, 14, 0); + } ++ sPrevSnowParticleCount = gSnowParticleCount; ++ sPrevSnowTimestamp = gGlobalTimer; + + gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++); + +diff --git a/src/game/envfx_snow.h b/src/game/envfx_snow.h +index 7a83b53..f4acc2d 100644 +--- a/src/game/envfx_snow.h ++++ b/src/game/envfx_snow.h +@@ -25,7 +25,8 @@ struct EnvFxParticle { + s32 angleAndDist[2]; // for whirpools, [0] = angle from center, [1] = distance from center + s32 unusedBubbleVar; // set to zero for bubbles when respawning, never used elsewhere + s32 bubbleY; // for Bubbles, yPos is always set to this +- s8 filler20[56 - 0x20]; ++ //s8 filler20[56 - 0x20]; ++ u32 spawnTimestamp; + }; + + extern s8 gEnvFxMode; +diff --git a/src/game/hud.c b/src/game/hud.c +index 1540b67..0de6e0b 100644 +--- a/src/game/hud.c ++++ b/src/game/hud.c +@@ -59,6 +59,20 @@ static struct UnusedHUDStruct sUnusedHUDValues = { 0x00, 0x0A, 0x00 }; + + static struct CameraHUD sCameraHUD = { CAM_STATUS_NONE }; + ++static u32 sPowerMeterLastRenderTimestamp; ++static s16 sPowerMeterLastY; ++static Gfx *sPowerMeterDisplayListPos; ++ ++void patch_interpolated_hud(void) { ++ if (sPowerMeterDisplayListPos != NULL) { ++ Mtx *mtx = alloc_display_list(sizeof(Mtx)); ++ guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); ++ gSPMatrix(sPowerMeterDisplayListPos, VIRTUAL_TO_PHYSICAL(mtx), ++ G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); ++ sPowerMeterDisplayListPos = NULL; ++ } ++} ++ + /** + * Renders a rgba16 16x16 glyph texture from a table list. + */ +@@ -111,6 +125,7 @@ void render_power_meter_health_segment(s16 numHealthWedges) { + */ + void render_dl_power_meter(s16 numHealthWedges) { + Mtx *mtx; ++ f32 interpolatedY; + + mtx = alloc_display_list(sizeof(Mtx)); + +@@ -118,7 +133,15 @@ void render_dl_power_meter(s16 numHealthWedges) { + return; + } + +- guTranslate(mtx, (f32) sPowerMeterHUD.x, (f32) sPowerMeterHUD.y, 0); ++ if (gGlobalTimer == sPowerMeterLastRenderTimestamp + 1) { ++ interpolatedY = (sPowerMeterLastY + sPowerMeterHUD.y) / 2.0f; ++ } else { ++ interpolatedY = sPowerMeterHUD.y; ++ } ++ guTranslate(mtx, (f32) sPowerMeterHUD.x, interpolatedY, 0); ++ sPowerMeterLastY = sPowerMeterHUD.y; ++ sPowerMeterLastRenderTimestamp = gGlobalTimer; ++ sPowerMeterDisplayListPos = gDisplayListHead; + + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx++), + G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); +diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c +index 7ae9f1e..1c23c96 100644 +--- a/src/game/ingame_menu.c ++++ b/src/game/ingame_menu.c +@@ -130,6 +130,42 @@ s32 gDialogResponse = 0; + static struct CachedChar { u8 used; u8 data[CHCACHE_BUFLEN]; } charCache[256]; + #endif // VERSION + ++static Gfx *sInterpolatedDialogOffsetPos; ++static f32 sInterpolatedDialogOffset; ++static Gfx *sInterpolatedDialogRotationPos; ++static f32 sInterpolatedDialogScale; ++static f32 sInterpolatedDialogRotation; ++static Gfx *sInterpolatedDialogZoomPos; ++ ++void patch_interpolated_dialog(void) { ++ Mtx *matrix; ++ ++ if (sInterpolatedDialogOffsetPos != NULL) { ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guTranslate(matrix, 0, sInterpolatedDialogOffset, 0); ++ gSPMatrix(sInterpolatedDialogOffsetPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ sInterpolatedDialogOffsetPos = NULL; ++ } ++ if (sInterpolatedDialogRotationPos != NULL) { ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); ++ gSPMatrix(sInterpolatedDialogRotationPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guRotate(matrix, sInterpolatedDialogRotation * 4.0f, 0, 0, 1.0f); ++ gSPMatrix(sInterpolatedDialogRotationPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ sInterpolatedDialogRotationPos = NULL; ++ } ++ if (sInterpolatedDialogZoomPos != NULL) { ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guTranslate(matrix, 65.0 - (65.0 / sInterpolatedDialogScale), (40.0 / sInterpolatedDialogScale) - 40, 0); ++ gSPMatrix(sInterpolatedDialogZoomPos++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); ++ guScale(matrix, 1.0 / sInterpolatedDialogScale, 1.0 / sInterpolatedDialogScale, 1.0f); ++ gSPMatrix(sInterpolatedDialogZoomPos, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); ++ sInterpolatedDialogZoomPos = NULL; ++ } ++} ++ + void create_dl_identity_matrix(void) { + Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + +@@ -969,6 +1005,14 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { + switch (gDialogBoxType) { + case DIALOG_TYPE_ROTATE: // Renders a dialog black box with zoom and rotation + if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { ++ sInterpolatedDialogRotationPos = gDisplayListHead; ++ if (gDialogBoxState == DIALOG_STATE_OPENING) { ++ sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; ++ sInterpolatedDialogRotation = gDialogBoxOpenTimer - 7.5f / 2; ++ } else { ++ sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; ++ sInterpolatedDialogRotation = gDialogBoxOpenTimer + 7.5f / 2; ++ } + create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); + // convert the speed into angle + create_dl_rotation_matrix(MENU_MTX_NOPUSH, gDialogBoxOpenTimer * 4.0f, 0, 0, 1.0f); +@@ -977,6 +1021,12 @@ void render_dialog_box_type(struct DialogEntry *dialog, s8 linesPerBox) { + break; + case DIALOG_TYPE_ZOOM: // Renders a dialog white box with zoom + if (gDialogBoxState == DIALOG_STATE_OPENING || gDialogBoxState == DIALOG_STATE_CLOSING) { ++ sInterpolatedDialogZoomPos = gDisplayListHead; ++ if (gDialogBoxState == DIALOG_STATE_OPENING) { ++ sInterpolatedDialogScale = gDialogBoxScale - 2 / 2; ++ } else { ++ sInterpolatedDialogScale = gDialogBoxScale + 2 / 2; ++ } + create_dl_translation_matrix(MENU_MTX_NOPUSH, 65.0 - (65.0 / gDialogBoxScale), + (40.0 / gDialogBoxScale) - 40, 0); + create_dl_scale_matrix(MENU_MTX_NOPUSH, 1.0 / gDialogBoxScale, 1.0 / gDialogBoxScale, 1.0f); +@@ -1259,6 +1309,8 @@ void handle_dialog_text_and_pages(s8 colorMode, struct DialogEntry *dialog, s8 l + #ifdef VERSION_EU + gDialogY -= gDialogScrollOffsetY; + #else ++ sInterpolatedDialogOffset = gDialogScrollOffsetY + dialog->linesPerBox; ++ sInterpolatedDialogOffsetPos = gDisplayListHead; + create_dl_translation_matrix(MENU_MTX_NOPUSH, 0, (f32) gDialogScrollOffsetY, 0); + #endif + } +diff --git a/src/game/level_geo.c b/src/game/level_geo.c +index 4c98e70..abc5121 100644 +--- a/src/game/level_geo.c ++++ b/src/game/level_geo.c +@@ -34,12 +34,16 @@ Gfx *geo_envfx_main(s32 callContext, struct GraphNode *node, Mat4 mtxf) { + vec3f_to_vec3s(marioPos, gPlayerCameraState->pos); + particleList = envfx_update_particles(snowMode, marioPos, camTo, camFrom); + if (particleList != NULL) { ++#if 0 + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + gfx = alloc_display_list(2 * sizeof(*gfx)); + mtxf_to_mtx(mtx, mtxf); + gSPMatrix(&gfx[0], VIRTUAL_TO_PHYSICAL(mtx), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gSPBranchList(&gfx[1], VIRTUAL_TO_PHYSICAL(particleList)); ++#else ++ gfx = particleList; ++#endif + execNode->fnNode.node.flags = (execNode->fnNode.node.flags & 0xFF) | 0x400; + } + SET_HIGH_U16_OF_32(*params, gAreaUpdateCounter); +diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c +index 22b45b3..109d7f7 100644 +--- a/src/game/object_helpers.c ++++ b/src/game/object_helpers.c +@@ -1554,6 +1554,7 @@ void cur_obj_set_pos_to_home(void) { + o->oPosX = o->oHomeX; + o->oPosY = o->oHomeY; + o->oPosZ = o->oHomeZ; ++ o->header.gfx.skipInterpolationTimestamp = gGlobalTimer; + } + + void cur_obj_set_pos_to_home_and_stop(void) { +diff --git a/src/game/paintings.c b/src/game/paintings.c +index 6cae19c..a304d4a 100644 +--- a/src/game/paintings.c ++++ b/src/game/paintings.c +@@ -189,6 +189,32 @@ struct Painting **sPaintingGroups[] = { + s16 gPaintingUpdateCounter = 1; + s16 gLastPaintingUpdateCounter = 0; + ++static Vtx sLastVertices[2 * 264 * 3]; ++static u32 sLastVerticesTimestamp; ++static Vtx *sVerticesPtr[2]; ++static s32 sVerticesCount; ++ ++void patch_interpolated_paintings(void) { ++ if (sVerticesPtr[0] != NULL) { ++ s32 i; ++ if (sVerticesPtr[1] != NULL) { ++ for (i = 0; i < sVerticesCount / 2; i++) { ++ sVerticesPtr[0][i] = sLastVertices[i]; ++ } ++ for (; i < sVerticesCount; i++) { ++ sVerticesPtr[1][i - sVerticesCount / 2] = sLastVertices[i]; ++ } ++ } else { ++ for (i = 0; i < sVerticesCount; i++) { ++ sVerticesPtr[0][i] = sLastVertices[i]; ++ } ++ } ++ sVerticesPtr[0] = NULL; ++ sVerticesPtr[1] = NULL; ++ sVerticesCount = 0; ++ } ++} ++ + /** + * Stop paintings in paintingGroup from rippling if their id is different from *idptr. + */ +@@ -890,6 +916,23 @@ Gfx *render_painting(u8 *img, s16 tWidth, s16 tHeight, s16 *textureMap, s16 mapV + gSP1Triangle(gfx++, group * 3, group * 3 + 1, group * 3 + 2, 0); + } + ++ if (sVerticesCount >= numVtx * 2) { ++ sVerticesCount = 0; ++ } ++ for (map = 0; map < numVtx; map++) { ++ Vtx v = verts[map]; ++ if (gGlobalTimer == sLastVerticesTimestamp + 1) { ++ s32 i; ++ for (i = 0; i < 3; i++) { ++ verts[map].n.ob[i] = (v.n.ob[i] + sLastVertices[sVerticesCount + map].n.ob[i]) / 2; ++ verts[map].n.n[i] = (v.n.n[i] + sLastVertices[sVerticesCount + map].n.n[i]) / 2; ++ } ++ } ++ sLastVertices[sVerticesCount + map] = v; ++ } ++ sVerticesPtr[sVerticesCount / numVtx] = verts; ++ sVerticesCount += numVtx; ++ + gSPEndDisplayList(gfx); + return dlist; + } +@@ -954,6 +997,7 @@ Gfx *painting_ripple_image(struct Painting *painting) { + meshTris = textureMap[meshVerts * 3 + 1]; + gSPDisplayList(gfx++, render_painting(textures[i], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); + } ++ sLastVerticesTimestamp = gGlobalTimer; + + // Update the ripple, may automatically reset the painting's state. + painting_update_ripple_state(painting); +@@ -991,6 +1035,7 @@ Gfx *painting_ripple_env_mapped(struct Painting *painting) { + meshVerts = textureMap[0]; + meshTris = textureMap[meshVerts * 3 + 1]; + gSPDisplayList(gfx++, render_painting(tArray[0], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha)); ++ sLastVerticesTimestamp = gGlobalTimer; + + // Update the ripple, may automatically reset the painting's state. + painting_update_ripple_state(painting); +diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c +index d5bf577..71656b4 100644 +--- a/src/game/rendering_graph_node.c ++++ b/src/game/rendering_graph_node.c +@@ -39,6 +39,8 @@ + s16 gMatStackIndex; + Mat4 gMatStack[32]; + Mtx *gMatStackFixed[32]; ++Mat4 gMatStackInterpolated[32]; ++Mtx *gMatStackInterpolatedFixed[32]; + + /** + * Animation nodes have state in global variables, so this struct captures +@@ -52,6 +54,7 @@ struct GeoAnimState { + /*0x04*/ f32 translationMultiplier; + /*0x08*/ u16 *attribute; + /*0x0C*/ s16 *data; ++ s16 prevFrame; + }; + + // For some reason, this is a GeoAnimState struct, but the current state consists +@@ -61,6 +64,7 @@ struct GeoAnimState gGeoTempState; + u8 gCurAnimType; + u8 gCurAnimEnabled; + s16 gCurrAnimFrame; ++s16 gPrevAnimFrame; + f32 gCurAnimTranslationMultiplier; + u16 *gCurrAnimAttribute; + s16 *gCurAnimData; +@@ -129,6 +133,46 @@ u16 gAreaUpdateCounter = 0; + LookAt lookAt; + #endif + ++static Gfx *sPerspectivePos; ++static Mtx *sPerspectiveMtx; ++ ++struct { ++ Gfx *pos; ++ void *mtx; ++ void *displayList; ++} gMtxTbl[6400]; ++s32 gMtxTblSize; ++ ++static Gfx *sViewportPos; ++static Vp sPrevViewport; ++ ++void mtx_patch_interpolated(void) { ++ s32 i; ++ ++ if (sPerspectivePos != NULL) { ++ gSPMatrix(sPerspectivePos, VIRTUAL_TO_PHYSICAL(sPerspectiveMtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ } ++ ++ for (i = 0; i < gMtxTblSize; i++) { ++ Gfx *pos = gMtxTbl[i].pos; ++ gSPMatrix(pos++, VIRTUAL_TO_PHYSICAL(gMtxTbl[i].mtx), ++ G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); ++ gSPDisplayList(pos++, gMtxTbl[i].displayList); ++ } ++ ++ if (sViewportPos != NULL) { ++ Gfx *saved = gDisplayListHead; ++ gDisplayListHead = sViewportPos; ++ make_viewport_clip_rect(&sPrevViewport); ++ gSPViewport(gDisplayListHead, VIRTUAL_TO_PHYSICAL(&sPrevViewport)); ++ gDisplayListHead = saved; ++ } ++ ++ gMtxTblSize = 0; ++ sPerspectivePos = NULL; ++ sViewportPos = NULL; ++} ++ + /** + * Process a master list node. + */ +@@ -156,9 +200,14 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { + if ((currList = node->listHeads[i]) != NULL) { + gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]); + while (currList != NULL) { +- gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transform), ++ if ((u32) gMtxTblSize < sizeof(gMtxTbl) / sizeof(gMtxTbl[0])) { ++ gMtxTbl[gMtxTblSize].pos = gDisplayListHead; ++ gMtxTbl[gMtxTblSize].mtx = currList->transform; ++ gMtxTbl[gMtxTblSize++].displayList = currList->displayList; ++ } ++ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transformInterpolated), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); +- gSPDisplayList(gDisplayListHead++, currList->displayList); ++ gSPDisplayList(gDisplayListHead++, currList->displayListInterpolated); + currList = currList->next; + } + } +@@ -174,7 +223,7 @@ static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { + * parameter. Look at the RenderModeContainer struct to see the corresponding + * render modes of layers. + */ +-static void geo_append_display_list(void *displayList, s16 layer) { ++static void geo_append_display_list2(void *displayList, void *displayListInterpolated, s16 layer) { + + #ifdef F3DEX_GBI_2 + gSPLookAt(gDisplayListHead++, &lookAt); +@@ -184,7 +233,9 @@ static void geo_append_display_list(void *displayList, s16 layer) { + alloc_only_pool_alloc(gDisplayListHeap, sizeof(struct DisplayListNode)); + + listNode->transform = gMatStackFixed[gMatStackIndex]; ++ listNode->transformInterpolated = gMatStackInterpolatedFixed[gMatStackIndex]; + listNode->displayList = displayList; ++ listNode->displayListInterpolated = displayListInterpolated; + listNode->next = 0; + if (gCurGraphNodeMasterList->listHeads[layer] == 0) { + gCurGraphNodeMasterList->listHeads[layer] = listNode; +@@ -195,6 +246,10 @@ static void geo_append_display_list(void *displayList, s16 layer) { + } + } + ++static void geo_append_display_list(void *displayList, s16 layer) { ++ geo_append_display_list2(displayList, displayList, layer); ++} ++ + /** + * Process the master list node. + */ +@@ -241,7 +296,9 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + } + if (node->fnNode.node.children != NULL) { + u16 perspNorm; ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ f32 fovInterpolated; + + #ifdef VERSION_EU + f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; +@@ -250,9 +307,23 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + #endif + + guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); +- gSPPerspNormalize(gDisplayListHead++, perspNorm); + +- gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { ++ ++ fovInterpolated = (node->prevFov + node->fov) / 2.0f; ++ guPerspective(mtxInterpolated, &perspNorm, fovInterpolated, aspect, node->near, node->far, 1.0f); ++ gSPPerspNormalize(gDisplayListHead++, perspNorm); ++ ++ sPerspectivePos = gDisplayListHead; ++ sPerspectiveMtx = mtx; ++ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtxInterpolated), ++ G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ } else { ++ gSPPerspNormalize(gDisplayListHead++, perspNorm); ++ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); ++ } ++ node->prevFov = node->fov; ++ node->prevTimestamp = gGlobalTimer; + + gCurGraphNodeCamFrustum = node; + geo_process_node_and_siblings(node->fnNode.node.children); +@@ -297,6 +368,39 @@ static void geo_process_switch(struct GraphNodeSwitchCase *node) { + } + } + ++void interpolate_vectors(Vec3f res, Vec3f a, Vec3f b) { ++ res[0] = (a[0] + b[0]) / 2.0f; ++ res[1] = (a[1] + b[1]) / 2.0f; ++ res[2] = (a[2] + b[2]) / 2.0f; ++} ++ ++void interpolate_vectors_s16(Vec3s res, Vec3s a, Vec3s b) { ++ res[0] = (a[0] + b[0]) / 2; ++ res[1] = (a[1] + b[1]) / 2; ++ res[2] = (a[2] + b[2]) / 2; ++} ++ ++static s16 interpolate_angle(s16 a, s16 b) { ++ s32 absDiff = b - a; ++ if (absDiff < 0) { ++ absDiff = -absDiff; ++ } ++ if (absDiff >= 0x4000 && absDiff <= 0xC000) { ++ return b; ++ } ++ if (absDiff <= 0x8000) { ++ return (a + b) / 2; ++ } else { ++ return (a + b) / 2 + 0x8000; ++ } ++} ++ ++static void interpolate_angles(Vec3s res, Vec3s a, Vec3s b) { ++ res[0] = interpolate_angle(a[0], b[0]); ++ res[1] = interpolate_angle(a[1], b[1]); ++ res[2] = interpolate_angle(a[2], b[2]); ++} ++ + /** + * Process a camera node. + */ +@@ -304,6 +408,9 @@ static void geo_process_camera(struct GraphNodeCamera *node) { + Mat4 cameraTransform; + Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx)); + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ Vec3f posInterpolated; ++ Vec3f focusInterpolated; + + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); +@@ -314,12 +421,40 @@ static void geo_process_camera(struct GraphNodeCamera *node) { + + mtxf_lookat(cameraTransform, node->pos, node->focus, node->roll); + mtxf_mul(gMatStack[gMatStackIndex + 1], cameraTransform, gMatStack[gMatStackIndex]); ++ ++ if (gGlobalTimer == node->prevTimestamp + 1 && gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->prevPos, node->pos); ++ interpolate_vectors(focusInterpolated, node->prevFocus, node->focus); ++ float magnitude = 0; ++ for (int i = 0; i < 3; i++) { ++ float diff = node->pos[i] - node->prevPos[i]; ++ magnitude += diff * diff; ++ } ++ if (magnitude > 500000) { ++ // Observed ~479000 in BBH when toggling R camera ++ // Can get over 3 million in VCUTM though... ++ vec3f_copy(posInterpolated, node->pos); ++ vec3f_copy(focusInterpolated, node->focus); ++ } ++ } else { ++ vec3f_copy(posInterpolated, node->pos); ++ vec3f_copy(focusInterpolated, node->focus); ++ } ++ vec3f_copy(node->prevPos, node->pos); ++ vec3f_copy(node->prevFocus, node->focus); ++ node->prevTimestamp = gGlobalTimer; ++ mtxf_lookat(cameraTransform, posInterpolated, focusInterpolated, node->roll); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], cameraTransform, gMatStackInterpolated[gMatStackIndex]); ++ + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->fnNode.node.children != 0) { + gCurGraphNodeCamera = node; + node->matrixPtr = &gMatStack[gMatStackIndex]; ++ node->matrixPtrInterpolated = &gMatStackInterpolated[gMatStackIndex]; + geo_process_node_and_siblings(node->fnNode.node.children); + gCurGraphNodeCamera = NULL; + } +@@ -336,13 +471,17 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation + Mat4 mtxf; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + vec3s_to_vec3f(translation, node->translation); + mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -361,13 +500,17 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { + Mat4 mtxf; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + vec3s_to_vec3f(translation, node->translation); + mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -385,12 +528,23 @@ static void geo_process_translation(struct GraphNodeTranslation *node) { + static void geo_process_rotation(struct GraphNodeRotation *node) { + Mat4 mtxf; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ Vec3s rotationInterpolated; + + mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ if (gGlobalTimer == node->prevTimestamp + 1) { ++ interpolate_angles(rotationInterpolated, node->prevRotation, node->rotation); ++ mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, rotationInterpolated); ++ } ++ vec3s_copy(node->prevRotation, node->rotation); ++ node->prevTimestamp = gGlobalTimer; ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -409,12 +563,16 @@ static void geo_process_scale(struct GraphNodeScale *node) { + UNUSED Mat4 transform; + Vec3f scaleVec; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + vec3f_set(scaleVec, node->scale, node->scale, node->scale); + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], scaleVec); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -433,21 +591,30 @@ static void geo_process_scale(struct GraphNodeScale *node) { + static void geo_process_billboard(struct GraphNodeBillboard *node) { + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + gMatStackIndex++; + vec3s_to_vec3f(translation, node->translation); + mtxf_billboard(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex - 1], translation, + gCurGraphNodeCamera->roll); ++ mtxf_billboard(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex - 1], translation, ++ gCurGraphNodeCamera->roll); + if (gCurGraphNodeHeldObject != NULL) { + mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], + gCurGraphNodeHeldObject->objNode->header.gfx.scale); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], ++ gCurGraphNodeHeldObject->objNode->header.gfx.scale); + } else if (gCurGraphNodeObject != NULL) { + mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], + gCurGraphNodeObject->scale); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex], gMatStackInterpolated[gMatStackIndex], ++ gCurGraphNodeObject->scale); + } + + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -496,13 +663,39 @@ static void geo_process_generated_list(struct GraphNodeGenerated *node) { + */ + static void geo_process_background(struct GraphNodeBackground *node) { + Gfx *list = NULL; ++ Gfx *listInterpolated = NULL; + + if (node->fnNode.func != NULL) { ++ Vec3f posCopy; ++ Vec3f focusCopy; ++ Vec3f posInterpolated; ++ Vec3f focusInterpolated; ++ ++ if (gGlobalTimer == node->prevCameraTimestamp + 1 && ++ gGlobalTimer != gLakituState.skipCameraInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->prevCameraPos, gLakituState.pos); ++ interpolate_vectors(focusInterpolated, node->prevCameraFocus, gLakituState.focus); ++ } else { ++ vec3f_copy(posInterpolated, gLakituState.pos); ++ vec3f_copy(focusInterpolated, gLakituState.focus); ++ } ++ vec3f_copy(node->prevCameraPos, gLakituState.pos); ++ vec3f_copy(node->prevCameraFocus, gLakituState.focus); ++ node->prevCameraTimestamp = gGlobalTimer; ++ + list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex]); ++ vec3f_copy(posCopy, gLakituState.pos); ++ vec3f_copy(focusCopy, gLakituState.focus); ++ vec3f_copy(gLakituState.pos, posInterpolated); ++ vec3f_copy(gLakituState.focus, focusInterpolated); ++ listInterpolated = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, NULL); ++ vec3f_copy(gLakituState.pos, posCopy); ++ vec3f_copy(gLakituState.focus, focusCopy); + } + if (list != 0) { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(list), ++ (void *) VIRTUAL_TO_PHYSICAL(listInterpolated), node->fnNode.node.flags >> 8); + } else if (gCurGraphNodeMasterList != NULL) { + #ifndef F3DEX_GBI_2E + Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 7); +@@ -527,61 +720,81 @@ static void geo_process_background(struct GraphNodeBackground *node) { + } + } + +-/** +- * Render an animated part. The current animation state is not part of the node +- * but set in global variables. If an animated part is skipped, everything afterwards desyncs. +- */ +-static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { +- Mat4 matrix; +- Vec3s rotation; +- Vec3f translation; +- Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); +- +- vec3s_copy(rotation, gVec3sZero); +- vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); +- if (gCurAnimType == ANIM_TYPE_TRANSLATION) { +- translation[0] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++static void anim_process(Vec3f translation, Vec3s rotation, u8 *animType, s16 animFrame, u16 **animAttribute) { ++ if (*animType == ANIM_TYPE_TRANSLATION) { ++ translation[0] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- translation[1] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ translation[1] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- translation[2] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ translation[2] += gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurAnimType = ANIM_TYPE_ROTATION; ++ *animType = ANIM_TYPE_ROTATION; + } else { +- if (gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION) { ++ if (*animType == ANIM_TYPE_LATERAL_TRANSLATION) { + translation[0] += +- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurrAnimAttribute += 2; ++ *animAttribute += 2; + translation[2] += +- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurAnimType = ANIM_TYPE_ROTATION; ++ *animType = ANIM_TYPE_ROTATION; + } else { +- if (gCurAnimType == ANIM_TYPE_VERTICAL_TRANSLATION) { +- gCurrAnimAttribute += 2; ++ if (*animType == ANIM_TYPE_VERTICAL_TRANSLATION) { ++ *animAttribute += 2; + translation[1] += +- gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] ++ gCurAnimData[retrieve_animation_index(animFrame, animAttribute)] + * gCurAnimTranslationMultiplier; +- gCurrAnimAttribute += 2; +- gCurAnimType = ANIM_TYPE_ROTATION; +- } else if (gCurAnimType == ANIM_TYPE_NO_TRANSLATION) { +- gCurrAnimAttribute += 6; +- gCurAnimType = ANIM_TYPE_ROTATION; ++ *animAttribute += 2; ++ *animType = ANIM_TYPE_ROTATION; ++ } else if (*animType == ANIM_TYPE_NO_TRANSLATION) { ++ *animAttribute += 6; ++ *animType = ANIM_TYPE_ROTATION; + } + } + } + +- if (gCurAnimType == ANIM_TYPE_ROTATION) { +- rotation[0] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; +- rotation[1] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; +- rotation[2] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; ++ if (*animType == ANIM_TYPE_ROTATION) { ++ rotation[0] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; ++ rotation[1] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; ++ rotation[2] = gCurAnimData[retrieve_animation_index(animFrame, animAttribute)]; + } ++} ++ ++/** ++ * Render an animated part. The current animation state is not part of the node ++ * but set in global variables. If an animated part is skipped, everything afterwards desyncs. ++ */ ++static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { ++ Mat4 matrix; ++ Vec3s rotation; ++ Vec3f translation; ++ Vec3s rotationInterpolated; ++ Vec3f translationInterpolated; ++ Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ u16 *animAttribute = gCurrAnimAttribute; ++ u8 animType = gCurAnimType; ++ ++ vec3s_copy(rotation, gVec3sZero); ++ vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); ++ vec3s_copy(rotationInterpolated, rotation); ++ vec3f_copy(translationInterpolated, translation); ++ ++ anim_process(translationInterpolated, rotationInterpolated, &animType, gPrevAnimFrame, &animAttribute); ++ anim_process(translation, rotation, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); ++ interpolate_vectors(translationInterpolated, translationInterpolated, translation); ++ interpolate_angles(rotationInterpolated, rotationInterpolated, rotation); ++ + mtxf_rotate_xyz_and_translate(matrix, translation, rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]); ++ mtxf_rotate_xyz_and_translate(matrix, translationInterpolated, rotationInterpolated); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], matrix, gMatStackInterpolated[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(matrixPtr, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = matrixPtr; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } +@@ -613,6 +826,17 @@ void geo_set_animation_globals(struct GraphNodeObject_sub *node, s32 hasAnimatio + } + + gCurrAnimFrame = node->animFrame; ++ if (node->prevAnimPtr == anim && node->prevAnimID == node->animID && ++ gGlobalTimer == node->prevAnimFrameTimestamp + 1) { ++ gPrevAnimFrame = node->prevAnimFrame; ++ } else { ++ gPrevAnimFrame = node->animFrame; ++ } ++ node->prevAnimPtr = anim; ++ node->prevAnimID = node->animID; ++ node->prevAnimFrame = node->animFrame; ++ node->prevAnimFrameTimestamp = gGlobalTimer; ++ + gCurAnimEnabled = (anim->flags & ANIM_FLAG_5) == 0; + gCurrAnimAttribute = segmented_to_virtual((void *) anim->index); + gCurAnimData = segmented_to_virtual((void *) anim->values); +@@ -631,8 +855,10 @@ void geo_set_animation_globals(struct GraphNodeObject_sub *node, s32 hasAnimatio + */ + static void geo_process_shadow(struct GraphNodeShadow *node) { + Gfx *shadowList; ++ Gfx *shadowListInterpolated; + Mat4 mtxf; + Vec3f shadowPos; ++ Vec3f shadowPosInterpolated; + Vec3f animOffset; + f32 objScale; + f32 shadowScale; +@@ -640,6 +866,7 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { + f32 cosAng; + struct GraphNode *geo; + Mtx *mtx; ++ Mtx *mtxInterpolated; + + if (gCurGraphNodeCamera != NULL && gCurGraphNodeObject != NULL) { + if (gCurGraphNodeHeldObject != NULL) { +@@ -678,21 +905,57 @@ static void geo_process_shadow(struct GraphNodeShadow *node) { + } + } + ++ if (gCurGraphNodeHeldObject != NULL) { ++ if (gGlobalTimer == gCurGraphNodeHeldObject->prevShadowPosTimestamp + 1) { ++ interpolate_vectors(shadowPosInterpolated, gCurGraphNodeHeldObject->prevShadowPos, shadowPos); ++ } else { ++ vec3f_copy(shadowPosInterpolated, shadowPos); ++ } ++ vec3f_copy(gCurGraphNodeHeldObject->prevShadowPos, shadowPos); ++ gCurGraphNodeHeldObject->prevShadowPosTimestamp = gGlobalTimer; ++ } else { ++ if (gGlobalTimer == gCurGraphNodeObject->prevShadowPosTimestamp + 1 && ++ gGlobalTimer != gCurGraphNodeObject->skipInterpolationTimestamp) { ++ interpolate_vectors(shadowPosInterpolated, gCurGraphNodeObject->prevShadowPos, shadowPos); ++ } else { ++ vec3f_copy(shadowPosInterpolated, shadowPos); ++ } ++ vec3f_copy(gCurGraphNodeObject->prevShadowPos, shadowPos); ++ gCurGraphNodeObject->prevShadowPosTimestamp = gGlobalTimer; ++ } ++ ++ extern u8 gInterpolatingSurfaces; ++ gInterpolatingSurfaces = TRUE; ++ shadowListInterpolated = create_shadow_below_xyz(shadowPosInterpolated[0], shadowPosInterpolated[1], ++ shadowPosInterpolated[2], shadowScale, ++ node->shadowSolidity, node->shadowType); ++ gInterpolatingSurfaces = FALSE; + shadowList = create_shadow_below_xyz(shadowPos[0], shadowPos[1], shadowPos[2], shadowScale, + node->shadowSolidity, node->shadowType); +- if (shadowList != NULL) { ++ if (shadowListInterpolated != NULL && shadowList != NULL) { + mtx = alloc_display_list(sizeof(*mtx)); ++ mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + gMatStackIndex++; ++ + mtxf_translate(mtxf, shadowPos); + mtxf_mul(gMatStack[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtr); + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ ++ mtxf_translate(mtxf, shadowPosInterpolated); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtrInterpolated); ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; ++ + if (gShadowAboveWaterOrLava == 1) { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 4); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), ++ (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 4); + } else if (gMarioOnIceOrCarpet == 1) { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 5); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), ++ (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 5); + } else { +- geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 6); ++ geo_append_display_list2((void *) VIRTUAL_TO_PHYSICAL(shadowList), ++ (void *) VIRTUAL_TO_PHYSICAL(shadowListInterpolated), 6); + } + gMatStackIndex--; + } +@@ -789,31 +1052,101 @@ static int obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { + return TRUE; + } + ++static void interpolate_matrix(Mat4 result, Mat4 a, Mat4 b) { ++ s32 i, j; ++ for (i = 0; i < 4; i++) { ++ for (j = 0; j < 4; j++) { ++ result[i][j] = (a[i][j] + b[i][j]) / 2.0f; ++ } ++ } ++} ++ + /** + * Process an object node. + */ + static void geo_process_object(struct Object *node) { + Mat4 mtxf; + s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; ++ Vec3f scaleInterpolated; + + if (node->header.gfx.unk18 == gCurGraphNodeRoot->areaIndex) { + if (node->header.gfx.throwMatrix != NULL) { + mtxf_mul(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, + gMatStack[gMatStackIndex]); ++ if (gGlobalTimer == node->header.gfx.prevThrowMatrixTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_matrix(mtxf, *node->header.gfx.throwMatrix, node->header.gfx.prevThrowMatrix); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, ++ gMatStackInterpolated[gMatStackIndex]); ++ } else { ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], (void *) node->header.gfx.throwMatrix, ++ gMatStackInterpolated[gMatStackIndex]); ++ } ++ mtxf_copy(node->header.gfx.prevThrowMatrix, *node->header.gfx.throwMatrix); ++ node->header.gfx.prevThrowMatrixTimestamp = gGlobalTimer; + } else if (node->header.gfx.node.flags & GRAPH_RENDER_CYLBOARD) { ++ Vec3f posInterpolated; ++ if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); ++ } else { ++ vec3f_copy(posInterpolated, node->header.gfx.pos); ++ } ++ vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); ++ node->header.gfx.prevTimestamp = gGlobalTimer; + mtxf_cylboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], + node->header.gfx.pos, gCurGraphNodeCamera->roll); ++ mtxf_cylboard(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], ++ posInterpolated, gCurGraphNodeCamera->roll); + } else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) { ++ Vec3f posInterpolated; ++ if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); ++ } else { ++ vec3f_copy(posInterpolated, node->header.gfx.pos); ++ } ++ vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); ++ node->header.gfx.prevTimestamp = gGlobalTimer; + mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], + node->header.gfx.pos, gCurGraphNodeCamera->roll); ++ mtxf_billboard(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex], ++ posInterpolated, gCurGraphNodeCamera->roll); + } else { ++ Vec3f posInterpolated; ++ Vec3s angleInterpolated; ++ if (gGlobalTimer == node->header.gfx.prevTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(posInterpolated, node->header.gfx.prevPos, node->header.gfx.pos); ++ interpolate_angles(angleInterpolated, node->header.gfx.prevAngle, node->header.gfx.angle); ++ } else { ++ vec3f_copy(posInterpolated, node->header.gfx.pos); ++ vec3s_copy(angleInterpolated, node->header.gfx.angle); ++ } ++ vec3f_copy(node->header.gfx.prevPos, node->header.gfx.pos); ++ vec3s_copy(node->header.gfx.prevAngle, node->header.gfx.angle); ++ node->header.gfx.prevTimestamp = gGlobalTimer; + mtxf_rotate_zxy_and_translate(mtxf, node->header.gfx.pos, node->header.gfx.angle); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); ++ mtxf_rotate_zxy_and_translate(mtxf, posInterpolated, angleInterpolated); ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mtxf, gMatStackInterpolated[gMatStackIndex]); ++ } ++ ++ if (gGlobalTimer == node->header.gfx.prevScaleTimestamp + 1 && ++ gGlobalTimer != node->header.gfx.skipInterpolationTimestamp) { ++ interpolate_vectors(scaleInterpolated, node->header.gfx.prevScale, node->header.gfx.scale); ++ } else { ++ vec3f_copy(scaleInterpolated, node->header.gfx.scale); + } ++ vec3f_copy(node->header.gfx.prevScale, node->header.gfx.scale); ++ node->header.gfx.prevScaleTimestamp = gGlobalTimer; + + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], + node->header.gfx.scale); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], ++ scaleInterpolated); + node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex]; ++ node->header.gfx.throwMatrixInterpolated = &gMatStackInterpolated[gMatStackIndex]; + node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0]; + node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1]; + node->header.gfx.cameraToObject[2] = gMatStack[gMatStackIndex][3][2]; +@@ -824,9 +1157,12 @@ static void geo_process_object(struct Object *node) { + } + if (obj_is_in_view(&node->header.gfx, gMatStack[gMatStackIndex])) { + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); + + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + if (node->header.gfx.sharedChild != NULL) { + gCurGraphNodeObject = (struct GraphNodeObject *) node; + node->header.gfx.sharedChild->parent = &node->header.gfx.node; +@@ -837,11 +1173,16 @@ static void geo_process_object(struct Object *node) { + if (node->header.gfx.node.children != NULL) { + geo_process_node_and_siblings(node->header.gfx.node.children); + } ++ } else { ++ node->header.gfx.prevThrowMatrixTimestamp = 0; ++ node->header.gfx.prevTimestamp = 0; ++ node->header.gfx.prevScaleTimestamp = 0; + } + + gMatStackIndex--; + gCurAnimType = ANIM_TYPE_NONE; + node->header.gfx.throwMatrix = NULL; ++ node->header.gfx.throwMatrixInterpolated = NULL; + } + } + +@@ -868,6 +1209,8 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + Mat4 mat; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); ++ Mtx *mtxInterpolated = alloc_display_list(sizeof(*mtxInterpolated)); ++ Vec3f scaleInterpolated; + + #ifdef F3DEX_GBI_2 + gSPLookAt(gDisplayListHead++, &lookAt); +@@ -883,6 +1226,14 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + translation[1] = node->translation[1] / 4.0f; + translation[2] = node->translation[2] / 4.0f; + ++ if (gGlobalTimer == node->objNode->header.gfx.prevScaleTimestamp + 1) { ++ interpolate_vectors(scaleInterpolated, node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); ++ } else { ++ vec3f_copy(scaleInterpolated, node->objNode->header.gfx.scale); ++ } ++ vec3f_copy(node->objNode->header.gfx.prevScale, node->objNode->header.gfx.scale); ++ node->objNode->header.gfx.prevScaleTimestamp = gGlobalTimer; ++ + mtxf_translate(mat, translation); + mtxf_copy(gMatStack[gMatStackIndex + 1], *gCurGraphNodeObject->throwMatrix); + gMatStack[gMatStackIndex + 1][3][0] = gMatStack[gMatStackIndex][3][0]; +@@ -891,6 +1242,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + mtxf_mul(gMatStack[gMatStackIndex + 1], mat, gMatStack[gMatStackIndex + 1]); + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], + node->objNode->header.gfx.scale); ++ mtxf_copy(gMatStackInterpolated[gMatStackIndex + 1], (void *) gCurGraphNodeObject->throwMatrixInterpolated); ++ gMatStackInterpolated[gMatStackIndex + 1][3][0] = gMatStackInterpolated[gMatStackIndex][3][0]; ++ gMatStackInterpolated[gMatStackIndex + 1][3][1] = gMatStackInterpolated[gMatStackIndex][3][1]; ++ gMatStackInterpolated[gMatStackIndex + 1][3][2] = gMatStackInterpolated[gMatStackIndex][3][2]; ++ mtxf_mul(gMatStackInterpolated[gMatStackIndex + 1], mat, gMatStackInterpolated[gMatStackIndex + 1]); ++ mtxf_scale_vec3f(gMatStackInterpolated[gMatStackIndex + 1], gMatStackInterpolated[gMatStackIndex + 1], ++ scaleInterpolated); + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex + 1]); +@@ -898,12 +1256,15 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; ++ mtxf_to_mtx(mtxInterpolated, gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = mtxInterpolated; + gGeoTempState.type = gCurAnimType; + gGeoTempState.enabled = gCurAnimEnabled; + gGeoTempState.frame = gCurrAnimFrame; + gGeoTempState.translationMultiplier = gCurAnimTranslationMultiplier; + gGeoTempState.attribute = gCurrAnimAttribute; + gGeoTempState.data = gCurAnimData; ++ gGeoTempState.prevFrame = gPrevAnimFrame; + gCurAnimType = 0; + gCurGraphNodeHeldObject = (void *) node; + if (node->objNode->header.gfx.unk38.curAnim != NULL) { +@@ -918,6 +1279,7 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { + gCurAnimTranslationMultiplier = gGeoTempState.translationMultiplier; + gCurrAnimAttribute = gGeoTempState.attribute; + gCurAnimData = gGeoTempState.data; ++ gPrevAnimFrame = gGeoTempState.prevFrame; + gMatStackIndex--; + } + +@@ -1039,6 +1401,7 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) + if (node->node.flags & GRAPH_RENDER_ACTIVE) { + Mtx *initialMatrix; + Vp *viewport = alloc_display_list(sizeof(*viewport)); ++ Vp *viewportInterpolated = viewport; + + gDisplayListHeap = alloc_only_pool_init(main_pool_available() - sizeof(struct AllocOnlyPool), + MEMORY_POOL_LEFT); +@@ -1049,7 +1412,12 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) + vec3s_set(viewport->vp.vscale, node->width * 4, node->height * 4, 511); + if (b != NULL) { + clear_frame_buffer(clearColor); +- make_viewport_clip_rect(b); ++ viewportInterpolated = alloc_display_list(sizeof(*viewportInterpolated)); ++ interpolate_vectors_s16(viewportInterpolated->vp.vtrans, sPrevViewport.vp.vtrans, b->vp.vtrans); ++ interpolate_vectors_s16(viewportInterpolated->vp.vscale, sPrevViewport.vp.vscale, b->vp.vscale); ++ ++ sViewportPos = gDisplayListHead; ++ make_viewport_clip_rect(viewportInterpolated); + *viewport = *b; + } + +@@ -1057,11 +1425,16 @@ void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) + clear_frame_buffer(clearColor); + make_viewport_clip_rect(c); + } ++ sPrevViewport = *viewport; + + mtxf_identity(gMatStack[gMatStackIndex]); + mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = initialMatrix; +- gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewport)); ++ ++ mtxf_identity(gMatStackInterpolated[gMatStackIndex]); ++ gMatStackInterpolatedFixed[gMatStackIndex] = initialMatrix; ++ ++ gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewportInterpolated)); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gCurGraphNodeRoot = node; +diff --git a/src/game/screen_transition.c b/src/game/screen_transition.c +index b49ddaf..d6656af 100644 +--- a/src/game/screen_transition.c ++++ b/src/game/screen_transition.c +@@ -16,6 +16,19 @@ + u8 sTransitionColorFadeCount[4] = { 0 }; + u16 sTransitionTextureFadeCount[2] = { 0 }; + ++static Gfx *sScreenTransitionVerticesPos[2]; ++static Vtx *sScreenTransitionVertices; ++ ++void patch_screen_transition_interpolated(void) { ++ if (sScreenTransitionVerticesPos[0] != NULL) { ++ gSPVertex(sScreenTransitionVerticesPos[0], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 8, 0); ++ gSPVertex(sScreenTransitionVerticesPos[1], VIRTUAL_TO_PHYSICAL(sScreenTransitionVertices), 4, 0); ++ sScreenTransitionVerticesPos[0] = NULL; ++ sScreenTransitionVerticesPos[1] = NULL; ++ sScreenTransitionVertices = NULL; ++ } ++} ++ + s32 set_and_reset_transition_fade_timer(s8 fadeTimer, u8 transTime) { + s32 reset = FALSE; + +@@ -85,14 +98,29 @@ s32 render_fade_transition_into_color(s8 fadeTimer, u8 transTime, struct WarpTra + return dl_transition_color(fadeTimer, transTime, transData, alpha); + } + ++#if 0 ++ + s16 calc_tex_transition_radius(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { + f32 texRadius = transData->endTexRadius - transData->startTexRadius; + f32 radiusTime = sTransitionColorFadeCount[fadeTimer] * texRadius / (f32)(transTime - 1); + f32 result = transData->startTexRadius + radiusTime; + +- return (s16)(result + 0.5);; ++ return (s16)(result + 0.5); + } + ++#else ++ ++s16 calc_tex_transition_radius(s8 fadeTimer, f32 interpolationFraction, s8 transTime, struct WarpTransitionData *transData) { ++ f32 texRadius = transData->endTexRadius - transData->startTexRadius; ++ f32 radiusTime = (sTransitionColorFadeCount[fadeTimer] == 0 ? 0 : ++ sTransitionColorFadeCount[fadeTimer] - 1 + interpolationFraction) * texRadius / (f32)(transTime - 1); ++ f32 result = transData->startTexRadius + radiusTime; ++ ++ return (s16)(result + 0.5); ++} ++ ++#endif ++ + f32 calc_tex_transition_time(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData) { + f32 startX = transData->startTexX; + f32 startY = transData->startTexY; +@@ -166,6 +194,8 @@ void *sTextureTransitionID[] = { + texture_transition_bowser_half, + }; + ++#if 0 ++ + s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { + f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); + u16 texTransPos = convert_tex_transition_angle_to_pos(transData); +@@ -206,6 +236,56 @@ s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransition + return set_and_reset_transition_fade_timer(fadeTimer, transTime); + } + ++#else ++ ++s32 render_textured_transition(s8 fadeTimer, s8 transTime, struct WarpTransitionData *transData, s8 texID, s8 transTexType) { ++ f32 texTransTime = calc_tex_transition_time(fadeTimer, transTime, transData); ++ u16 texTransPos = convert_tex_transition_angle_to_pos(transData); ++ s16 centerTransX = center_tex_transition_x(transData, texTransTime, texTransPos); ++ s16 centerTransY = center_tex_transition_y(transData, texTransTime, texTransPos); ++ s16 texTransRadius = calc_tex_transition_radius(fadeTimer, 1.0f, transTime, transData); ++ s16 texTransRadiusInterpolated = calc_tex_transition_radius(fadeTimer, 0.5f, transTime, transData); ++ Vtx *verts = alloc_display_list(8 * sizeof(*verts)); ++ Vtx *vertsInterpolated = alloc_display_list(8 * sizeof(*vertsInterpolated)); ++ ++ if (verts != NULL && vertsInterpolated != NULL) { ++ load_tex_transition_vertex(verts, fadeTimer, transData, centerTransX, centerTransY, texTransRadius, transTexType); ++ load_tex_transition_vertex(vertsInterpolated, fadeTimer, transData, centerTransX, centerTransY, texTransRadiusInterpolated, transTexType); ++ sScreenTransitionVertices = verts; ++ gSPDisplayList(gDisplayListHead++, dl_proj_mtx_fullscreen) ++ gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); ++ gDPSetRenderMode(gDisplayListHead++, G_RM_AA_OPA_SURF, G_RM_AA_OPA_SURF2); ++ sScreenTransitionVerticesPos[0] = gDisplayListHead; ++ gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 8, 0); ++ gSPDisplayList(gDisplayListHead++, dl_transition_draw_filled_region); ++ gDPPipeSync(gDisplayListHead++); ++ gDPSetCombineMode(gDisplayListHead++, G_CC_MODULATEIDECALA, G_CC_MODULATEIDECALA); ++ gDPSetRenderMode(gDisplayListHead++, G_RM_AA_XLU_SURF, G_RM_AA_XLU_SURF2); ++ gDPSetTextureFilter(gDisplayListHead++, G_TF_BILERP); ++ switch (transTexType) { ++ case TRANS_TYPE_MIRROR: ++ gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 32, 64, 0, ++ G_TX_WRAP | G_TX_MIRROR, G_TX_WRAP | G_TX_MIRROR, 5, 6, G_TX_NOLOD, G_TX_NOLOD); ++ break; ++ case TRANS_TYPE_CLAMP: ++ gDPLoadTextureBlock(gDisplayListHead++, sTextureTransitionID[texID], G_IM_FMT_IA, G_IM_SIZ_8b, 64, 64, 0, ++ G_TX_CLAMP, G_TX_CLAMP, 6, 6, G_TX_NOLOD, G_TX_NOLOD); ++ break; ++ } ++ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); ++ sScreenTransitionVerticesPos[1] = gDisplayListHead; ++ gSPVertex(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(vertsInterpolated), 4, 0); ++ gSPDisplayList(gDisplayListHead++, dl_draw_quad_verts_0123); ++ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); ++ gSPDisplayList(gDisplayListHead++, dl_screen_transition_end); ++ sTransitionTextureFadeCount[fadeTimer] += transData->texTimer; ++ } else { ++ } ++ return set_and_reset_transition_fade_timer(fadeTimer, transTime); ++} ++ ++#endif ++ + int render_screen_transition(s8 fadeTimer, s8 transType, u8 transTime, struct WarpTransitionData *transData) { + switch (transType) { + case WARP_TRANSITION_FADE_FROM_COLOR: +diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c +index 37c6752..d823d40 100644 +--- a/src/menu/intro_geo.c ++++ b/src/menu/intro_geo.c +@@ -1,5 +1,6 @@ + #include + ++#include "engine/math_util.h" + #include "game/memory.h" + #include "game/segment2.h" + #include "game/segment7.h" +@@ -70,6 +71,18 @@ s8 gameOverBackgroundTable[] = { + s8 gameOverBackgroundFlipOrder[] = { 0x00, 0x01, 0x02, 0x03, 0x07, 0x0B, + 0x0a, 0x09, 0x08, 0x04, 0x05, 0x06 }; + ++static Gfx *sIntroScalePos; ++static Vec3f sIntroScale; ++ ++void patch_title_screen_scales(void) { ++ if (sIntroScalePos != NULL) { ++ Mtx *scaleMat = alloc_display_list(sizeof(*scaleMat)); ++ guScale(scaleMat, sIntroScale[0], sIntroScale[1], sIntroScale[2]); ++ gSPMatrix(sIntroScalePos, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); ++ sIntroScalePos = NULL; ++ } ++} ++ + Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { + struct GraphNode *graphNode; // sp4c + Gfx *displayList; // sp48 +@@ -80,6 +93,8 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { + f32 scaleX; // sp34 + f32 scaleY; // sp30 + f32 scaleZ; // sp2c ++ Vec3f scale; ++ Vec3f scaleInterpolated; + graphNode = sp54; + displayList = NULL; + displayListIter = NULL; +@@ -110,7 +125,11 @@ Gfx *geo_title_screen(s32 sp50, struct GraphNode *sp54, UNUSED void *context) { + scaleY = 0.0f; + scaleZ = 0.0f; + } +- guScale(scaleMat, scaleX, scaleY, scaleZ); ++ vec3f_set(scale, scaleX, scaleY, scaleZ); ++ interpolate_vectors(scaleInterpolated, sIntroScale, scale); ++ vec3f_set(sIntroScale, scaleX, scaleY, scaleZ); ++ guScale(scaleMat, scaleInterpolated[0], scaleInterpolated[1], scaleInterpolated[2]); ++ sIntroScalePos = displayListIter; + gSPMatrix(displayListIter++, scaleMat, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH); + gSPDisplayList(displayListIter++, &intro_seg7_dl_0700B3A0); + gSPPopMatrix(displayListIter++, G_MTX_MODELVIEW); +diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp +index 0467495..fa4eb33 100644 +--- a/src/pc/gfx/gfx_dxgi.cpp ++++ b/src/pc/gfx/gfx_dxgi.cpp +@@ -36,10 +36,10 @@ + + #ifdef VERSION_EU + #define FRAME_INTERVAL_US_NUMERATOR 40000 +-#define FRAME_INTERVAL_US_DENOMINATOR 1 ++#define FRAME_INTERVAL_US_DENOMINATOR 2 + #else + #define FRAME_INTERVAL_US_NUMERATOR 100000 +-#define FRAME_INTERVAL_US_DENOMINATOR 3 ++#define FRAME_INTERVAL_US_DENOMINATOR 6 + #endif + + using namespace Microsoft::WRL; // For ComPtr +diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c +index a39b76d..3fb1bb1 100644 +--- a/src/pc/gfx/gfx_sdl2.c ++++ b/src/pc/gfx/gfx_sdl2.c +@@ -141,7 +141,7 @@ static inline void gfx_sdl_set_vsync(int mode) { + if (mode > 1) { + // try to detect refresh rate + SDL_GL_SetSwapInterval(1); +- const int vblanks = test_vsync(); ++ const int vblanks = test_vsync() / 2; + if (vblanks) { + printf("determined swap interval: %d\n", vblanks); + SDL_GL_SetSwapInterval(vblanks); +@@ -225,7 +225,7 @@ static void gfx_sdl_init(const char *window_title) { + gfx_sdl_set_fullscreen(); + + qpc_freq = SDL_GetPerformanceFrequency(); +- frame_time = qpc_freq / FRAMERATE; ++ frame_time = qpc_freq / (2 * FRAMERATE); + + for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { + inverted_scancode_table[windows_scancode_table[i]] = i; +diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c +index ed6ee74..63679ad 100644 +--- a/src/pc/pc_main.c ++++ b/src/pc/pc_main.c +@@ -83,6 +83,25 @@ void send_display_list(struct SPTask *spTask) { + #define SAMPLES_LOW 528 + #endif + ++static inline void patch_interpolations(void) { ++ extern void mtx_patch_interpolated(void); ++ extern void patch_screen_transition_interpolated(void); ++ extern void patch_title_screen_scales(void); ++ extern void patch_interpolated_dialog(void); ++ extern void patch_interpolated_hud(void); ++ extern void patch_interpolated_paintings(void); ++ extern void patch_interpolated_bubble_particles(void); ++ extern void patch_interpolated_snow_particles(void); ++ mtx_patch_interpolated(); ++ patch_screen_transition_interpolated(); ++ patch_title_screen_scales(); ++ patch_interpolated_dialog(); ++ patch_interpolated_hud(); ++ patch_interpolated_paintings(); ++ patch_interpolated_bubble_particles(); ++ patch_interpolated_snow_particles(); ++} ++ + void produce_one_frame(void) { + gfx_start_frame(); + +@@ -110,6 +129,11 @@ void produce_one_frame(void) { + audio_api->play((u8 *)audio_buffer, 2 * num_audio_samples * 4); + + gfx_end_frame(); ++ ++ gfx_start_frame(); ++ patch_interpolations(); ++ send_display_list(gGfxSPTask); ++ gfx_end_frame(); + } + + void audio_shutdown(void) { diff --git a/enhancements/L-trigger-mapping/README.md b/enhancements/L-trigger-mapping/README.md deleted file mode 100644 index 0538f094..00000000 --- a/enhancements/L-trigger-mapping/README.md +++ /dev/null @@ -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. diff --git a/enhancements/README.md b/enhancements/README.md index 1bc26dff..4d1e13ce 100644 --- a/enhancements/README.md +++ b/enhancements/README.md @@ -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. diff --git a/enhancements/RecordDemo.js b/enhancements/RecordDemo.js deleted file mode 100644 index 9442b804..00000000 --- a/enhancements/RecordDemo.js +++ /dev/null @@ -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 -} diff --git a/enhancements/crash.patch b/enhancements/crash.patch deleted file mode 100644 index f0eb61a9..00000000 --- a/enhancements/crash.patch +++ /dev/null @@ -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 -+ -+#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 -+ -+#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_ */ diff --git a/enhancements/crash_font.bin b/enhancements/crash_font.bin deleted file mode 100644 index 15777388..00000000 Binary files a/enhancements/crash_font.bin and /dev/null differ diff --git a/enhancements/debug_box.patch b/enhancements/debug_box.patch deleted file mode 100644 index e02bbf43..00000000 --- a/enhancements/debug_box.patch +++ /dev/null @@ -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 -+ -+#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 */ diff --git a/enhancements/ique_support.patch b/enhancements/ique_support.patch deleted file mode 100644 index f52b9b16..00000000 --- a/enhancements/ique_support.patch +++ /dev/null @@ -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 -+ -+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 - - // 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 - - 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 - - #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 -+#include - - #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 - diff --git a/enhancements/mem_error_screen.patch b/enhancements/mem_error_screen.patch deleted file mode 100644 index 87d995d6..00000000 --- a/enhancements/mem_error_screen.patch +++ /dev/null @@ -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 -+#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 diff --git a/enhancements/record_demo.patch b/enhancements/record_demo.patch deleted file mode 100644 index 5d2c25de..00000000 --- a/enhancements/record_demo.patch +++ /dev/null @@ -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(); -