'******************************************************************************
'* 
'* NES MEMORY MAPPING IMPLEMENTATION
'*
'* Version history:
'*  2009 - initial emulation (WadiM)
'*
'****************************************************************************** 

//DEBUG.ON 'uncomment to enable debug messages (can be slow for hi-freq events)

//Modes: by cartridge(0), allpages(1), horizontal (2), vertical (3), 4xPage(4)
public const vramDefault    as byte=0 'by cartridge
public const vram1xPage     as byte=1 'all pages to one
public const vramHorizontal as byte=2 'horizontal mirroring 
public const vramVertical   as byte=3 'vertical mirroring
public const vram4xPages    as byte=4 '4 pages of VRAM

//Current VRAM mode
public dim nes_VRAMMode as byte=vramDefault

dim sMap as string="[MAPPER_"+Str(nes_Mapper)+"] " 'prefix in debug messages

'------------------------------ Tools -----------------------------------------

//Remap R/W of Count Kilobytes from Src (Kb index) to Dst (Kb index)
procedure Remap(Src as dword, Dst as dword, Count as dword)
 dim i as integer : for i=0 to Count-1 : mem.KBIndex(Src+i)=Dst+i : next
end

//Remap READ of Count Kilobytes from Src (Kb index) to Dst (Kb index)
procedure RemapRead(Src as dword, Dst as dword, Count as dword)
 dim i as integer : for i=0 to Count-1 : mem.KBReadIndex(Src+i)=Dst+i : next
end

//Remap WRITE of Count Kilobytes from Src (Kb index) to Dst (Kb index)
procedure RemapWrite(Src as dword, Dst as dword, Count as dword)
 dim i as integer : for i=0 to Count-1 : mem.KBWriteIndex(Src+i)=Dst+i : next
end

//Set Read-Only Access to Count Kilobytes from Src (Kb index)
procedure MakeReadOnly(Src as dword, Count as dword)
 dim i as integer : for i=0 to Count-1 : mem.KBAccess(Src+i)=memReadOnly : next
end

//Set Read-Write Access to Count Kilobytes from Src (Kb index)
procedure MakeReadWrite(Src as dword, Count as dword)
 dim i as integer : for i=0 to Count-1 : mem.KBAccess(Src+i)=memReadWrite : next
end

//Remap 16Kb CPU ROM Bank ($8000...$FFFF) to cartridge ROM Bank by indexes
procedure MapROMBank16(Src as dword, Dst as dword) 
 dim i as integer=Dst*16 : if ((i+16)<=nes_ROMSize) and (Src<2) then 
 RemapRead(32+Src*16,nes_ROMStart+i,16) : end
end

//Remap 32Kb CPU ROM Bank ($8000...$FFFF) to cartridge ROM Bank by indexes
procedure MapROMBank32(Src as dword, Dst as dword) 
 dim i as integer=Dst*32 : if ((i+32)<=nes_ROMSize) and (Src<1) then 
 RemapRead(32+Src*32,nes_ROMStart+i,32) : end
end

//Remap 4Kb PPU ROM Bank ($0000...$1FFF) to cartridge VROM Bank by indexes
procedure MapVROMBank4(Src as dword, Dst as dword) 
 dim i as integer=Dst*4 : if ((i+4)<=nes_VROMSize) and (Src<2) then 
 RemapRead(nes_VRAMStart+Src*4,nes_VROMStart+i,4) : end
end

//Remap 8Kb PPU ROM Bank ($0000...$1FFF) to cartridge VROM Bank by indexes
procedure MapVROMBank8(Src as dword, Dst as dword) 
 dim i as integer=Dst*8 : if ((i+8)<=nes_VROMSize) and (Src<1) then 
 RemapRead(nes_VRAMStart+Src*8,nes_VROMStart+i,8) : end
end

'-------------------------- PPU VRAM Mirroring --------------------------------

//Index is used to specify name table in case it needed to mode
public procedure MapVRAM(Mode as byte=vramDefault, Index as byte=0)
 dim i as integer, j as integer=nes_VRAMStart+8 'start KB of Name Tables
 if Mode=vramDefault then 
    if nes_4xScreenVRAM then : Mode=vram4xPages
    elseif nes_VertMirror then : Mode=vramVertical
    else : Mode=vramHorizontal
 end if : end if
 select case Mode
 case vram1xPage 'map to one page
     if Index<=3 then
       for i=0 to 3 : mem.KBIndex(j+i)=j+Index : next 
      end if
      ?? sMap;"VRAM Mirroring is all Pages As One"
 case vramHorizontal 'horizontal mirror
      mem.KBIndex(j)=j     : mem.KBIndex(j+1)=j 
      mem.KBIndex(j+2)=j+1 : mem.KBIndex(j+3)=j+1
      ?? sMap;"VRAM Mirroring is Horizontal"
 case vramVertical 'vertical mirror
      mem.KBIndex(j)=j     : mem.KBIndex(j+1)=j+1   
      mem.KBIndex(j+2)=j   : mem.KBIndex(j+3)=j+1
      ?? sMap;"VRAM Mirroring is Vertical"
 case vram4xPages 'map to 4 pages
      for i=0 to 3 : mem.KBIndex(j+i)=j+i : next 
     ?? sMap;"VRAM Mirroring is separate 4 Pages"
 end select
 nes_VRAMMode=Mode
