shithub: zelda3

Download patch

ref: 572f8dd7ed70fa2f4d4efd1e4f483a0d644d340c
parent: c11300e66940377c5d7266dae33859ff363572ed
author: Snesrev <[email protected]>
date: Mon Sep 12 13:31:48 EDT 2022

Add config option to play in 16:9 or 16:10 resolution (Fixes #37)

 - Add ExtendedAspectRatio = 16:10 to play in 16:10
 - Doesn't behave perfect everywhere
 - Optionally spawn/kill sprites so they behave better

--- a/ancilla.c
+++ b/ancilla.c
@@ -382,11 +382,11 @@
   assert(t < 32);
   for(int i = 0; i < 4; i++, t++) {
     if (kInitialSpinSpark_Char[t] != 0xff) {
-      Ancilla_SetOam_XY(oam,
+      uint8 ext = Ancilla_SetOam_XY(oam,
         info.x + kInitialSpinSpark_X[t], info.y + kInitialSpinSpark_Y[t]);
       oam->charnum = kInitialSpinSpark_Char[t];
       oam->flags = kInitialSpinSpark_Flags[t] & ~0x30 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   }
@@ -1487,10 +1487,10 @@
   OamEnt *oam = GetOamCurPtr();
   for (int n = 3; n >= 0; n--, t++) {
     if (kWallHit_Char[t] != 0) {
-      Ancilla_SetOam_XY(oam, info.x + kWallHit_X[t], info.y + kWallHit_Y[t]);
+      uint8 ext = Ancilla_SetOam_XY(oam, info.x + kWallHit_X[t], info.y + kWallHit_Y[t]);
       oam->charnum = kWallHit_Char[t];
       oam->flags = kWallHit_Flags[t] & ~0x30 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
     oam = Ancilla_AllocateOamFromCustomRegion(oam);
@@ -1930,13 +1930,13 @@
   for (int i = 0; i != 2; i++) {
     int t = j * 2 + i;
     //kDoorDebris_XY
-    Ancilla_SetOam_XY(oam, x + kDoorDebris_XY[t * 2 + 1], y + kDoorDebris_XY[t * 2 + 0]);
+    uint8 ext = Ancilla_SetOam_XY(oam, x + kDoorDebris_XY[t * 2 + 1], y + kDoorDebris_XY[t * 2 + 0]);
 
     uint16 d = kDoorDebris_CharFlags[t];
     oam->charnum = d;
     oam->flags = (d >> 8) & 0xc0 | HIBYTE(oam_priority_value);
 
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     oam = Ancilla_AllocateOamFromCustomRegion(oam + 1);
   }
 }
@@ -2048,10 +2048,10 @@
   uint8 flags = (link_item_bow & 4) ? 2 : 4;
   for (int i = 0; i != 2; i++, j++) {
     if (kArrow_Draw_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam, x + kArrow_Draw_X[j], y + kArrow_Draw_Y[j]);
+      uint8 ext = Ancilla_SetOam_XY(oam, x + kArrow_Draw_X[j], y + kArrow_Draw_Y[j]);
       oam->charnum = kArrow_Draw_Char[j];
       oam->flags = kArrow_Draw_Flags[j] & ~0x3E | flags | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   }
@@ -2277,18 +2277,18 @@
   int j = ancilla_item_to_link[k];
   uint8 flags = 0;
   for (int i = 0; i < 2; i++) {
-    Ancilla_SetOam_XY(oam, pt.x, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
     oam->charnum = kAncilla_JumpSplash_Char[j];
     oam->flags = 0x24 | flags;
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     oam = Ancilla_AllocateOamFromCustomRegion(oam + 1);
     pt.x = x8;
     flags = 0x40;
   }
-  Ancilla_SetOam_XY(oam, x6, pt.y);
+  uint8 ext = Ancilla_SetOam_XY(oam, x6, pt.y);
   oam->charnum = 0xc0;
   oam->flags = 0x24;
-  bytewise_extended_oam[oam - oam_buf] = (j == 1) ? 1 : 2;
+  bytewise_extended_oam[oam - oam_buf] = ext | ((j == 1) ? 1 : 2);
 }
 
 void Ancilla16_HitStars(int k) {  // 88a8e5
@@ -2328,10 +2328,10 @@
   uint16 x = info.x, y = info.y;
   uint8 flags = 0;
   for (int i = 1; i >= 0; i--) {
-    Ancilla_SetOam_XY(oam, x, y);
+    uint8 ext = Ancilla_SetOam_XY(oam, x, y);
     oam->charnum = kAncilla_HitStars_Char[ancilla_item_to_link[k]];
     oam->flags = HIBYTE(oam_priority_value) | 4 | flags;
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     flags = 0x40;
     BYTE(x) = r8;
     oam = HitStars_UpdateOamBufferPosition(oam + 1);
@@ -2356,10 +2356,10 @@
   pt.x += kShovelDirt_XY[j * 2 + 1];
   pt.y += kShovelDirt_XY[j * 2 + 0];
   for (int i = 0; i < 2; i++) {
-    Ancilla_SetOam_XY(oam, pt.x + i * 8, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x + i * 8, pt.y);
     oam->charnum = kShovelDirt_Char[b] + i;
     oam->flags = 4 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     oam = Ancilla_AllocateOamFromCustomRegion(oam + 1);
   }
 }
@@ -2386,10 +2386,10 @@
   Point16U pt;
   Ancilla_PrepOamCoord(k, &pt);
   OamEnt *oam = GetOamCurPtr();
-  Ancilla_SetOam_XY(oam, pt.x, pt.y);
+  uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
   oam->charnum = kBlastWallFireball_Char[blastwall_var12[k] & 8 ? 0 : blastwall_var12[k] & 4 ? 1 : 2];
   oam->flags = 0x22;
-  bytewise_extended_oam[oam - oam_buf] = 0;
+  bytewise_extended_oam[oam - oam_buf] = ext;
 }
 
 void Ancilla18_EtherSpell(int k) {  // 88aaa0
@@ -2554,10 +2554,10 @@
   static const uint8 kEther_BlitzBall_Char[2] = {0x68, 0x6a};
   int x = (arp->r6 ? -arp->r4 : arp->r4) + ether_x2 - 8 - BG2HOFS_copy2;
   int y = (arp->r2 ? -arp->r0 : arp->r0) + ether_y3 - 8 - BG2VOFS_copy2;
-  Ancilla_SetOam_XY(oam, x, y);
+  uint8 ext = Ancilla_SetOam_XY(oam, x, y);
   oam->charnum = kEther_BlitzBall_Char[s];
   oam->flags = 0x3c;
-  bytewise_extended_oam[oam - oam_buf] = 2;
+  bytewise_extended_oam[oam - oam_buf] = ext | 2;
   return Ancilla_AllocateOamFromCustomRegion(oam + 1);
 }
 
@@ -2574,18 +2574,18 @@
   };
   int x = (arp->r6 ? -arp->r4 : arp->r4);
   int y = (arp->r2 ? -arp->r0 : arp->r0);
-  Ancilla_SetOam_XY(oam, x + ether_x2 - 8 - BG2HOFS_copy2, y + ether_y3 - 8 - BG2VOFS_copy2);
+  uint8 ext = Ancilla_SetOam_XY(oam, x + ether_x2 - 8 - BG2HOFS_copy2, y + ether_y3 - 8 - BG2VOFS_copy2);
   int t = s * 8 + k;
   oam->charnum = kEther_SpllittingBlitzSegment_Char[t * 2];
   oam->flags = kEther_SpllittingBlitzSegment_Flags[t * 2];
-  bytewise_extended_oam[oam - oam_buf] = 2;
+  bytewise_extended_oam[oam - oam_buf] = ext | 2;
   oam++;
-  Ancilla_SetOam_XY(oam,
+  ext = Ancilla_SetOam_XY(oam,
       x + ether_x2 + kEther_SpllittingBlitzSegment_X[t] - BG2HOFS_copy2,
       y + ether_y3 + kEther_SpllittingBlitzSegment_Y[t] - BG2VOFS_copy2);
   oam->charnum = kEther_SpllittingBlitzSegment_Char[t * 2 + 1];
   oam->flags = kEther_SpllittingBlitzSegment_Flags[t * 2 + 1];
-  bytewise_extended_oam[oam - oam_buf] = 2;
+  bytewise_extended_oam[oam - oam_buf] = ext | 2;
   return Ancilla_AllocateOamFromCustomRegion(oam + 1);
 }
 
@@ -2597,10 +2597,10 @@
   int i = ancilla_arr25[k];
   int m = 0;
   do {
-    Ancilla_SetOam_XY(oam, info.x, info.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, info.x, info.y);
     oam->charnum = kEther_BlitzSegment_Char[t * 2 + m];
     oam->flags = kEther_BlitzOrb_Flags[0] | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     info.y -= 16;
     oam++;
     m ^= 1;
@@ -2615,10 +2615,10 @@
   int t = ancilla_item_to_link[k] * 4;
 
   for (int i = 0; i < 4; i++) {
-    Ancilla_SetOam_XY(oam, x, y);
+    uint8 ext = Ancilla_SetOam_XY(oam, x, y);
     oam->charnum = kEther_BlitzOrb_Char[t + i];
     oam->flags = kEther_BlitzOrb_Flags[t + i];
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     oam++;
     oam = Ancilla_AllocateOamFromCustomRegion(oam);
     x += 16;
@@ -2803,10 +2803,10 @@
         uint16 y = bombos_y_lo[kk] | bombos_y_hi[kk] << 8;
         y += kBombosSpell_FireColumn_Y[k] - BG2VOFS_copy2;
         x += kBombosSpell_FireColumn_X[k] - BG2HOFS_copy2;
-        Ancilla_SetOam_XY(oam, x, y);
+        uint8 ext = Ancilla_SetOam_XY(oam, x, y);
         oam->charnum = kBombosSpell_FireColumn_Char[k];
         oam->flags = kBombosSpell_FireColumn_Flags[k];
-        bytewise_extended_oam[oam - oam_buf] = 2;
+        bytewise_extended_oam[oam - oam_buf] = ext | 2;
         oam++;
       }
       oam = Ancilla_AllocateOamFromCustomRegion(oam);
@@ -2893,12 +2893,12 @@
   int t = bombos_arr3[k] * 4 + 3;
   for (int j = 0; j < 4; j++, t--) {
     if (kBombosSpell_DrawBlast_Char[t] != 0xff) {
-      Ancilla_SetOam_XY(oam,
+      uint8 ext = Ancilla_SetOam_XY(oam,
         x + kBombosSpell_DrawBlast_X[t] - BG2HOFS_copy2,
         y + kBombosSpell_DrawBlast_Y[t] - BG2VOFS_copy2);
       oam->charnum = kBombosSpell_DrawBlast_Char[t];
       oam->flags = kBombosSpell_DrawBlast_Flags[t];
-      bytewise_extended_oam[oam - oam_buf] = 2;
+      bytewise_extended_oam[oam - oam_buf] = ext | 2;
       oam++;
     }
     oam = Ancilla_AllocateOamFromCustomRegion(oam);
@@ -3075,10 +3075,10 @@
   OamEnt *oam = GetOamCurPtr();
   int b = ancilla_arr25[k];
   for (int i = 0; i < 4; i++, oam++) {
-    Ancilla_SetOam_XY(oam, info.x + kMagicPowder_DrawX[b * 4 + i], info.y + kMagicPowder_DrawY[b * 4 + i]);
+    uint8 ext = Ancilla_SetOam_XY(oam, info.x + kMagicPowder_DrawX[b * 4 + i], info.y + kMagicPowder_DrawY[b * 4 + i]);
     oam->charnum = kMagicPowder_Draw_Char[b];
     oam->flags = kMagicPowder_Draw_Flags[b * 4 + i] & ~0x30 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 }
 
@@ -3166,10 +3166,10 @@
 
   for (int n = 2; n >= 0; n--, t++) {
     if (kDashDust_Draw_Char[t] != 0xff) {
-      Ancilla_SetOam_XY(oam, info.x + r12 + kDashDust_Draw_X[t], info.y + kDashDust_Draw_Y[t]);
+      uint8 ext = Ancilla_SetOam_XY(oam, info.x + r12 + kDashDust_Draw_X[t], info.y + kDashDust_Draw_Y[t]);
       oam->charnum = kDashDust_Draw_Char[t];
       oam->flags = 4 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   }
@@ -3284,10 +3284,10 @@
   int x = info.x, y = info.y;
   for (int i = 2; i >= 0; i--, j++) {
     if (kHookShot_Draw_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam, x, y);
+      uint8 ext = Ancilla_SetOam_XY(oam, x, y);
       oam->charnum = kHookShot_Draw_Char[j];
       oam->flags = kHookShot_Draw_Flags[j] | 2 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
     if (i == 1)
@@ -3318,10 +3318,10 @@
     if (kHookShot_Move_X[j])
       x += kHookShot_Move_X[j] + r10;
     if (!Hookshot_CheckProximityToLink(x, y)) {
-      Ancilla_SetOam_XY(oam, x, y);
+      uint8 ext = Ancilla_SetOam_XY(oam, x, y);
       oam->charnum = 0x19;
       oam->flags = (frame_counter & 2) << 6 | 2 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   } while (--n >= 0);
@@ -3343,10 +3343,10 @@
   int j = link_pose_during_opening ? 4 : 0;
   uint16 x = pt.x, y = pt.y;
   for (int i = 3; i >= 0; i--, j++, oam++) {
-    Ancilla_SetOam_XY(oam, x, y);
+    uint8 ext = Ancilla_SetOam_XY(oam, x, y);
     oam->charnum = kBedSpread_Char[j];
     oam->flags = kBedSpread_Flags[j] | 0xd | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     x += 16;
     if (i == 2)
       x -= 32, y += 8;
@@ -3371,10 +3371,10 @@
   Point16U pt;
   Ancilla_PrepOamCoord(k, &pt);
   OamEnt *oam = GetOamCurPtr();
-  Ancilla_SetOam_XY(oam, pt.x, pt.y);
+  uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
   oam->charnum = 9;
   oam->flags = 0x24;
-  bytewise_extended_oam[oam - oam_buf] = 0;
+  bytewise_extended_oam[oam - oam_buf] = ext;
 }
 
 void Ancilla3B_SwordUpSparkle(int k) {  // 88c167
@@ -3404,12 +3404,12 @@
   int j = ancilla_item_to_link[k] * 4;
   for (int i = 0; i < 4; i++, j++) {
     if (kAncilla_VictorySparkle_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam,
+      uint8 ext = Ancilla_SetOam_XY(oam,
                         link_x_coord + kAncilla_VictorySparkle_X[j] - BG2HOFS_copy2,
                         link_y_coord + kAncilla_VictorySparkle_Y[j] - BG2VOFS_copy2);
       oam->charnum = kAncilla_VictorySparkle_Char[j];
       oam->flags = kAncilla_VictorySparkle_Flags[j] | 4 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   }
@@ -3430,11 +3430,11 @@
   Point16U info;
   Ancilla_PrepOamCoord(k, &info);
   OamEnt *oam = GetOamCurPtr();
-  Ancilla_SetOam_XY(oam, info.x, info.y);
+  uint8 ext = Ancilla_SetOam_XY(oam, info.x, info.y);
   int j = ancilla_item_to_link[k];
   oam->charnum = kSwordChargeSpark_Char[j];
   oam->flags = kSwordChargeSpark_Flags[j] | HIBYTE(oam_priority_value);
-  bytewise_extended_oam[oam - oam_buf] = 0;
+  bytewise_extended_oam[oam - oam_buf] = ext;
 }
 
 void Ancilla35_MasterSwordReceipt(int k) {  // 88c25f
@@ -3459,10 +3459,10 @@
     return;
 
   for (int i = 0; i < 4; i++, j++, oam++) {
-    Ancilla_SetOam_XY(oam, pt.x + kSwordCeremony_X[j], pt.y + kSwordCeremony_Y[j]);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kSwordCeremony_X[j], pt.y + kSwordCeremony_Y[j]);
     oam->charnum = kSwordCeremony_Char[j];
     oam->flags = kSwordCeremony_Flags[j] & ~0x30 | 4 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 }
 
@@ -3643,20 +3643,20 @@
 OamEnt *Ancilla_ReceiveItem_Draw(int k, int x, int y) {  // 88c690
   OamEnt *oam = GetOamCurPtr();
   int j = ancilla_item_to_link[k];
-  Ancilla_SetOam_XY(oam, x, y);
+  uint8 ext = Ancilla_SetOam_XY(oam, x, y);
   oam->charnum = 0x24;
   uint8 a = kWishPond2_OamFlags[j];
   if (sign8(a))
     a = ancilla_arr4[k];
   oam->flags = a * 2 | 0x30;
-  uint8 ext = kReceiveItem_Tab1[j];
+  ext |= kReceiveItem_Tab1[j];
   bytewise_extended_oam[oam - oam_buf] = ext;
   oam++;
   if (ext == 0) {
-    Ancilla_SetOam_XY(oam, x, y + 8);
+    ext = Ancilla_SetOam_XY(oam, x, y + 8);
     oam->charnum = 0x34;
     oam->flags = a * 2 | 0x30;
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     oam++;
   }
   return oam;
@@ -3825,10 +3825,10 @@
   int j = ancilla_item_to_link[k] * 2;
   for (int i = 0; i != 2; i++, j++) {
     if (kObjectSplash_Draw_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam, pt.x + kObjectSplash_Draw_X[j], pt.y + kObjectSplash_Draw_Y[j]);
+      uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kObjectSplash_Draw_X[j], pt.y + kObjectSplash_Draw_Y[j]);
       oam->charnum = kObjectSplash_Draw_Char[j];
       oam->flags = kObjectSplash_Draw_Flags[j] | 0x24;
-      bytewise_extended_oam[oam - oam_buf] = kObjectSplash_Draw_Ext[j];
+      bytewise_extended_oam[oam - oam_buf] = ext | kObjectSplash_Draw_Ext[j];
       oam++;
     }
   }
@@ -4111,11 +4111,11 @@
 
     int x = breaktowerseal_sparkle_x_hi[k] << 8 | breaktowerseal_sparkle_x_lo[k];
     int y = breaktowerseal_sparkle_y_hi[k] << 8 | breaktowerseal_sparkle_y_lo[k];
-    Ancilla_SetOam_XY(oam, x, y);
+    uint8 ext = Ancilla_SetOam_XY(oam, x, y);
     int j = breaktowerseal_sparkle_var1[k];
     oam->charnum = kSwordChargeSpark_Char[j];
     oam->flags = kSwordChargeSpark_Flags[j] | 0x30;
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     oam++;
   }
   return oam;
@@ -4147,10 +4147,10 @@
   Point16U pt;
   Ancilla_PrepAdjustedOamCoord(k, &pt);
   OamEnt *oam = GetOamCurPtr();
-  Ancilla_SetOam_XY(oam, pt.x, pt.y - (int8)ancilla_z[k]);
+  uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y - (int8)ancilla_z[k]);
   oam->charnum = 0x24;
   oam->flags = HIBYTE(oam_priority_value) | 4;
-  bytewise_extended_oam[oam - oam_buf] = 2;
+  bytewise_extended_oam[oam - oam_buf] = ext | 2;
   if (oam->y == 0xf0)
     ancilla_type[k] = 0;
 }
@@ -4225,11 +4225,11 @@
   if (sign8(i))
     return;
   OamEnt *oam = GetOamCurPtr() + (weathervane_var14 >> 2);
-  Ancilla_SetOam_XY(oam, pt.x, pt.y);
+  uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
   oam->charnum = kWeathervane_Explode_Char[i];
   oam->flags = 0x3c;
   weathervane_var14 += 4;
-  bytewise_extended_oam[oam - oam_buf] = 0;
+  bytewise_extended_oam[oam - oam_buf] = ext;
 }
 
 void Ancilla38_CutsceneDuck(int k) {  // 88d1d8
@@ -4279,10 +4279,10 @@
   Ancilla_PrepOamCoord(k, &info);
   OamEnt *oam = GetOamCurPtr();
 
-  Ancilla_SetOam_XY(oam, info.x + kTravelBird_Draw_X[0], info.y + (int8)ancilla_z[k] + kTravelBird_Draw_Y[0]);
+  uint8 ext = Ancilla_SetOam_XY(oam, info.x + kTravelBird_Draw_X[0], info.y + (int8)ancilla_z[k] + kTravelBird_Draw_Y[0]);
   oam->charnum = kTravelBird_Draw_Char[0];
   oam->flags = kTravelBird_Draw_Flags[0] | 0x30 | kTravelBirdIntro_Tab0[ancilla_dir[k] & 1];
-  bytewise_extended_oam[oam - oam_buf] = 2;
+  bytewise_extended_oam[oam - oam_buf] = ext | 2;
   oam++;
   AncillaDraw_Shadow(oam, 1, info.x, info.y + 48, 0x30);
   if (!sign16(info.x) && info.x >= 248) {
@@ -4331,10 +4331,10 @@
   uint8 ext = kMorphPoof_Ext[j];
   uint8 chr = kMorphPoof_Char[j];
   for (int i = 0; i < 4; i++, oam++) {
-    Ancilla_SetOam_XY(oam, info.x + kMorphPoof_X[j * 4 + i], info.y + kMorphPoof_Y[j * 4 + i]);
+    uint8 ext2 = Ancilla_SetOam_XY(oam, info.x + kMorphPoof_X[j * 4 + i], info.y + kMorphPoof_Y[j * 4 + i]);
     oam->charnum = chr;
     oam->flags = kMorphPoof_Flags[j * 4 + i] | 4 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = ext;
+    bytewise_extended_oam[oam - oam_buf] = ext | ext2;
     if (ext == 2)
       break;
   }
@@ -4372,10 +4372,10 @@
 
   int j = ancilla_item_to_link[k] * 4;
   for (int i = 0; i < 4; i++, j++, oam++) {
-    Ancilla_SetOam_XY(oam, pt.x + kBushPoof_Draw_X[j], pt.y + kBushPoof_Draw_Y[j]);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kBushPoof_Draw_X[j], pt.y + kBushPoof_Draw_Y[j]);
     oam->charnum = kBushPoof_Draw_Char[j];
     oam->flags = kBushPoof_Draw_Flags[j] | 4 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 }
 
@@ -4421,8 +4421,8 @@
       continue;
     oam->charnum = chr;
     oam->flags = kSwordSwingSparkle_Flags[k] | 0x4 | (oam_priority_value >> 8);
-    Ancilla_SetOam_XY(oam, info.x + kSwordSwingSparkle_X[k], info.y + kSwordSwingSparkle_Y[k]);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    uint8 ext = Ancilla_SetOam_XY(oam, info.x + kSwordSwingSparkle_X[k], info.y + kSwordSwingSparkle_Y[k]);
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 }
 
@@ -4505,10 +4505,10 @@
     if (submodule_index == 0)
       swordbeam_arr[i] = (swordbeam_arr[i] + 4) & 0x3f;
     Point16U pt = Sparkle_PrepOamFromRadial(Ancilla_GetRadialProjection(swordbeam_arr[i], swordbeam_var2));
-    Ancilla_SetOam_XY(oam, pt.x, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
     oam->charnum = kSpinSpark_Char[i];
     oam->flags = flags | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   } while (oam++, --i >= 0);
 
   if (submodule_index == 0) {
@@ -4527,10 +4527,10 @@
   if (t != 3) {
     static const uint8 kSpinSpark_Char2[3] = {0xb7, 0x80, 0x83};
     Point16U pt = Sparkle_PrepOamFromRadial(Ancilla_GetRadialProjection(swordbeam_var1, swordbeam_var2));
-    Ancilla_SetOam_XY(oam, pt.x, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
     oam->charnum = kSpinSpark_Char2[t];
     oam->flags = 4 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 endif_2:
   if (ancilla_item_to_link[k] == 7)
@@ -4591,10 +4591,10 @@
   OamEnt *oam = GetOamCurPtr();
   for (int i = 0; i < 4; i++, j++) {
     if (kInitialCaneSpark_Draw_Char[j] != 255) {
-      Ancilla_SetOam_XY(oam, pt.x + kInitialCaneSpark_Draw_X[j], pt.y + kInitialCaneSpark_Draw_Y[j]);
+      uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kInitialCaneSpark_Draw_X[j], pt.y + kInitialCaneSpark_Draw_Y[j]);
       oam->charnum = kInitialCaneSpark_Draw_Char[j];
       oam->flags = kInitialCaneSpark_Draw_Flags[j] & ~0x30 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   }
@@ -4674,10 +4674,10 @@
     if (!submodule_index)
       swordbeam_arr[i] = (swordbeam_arr[i] + 3) & 0x3f;
     Point16U pt = Sparkle_PrepOamFromRadial(Ancilla_GetRadialProjection(swordbeam_arr[i], swordbeam_var2));
-    Ancilla_SetOam_XY(oam, pt.x, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
     oam->charnum = kCaneSpark_Char[i];
     oam->flags = flags | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
 
     Ancilla_SetXY(k, pt.x + BG2HOFS_copy2, pt.y + BG2VOFS_copy2);
     ancilla_dir[k] = 0;
@@ -4724,10 +4724,10 @@
     if (submodule_index == 0)
       swordbeam_arr[i] = (swordbeam_arr[i] + s) & 0x3f;
     Point16U pt = Sparkle_PrepOamFromRadial(Ancilla_GetRadialProjection(swordbeam_arr[i], swordbeam_var2));
-    Ancilla_SetOam_XY(oam, pt.x, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
     oam->charnum = kSwordBeam_Char[i];
     oam->flags = flags | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 
   if (submodule_index == 0) {
@@ -4746,10 +4746,10 @@
   if (t != 3) {
     static const uint8 kSwordBeam_Char2[3] = {0xb7, 0x80, 0x83};
     Point16U pt = Sparkle_PrepOamFromRadial(Ancilla_GetRadialProjection(swordbeam_var1, swordbeam_var2));
-    Ancilla_SetOam_XY(oam, pt.x, pt.y);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y);
     oam->charnum = kSwordBeam_Char2[t];
     oam->flags = 4 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 endif_2:
   oam -= 4;
@@ -4779,10 +4779,10 @@
 
   oam_priority_value = kSwordFullChargeSpark_Flags[ancilla_floor[k]] << 8;
   OamEnt *oam = GetOamCurPtr();
-  Ancilla_SetOam_XY(oam, x, y);
+  uint8 ext = Ancilla_SetOam_XY(oam, x, y);
   oam->charnum = 0xd7;
   oam->flags = HIBYTE(oam_priority_value) | 2;
-  bytewise_extended_oam[oam - oam_buf] = 0;
+  bytewise_extended_oam[oam - oam_buf] = ext;
 }
 
 void Ancilla27_Duck(int k) {  // 88dde8
@@ -4890,10 +4890,10 @@
   int z = ancilla_z[k] ? ancilla_z[k] | ~0xff : 0;
   int i = 0, n = ancilla_step[k] + 1;
   do {
-    Ancilla_SetOam_XY(oam, info.x + (int8)kTravelBird_Draw_X[i], info.y + z + (int8)kTravelBird_Draw_Y[i]);
+    uint8 ext = Ancilla_SetOam_XY(oam, info.x + (int8)kTravelBird_Draw_X[i], info.y + z + (int8)kTravelBird_Draw_Y[i]);
     oam->charnum = kTravelBird_Draw_Char[i];
     oam->flags = kTravelBird_Draw_Flags[i] | 0x30;
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     oam++;
   } while (++i != n);
 
@@ -5270,10 +5270,10 @@
   int j = ancilla_item_to_link[k] * 2;
   for (int i = 0; i < 2; i++, j++, oam++) {
     if (kSomariaBlockFizzle_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam, x + kSomariaBlockFizzle_X[j], y + kSomariaBlockFizzle_Y[j]);
+      uint8 ext = Ancilla_SetOam_XY(oam, x + kSomariaBlockFizzle_X[j], y + kSomariaBlockFizzle_Y[j]);
       oam->charnum = kSomariaBlockFizzle_Char[j];
       oam->flags = kSomariaBlockFizzle_Flags[j] & ~0x30 | HIBYTE(oam_priority_value);
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
     }
   }
 }
@@ -5332,10 +5332,10 @@
   int8 z = ancilla_z[k] + (ancilla_K[k] == 3 && BYTE(link_z_coord) != 0xff ? BYTE(link_z_coord) : 0);
   int j = ancilla_item_to_link[k] * 8;
   for (int i = 0; i != 8; i++, j++, oam++) {
-    Ancilla_SetOam_XY(oam, pt.x + kSomarianBlockDivide_X[j], pt.y + kSomarianBlockDivide_Y[j] - z);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kSomarianBlockDivide_X[j], pt.y + kSomarianBlockDivide_Y[j] - z);
     oam->charnum = kSomarianBlockDivide_Char[j];
     oam->flags = kSomarianBlockDivide_Flags[j] & ~0x30 | HIBYTE(oam_priority_value);
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
   }
 }
 
@@ -5354,10 +5354,10 @@
   int j = (ancilla_timer[k] & 0xf8) >> 1;
   do {
     if (kLampFlame_Draw_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam, pt.x + kLampFlame_Draw_X[j], pt.y + kLampFlame_Draw_Y[j]);
+      uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kLampFlame_Draw_X[j], pt.y + kLampFlame_Draw_Y[j]);
       oam->charnum = kLampFlame_Draw_Char[j];
       oam->flags = HIBYTE(oam_priority_value) | 2;
-      bytewise_extended_oam[oam - oam_buf] = 0;
+      bytewise_extended_oam[oam - oam_buf] = ext;
       oam++;
     }
   } while (++j & 3);
@@ -5404,10 +5404,10 @@
   int j = ancilla_item_to_link[k] * 2;
   for (int i = 0; i != 2; i++, j++, oam++) {
     if (kWaterfallSplash_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam, pt.x + kWaterfallSplash_X[j], pt.y + kWaterfallSplash_Y[j]);
+      uint8 ext = Ancilla_SetOam_XY(oam, pt.x + kWaterfallSplash_X[j], pt.y + kWaterfallSplash_Y[j]);
       oam->charnum = kWaterfallSplash_Char[j];
       oam->flags = kWaterfallSplash_Flags[j] | 0x30;
-      bytewise_extended_oam[oam - oam_buf] = kWaterfallSplash_Ext[j];
+      bytewise_extended_oam[oam - oam_buf] = ext | kWaterfallSplash_Ext[j];
     }
   }
 }
@@ -5421,10 +5421,10 @@
   OamEnt *oam = GetOamCurPtr();
   uint16 x = pt.x, y = pt.y;
   for (int i = 0; i < 4; i++, oam++) {
-    Ancilla_SetOam_XY(oam, x, y);
+    uint8 ext = Ancilla_SetOam_XY(oam, x, y);
     oam->charnum = kAncilla_Gravestone_Char[i];
     oam->flags = kAncilla_Gravestone_Flags[i] | 0x3d;
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     x += 16;
     if (i == 1)
       x -= 32, y += 8;
@@ -5487,17 +5487,17 @@
       int j = skullwoodsfire_var0[k];
       uint16 x = skullwoodsfire_x_arr[k] - BG2HOFS_copy2;
       uint16 y = skullwoodsfire_y_arr[k] - BG2VOFS_copy2 + kSkullWoodsFire_Draw_Y[j];
-      Ancilla_SetOam_XY(oam, x, y);
+      uint8 ext2 = Ancilla_SetOam_XY(oam, x, y);
       oam->charnum = kSkullWoodsFire_Draw_Char[j];
       oam->flags = 0x32;
       uint8 ext = kSkullWoodsFire_Draw_Ext[j];
-      bytewise_extended_oam[oam - oam_buf] = ext;
+      bytewise_extended_oam[oam - oam_buf] = ext | ext2;
       oam++;
       if (kSkullWoodsFire_Draw_Ext[j] != 2) {
-        Ancilla_SetOam_XY(oam, x + 8, y);
+        uint8 ext2 = Ancilla_SetOam_XY(oam, x + 8, y);
         oam->charnum = kSkullWoodsFire_Draw_Char[j] + 1;
         oam->flags = 0x32;
-        bytewise_extended_oam[oam - oam_buf] = ext;
+        bytewise_extended_oam[oam - oam_buf] = ext | ext2;
         oam++;
       }
     }
@@ -5516,12 +5516,12 @@
   int j = ancilla_item_to_link[k] * 6;
   for (int i = 0; i < 6; i++, j++) {
     if (kSkullWoodsFire_Draw2_Char[j] != 0xff) {
-      Ancilla_SetOam_XY(oam,
+      uint8 ext = Ancilla_SetOam_XY(oam,
           168 - BG2HOFS_copy2 + kSkullWoodsFire_Draw2_X[j],
           200 - BG2VOFS_copy2 + kSkullWoodsFire_Draw2_Y[j]);
       oam->charnum = kSkullWoodsFire_Draw2_Char[j];
       oam->flags = kSkullWoodsFire_Draw2_Flags[j] | 0x32;
-      bytewise_extended_oam[oam - oam_buf] = kSkullWoodsFire_Draw2_Ext[j];
+      bytewise_extended_oam[oam - oam_buf] = ext | kSkullWoodsFire_Draw2_Ext[j];
       oam++;
     }
   }
@@ -5618,7 +5618,7 @@
     Ancilla_PrepOamCoord(k, &pt);
     OamEnt *oam = GetOamCurPtr();
     int t = (ancilla_step[k] == 1 && ancilla_L[k]) ? ancilla_item_to_link[k] + 1 : 0;
-    Ancilla_SetOam_XY(oam, pt.x, pt.y - (int8)ancilla_z[k]);
+    uint8 ext = Ancilla_SetOam_XY(oam, pt.x, pt.y - (int8)ancilla_z[k]);
     if (t != 0)
       t += 1;
     else
@@ -5625,7 +5625,7 @@
       t = (frame_counter >> 2) & 1;
     oam->charnum = kAncilla_RevivalFaerie_Tab1[t];
     oam->flags = 0x74;
-    bytewise_extended_oam[oam - oam_buf] = 2;
+    bytewise_extended_oam[oam - oam_buf] = ext | 2;
     if (oam->y == 0xf0) {
       ancilla_step[k] = 3;
       submodule_index++;
@@ -5708,15 +5708,15 @@
   OamEnt *oam = GetOamCurPtr();
   int k = flag_for_boomerang_in_place;
   do {
-    Ancilla_SetOam_XY(oam, Ancilla_GetX(k), 0x57);
+    uint8 ext = Ancilla_SetOam_XY(oam, Ancilla_GetX(k), 0x57);
     oam->charnum = kGameOverText_Chars[k * 2 + 0];
     oam->flags = 0x3c;
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     oam++;
-    Ancilla_SetOam_XY(oam, Ancilla_GetX(k), 0x5f);
+    ext = Ancilla_SetOam_XY(oam, Ancilla_GetX(k), 0x5f);
     oam->charnum = kGameOverText_Chars[k * 2 + 1];
     oam->flags = 0x3c;
-    bytewise_extended_oam[oam - oam_buf] = 0;
+    bytewise_extended_oam[oam - oam_buf] = ext;
     oam++;
   } while (--k >= 0);
 }
@@ -5748,14 +5748,19 @@
   info->y = Ancilla_GetY(k) - BG2VOFS_copy;
 }
 
-void Ancilla_SetOam_XY(OamEnt *oam, uint16 x, uint16 y) {  // 88f6e1
+  
+uint8 Ancilla_SetOam_XY(OamEnt *oam, uint16 x, uint16 y) {  // 88f6e1
+  uint8 rv = 0;
   uint8 yval = 0xf0;
-  if (x < 256 && y < 256) {
+  int xt = (g_ram[kRam_Features0] & kFeatures0_ExtendScreen64) ? 0x40 : 0;
+  if ((uint16)(x + xt) < 256 + xt * 2 && y < 256) {
+    rv = (x >> 8) & 1;
     oam->x = x;
     if (y < 0xf0)
       yval = y;
   }
   oam->y = yval;
+  return rv;
 }
 
 uint8 Ancilla_SetOam_XY_safe(OamEnt *oam, uint16 x, uint16 y) {  // 88f702
@@ -7170,10 +7175,10 @@
   Point16U info;
   Ancilla_PrepOamCoord(k, &info);
   OamEnt *oam = GetOamCurPtr();
-  Ancilla_SetOam_XY(oam, info.x, info.y);
+  uint8 ext = Ancilla_SetOam_XY(oam, info.x, info.y);
   oam->charnum = kMotiveDashDust_Draw_Char[ancilla_item_to_link[k]];
   oam->flags = 4 | HIBYTE(oam_priority_value);
-  bytewise_extended_oam[oam - oam_buf] = 0;
+  bytewise_extended_oam[oam - oam_buf] = ext;
 }
 
 uint8 Ancilla_CalculateSfxPan(int k) {  // 8dbb5e
@@ -7302,9 +7307,10 @@
 }
 
 void Ancilla_TerminateIfOffscreen(int j) {  // 8ffd52
-  uint16 x = Ancilla_GetX(j) - BG2HOFS_copy2;
+  int xt = (g_ram[kRam_Features0] & kFeatures0_ExtendScreen64) ? 0x40 : 0;
+  uint16 x = Ancilla_GetX(j) - BG2HOFS_copy2 + xt;
   uint16 y = Ancilla_GetY(j) - BG2VOFS_copy2;
-  if (x >= 244 || y >= 240)
+  if (x >= 244 + xt * 2 || y >= 240)
     ancilla_type[j] = 0;
 }
 
--- a/ancilla.h
+++ b/ancilla.h
@@ -197,7 +197,7 @@
 int AncillaAdd_AddAncilla_Bank08(uint8 type, uint8 y);
 void Ancilla_PrepOamCoord(int k, Point16U *info);
 void Ancilla_PrepAdjustedOamCoord(int k, Point16U *info);
-void Ancilla_SetOam_XY(OamEnt *oam, uint16 x, uint16 y);
+uint8 Ancilla_SetOam_XY(OamEnt *oam, uint16 x, uint16 y);
 uint8 Ancilla_SetOam_XY_safe(OamEnt *oam, uint16 x, uint16 y);
 bool Ancilla_CheckLinkCollision(int k, int j, CheckPlayerCollOut *out);
 bool Hookshot_CheckProximityToLink(int x, int y);
--- a/config.c
+++ b/config.c
@@ -238,6 +238,20 @@
     if (StringEqualsNoCase(key, "Autosave")) {
       g_config.autosave = (bool)strtol(value, (char**)NULL, 10);
       return true;
+    } else if (StringEqualsNoCase(key, "ExtendedAspectRatio")) {
+      const char* s;
+      while ((s = NextDelim(&value, ',')) != NULL) {
+        if (strcmp(s, "16:9") == 0)
+          g_config.extended_aspect_ratio = (224 * 16 / 9 - 256) / 2;
+        else if (strcmp(s, "16:10") == 0)
+          g_config.extended_aspect_ratio = (224 * 16 / 10 - 256) / 2;
+        else if (strcmp(s, "unchanged_sprites") == 0)
+          g_config.extended_aspect_ratio_nospr = true;
+        else
+          return false;
+      }
+      
+      return true;
     } else if (StringEqualsNoCase(key, "DisplayPerfInTitle")) {
       g_config.display_perf_title = (bool)strtol(value, (char**)NULL, 10);
       return true;
@@ -265,23 +279,6 @@
   if (length) *length = size;
   return buffer;
 }
-
-/*
-uint8 *WriteFile(const char *name) {
-  FILE *f = fopen(name, "w");
-  if (f == NULL)
-    return NULL;
-}
-
-void SaveConfigFile() {
-  uint8* file = ReadFile("zelda3.user.ini", NULL);
-  if (!file) {
-    file = ReadFile("zelda3.ini", NULL);
-    if (!file)
-      return;
-  }
-}
-*/
 
 void ParseConfigFile() {
   uint8 *file = ReadFile("zelda3.user.ini", NULL);
--- a/config.h
+++ b/config.h
@@ -42,6 +42,8 @@
   uint8 audio_channels;
   uint16 audio_samples;
   bool autosave;
+  uint8 extended_aspect_ratio;
+  bool extended_aspect_ratio_nospr;
   bool display_perf_title;
 } Config;
 
--- a/dungeon.h
+++ b/dungeon.h
@@ -1,5 +1,7 @@
 #pragma once
 
+typedef struct RoomBounds RoomBounds;
+
 enum {
   kDoorType_Regular = 0,
   kDoorType_Regular2 = 2,
@@ -40,18 +42,6 @@
   uint8 pal2;
   uint8 pal3;
 } DungPalInfo;
-
-typedef struct RoomBounds {
-  union {
-    struct {
-      uint16 a0, b0, a1, b1;
-    };
-    uint16 v[4];
-  };
-} RoomBounds;
-
-#define room_bounds_y (*(RoomBounds*)(g_ram+0x600))
-#define room_bounds_x (*(RoomBounds*)(g_ram+0x608))
 
 extern const uint8 kDungAnimatedTiles[24];
 uint16 *DstoPtr(uint16 d);
--- a/main.c
+++ b/main.c
@@ -43,7 +43,6 @@
 
 
 enum {
-  kRenderWidth = 256 * 2,
   kRenderHeight = 224 * 2,
   kDefaultFullscreen = 0,
   kDefaultWindowScale = 2,
@@ -66,9 +65,10 @@
 static bool g_display_perf;
 static int g_curr_fps;
 static int g_ppu_render_flags = 0;
-bool g_run_without_emu = false;
+static bool g_run_without_emu = false;
+static int g_snes_width;
+static const int g_snes_height = kRenderHeight;
 
-
 void NORETURN Die(const char *error) {
   fprintf(stderr, "Error: %s\n", error);
   exit(1);
@@ -99,14 +99,14 @@
       bt = 31;
     }
     // Allow a scale level slightly above the max that fits on screen
-    int mw = (bounds.w - bl - br + (kRenderWidth / kDefaultWindowScale) / 4) / (kRenderWidth / kDefaultWindowScale);
-    int mh = (bounds.h - bt - bb + (kRenderHeight / kDefaultWindowScale) / 4) / (kRenderHeight / kDefaultWindowScale);
+    int mw = (bounds.w - bl - br + (g_snes_width / kDefaultWindowScale) / 4) / (g_snes_width / kDefaultWindowScale);
+    int mh = (bounds.h - bt - bb + (g_snes_height / kDefaultWindowScale) / 4) / (g_snes_height / kDefaultWindowScale);
     max_scale = IntMin(mw, mh);
   }
   int new_scale = IntMax(IntMin(g_current_window_scale + scale_step, max_scale), 1);
   g_current_window_scale = new_scale;
-  int w = new_scale * (kRenderWidth / kDefaultWindowScale);
-  int h = new_scale * (kRenderHeight / kDefaultWindowScale);
+  int w = new_scale * (g_snes_width / kDefaultWindowScale);
+  int h = new_scale * (g_snes_height / kDefaultWindowScale);
   
   //SDL_RenderSetLogicalSize(g_renderer, w, h);
   SDL_SetWindowSize(g_window, w, h);
@@ -179,6 +179,10 @@
   ParseConfigFile();
   AfterConfigParse();
 
+  ZeldaInitialize();
+  g_zenv.ppu->extraLeftRight = UintMin(g_config.extended_aspect_ratio, kPpuExtraLeftRight);
+  g_snes_width = 2 * (g_config.extended_aspect_ratio * 2 + 256);
+  g_wanted_zelda_features = (g_zenv.ppu->extraLeftRight && !g_config.extended_aspect_ratio_nospr) ? kFeatures0_ExtendScreen64 : 0;
   g_ppu_render_flags = g_config.new_renderer * kPpuRenderFlags_NewRenderer | g_config.enhanced_mode7 * kPpuRenderFlags_4x4Mode7;
   
   if (g_config.fullscreen == 1)
@@ -207,8 +211,8 @@
     return 1;
   }
 
-  int window_width = g_current_window_scale * (kRenderWidth / kDefaultWindowScale);
-  int window_height = g_current_window_scale * (kRenderHeight / kDefaultWindowScale);
+  int window_width = g_current_window_scale * (g_snes_width / kDefaultWindowScale);
+  int window_height = g_current_window_scale * (g_snes_height / kDefaultWindowScale);
   SDL_Window* window = SDL_CreateWindow(kWindowTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height, g_win_flags);
   if(window == NULL) {
     printf("Failed to create window: %s\n", SDL_GetError());
@@ -231,8 +235,8 @@
 
   g_renderer = renderer;
   if (!g_config.ignore_aspect_ratio)
-    SDL_RenderSetLogicalSize(renderer, kRenderWidth, kRenderHeight);
-  SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, kRenderWidth * 2, kRenderHeight * 2);
+    SDL_RenderSetLogicalSize(renderer, g_snes_width, g_snes_height);
+  SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, g_snes_width * 2, g_snes_height * 2);
   if(texture == NULL) {
     printf("Failed to create texture: %s\n", SDL_GetError());
     return 1;
@@ -274,7 +278,6 @@
 #endif
 
   SetSnes(snes);
-  ZeldaInitialize();
   ZeldaReadSram(snes);
 
   for (int i = 0; i < SDL_NumJoysticks(); i++)
@@ -486,7 +489,7 @@
   uint64 t3 = SDL_GetPerformanceCounter();
   SDL_RenderClear(renderer);
   uint64 t4 = SDL_GetPerformanceCounter();
-  SDL_Rect src_rect = { 0, 0, kRenderWidth, kRenderHeight };
+  SDL_Rect src_rect = { 0, 0, g_snes_width, g_snes_height };
   SDL_RenderCopy(renderer, texture, hq ? NULL : &src_rect, NULL);
   uint64 t5 = SDL_GetPerformanceCounter();
 
--- a/snes/ppu.c
+++ b/snes/ppu.c
@@ -38,6 +38,9 @@
   memset(ppu->vram, 0, sizeof(ppu->vram));
   ppu->lastBrightnessMult = 0xff;
   ppu->lastMosaicModulo = 0xff;
+  ppu->extraLeftCur = 0;
+  ppu->extraRightCur = 0;
+  ppu->extraLeftRight = kPpuExtraLeftRight;
   ppu->vramPointer = 0;
   ppu->vramIncrementOnHigh = false;
   ppu->vramIncrement = 1;
@@ -133,7 +136,9 @@
 }
 
 void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx) {
-  func(ctx, &ppu->vram, offsetof(Ppu, mosaicModulo) - offsetof(Ppu, vram));
+  func(ctx, &ppu->vram, offsetof(Ppu, objBuffer) - offsetof(Ppu, vram));
+  func(ctx, &ppu->objBuffer, 512);
+  func(ctx, &ppu->timeOver, offsetof(Ppu, mosaicModulo) - offsetof(Ppu, timeOver));
 }
 
 bool PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags) {
@@ -160,7 +165,6 @@
       ppu->colorMapRgb[i] = ppu->brightnessMult[color & 0x1f] << 16 | ppu->brightnessMult[(color >> 5) & 0x1f] << 8 | ppu->brightnessMult[(color >> 10) & 0x1f];
     }
   }
