/* adapted from https://github.com/dimok789/homebrew_launcher */

/****************************************************************************
 *
 * Copyright (C) 2015 Dimok
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************/

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <inttypes.h>
#include <wiiu/os.h>

#include "hbl.h"

#ifdef WIIU_LOG_RPX
#include "../verbosity.h"
#endif

#define MEM_AREA_TABLE              ((s_mem_area*)(MEM_BASE + 0x1600))
#define ELF_DATA_ADDR               (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x00))
#define ELF_DATA_SIZE               (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x04))
#define RPX_MAX_SIZE                (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x0C))
#define RPX_MAX_CODE_SIZE           (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x10))
#define APP_BASE_MEM                ((unsigned char*)(MEM_BASE + 0x2000))

typedef struct _s_mem_area
{
   unsigned int        address;
   unsigned int        size;
   struct _s_mem_area *next;
} s_mem_area;

void SC0x25_KernelCopyData(unsigned int addr, unsigned int src, unsigned int len);

typedef struct _memory_values_t
{
    unsigned int start_address;
    unsigned int end_address;
} memory_values_t;

static const memory_values_t mem_vals_540[] =
{
    { 0x2E609EFC, 0x2FF82000 }, /* 26083 kB */
    { 0x29030800, 0x293F6000 }, /* 3864 kB */
    { 0x288EEC30, 0x28B06800 }, /* 2144 kB */
    { 0x2D3B966C, 0x2D894000 }, /* 4971 kB */
    { 0x2CB56370, 0x2D1EF000 }, /* 6756 kB */
    { 0x2D8AD3D8, 0x2E000000 }, /* 7499 kB */
    { 0x2970200C, 0x298B9800 }, /* 1759 kB */
    { 0x2A057B68, 0x2A1B9000 }, /* 1414 kB */
    { 0x2ABBCC4C, 0x2ACB9000 }, /* 1010 kB */
    {0, 0}
};

static inline void memoryAddArea(int start, int end, int cur_index)
{
   /* Create and copy new memory area */
   s_mem_area *mem_area        = MEM_AREA_TABLE;

   mem_area[cur_index].address = start;
   mem_area[cur_index].size    = end - start;
   mem_area[cur_index].next    = 0;

   /* Fill pointer to this area in the previous area */
   if (cur_index > 0)
      mem_area[cur_index - 1].next = &mem_area[cur_index];
}

void *getApplicationEndAddr(void)
{
   extern u32 _end[];
   if ((u32)_end >= 0x01000000)
      return APP_BASE_MEM;
   return _end;
}

/* Create memory areas arrays */
static void memoryInitAreaTable(u32 args_size)
{
    int i = 0;
    u32 ApplicationMemoryEnd = (u32)getApplicationEndAddr() + args_size;

    /* This one seems to be available on every firmware and therefore its our code area but also our main RPX area behind our code */
    /* 22876 kB - our application      ok  */
    memoryAddArea(ApplicationMemoryEnd + 0x30000000, 0x30000000 + 0x01E20000, 0);

    const memory_values_t * mem_vals = mem_vals_540;

    /* Fill entries */
    while (mem_vals[i].start_address)
    {
        memoryAddArea(mem_vals[i].start_address, mem_vals[i].end_address, i + 1);
        i++;
    }
}

static int HomebrewCopyMemory(u8 *address, u32 bytes, u32 args_size)
{
   args_size += 0x7;
   args_size &= ~0x7;
   if (args_size > 0x10000)
      args_size = 0x10000;

   memoryInitAreaTable(args_size);

   RPX_MAX_SIZE = 0x40000000;
   RPX_MAX_CODE_SIZE = 0x03000000;

   /* check if we load an RPX or an ELF */
   if (*(u16 *)&address[7] != 0xCAFE)
   {
      /* assume ELF */
      ELF_DATA_ADDR = (u32)getApplicationEndAddr() + args_size;
      if (ELF_DATA_ADDR >= 0x01000000)
         return -1;
   }
   else
   {
      /* RPX */
      ELF_DATA_ADDR = MEM_AREA_TABLE->address;
   }

   /*! if we load an ELF file */
   if (ELF_DATA_ADDR < 0x01000000)
   {

      if ((ELF_DATA_ADDR + bytes) > 0x01000000)
         return -1;

      memcpy((void *)ELF_DATA_ADDR, address, bytes);
      ELF_DATA_SIZE = bytes;
   }
   else
   {
      u32 done            = 0;
      u32 mapPosition     = 0;

      DCFlushRange(address, bytes);

      u32 addressPhysical = (u32)OSEffectiveToPhysical(address);

      s_mem_area *mem_map = MEM_AREA_TABLE;

      while ((done < bytes) && mem_map)
      {
         if (mapPosition >= mem_map->size)
         {
            mem_map = mem_map->next;

            if (!mem_map)
               return -1;

            mapPosition = 0;
         }

         u32 blockSize = bytes - done;

         if ((mapPosition + blockSize) > mem_map->size)
            blockSize = mem_map->size - mapPosition;

         SC0x25_KernelCopyData(mem_map->address + mapPosition, (addressPhysical + done), blockSize);

         mapPosition += blockSize;
         done += blockSize;
      }

      ELF_DATA_SIZE = done;
   }
   return bytes;
}

#ifdef WIIU_LOG_RPX

#define LINE_LEN 32
/**
 * This is called between when the RPX is read off the storage medium and
 * when it is sent to the loader. It prints a hexdump to the logger, which
 * can then be parsed out by a script.
 *
 * If we can at least semi-reliably generate the "System Memory Error", this
 * can be useful in identifying if the problem is corrupt file i/o vs in-memory
 * corruption.
 */
void log_rpx(const char *filepath, unsigned char *buf, size_t len)
{
   int i;
   unsigned int line_buffer[LINE_LEN];

   RARCH_LOG("=== BEGIN file=%s size=%d ===\n", filepath, len);

   for (i = 0; i < len; i++)
   {
      int offset = i % LINE_LEN;
      line_buffer[offset] = buf[i];

      if (offset == (LINE_LEN-1))
      {
         RARCH_LOG("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
               line_buffer[0], line_buffer[1], line_buffer[2], line_buffer[3],
               line_buffer[4], line_buffer[5], line_buffer[6], line_buffer[7],
               line_buffer[8], line_buffer[9], line_buffer[10], line_buffer[11],
               line_buffer[12], line_buffer[13], line_buffer[14], line_buffer[15],
               line_buffer[16], line_buffer[17], line_buffer[18], line_buffer[19],
               line_buffer[20], line_buffer[21], line_buffer[22], line_buffer[23],
               line_buffer[24], line_buffer[25], line_buffer[26], line_buffer[27],
               line_buffer[28], line_buffer[29], line_buffer[30], line_buffer[31]);
      }
   }
   if ((len % LINE_LEN) != 0)
   {
      for (i = (LINE_LEN - (len % LINE_LEN)); i < LINE_LEN; i++)
         line_buffer[i] = 0;

      RARCH_LOG("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
            line_buffer[0], line_buffer[1], line_buffer[2], line_buffer[3],
            line_buffer[4], line_buffer[5], line_buffer[6], line_buffer[7],
            line_buffer[8], line_buffer[9], line_buffer[10], line_buffer[11],
            line_buffer[16], line_buffer[17], line_buffer[18], line_buffer[19],
            line_buffer[20], line_buffer[21], line_buffer[22], line_buffer[23],
            line_buffer[24], line_buffer[25], line_buffer[26], line_buffer[27],
            line_buffer[28], line_buffer[29], line_buffer[30], line_buffer[31]);

   }
   RARCH_LOG("=== END %s ===\n", filepath);
}

#endif

int HBL_loadToMemory(const char *filepath, u32 args_size)
{
   int ret;
   FILE *fp;
   u32 bytesRead = 0;
   u32 fileSize  = 0;
   if (!filepath || !*filepath)
      return -1;

   if (!(fp = fopen(filepath, "rb")))
      return -1;

   fseek(fp, 0, SEEK_END);
   fileSize = ftell(fp);
   fseek(fp, 0, SEEK_SET);

   size_t buffer_size = (fileSize + 0x3f) & ~0x3f;
   unsigned char *buffer = (unsigned char *) memalign(0x40, buffer_size);

   if (!buffer)
      return -1;

   memset(buffer, 0, buffer_size);

   /* Copy rpl in memory */
   while (bytesRead < fileSize)
   {
      int ret;
      u32 blockSize = 0x8000;

      if (blockSize > (fileSize - bytesRead))
         blockSize = fileSize - bytesRead;

      ret = fread(buffer + bytesRead, 1, blockSize, fp);

      if (ret <= 0)
         break;

      bytesRead += ret;
   }

   if (bytesRead != fileSize)
   {
      free(buffer);
      return -1;
   }
#ifdef WIIU_LOG_RPX
   log_rpx(filepath, buffer, bytesRead);
#endif

   ret = HomebrewCopyMemory(buffer, bytesRead, args_size);

   free(buffer);

   if (ret < 0)
      return -1;

   return fileSize;
}
