/* * 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 #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; }