-
   
   return hq;
 }
@@ -174,7 +178,7 @@
     if (ppu->mosaicSize != ppu->lastMosaicModulo) {
       int mod = ppu->mosaicSize;
       ppu->lastMosaicModulo = mod;
-      for (int i = 0, j = 0; i < 256; i++) {
+      for (int i = 0, j = 0; i < countof(ppu->mosaicModulo); i++) {
         ppu->mosaicModulo[i] = i - j;
         j = (j + 1 == mod ? 0 : j + 1);
       }
@@ -195,19 +199,18 @@
       uint8 *dst = ppu->renderBuffer + ((line - 1) * 2 * ppu->renderPitch);
       memcpy(dst + ppu->renderPitch, dst, 512 * 4);
     }
-
   }
 }
 
 typedef struct PpuWindows {
-  uint16 edges[6];
+  int16 edges[6];
   uint8 nr;
   uint8 bits;
 } PpuWindows;
 
-static void PpuWindows_Clear(PpuWindows *win) {
-  win->edges[0] = 0;
-  win->edges[1] = 256;
+static void PpuWindows_Clear(PpuWindows *win, Ppu *ppu) {
+  win->edges[0] = -ppu->extraLeftCur;
+  win->edges[1] = 256 + ppu->extraRightCur;
   win->nr = 1;
   win->bits = 0;
 }
@@ -218,19 +221,21 @@
   // There are at most 5 windows.
   // Algorithm from Snes9x
   uint nr = 1;
-  win->edges[0] = 0;
-  win->edges[1] = 256;
+  int window_right = 256 + ppu->extraRightCur;
+  win->edges[0] = - ppu->extraLeftCur;
+  win->edges[1] = window_right;
   uint8 window_bits = 0;
-  uint i, j, t;
+  uint i, j;
+  int t;
   bool w1_ena = wl->window1enabled && ppu->window1left <= ppu->window1right;
   if (w1_ena) {
-    if (ppu->window1left) {
+    if (ppu->window1left > win->edges[0]) {
       win->edges[nr] = ppu->window1left;
-      win->edges[++nr] = 256;
+      win->edges[++nr] = window_right;
     }
-    if (ppu->window1right < 255) {
+    if (ppu->window1right + 1 < window_right) {
       win->edges[nr] = ppu->window1right + 1;
-      win->edges[++nr] = 256;
+      win->edges[++nr] = window_right;
     }
   }
   bool w2_ena = wl->window2enabled && ppu->window2left <= ppu->window2right;
@@ -276,10 +281,10 @@
 static void PpuDrawBackground_4bpp(Ppu *ppu, uint y, bool sub, uint layer, uint8 zhi, uint8 zlo) {
 #define DO_PIXEL(i) do { \
   pixel = (bits >> i) & 1 | (bits >> (7 + i)) & 2 | (bits >> (14 + i)) & 4 | (bits >> (21 + i)) & 8; \
-  if (pixel && z > dst[256 + i]) dst[i] = paletteBase + pixel, dst[256 + i] = z; } while (0)
+  if (pixel && z > dst[kPpuXPixels + i]) dst[i] = paletteBase + pixel, dst[kPpuXPixels + i] = z; } while (0)
 #define DO_PIXEL_HFLIP(i) do { \
   pixel = (bits >> (7 - i)) & 1 | (bits >> (14 - i)) & 2 | (bits >> (21 - i)) & 4 | (bits >> (28 - i)) & 8; \
-  if (pixel && z > dst[256 + i]) dst[i] = paletteBase + pixel, dst[256 + i] = z; } while (0)
+  if (pixel && z > dst[kPpuXPixels + i]) dst[i] = paletteBase + pixel, dst[kPpuXPixels + i] = z; } while (0)
 #define READ_BITS(ta, tile) (addr = &ppu->vram[((ta) + (tile) * 16) & 0x7fff], addr[0] | addr[8] << 16)
   enum { kPaletteShift = 6 };
   Layer *layerp = &ppu->layer[layer];
