15 Commits

Author SHA1 Message Date
Snesrev
b7be4d672b Show a message box on errors in release mode on Windows. 2022-10-11 21:52:26 +02:00
Snesrev
393e572945 Improve render perf by not locking the whole texture 2022-10-11 17:34:53 +02:00
Snesrev
95659d2f40 Disabling EnhancedMode7 led to broken map rendering (Fixes #141) 2022-10-11 02:29:21 +02:00
Snesrev
3f52f05e03 Submodule got overwritten if using staircase when trapdoors opening (#126)
Bug report:
If you use a staircase on the same frame that a door opens,
enemy spawns shift into different rooms and doors start
acting funny until you leave the dungeon or use the mirror.
2022-10-09 03:36:55 +02:00
Snesrev
73a7155f7f Fix messed up module index when using bottles in a transition (#126)
This was caused by the door transition code assuming that the module
and submodule indexes were 7,0. But when using a bottle this got changed
to module 14, which was then incorrectly modified.
2022-10-09 03:01:34 +02:00
Snesrev
5baed18b31 Fix VerifySnapshotsEq issue with hdma_table 2022-10-09 02:33:31 +02:00
Snesrev
b67db8a713 Fix Heart dupe vanilla bug (#126) 2022-10-08 04:37:41 +02:00
Snesrev
c28692b32d Fix some clipping bugs that moved Link to impossible places (#126)
- Citrus Clip outside of Link's house that moved him on top of the hill
 - Dash Buffering glitch on death mountain

Bug report:
If you charge your sword and tap left or right+dash
while standing next to certain solid railings or slopes,
you will clip into them by 1 pixel. This can be repeated
until you are so far into the railing that you will jump
over and trigger a variety of issues.
2022-10-08 04:12:12 +02:00
Snesrev
dd8556eebe Rename a function 2022-10-08 03:36:13 +02:00
Snesrev
39c5060cbe Fix so super bomb won't return to the player on screen change (#126)
Bug report:
A ticking super bomb will cancel and teleport back to you
as a follower if you do any of these at count 0:
(1) change screen via walking, mirror, or bird travel
(2) fill all ancillary slots
(3) die with a bottled faerie.
2022-10-08 00:51:09 +02:00
Snesrev
3a080d6bb3 Don't levitate across chasms with turbo (#126)
Bug report:
If you use a turbo controller to perfectly spam the
dash button, the check for Link being in a hole is
endlessly skipped and you can levitate across chasms.
2022-10-08 00:18:10 +02:00
Snesrev
e272415f12 Clear Mosaic variable when loading a save (#126)
Bug report:
If you save/quit in the middle of a mosaic effect, such
as being electrocuted by a buzz blob, the resumed game
will skip the location prompt and start in the sanctuary.
2022-10-07 23:31:39 +02:00
Snesrev
b23a298b54 Avoid cape magic underflow if an anti-fairy consumes magic. (#126) 2022-10-07 18:17:53 +02:00
Snesrev
12bb1f82bc Using the Cane of Somaria might refund magic. (#126)
Changed so it's more explicit when magic is refunded.

Bug report:
If you use the Cane of Somaria while two bombs and the
boomerang are active, magic will be refunded instead of used.
2022-10-07 18:06:12 +02:00
Snesrev
9b608eb53a Cane of Somaria didn't reset a variable when out of magic (#126)
Bug report:
If you use the Cane of Somaria with an empty magic meter,
then quickly switch to the mushroom or magic powder after
the "no magic" prompt, you will automatically sprinkle
magic powder despite pressing no button and having no magic.
2022-10-07 17:53:23 +02:00
14 changed files with 207 additions and 95 deletions

View File

@@ -4753,12 +4753,10 @@ endif_5:
}
}
void AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
int AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
int k = AncillaAdd_AddAncilla_Bank08(type, y);
if (k < 0) {
Refund_Magic(4);
return;
}
if (k < 0)
return k;
for (int j = 4; j >= 0; j--) {
if (j == k || ancilla_type[j] != 0x2c)
continue;
@@ -4771,7 +4769,7 @@ void AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
bitmask_of_dragstate = 0;
link_speed_setting = 0;
}
return;
return k;
}
Ancilla_Sfx3_Near(0x2a);
@@ -4804,6 +4802,7 @@ void AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
Ancilla_SetY(k, link_y_coord + kCaneOfSomaria_Y[j]);
SomariaBlock_CheckForTransitTile(k);
}
return k;
}
void SomariaBlock_CheckForTransitTile(int k) { // 88e191
@@ -5378,7 +5377,10 @@ void Ancilla3A_BigBombExplosion(int k) { // 88f18d
}
if (ancilla_item_to_link[k] == 3 && ancilla_arr3[k] == 1) {
Bomb_CheckForDestructibles(Ancilla_GetX(k), Ancilla_GetY(k), 0); // r14?
follower_indicator = 0;
// Changed so this is reset elsewhere
if (!(enhanced_features0 & kFeatures0_MiscBugFixes))
follower_indicator = 0;
}
}
@@ -6087,7 +6089,7 @@ void AncillaAdd_SomariaPlatformPoof(int k) { // 898dd2
Player_TileDetectNearby();
}
void AncillaAdd_SuperBombExplosion(uint8 a, uint8 y) { // 898df9
int AncillaAdd_SuperBombExplosion(uint8 a, uint8 y) { // 898df9
int k = Ancilla_AddAncilla(a, y);
if (k >= 0) {
ancilla_R[k] = 0;
@@ -6101,6 +6103,7 @@ void AncillaAdd_SuperBombExplosion(uint8 a, uint8 y) { // 898df9
int x = tagalong_x_lo[j] | tagalong_x_hi[j] << 8;
Ancilla_SetXY(k, x + 8, y + 16);
}
return k;
}
void ConfigureRevivalAncillae() { // 898e4e

View File

@@ -173,7 +173,7 @@ void Ancilla31_ByrnaSpark(int k);
void Ancilla_SwordBeam(int k);
void Ancilla0D_SpinAttackFullChargeSpark(int k);
void Ancilla27_Duck(int k);
void AncillaAdd_SomariaBlock(uint8 type, uint8 y);
int AncillaAdd_SomariaBlock(uint8 type, uint8 y);
void SomariaBlock_CheckForTransitTile(int k);
int Ancilla_CheckBasicSpriteCollision(int k);
bool Ancilla_CheckBasicSpriteCollision_Single(int k, int j);
@@ -219,7 +219,7 @@ void AncillaAdd_ChargedSpinAttackSparkle();
void AncillaAdd_ExplodingWeatherVane(uint8 a, uint8 y);
void AncillaAdd_CutsceneDuck(uint8 a, uint8 y);
void AncillaAdd_SomariaPlatformPoof(int k);
void AncillaAdd_SuperBombExplosion(uint8 a, uint8 y);
int AncillaAdd_SuperBombExplosion(uint8 a, uint8 y);
void ConfigureRevivalAncillae();
void AncillaAdd_LampFlame(uint8 a, uint8 y);
void AncillaAdd_MSCutscene(uint8 a, uint8 y);

View File

@@ -2052,13 +2052,14 @@ void RoomBounds_SubA(RoomBounds *r) {
}
void Dungeon_StartInterRoomTrans_Left() {
assert(submodule_index == 0);
link_quadrant_x ^= 1;
Dungeon_AdjustQuadrant();
RoomBounds_SubA(&room_bounds_x);
Dung_SaveDataForCurrentRoom();
DungeonTransition_AdjustCamera_X(link_quadrant_x ^ 1);
HandleEdgeTransition_AdjustCameraBoundaries(3);
submodule_index++;
submodule_index = 1;
if (link_quadrant_x) {
RoomBounds_SubB(&room_bounds_x);
BYTE(dungeon_room_index_prev) = dungeon_room_index;
@@ -2072,7 +2073,7 @@ void Dungeon_StartInterRoomTrans_Left() {
}
dungeon_room_index--;
}
submodule_index += 1;
submodule_index = 2;
if (room_transitioning_flags & 1) {
link_is_on_lower_level ^= 1;
link_is_on_lower_level_mirror = link_is_on_lower_level;
@@ -2091,13 +2092,14 @@ void Dung_StartInterRoomTrans_Left_Plus() {
}
void Dungeon_StartInterRoomTrans_Up() {
assert(submodule_index == 0);
link_quadrant_y ^= 2;
Dungeon_AdjustQuadrant();
RoomBounds_SubA(&room_bounds_y);
Dung_SaveDataForCurrentRoom();
DungeonTransition_AdjustCamera_Y(link_quadrant_y ^ 2);
HandleEdgeTransition_AdjustCameraBoundaries(1);
submodule_index++;
submodule_index = 1;
if (link_quadrant_y) {
RoomBounds_SubB(&room_bounds_y);
BYTE(dungeon_room_index_prev) = dungeon_room_index;
@@ -2119,7 +2121,7 @@ void Dungeon_StartInterRoomTrans_Up() {
Dungeon_AdjustAfterSpiralStairs();
}
BYTE(dungeon_room_index) -= 0x10;
submodule_index += 1;
submodule_index = 2;
if (room_transitioning_flags & 1) {
link_is_on_lower_level ^= 1;
link_is_on_lower_level_mirror = link_is_on_lower_level;
@@ -2133,13 +2135,14 @@ void Dungeon_StartInterRoomTrans_Up() {
}
void Dungeon_StartInterRoomTrans_Down() {
assert(submodule_index == 0);
link_quadrant_y ^= 2;
Dungeon_AdjustQuadrant();
RoomBounds_AddA(&room_bounds_y);
Dung_SaveDataForCurrentRoom();
DungeonTransition_AdjustCamera_Y(link_quadrant_y);
HandleEdgeTransition_AdjustCameraBoundaries(0);
submodule_index++;
submodule_index = 1;
if (!link_quadrant_y) {
RoomBounds_AddB(&room_bounds_y);
BYTE(dungeon_room_index_prev) = dungeon_room_index;
@@ -2153,7 +2156,7 @@ void Dungeon_StartInterRoomTrans_Down() {
Dungeon_AdjustAfterSpiralStairs();
}
BYTE(dungeon_room_index) += 16;
submodule_index += 1;
submodule_index = 2;
if (room_transitioning_flags & 1) {
link_is_on_lower_level ^= 1;
link_is_on_lower_level_mirror = link_is_on_lower_level;
@@ -4281,6 +4284,14 @@ void Dungeon_FlipCrystalPegAttribute() { // 81c22a
void Dungeon_HandleRoomTags() { // 81c2fd
if (!flag_skip_call_tag_routines) {
Dungeon_DetectStaircase();
// Dungeon_DetectStaircase might change the submodule, so avoid
// calling the tag routines cause they could also change the submodule,
// causing items to spawn in incorrect locations cause link_x/y_coord gets
// out of sync if you enter a staircase exactly when a room tag triggers.
if (enhanced_features0 & kFeatures0_MiscBugFixes && submodule_index != 0)
return;
g_ram[14] = 0;
kDungTagroutines[dung_hdr_tag[0]](0);
g_ram[14] = 1;
@@ -7958,13 +7969,14 @@ void HandleEdgeTransitionMovementEast_RightBy8() { // 82b62e
}
void Dungeon_StartInterRoomTrans_Right() { // 82b63a
assert(submodule_index == 0);
link_quadrant_x ^= 1;
Dungeon_AdjustQuadrant();
RoomBounds_AddA(&room_bounds_x);
Dung_SaveDataForCurrentRoom();
DungeonTransition_AdjustCamera_X(link_quadrant_x);
HandleEdgeTransition_AdjustCameraBoundaries(2);
submodule_index++;
submodule_index = 1;
if (!link_quadrant_x) {
RoomBounds_AddB(&room_bounds_x);
BYTE(dungeon_room_index_prev) = dungeon_room_index;
@@ -7978,7 +7990,7 @@ void Dungeon_StartInterRoomTrans_Right() { // 82b63a
}
dungeon_room_index += 1;
}
submodule_index += 1;
submodule_index = 2;
if (room_transitioning_flags & 1) {
link_is_on_lower_level ^= 1;
link_is_on_lower_level_mirror = link_is_on_lower_level;
@@ -8591,7 +8603,7 @@ void HandleLinkOnSpiralStairs() { // 87f2c1
link_actual_vel_x = 2;
}
}
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
Link_HandleMovingAnimation_StartWithDash();
if (!link_timer_push_get_tired && sign8(--countdown_timer_for_staircases)) {
countdown_timer_for_staircases = 0;
@@ -8630,7 +8642,7 @@ void SpiralStairs_FindLandingSpot() { // 87f391
link_actual_vel_x = -4, link_actual_vel_y = 2;
if (some_animation_timer_steps == 2)
link_actual_vel_x = 0, link_actual_vel_y = 16;
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
Link_HandleMovingAnimation_StartWithDash();
if ((uint8)link_x_coord == (uint8)tiledetect_which_y_pos[1])
some_animation_timer_steps = 2;

38
main.c
View File

@@ -65,6 +65,9 @@ static int g_snes_width, g_snes_height;
static int g_sdl_audio_mixer_volume = SDL_MIX_MAXVOLUME;
void NORETURN Die(const char *error) {
#if defined(NDEBUG) && defined(_WIN32)
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, kWindowTitle, error, NULL);
#endif
fprintf(stderr, "Error: %s\n", error);
exit(1);
}
@@ -123,13 +126,12 @@ static SDL_HitTestResult HitTestCallback(SDL_Window *win, const SDL_Point *area,
(SDL_GetModState() & KMOD_CTRL) != 0 ? SDL_HITTEST_DRAGGABLE : SDL_HITTEST_NORMAL;
}
static bool RenderScreenWithPerf(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
bool rv;
static void RenderScreenWithPerf(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
if (g_display_perf || g_config.display_perf_title) {
static float history[64], average;
static int history_pos;
uint64 before = SDL_GetPerformanceCounter();
rv = ZeldaDrawPpuFrame(pixel_buffer, pitch, render_flags);
ZeldaDrawPpuFrame(pixel_buffer, pitch, render_flags);
uint64 after = SDL_GetPerformanceCounter();
float v = (double)SDL_GetPerformanceFrequency() / (after - before);
average += v - history[history_pos];
@@ -137,9 +139,8 @@ static bool RenderScreenWithPerf(uint8 *pixel_buffer, size_t pitch, uint32 rende
history_pos = (history_pos + 1) & 63;
g_curr_fps = average * (1.0f / 64);
} else {
rv = ZeldaDrawPpuFrame(pixel_buffer, pitch, render_flags);
ZeldaDrawPpuFrame(pixel_buffer, pitch, render_flags);
}
return rv;
}
// Go some steps up and find zelda3.ini
@@ -481,30 +482,27 @@ static void RenderNumber(uint8 *dst, size_t pitch, int n, bool big) {
}
static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture *texture, bool fullscreen) {
uint8* pixels = NULL;
uint8 *pixels = 0;
int pitch = 0;
int render_scale = PpuGetCurrentRenderScale(g_zenv.ppu, g_ppu_render_flags);
SDL_Rect src_rect = { 0, 0, g_snes_width * render_scale >> 1, g_snes_height * render_scale >> 1};
uint64 t0 = SDL_GetPerformanceCounter();
if(SDL_LockTexture(texture, NULL, (void**)&pixels, &pitch) != 0) {
if(SDL_LockTexture(texture, &src_rect, (void**)&pixels, &pitch) != 0) {
printf("Failed to lock texture: %s\n", SDL_GetError());
return;
}
uint64 t1 = SDL_GetPerformanceCounter();
bool hq = RenderScreenWithPerf(pixels, pitch, g_ppu_render_flags);
RenderScreenWithPerf(pixels, pitch, g_ppu_render_flags);
if (g_display_perf) {
RenderNumber(pixels + (pitch * 2 << hq), pitch, g_curr_fps, hq);
}
if (g_config.display_perf_title) {
char title[60];
snprintf(title, sizeof(title), "%s | FPS: %d", kWindowTitle, g_curr_fps);
SDL_SetWindowTitle(window, title);
RenderNumber(pixels + pitch * render_scale, pitch, g_curr_fps, render_scale == 4);
}
uint64 t2 = SDL_GetPerformanceCounter();
SDL_UnlockTexture(texture);
uint64 t3 = SDL_GetPerformanceCounter();
SDL_RenderClear(renderer);
uint64 t4 = SDL_GetPerformanceCounter();
SDL_Rect src_rect = { 0, 0, g_snes_width, g_snes_height };
SDL_RenderCopy(renderer, texture, hq ? NULL : &src_rect, NULL);
SDL_RenderCopy(renderer, texture, &src_rect, NULL);
uint64 t5 = SDL_GetPerformanceCounter();
double f = 1e3 / (double)SDL_GetPerformanceFrequency();
@@ -516,7 +514,11 @@ static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture
(t5 - t4) * f
);
if (g_config.display_perf_title) {
char title[60];
snprintf(title, sizeof(title), "%s | FPS: %d", kWindowTitle, g_curr_fps);
SDL_SetWindowTitle(window, title);
}
}
static void HandleCommand_Locked(uint32 j, bool pressed);
@@ -743,7 +745,7 @@ static void LoadAssets() {
uint8 *data = ReadFile("tables/zelda3_assets.dat", &length);
if (!data)
data = ReadFile("zelda3_assets.dat", &length);
if (!data) Die("Failed to read zelda3_assets.dat");
if (!data) Die("Failed to read zelda3_assets.dat. Please see the README for information about how you get this file.");
static const char kAssetsSig[] = { kAssets_Sig };

View File

@@ -2220,6 +2220,12 @@ void CopySaveToWRAM() { // 8ccfbb
word_7E021F = 0x7f;
word_7E0221 = 0xffff;
// If you save / quit in the middle of a mosaic effect, such as
// being electrocuted by a buzz blob, the resumed game will skip
// the location prompt and start in the sanctuary.
if (enhanced_features0 & kFeatures0_MiscBugFixes)
mosaic_level = 0;
hud_var1 = 128;
main_module_index = 5;
submodule_index = 0;

132
player.c
View File

@@ -242,14 +242,20 @@ void PlayerHandler_00_Ground_3() { // 8781a0
Link_HandleYItem();
if (sram_progress_indicator != 0) {
Link_HandleSwordCooldown();
if (link_player_handler_state == 3) {
link_x_vel = link_y_vel = 0;
goto getout_dostuff;
}
}
}
}
// Ensure we're not handling potions. Things further
// down don't assume this and change the module indexes randomly.
// Also check for spin attack for some reason.
if ((enhanced_features0 & kFeatures0_MiscBugFixes) && main_module_index == 14 && submodule_index != 2 ||
link_player_handler_state == 3) {
link_x_vel = link_y_vel = 0;
goto getout_dostuff;
}
Link_HandleCape_passive_LiftCheck();
if (link_incapacitated_timer) {
link_moving_against_diag_tile = 0;
@@ -737,7 +743,7 @@ lbl_jump_into_middle:
if ((link_direction & 0xc) == 0)
link_actual_vel_y = 0;
}
LinkHop_FindArbitraryLandingSpot(); // not
Link_MovePosition(); // not
timer_running:
if (link_player_handler_state != 6) {
Link_HandleCardinalCollision(); // not
@@ -791,7 +797,7 @@ void LinkHop_HoppingSouthOW() { // 87894e
link_actual_vel_z_copy = link_actual_vel_z_copy_mirror;
link_z_coord = link_z_coord_mirror;
link_actual_vel_z -= 2;
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
if (sign8(link_actual_vel_z)) {
if (link_actual_vel_z < 0xa0)
link_actual_vel_z = 0xa0;
@@ -827,7 +833,7 @@ void LinkState_HandlingJump() { // 878a05
link_actual_vel_z_copy = link_actual_vel_z_copy_mirror;
BYTE(link_z_coord) = link_z_coord_mirror;
link_actual_vel_z -= 2;
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
if (sign8(link_actual_vel_z)) {
if (link_actual_vel_z < 0xa0)
link_actual_vel_z = 0xa0;
@@ -1014,7 +1020,7 @@ finish:
void LinkState_HoppingDiagonallyUpOW() { // 878dc6
draw_water_ripples_or_grass = 0;
Player_ChangeZ(2);
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
if (sign8(link_z_coord)) {
Link_SplashUponLanding();
if (link_player_handler_state != kPlayerState_Swimming && !link_is_in_deep_water)
@@ -1041,9 +1047,15 @@ void LinkState_HoppingDiagonallyDownOW() { // 878e15
LinkHop_FindLandingSpotDiagonallyDown();
link_x_coord = old_x;
static const uint8 kLedgeVelX[] = { 4, 4, 4, 10, 10, 10, 11, 18, 18, 18, 20, 20, 20, 20, 22, 22, 26, 26, 26, 26, 28, 28, 28, 28 };
static const uint8 kLedgeVelX[] = {
4, 4, 4, 10, 10, 10, 11, 18,
18, 18, 20, 20, 20, 20, 22, 22,
26, 26, 26, 26, 28, 28, 28, 28
};
int8 velx = kLedgeVelX[(uint16)(link_y_coord - link_y_coord_original) >> 3];
int t = (uint16)(link_y_coord - link_y_coord_original);
// Fix out of bounds read
int8 velx = kLedgeVelX[IntMin(t >> 3, 23)];
link_actual_vel_x = (dir != 2) ? velx : -velx;
if (!player_is_indoors)
link_is_on_lower_level = 2;
@@ -1359,7 +1371,12 @@ void LinkState_Pits() { // 8792d3
} else {
if (!link_is_running)
goto aux_state;
if (link_countdown_for_dash) {
// If you use a turbo controller to perfectly spam the dash button,
// the check for Link being in a hole is endlessly skipped and you
// can levitate across chasms.
// Fix this by ensuring that the dash button is held down before proceeding to the dash state.
if (link_countdown_for_dash &&
(!(enhanced_features0 & kFeatures0_MiscBugFixes) || (joypad1L_last & 0x80))) {
LinkState_Dashing();
return;
}
@@ -1444,6 +1461,8 @@ endif_1:
ApplyLinksMovementToCamera();
return;
}
// Initiate fall down
if (player_near_pit_state != 2) {
if (link_item_moon_pearl) {
link_need_for_poof_for_transform = 0;
@@ -2736,7 +2755,7 @@ void LinkState_UsingQuake() { // 87a6d6
BYTE(link_z_coord) = link_z_coord_mirror;
link_auxiliary_state = 2;
Player_ChangeZ(2);
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
link_actual_vel_z_mirror = link_actual_vel_z;
link_actual_vel_z_copy_mirror = link_actual_vel_z_copy;
BYTE(link_z_coord_mirror) = link_z_coord;
@@ -3144,7 +3163,7 @@ void LinkState_Hookshotting() { // 87ab7c
}
return;
loc_87AD49:
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
TileDetect_MainHandler(5);
if (player_is_indoors) {
uint8 x = tiledetect_vertical_ledge >> 4 | tiledetect_vertical_ledge | detection_of_ledge_tiles_horiz_uphoriz;
@@ -3197,7 +3216,9 @@ void LinkItem_Cape() { // 87adc1
link_direction &= ~0xf;
if (!--cape_decrement_counter) {
cape_decrement_counter = kCapeDepletionTimers[link_magic_consumption];
if (!--link_magic_power) {
// Avoid magic underflow if an anti-fairy consumes magic.
if (link_magic_power == 0 && (enhanced_features0 & kFeatures0_MiscBugFixes) ||
!--link_magic_power) {
Link_ForceUnequipCape();
return;
}
@@ -3260,15 +3281,30 @@ void LinkItem_CaneOfSomaria() { // 87aec0
if (player_on_somaria_platform || is_standing_in_doorway || !CheckYButtonPress())
return;
int i = 4;
bool did_charge_magic = false;
while (ancilla_type[i] != 0x2c) {
if (--i < 0) {
if (!LinkCheckMagicCost(4))
if (!LinkCheckMagicCost(4)) {
// If you use the Cane of Somaria with an empty magic meter,
// then quickly switch to the mushroom or magic powder after
// the "no magic" prompt, you will automatically sprinkle magic powder
// despite pressing no button and having no magic.
if (enhanced_features0 & kFeatures0_MiscBugFixes)
goto out;
return;
}
did_charge_magic = true;
break;
}
}
link_debug_value_2 = 1;
AncillaAdd_SomariaBlock(44, 1);
if (AncillaAdd_SomariaBlock(0x2c, 1) < 0) {
// If you use the Cane of Somaria while two bombs and the boomerang are active,
// magic will be refunded instead of used.
if (did_charge_magic || !(enhanced_features0 & kFeatures0_MiscBugFixes))
Refund_Magic(4);
}
link_delay_timer_spin_attack = kRodAnimDelays[0];
link_animation_steps = 0;
player_handler_timer = 0;
@@ -3289,8 +3325,9 @@ void LinkItem_CaneOfSomaria() { // 87aec0
player_handler_timer = 0;
link_delay_timer_spin_attack = 0;
link_debug_value_2 = 0;
button_mask_b_y &= ~0x40;
link_position_mode &= ~8;
out:
button_mask_b_y &= ~0x40;
}
void LinkItem_CaneOfByrna() { // 87af3e
@@ -3708,7 +3745,7 @@ reset_to_normal:
if (!(link_direction & 0xc))
link_actual_vel_y = 0;
out:
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
out2:
Link_HandleCardinalCollision();
HandleIndoorCameraAndDoors();
@@ -5099,11 +5136,17 @@ void StartMovementCollisionChecks_X_HandleOutdoors() { // 87c8e9
return;
} // endif_8
if ((R14 & 2) == 0 && (R12 & 5) != 0 && (!link_is_running || (link_direction_facing & 4))) {
FlagMovingIntoSlopes_X();
if ((link_moving_against_diag_tile & 0xf) != 0)
return;
} // endif_9
// If force facing down (hold B button), while turboing on the Run key, we'll never
// reach FlagMovingIntoSlopes_X causing a Dash Buffering glitch.
// Fix by always calling it, not sure why you wouldn't always want to call it.
if ((R14 & 2) == 0 && (R12 & 5) != 0) {
bool skip_check = link_is_running && !(link_direction_facing & 4);
if (!skip_check || (enhanced_features0 & kFeatures0_MiscBugFixes)) {
FlagMovingIntoSlopes_X();
if ((link_moving_against_diag_tile & 0xf) != 0)
return;
} // endif_9
}
link_moving_against_diag_tile = 0;
@@ -5214,8 +5257,9 @@ void Link_HandleDiagonalKickback() { // 87ccab
static const int8 x1[] = { 0, -1, -1, -1, -2, -2, -2, -3, -3, -3 };
link_x_coord += sign8(link_x_vel) ? x1[-(int8)link_x_vel] : x0[link_x_vel];
static const int8 y0[] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3 };
static const int8 y1[] = { 0, 1, 1, 2, 2, 2, 3, 3, 3, 3 };
static const int8 y0[10] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3 };
// Bug in zelda, might read index 15
static const int8 y1[16] = { 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 0xa5, 0x30, 0xf0, 0x04, 0xa5, 0x31 };
link_y_coord += sign8(link_y_vel) ? y1[-(int8)link_y_vel] : y0[link_y_vel];
} else {
noHorizOrNoVertical:
@@ -5511,12 +5555,25 @@ void FlagMovingIntoSlopes_Y() { // 87e076
if (tiledetect_diagonal_tile & 5) {
int8 ym = tiledetect_which_y_pos[0] & 7;
if (!(tiledetect_diag_state & 2)) {
ym = 8 - ym;
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
if (tiledetect_diag_state & 2) {
ym = -ym;
} else {
ym = kAvoidJudder1[o] - (8 - ym);
}
} else {
ym += 8;
// This code is bad because it could cause the player
// to move up to 15 pixels, causing an array out bounds read.
// Not sure how it works, but changed it to look more like the X version.
if (!(tiledetect_diag_state & 2)) {
ym = 8 - ym; // 0 to 8
} else {
ym += 8; // 8 to 15
}
// -15 to 7
ym = kAvoidJudder1[o] - ym;
}
ym = kAvoidJudder1[o] - ym;
if (link_y_vel == 0)
return;
if (sign8(link_y_vel))
@@ -5548,12 +5605,9 @@ void FlagMovingIntoSlopes_X() { // 87e112
int8 xm = link_x_coord & 7;
if (tiledetect_diag_state != 4 && tiledetect_diag_state != 6) {
o ^= 7;
xm = -xm;
} else {
xm -= 8;
xm = -xm;
xm = kAvoidJudder1[o] - xm;
xm = kAvoidJudder1[o] - (8 - xm);
} // endif_5
if (link_x_vel == 0)
return;
@@ -5677,10 +5731,10 @@ void Link_HandleVelocity() { // 87e245
link_actual_vel_z = 0xff;
link_z_coord = 0xffff;
link_subpixel_z = 0;
LinkHop_FindArbitraryLandingSpot();
Link_MovePosition();
}
void LinkHop_FindArbitraryLandingSpot() { // 87e370
void Link_MovePosition() { // 87e370
uint16 x = link_x_coord, y = link_y_coord;
link_y_coord_safe_return_lo = link_y_coord;
link_y_coord_safe_return_hi = link_y_coord >> 8;
@@ -6015,6 +6069,14 @@ void HandleDoorTransitions() { // 87e901
link_x_page_movement_delta = 0;
link_y_page_movement_delta = 0;
// Using a potion might have changed us into a different module, and the routines
// below just increment the submodule value, causing all kinds of havoc.
// There's an added return to catch the same behavior a bit up, but this one catches more cases,
// at the expense of link already having done his movement, so by returning here we might
// miss handling the door causing other kinds of issues.
if ((enhanced_features0 & kFeatures0_MiscBugFixes) && !(main_module_index == 7 && submodule_index == 0))
return;
if (link_direction_last & 0xC && is_standing_in_doorway == 1) {
if (link_direction_last & 4) {
if (((t = link_y_coord + 28) & 0xfc) == 0)

View File

@@ -207,7 +207,7 @@ void FlagMovingIntoSlopes_X();
void Link_HandleRecoiling();
void Player_HandleIncapacitated_Inner2();
void Link_HandleVelocity();
void LinkHop_FindArbitraryLandingSpot();
void Link_MovePosition();
void Link_HandleVelocityAndSandDrag(uint16 x, uint16 y);
void HandleSwimStrokeAndSubpixels();
void Player_SomethingWithVelocity_TiredOrSwim(uint16 xvel, uint16 yvel);

View File

@@ -122,10 +122,14 @@ void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx) {
func(ctx, tmp, 123);
}
bool PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags) {
int PpuGetCurrentRenderScale(Ppu *ppu, uint32_t render_flags) {
bool hq = ppu->mode == 7 && !ppu->forcedBlank &&
(ppu->renderFlags & (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer)) == (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer);
return hq ? 4 : 2;
}
void PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags) {
ppu->renderFlags = render_flags;
bool hq = ppu->mode == 7 && !ppu->forcedBlank &&
(ppu->renderFlags & (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer)) == (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer);
ppu->renderPitch = (uint)pitch;
ppu->renderBuffer = pixels;
@@ -140,14 +144,12 @@ bool PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_fl
memset(&ppu->brightnessMult[32], ppu->brightnessMult[31], 31);
}
if (hq) {
if (PpuGetCurrentRenderScale(ppu, ppu->renderFlags) == 4) {
for (int i = 0; i < 256; i++) {
uint32 color = ppu->cgram[i];
ppu->colorMapRgb[i] = ppu->brightnessMult[color & 0x1f] << 16 | ppu->brightnessMult[(color >> 5) & 0x1f] << 8 | ppu->brightnessMult[(color >> 10) & 0x1f];
}
}
return hq;
}
static inline void ClearBackdrop(PpuPixelPrioBufs *buf) {
@@ -833,7 +835,7 @@ static void PpuDrawBackgrounds(Ppu *ppu, int y, bool sub) {
PpuDrawBackground_2bpp(ppu, y, sub, 2, 0xf200, 0x1200);
} else {
// mode 7
PpuDrawBackground_mode7(ppu, y, sub, 0xc0);
PpuDrawBackground_mode7(ppu, y, sub, 0xc000);
if (ppu->lineHasSprites)
PpuDrawSprites(ppu, y, sub, false);
}

View File

@@ -135,7 +135,10 @@ void ppu_runLine(Ppu* ppu, int line);
uint8_t ppu_read(Ppu* ppu, uint8_t adr);
void ppu_write(Ppu* ppu, uint8_t adr, uint8_t val);
void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx);
bool PpuBeginDrawing(Ppu *ppu, uint8_t *buffer, size_t pitch, uint32_t render_flags);
void PpuBeginDrawing(Ppu *ppu, uint8_t *buffer, size_t pitch, uint32_t render_flags);
// Returns the current render scale, 1x = 256px, 2x=512px, 4x=1024px
int PpuGetCurrentRenderScale(Ppu *ppu, uint32_t render_flags);
void PpuSetMode7PerspectiveCorrection(Ppu *ppu, int low, int high);
void PpuSetExtraSideSpace(Ppu *ppu, int left, int right, int bottom);

View File

@@ -11804,6 +11804,12 @@ void Sprite_D8_Heart(int k) { // 86cec0
if (Sprite_ReturnIfInactive(k))
return;
Sprite_CheckAbsorptionByPlayer(k);
// Avoid calling Sprite_HandleAbsorptionByPlayer twice, it's called
// also from within Sprite_HandleDraggingByAncilla
if (sprite_state[k] == 0 && (enhanced_features0 & kFeatures0_MiscBugFixes))
return;
if (Sprite_HandleDraggingByAncilla(k))
return;
Sprite_MoveXY(k);

View File

@@ -426,8 +426,23 @@ void Follower_NotFollowing() { // 89a2b2
Tagalong_Draw();
} else {
if (follower_indicator == 13 && !player_is_indoors && !super_bomb_indicator_unk2) {
AncillaAdd_SuperBombExplosion(0x3a, 0);
follower_dropped = 0;
// Fixed so we wait a little bit if we can't spawn the ancilla
if (AncillaAdd_SuperBombExplosion(0x3a, 0) >= 0) {
follower_dropped = 0;
// A ticking super bomb will cancel and teleport back to you as a follower if you do
// any of these at count 0: (1) change screen via walking, mirror, or bird travel
// (2) fill all ancillary slots
// (3) die with a bottled faerie.
// Fixed this by clearing the follower indicator here, instead of in the ancilla
// bomb code.
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
follower_indicator = 0;
return;
}
} else {
super_bomb_indicator_unk1 = 1;
}
}
Follower_DoLayers();
}
@@ -650,8 +665,11 @@ skip_first_sprites:
if (pal == 7 && overworld_palette_swap_flag)
pal = 0;
if (follower_indicator == 13 && super_bomb_indicator_unk2 == 1)
pal = (frame_counter & 7);
if (follower_indicator == 13) {
// Display colorful superbomb palette also on frame 0.
if (enhanced_features0 & kFeatures0_MiscBugFixes ? (super_bomb_indicator_unk2 <= 1) : (super_bomb_indicator_unk2 == 1))
pal = (frame_counter & 7);
}
const TagalongSprXY *sprd = kTagalongDraw_SprXY + frame + (kTagalongDraw_Offs[follower_indicator] >> 3);
const TagalongDmaFlags *sprf = kTagalongDmaAndFlags + frame;

View File

@@ -46,12 +46,14 @@ static void MakeSnapshot(Snapshot *s) {
memcpy(s->ram, g_snes->ram, 0x20000);
memcpy(s->sram, g_snes->cart->ram, g_snes->cart->ramSize);
memcpy(s->vram, g_snes->ppu->vram, sizeof(uint16) * 0x8000);
memcpy(s->ram + 0x1DBA0, s->ram + 0x1B00, 224 * 2); // hdma_table (partial)
}
static void MakeMySnapshot(Snapshot *s) {
memcpy(s->ram, g_zenv.ram, 0x20000);
memcpy(s->sram, g_zenv.sram, 0x2000);
memcpy(s->vram, g_zenv.ppu->vram, sizeof(uint16) * 0x8000);
memcpy(s->ram + 0x1B00, s->ram + 0x1DBA0, 224 * 2); // hdma_table (partial)
}
static void RestoreMySnapshot(Snapshot *s) {
@@ -105,10 +107,8 @@ static void VerifySnapshotsEq(Snapshot *b, Snapshot *a, Snapshot *prev) {
memcpy(&b->ram[0x1f0d], &a->ram[0x1f0d], 0x3f - 0xd);
memcpy(b->ram + 0x138, a->ram + 0x138, 256 - 0x38); // copy the stack over
memcpy(a->ram + 0x1DBA0, b->ram + 0x1DBA0, 240 * 2); // hdma_table
memcpy(b->ram + 0x1B00, b->ram + 0x1DBA0, 224 * 2); // hdma_table (partial)
memcpy(a->ram + 0x1cc0, b->ram + 0x1cc0, 2); // some leftover stuff in hdma table
memcpy(a->ram + 0x1dd60, b->ram + 0x1dd60, 16 * 2); // some leftover stuff in hdma table
if (memcmp(b->ram, a->ram, 0x20000)) {
fprintf(stderr, "@%d: Memory compare failed (mine != theirs, prev):\n", frame_counter);
@@ -351,7 +351,7 @@ again_mine:
VerifySnapshotsEq(&g_snapshot_mine, &g_snapshot_theirs, &g_snapshot_before);
if (g_fail) {
// g_fail = false;
g_fail = false;
if (1) {
RestoreMySnapshot(&g_snapshot_before);
//SaveLoadSlot(kSaveLoad_Save, 0);

View File

@@ -215,10 +215,10 @@ static void ConfigurePpuSideSpace() {
PpuSetExtraSideSpace(g_zenv.ppu, extra_left, extra_right, extra_bottom);
}
bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
void ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
SimpleHdma hdma_chans[2];
bool rv = PpuBeginDrawing(g_zenv.ppu, pixel_buffer, pitch, render_flags);
PpuBeginDrawing(g_zenv.ppu, pixel_buffer, pitch, render_flags);
dma_startDma(g_zenv.dma, HDMAEN_copy, true);
@@ -257,8 +257,6 @@ bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
SimpleHdma_DoLine(&hdma_chans[0]);
SimpleHdma_DoLine(&hdma_chans[1]);
}
return rv;
}
void HdmaSetup(uint32 addr6, uint32 addr7, uint8 transfer_unit, uint8 reg6, uint8 reg7, uint8 indirect_bank) {

View File

@@ -42,7 +42,7 @@ void HdmaSetup(uint32 addr6, uint32 addr7, uint8 transfer_unit, uint8 reg6, uint
void ZeldaInitialize();
void ZeldaReset(bool preserve_sram);
bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags);
void ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags);
void ZeldaRunFrameInternal(uint16 input, int run_what);
bool ZeldaRunFrame(int input_state);
void LoadSongBank(const uint8 *p);