#include "global.h"
#include "agb_sram.h"

// this should be in .bss
extern /*static*/ u16 verifySramFast_Work[80]; // buffer to hold code of VerifySramFast_Core
extern /*static*/ u16 readSramFast_Work[64];  // buffer to hold code of ReadSramFast_Core

u32 (*VerifySramFast)(const u8 *src, u8 *dest, u32 size);  // pointer to verifySramFast_Work
void (*ReadSramFast)(const u8 *src, u8 *dest, u32 size);  // pointer to readSramFast_Work

void ReadSramFast_Core(const u8 *src, u8 *dest, u32 size)
{
    REG_WAITCNT = (REG_WAITCNT & ~3) | 3;
    while (--size != -1)
        *dest++ = *src++;
}

void WriteSramFast(const u8 *src, u8 *dest, u32 size)
{
    REG_WAITCNT = (REG_WAITCNT & ~3) | 3;
    while (--size != -1)
        *dest++ = *src++;
}

u32 VerifySramFast_Core(const u8 *src, u8 *dest, u32 size)
{
    REG_WAITCNT = (REG_WAITCNT & ~3) | 3;
    while (--size != -1)
    {
        if (*dest++ != *src++)
            return (u32)(dest - 1);
    }
    return 0;
}

void SetSramFastFunc(void)
{
    u16 *src;
    u16 *dest;
    u16 size;

    src = (u16 *)ReadSramFast_Core;
    // clear the least significant bit so that we get the actual start address of the function
    src = (u16 *)((uintptr_t)src ^ 1);  // NOTE: In Fire Emblem 8, this is '& ~1' instead of '^ 1'.
    dest = readSramFast_Work;
    // get the size of the function by subtracting the address of the next function
    size = ((uintptr_t)WriteSramFast - (uintptr_t)ReadSramFast_Core) / 2;
    // copy the function into the WRAM buffer
    while (size != 0)
    {
        *dest++ = *src++;
        size--;
    }
    // add 1 to the address of the buffer so that we stay in THUMB mode when bx-ing to the address
    ReadSramFast = (void *)((uintptr_t)readSramFast_Work + 1);

    src = (u16 *)VerifySramFast_Core;
    // clear the least significant bit so that we get the actual start address of the function
    src = (u16 *)((uintptr_t)src ^ 1);  // NOTE: In Fire Emblem 8, this is '& ~1' instead of '^ 1'.
    dest = verifySramFast_Work;
    // get the size of the function by subtracting the address of the next function
    size = ((uintptr_t)SetSramFastFunc - (uintptr_t)VerifySramFast_Core) / 2;
    // copy the function into the WRAM buffer
    while (size != 0)
    {
        *dest++ = *src++;
        size--;
    }
    // add 1 to the address of the buffer so that we stay in THUMB mode when bx-ing to the address
    VerifySramFast = (void *)((uintptr_t)verifySramFast_Work + 1);

    REG_WAITCNT = (REG_WAITCNT & ~3) | 3;
}

u32 WriteAndVerifySramFast(const u8 *src, u8 *dest, u32 size)
{
    u8 i;
    u32 errorAddr;

    // try writing and verifying the data 3 times
    for (i = 0; i < 3; i++)
    {
        WriteSramFast(src, dest, size);
        errorAddr = VerifySramFast(src, dest, size);
        if (errorAddr == 0)
            break;
    }

    return errorAddr;
}