@@ -286,7 +291,7 @@
   if (!layerp->screenEnabled[sub])
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win);
+  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y += bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -304,16 +309,17 @@
       continue;  // layer is disabled for this window part
     uint x = win.edges[windex] + bglayer->hScroll;
     uint w = win.edges[windex + 1] - win.edges[windex];
-    uint8 *dst = ppu->bgBuffers[sub].pixel + win.edges[windex];
+    uint8 *dst = ppu->bgBuffers[sub].pixel + win.edges[windex] + kPpuExtraLeftRight;
     const uint16 *tp = tps[x >> 8 & 1] + ((x >> 3) & 0x1f);
     const uint16 *tp_last = tps[x >> 8 & 1] + 31;
     const uint16 *tp_next = tps[(x >> 8 & 1) ^ 1];
+#define NEXT_TP() if (tp != tp_last) tp += 1; else tp = tp_next, tp_next = tp_last - 31, tp_last = tp + 31;
     // Handle clipped pixels on left side
     if (x & 7) {
       int curw = IntMin(8 - (x & 7), w);
       w -= curw;
       uint32 tile = *tp;
-      tp = (tp != tp_last) ? tp + 1 : tp_next;
+      NEXT_TP();
       int ta = (tile & 0x8000) ? tileadr1 : tileadr0;
       uint8 z = (tile & 0x2000) ? zhi : zlo;
       uint32 bits = READ_BITS(ta, tile & 0x3ff);
@@ -333,7 +339,7 @@
     // Handle full tiles in the middle
     while (w >= 8) {
       uint32 tile = *tp;
-      tp = (tp != tp_last) ? tp + 1 : tp_next;
+      NEXT_TP();
       int ta = (tile & 0x8000) ? tileadr1 : tileadr0;
       uint8 z = (tile & 0x2000) ? zhi : zlo;
       uint32 bits = READ_BITS(ta, tile & 0x3ff);
@@ -374,10 +380,10 @@
 static void PpuDrawBackground_2bpp(Ppu *ppu, uint y, bool sub, uint layer, uint8 zhi, uint8 zlo) {
 #define DO_PIXEL(i) do { \
   pixel = (bits >> i) & 1 | (bits >> (7 + i)) & 2; \
-  if (pixel && z > dst[256 + i]) dst[i] = paletteBase + pixel, dst[256 + i] = z; } while (0)
+  if (pixel && z > dst[kPpuXPixels + i]) dst[i] = paletteBase + pixel, dst[kPpuXPixels + i] = z; } while (0)
 #define DO_PIXEL_HFLIP(i) do { \
   pixel = (bits >> (7 - i)) & 1 | (bits >> (14 - i)) & 2; \
-  if (pixel && z > dst[256 + i]) dst[i] = paletteBase + pixel, dst[256 + i] = z; } while (0)
+  if (pixel && z > dst[kPpuXPixels + i]) dst[i] = paletteBase + pixel, dst[kPpuXPixels + i] = z; } while (0)
 #define READ_BITS(ta, tile) (addr = &ppu->vram[(ta) + (tile) * 8 & 0x7fff], addr[0])
   enum { kPaletteShift = 8 };
   Layer *layerp = &ppu->layer[layer];
