cog/Frameworks/libsidplay/sidplay-residfp-code/libsidplayfp/src/reloc65.cpp

297 lines
6.9 KiB
C++

/*
* This file is part of libsidplayfp, a SID player engine.
*
* Copyright (C) 2013-2016 Leandro Nini
* Copyright (C) 2001 Dag Lem
* Copyright (C) 1989-1997 André Fachat (a.fachat@physik.tu-chemnitz.de)
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "reloc65.h"
#include <cstring>
#include "sidplayfp/siddefs.h"
/// 16 bit header
const int HEADER_SIZE = (8 + 9 * 2);
/// Magic number
const unsigned char o65hdr[] = {1, 0, 'o', '6', '5'};
/**
* Read a 16 bit word from a buffer at specific location.
*
* @param buffer
* @param idx
*/
inline int getWord(unsigned char *buffer, int idx)
{
return buffer[idx] | (buffer[idx+1] << 8);
}
/**
* Write a 16 bit word into a buffer at specific location.
*
* @param buffer
* @param idx
* @param value
*/
inline void setWord(unsigned char *buffer, int idx, int value)
{
buffer[idx] = value & 0xff;
buffer[idx+1] = (value >> 8) & 0xff;
}
/**
* Get the size of header options section.
*
* @param buf
*/
inline int read_options(unsigned char *buf)
{
int l = 0;
unsigned char c = buf[0];
while (c)
{
l += c;
c = buf[l];
}
return ++l;
}
/**
* Get the size of undefined references list.
*
* @param buf
*/
inline int read_undef(unsigned char *buf)
{
int l = 2;
int n = getWord(buf, 0);
while (n)
{
n--;
while (!buf[l++]) {}
}
return l;
}
reloc65::reloc65() :
m_tbase(0),
m_dbase(0),
m_bbase(0),
m_zbase(0),
m_tflag(false),
m_dflag(false),
m_bflag(false),
m_zflag(false),
m_extract(WHOLE) {}
void reloc65::setReloc(segment_t type, int addr)
{
switch (type)
{
case TEXT:
m_tflag = true;
m_tbase = addr;
break;
case DATA:
m_dflag = true;
m_dbase = addr;
break;
case BSS:
m_bflag = true;
m_bbase = addr;
break;
case ZEROPAGE:
m_zflag = true;
m_zbase = addr;
break;
default:
break;
}
}
void reloc65::setExtract(segment_t type)
{
m_extract = type;
}
bool reloc65::reloc(unsigned char **buf, int *fsize)
{
unsigned char *tmpBuf = *buf;
if (memcmp(tmpBuf, o65hdr, 5) != 0)
{
return false;
}
const int mode = getWord(tmpBuf, 6);
if ((mode & 0x2000) // 32 bit size not supported
|| (mode & 0x4000)) // pagewise relocation not supported
{
return false;
}
const int hlen = HEADER_SIZE + read_options(tmpBuf + HEADER_SIZE);
const int tbase = getWord(tmpBuf, 8);
const int tlen = getWord(tmpBuf, 10);
m_tdiff = m_tflag ? m_tbase - tbase : 0;
const int dbase = getWord(tmpBuf, 12);
const int dlen = getWord(tmpBuf, 14);
m_ddiff = m_dflag ? m_dbase - dbase : 0;
const int bbase = getWord(tmpBuf, 16);
const int blen SID_UNUSED = getWord(tmpBuf, 18);
m_bdiff = m_bflag ? m_bbase - bbase : 0;
const int zbase = getWord(tmpBuf, 20);
const int zlen SID_UNUSED = getWord(tmpBuf, 21);
m_zdiff = m_zflag ? m_zbase - zbase : 0;
unsigned char *segt = tmpBuf + hlen; // Text segment
unsigned char *segd = segt + tlen; // Data segment
unsigned char *utab = segd + dlen; // Undefined references list
unsigned char *rttab = utab + read_undef(utab); // Text relocation table
unsigned char *rdtab = reloc_seg(segt, tlen, rttab); // Data relocation table
unsigned char *extab = reloc_seg(segd, dlen, rdtab); // Exported globals list
reloc_globals(extab);
if (m_tflag)
{
setWord(tmpBuf, 8, m_tbase);
}
if (m_dflag)
{
setWord(tmpBuf, 12, m_dbase);
}
if (m_bflag)
{
setWord(tmpBuf, 16, m_bbase);
}
if (m_zflag)
{
setWord(tmpBuf, 20, m_zbase);
}
switch (m_extract)
{
case WHOLE:
return true;
case TEXT:
*buf = segt;
*fsize = tlen;
return true;
case DATA:
*buf = segd;
*fsize = dlen;
return true;
default:
return false;
}
}
int reloc65::reldiff(unsigned char s)
{
switch (s)
{
case 2: return m_tdiff;
case 3: return m_ddiff;
case 4: return m_bdiff;
case 5: return m_zdiff;
default: return 0;
}
}
unsigned char* reloc65::reloc_seg(unsigned char *buf, int len, unsigned char *rtab)
{
int adr = -1;
while (*rtab)
{
if ((*rtab & 255) == 255)
{
adr += 254;
rtab++;
}
else
{
adr += *rtab & 255;
rtab++;
const unsigned char type = *rtab & 0xe0;
unsigned char seg = *rtab & 0x07;
rtab++;
switch(type)
{
case 0x80: {
const int oldVal = getWord(buf, adr);
const int newVal = oldVal + reldiff(seg);
setWord(buf, adr, newVal);
break; }
case 0x40: {
const int oldVal = buf[adr] * 256 + *rtab;
const int newVal = oldVal + reldiff(seg);
buf[adr] = (newVal >> 8) & 255;
*rtab = newVal & 255;
rtab++;
break; }
case 0x20: {
const int oldVal = buf[adr];
const int newVal = oldVal + reldiff(seg);
buf[adr] = newVal & 255;
break; }
}
if (seg == 0)
{
rtab += 2;
}
}
if (adr > len)
{
// Warning: relocation table entries past segment end!
}
}
return ++rtab;
}
unsigned char *reloc65::reloc_globals(unsigned char *buf)
{
int n = getWord(buf, 0);
buf +=2;
while (n)
{
while (*(buf++)) {}
unsigned char seg = *buf;
const int oldVal = getWord(buf, 1);
const int newVal = oldVal + reldiff(seg);
setWord(buf, 1, newVal);
buf +=3;
n--;
}
return buf;
}