/* 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 "xfdmaster_internal.h"

extern uint32 Xfd68k[];

struct xfd68KHeader {
	ULONG            xfd68k_ID;
	struct xfdSlave *xfd68k_FirstSlave;
	APTR             xfd68k_LinkerBrain;
	BOOL           (*xfd68k_AnalyseDiz)();
	BOOL           (*xfd68k_UnlinkDiz)();
	CONST_STRPTR     xfd68k_NameDiz;
};

xfdAnalyseFunc LB_AnalyseDiz;
xfdUnlinkFunc LB_UnlinkDiz;
CONST_STRPTR LB_NameDiz;

extern struct xfdSlave S_XPKpacked;

static struct xfdSlave *const XfdInternalSlaves[] = {
	&S_XPKpacked,
	NULL
};

void SetupInternal(struct xfdMasterBase *libBase) {
	struct xfd68KHeader *m68k;
	APTR baseptr;
	struct xfdSlave *const*slaveptr, *lastslave, *slave;
	APTR linker;

	/* internal 68K slaves and linkers */
	m68k = baseptr = Xfd68k;
	if (m68k->xfd68k_ID == XFDF_ID) {
		#define RELOC(ptr) if ((ptr) != NULL) (ptr) = baseptr + (ULONG)(ptr)
		RELOC(m68k->xfd68k_FirstSlave);
		slave = m68k->xfd68k_FirstSlave;
		while (slave != NULL) {
			RELOC(slave->xfds_Next);
			RELOC(slave->xfds_PackerName);
			RELOC(slave->xfds_RecogBuffer);
			RELOC(slave->xfds_DecrunchBuffer);
			RELOC(slave->xfds_RecogSegment);
			RELOC(slave->xfds_DecrunchSegment);
			slave = slave->xfds_Next;
		}
		libBase->xfdm_FirstSlave = m68k->xfd68k_FirstSlave;

		RELOC(m68k->xfd68k_LinkerBrain);
		linker = m68k->xfd68k_LinkerBrain;
		if (linker != NULL) {
			LONG longs;
			while ((longs = READ_WORD_INC(linker)) >= 0) {
				if (longs > 0) linker += (longs + 1) * sizeof(ULONG);
				RELOC(((APTR *)linker)[0]); // Analyze
				RELOC(((APTR *)linker)[1]); // Unlink
				RELOC(((APTR *)linker)[2]); // Name
				linker += 3 * sizeof(APTR);
			}
		}
		libBase->xfdm_LinkerBrain = m68k->xfd68k_LinkerBrain;

		RELOC(m68k->xfd68k_AnalyseDiz);
		RELOC(m68k->xfd68k_UnlinkDiz);
		RELOC(m68k->xfd68k_NameDiz);
		LB_AnalyseDiz = m68k->xfd68k_AnalyseDiz;
		LB_UnlinkDiz = m68k->xfd68k_UnlinkDiz;
		LB_NameDiz = m68k->xfd68k_NameDiz;
	}

	/* internal PPC slaves */
	slaveptr = XfdInternalSlaves;
	while ((slave = *slaveptr++) != NULL) {
		lastslave = libBase->xfdm_FirstSlave;
		if (lastslave == NULL) {
			libBase->xfdm_FirstSlave = slave;
		} else {
			while (lastslave->xfds_Next != NULL) {
				lastslave = lastslave->xfds_Next;
			}
			lastslave->xfds_Next = slave;
		}
	}
}