@@ -384,7 +390,7 @@
   if (!layerp->screenEnabled[sub])
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win);
+  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y += bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -396,6 +402,7 @@
   };
   int tileadr = ppu->bgLayer[layer].tileAdr, pixel;
   int tileadr1 = tileadr + 7 - (y & 0x7), tileadr0 = tileadr + (y & 0x7);
+
   const uint16 *addr;
   for (size_t windex = 0; windex < win.nr; windex++) {
     if (win.bits & (1 << windex))
@@ -402,16 +409,18 @@
       continue;  // layer is disabled for this window part
     uint x = win.edges[windex] + bglayer->hScroll;
     uint w = win.edges[windex + 1] - win.edges[windex];
-    uint8 *dst = ppu->bgBuffers[sub].pixel + win.edges[windex];
+    uint8 *dst = ppu->bgBuffers[sub].pixel + win.edges[windex] + kPpuExtraLeftRight;
     const uint16 *tp = tps[x >> 8 & 1] + ((x >> 3) & 0x1f);
     const uint16 *tp_last = tps[x >> 8 & 1] + 31;
     const uint16 *tp_next = tps[(x >> 8 & 1) ^ 1];
+
+#define NEXT_TP() if (tp != tp_last) tp += 1; else tp = tp_next, tp_next = tp_last - 31, tp_last = tp + 31;
     // Handle clipped pixels on left side
     if (x & 7) {
       int curw = IntMin(8 - (x & 7), w);
       w -= curw;
       uint32 tile = *tp;
-      tp = (tp != tp_last) ? tp + 1 : tp_next;
+      NEXT_TP();
       int ta = (tile & 0x8000) ? tileadr1 : tileadr0;
       uint8 z = (tile & 0x2000) ? zhi : zlo;
       uint32 bits = READ_BITS(ta, tile & 0x3ff);
@@ -431,7 +440,7 @@
     // Handle full tiles in the middle
     while (w >= 8) {
       uint32 tile = *tp;
-      tp = (tp != tp_last) ? tp + 1 : tp_next;
+      NEXT_TP();
       int ta = (tile & 0x8000) ? tileadr1 : tileadr0;
       uint8 z = (tile & 0x2000) ? zhi : zlo;
       uint32 bits = READ_BITS(ta, tile & 0x3ff);
@@ -463,6 +472,7 @@
       }
     }
   }
