From cbbaf2ee843870d96e72380e2dedbdbda471ca76 Mon Sep 17 00:00:00 2001
From: Lior Halphon <LIJI32@gmail.com>
Date: Sun, 20 Aug 2017 01:34:12 +0300
Subject: [PATCH] Refined Window behavior once more, Fixes #12 (While not
 breaking Donkey Kong or 007)

---
 Core/display.c | 48 ++++++++++++++++++++++++++++++++++++------------
 Core/display.h |  1 +
 Core/gb.h      |  3 ++-
 Core/memory.c  | 10 +++++++---
 4 files changed, 46 insertions(+), 16 deletions(-)

diff --git a/Core/display.c b/Core/display.c
index 0573b9c..aa6e681 100755
--- a/Core/display.c
+++ b/Core/display.c
@@ -75,7 +75,8 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
             bg_enabled = false;
         }
     }
-    if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] && x + 7 >= gb->io_registers[GB_IO_WX] && gb->current_window_line != 0xFF) {
+
+    if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] + gb->wy_diff && x + 7 >= gb->io_registers[GB_IO_WX]) {
         in_window = true;
     }
 
@@ -128,7 +129,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
 
     if (in_window) {
         x -= gb->io_registers[GB_IO_WX] - 7; // Todo: This value is probably latched
-        y = gb->current_window_line;
+        y -= gb->io_registers[GB_IO_WY] + gb->wy_diff;
     }
     else {
         x += gb->effective_scx;
@@ -282,7 +283,8 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
         }
 
         /* Reset window rendering state */
-        gb->current_window_line = 0xFF;
+        gb->wy_diff = 0;
+        gb->window_disabled_while_active = false;
         return;
     }
     
@@ -330,7 +332,8 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
 
             
             /* Reset window rendering state */
-            gb->current_window_line = 0xFF;
+            gb->wy_diff = 0;
+            gb->window_disabled_while_active = false;
         }
         
         /* Entered VBlank state, update STAT and IF */
@@ -418,9 +421,6 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
             if (position_in_line == stat_delay) {
                 gb->io_registers[GB_IO_STAT] &= ~3;
                 gb->io_registers[GB_IO_STAT] |= 2;
-                if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH >= gb->io_registers[GB_IO_WY]) {
-                    gb->current_window_line++;
-                }
             }
             else if (position_in_line == 0 && gb->display_cycles != 0) {
                 should_compare_ly = gb->is_cgb;
@@ -431,11 +431,6 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
                 gb->io_registers[GB_IO_STAT] |= 3;
                 gb->effective_scx = gb->io_registers[GB_IO_SCX];
                 gb->previous_lcdc_x = - (gb->effective_scx & 0x7);
-                
-                /* Todo: This works on both 007 - The World Is Not Enough and Donkey Kong 94, but should be verified better */
-                if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH == gb->io_registers[GB_IO_WY] && gb->current_window_line == 0xFF) {
-                    gb->current_window_line = 0;
-                }
             }
             else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay) {
                 gb->io_registers[GB_IO_STAT] &= ~3;
@@ -800,3 +795,32 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h
     }
     return count;
 }
+
+/* Called when a write might enable or disable the window */
+void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value)
+{
+    bool before = window_enabled(gb);
+    gb->io_registers[addr] = value;
+    bool after = window_enabled(gb);
+    
+    if (before != after && gb->display_cycles < LINES * LINE_LENGTH) {
+        /* Window was disabled or enabled outside of vblank */
+        uint8_t current_line = gb->display_cycles / LINE_LENGTH;
+        if (current_line >= gb->io_registers[GB_IO_WY]) {
+            if (after) {
+                if (!gb->window_disabled_while_active) {
+                    /* Window was turned on for the first time this frame while LY > WY,
+                       should start window in the next line */
+                    gb->wy_diff = current_line + 1 - gb->io_registers[GB_IO_WY];
+                }
+                else {
+                    gb->wy_diff += current_line;
+                }
+            }
+            else {
+                gb->wy_diff -= current_line;
+                gb->window_disabled_while_active = true;
+            }
+        }
+    }
+}
diff --git a/Core/display.h b/Core/display.h
index 3967787..a622923 100644
--- a/Core/display.h
+++ b/Core/display.h
@@ -5,6 +5,7 @@
 #ifdef GB_INTERNAL
 void GB_display_run(GB_gameboy_t *gb, uint8_t cycles);
 void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
+void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value);
 #endif
 
 typedef enum {
diff --git a/Core/gb.h b/Core/gb.h
index 8260882..5965d51 100644
--- a/Core/gb.h
+++ b/Core/gb.h
@@ -367,7 +367,7 @@ struct GB_gameboy_internal_s {
         int16_t previous_lcdc_x;
         bool stat_interrupt_line;
         uint8_t effective_scx;
-        uint8_t current_window_line;
+        uint8_t wy_diff;
         /* The LCDC will skip the first frame it renders after turning it on.
            On the CGB, a frame is not skipped if the previous frame was skipped as well.
            See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */
@@ -383,6 +383,7 @@ struct GB_gameboy_internal_s {
         bool vram_read_blocked;
         bool oam_write_blocked;
         bool vram_write_blocked;
+        bool window_disabled_while_active;
     );
 
     /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
diff --git a/Core/memory.c b/Core/memory.c
index e1bffcd..8d34917 100644
--- a/Core/memory.c
+++ b/Core/memory.c
@@ -408,10 +408,14 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
         return;
     }
 
+    /* Todo: Clean this code up: use a function table and move relevant code to display.c and timing.c
+       (APU read and writes are already at apu.c) */
     if (addr < 0xFF80) {
         /* Hardware registers */
         switch (addr & 0xFF) {
-
+            case GB_IO_WX:
+                GB_window_related_write(gb, addr & 0xFF, value);
+                break;
             case GB_IO_SCX:
             case GB_IO_IF:
             case GB_IO_SCY:
@@ -420,7 +424,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
             case GB_IO_OBP0:
             case GB_IO_OBP1:
             case GB_IO_WY:
-            case GB_IO_WX:
             case GB_IO_SB:
             case GB_IO_DMG_EMULATION_INDICATION:
             case GB_IO_UNKNOWN2:
@@ -463,7 +466,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
                     /* Sync after turning off LCD */
                     GB_timing_sync(gb);
                 }
-                gb->io_registers[GB_IO_LCDC] = value;
+                /* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */
+                GB_window_related_write(gb, addr & 0xFF, value);
                 return;
 
             case GB_IO_STAT: