/*============================================================================== * files.c: Read, write and prepare the files. * * $Id: files.c,v 1.7 2004/07/15 13:10:09 Olivier Exp $ * * - GhostBuster for TI89 Titanium - * Copyright (C) 2004 Olivier Armand * and Kevin Kofler * * 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., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. * * In addition, as a special exception, Olivier Armand and Kevin Kofler give * permission to link the code of this program with the ttunpack decompression * library by the TI-Chess Team, and distribute linked combinations including * the two. You must obey the GNU General Public License in all respects for * all of the code used other than the ttunpack decompression library. If you * modify this program, you may extend this exception to your version of the * program, but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. * *==============================================================================*/ #include "common.h" #include "main.h" #include "files.h" #include "patch.h" #include "ttunpack.h" static HANDLE OutfileHandle; static const char NoMemStr[] = "Not enough memory."; static const unsigned char PPG_Sign[] = {0, 'p', 'p', 'g', 0, 0xF8}; /* Must be called before starting to patch any program. */ void PatchLoopInit(void) { if (OptAll) { /* This first search will find a folder in any case. */ SymFindFirst(NULL, FO_RECURSE); } } /* Return whether the program pointed to by 'prgm' is a kernel- * dependent program or not. 'prgm' must point to the first byte * (the size) of an assembly program. */ static PRGM_T CheckKernelBased(void *prgm) { /* Signature of a kernel-dependent program (after the bsr.w to * its entry point). */ long sig = *(long*)(prgm + sizeof(unsigned short) + sizeof(long)); /* Is it "68kP" or "68kL"? */ return sig == 0x36386B50 || sig == 0x36386B4C ? PRGM_KERNEL : PRGM_NOSTUB; } /* Check if the file whose handle is 'handle' is an ASM or * a PPG file. Return its type. */ PRGM_T CheckFile(HANDLE handle) { unsigned long prgm_len; unsigned char *fileptr; fileptr = HeapDeref(handle); prgm_len = (unsigned long)(*(unsigned short*)fileptr + sizeof(unsigned short)); if (*(fileptr + prgm_len - 1) == ASM_TAG) { return CheckKernelBased(fileptr); } else if (!memcmp(fileptr + prgm_len - sizeof(PPG_Sign), PPG_Sign, sizeof(PPG_Sign)), ttunpack_valid(fileptr + sizeof(unsigned short))) return PRGM_PPG; else return PRGM_OTHER; } /* Return the program type (PRGM_???) of the next file to be patched, * and write its handle to NextFileHandle. * If PRGM_NULL is returned, there are no more programs to patch. */ PRGM_T GetNextFile(void) { PRGM_T ret; SYM_ENTRY *symptr; if (OptAll) { while (1) { do { if (!(symptr = SymFindNext())) return PRGM_NULL; /* Do not patch a folder or ourself */ } while(symptr->flags.bits.folder == 1 || symptr->flags.bits.hidden == 1); /* If the file is a program, and if its name does not end with '_', * we are going to try to patch it. */ if ( (ret = CheckFile(NextFileHandle = symptr->handle)) != PRGM_OTHER && (symptr->name[strlen(symptr->name) - 1] != '_')) { Outfile = symptr->name; return ret; } } } /* If we have already patched the program that needed to be patched */ else if (!NextFileHandle) return PRGM_NULL; else if ((ret = CheckFile(NextFileHandle)) == PRGM_OTHER) fatal("Neither ASM nor PPG file..."); return ret; } #if 0 /* Add the stub to the program of type prgm_type pointed to by prgm. */ static void AddStub(void *prgm, PRGM_T prgm_type) { if (prgm_type == PRGM_NOSTUB) { /* Copy the stub at the beginning of the program */ memcpy((char*)prgm + sizeof(unsigned short), &StubHead, STUB_SIZE); } else { char *stub_ptr, *ptr; /* We cannot add the stub at the beginning because patching the * relocation in the kernel stub would require too much work. So we * add the stub at the end of the program, and we patch the first * instruction (bsr.w) to branch to our stub. * This is only possible for kernel-based program because we know * what the first instruction is. For nostub programs the stub must * be added at the beginning and the relocation table must be * patched (see further). */ /* Add STUB_SIZE to the program's size because it has been * overwritten by tt_decompress or by the memcpy of the program in * the case of a kernel-based program. If the size was even it has * been corrected to become odd (else the stub would not * be word-aligned). */ unsigned short prgm_len = *(unsigned short*)prgm; *((unsigned short*)prgm)++ += STUB_SIZE + (!(prgm_len % 2) ? sizeof(char) : 0); stub_ptr = ((char*)prgm + prgm_len - sizeof(ASM_TAG) - sizeof(short)); if ((long)stub_ptr % 2) stub_ptr++; /* align it */ /* The ASM tag and the empty relocation table (a null word) are * overwritten and rewritten. */ memcpy(stub_ptr, &StubHead, STUB_SIZE); ptr = stub_ptr; ptr += STUB_SIZE; *((short*)ptr)++ = 0; /* the empty relocation table */ *ptr = ASM_TAG; /* Point to the offset of the bsr.w to _main */ ((short*)prgm)++; /* Correct the stub to make it known where _main is: * The new offset is dest - src, where dest is prgm + old_offset, * and src is StubRunPrgm_offset - StubHead + stub_ptr */ ptr = (char*)(&StubRunPrgm + 1) - (char*)&StubHead + stub_ptr; *(short*)ptr = (long)prgm + *(short*)prgm - (long)ptr; /* Correct the bsr.w of the kernel stub it to make it point to * our stub */ *(short*)prgm = (long)stub_ptr - (long)prgm; } } #endif /* Prepare the outfile for the patches. 'prgm_type' must be the type of the * program. Return in the variable pointed to by prgm_len the length of the * program (only the code), and return a pointer to the program (after the * 2 bytes for its size and the space for the stub of nostub programs).*/ unsigned char *PatchAlloc(PRGM_T prgm_type, unsigned long *prgm_len) { unsigned char *prgm, *out_prgm; char charbuf[30]; #if 0 unsigned char *prgm_ptr; unsigned short rt_len; unsigned short rt_end; void *prgm_head; #endif prgm = HLock(NextFileHandle); sprintf(charbuf, "Processing \'%s\'...", Outfile); xST_helpMsg(charbuf); /* Prepare the error message */ strcpy(charbuf, NoMemStr); sprintf(charbuf + sizeof(NoMemStr) - sizeof((char)'\0'), " [%s]", Outfile); if (prgm_type == PRGM_PPG) { *prgm_len = ttunpack_size(prgm + sizeof(unsigned short)); /* The place for the stub added at the beginning of the patched * program, or at the end if it is a kernel-based program, is filled * further. In the case of a nostub program, the last unsigned * short of the stub will be used for the old size of the program, * but it is not a problem. */ if (!(OutfileHandle = HeapAlloc(*prgm_len + STUB_SIZE))) { HeapUnlock(NextFileHandle); fatal(charbuf); } out_prgm = HeapDeref(OutfileHandle); /* Write the size of the program */ *(unsigned short*)out_prgm = *prgm_len + STUB_SIZE - sizeof(unsigned short); #if 0 prgm_head = out_prgm; if ((prgm_type = CheckKernelBased(out_prgm)) == PRGM_NOSTUB) { /* Keep space for the stub */ out_prgm += STUB_SIZE; } /* The stub added at the end of the program must be word-aligned. It * will be only if its original length is odd (an even code size + * one char of ASM tag). */ else if (!(*prgm_len % 2)) { if (!HeapRealloc(OutfileHandle, *prgm_len + STUB_SIZE + sizeof(char))) { HeapUnlock(NextFileHandle); fatal(charbuf); } prgm_head = out_prgm = HeapDeref(OutfileHandle); } #endif if (ttunpack_decompress(prgm + sizeof(unsigned short), out_prgm)) { HeapFree(OutfileHandle); HeapUnlock(NextFileHandle); fatal("Invalid PPG infile."); } #if 0 /* Must be added *after* the decompression because ttunpack_decompress() * also writes the size of the program (in the last word of the place * reserved for the stub in nostub programs which is not a problem, or * on the real size of the block, which will be corrected by the * function). */ AddStub(prgm_head, prgm_type); #endif } else { /* not PPG */ *prgm_len = (unsigned long)(*(unsigned short*)prgm + sizeof(unsigned short)); if (!(OutfileHandle = HeapAlloc(*prgm_len + STUB_SIZE))) { HeapUnlock(NextFileHandle); fatal(charbuf); } out_prgm = HeapDeref(OutfileHandle); /* Write the size of the program */ *(unsigned short*)out_prgm = *prgm_len + STUB_SIZE - sizeof(unsigned short); #if 0 /* Keep space for the stub at the beginning only for nostub programs */ prgm_head = out_prgm; if (prgm_type == PRGM_NOSTUB) out_prgm += STUB_SIZE; #endif memcpy(out_prgm, prgm, *prgm_len); #if 0 AddStub(prgm_head, prgm_type); #endif } /* else (not PPG) */ #if 0 /* Patch the relocation table for a nostub program because of the * stub added and calculate how big it is also for kernel-based * programs. */ /* Point to last word of the program (jump the byte for the * tag). This word is further for kernel-based programs because of * the stub added. We do not modify directly prgm_len because we * do not want the stub to be patched. * Caution, prgm_ptr can be odd! */ prgm_ptr = out_prgm + *prgm_len - sizeof(unsigned short) - sizeof(char) + ((prgm_type == PRGM_KERNEL) ? STUB_SIZE : 0); /* Size in bytes. There is at least the end of the table (one * null word). */ rt_len = sizeof(short); /* rt_end is even if the offset patched is a destination: we * must test if it is null to stop patching the table. */ rt_end = 0; while (1) { unsigned short i = (*prgm_ptr << sizeof(char) * 8) + *(prgm_ptr + 1); /* While it is not the end of the table */ if (!(rt_end % 2) && !i) break; if (prgm_type == PRGM_NOSTUB) { i += STUB_SIZE; *prgm_ptr = i >> sizeof(char) * 8; *(prgm_ptr + 1) = (char)i; } rt_len += sizeof(short); prgm_ptr -= sizeof(short); rt_end++; } /* Don't count the 2 bytes for the size, the last byte for * the tag, and the relocation table for the program length. */ *prgm_len -= sizeof(unsigned short) + sizeof(char) + rt_len; #endif HeapUnlock(NextFileHandle); /* So that GetNextFile() will return PRGM_NULL the next time it is * called, if option "all" is not used. */ NextFileHandle = H_NULL; return out_prgm + sizeof(unsigned short); } /* Must be called after having applied the patches to a file. * 'patched' must be TRUE if the file was patched. */ void FinalizePatch(BOOL patched) { char charbuf[MAX_SYM_LEN + sizeof((char)'\0')]; HSym hsym; if (patched == FALSE) { HeapFree(OutfileHandle); return; } TokenizeName(Outfile, charbuf); if (OptAll || OptReplace) { /* Delete the infile (which is the outfile). */ SYM_ENTRY *symptr = SymFindPtr(charbuf + MAX_SYM_LEN, 0); if ( symptr->flags.bits.archived && !EM_moveSymFromExtMem(charbuf + MAX_SYM_LEN, HS_NULL)) { HeapFree(OutfileHandle); fatal(NoMemStr); } SymDel(charbuf + MAX_SYM_LEN); } if(!(hsym = SymAdd(charbuf + MAX_SYM_LEN)).folder) { HeapFree(OutfileHandle); fatal(NoMemStr); } DerefSym(hsym)->handle = OutfileHandle; if (!OptRam) { EM_moveSymToExtMem(charbuf + MAX_SYM_LEN, HS_NULL); } }