+#undef NEXT_TP
 #undef READ_BITS
 #undef DO_PIXEL
 #undef DO_PIXEL_HFLIP
@@ -478,7 +488,7 @@
   if (!layerp->screenEnabled[sub])
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win);
+  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y = ppu->mosaicModulo[y] + bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -495,8 +505,8 @@
     if (win.bits & (1 << windex))
       continue;  // layer is disabled for this window part
     int sx = win.edges[windex];
-    uint8 *dst = ppu->bgBuffers[sub].pixel + sx;
-    uint8 *dst_end = ppu->bgBuffers[sub].pixel + win.edges[windex + 1];
+    uint8 *dst = ppu->bgBuffers[sub].pixel + sx + kPpuExtraLeftRight;
+    uint8 *dst_end = ppu->bgBuffers[sub].pixel + win.edges[windex + 1] + kPpuExtraLeftRight;
     uint x = sx + bglayer->hScroll;
     const uint16 *tp = tps[x >> 8 & 1] + ((x >> 3) & 0x1f);
     const uint16 *tp_last = tps[x >> 8 & 1] + 31, *tp_next = tps[(x >> 8 & 1) ^ 1];
@@ -513,8 +523,8 @@
         pixel += (tile & 0x1c00) >> kPaletteShift;
         int i = 0;
         do {
-          if (z > dst[i + 256])
-            dst[i] = pixel, dst[i + 256] = z;
+          if (z > dst[i + kPpuXPixels])
+            dst[i] = pixel, dst[i + kPpuXPixels] = z;
         } while (++i != w);
       }
       dst += w, x += w;
