/*
    genstubdll - Generate stub-library acting like an import-library
                 using .def file syntax.
    Copyright (C) 2014-2016  mingw-w64 project

    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 <stdlib.h>
#include <string.h>
#include <direct.h>

#include "token.h"

static void generate_stub_c (FILE *fp);

const char *cur_IAT = NULL;
const char *cur_dllsym = NULL;

static void printHeader (FILE *fp)
{
  fprintf (fp, "/* This file was generated by genstubdll tool.\n");
  fprintf (fp, " * Written by Kai Tietz 2014\n");
  fprintf (fp, " */\n");
      fprintf (fp, "\n");
}

static void init_curIAT (void)
{
  char *h;
  char *r;

  h = (char *) strdup (cur_libname);
  while ((r = strchr (h, '.')) != NULL)
    *r = '_';
  cur_dllsym = unifyStr (h);
  free (h);

  cur_IAT = unifyCat ("__IAT_", cur_dllsym);
}

static void printStubCode (FILE *fp, const char *hname, const char *sym)
{
  printHeader (fp);

  fprintf (fp, "\t.file \"stub_%s\"\n", hname);
  fprintf (fp, "\t.text\n");
  fprintf (fp, "#ifdef __x86_64__\n");
  fprintf (fp,"\t.globl %s\n", sym);
  fprintf (fp, "%s:\n", sym);
  fprintf (fp, "\tjmp *__imp_%s(%%rip)\n", sym);
  fprintf (fp, "#elif __i386__\n");
  fprintf (fp,"\t.globl _%s\n", sym);
  fprintf (fp, "_%s:\n", sym);
  fprintf (fp, "\tjmp *__imp__%s\n", sym);
  fprintf (fp, "#else\n"
    "#error \"Unknown target\"\n"
    "#endif\n");
}

void
outputSyms (void)
{
  FILE *fp;
  sSymbol *l;
  const char *pth = unifyCat ("./s_", cur_libname);

  _mkdir (pth);

  init_curIAT ();

  pth = unifyCat (pth, "/");

  fp = fopen (unifyCat (pth, "do_init.c"), "wb");
  generate_stub_c (fp);
  fclose (fp);

  fp = fopen (unifyCat (pth, "sec_start.S"), "wb");
  printHeader (fp);

  fprintf (fp, "\t.section .rdata, \"dr\"\n");
  fprintf (fp, "__def_module:\n");
  fprintf (fp, "\t.ascii \"%s\"\n", cur_libname);
  fprintf (fp,"\t.section .rdata$imp.%s, \"dr\"\n", cur_libname);
  fprintf (fp, "#ifdef __x86_64__\n");
  fprintf (fp, "\t.align 16\n");
  fprintf (fp, "\t.globl %s\n%s:\n", cur_IAT, cur_IAT);
  fprintf (fp, "\t.quad __def_module, 0\n");
  fprintf (fp, "#elif __i386__\n");
  fprintf (fp, "\t.align 8\n");
  fprintf (fp, "\t.globl _%s\n_%s:\n", cur_IAT, cur_IAT);
  fprintf (fp, "\t.long __def_module, 0\n");
  fprintf (fp, "#else\n#error \"Unknown target\"\n#endif\n");
  fprintf (fp, "\n");
  fprintf (fp,"\t.section .rdata$imp.xxxxxxxx, \"dr\"\n", cur_libname);
  fprintf (fp,"\t.linkonce\n");
  fprintf (fp, "#ifdef __x86_64__\n");
  fprintf (fp, "\t.align 8\n");
  fprintf (fp, "\t.quad 0, 0\n");
  fprintf (fp, "#elif __i386__\n");
  fprintf (fp, "\t.align 4\n");
  fprintf (fp, "\t.long 0, 0\n");
  fprintf (fp, "#else\n#error \"Unknown target\"\n#endif\n");
  fclose (fp);

  l = t_sym;
  while (l != NULL)
    {
      const char *hname = unifyCat (l->sym, ".S");

      if (l->is_data == 0)
      {
	fp = fopen (unifyCat (pth, unifyCat ("stub_", hname)), "wb");

	printStubCode (fp, hname, l->sym);

	fclose (fp);
      }
      fp = fopen (unifyCat (pth, unifyCat ("imp_", hname)), "wb");

      printHeader (fp);

      fprintf (fp, "\t.file \"imp_%s\"\n", hname);

      fprintf (fp, "#ifdef __x86_64__\n");
      fprintf (fp, "\t.section .data$%s.iat.%s,\"w\"\n", cur_libname, l->sym);
      fprintf (fp,"\t.globl __imp_%s\n", l->sym);
      fprintf (fp, "\t.align 8\n");
      fprintf (fp, "__imp_%s:\n", l->sym);
      fprintf (fp, "\t.quad %s\n", (l->libsym[0] != 0 ? l->libsym : "0"));
      fprintf (fp, "\t.section .rdata, \"dr\"\n");
      fprintf (fp, "__imp_%s_name:\n", l->sym);
      fprintf (fp, "\t.ascii \"%s\"\n", l->sym);
      fprintf (fp, "\t.section .rdata$imp.%s.%s, \"dr\"\n", cur_libname, l->sym);
      fprintf (fp, "\t.align 8\n");
      fprintf (fp, "\t.quad __imp_%s_name, __imp_%s\n", l->sym, l->sym);

      fprintf (fp, "#elif __i386__\n");
      fprintf (fp, "\t.section .data$%s.iat.%s, \"w\"\n", cur_libname, l->sym);
      fprintf (fp, "\t.align 4\n");
      fprintf (fp,"\t.globl __imp__%s\n", l->sym);
      fprintf (fp, "__imp__%s:\n", l->sym);
      fprintf (fp, "\t.long %s%s\n",
	       (l->libsym[0] != 0 ? "_" : ""),
	       (l->libsym[0] != 0 ? l->libsym : "0"));
      fprintf (fp, "\t.section .rdata, \"dr\"\n");
      fprintf (fp, "__imp__%s_name:\n", l->sym);
      fprintf (fp, "\t.ascii \"%s\"\n", l->sym);
      fprintf (fp, "\t.section .rdata$imp.%s.%s, \"dr\"\n", cur_libname, l->sym);
      fprintf (fp, "\t.align 4\n");
      fprintf (fp, "\t.long __imp_%s__name, __imp__%s\n", l->sym, l->sym);

      fprintf (fp, "#else\n#error \"Unknown target\"\n#endif\n");

      fclose (fp);

      if (l->subs != NULL)
      {
	sSymbol *p;

	p = l->subs;
	while (p != NULL)
	{
	  const char *hn = unifyCat (p->sym, ".S");
	  fp = fopen (unifyCat (pth, unifyCat ("stub_", hn)), "wb");

	  printStubCode (fp, hn, p->sym);

	  fclose (fp);

	  p = p->next;
	}
	
	p = l->subs;
	while (p != NULL)
	{
	  const char *hn = unifyCat (p->sym, ".S");
	  fp = fopen (unifyCat (pth, unifyCat ("imp_", hn)), "wb");

	  printHeader (fp);
	  
	  fprintf (fp, "\t.file \"imp_%s\"\n", hn);

	  fprintf (fp, "#ifdef __x86_64__\n");
	  fprintf (fp, "\t.section .data$%s.iat.%s,\"w\"\n", cur_libname, p->sym);
	  fprintf (fp,"\t.globl __imp_%s\n", p->sym);
	  fprintf (fp, "\t.align 8\n");
	  fprintf (fp, "__imp_%s:\n", p->sym);
	  fprintf (fp, "\t.quad %s\n", (p->libsym[0] != 0 ? p->libsym : (l->libsym[0] != 0 ? l->libsym : "0")));

	  fprintf (fp, "\t.section .rdata$imp.%s.%s, \"dr\"\n", cur_libname, p->sym);
	  fprintf (fp, "\t.align 8\n");
	  fprintf (fp, "\t.quad __imp_%s, 1, __imp_%s, 0\n", l->sym, p->sym);

	  fprintf (fp, "#elif __i386__\n");
	  fprintf (fp, "\t.section .data$%s.iat.%s, \"w\"\n", cur_libname, p->sym);
	  fprintf (fp, "\t.align 4\n");
	  fprintf (fp,"\t.globl __imp__%s\n", p->sym);
	  fprintf (fp, "__imp__%s:\n", p->sym);
	  fprintf (fp, "\t.long %s\n", (p->libsym[0] != 0 ? p->libsym : (l->libsym[0] != 0 ? l->libsym : "0")));

	  fprintf (fp, "\t.section .rdata$imp.%s.%s, \"dr\"\n", cur_libname, p->sym);
	  fprintf (fp, "\t.align 4\n");
	  fprintf (fp, "\t.long __imp__%s, 1, __imp__%s, 0\n", l->sym, p->sym);

	  fprintf (fp, "#else\n#error \"Unknown target\"\n#endif\n");

	  fclose (fp);

	  p = p->next;
	}
      }

      l = l->next;
    }
}