end 

'-------------------------- Mapper 0 (Default) --------------------------------

//Initialization
public procedure InitMap0
 MapROMBank16(0,0) 'ROM bank 0
 if nes_ROMSize=16 then MapROMBank16(1,0) else MapROMBank16(1,1) 'ROM bank 1
 if nes_VROMSize>0 then MapVROMBank8(0,0) 'VROM bank 0
end

'--------------------------- Mapper 1 (MMC1) ----------------------------------

//TODO - it is not complete and tested implementation yet!!!!

dim map1buff as byte=0, map1num as byte=0
dim map1reg0 as byte=0xC, map1reg1 as byte=0
dim map1reg2 as byte=0, map1reg3 as byte=0

//ROM mapping
procedure UpdateROMMap1
 dim bigoffs as byte=0
 //if (map1reg1 and 0x10)<>0 then bigoffs=0x10 else bigoffs=0 '512Kb ROM//TODO more
 //processing register value
 if (map1reg0 and 8)=0 then //32Kb ROM banks
    MapROMBank32(0,shr(map1reg3 and 0xF,1)+bigoffs)
 else //16KB ROM banks
    if (map1reg0 and 4)=0 then //16KB ROM bank at $C000
       MapROMBank16(1,(map1reg3 and 0xF)+bigoffs) 
    else//16KbROM bank at $8000
       MapROMBank16(0,(map1reg3 and 0xF)+bigoffs)
    end if
 end if
end

//VROM mapping
procedure UpdateVROMMap1
 if (map1reg0 and 0x10)=0 then //8Kb VROM banks
    MapVROMBank8(0,map1reg1 and 0xF)
 else //4KB VROM banks
    MapVROMBank4(0,map1reg1 and 0xF)
    MapVROMBank4(1,map1reg2 and 0xF)
 end if
end

//VRAM mirroring
procedure UpdateVRAMMap1
 select case (map1reg0 and 3)
 case 0 : MapVRAM(vram1xPage,0)
 case 1 : MapVRAM(vram1xPage,1)
 case 2 : MapVRAM(vramVertical)
 case 3 : MapVRAM(vramHorizontal)
 end select
end

//Write handler (ROM bank mapping)
function Map1Writer(Addr as dword, Value as byte) as boolean
 if (Value and 0x80)<>0 then 'reset buffer
    ?? "[MAPPER_1] Reset<";Hex(Value,2)
    map1buff=0 : map1num=0
    map1reg0=map1reg0 or 0xC 'TOCHECK
    UpdateROMMap1 'update ROM mapping
    UpdateVROMMap1 'update VROM mapping
    UpdateVRAMMap1 'update VRAM mapping
    exit(true)
 end if
 //store bit to buffer
 map1buff=map1buff or shl(Value and 1,map1num)
 map1num=map1num+1
 //process registers
 select case shr(Addr,13) and 0x7
 case 4  'Reg0 ($8000-$9FFF)
   ?? sMap;"Reg0<";Hex(Value,2)
   if map1num>=5 then '5-bit value ready
      map1reg0=map1buff : map1buff=0 : map1num=0 'store and reset
      ?? sMap;"Reg0=";Hex(map1reg0,2)
      UpdateROMMap1 'update ROM mapping
      UpdateVROMMap1 'update VROM mapping
      UpdateVRAMMap1 'update VRAM mapping
   end if
 case 5  'Reg1 ($A000-$BFFF)
   ?? sMap;"Reg1<";Hex(Value,2)
   if map1num>=5 then '5-bit value ready
      map1reg1=map1buff : map1buff=0 : map1num=0 'store and reset
      ?? sMap;"Reg1=";Hex(map1reg1,2)
      UpdateROMMap1 'update ROM mapping
      UpdateVROMMap1 'update VROM mapping
      UpdateVRAMMap1 'update VRAM mapping
   end if
 case 6  'Reg2 ($C000-$DFFF)
   ?? sMap;"Reg2<";Hex(Value,2)
   if map1num>=5 then '5-bit value ready
      map1reg2=map1buff : map1buff=0 : map1num=0 'store and reset
      ?? sMap;"Reg2=";Hex(map1reg2,2)
      UpdateROMMap1 'update  ROM mapping
      UpdateVROMMap1 'update VROM mapping
   end if
 case 7  'Reg3 ($E000-$FFFF)
   ?? sMap;"Reg3<";Hex(Value,2)
   if map1num>=5 then '5-bit value ready
      map1reg3=map1buff : map1buff=0 : map1num=0 'store and reset
      ?? sMap;"Reg3=";Hex(map1reg3,2)
      UpdateROMMap1 'update ROM mapping
   end if
 case else : if map1num>=5 then '5-bit value ready
   map1buff=0 : map1num=0 : end if 'reset
 end select
 result=true
