#include <proto/exec.h>
#include <exec/execbase.h>

#include "a2ixlibrary.h"

#ifdef DEBUG
void dprintf(const char *,...);
#define DB(x) x
#else
#define DB(x)
#endif

#define __a4_offset (-4 - 4 * LIBRARY_ID)

typedef void (*func_ptr) (void);

extern int __datadata_relocs();
/*extern int __a4_offset();*/

extern void (*__dtrslist[])() __attribute__((section(".dtors")));

/* The a4 pointers are stored *before* the start of this struct! */
struct user {
	/* both a magic cookie and a way to get at the library base thru u */
	void 	*u_ixbase;
	void 	*u_reserved;		/* for future use */

	long	u_a4_pointers_size;	/* number of a4 pointers */
	long	u_a4_pointers[0];
};

#ifdef INSTANCE_LIBS
INSTANCE_LIBS
#endif

#define u  (*(struct user *)(SysBase->ThisTask->tc_TrapData))

/* have to do this this way, or it is done base-relative.. */
int __dbsize(void);
asm("
	.section  \".text\"
	.type  	  __dbsize,@function
	.globl    __dbsize
__dbsize:
	lis 3,__sdata_size@ha
	addis 3,3,__sbss_size@ha
	addi 3,3,__sdata_size@l
	addi 3,3,__sbss_size@l
	blr
__end__dbsize:
	.size	  __dbsize,__end__dbsize-__dbsize
");

void *__get_data(void);
asm("
	.section  \".text\"
	.type  	  __get_data,@function
	.globl    __get_data
__get_data:
	lis 3,__r13_init@ha
	addi 3,3,__r13_init@l
	addi 3,3,-0x8000
	blr
__end__get_data:
	.size	  __get_data,__end__get_data-__get_data
");

long lib_open(long base)
{
  char *origdata, *newdata;
  int datasize, numrel, i;
  long *relocs = (long *)__datadata_relocs;
  int offset = (-(long)__a4_offset) / 4 - 1;
  /* cannot use the global SysBase variable because A4 isn't (yet) initialized */
  struct ExecBase *SysBase = (struct ExecBase *)(*((long *)4));

  DB(dprintf("lib_open(%lx), offset = %d, relocs = %lx\n", base, offset, relocs);)
  if (offset < 0 || offset >= u.u_a4_pointers_size)
    return 0;
  if (u.u_a4_pointers[-offset - 4])
    return base;

  datasize = __dbsize();
  origdata = __get_data();
  newdata = AllocMem(datasize, 0);
  DB(dprintf("datasize = %ld, origdata = %lx, newdata = %lx\n", datasize, origdata, newdata);)
  if (!newdata) {
    return 0;
  }
  CopyMem(origdata, newdata, datasize);
  numrel = relocs[0];
  while (numrel--)
    *(long *)(newdata + *++relocs) -= origdata - newdata;
  u.u_a4_pointers[-offset - 4] = (long)(newdata + 0x8000);
#ifdef INSTANCE_LIBS
  if (!open_libs()) {
    close_libs();
    u.u_a4_pointers[-offset - 4] = 0;
    FreeMem(newdata, datasize);
    return 0;
  }
#endif
#ifdef CALL_INITS
  CALL_INITS
#endif
  DB(dprintf("done\n");)
  return base;
}

void __LibCloseInstance(void)
{
  int datasize;
  int offset = (-(long)__a4_offset) / 4 - 1;
  /* A4 may be invalid here, so we don't use the global SysBase */
  struct ExecBase *SysBase = (struct ExecBase *)(*((long *)4));

  DB(dprintf("closeInstance()\n");)
  if (u.u_a4_pointers[-offset - 4])
  {
    int i, size;

    size = (int)__dtrslist[-2] / sizeof(__dtrslist[0]) - 2;
    for (i = 0; i < size; ++i) {
      if (__dtrslist[i])
	__dtrslist[i]();
    }
#ifdef INSTANCE_LIBS
    close_libs();
#endif
    datasize = __dbsize();
    FreeMem((void *)(u.u_a4_pointers[-offset - 4] - 0x8000), datasize);
    u.u_a4_pointers[-offset - 4] = 0;
  }
}

asm("
	.section \".dtors\",\"a\",@progbits
__dtrslist:
	.long 0
");