static void generate_stub_c (FILE *fp)
{
  printHeader (fp);
  fprintf (fp,
    "#include <windows.h>\n"
    "#include <stdlib.h>\n"
    "\n"
    "extern size_t %s[];\n"
    "\n"
    "int do_stub_%s (void);\n"
    "\n", cur_IAT, cur_dllsym);
  fprintf (fp,
    "int\n"
    "do_stub_%s (void)\n"
    "{\n"
    "  const char *pc, *dllname = \"\";\n"
    "  int i;\n"
    "  size_t *pimp, *piat, p;\n"
    "  HMODULE hmod = NULL;\n"
    "\n"
    "  for (i = 0; %s[i] != 0;)\n"
    "    {\n"
    "    if (%s[i+1] == 0)\n", cur_dllsym, cur_IAT, cur_IAT);
  fprintf (fp,
    "      {\n"
    "\tdllname = (const char *) %s[0];\n"
    "\tif (!hmod)\n"
    "\t  hmod = GetModuleHandleA (dllname);\n"
    "\ti += 2;\n"
    "      }\n", cur_IAT);

  fprintf (fp,
    "    else if (%s[i+1] == 1)\n"
    "      {\n"
    "\tpiat = (size_t *) %s[i];\n"
    "\tp = piat[0];\n"
    "\ti += 2;\n"
    "\twhile ((piat = (size_t *) %s[i]) != NULL)\n"
    "\t  {\n"
    "\t    piat[0] = p;\n"
    "\t    ++i;\n"
    "\t  }\n"
    "\t++i;\n"
    "      }\n", cur_IAT, cur_IAT, cur_IAT);

  fprintf (fp,
    "    else\n"
    "      {\n"
    "\tif (!hmod)\n"
    "\t  hmod = LoadLibraryA (\"%s\");\n"
    "\tpc = (const char *) %s[i];\n"
    "\tpimp = (size_t *) %s[i+1];\n"
    "\tp = (size_t) GetProcAddress (hmod, pc);\n"
    "\n"
    "\tif (p)\n"
    "\t  pimp[0] = p;\n"
    "\ti += 2;\n"
    "      }\n", cur_libname, cur_IAT, cur_IAT);

  fprintf (fp,
    "  }\n"
    "\n"
    "  return 0;\n"
    "}\n");
}