end

//Initialization 
public procedure InitMap1 : dim i as integer
 MapROMBank16(0,0) 'ROM bank 0
 if nes_ROMSize=16 then : MapROMBank16(1,0) : else 
    MapROMBank16(1,shr(nes_ROMSize,4)-1) 'last ROM bank
 end if
 if nes_VROMSize>0 then MapVROMBank8(0,0) 'VROM bank 0
 //make both memory banks writable and attach write handler
 MakeReadWrite(32,32) : for i=0 to 31 : mem.KBWriter(32+i)=Map1Writer : next 
 UpdateROMMap1 'update ROM mapping
 UpdateVROMMap1 'update VROM mapping
 UpdateVRAMMap1 'update VRAM mapping
end

'--------------------------- Mapper 2 (UNROM) ---------------------------------

//Write handler (ROM bank mapping)
function Map2Writer(Addr as dword, Value as byte) as boolean
 MapROMBank16(0,Value) : result=true
 ?? sMap;"Value=";Hex(Value,2)
end

//Initialization 
public procedure InitMap2 : dim i as integer
 MapROMBank16(0,0) 'ROM bank 0
 if nes_ROMSize=16 then : MapROMBank16(1,0) : else 
    MapROMBank16(1,shr(nes_ROMSize,4)-1) 'last ROM bank
 end if
 //this mapper not contains VROM
 //make both memory banks writable and attach write handler
 MakeReadWrite(32,32) : for i=0 to 31 : mem.KBWriter(32+i)=Map2Writer : next 
end

'--------------------------- Mapper 3 (CNROM) ---------------------------------

//Write handler (VROM bank mapping)
function Map3Writer(Addr as dword, Value as byte) as boolean
 MapVROMBank8(0,Value) : result=true
 ?? sMap;"Value=";Hex(Value,2)
end

//Initialization 
public procedure InitMap3 : dim i as integer
 MapROMBank16(0,0) 'ROM bank 0
 if nes_ROMSize=16 then MapROMBank16(1,0) else MapROMBank16(1,1) 'ROM bank 1
 if nes_VROMSize>0 then MapVROMBank8(0,0) 'VROM bank 0
 //make both memory banks writable and attach write handler
 MakeReadWrite(32,32) : for i=0 to 31 : mem.KBWriter(32+i)=Map3Writer : next 
end

'------------------------ Mapper 11 (Color Dreams) ----------------------------

//Write handler (ROM and VROM bank mapping)
function Map11Writer(Addr as dword, Value as byte) as boolean
 MapROMBank32(0,Value and 0xF) : MapVROMBank8(0,shr(Value,4)) : result=true
 ?? sMap;"Value=";Hex(Value,2)
end

//Initialization 
public procedure InitMap11 : dim i as integer
 MapROMBank32(0,0) : MapVROMBank8(0,0) 'ROM and VROM bank 0
 //make both memory banks writable and attach write handler
 MakeReadWrite(32,32) : for i=0 to 31 : mem.KBWriter(32+i)=Map11Writer : next 
end


'--------------------------- Mapper 79 (Nina-3) ---------------------------------

//Write handler (ROM and VROM bank mapping) //TOCHECK
function Map79Writer(Addr as dword, Value as byte) as boolean
 MapROMBank32(0,shr(Value,3)) : MapVROMBank8(0,Value and 7) : result=true
 ?? sMap;"Value=";Hex(Value,2)
end

//Initialization 
public procedure InitMap79 : dim i as integer
 MapROMBank32(0,0) : MapVROMBank8(0,0) 'ROM and VROM bank 0
 //make both memory banks writable and attach write handler
 MakeReadWrite(32,32) : for i=0 to 31 : mem.KBWriter(32+i)=Map79Writer : next 
end
