#include <EDK.h>
#include "General.h"
#include "CIECMaster.h"

#define Class CIECMaster
extern Class MSVC4Bug;

// Spaghetti warning: Changes C64 CPU and RAM!


////////////////////////////////////////////////////////////////////////////////

proc(Listen)

  push EBX
  push ESI
  mov ESI,ECX

  // get device number 0-31
  mov ECX,mvar(pCPU)
  mov AL,[ECX]CPU65xx.bAccu
  and EAX,31
  mov mvar(iDevice),EAX

  // check if device supports Listen
  mov ECX,mvar(apDevice[EAX*4])
  and ECX,ECX
  je NoListen
  mov EDX,[ECX]CIECDevice.pfnListen
  and EDX,EDX
  je NoListen

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no Listen function, use IEC bus
NoListen:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(ListenCh)

  push EBX
  push ESI
  mov ESI,ECX

  // check if device supports ListenCh
  mov ECX,mvar(iDevice)
  mov ECX,mvar(apDevice[ECX*4])
  and ECX,ECX
  je NoListenCh
  mov EDX,[ECX]CIECDevice.pfnListenCh
  and EDX,EDX
  je NoListenCh

  // get data
  mov EBX,mvar(pCPU)
  xor EAX,EAX
  mov AL,[EBX]CPU65xx.bAccu

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no ListenCh function, use IEC bus
NoListenCh:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(IECOut)

  push EBX
  push ESI
  mov ESI,ECX

  // check if device supports Receive
  mov ECX,mvar(iDevice)
  mov ECX,mvar(apDevice[ECX*4])
  and ECX,ECX
  je NoReceive
  mov EDX,[ECX]CIECDevice.pfnReceive
  and EDX,EDX
  je NoReceive

  // get data
  mov EBX,mvar(pCPU)
  xor EAX,EAX
  mov AL,[EBX]CPU65xx.bAccu

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no Receive function, use IEC bus
NoReceive:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(Unlisten)

  push EBX
  push ESI
  mov ESI,ECX

  // check if device supports Unlisten
  mov ECX,mvar(iDevice)
  mov ECX,mvar(apDevice[ECX*4])
  and ECX,ECX
  je NoUnlisten
  mov EDX,[ECX]CIECDevice.pfnUnlisten
  and EDX,EDX
  je NoUnlisten

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no Unlisten function, use IEC bus
NoUnlisten:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(Talk)

  push EBX
  push ESI
  mov ESI,ECX

  // get device number 0-31
  mov ECX,mvar(pCPU)
  mov AL,[ECX]CPU65xx.bAccu
  and EAX,31
  mov mvar(iDevice),EAX

  // check if device supports Talk
  mov ECX,mvar(apDevice[EAX*4])
  and ECX,ECX
  je NoTalk
  mov EDX,[ECX]CIECDevice.pfnTalk
  and EDX,EDX
  je NoTalk

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no Talk function, use IEC bus
NoTalk:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(TalkCh)

  push EBX
  push ESI
  mov ESI,ECX

  // check if device supports TalkCh
  mov ECX,mvar(iDevice)
  mov ECX,mvar(apDevice[ECX*4])
  and ECX,ECX
  je NoTalkCh
  mov EDX,[ECX]CIECDevice.pfnTalkCh
  and EDX,EDX
  je NoTalkCh

  // get data
  mov EBX,mvar(pCPU)
  xor EAX,EAX
  mov AL,[EBX]CPU65xx.bAccu

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no TalkCh function, use IEC bus
NoTalkCh:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(IECIn)

  push EBX
  push ESI
  mov ESI,ECX

  // check if device supports Send
  mov ECX,mvar(iDevice)
  mov ECX,mvar(apDevice[ECX*4])
  and ECX,ECX
  je NoSend
  mov EDX,[ECX]CIECDevice.pfnSend
  and EDX,EDX
  je NoSend

  // call device function
  call EDX

  // store data and result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,AL
  mov byte ptr ([ECX]CPU65xx.iNCFlag),AL
  mov [ECX]CPU65xx.bZVal,AL
  mov [EDX+0xA4],AL
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no Send function, use IEC bus
NoSend:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