static void AddExternal(struct xfdMasterBase *libBase, CONST_STRPTR filename) {
	struct DOSIFace *IDOS = libBase->xfdm_IDos;
	BPTR seglist;

	seglist = IDOS->LoadSeg(filename);
	if (seglist != ZERO) {
		Elf32_Handle elfh = NULL;
		struct xfdForeMan *foreman = NULL;

		IDOS->GetSegListInfoTags(seglist,
			GSLI_ElfHandle, &elfh,
			TAG_END);
		if (elfh != NULL) {
			struct ElfIFace *IElf = libBase->xfdm_IElf;
			elfh = IElf->OpenElfTags(
				OET_ElfHandle, elfh,
				TAG_END);
			if (elfh != NULL) {
				struct Elf32_SymbolQuery symq;
				symq.Flags = ELF32_SQ_BYNAME;
				symq.Name = (STRPTR)"xfdForeMan";
				IElf->SymbolQuery(elfh, 1, &symq);
				if (symq.Found) {
					foreman = (struct xfdForeMan *)symq.Value;
				}
				IElf->CloseElfTags(elfh, TAG_END);
			}
		} else {
			foreman = BADDR(seglist) + sizeof(BPTR);
		}

		if (foreman != NULL && foreman->xfdf_ID == XFDF_ID) {
			struct xfdForeMan *lastforeman;
			struct xfdSlave *slave, *lastslave;

			foreman->xfdf_SegList = seglist;
			foreman->xfdf_Next = NULL;

			lastforeman = libBase->xfdm_FirstForeMan;
			if (lastforeman == NULL) {
				libBase->xfdm_FirstForeMan = foreman;
			} else {
				while (lastforeman->xfdf_Next != NULL) {
					lastforeman = lastforeman->xfdf_Next;
				}
				lastforeman->xfdf_Next = foreman;
			}

			slave = foreman->xfdf_FirstSlave;
			lastslave = libBase->xfdm_FirstSlave;
			if (lastslave == NULL) {
				libBase->xfdm_FirstSlave = slave;
			} else {
				while (lastslave->xfds_Next != NULL) {
					lastslave = lastslave->xfds_Next;
				}
				lastslave->xfds_Next = slave;
			}

			while (slave != NULL) {
				slave->xfds_PackerFlags |= XFDPFF_EXTERN;

				if (slave->xfds_Version >= 2 && slave->xfds_ReplaceID != 0) {
					struct xfdSlave *currentslave;

					currentslave = libBase->xfdm_FirstSlave;
					lastslave = NULL;
					while (currentslave != NULL) {
						if (currentslave->xfds_Version >= 2 &&
							currentslave->xfds_SlaveID == slave->xfds_ReplaceID)
						{
							if (lastslave == NULL) {
								libBase->xfdm_FirstSlave = currentslave->xfds_Next;
							} else {
								lastslave->xfds_Next = currentslave->xfds_Next;
							}
						}
						lastslave = currentslave;
						currentslave = currentslave->xfds_Next;
					}
				}

				slave = slave->xfds_Next;
			}
		} else {
			IDOS->UnLoadSeg(seglist);
		}
	}
}

void LoadExternal(struct xfdMasterBase *libBase) {
	struct DOSIFace *IDOS = libBase->xfdm_IDos;
	APTR context;
	struct ExamineData *exd;

	context = IDOS->ObtainDirContextTags(
		EX_StringNameInput, "LIBS:xfd",
		EX_DataFields,      EXF_TYPE|EXF_NAME,
		EX_DoCurrentDir,    TRUE,
		TAG_END);
	if (context != NULL) {
		while ((exd = IDOS->ExamineDir(context)) != NULL) {
			if (EXD_IS_FILE(exd)) {
				AddExternal(libBase, exd->Name);
			}
		}

		IDOS->ReleaseDirContext(context);
	}
}

APTR xfdGetHunk(struct xfdMasterBase *libBase, APTR buffer, LONG length, LONG num) {
	WORD hunks, type;
	LONG size;
	if ((length -= 20) < 0 || READ_ULONG_INC(buffer) != HUNK_HEADER ||
		READ_ULONG_INC(buffer) != 0)
	{
		return NULL;
	}
	hunks = READ_WORD(buffer, 10) - READ_WORD(buffer, 6) + 1;
	if (hunks <= num) {
		return NULL;
	}
	buffer += 12;

	do {
		if ((length -= 4) < 0) {
			return NULL;
		}
		if ((READ_ULONG_INC(buffer) & (3UL<<30)) == (3UL<<30)) {
			if (length < 4) {
				return NULL;
			}
			length -= 4;
			buffer += 4;
		}
	} while (--hunks);

	while (TRUE) {
		if ((length -= 4) < 0) {
			return NULL;
		}
		type = (WORD)READ_ULONG_INC(buffer);
		switch (type) {
			case HUNK_CODE:
			case HUNK_DATA:
				if ((num -= 1) < 0) {
					return buffer - 4;
				}
			case HUNK_NAME:
			case HUNK_DEBUG:
				if ((length -= 4) < 0) {
					return NULL;
				}
				size = READ_ULONG_INC(buffer) << 2;
				if ((length -= size) < 0) {
					return NULL;
				}
				buffer += size;
				break;
			case HUNK_BSS:
				if ((num -= 1) < 0) {
					return buffer - 4;
				}
				if ((length -= 4) < 0) {
					return NULL;
				}
				buffer += 4;
				break;
			case HUNK_RELOC32:
			case HUNK_SYMBOL:
				while (TRUE) {
					if ((length -= 4) < 0) {
						return NULL;
					}
					size = READ_ULONG_INC(buffer) << 2;
					if (size == 0) {
						break;
					}
					size += 4;
					if ((length -= size) < 0) {
						return NULL;
					}
					buffer += size;
				}
				break;
			case HUNK_END:
				break;
			default:
				return NULL;
		}
	}
}