@@ -538,7 +548,7 @@
   if (!layerp->screenEnabled[sub])
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win);
+  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y = ppu->mosaicModulo[y] + bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -555,8 +565,8 @@
     if (win.bits & (1 << windex))
       continue;  // layer is disabled for this window part
     int sx = win.edges[windex];
-    uint8 *dst = ppu->bgBuffers[sub].pixel + sx;
-    uint8 *dst_end = ppu->bgBuffers[sub].pixel + win.edges[windex + 1];
+    uint8 *dst = ppu->bgBuffers[sub].pixel + sx + kPpuExtraLeftRight;
+    uint8 *dst_end = ppu->bgBuffers[sub].pixel + win.edges[windex + 1] + kPpuExtraLeftRight;
     uint x = sx + bglayer->hScroll;
     const uint16 *tp = tps[x >> 8 & 1] + ((x >> 3) & 0x1f);
     const uint16 *tp_last = tps[x >> 8 & 1] + 31, *tp_next = tps[(x >> 8 & 1) ^ 1];
@@ -573,8 +583,8 @@
         pixel += (tile & 0x1c00) >> kPaletteShift;
         uint i = 0;
         do {
-          if (z > dst[i + 256])
-            dst[i] = pixel, dst[i + 256] = z;
+          if (z > dst[i + kPpuXPixels])
+            dst[i] = pixel, dst[i + kPpuXPixels] = z;
         } while (++i != w);
       }
       dst += w, x += w;
@@ -598,21 +608,21 @@
   if (!layerp->screenEnabled[sub])
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, 4) : PpuWindows_Clear(&win);
+  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, 4) : PpuWindows_Clear(&win, ppu);
   for (size_t windex = 0; windex < win.nr; windex++) {
     if (win.bits & (1 << windex))
       continue;  // layer is disabled for this window part
     int left = win.edges[windex];
     int width = win.edges[windex + 1] - left;
-    uint8 *src = ppu->objBuffer.pixel + left;
-    uint8 *dst = ppu->bgBuffers[sub].pixel + left;
+    uint8 *src = ppu->objBuffer.pixel + left + kPpuExtraLeftRight;
+    uint8 *dst = ppu->bgBuffers[sub].pixel + left + kPpuExtraLeftRight;
     if (clear_backdrop) {
       memcpy(dst, src, width);
-      memcpy(dst + 256, src + 256, width);
+      memcpy(dst + kPpuXPixels, src + kPpuXPixels, width);
     } else {
       do {
-        if (src[256] > dst[256])
-          dst[0] = src[0], dst[256] = src[256];
+        if (src[kPpuXPixels] > dst[kPpuXPixels])
+          dst[0] = src[0], dst[kPpuXPixels] = src[kPpuXPixels];
       } while (src++, dst++, --width);
     }
   }
@@ -625,7 +635,7 @@
   if (!layerp->screenEnabled[sub])
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win);
+  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu);
 
   // expand 13-bit values to signed values
   int hScroll = ((int16_t)(ppu->m7matrix[6] << 3)) >> 3;
@@ -648,7 +658,7 @@
     if (win.bits & (1 << windex))
       continue;  // layer is disabled for this window part
     int x = win.edges[windex], x2 = win.edges[windex + 1], tile;
-    uint8 *dst = ppu->bgBuffers[sub].pixel + x, *dst_end = ppu->bgBuffers[sub].pixel + x2;
+    uint8 *dst = ppu->bgBuffers[sub].pixel + x + kPpuExtraLeftRight, *dst_end = ppu->bgBuffers[sub].pixel + x2 + kPpuExtraLeftRight;
     uint32 rx = ppu->m7xFlip ? 255 - x : x;
     uint32 xpos = m7startX + ppu->m7matrix[0] * rx;
     uint32 ypos = m7startY + ppu->m7matrix[2] * rx;
@@ -670,7 +680,7 @@
         uint8 pixel = ppu->vram[tile * 64 + (ypos >> 8 & 7) * 8 + (xpos >> 8 & 7)] >> 8;
         if (pixel) {
           int i = 0;
-          do dst[i] = pixel, dst[i + 256] = z; while (++i != w);
+          do dst[i] = pixel, dst[i + kPpuXPixels] = z; while (++i != w);
         }
       } while (xpos += dx * w, ypos += dy * w, dst += w, w = ppu->mosaicSize, dst_end - dst != 0);
     } else {
@@ -684,18 +694,22 @@
         }
         uint8 pixel = ppu->vram[tile * 64 + (ypos >> 8 & 7) * 8 + (xpos >> 8 & 7)] >> 8;
         if (pixel)
-          dst[0] = pixel, dst[256] = z;
+          dst[0] = pixel, dst[kPpuXPixels] = z;
       } while (xpos += dx, ypos += dy, ++dst != dst_end);
     }
   }
 }
 
-uint16 g_mode7_lo, g_mode7_hi;
 void PpuSetMode7PerspectiveCorrection(Ppu *ppu, int low, int high) {
   ppu->mode7PerspectiveLow = low ? 1.0f / low : 0.0f;
   ppu->mode7PerspectiveHigh = 1.0f / high;
 }
 
+void PpuSetExtraSideSpace(Ppu *ppu, int left, int right) {
+  ppu->extraLeftCur = UintMin(left, ppu->extraLeftRight);
+  ppu->extraRightCur = UintMin(right, ppu->extraLeftRight);
+}
+
 static FORCEINLINE float FloatInterpolate(float x, float xmin, float xmax, float ymin, float ymax) {
   return ymin + (ymax - ymin) * (x - xmin) * (1.0f / (xmax - xmin));
 }
@@ -710,7 +724,7 @@
   uint32 clippedV = (((int16_t)(ppu->m7matrix[7] << 3)) >> 3) - yCenter;
   uint32 m0 = ppu->m7matrix[0];  // xpos increment per horiz movement
   uint32 m3 = ppu->m7matrix[3];  // ypos increment per vert movement
