shithub: zelda3

Download patch

ref: d3138f9e13a3535c1c42aab37b592919a48df4d8
parent: bd9ecf21506e8bd9bedb37a1c86775c01186b9ed
author: Snesrev <snesrev@protonmail.com>
date: Sat Oct 1 13:32:06 EDT 2022

Simplify PPU code / interactions

--- a/attract.c
+++ b/attract.c
@@ -452,8 +452,6 @@
   COLDATA_copy2 = 0x85;
   CGWSEL_copy = 0x10;
   CGADSUB_copy = 0xa3;
-  zelda_ppu_write(WBGLOG, 0);
-  zelda_ppu_write(WOBJLOG, 0);
 
   music_control = 6;
   attract_legend_flag++;
@@ -516,9 +514,7 @@
   zelda_ppu_write(BG2SC, 0x3);
   CGWSEL_copy = 0x80;
   CGADSUB_copy = 0x21;
-  zelda_ppu_write(BGMODE, 7);
   BGMODE_copy = 7;
-  zelda_ppu_write(M7SEL, 0x80);
   WorldMap_LoadLightWorldMap();
   M7Y_copy = 0xed;
   M7X_copy = 0x100;
@@ -532,7 +528,6 @@
 }
 
 void AttractScene_ThroneRoom() {  // 8cef4e
-  zelda_snes_dummy_write(HDMAEN, 0);
   HDMAEN_copy = 0;
   CGWSEL_copy = 2;
   CGADSUB_copy = 0x20;
@@ -712,7 +707,6 @@
     }
   } else {
     EnableForceBlank();
-    zelda_ppu_write(BGMODE, 9);
     BGMODE_copy = 9;
     EraseTileMaps_normal();
     attract_sequence++;
--- a/dungeon.c
+++ b/dungeon.c
@@ -5915,7 +5915,6 @@
 void Dungeon_SetAttrForActivatedWaterOff() {  // 81ef93
   CGWSEL_copy = 2;
   CGADSUB_copy = 0x32;
-  zelda_ppu_write(TS, 0);
   TS_copy = 0;
   W12SEL_copy = 0;
   dung_hdr_collision = 0;
@@ -6491,11 +6490,8 @@
 void LoadOWMusicIfNeeded() {  // 82854c
   if (!flag_which_music_type)
     return;
-  zelda_snes_dummy_write(NMITIMEN, 0);
-  zelda_snes_dummy_write(HDMAEN, 0);
   flag_which_music_type = 0;
   LoadOverworldSongs();
-  zelda_snes_dummy_write(NMITIMEN, 0x81);
 }
 
 void Module07_Dungeon() {  // 8287a2
@@ -7853,7 +7849,6 @@
   Dungeon_LoadAttributeTable();
   subsubmodule_index = bak + 1;
   misc_sprites_graphics_index = 10;
-  zelda_ppu_write(OBSEL, 2);
   InitializeTilesets();
   palette_sp6 = 10;
   Dungeon_LoadPalettes();
@@ -7879,11 +7874,8 @@
   } else {
     if (flag_which_music_type)
       return;
-    zelda_snes_dummy_write(NMITIMEN, 0);
-    zelda_snes_dummy_write(HDMAEN, 0);
     flag_which_music_type = 1;
     LoadDungeonSongs();
-    zelda_snes_dummy_write(NMITIMEN, 0x81);
   }
 }
 
@@ -8273,7 +8265,6 @@
 // This gets called when entering a dungeon from ow.
 void Dungeon_LoadAndDrawRoom() {  // 82c57b
   int bak = HDMAEN_copy;
-  zelda_snes_dummy_write(HDMAEN, 0);
   HDMAEN_copy = 0;
   Dungeon_LoadRoom();
   overworld_screen_transition = 0;
--- a/ending.c
+++ b/ending.c
@@ -138,7 +138,6 @@
   TS_copy = 0;
   Intro_InitializeBackgroundSettings();
   CGWSEL_copy = 0x20;
-  zelda_ppu_write(OBSEL, 2);
   load_chr_halfslot_even_odd = 20;
   Graphics_LoadChrHalfSlot();
   load_chr_halfslot_even_odd = 0;
@@ -355,9 +354,7 @@
     break;
   case 2:  //
     EnableForceBlank();
-    zelda_snes_dummy_write(NMITIMEN, 0);
     LoadCreditsSongs();
-    zelda_snes_dummy_write(NMITIMEN, 0x81);
     dungeon_room_index = 0x189;
     EraseTileMaps_normal();
     Palette_RevertTranslucencySwap();
@@ -488,14 +485,11 @@
 }
 
 void Intro_InitializeBackgroundSettings() {  // 82c500
-  zelda_ppu_write(SETINI, 0);
   BGMODE_copy = 9;
   MOSAIC_copy = 0;
   zelda_ppu_write(BG1SC, 0x13);
   zelda_ppu_write(BG2SC, 3);
   zelda_ppu_write(BG3SC, 0x63);
-  zelda_ppu_write(BG12NBA, 0x22);
-  zelda_ppu_write(BG34NBA, 7);
   CGADSUB_copy = 32;
   COLDATA_copy0 = 32;
   COLDATA_copy1 = 64;
@@ -575,7 +569,6 @@
 void Intro_InitializeMemory_darken() {  // 8cc1f5
   EnableForceBlank();
   EraseTileMaps_normal();
-  zelda_ppu_write(OBSEL, 2);
   main_tile_theme_index = 35;
   sprite_graphics_index = 125;
   aux_tile_theme_index = 81;
--- a/load_gfx.c
+++ b/load_gfx.c
@@ -877,15 +877,13 @@
 void Attract_LoadBG3GFX() {  // 80e36d
   // load 2bpp gfx for attract images
   zelda_ppu_write(VMAIN, 0x80);
-  zelda_ppu_write(VMADDL, 0);
-  zelda_ppu_write(VMADDH, 0x78);
+  zelda_ppu_write_word(VMADDL, 0x7800);
   DecompAndUpload2bpp(0x67);
 }
 
 void LoadCommonSprites_2() {  // 80e384
   zelda_ppu_write(VMAIN, 0x80);
-  zelda_ppu_write(VMADDL, 0);
-  zelda_ppu_write(VMADDH, 0x44);
+  zelda_ppu_write_word(VMADDL, 0x4400);
   LoadCommonSprites();
 }
 
@@ -951,7 +949,6 @@
 }
 
 void TransferFontToVRAM() {  // 80e556
-  zelda_ppu_write(OBSEL, 2);
   zelda_ppu_write(VMAIN, 0x80);
   zelda_ppu_write_word(VMADDL, 0x7000);
   const uint16 *src = GetFontPtr();
--- a/messaging.c
+++ b/messaging.c
@@ -949,8 +949,6 @@
     death_var4 = 0;
     death_var5 = 0;
     buffer_for_playing_songs = 0;
-    zelda_snes_dummy_write(NMITIMEN, 0);
-    zelda_snes_dummy_write(HDMAEN, 0);
     BG1HOFS_copy2 = 0;
     BG2HOFS_copy2 = 0;
     BG3HOFS_copy2 = 0;
@@ -964,7 +962,6 @@
     memset(save_dung_info, 0, 256 * 5);
     flag_which_music_type = 0;
     LoadOverworldSongs();
-    zelda_snes_dummy_write(NMITIMEN, 0x81);
   }
 }
 
@@ -1229,9 +1226,7 @@
   sound_effect_2 = 16;
   sound_effect_ambient = 5;
   music_control = 0xf2;
-  zelda_ppu_write(BGMODE, 7);
   BGMODE_copy = 7;
-  zelda_ppu_write(M7SEL, 0x80);
 }
 
 void WorldMap_LoadLightWorldMap() {  // 8aba30
@@ -1343,7 +1338,6 @@
 void Attract_SetUpConclusionHDMA() {  // 8abc33
   HdmaSetup(0xABDDD, 0xABDDD, 0x42, (uint8)M7A, (uint8)M7D, 0);
   HDMAEN_copy = 0x80;
-  zelda_ppu_write(BGMODE, 9);
   BGMODE_copy = 9;
   nmi_disable_core_updates = 0;
 }
@@ -1375,14 +1369,6 @@
   WOBJSEL_copy = 0;
   TMW_copy = 0;
   TSW_copy = 0;
-  zelda_ppu_write(M7B, 0);
-  zelda_ppu_write(M7B, 0);
-  zelda_ppu_write(M7C, 0);
-  zelda_ppu_write(M7C, 0);
-  zelda_ppu_write(M7X, 0);
-  zelda_ppu_write(M7X, 1);
-  zelda_ppu_write(M7Y, 0);
-  zelda_ppu_write(M7Y, 1);
 
   if (main_module_index == 20) {
     HdmaSetup(0xABDDD, 0xABDDD, 0x42, (uint8)M7A, (uint8)M7D, 0);
@@ -1665,7 +1651,6 @@
 
 void Module0E_03_01_00_PrepMapGraphics() {  // 8ae0e4
   uint8 hdmaen_bak = HDMAEN_copy;
-  zelda_snes_dummy_write(HDMAEN, 0);
   HDMAEN_copy = 0;
   mapbak_main_tile_theme_index = main_tile_theme_index;
   mapbak_sprite_graphics_index = sprite_graphics_index;
@@ -2205,7 +2190,6 @@
 
 void DungeonMap_RecoverGFX() {  // 8aef19
   uint8 hdmaen_bak = HDMAEN_copy;
-  zelda_snes_dummy_write(HDMAEN, 0);
   HDMAEN_copy = 0;
   EraseTileMaps_normal();
 
--- a/misc.c
+++ b/misc.c
@@ -549,7 +549,6 @@
   byte_7E0379 = 0;
   byte_7E03FD = 0;
   EraseTileMaps_normal();
-  zelda_ppu_write(OBSEL, 2);
   LoadDefaultGraphics();
   Sprite_LoadGraphicsProperties();
   Init_LoadDefaultTileAttr();
--- a/nmi.c
+++ b/nmi.c
@@ -65,6 +65,48 @@
   }
 }
 
+void WritePpuRegisters() {
+  zelda_ppu_write(W12SEL, W12SEL_copy);
+  zelda_ppu_write(W34SEL, W34SEL_copy);
+  zelda_ppu_write(WOBJSEL, WOBJSEL_copy);
+  zelda_ppu_write(CGWSEL, CGWSEL_copy);
+  zelda_ppu_write(CGADSUB, CGADSUB_copy);
+  zelda_ppu_write(COLDATA, COLDATA_copy0);
+  zelda_ppu_write(COLDATA, COLDATA_copy1);
+  zelda_ppu_write(COLDATA, COLDATA_copy2);
+  zelda_ppu_write(TM, TM_copy);
+  zelda_ppu_write(TS, TS_copy);
+  zelda_ppu_write(TMW, TMW_copy);
+  zelda_ppu_write(TSW, TSW_copy);
+  zelda_ppu_write(BG1HOFS, BG1HOFS_copy);
+  zelda_ppu_write(BG1HOFS, BG1HOFS_copy >> 8);
+  zelda_ppu_write(BG1VOFS, BG1VOFS_copy);
+  zelda_ppu_write(BG1VOFS, BG1VOFS_copy >> 8);
+  zelda_ppu_write(BG2HOFS, BG2HOFS_copy);
+  zelda_ppu_write(BG2HOFS, BG2HOFS_copy >> 8);
+  zelda_ppu_write(BG2VOFS, BG2VOFS_copy);
+  zelda_ppu_write(BG2VOFS, BG2VOFS_copy >> 8);
+  zelda_ppu_write(BG3HOFS, BG3HOFS_copy2);
+  zelda_ppu_write(BG3HOFS, BG3HOFS_copy2 >> 8);
+  zelda_ppu_write(BG3VOFS, BG3VOFS_copy2);
+  zelda_ppu_write(BG3VOFS, BG3VOFS_copy2 >> 8);
+  zelda_ppu_write(INIDISP, INIDISP_copy);
+  zelda_ppu_write(MOSAIC, MOSAIC_copy);
+  zelda_ppu_write(BGMODE, BGMODE_copy);
+  if ((BGMODE_copy & 7) == 7) {
+    zelda_ppu_write(M7B, 0);
+    zelda_ppu_write(M7B, 0);
+    zelda_ppu_write(M7C, 0);
+    zelda_ppu_write(M7C, 0);
+    zelda_ppu_write(M7X, M7X_copy);
+    zelda_ppu_write(M7X, M7X_copy >> 8);
+    zelda_ppu_write(M7Y, M7Y_copy);
+    zelda_ppu_write(M7Y, M7Y_copy >> 8);
+  }
+  zelda_ppu_write(BG12NBA, 0x22);
+  zelda_ppu_write(BG34NBA, 7);
+}
+
 void Interrupt_NMI(uint16 joypad_input) {  // 8080c9
   if (music_control == 0) {
     if (zelda_apu_read(APUI00) == last_music_control)
@@ -92,8 +134,6 @@
   sound_effect_1 = 0;
   sound_effect_2 = 0;
 
-  zelda_ppu_write(INIDISP, 0x80);
-  zelda_snes_dummy_write(HDMAEN, 0);
   if (!nmi_boolean) {
     nmi_boolean = true;
     NMI_DoUpdates();
@@ -101,91 +141,12 @@
   }
 
   if (is_nmi_thread_active) {
-    NMI_SwitchThread();
-  } else {
-    zelda_ppu_write(W12SEL, W12SEL_copy);
-    zelda_ppu_write(W34SEL, W34SEL_copy);
-    zelda_ppu_write(WOBJSEL, WOBJSEL_copy);
-    zelda_ppu_write(CGWSEL, CGWSEL_copy);
-    zelda_ppu_write(CGADSUB, CGADSUB_copy);
-    zelda_ppu_write(COLDATA, COLDATA_copy0);
-    zelda_ppu_write(COLDATA, COLDATA_copy1);
-    zelda_ppu_write(COLDATA, COLDATA_copy2);
-    zelda_ppu_write(TM, TM_copy);
-    zelda_ppu_write(TS, TS_copy);
-    zelda_ppu_write(TMW, TMW_copy);
-    zelda_ppu_write(TSW, TSW_copy);
-    zelda_ppu_write(BG1HOFS, BG1HOFS_copy);
-    zelda_ppu_write(BG1HOFS, BG1HOFS_copy >> 8);
-    zelda_ppu_write(BG1VOFS, BG1VOFS_copy);
-    zelda_ppu_write(BG1VOFS, BG1VOFS_copy >> 8);
-    zelda_ppu_write(BG2HOFS, BG2HOFS_copy);
-    zelda_ppu_write(BG2HOFS, BG2HOFS_copy >> 8);
-    zelda_ppu_write(BG2VOFS, BG2VOFS_copy);
-    zelda_ppu_write(BG2VOFS, BG2VOFS_copy >> 8);
-    zelda_ppu_write(BG3HOFS, BG3HOFS_copy2);
-    zelda_ppu_write(BG3HOFS, BG3HOFS_copy2 >> 8);
-    zelda_ppu_write(BG3VOFS, BG3VOFS_copy2);
-    zelda_ppu_write(BG3VOFS, BG3VOFS_copy2 >> 8);
-    zelda_ppu_write(MOSAIC, MOSAIC_copy);
-    zelda_ppu_write(BGMODE, BGMODE_copy);
-    if ((BGMODE_copy & 7) == 7) {
-      zelda_ppu_write(M7B, 0);
-      zelda_ppu_write(M7B, 0);
-      zelda_ppu_write(M7C, 0);
-      zelda_ppu_write(M7C, 0);
-      zelda_ppu_write(M7X, M7X_copy);
-      zelda_ppu_write(M7X, M7X_copy >> 8);
-      zelda_ppu_write(M7Y, M7Y_copy);
-      zelda_ppu_write(M7Y, M7Y_copy >> 8);
-    }
-    //if (irq_flag) {
-    //  snes_dummy_read(g_snes, TIMEUP);
-    //  snes_dummy_write(g_snes, VTIMEL, 0x80);
-    //  snes_dummy_write(g_snes, VTIMEH, 0);
-    //  snes_dummy_write(g_snes, HTIMEL, 0);
-    //  snes_dummy_write(g_snes, HTIMEH, 0);
-    //  snes_dummy_write(g_snes, NMITIMEN, 0xa1);
-    //}
-    zelda_ppu_write(INIDISP, INIDISP_copy);
-    zelda_snes_dummy_write(HDMAEN, HDMAEN_copy);
+    NMI_UpdateIRQGFX();
+    thread_other_stack = (thread_other_stack != 0x1f31) ? 0x1f31 : 0x1f2;
   }
+  WritePpuRegisters();
 }
 
-void NMI_SwitchThread() {  // 80822d
-  NMI_UpdateIRQGFX();
-  //zelda_snes_dummy_write(VTIMEL, virq_trigger);
-  //zelda_snes_dummy_write(VTIMEH, 0);
-  //zelda_snes_dummy_write(NMITIMEN, 0xa1);
-  zelda_ppu_write(W12SEL, W12SEL_copy);
-  zelda_ppu_write(W34SEL, W34SEL_copy);
-  zelda_ppu_write(WOBJSEL, WOBJSEL_copy);
-  zelda_ppu_write(CGWSEL, CGWSEL_copy);
-  zelda_ppu_write(CGADSUB, CGADSUB_copy);
-  zelda_ppu_write(COLDATA, COLDATA_copy0);
-  zelda_ppu_write(COLDATA, COLDATA_copy1);
-  zelda_ppu_write(COLDATA, COLDATA_copy2);
-  zelda_ppu_write(TM, TM_copy);
-  zelda_ppu_write(TS, TS_copy);
-  zelda_ppu_write(TMW, TMW_copy);
-  zelda_ppu_write(TSW, TSW_copy);
-  zelda_ppu_write(BG1HOFS, BG1HOFS_copy);
-  zelda_ppu_write(BG1HOFS, BG1HOFS_copy >> 8);
-  zelda_ppu_write(BG1VOFS, BG1VOFS_copy);
-  zelda_ppu_write(BG1VOFS, BG1VOFS_copy >> 8);
-  zelda_ppu_write(BG2HOFS, BG2HOFS_copy);
-  zelda_ppu_write(BG2HOFS, BG2HOFS_copy >> 8);
-  zelda_ppu_write(BG2VOFS, BG2VOFS_copy);
-  zelda_ppu_write(BG2VOFS, BG2VOFS_copy >> 8);
-  zelda_ppu_write(BG3HOFS, BG3HOFS_copy2);
-  zelda_ppu_write(BG3HOFS, BG3HOFS_copy2 >> 8);
-  zelda_ppu_write(BG3VOFS, BG3VOFS_copy2);
-  zelda_ppu_write(BG3VOFS, BG3VOFS_copy2 >> 8);
-  zelda_ppu_write(INIDISP, INIDISP_copy);
-  zelda_snes_dummy_write(HDMAEN, HDMAEN_copy);
-  thread_other_stack = (thread_other_stack != 0x1f31) ? 0x1f31 : 0x1f2;
-}
-
 void NMI_ReadJoypads(uint16 joypad_input) {  // 8083d1
   uint16 both = joypad_input;
   uint16 reversed = 0;
@@ -249,8 +210,7 @@
   flag_update_hud_in_nmi = 0;
   flag_update_cgram_in_nmi = 0;
 
-  memcpy(g_zenv.ppu->oam, &g_ram[0x800], 0x200);
-  memcpy(g_zenv.ppu->highOam, &g_ram[0xa00], 0x20);
+  memcpy(g_zenv.ppu->oam, &g_ram[0x800], 0x220);
 
   if (nmi_load_bg_from_vram) {
     const uint8 *p;
--- a/nmi.h
+++ b/nmi.h
@@ -7,7 +7,6 @@
 void CopyToVramVertical(uint32 dstv, const uint8 *src, int len);
 void CopyToVramLow(const uint8 *src, uint32 addr, int num);
 void Interrupt_NMI(uint16 joypad_input);
-void NMI_SwitchThread();
 void NMI_ReadJoypads(uint16 joypad_input);
 void NMI_DoUpdates();
 void NMI_UploadTilemap();
--- a/select_file.c
+++ b/select_file.c
@@ -184,7 +184,6 @@
 }
 
 void LoadFileSelectGraphics() {  // 80e4e9
-  zelda_ppu_write(OBSEL, 2);
   zelda_ppu_write(VMAIN, 0x80);
   zelda_ppu_write_word(VMADDL, 0x5000);
 
--- a/snes/ppu.c
+++ b/snes/ppu.c
@@ -20,9 +20,19 @@
 static int ppu_getPixelForMode7(Ppu* ppu, int x, int layer, bool priority);
 static bool ppu_getWindowState(Ppu* ppu, int layer, int x);
 static bool ppu_evaluateSprites(Ppu* ppu, int line);
-static uint16_t ppu_getVramRemap(Ppu* ppu);
 static void PpuDrawWholeLine(Ppu *ppu, uint y);
 
+#define IS_SCREEN_ENABLED(ppu, sub, layer) (ppu->screenEnabled[sub] & (1 << layer))
+#define IS_SCREEN_WINDOWED(ppu, sub, layer) (ppu->screenWindowed[sub] & (1 << layer))
+#define IS_MOSAIC_ENABLED(ppu, layer) ((ppu->mosaicEnabled & (1 << layer)))
+#define GET_WINDOW_FLAGS(ppu, layer) (ppu->windowsel >> (layer * 4))
+enum {
+  kWindow1Inversed = 1,
+  kWindow1Enabled = 2,
+  kWindow2Inversed = 4,
+  kWindow2Enabled = 8,
+};
+
 Ppu* ppu_init() {
   Ppu* ppu = (Ppu * )malloc(sizeof(Ppu));
   ppu->extraLeftRight = kPpuExtraLeftRight;
@@ -43,28 +53,18 @@
   ppu->vramPointer = 0;
   ppu->vramIncrementOnHigh = false;
   ppu->vramIncrement = 1;
-  ppu->vramRemapMode = 0;
-  ppu->vramReadBuffer = 0;
   memset(ppu->cgram, 0, sizeof(ppu->cgram));
   ppu->cgramPointer = 0;
   ppu->cgramSecondWrite = false;
   ppu->cgramBuffer = 0;
   memset(ppu->oam, 0, sizeof(ppu->oam));
-  memset(ppu->highOam, 0, sizeof(ppu->highOam));
   ppu->oamAdr = 0;
-  ppu->oamAdrWritten = 0;
-  ppu->oamInHigh = false;
-  ppu->oamInHighWritten = false;
   ppu->oamSecondWrite = false;
   ppu->oamBuffer = 0;
-  ppu->objPriority = false;
-  ppu->objTileAdr1 = 0;
-  ppu->objTileAdr2 = 0;
+  ppu->objTileAdr1 = 0x4000;
+  ppu->objTileAdr2 = 0x5000;
   ppu->objSize = 0;
   memset(&ppu->objBuffer, 0, sizeof(ppu->objBuffer));
-  ppu->timeOver = false;
-  ppu->rangeOver = false;
-  ppu->objInterlace_always_zero = false;
   for(int i = 0; i < 4; i++) {
     ppu->bgLayer[i].hScroll = 0;
     ppu->bgLayer[i].vScroll = 0;
@@ -72,22 +72,15 @@
     ppu->bgLayer[i].tilemapHigher = false;
     ppu->bgLayer[i].tilemapAdr = 0;
     ppu->bgLayer[i].tileAdr = 0;
-    ppu->bgLayer[i].bigTiles_always_zero = false;
-    ppu->bgLayer[i].mosaicEnabled = false;
   }
   ppu->scrollPrev = 0;
   ppu->scrollPrev2 = 0;
   ppu->mosaicSize = 1;
-  ppu->mosaicStartLine = 1;
-  for(int i = 0; i < 5; i++) {
-    ppu->layer[i].screenEnabled[0] = false;
-    ppu->layer[i].screenEnabled[1] = false;
-    ppu->layer[i].screenWindowed[0] = false;
-    ppu->layer[i].screenWindowed[1] = false;
-  }
+  ppu->screenEnabled[0] = ppu->screenEnabled[1] = 0;
+  ppu->screenWindowed[0] = ppu->screenWindowed[1] = 0;
   memset(ppu->m7matrix, 0, sizeof(ppu->m7matrix));
   ppu->m7prev = 0;
-  ppu->m7largeField = false;
+  ppu->m7largeField = true;
   ppu->m7charFill = false;
   ppu->m7xFlip = false;
   ppu->m7yFlip = false;
@@ -94,13 +87,7 @@
   ppu->m7extBg_always_zero = false;
   ppu->m7startX = 0;
   ppu->m7startY = 0;
-  for(int i = 0; i < 6; i++) {
-    ppu->windowLayer[i].window1enabled = false;
-    ppu->windowLayer[i].window2enabled = false;
-    ppu->windowLayer[i].window1inversed = false;
-    ppu->windowLayer[i].window2inversed = false;
-    ppu->windowLayer[i].maskLogic_always_zero = 0;
-  }
+  ppu->windowsel = 0;
   ppu->window1left = 0;
   ppu->window1right = 0;
   ppu->window2left = 0;
@@ -110,7 +97,7 @@
   ppu->addSubscreen = false;
   ppu->subtractColor = false;
   ppu->halfColor = false;
-  memset(ppu->mathEnabled, 0, sizeof(ppu->mathEnabled));
+  ppu->mathEnabled = 0;
   ppu->fixedColorR = 0;
   ppu->fixedColorG = 0;
   ppu->fixedColorB = 0;
@@ -117,33 +104,24 @@
   ppu->forcedBlank = true;
   ppu->brightness = 0;
   ppu->mode = 0;
-  ppu->bg3priority = false;
-  ppu->evenFrame = false;
-  ppu->pseudoHires_always_zero = false;
-  ppu->overscan_always_zero = false;
-  ppu->frameOverscan_always_zero = false;
-  ppu->interlace_always_zero = false;
-  ppu->frameInterlace_always_zero = false;
-  ppu->directColor_always_zero = false;
-  ppu->hCount = 0;
-  ppu->vCount = 0;
-  ppu->hCountSecond = false;
-  ppu->vCountSecond = false;
-  ppu->countersLatched = false;
-  ppu->ppu1openBus = 0;
-  ppu->ppu2openBus = 0;
 }
 
 void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx) {
-  size_t s1 = offsetof(Ppu, objSize) + 1 - offsetof(Ppu, vram);
-  size_t s2 = offsetof(Ppu, mosaicModulo) - offsetof(Ppu, timeOver);
-  assert(s1 == 66619 && s2 == 174);
-  func(ctx, &ppu->vram, s1);
-  func(ctx, &ppu->objBuffer, 512);
-  func(ctx, &ppu->timeOver, s2);
+  uint8 tmp[556] = { 0 };
+
+  func(ctx, &ppu->vram, 0x8000 * 2);
+  func(ctx, tmp, 10);
+  func(ctx, &ppu->cgram, 512);
+  func(ctx, tmp, 556);
+  func(ctx, tmp, 520);
+  for (int i = 0; i < 4; i++) {
+    func(ctx, tmp, 4);
+    func(ctx, &ppu->bgLayer[i].tilemapWider, 4);
+    func(ctx, tmp, 4);
+  }
+  func(ctx, tmp, 123);
 }
 
-
 bool PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags) {
   ppu->renderFlags = render_flags;
   bool hq = ppu->mode == 7 && !ppu->forcedBlank && 
@@ -179,11 +157,7 @@
 
 
 void ppu_runLine(Ppu *ppu, int line) {
-  if(line == 0) {
-    ppu->rangeOver = false;
-    ppu->timeOver = false;
-    ppu->evenFrame = !ppu->evenFrame;
-  } else {
+  if(line != 0) {
     if (ppu->mosaicSize != ppu->lastMosaicModulo) {
       int mod = ppu->mosaicSize;
       ppu->lastMosaicModulo = mod;
@@ -238,10 +212,10 @@
 }
 
 static void PpuWindows_Calc(PpuWindows *win, Ppu *ppu, uint layer) {
-  WindowLayer *wl = &ppu->windowLayer[layer];
   // Evaluate which spans to render based on the window settings.
   // There are at most 5 windows.
   // Algorithm from Snes9x
+  uint32 winflags = GET_WINDOW_FLAGS(ppu, layer);
   uint nr = 1;
   int window_right = 256 + (layer != 2 ? ppu->extraRightCur : 0);
   win->edges[0] = - (layer != 2 ? ppu->extraLeftCur : 0);
@@ -249,7 +223,7 @@
   uint8 window_bits = 0;
   uint i, j;
   int t;
-  bool w1_ena = wl->window1enabled && ppu->window1left <= ppu->window1right;
+  bool w1_ena = (winflags & kWindow1Enabled) && ppu->window1left <= ppu->window1right;
   if (w1_ena) {
     if (ppu->window1left > win->edges[0]) {
       win->edges[nr] = ppu->window1left;
@@ -260,7 +234,7 @@
       win->edges[++nr] = window_right;
     }
   }
-  bool w2_ena = wl->window2enabled && ppu->window2left <= ppu->window2right;
+  bool w2_ena = (winflags & kWindow2Enabled) && ppu->window2left <= ppu->window2right;
   if (w2_ena) {
     for (i = 0; i <= nr && (t = ppu->window2left) != win->edges[i]; i++) {
       if (t < win->edges[i]) {
@@ -287,7 +261,7 @@
     for (j = i; win->edges[j] != ppu->window1right + 1; j++);
     w1_bits = ((1 << (j - i)) - 1) << i;
   }
-  if (wl->window1enabled & wl->window1inversed)
+  if ((winflags & (kWindow1Enabled | kWindow1Inversed)) == (kWindow1Enabled | kWindow1Inversed))
     w1_bits = ~w1_bits;
   if (w2_ena) {
     for (i = 0; win->edges[i] != ppu->window2left; i++);
@@ -294,7 +268,7 @@
     for (j = i; win->edges[j] != ppu->window2right + 1; j++);
     w2_bits = ((1 << (j - i)) - 1) << i;
   }
-  if (wl->window2enabled & wl->window2inversed)
+  if ((winflags & (kWindow2Enabled | kWindow2Inversed)) == (kWindow2Enabled | kWindow2Inversed))
     w2_bits = ~w2_bits;
   win->bits = w1_bits | w2_bits;
 }
@@ -309,11 +283,10 @@
   if ((bits & (0x80808080 >> i)) && z > dstz[i]) dstz[i] = z + pixel; } 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];
-  if (!layerp->screenEnabled[sub])
+  if (!IS_SCREEN_ENABLED(ppu, sub, layer))
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
+  IS_SCREEN_WINDOWED(ppu, sub, layer) ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y += bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -408,11 +381,10 @@
   if (pixel && z > dstz[i]) dstz[i] = z + pixel; } while (0)
 #define READ_BITS(ta, tile) (addr = &ppu->vram[(ta) + (tile) * 8 & 0x7fff], addr[0])
   enum { kPaletteShift = 8 };
-  Layer *layerp = &ppu->layer[layer];
-  if (!layerp->screenEnabled[sub])
+  if (!IS_SCREEN_ENABLED(ppu, sub, layer))
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
+  IS_SCREEN_WINDOWED(ppu, sub, layer) ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y += bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -506,11 +478,10 @@
 #define GET_PIXEL_HFLIP(i) pixel = (bits >> 7) & 1 | (bits >> 14) & 2 | (bits >> 21) & 4 | (bits >> 28) & 8
 #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];
-  if (!layerp->screenEnabled[sub])
+  if (!IS_SCREEN_ENABLED(ppu, sub, layer))
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
+  IS_SCREEN_WINDOWED(ppu, sub, layer) ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y = ppu->mosaicModulo[y] + bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -566,11 +537,10 @@
 #define GET_PIXEL_HFLIP(i) pixel = (bits >> 7) & 1 | (bits >> 14) & 2
 #define READ_BITS(ta, tile) (addr = &ppu->vram[((ta) + (tile) * 8) & 0x7fff], addr[0])
   enum { kPaletteShift = 8 };
-  Layer *layerp = &ppu->layer[layer];
-  if (!layerp->screenEnabled[sub])
+  if (!IS_SCREEN_ENABLED(ppu, sub, layer))
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
+  IS_SCREEN_WINDOWED(ppu, sub, layer) ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
   BgLayer *bglayer = &ppu->bgLayer[layer];
   y = ppu->mosaicModulo[y] + bglayer->vScroll;
   int sc_offs = bglayer->tilemapAdr + (((y >> 3) & 0x1f) << 5);
@@ -626,11 +596,11 @@
 #define SPRITE_PRIO_TO_PRIO_HI(prio) ((prio) * 4 + 2)
 
 static void PpuDrawSprites(Ppu *ppu, uint y, uint sub, bool clear_backdrop) {
-  Layer *layerp = &ppu->layer[4];
-  if (!layerp->screenEnabled[sub])
+  int layer = 4;
+  if (!IS_SCREEN_ENABLED(ppu, sub, layer))
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, 4) : PpuWindows_Clear(&win, ppu, 4);
+  IS_SCREEN_WINDOWED(ppu, sub, layer) ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
   for (size_t windex = 0; windex < win.nr; windex++) {
     if (win.bits & (1 << windex))
       continue;  // layer is disabled for this window part
@@ -652,11 +622,10 @@
 // Assumes it's drawn on an empty backdrop
 static void PpuDrawBackground_mode7(Ppu *ppu, uint y, bool sub, PpuZbufType z) {
   int layer = 0;
-  Layer *layerp = &ppu->layer[layer];
-  if (!layerp->screenEnabled[sub])
+  if (!IS_SCREEN_ENABLED(ppu, sub, layer))
     return;  // layer is completely hidden
   PpuWindows win;
-  layerp->screenWindowed[sub] ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
+  IS_SCREEN_WINDOWED(ppu, sub, layer) ? PpuWindows_Calc(&win, ppu, layer) : PpuWindows_Clear(&win, ppu, layer);
 
   // expand 13-bit values to signed values
   int hScroll = ((int16_t)(ppu->m7matrix[6] << 3)) >> 3;
@@ -667,7 +636,7 @@
   int clippedV = vScroll - yCenter;
   clippedH = (clippedH & 0x2000) ? (clippedH | ~1023) : (clippedH & 1023);
   clippedV = (clippedV & 0x2000) ? (clippedV | ~1023) : (clippedV & 1023);
-  bool mosaic_enabled = ppu->bgLayer[0].mosaicEnabled && ppu->mosaicSize > 1;
+  bool mosaic_enabled = IS_MOSAIC_ENABLED(ppu, 0);
   if (mosaic_enabled)
     y = ppu->mosaicModulo[y];
   uint32 ry = ppu->m7yFlip ? 255 - y : y;
@@ -825,10 +794,6 @@
     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
 }
 
@@ -852,17 +817,17 @@
     if (ppu->lineHasSprites)
       PpuDrawSprites(ppu, y, sub, true);
 
-    if (ppu->bgLayer[0].mosaicEnabled && ppu->mosaicSize > 1)
+    if (IS_MOSAIC_ENABLED(ppu, 0))
       PpuDrawBackground_4bpp_mosaic(ppu, y, sub, 0, 0xc000, 0x8000);
     else
       PpuDrawBackground_4bpp(ppu, y, sub, 0, 0xc000, 0x8000);
 
-    if (ppu->bgLayer[1].mosaicEnabled && ppu->mosaicSize > 1)
+    if (IS_MOSAIC_ENABLED(ppu, 1))
       PpuDrawBackground_4bpp_mosaic(ppu, y, sub, 1, 0xb100, 0x7100);
     else
       PpuDrawBackground_4bpp(ppu, y, sub, 1, 0xb100, 0x7100);
 
-    if (ppu->bgLayer[2].mosaicEnabled && ppu->mosaicSize > 1)
+    if (IS_MOSAIC_ENABLED(ppu, 2))
       PpuDrawBackground_2bpp_mosaic(ppu, y, sub, 2, 0xf200, 0x1200);
     else
       PpuDrawBackground_2bpp(ppu, y, sub, 2, 0xf200, 0x1200);
@@ -895,16 +860,13 @@
   PpuDrawBackgrounds(ppu, y, false);
 
   // The 6:th bit is automatically zero, math is never applied to the first half of the sprites.
-  uint32 math_enabled = ppu->mathEnabled[0] << 0 | ppu->mathEnabled[1] << 1 | ppu->mathEnabled[2] << 2 |
-                        ppu->mathEnabled[3] << 3 | ppu->mathEnabled[4] << 4 | ppu->mathEnabled[5] << 5;
+  uint32 math_enabled = ppu->mathEnabled;
 
   // Render also the subscreen?
   bool rendered_subscreen = false;
   if (ppu->preventMathMode != 3 && ppu->addSubscreen && math_enabled) {
     ClearBackdrop(&ppu->bgBuffers[1]);
-
-    if (ppu->layer[0].screenEnabled[1] | ppu->layer[1].screenEnabled[1] | ppu->layer[2].screenEnabled[1] |
-        ppu->layer[3].screenEnabled[1] | ppu->layer[4].screenEnabled[1]) {
+    if (ppu->screenEnabled[1] != 0) {
       PpuDrawBackgrounds(ppu, y, true);
       rendered_subscreen = true;
     }
@@ -1005,12 +967,12 @@
       r = g = b = 0;
     }
     int secondLayer = 5; // backdrop
-    bool mathEnabled = mainLayer < 6 && ppu->mathEnabled[mainLayer] && !(
+    bool mathEnabled = mainLayer < 6 && (ppu->mathEnabled & (1 << mainLayer)) && !(
       ppu->preventMathMode == 3 ||
       (ppu->preventMathMode == 2 && colorWindowState) ||
       (ppu->preventMathMode == 1 && !colorWindowState)
       );
-    if ((mathEnabled && ppu->addSubscreen) || ppu->pseudoHires_always_zero || ppu->mode == 5 || ppu->mode == 6) {
+    if ((mathEnabled && ppu->addSubscreen) || ppu->mode == 5 || ppu->mode == 6) {
       secondLayer = ppu_getPixel(ppu, x, y, true, &r2, &g2, &b2);
     }
     // TODO: subscreen pixels can be clipped to black as well
@@ -1037,7 +999,7 @@
       if (g < 0) g = 0;
       if (b < 0) b = 0;
     }
-    if (!(ppu->pseudoHires_always_zero || ppu->mode == 5 || ppu->mode == 6)) {
+    if (!(ppu->mode == 5 || ppu->mode == 6)) {
       r2 = r; g2 = g; b2 = b;
     }
   }
@@ -1104,7 +1066,7 @@
   
   // figure out which color is on this location on main- or subscreen, sets it in r, g, b
   // returns which layer it is: 0-3 for bg layer, 4 or 6 for sprites (depending on palette), 5 for backdrop
-  int actMode = ppu->mode == 1 && ppu->bg3priority ? 8 : ppu->mode;
+  int actMode = ppu->mode == 1 ? 8 : ppu->mode;
   actMode = ppu->mode == 7 && ppu->m7extBg_always_zero ? 9 : actMode;
   int layer = 5;
   int pixel = 0;
@@ -1113,12 +1075,12 @@
     int curPriority = prioritysPerMode[actMode][i];
     bool layerActive = false;
     if (!sub) {
-      layerActive = ppu->layer[curLayer].screenEnabled[0] && (
-        !ppu->layer[curLayer].screenWindowed[0] || !ppu_getWindowState(ppu, curLayer, x)
+      layerActive = IS_SCREEN_ENABLED(ppu, 0, curLayer) && (
+        !IS_SCREEN_WINDOWED(ppu, 0, curLayer) || !ppu_getWindowState(ppu, curLayer, x)
         );
     } else {
-      layerActive = ppu->layer[curLayer].screenEnabled[1] && (
-        !ppu->layer[curLayer].screenWindowed[1] || !ppu_getWindowState(ppu, curLayer, x)
+      layerActive = IS_SCREEN_ENABLED(ppu, 1, curLayer) && (
+        !IS_SCREEN_WINDOWED(ppu, 1, curLayer) || !ppu_getWindowState(ppu, curLayer, x)
         );
     }
     if (layerActive) {
@@ -1126,22 +1088,14 @@
         // bg layer
         int lx = x;
         int ly = y;
-        if (ppu->bgLayer[curLayer].mosaicEnabled && ppu->mosaicSize > 1) {
+        if (IS_MOSAIC_ENABLED(ppu, curLayer)) {
           lx -= lx % ppu->mosaicSize;
-          ly -= (ly - ppu->mosaicStartLine) % ppu->mosaicSize;
+          ly -= (ly - 1) % ppu->mosaicSize;
         }
         if (ppu->mode == 7) {
           pixel = ppu_getPixelForMode7(ppu, lx, curLayer, curPriority);
         } else {
           lx += ppu->bgLayer[curLayer].hScroll;
-          if (ppu->mode == 5 || ppu->mode == 6) {
-            lx *= 2;
-            lx += (sub || ppu->bgLayer[curLayer].mosaicEnabled) ? 0 : 1;
-            if (ppu->interlace_always_zero) {
-              ly *= 2;
-              ly += (ppu->evenFrame || ppu->bgLayer[curLayer].mosaicEnabled) ? 0 : 1;
-            }
-          }
           ly += ppu->bgLayer[curLayer].vScroll;
           pixel = ppu_getPixelForBgLayer(
             ppu, lx & 0x3ff, ly & 0x3ff,
@@ -1160,16 +1114,10 @@
       break;
     }
   }
-  if (ppu->directColor_always_zero && layer < 4 && bitDepthsPerMode[actMode][layer] == 8) {
-    *r = ((pixel & 0x7) << 2) | ((pixel & 0x100) >> 7);
-    *g = ((pixel & 0x38) >> 1) | ((pixel & 0x200) >> 8);
-    *b = ((pixel & 0xc0) >> 3) | ((pixel & 0x400) >> 8);
-  } else {
-    uint16_t color = ppu->cgram[pixel & 0xff];
-    *r = color & 0x1f;
-    *g = (color >> 5) & 0x1f;
-    *b = (color >> 10) & 0x1f;
-  }
+  uint16_t color = ppu->cgram[pixel & 0xff];
+  *r = color & 0x1f;
+  *g = (color >> 5) & 0x1f;
+  *b = (color >> 10) & 0x1f;
   if (layer == 4 && pixel < 0xc0) layer = 6; // sprites with palette color < 0xc0
   return layer;
 
@@ -1179,11 +1127,11 @@
 static int ppu_getPixelForBgLayer(Ppu *ppu, int x, int y, int layer, bool priority) {
   BgLayer *layerp = &ppu->bgLayer[layer];
   // figure out address of tilemap word and read it
-  bool wideTiles = layerp->bigTiles_always_zero || ppu->mode == 5 || ppu->mode == 6;
+  bool wideTiles = ppu->mode == 5 || ppu->mode == 6;
   int tileBitsX = wideTiles ? 4 : 3;
   int tileHighBitX = wideTiles ? 0x200 : 0x100;
-  int tileBitsY = layerp->bigTiles_always_zero ? 4 : 3;
-  int tileHighBitY = layerp->bigTiles_always_zero ? 0x200 : 0x100;
+  int tileBitsY = 3;
+  int tileHighBitY = 0x100;
   uint16_t tilemapAdr = layerp->tilemapAdr + (((y >> tileBitsY) & 0x1f) << 5 | ((x >> tileBitsX) & 0x1f));
   if ((x & tileHighBitX) && layerp->tilemapWider) tilemapAdr += 0x400;
   if ((y & tileHighBitY) && layerp->tilemapHigher) tilemapAdr += layerp->tilemapWider ? 0x800 : 0x400;
@@ -1199,10 +1147,6 @@
     // if unflipped right half of tile, or flipped left half of tile
     if (((bool)(x & 8)) ^ ((bool)(tile & 0x4000))) tileNum += 1;
   }
-  if (layerp->bigTiles_always_zero) {
-    // if unflipped bottom half of tile, or flipped upper half of tile
-    if (((bool)(y & 8)) ^ ((bool)(tile & 0x8000))) tileNum += 0x10;
-  }
   // read tiledata, ajust palette for mode 0
   int bitDepth = bitDepthsPerMode[ppu->mode][layer];
   if (ppu->mode == 0) paletteNum += 8 * layer;
@@ -1243,8 +1187,8 @@
   int clippedV = vScroll - yCenter;
   clippedH = (clippedH & 0x2000) ? (clippedH | ~1023) : (clippedH & 1023);
   clippedV = (clippedV & 0x2000) ? (clippedV | ~1023) : (clippedV & 1023);
-  if(ppu->bgLayer[0].mosaicEnabled && ppu->mosaicSize > 1) {
-    y -= (y - ppu->mosaicStartLine) % ppu->mosaicSize;
+  if(IS_MOSAIC_ENABLED(ppu, 0)) {
+    y -= (y - 1) % ppu->mosaicSize;
   }
   uint8_t ry = ppu->m7yFlip ? 255 - y : y;
   ppu->m7startX = (
@@ -1262,7 +1206,7 @@
 }
 
 static int ppu_getPixelForMode7(Ppu* ppu, int x, int layer, bool priority) {
-  if (ppu->bgLayer[layer].mosaicEnabled && ppu->mosaicSize > 1)
+  if (IS_MOSAIC_ENABLED(ppu, layer))
     x -= x % ppu->mosaicSize;
   uint8_t rx = ppu->m7xFlip ? 255 - x : x;
   int xPos = (ppu->m7startX + ppu->m7matrix[0] * rx) >> 8;
@@ -1281,28 +1225,23 @@
 }
 
 static bool ppu_getWindowState(Ppu* ppu, int layer, int x) {
-  if (!ppu->windowLayer[layer].window1enabled && !ppu->windowLayer[layer].window2enabled) {
+  uint32 winflags = GET_WINDOW_FLAGS(ppu, layer);
+  if (!(winflags & kWindow1Enabled) && !(winflags & kWindow2Enabled)) {
     return false;
   }
-  if (ppu->windowLayer[layer].window1enabled && !ppu->windowLayer[layer].window2enabled) {
+  if ((winflags & kWindow1Enabled) && !(winflags & kWindow2Enabled)) {
     bool test = x >= ppu->window1left && x <= ppu->window1right;
-    return ppu->windowLayer[layer].window1inversed ? !test : test;
+    return (winflags & kWindow1Inversed) ? !test : test;
   }
-  if (!ppu->windowLayer[layer].window1enabled && ppu->windowLayer[layer].window2enabled) {
+  if (!(winflags & kWindow1Enabled) && (winflags & kWindow2Enabled)) {
     bool test = x >= ppu->window2left && x <= ppu->window2right;
-    return ppu->windowLayer[layer].window2inversed ? !test : test;
+    return (winflags & kWindow2Inversed) ? !test : test;
   }
   bool test1 = x >= ppu->window1left && x <= ppu->window1right;
   bool test2 = x >= ppu->window2left && x <= ppu->window2right;
-  if (ppu->windowLayer[layer].window1inversed) test1 = !test1;
-  if (ppu->windowLayer[layer].window2inversed) test2 = !test2;
-  switch (ppu->windowLayer[layer].maskLogic_always_zero) {
-  case 0: return test1 || test2;
-  case 1: return test1 && test2;
-  case 2: return test1 != test2;
-  case 3: return test1 == test2;
-  }
-  return false;
+  if (winflags & kWindow1Inversed) test1 = !test1;
+  if (winflags & kWindow2Inversed) test2 = !test2;
+  return test1 || test2;
 }
 
 static bool ppu_evaluateSprites(Ppu* ppu, int line) {
@@ -1309,7 +1248,7 @@
   // TODO: iterate over oam normally to determine in-range sprites,
   //   then iterate those in-range sprites in reverse for tile-fetching
   // TODO: rectangular sprites, wierdness with sprites at -256
-  int index = ppu->objPriority ? (ppu->oamAdr & 0xfe) : 0, index_end = index;
+  int index = 0, index_end = index;
   int spritesLeft = 32 + 1, tilesLeft = 34 + 1;
   uint8 spriteSizes[2] = { kSpriteSizes[ppu->objSize][0], kSpriteSizes[ppu->objSize][1] };
   int extra_left_right = ppu->extraLeftRight;
@@ -1323,7 +1262,7 @@
       continue;  // this works for zelda because sprites are always 8 or 16.
     // check if the sprite is on this line and get the sprite size
     int row = (line - yy) & 0xff;
-    int highOam = ppu->highOam[index >> 3] >> (index & 7);
+    int highOam = ppu->oam[0x100 + (index >> 4)] >> (index & 15);
     int spriteSize = spriteSizes[(highOam >> 1) & 1];
     if (row >= spriteSize)
       continue;
@@ -1335,7 +1274,6 @@
       continue;
     // break if we found 32 sprites already
     if (--spritesLeft == 0) {
-      ppu->rangeOver = true;
       break;
     }
     // get some data for the sprite and y-flip row if needed
@@ -1352,7 +1290,6 @@
       if (col + x > -8 - extra_left_right && col + x < 256 + extra_left_right) {
         // break if we found 34 8*1 slivers already
         if (--tilesLeft == 0) {
-          ppu->timeOver = true;
           return true;
         }
         // figure out which tile this uses, looping within 16x16 pages, and get it's data
@@ -1379,17 +1316,6 @@
   return (tilesLeft != tilesLeftOrg);
 }
 
-static uint16_t ppu_getVramRemap(Ppu* ppu) {
-  uint16_t adr = ppu->vramPointer;
-  switch(ppu->vramRemapMode) {
-    case 0: return adr;
-    case 1: return (adr & 0xff00) | ((adr & 0xe0) >> 5) | ((adr & 0x1f) << 3);
-    case 2: return (adr & 0xfe00) | ((adr & 0x1c0) >> 6) | ((adr & 0x3f) << 3);
-    case 3: return (adr & 0xfc00) | ((adr & 0x380) >> 7) | ((adr & 0x7f) << 3);
-  }
-  return adr;
-}
-
 uint8_t ppu_read(Ppu* ppu, uint8_t adr) {
   switch (adr) {
   case 0x34:
@@ -1404,70 +1330,49 @@
 
 void ppu_write(Ppu* ppu, uint8_t adr, uint8_t val) {
   switch(adr) {
-    case 0x00: {
-      // TODO: oam address reset when written on first line of vblank, (and when forced blank is disabled?)
+    case 0x00: {  // INIDISP
       ppu->brightness = val & 0xf;
       ppu->forcedBlank = val & 0x80;
       break;
     }
     case 0x01: {
-      ppu->objSize = val >> 5;
-      ppu->objTileAdr1 = (val & 7) << 13;
-      ppu->objTileAdr2 = ppu->objTileAdr1 + (((val & 0x18) + 8) << 9);
+      assert(val == 2);
       break;
     }
     case 0x02: {
-      ppu->oamAdr = val;
-      ppu->oamAdrWritten = ppu->oamAdr;
-      ppu->oamInHigh = ppu->oamInHighWritten;
+      ppu->oamAdr = (ppu->oamAdr & ~0xff) | val;
       ppu->oamSecondWrite = false;
       break;
     }
     case 0x03: {
-      ppu->objPriority = val & 0x80;
-      ppu->oamInHigh = val & 1;
-      ppu->oamInHighWritten = ppu->oamInHigh;
-      ppu->oamAdr = ppu->oamAdrWritten;
+      assert((val & 0x80) == 0);
+      ppu->oamAdr = (ppu->oamAdr & ~0xff00) | ((val & 1) << 8);
       ppu->oamSecondWrite = false;
       break;
     }
     case 0x04: {
-      if(ppu->oamInHigh) {
-        ppu->highOam[((ppu->oamAdr & 0xf) << 1) | (uint8_t)ppu->oamSecondWrite] = val;
-        if(ppu->oamSecondWrite) {
-          ppu->oamAdr++;
-          if(ppu->oamAdr == 0) ppu->oamInHigh = false;
-        }
+      if (!ppu->oamSecondWrite) {
+        ppu->oamBuffer = val;
       } else {
-        if(!ppu->oamSecondWrite) {
-          ppu->oamBuffer = val;
-        } else {
+        if (ppu->oamAdr < 0x110)
           ppu->oam[ppu->oamAdr++] = (val << 8) | ppu->oamBuffer;
-          if(ppu->oamAdr == 0) ppu->oamInHigh = true;
-        }
       }
       ppu->oamSecondWrite = !ppu->oamSecondWrite;
       break;
     }
-    case 0x05: {
+    case 0x05: {  // BGMODE
       ppu->mode = val & 0x7;
-      ppu->bg3priority = val & 0x8;
       assert(val == 7 || val == 9);
       assert(ppu->mode == 1 || ppu->mode == 7);
-      // bigTiles are never used
       assert((val & 0xf0) == 0);
       break;
     }
-    case 0x06: {
-      // TODO: mosaic line reset specifics
-      ppu->bgLayer[0].mosaicEnabled = val & 0x1;
-      ppu->bgLayer[1].mosaicEnabled = val & 0x2;
-      ppu->bgLayer[2].mosaicEnabled = val & 0x4;
-      ppu->bgLayer[3].mosaicEnabled = val & 0x8;
+    case 0x06: {  // MOSAIC
       ppu->mosaicSize = (val >> 4) + 1;
+      ppu->mosaicEnabled = (ppu->mosaicSize > 1) ? val : 0;
       break;
     }
-    case 0x07:
+    case 0x07:  // BG1SC
     case 0x08:
     case 0x09:
     case 0x0a: {
@@ -1477,17 +1382,17 @@
       ppu->bgLayer[adr - 7].tilemapAdr = (val & 0xfc) << 8;
       break;
     }
-    case 0x0b: {
+    case 0x0b: {  // BG12NBA
       ppu->bgLayer[0].tileAdr = (val & 0xf) << 12;
       ppu->bgLayer[1].tileAdr = (val & 0xf0) << 8;
       break;
     }
-    case 0x0c: {
+    case 0x0c: { // BG34NBA
       ppu->bgLayer[2].tileAdr = (val & 0xf) << 12;
       ppu->bgLayer[3].tileAdr = (val & 0xf0) << 8;
       break;
     }
-    case 0x0d: {
+    case 0x0d: { // BG1HOFS
       ppu->m7matrix[6] = ((val << 8) | ppu->m7prev) & 0x1fff;
       ppu->m7prev = val;
       // fallthrough to normal layer BG-HOFS
@@ -1500,7 +1405,7 @@
       ppu->scrollPrev2 = val;
       break;
     }
-    case 0x0e: {
+    case 0x0e: { // BG1VOFS
       ppu->m7matrix[7] = ((val << 8) | ppu->m7prev) & 0x1fff;
       ppu->m7prev = val;
       // fallthrough to normal layer BG-VOFS
@@ -1520,37 +1425,32 @@
       } else {
         ppu->vramIncrement = 128;
       }
-      ppu->vramRemapMode = (val & 0xc) >> 2;
+      assert(((val & 0xc) >> 2) == 0);
       ppu->vramIncrementOnHigh = val & 0x80;
       break;
     }
-    case 0x16: {
+    case 0x16: {  // VMADDL
       ppu->vramPointer = (ppu->vramPointer & 0xff00) | val;
-      ppu->vramReadBuffer = ppu->vram[ppu_getVramRemap(ppu) & 0x7fff];
       break;
     }
-    case 0x17: {
+    case 0x17: {  // VMADDH
       ppu->vramPointer = (ppu->vramPointer & 0x00ff) | (val << 8);
-      ppu->vramReadBuffer = ppu->vram[ppu_getVramRemap(ppu) & 0x7fff];
       break;
     }
-    case 0x18: {
-      // TODO: vram access during rendering (also cgram and oam)
-      uint16_t vramAdr = ppu_getVramRemap(ppu);
-      if (val != 0xef) {
-        val += 0;
-      }
+    case 0x18: {  // VMDATAL
+      uint16_t vramAdr = ppu->vramPointer;
       ppu->vram[vramAdr & 0x7fff] = (ppu->vram[vramAdr & 0x7fff] & 0xff00) | val;
       if(!ppu->vramIncrementOnHigh) ppu->vramPointer += ppu->vramIncrement;
       break;
     }
-    case 0x19: {
-      uint16_t vramAdr = ppu_getVramRemap(ppu);
+    case 0x19: {  // VMDATAH
+      uint16_t vramAdr = ppu->vramPointer;
       ppu->vram[vramAdr & 0x7fff] = (ppu->vram[vramAdr & 0x7fff] & 0x00ff) | (val << 8);
       if(ppu->vramIncrementOnHigh) ppu->vramPointer += ppu->vramIncrement;
       break;
     }
-    case 0x1a: {  
+    case 0x1a: {  // M7SEL
+      assert(val == 0x80);
       ppu->m7largeField = val & 0x80;
       ppu->m7charFill = val & 0x40;
       ppu->m7yFlip = val & 0x2;
@@ -1557,7 +1457,7 @@
       ppu->m7xFlip = val & 0x1;
       break;
     }
-    case 0x1b:
+    case 0x1b:  // M7A etc
     case 0x1c:
     case 0x1d:
     case 0x1e: {
@@ -1585,78 +1485,46 @@
       ppu->cgramSecondWrite = !ppu->cgramSecondWrite;
       break;
     }
-    case 0x23:
-    case 0x24:
-    case 0x25: {
-      ppu->windowLayer[(adr - 0x23) * 2].window1inversed = (val & 0x1) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2].window1enabled = (val & 0x2) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2].window2inversed = (val & 0x4) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2].window2enabled = (val & 0x8) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window1inversed = (val & 0x10) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window1enabled = (val & 0x20) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window2inversed = (val & 0x40) != 0;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window2enabled = (val & 0x80) != 0;
+    case 0x23:  // W12SEL
+      ppu->windowsel = (ppu->windowsel & ~0xff) | val;
       break;
-    }
-    case 0x26: {
+    case 0x24:  // W34SEL
+      ppu->windowsel = (ppu->windowsel & ~0xff00) | (val << 8);
+      break;
+    case 0x25:  // WOBJSEL
+      ppu->windowsel = (ppu->windowsel & ~0xff0000) | (val << 16);
+      break;
+    case 0x26:
       ppu->window1left = val;
       break;
-    }
-    case 0x27: {
+    case 0x27:
       ppu->window1right = val;
       break;
-    }
-    case 0x28: {
+    case 0x28:
       ppu->window2left = val;
       break;
-    }
-    case 0x29: {
+    case 0x29:
       ppu->window2right = val;
       break;
-    }
-    case 0x2a: {
+    case 0x2a:  // WBGLOG
       assert(val == 0);
-      // maskLogic_always_zero
       break;
-    }
-    case 0x2b: {
+    case 0x2b:  // WOBJLOG
       assert(val == 0);
-      // maskLogic_always_zero
       break;
-    }
-    case 0x2c: {
-      ppu->layer[0].screenEnabled[0] = val & 0x1;
-      ppu->layer[1].screenEnabled[0] = val & 0x2;
-      ppu->layer[2].screenEnabled[0] = val & 0x4;
-      ppu->layer[3].screenEnabled[0] = val & 0x8;
-      ppu->layer[4].screenEnabled[0] = val & 0x10;
+    case 0x2c:  // TM
+      ppu->screenEnabled[0] = val;
       break;
-    }
-    case 0x2d: {
-      ppu->layer[0].screenEnabled[1] = val & 0x1;
-      ppu->layer[1].screenEnabled[1] = val & 0x2;
-      ppu->layer[2].screenEnabled[1] = val & 0x4;
-      ppu->layer[3].screenEnabled[1] = val & 0x8;
-      ppu->layer[4].screenEnabled[1] = val & 0x10;
+    case 0x2d:  // TS
+      ppu->screenEnabled[1] = val;
       break;
-    }
-    case 0x2e: {
-      ppu->layer[0].screenWindowed[0] = val & 0x1;
-      ppu->layer[1].screenWindowed[0] = val & 0x2;
-      ppu->layer[2].screenWindowed[0] = val & 0x4;
-      ppu->layer[3].screenWindowed[0] = val & 0x8;
-      ppu->layer[4].screenWindowed[0] = val & 0x10;
+    case 0x2e: // TMW
+      ppu->screenWindowed[0] = val;
       break;
-    }
-    case 0x2f: {
-      ppu->layer[0].screenWindowed[1] = val & 0x1;
-      ppu->layer[1].screenWindowed[1] = val & 0x2;
-      ppu->layer[2].screenWindowed[1] = val & 0x4;
-      ppu->layer[3].screenWindowed[1] = val & 0x8;
-      ppu->layer[4].screenWindowed[1] = val & 0x10;
+    case 0x2f:  // TSW
+      ppu->screenWindowed[1] = val;
       break;
-    }
-    case 0x30: {
+    case 0x30: {  // CGWSEL
       assert((val & 1) == 0);  // directColor always zero
       ppu->addSubscreen = val & 0x2;
       ppu->preventMathMode = (val & 0x30) >> 4;
@@ -1663,15 +1531,13 @@
       ppu->clipMode = (val & 0xc0) >> 6;
       break;
     }
-    case 0x31: {
+    case 0x31: {  // CGADSUB
       ppu->subtractColor = val & 0x80;
       ppu->halfColor = val & 0x40;
-      for(int i = 0; i < 6; i++) {
-        ppu->mathEnabled[i] = val & (1 << i);
-      }
+      ppu->mathEnabled = val & 0x3f;
       break;
     }
-    case 0x32: {
+    case 0x32: {  // COLDATA
       if(val & 0x80) ppu->fixedColorB = val & 0x1f;
       if(val & 0x40) ppu->fixedColorG = val & 0x1f;
       if(val & 0x20) ppu->fixedColorR = val & 0x1f;
@@ -1679,10 +1545,6 @@
     }
     case 0x33: {
       assert(val == 0);
-      ppu->interlace_always_zero = val & 0x1;
-      ppu->objInterlace_always_zero = val & 0x2;
-      ppu->overscan_always_zero = val & 0x4;
-      ppu->pseudoHires_always_zero = val & 0x8;
       ppu->m7extBg_always_zero = val & 0x40;
       break;
     }
--- a/snes/ppu.h
+++ b/snes/ppu.h
@@ -15,27 +15,14 @@
 typedef struct BgLayer {
   uint16_t hScroll;
   uint16_t vScroll;
+  // -- snapshot starts here
   bool tilemapWider;
   bool tilemapHigher;
   uint16_t tilemapAdr;
+  // -- snapshot ends here
   uint16_t tileAdr;
-  bool bigTiles_always_zero;
-  bool mosaicEnabled;
 } BgLayer;
 
-typedef struct Layer {
-  bool screenEnabled[2];   // 0 = main, 1 = sub
-  bool screenWindowed[2];  // 0 = main, 1 = sub
-} Layer;
-
-typedef struct WindowLayer {
-  bool window1enabled;
-  bool window2enabled;
-  bool window1inversed;
-  bool window2inversed;
-  uint8_t maskLogic_always_zero;
-} WindowLayer;
-
 enum {
   kPpuXPixels = 256 + kPpuExtraLeftRight * 2,
 };
@@ -67,49 +54,54 @@
   uint8_t *renderBuffer;
   uint8_t extraLeftCur, extraRightCur, extraLeftRight, extraBottomCur;
   float mode7PerspectiveLow, mode7PerspectiveHigh;
-  // store 31 extra entries to remove the need for clamp
-  uint8_t brightnessMult[32 + 31]; 
-  uint8_t brightnessMultHalf[32 * 2];
-  PpuPixelPrioBufs bgBuffers[2];
+
+  // TMW / TSW etc
+  uint8 screenEnabled[2];
+  uint8 screenWindowed[2];
+  uint8 mosaicEnabled;
+  uint8 mosaicSize;
+  // object/sprites
+  uint16_t objTileAdr1;
+  uint16_t objTileAdr2;
+  uint8_t objSize;
+  // Window
+  uint8_t window1left;
+  uint8_t window1right;
+  uint8_t window2left;
+  uint8_t window2right;
+  uint32_t windowsel;
+
+  // color math
+  uint8_t clipMode;
+  uint8_t preventMathMode;
+  bool addSubscreen;
+  bool subtractColor;
+  bool halfColor;
+  uint8 mathEnabled;
+  uint8_t fixedColorR, fixedColorG, fixedColorB;
+  // settings
+  bool forcedBlank;
+  uint8_t brightness;
+  uint8_t mode;
+
   // vram access
-  uint16_t vram[0x8000];
   uint16_t vramPointer;
-  bool vramIncrementOnHigh;
   uint16_t vramIncrement;
-  uint8_t vramRemapMode;
-  uint16_t vramReadBuffer;
+  bool vramIncrementOnHigh;
   // cgram access
-  uint16_t cgram[0x100];
   uint8_t cgramPointer;
   bool cgramSecondWrite;
   uint8_t cgramBuffer;
   // oam access
-  uint16_t oam[0x100];
-  uint8_t highOam[0x20];
-  uint8_t oamAdr;
-  uint8_t oamAdrWritten;
-  bool oamInHigh;
-  bool oamInHighWritten;
+  uint16_t oamAdr;
   bool oamSecondWrite;
   uint8_t oamBuffer;
-  // object/sprites
-  bool objPriority;
-  uint16_t objTileAdr1;
-  uint16_t objTileAdr2;
-  uint8_t objSize;
-  PpuPixelPrioBufs objBuffer;
-  uint8 pad[3];
-  bool timeOver;
-  bool rangeOver;
-  bool objInterlace_always_zero;
+
   // background layers
   BgLayer bgLayer[4];
   uint8_t scrollPrev;
   uint8_t scrollPrev2;
-  uint8_t mosaicSize;
-  uint8_t mosaicStartLine;
-  // layers
-  Layer layer[5];
+  
   // mode 7
   int16_t m7matrix[8]; // a, b, c, d, x, y, h, v
   uint8_t m7prev;
@@ -121,45 +113,18 @@
   // mode 7 internal
   int32_t m7startX;
   int32_t m7startY;
-  // windows
-  WindowLayer windowLayer[6];
-  uint8_t window1left;
-  uint8_t window1right;
-  uint8_t window2left;
-  uint8_t window2right;
-  // color math
-  uint8_t clipMode;
-  uint8_t preventMathMode;
-  bool addSubscreen;
-  bool subtractColor;
-  bool halfColor;
-  bool mathEnabled[6];
-  uint8_t fixedColorR;
-  uint8_t fixedColorG;
-  uint8_t fixedColorB;
-  // settings
-  bool forcedBlank;
-  uint8_t brightness;
-  uint8_t mode;
-  bool bg3priority;
-  bool evenFrame;
-  bool pseudoHires_always_zero;
-  bool overscan_always_zero;
-  bool frameOverscan_always_zero; // if we are overscanning this frame (determined at 0,225)
-  bool interlace_always_zero;
-  bool frameInterlace_always_zero; // if we are interlacing this frame (determined at start vblank)
-  bool directColor_always_zero;
-  // latching
-  uint16_t hCount;
-  uint16_t vCount;
-  bool hCountSecond;
-  bool vCountSecond;
-  bool countersLatched;
-  uint8_t ppu1openBus;
-  uint8_t ppu2openBus;
 
+  uint16_t oam[0x110];
+  
+  // store 31 extra entries to remove the need for clamp
+  uint8_t brightnessMult[32 + 31];
+  uint8_t brightnessMultHalf[32 * 2];
+  uint16_t cgram[0x100];
   uint8_t mosaicModulo[kPpuXPixels];
   uint32_t colorMapRgb[256];
+  PpuPixelPrioBufs bgBuffers[2];
+  PpuPixelPrioBufs objBuffer;
+  uint16_t vram[0x8000];
 };
 
 Ppu* ppu_init();
--- a/zelda_cpu_infra.c
+++ b/zelda_cpu_infra.c
@@ -312,7 +312,7 @@
 // Copy state into the emulator, we can skip dsp/apu because 
 // we're not emulating that.
 static void EmuSynchronizeWholeState() {
-  memcpy(&g_snes->ppu->vram, g_zenv.ppu->vram, offsetof(Ppu, ppu2openBus) + 1 - offsetof(Ppu, vram));
+  *g_snes->ppu = *g_zenv.ppu;
   memcpy(g_snes->ram, g_zenv.ram, 0x20000);
   memcpy(g_snes->cart->ram, g_zenv.sram, 0x2000);
   memcpy(g_snes->dma->channel, g_zenv.dma->channel, sizeof(Dma) - offsetof(Dma, channel));
--- a/zelda_rtl.c
+++ b/zelda_rtl.c
@@ -283,7 +283,6 @@
   zelda_apu_write(APUI01, 0);
   zelda_apu_write(APUI02, 0);
   zelda_apu_write(APUI03, 0);
-  zelda_ppu_write(INIDISP, 0x80);
 
   Sound_LoadIntroSongBank();
   Startup_InitializeMemory();
@@ -382,7 +381,6 @@
     WORD(sram[0x8e5]) = 0;
   if (WORD(sram[0xde5]) != 0x55aa)
     WORD(sram[0xde5]) = 0;
-  zelda_ppu_write(TMW, 0);
   INIDISP_copy = 0x80;
   flag_update_cgram_in_nmi++;
 }