/* OpenAmiga.org
 *  This file is part of the OpenAmiga "xfdmaster library project"
 *
 * For more information look at:
 *   http://openamiga.org/?function=viewproject&projectid=67
 */

/****************************************************************************
*   XFD library system for cruncher handling
*   Copyright (C) 1994-1998 by Georg Hörmann
*   Copyright (C) 1998 by Dirk Stöcker <soft@dstoecker.de>
*   Copyright (C) 2014 by Fredrik Wikström <fredrik@a500.org>
*
*   This library is free software; you can redistribute it and/or
*   modify it under the terms of the GNU Lesser General Public
*   License as published by the Free Software Foundation; either
*   version 2.1 of the License, or (at your option) any later version.
*
*   This library 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
*   Lesser General Public License for more details.
*
*   You should have received a copy of the GNU Lesser General Public
*   License along with this library; if not, write to the Free Software
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
****************************************************************************/

#include <interfaces/xfdmaster.h>
#include "../xfdmaster_vectors.h"
#include "../xfdmaster_internal.h"

/****** xfdmaster.library/xfdRelocate ******************************************
*
*   NAME
*      xfdRelocate -- Description
*
*   SYNOPSIS
*      UWORD xfdRelocate(APTR buffer, ULONG length, ULONG * result, 
*          ULONG mode);
*
*   FUNCTION
*
*   INPUTS
*       buffer - 
*       length - 
*       result - 
*       mode - 
*
*   RESULT
*       The result ...
*
*   EXAMPLE
*
*   NOTES
*
*   BUGS
*
*   SEE ALSO
*
*****************************************************************************
*
*/

static APTR GetHunk(ULONG *result, LONG num) {
	do {
		result = BADDR(*result);
	} while (num--);
	return result + 1;
}

UWORD _xfdMaster_xfdRelocate(struct xfdMasterIFace *Self, APTR buffer,
	ULONG length, ULONG *result, ULONG mode)
{
	DEBUGF("xfdRelocate(0x%08lx, 0x%08lx, %lu, 0x%08lx, %08lx)\n",
		Self, buffer, length, result, mode);
	struct xfdMasterBase *libBase = (struct xfdMasterBase *)Self->Data.LibBase;
	struct ExecIFace *IExec = libBase->xfdm_IExec;
	ULONG *lastseg = result;
	UWORD error;
	APTR bufend, seg, hunk, base;
	LONG hunks, num, relocs, offs;
	ULONG size;
	ULONG memtype;
	WORD type;
	BPTR seglist;

	*result = ZERO;

	if (buffer == NULL || length == 0) {
		return XFDERR_NOSOURCE;
	}

	error = Self->xfdTestHunkStructureFlags(buffer, length, XFDTHF_NOOVERLAYS);
	if (error != XFDERR_OK) {
		return error;
	}

	bufend = buffer + length;
	hunks = READ_LONG(buffer, 16) - READ_LONG(buffer, 12) + 1;
	buffer += 20;

	do {
		size = READ_ULONG_INC(buffer);
		memtype = MEMF_PUBLIC|MEMF_CLEAR;
		switch (mode) {
			case XFDREL_DEFAULT:
				if (size & HUNKF_CHIP) memtype |= MEMF_CHIP;
				if (size & HUNKF_FAST) memtype |= MEMF_FAST;
				break;
			case XFDREL_FORCECHIP:
				memtype |= MEMF_CHIP;
				break;
			case XFDREL_FORCEFAST:
				memtype |= MEMF_FAST;
				break;
			default:
				error = XFDERR_BADRELMODE;
				goto MemError;
		}

		size = (size + 2) << 2;
		seg = IExec->AllocMem(size, memtype);
		if (seg == NULL) {
			error = XFDERR_NOMEMORY;
			goto MemError;
		}

		WRITE_ULONG_INC(seg, size);
		*lastseg = MKBADDR(seg);
		lastseg = seg;
	} while (--hunks);

	num = -1;

	while (buffer != bufend) {
		type = (WORD)READ_ULONG_INC(buffer);
		switch (type) {
			case HUNK_NAME:
			case HUNK_DEBUG:
				size = READ_ULONG_INC(buffer) << 2;
				buffer += size;
				break;
			case HUNK_SYMBOL:
				while ((size = READ_ULONG_INC(buffer) << 2)) {
					buffer += size + 4;
				}
				break;
			case HUNK_CODE:
			case HUNK_DATA:
				hunk = GetHunk(result, ++num);
				if ((size = READ_ULONG_INC(buffer) << 2)) {
					IExec->CopyMem(buffer, hunk, size);
					buffer += size;
				}
				break;
			case HUNK_BSS:
				num++;
				buffer += 4;
				break;
			case HUNK_RELOC32:
				hunk = GetHunk(result, num);
				while ((relocs = READ_ULONG_INC(buffer))) {
					base = GetHunk(result, READ_ULONG_INC(buffer));
					do {
						offs = READ_ULONG_INC(buffer);
						WRITE_APTR(hunk, offs, base + READ_ULONG(hunk, offs));
					} while (--relocs);
				}
				break;
			case HUNK_DREL32:
			case HUNK_RELOC32SHORT:
				hunk = GetHunk(result, num);
				while ((relocs = READ_UWORD_INC(buffer))) {
					base = GetHunk(result, READ_UWORD_INC(buffer));
					do {
						offs = READ_UWORD_INC(buffer);
						WRITE_APTR(hunk, offs, base + READ_ULONG(hunk, offs));
					} while (--relocs);
				}
				if ((bufend - buffer) & 0x2) {
					buffer += 2;
				}
				break;
			default:
				error = XFDERR_UNSUPPORTEDHUNK;
				goto MemError;
		}
	}

	return XFDERR_OK;

MemError:
	seglist = *result;
	while ((seg = BADDR(seglist))) {
		seglist = READ_ULONG(seg, 0);
		seg -= 4;
		IExec->FreeMem(seg, READ_ULONG(seg, 0));
	}
	*result = ZERO;

	return error;
}