-  uint8 *dst_start = &ppu->renderBuffer[(y - 1) * 4 * ppu->renderPitch], *dst_end, *dst = dst_start;
+  uint8 *dst_start = &ppu->renderBuffer[(y - 1) * 4 * ppu->renderPitch], *dst_end, *dst = dst_start + ppu->extraLeftRight * 4 * 4;
   int32 m0v[4];
   if (*(uint32*)&ppu->mode7PerspectiveLow == 0) {
     m0v[0] = m0v[1] = m0v[2] = m0v[3] = ppu->m7matrix[0] << 12;
@@ -753,12 +767,11 @@
   if (ppu->lineHasSprites) {
     uint8 *pixels = ppu->objBuffer.pixel;
     size_t pitch = ppu->renderPitch;
-    for (size_t i = 0; i < 256; i++) {
-      uint32 pixel = pixels[i];
+    uint8 *dst = dst_start + ppu->extraLeftRight * 16;
+    for (size_t i = 0; i < 256; i++, dst += 16) {
+      uint32 pixel = pixels[i + kPpuExtraLeftRight];
       if (pixel) {
         uint32 color = ppu->colorMapRgb[pixel];
-        uint8 *dst = dst_start + i * 16;
-
         ((uint32 *)dst)[3] = ((uint32 *)dst)[2] = ((uint32 *)dst)[1] = ((uint32 *)dst)[0] = color;
         ((uint32 *)(dst + pitch * 1))[3] = ((uint32 *)(dst + pitch * 1))[2] = ((uint32 *)(dst + pitch * 1))[1] = ((uint32 *)(dst + pitch * 1))[0] = color;
         ((uint32 *)(dst + pitch * 2))[3] = ((uint32 *)(dst + pitch * 2))[2] = ((uint32 *)(dst + pitch * 2))[1] = ((uint32 *)(dst + pitch * 2))[0] = color;
@@ -767,6 +780,22 @@
     }
   }
 
+  if (ppu->extraLeftRight - ppu->extraLeftCur != 0) {
+    size_t n = 4 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraLeftCur);
+    size_t pitch = ppu->renderPitch;
+    for(int i = 0; i < 4; i++)
+      memset(dst_start + pitch * i, 0, n);
+  }
+  if (ppu->extraLeftRight - ppu->extraRightCur != 0) {
+    size_t n = 4 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraRightCur);
+    size_t pitch = ppu->renderPitch;
+    for (int i = 0; i < 4; i++)
+      memset(dst_start + pitch * i + (256 + ppu->extraLeftRight * 2 - (ppu->extraLeftRight - ppu->extraRightCur)) * 4 * sizeof(uint32), 0, n);
+  }
+    
+      
+
+
 #undef DRAW_PIXEL
 }
 
@@ -815,11 +844,9 @@
 static NOINLINE void PpuDrawWholeLine(Ppu *ppu, uint y) {
   if (ppu->forcedBlank) {
     uint8 *dst = &ppu->renderBuffer[(y - 1) * 2 * ppu->renderPitch];
-    size_t pitch = ppu->renderPitch;
-    for (int i = 0; i < 256; i++, dst += 8) {
-      ((uint32*)dst)[1] = ((uint32 *)dst)[0] = 0;
-      ((uint32*)(dst + pitch))[1] = ((uint32*)(dst + pitch))[0] = 0;
-    }
+    size_t n = sizeof(uint32) * 2 * (256 + ppu->extraLeftRight * 2);
+    memset(dst, 0, n);
+    memset(dst + ppu->renderPitch, 0, n);
     return;
   }
 
@@ -828,7 +855,6 @@
     return;
   }
 
-
   // Default background is backdrop
   memset(&ppu->bgBuffers[0].pixel, 0, sizeof(ppu->bgBuffers[0].pixel));
   memset(&ppu->bgBuffers[0].prio, 0x05, sizeof(ppu->bgBuffers[0].prio));
@@ -862,11 +888,13 @@
   uint32 cw_clip_math = ((cwin.bits & kCwBitsMod[ppu->clipMode]) ^ kCwBitsMod[ppu->clipMode + 4]) |
                         ((cwin.bits & kCwBitsMod[ppu->preventMathMode]) ^ kCwBitsMod[ppu->preventMathMode + 4]) << 8;
 
-  uint32 *dst = (uint32*)&ppu->renderBuffer[(y - 1) * 2 * ppu->renderPitch];
+  uint32 *dst = (uint32*)&ppu->renderBuffer[(y - 1) * 2 * ppu->renderPitch], *dst_org = dst;
   
+  dst += 2 * (ppu->extraLeftRight - ppu->extraLeftCur);
+
   uint32 windex = 0;
   do {
-    uint32 left = cwin.edges[windex], right = cwin.edges[windex + 1];
+    uint32 left = cwin.edges[windex] + kPpuExtraLeftRight, right = cwin.edges[windex + 1] + kPpuExtraLeftRight;
     // If clip is set, then zero out the rgb values from the main screen.
     uint32 clip_color_mask = (cw_clip_math & 1) ? 0x1f : 0;
     uint32 math_enabled_cur = (cw_clip_math & 0x100) ? math_enabled : 0;
@@ -918,9 +946,15 @@
     }
   } while (cw_clip_math >>= 1, ++windex < cwin.nr);
 
-  // Duplicate one line
-  memcpy((uint8*)(dst - 512) + ppu->renderPitch, dst - 512, 512 * 4);
+  // Clear out stuff on the sides.
+  if (ppu->extraLeftRight - ppu->extraLeftCur != 0)
+    memset(dst_org, 0, 2 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraLeftCur));
+  if (ppu->extraLeftRight - ppu->extraRightCur != 0)
+    memset(dst_org + 2 * (kPpuXPixels - (ppu->extraLeftRight - ppu->extraRightCur)), 0, 
+        2 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraRightCur));
 