proc(Untalk)

  push EBX
  push ESI
  mov ESI,ECX

  // check if device supports Untalk
  mov ECX,mvar(iDevice)
  mov ECX,mvar(apDevice[ECX*4])
  and ECX,ECX
  je NoUntalk
  mov EDX,[ECX]CIECDevice.pfnUntalk
  and EDX,EDX
  je NoUntalk

  // call device function
  call EDX

  // store result
  mov ECX,mvar(pCPU)
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov [ECX]CPU65xx.bAccu,0
  or [EDX+0x90],AH
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xEDAC
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

  // device has no Untalk function, use IEC bus
NoUntalk:
  mov EAX,mvar(pCPU)
  mov EAX,[EAX]CPU65xx.iRWAddress
  sub EAX,0xE000
  add EAX,mvar(pbKernal)
  mov AL,[EAX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////

static CIECMaster* gpIECMaster;
static byte* gpbRAM;
static char gacMessage[40];

flag DoLoad() {

  // check verify flag
  if (gpbRAM[0x93] != 0) {
    return FALSE;
  }

  // get device
  CIECDevice* pDevice = gpIECMaster->apDevice[gpbRAM[0xBA]];
  if (pDevice == NULL) {
    return FALSE;
  }

  // load file into buffer
  byte* pbBuffer = MemAlloc(65536);
  int iSize = pDevice->Load(CString((char*)gpbRAM + *(word*)(gpbRAM + 0xBB), gpbRAM[0xB7]), pbBuffer, 65536);
  if (iSize == 0) {
    MemFree(pbBuffer);
    return FALSE;
  }

  // check for file size
  word wStart = *(word*)(gpbRAM + 0xC3);
  if (gpbRAM[0xB9] != 0) {
    wStart = *(word*)pbBuffer;
  }
  word wEnd = (word)(wStart + iSize - 2);

  // check for loading into IO address space
  if (wStart < 2 || (wStart < 0xE000 && wEnd > 0xD000) || (wEnd < wStart && wEnd != 0)) {
    MemFree(pbBuffer);
    return FALSE;
  }

  // copy buffer into RAM
  memcpy(gpbRAM + wStart, pbBuffer + 2, iSize - 2);
  MemFree(pbBuffer);

  // set result
  gpbRAM[0x90] = 0;
  *(word*)(gpbRAM + 0xAE) = wEnd;

  // display FAST LOADING string
  wsprintf(gacMessage, "FAST LOADING FROM $%04X TO $%04X", wStart, wEnd);
  if ((gpbRAM[0x9D] & 0x80) != 0) {
    byte* pbSource = (byte*)gacMessage;
    byte* pbDest = gpbRAM + *(word*)(gpbRAM + 0xD1);
    while (*pbSource != 0) {
      const byte abCBMtoScreen[8] = {
        0x80,0x20,0x00,0x40,0xC0,0x60,0x40,0x60
      };
      *pbDest++ = (byte)(*pbSource & 31 | abCBMtoScreen[*pbSource >> 5]);
      pbSource++;
    }
  }

  return TRUE;
}


proc(Load)

  push EBX
  push ESI
  mov ESI,ECX

  // call Load function
  mov ECX,mvar(pCPU)
  mov gpIECMaster,ThisReg
  mov EDX,[ECX]CPU16x4k.apbReadMapping[0]
  mov gpbRAM,EDX
  call DoLoad
  and EAX,EAX
  je NoFastLoad

  // CLI : JMP $F5A9
  mov ECX,mvar(pCPU)
  mov byte ptr ([ECX]CPU65xx.iNCFlag+1),0
  mov EAX,[ECX]CPU65xx.pbPCBase
  add EAX,0xF5A9
  mov [ECX]CPU65xx.pbPCPtr,EAX
  mov AL,0x58

  pop ESI
  pop EBX
  ret

NoFastLoad:
  mov EBX,mvar(pCPU)
  mov EBX,[EBX]CPU65xx.iRWAddress
  sub EBX,0xE000
  add EBX,mvar(pbKernal)
  mov AL,[EBX]

  pop ESI
  pop EBX
  ret

endp


////////////////////////////////////////////////////////////////////////////////
// constructor

CIECMaster::CIECMaster() {

  memset(apDevice, 0, sizeof apDevice);
  iDevice = 0;
  pCPU = NULL;

  Reset.Init("Reset", this);
  SRQ.Init("SRQ", this);
  ATN.Init("ATN", this);
  Clock.Init("Clock", this);
  Data.Init("Data", this);

}