+  // Duplicate one line
+  memcpy((uint8*)dst_org + ppu->renderPitch, dst_org, (ppu->extraLeftRight * 2 + 256) * 2 * sizeof(uint32));
 }
 
 static void ppu_handlePixel(Ppu* ppu, int x, int y) {
@@ -1245,6 +1279,7 @@
   int index = ppu->objPriority ? (ppu->oamAdr & 0xfe) : 0, index_end = index;
   int spritesFound = 0, tilesFound = 0;
   uint8 spriteSizes[2] = { kSpriteSizes[ppu->objSize][0], kSpriteSizes[ppu->objSize][1] };
+  int extra_left_right = ppu->extraLeftRight;
   do {
     int yy = ppu->oam[index] >> 8;
     if (yy == 0xf0)
@@ -1256,9 +1291,10 @@
     if (row >= spriteSize)
       continue;
     // in y-range, get the x location, using the high bit as well
-    int x = (ppu->oam[index] & 0xff) - (highOam & 1) * 256;
+    int x = (ppu->oam[index] & 0xff) + (highOam & 1) * 256;
+    x -= (x >= 256 + extra_left_right) * 512;
     // if in x-range
-    if (x <= -spriteSize)
+    if (x <= -(spriteSize + extra_left_right))
       continue;
     // break if we found 32 sprites already
     if (++spritesFound > 32) {
@@ -1273,8 +1309,9 @@
     // fetch all tiles in x-range
     uint8 paletteBase = 0x80 + 16 * ((oam1 & 0xe00) >> 9);
     uint8 prio = SPRITE_PRIO_TO_PRIO((oam1 & 0x3000) >> 12, (oam1 & 0x800) == 0);
+    
     for (int col = 0; col < spriteSize; col += 8) {
-      if (col + x > -8 && col + x < 256) {
+      if (col + x > -8 - extra_left_right && col + x < 256 + extra_left_right) {
         // break if we found 34 8*1 slivers already
         if (++tilesFound > 34) {
           ppu->timeOver = true;
@@ -1286,9 +1323,9 @@
         uint16 *addr = &ppu->vram[(objAdr + usedTile * 16 + (row & 0x7)) & 0x7fff];
         uint32 plane = addr[0] | addr[8] << 16;
         // go over each pixel
-        int px_left = IntMax(-(col + x), 0);
-        int px_right = IntMin(256 - (col + x), 8);
-        uint8 *dst = ppu->objBuffer.pixel + col + x + px_left;
+        int px_left = IntMax(-(col + x + kPpuExtraLeftRight), 0);
+        int px_right = IntMin(256 + kPpuExtraLeftRight - (col + x), 8);
+        uint8 *dst = ppu->objBuffer.pixel + col + x + px_left + kPpuExtraLeftRight;
         
         for (int px = px_left; px < px_right; px++, dst++) {
           int shift = oam1 & 0x4000 ? px : 7 - px;
@@ -1296,7 +1333,7 @@
           int pixel = (bits >> 0) & 1 | (bits >> 7) & 2 | (bits >> 14) & 4 | (bits >> 21) & 8;
           // draw it in the buffer if there is a pixel here, and the buffer there is still empty
           if (pixel != 0 && dst[0] == 0)
-            dst[0] = paletteBase + pixel, dst[256] = prio;
+            dst[0] = paletteBase + pixel, dst[kPpuXPixels] = prio;
         }
       }
     }
--- a/snes/ppu.h
+++ b/snes/ppu.h
@@ -11,6 +11,7 @@
 typedef struct Ppu Ppu;
 
 #include "snes.h"
+#include "../types.h"
 
 typedef struct BgLayer {
   uint16_t hScroll;
@@ -36,9 +37,13 @@
   uint8_t maskLogic_always_zero;
 } WindowLayer;
 
+enum {
+  kPpuXPixels = 256 + kPpuExtraLeftRight * 2,
+};
+
 typedef struct PpuPixelPrioBufs {
-  uint8_t pixel[256];
-  uint8_t prio[256];
+  uint8_t pixel[kPpuXPixels];
+  uint8_t prio[kPpuXPixels];
 } PpuPixelPrioBufs;
 
 enum {
@@ -55,6 +60,7 @@
   uint8_t renderFlags;
   uint32_t renderPitch;
   uint8_t *renderBuffer;
+  uint8_t extraLeftCur, extraRightCur, extraLeftRight;
   float mode7PerspectiveLow, mode7PerspectiveHigh;
 
   Snes* snes;
@@ -148,7 +154,7 @@
   uint8_t ppu1openBus;
   uint8_t ppu2openBus;
 
-  uint8_t mosaicModulo[256];
+  uint8_t mosaicModulo[kPpuXPixels];
   uint32_t colorMapRgb[256];
 };
 
@@ -163,5 +169,6 @@
 bool PpuBeginDrawing(Ppu *ppu, uint8_t *buffer, size_t pitch, uint32_t render_flags);
 
 void PpuSetMode7PerspectiveCorrection(Ppu *ppu, int low, int high);
+void PpuSetExtraSideSpace(Ppu *ppu, int left, int right);
 
 #endif
--- a/sprite.c
+++ b/sprite.c
@@ -1867,7 +1867,10 @@
   R2 = y - sprite_z[k];
   ret->flags = sprite_oam_flags[k] ^ sprite_obj_prio[k];
   ret->r4 = 0;
-  if ((uint16)(x + 0x40) >= 0x170 || (uint16)(y + 0x40) >= 0x170 && !(sprite_flags4[k] & 0x20)) {
+  int xt = (g_ram[kRam_Features0] & kFeatures0_ExtendScreen64) ? 0x40 : 0;
+
+  if ((uint16)(x + 0x40 + xt) >= (0x170 + xt * 2) ||
+      (uint16)(y + 0x40) >= 0x170 && !(sprite_flags4[k] & 0x20)) {
     sprite_pause[k]++;
     if (!(sprite_defl_bits[k] & 0x80))
       Sprite_KillSelf(k);
@@ -3812,7 +3815,10 @@
   uint16 bak0 = BG2HOFS_copy2;
   uint8 bak1 = byte_7E069E[1];
   byte_7E069E[1] = 0xff;
-  for (int i = 21; i >= 0; i--) {
+
+  int xt = (g_ram[kRam_Features0] & kFeatures0_ExtendScreen64) ? 0x40 : 0;
+  BG2HOFS_copy2 -= xt;
+  for (int i = 21 + (xt >> 3); i >= 0; i--) {
     Sprite_ActivateWhenProximal();
     BG2HOFS_copy2 += 16;
   }
@@ -3835,8 +3841,9 @@
 
 void Sprite_ActivateWhenProximal() {  // 89c5bb
   if (byte_7E069E[1]) {
-    uint16 x = BG2HOFS_copy2 + (sign8(byte_7E069E[1]) ? -0x10 : 0x110);
-    uint16 y = BG2VOFS_copy2 - 48;
+    int xt = (g_ram[kRam_Features0] & kFeatures0_ExtendScreen64) ? 0x40 : 0;
+    uint16 x = BG2HOFS_copy2 + (sign8(byte_7E069E[1]) ? -0x10 - xt : 0x110 + xt);
+    uint16 y = BG2VOFS_copy2 - 0x30;
     for (int i = 21; i >= 0; i--, y += 16)
       Sprite_Overworld_ProximityMotivatedLoad(x, y);
   }
@@ -3844,9 +3851,10 @@
 
 void Sprite_ActivateWhenProximalBig() {  // 89c5fa
   if (byte_7E069E[0]) {
-    uint16 x = BG2HOFS_copy2 - 48;
+    int xt = (g_ram[kRam_Features0] & kFeatures0_ExtendScreen64) ? 0x40 : 0;
+    uint16 x = BG2HOFS_copy2 - 0x30 - xt;
     uint16 y = BG2VOFS_copy2 + (sign8(byte_7E069E[0]) ? -0x10 : 0x110);
-    for (int i = 21; i >= 0; i--, x += 16)
+    for (int i = 21 + (xt >> 3); i >= 0; i--, x += 16)
       Sprite_Overworld_ProximityMotivatedLoad(x, y);
   }
 }
--- a/types.h
+++ b/types.h
@@ -3,6 +3,13 @@
 #include <stdbool.h>
 #pragma once
 
+// Build time config options
+enum {
+  kEnableLargeScreen = 1,
+  // How much extra spacing to add on the sides
+  kPpuExtraLeftRight = kEnableLargeScreen ? 71 : 0,
+};
+
 typedef uint8_t uint8;
 typedef int8_t int8;
 typedef uint16_t uint16;
@@ -33,6 +40,8 @@
 static FORCEINLINE uint8 abs8(uint8 t) { return sign8(t) ? -t : t; }
 static FORCEINLINE int IntMin(int a, int b) { return a < b ? a : b; }
 static FORCEINLINE int IntMax(int a, int b) { return a > b ? a : b; }
+static FORCEINLINE uint UintMin(uint a, uint b) { return a < b ? a : b; }
+static FORCEINLINE uint UintMax(uint a, uint b) { return a > b ? a : b; }
 
 #define BYTE(x) (*(uint8*)&(x))
 #define HIBYTE(x) (((uint8*)&(x))[1])
--- a/zelda3.ini
+++ b/zelda3.ini
@@ -3,6 +3,10 @@
 Autosave = 0
 DisplayPerfInTitle = 0
 
+# Extended aspect ratio, either 16:9 or 16:10.
+# Add ", unchanged_sprites" to avoid changing sprite spawn/die behavior. Without this
+# replays will be incompatible
+# ExtendedAspectRatio = 16:9
 
 [Graphics]
 # Fullscreen mode (0=windowed, 1=desktop fullscreen, 2=fullscreen w/mode change)
--- a/zelda_cpu_infra.c
+++ b/zelda_cpu_infra.c
@@ -17,6 +17,7 @@
 Snes *g_snes;
 Cpu *g_cpu;
 uint8 g_emulated_ram[0x20000];
+uint32 g_wanted_zelda_features;
 
 void SaveLoadSlot(int cmd, int which);
 
@@ -695,16 +696,23 @@
 
     // This is whether APUI00 is true or false, this is used by the ancilla code.
     uint8 apui00 = g_zenv.player->port_to_snes[0] != 0;
-    if (apui00 != g_ram[0x648]) {
-      g_emulated_ram[0x648] = g_ram[0x648] = apui00;
+    if (apui00 != g_ram[kRam_APUI00]) {
+      g_emulated_ram[kRam_APUI00] = g_ram[kRam_APUI00] = apui00;
       StateRecorder_RecordPatchByte(&state_recorder, 0x648, &apui00, 1);
     }
 
-    // Whenever we're no longer replaying, we'll remember what bugs were fixed,
-    // but only if game is initialized.
-    if (g_ram[kRam_BugsFixed] < kBugFix_Latest && animated_tile_data_src != 0) {
-      g_emulated_ram[kRam_BugsFixed] = g_ram[kRam_BugsFixed] = kBugFix_Latest;
-      StateRecorder_RecordPatchByte(&state_recorder, kRam_BugsFixed, &g_ram[kRam_BugsFixed], 1);
+    if (animated_tile_data_src != 0) {
+      // Whenever we're no longer replaying, we'll remember what bugs were fixed,
+      // but only if game is initialized.
+      if (g_ram[kRam_BugsFixed] < kBugFix_Latest) {
+        g_emulated_ram[kRam_BugsFixed] = g_ram[kRam_BugsFixed] = kBugFix_Latest;
+        StateRecorder_RecordPatchByte(&state_recorder, kRam_BugsFixed, &g_ram[kRam_BugsFixed], 1);
+      }
+
+      if (g_ram[kRam_Features0] != g_wanted_zelda_features) {
+        g_emulated_ram[kRam_Features0] = g_ram[kRam_Features0] = g_wanted_zelda_features;
+        StateRecorder_RecordPatchByte(&state_recorder, kRam_Features0, &g_ram[kRam_Features0], 1);
+      }
     }
   }
 
@@ -721,7 +729,8 @@
     g_emulated_ram[kRam_CrystalRotateCounter] = g_ram[kRam_CrystalRotateCounter];
   }
   
-  if (snes == NULL) {
+  if (snes == NULL || g_ram[kRam_Features0] != 0) {
+    // can't compare against real impl when running with extra features.
     ZeldaRunFrame(input_state, run_what);
     return turbo;
   }
--- a/zelda_rtl.c
+++ b/zelda_rtl.c
@@ -161,6 +161,26 @@
   c->rep_count--;
 }
 
+void ConfigurePpuSideSpace() {
+  // Let PPU impl know about the maximum allowed extra space on the sides
+  int extra_right = 0, extra_left = 0;
+//  printf("main %d, sub %d  (%d, %d, %d)\n", main_module_index, submodule_index, BG2HOFS_copy2, room_bounds_x.v[2 | (quadrant_fullsize_x >> 1)], quadrant_fullsize_x >> 1);
+  int mod = main_module_index;
+  if (mod == 14)
+    mod = saved_module_for_menu;
+  if (mod == 9) {
+    extra_left = BG2HOFS_copy2 - ow_scroll_vars0.xstart;
+    extra_right = ow_scroll_vars0.xend - BG2HOFS_copy2;
+  } else if (mod == 7) {
+    int qm = quadrant_fullsize_x >> 1;
+    extra_left = IntMax(BG2HOFS_copy2 - room_bounds_x.v[qm], 0);
+    extra_right = IntMax(room_bounds_x.v[qm + 2] - BG2HOFS_copy2, 0);
+  } else if (mod == 20) {
+    extra_left = kPpuExtraLeftRight, extra_right = kPpuExtraLeftRight;
+  }
+  PpuSetExtraSideSpace(g_zenv.ppu, extra_left, extra_right);
+}
+
 bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
   SimpleHdma hdma_chans[2];
 
@@ -182,6 +202,9 @@
     else
       PpuSetMode7PerspectiveCorrection(g_zenv.ppu, 0, 0);
   }
+
+  if (g_zenv.ppu->extraLeftRight != 0)
+    ConfigurePpuSideSpace();
 
   for (int i = 0; i < 225; i++) {
     if (i == 128 && irq_flag) {
--- a/zelda_rtl.h
+++ b/zelda_rtl.h
@@ -47,10 +47,22 @@
 #define movable_block_datas ((MovableBlockData*)(g_ram+0xf940))
 #define oam_buf ((OamEnt*)(g_ram+0x800))
 
+
+typedef struct RoomBounds {
+  union {
+    struct { uint16 a0, b0, a1, b1; };
+    uint16 v[4];
+  };
+} RoomBounds;
+#define room_bounds_y (*(RoomBounds*)(g_ram+0x600))
+#define room_bounds_x (*(RoomBounds*)(g_ram+0x608))
+
+
 typedef struct OwScrollVars {
   uint16 ystart, yend, xstart, xend;
 } OwScrollVars;
 
+
 #define ow_scroll_vars0 (*(OwScrollVars*)(g_ram+0x600))
 #define ow_scroll_vars1 (*(OwScrollVars*)(g_ram+0x608))
 
@@ -90,6 +102,7 @@
   kRam_APUI00 = 0x648,
   kRam_CrystalRotateCounter = 0x649,
   kRam_BugsFixed = 0x64a,
+  kRam_Features0 = 0x64c,
 };
 
 enum {
@@ -97,8 +110,12 @@
   kBugFix_PolyRenderer = 1,
   kBugFix_AncillaOverwrites = 1,
   kBugFix_Latest = 1,
+
+  // kRam_Features0
+  kFeatures0_ExtendScreen64 = 1,
 };
 
+extern uint32 g_wanted_zelda_features;
 
 #define scratch_0 (*(uint16*)(g_ram+0x72))
 #define scratch_1 (*(uint16*)(g_ram+0x74))