From 842687726f6ea8fbc108fa9ccc1eb0e829875c24 Mon Sep 17 00:00:00 2001 From: Artur K Date: Tue, 28 Apr 2015 14:59:00 +0200 Subject: [PATCH] Update the dcc tools code --- CMakeLists.txt | 2 +- common/PatternCollector.h | 77 ++++++ src/DccFrontend.cpp | 1 + src/chklib.cpp | 4 +- src/dcc.cpp | 74 +++--- tools/makedsig/LIB_PatternCollector.cpp | 234 ++++++++++++++++++- tools/makedsig/LIB_PatternCollector.h | 28 ++- tools/makedsig/TPL_PatternCollector.cpp | 299 ++++++++++++++++++++++++ tools/makedsig/TPL_PatternCollector.h | 33 +++ 9 files changed, 703 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eafc42a..68bea92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ enable_testing() ENDIF() -llvm_map_components_to_libnames(REQ_LLVM_LIBRARIES jit native mc support tablegen) +llvm_map_components_to_libnames(REQ_LLVM_LIBRARIES native mc support tablegen) INCLUDE_DIRECTORIES( 3rd_party/libdisasm include diff --git a/common/PatternCollector.h b/common/PatternCollector.h index 0cd4996..bb853ae 100644 --- a/common/PatternCollector.h +++ b/common/PatternCollector.h @@ -1,5 +1,82 @@ #ifndef PATTERNCOLLECTOR #define PATTERNCOLLECTOR +#include +#include +#include +#include +#define SYMLEN 16 /* Number of chars in the symbol name, incl null */ +#define PATLEN 23 /* Number of bytes in the pattern part */ + +struct HASHENTRY +{ + char name[SYMLEN]; /* The symbol name */ + uint8_t pat [PATLEN]; /* The pattern */ + uint16_t offset; /* Offset (needed temporarily) */ +}; + +struct PatternCollector { + uint8_t buf[100], bufSave[7]; /* Temp buffer for reading the file */ + uint16_t readShort(FILE *f) + { + uint8_t b1, b2; + + if (fread(&b1, 1, 1, f) != 1) + { + printf("Could not read\n"); + exit(11); + } + if (fread(&b2, 1, 1, f) != 1) + { + printf("Could not read\n"); + exit(11); + } + return (b2 << 8) + b1; + } + + void grab(FILE *f,int n) + { + if (fread(buf, 1, n, f) != (size_t)n) + { + printf("Could not read\n"); + exit(11); + } + } + + uint8_t readByte(FILE *f) + { + uint8_t b; + + if (fread(&b, 1, 1, f) != 1) + { + printf("Could not read\n"); + exit(11); + } + return b; + } + + uint16_t readWord(FILE *fl) + { + uint8_t b1, b2; + + b1 = readByte(fl); + b2 = readByte(fl); + + return b1 + (b2 << 8); + } + + /* Called by map(). Return the i+1th key in *pKeys */ + uint8_t *getKey(int i) + { + return keys[i].pat; + } + /* Display key i */ + void dispKey(int i) + { + printf("%s", keys[i].name); + } + std::vector keys; /* array of keys */ + virtual int readSyms(FILE *f)=0; +}; #endif // PATTERNCOLLECTOR diff --git a/src/DccFrontend.cpp b/src/DccFrontend.cpp index 4b4175b..ac51ac8 100644 --- a/src/DccFrontend.cpp +++ b/src/DccFrontend.cpp @@ -211,6 +211,7 @@ struct ComLoader : public DosLoader { return false; } bool load(PROG &prog,QFile &fp) { + fp.seek(0); /* COM file * In this case the load module size is just the file length */ diff --git a/src/chklib.cpp b/src/chklib.cpp index 23c7fc5..aa396c6 100644 --- a/src/chklib.cpp +++ b/src/chklib.cpp @@ -15,7 +15,7 @@ #include #include #include - +PerfectHash g_pattern_hasher; #define NIL -1 /* Used like NULL, but 0 is valid */ /* Hash table structure */ @@ -331,7 +331,7 @@ void SetupLibCheck(void) /* Initialise the perfhlib stuff. Also allocates T1, T2, g, etc */ /* Set the parameters for the hash table */ - g_pattern_hasher.init( + g_pattern_hasher.setHashParams( numKeys, /* The number of symbols */ PatLen, /* The length of the pattern to be hashed */ 256, /* The character set of the pattern (0-FF) */ diff --git a/src/dcc.cpp b/src/dcc.cpp index 7acdefd..310a132 100644 --- a/src/dcc.cpp +++ b/src/dcc.cpp @@ -65,7 +65,7 @@ bool TVisitor(raw_ostream &OS, RecordKeeper &Records) // rec = Records.getDef("CCR"); // if(rec) // rec->dump(); - for(auto val : Records.getDefs()) + for(const auto &val : Records.getDefs()) { //std::cout<< "Def "<createTargetMachine(TheTriple.getTriple(),MCPU,Features,opts); - std::cerr<getInstrInfo()->getName(97)<<"\n"; - const MCInstrDesc &ds(tm->getInstrInfo()->get(97)); - const MCOperandInfo *op1=ds.OpInfo; - uint16_t impl_def = ds.getImplicitDefs()[0]; - std::cerr<createTargetMachine(TheTriple.getTriple(),MCPU,Features,opts); +// std::cerr<getInstrInfo()->getName(97)<<"\n"; +// const MCInstrDesc &ds(tm->getInstrInfo()->get(97)); +// const MCOperandInfo *op1=ds.OpInfo; +// uint16_t impl_def = ds.getImplicitDefs()[0]; +// std::cerr< +#include +/** \note there is an untested assumption that the *first* segment definition + with class CODE will be the one containing all useful functions in the + LEDATA records. Functions such as _exit() have more than one segment + declared with class CODE (MSC8 libraries) */ -LIB_PatternCollector::LIB_PatternCollector() +extern void fixWildCards(uint8_t pat[]); +void readNN(int n, FILE *fl) { - + if (fseek(fl, (long)n, SEEK_CUR) != 0) + { + printf("Could not seek file\n"); + exit(2); + } } +void LIB_PatternCollector::readString(FILE *fl) +{ + uint8_t len; + + len = readByte(fl); + if (fread(buf, 1, len, fl) != len) + { + printf("Could not read string len %d\n", len); + exit(2); + } + buf[len] = '\0'; + offset += len; +} + +int LIB_PatternCollector::readSyms(FILE *fl) +{ + int i; + int count = 0; + int firstSym = 0; /* First symbol this module */ + uint8_t b, c, type; + uint16_t w, len; + + codeLNAMES = NONE; /* Invalidate indexes for code segment */ + codeSEGDEF = NONE; /* Else won't be assigned */ + + offset = 0; /* For diagnostics, really */ + + if ((leData = (uint8_t *)malloc(0xFF80)) == 0) + { + printf("Could not malloc 64k bytes for LEDATA\n"); + exit(10); + } + + while (!feof(fl)) + { + type = readByte(fl); + len = readWord(fl); + /* Note: uncommenting the following generates a *lot* of output */ + /*printf("Offset %05lX: type %02X len %d\n", offset-3, type, len);//*/ + switch (type) + { + + case 0x96: /* LNAMES */ + while (len > 1) + { + readString(fl); + ++lnum; + if (strcmp((char *)buf, "CODE") == 0) + { + /* This is the class name we're looking for */ + codeLNAMES= lnum; + } + len -= strlen((char *)buf)+1; + } + b = readByte(fl); /* Checksum */ + break; + + case 0x98: /* Segment definition */ + b = readByte(fl); /* Segment attributes */ + if ((b & 0xE0) == 0) + { + /* Alignment field is zero. Frame and offset follow */ + readWord(fl); + readByte(fl); + } + + w = readWord(fl); /* Segment length */ + + b = readByte(fl); /* Segment name index */ + ++segnum; + + b = readByte(fl); /* Class name index */ + if ((b == codeLNAMES) && (codeSEGDEF == NONE)) + { + /* This is the segment defining the code class */ + codeSEGDEF = segnum; + } + + b = readByte(fl); /* Overlay index */ + b = readByte(fl); /* Checksum */ + break; + + case 0x90: /* PUBDEF: public symbols */ + b = readByte(fl); /* Base group */ + c = readByte(fl); /* Base segment */ + len -= 2; + if (c == 0) + { + w = readWord(fl); + len -= 2; + } + while (len > 1) + { + readString(fl); + w = readWord(fl); /* Offset */ + b = readByte(fl); /* Type index */ + if (c == codeSEGDEF) + { + char *p; + HASHENTRY entry; + p = (char *)buf; + if (buf[0] == '_') /* Leading underscore? */ + { + p++; /* Yes, remove it*/ + } + i = std::min(size_t(SYMLEN-1), strlen(p)); + memcpy(entry.name, p, i); + entry.name[i] = '\0'; + entry.offset = w; + /*printf("%04X: %s is sym #%d\n", w, keys[count].name, count);//*/ + keys.push_back(entry); + count++; + } + len -= strlen((char *)buf) + 1 + 2 + 1; + } + b = readByte(fl); /* Checksum */ + break; + + + case 0xA0: /* LEDATA */ + { + b = readByte(fl); /* Segment index */ + w = readWord(fl); /* Offset */ + len -= 3; + /*printf("LEDATA seg %d off %02X len %Xh, looking for %d\n", b, w, len-1, codeSEGDEF);//*/ + + if (b != codeSEGDEF) + { + readNN(len,fl); /* Skip the data */ + break; /* Next record */ + } + + + if (fread(&leData[w], 1, len-1, fl) != len-1) + { + printf("Could not read LEDATA length %d\n", len-1); + exit(2); + } + offset += len-1; + maxLeData = std::max(maxLeData, w+len-1); + + readByte(fl); /* Checksum */ + break; + } + + default: + readNN(len,fl); /* Just skip the lot */ + + if (type == 0x8A) /* Mod end */ + { + /* Now find all the patterns for public code symbols that + we have found */ + for (i=firstSym; i < count; i++) + { + uint16_t off = keys[i].offset; + if (off == (uint16_t)-1) + { + continue; /* Ignore if already done */ + } + if (keys[i].offset > maxLeData) + { + printf( + "Warning: no LEDATA for symbol #%d %s " + "(offset %04X, max %04X)\n", + i, keys[i].name, off, maxLeData); + /* To make things consistant, we set the pattern for + this symbol to nulls */ + memset(&keys[i].pat, 0, PATLEN); + continue; + } + /* Copy to temp buffer so don't overrun later patterns. + (e.g. when chopping a short pattern). + Beware of short patterns! */ + if (off+PATLEN <= maxLeData) + { + /* Available pattern is >= PATLEN */ + memcpy(buf, &leData[off], PATLEN); + } + else + { + /* Short! Only copy what is available (and malloced!) */ + memcpy(buf, &leData[off], maxLeData-off); + /* Set rest to zeroes */ + memset(&buf[maxLeData-off], 0, PATLEN-(maxLeData-off)); + } + fixWildCards((uint8_t *)buf); + /* Save into the hash entry. */ + memcpy(keys[i].pat, buf, PATLEN); + keys[i].offset = (uint16_t)-1; // Flag it as done + //printf("Saved pattern for %s\n", keys[i].name); + } + + + while (readByte(fl) == 0); + readNN(-1,fl); /* Unget the last byte (= type) */ + lnum = 0; /* Reset index into lnames */ + segnum = 0; /* Reset index into snames */ + firstSym = count; /* Remember index of first sym this mod */ + codeLNAMES = NONE; /* Invalidate indexes for code segment */ + codeSEGDEF = NONE; + memset(leData, 0, maxLeData); /* Clear out old junk */ + maxLeData = 0; /* No data read this module */ + } + + else if (type == 0xF1) + { + /* Library end record */ + return count; + } + + } + } + + + free(leData); + keys.clear(); + + return count; +} diff --git a/tools/makedsig/LIB_PatternCollector.h b/tools/makedsig/LIB_PatternCollector.h index e69da28..c472edc 100644 --- a/tools/makedsig/LIB_PatternCollector.h +++ b/tools/makedsig/LIB_PatternCollector.h @@ -1,11 +1,25 @@ -#ifndef LIB_PATTERNCOLLECTOR_H -#define LIB_PATTERNCOLLECTOR_H +#pragma once +#include "PatternCollector.h" -class LIB_PatternCollector +struct LIB_PatternCollector : public PatternCollector { -public: - LIB_PatternCollector(); -}; +protected: + unsigned long offset; + uint8_t lnum = 0; /* Count of LNAMES so far */ + uint8_t segnum = 0; /* Count of SEGDEFs so far */ + uint8_t codeLNAMES; /* Index of the LNAMES for "CODE" class */ + uint8_t codeSEGDEF; /* Index of the first SEGDEF that has class CODE */ + #define NONE 0xFF /* Improbable segment index */ + uint8_t *leData; /* Pointer to 64K of alloc'd data. Some .lib files + have the symbols (PUBDEFs) *after* the data + (LEDATA), so you need to keep the data here */ + uint16_t maxLeData; /* How much data we have in there */ + /* read a length then string to buf[]; make it an asciiz string */ + void readString( FILE *fl); -#endif // LIB_PATTERNCOLLECTOR_H +public: + /* Read the .lib file, and put the keys into the array *keys[]. Returns the count */ + int readSyms(FILE *fl); + +}; diff --git a/tools/makedsig/TPL_PatternCollector.cpp b/tools/makedsig/TPL_PatternCollector.cpp index 8b13789..d007a70 100644 --- a/tools/makedsig/TPL_PatternCollector.cpp +++ b/tools/makedsig/TPL_PatternCollector.cpp @@ -1 +1,300 @@ +#include "TPL_PatternCollector.h" +#include +/** \note Fundamental problem: there seems to be no information linking the names + in the system unit ("V" category) with their routines, except trial and + error. I have entered a few. There is no guarantee that the same pmap + offset will map to the same routine in all versions of turbo.tpl. They + seem to match so far in version 4 and 5.0 */ + + +#define roundUp(w) ((w + 0x0F) & 0xFFF0) +extern void fixWildCards(uint8_t pat[]); +void TPL_PatternCollector::enterSym(FILE *f, const char *name, uint16_t pmapOffset) +{ + uint16_t pm, cm, codeOffset, pcode; + uint16_t j; + + /* Enter a symbol with given name */ + allocSym(count); + strcpy(keys[count].name, name); + pm = pmap + pmapOffset; /* Pointer to the 4 byte pmap structure */ + fseek(f, unitBase+pm, SEEK_SET);/* Go there */ + cm = readShort(f); /* CSeg map offset */ + codeOffset = readShort(f); /* How far into the code segment is our rtn */ + j = cm / 8; /* Index into the cmap array */ + pcode = csegBase+csegoffs[j]+codeOffset; + fseek(f, unitBase+pcode, SEEK_SET); /* Go there */ + grab(f,PATLEN); /* Grab the pattern to buf[] */ + fixWildCards(buf); /* Fix the wild cards */ + memcpy(keys[count].pat, buf, PATLEN); /* Copy to the key array */ + count++; /* Done one more */ +} + +void TPL_PatternCollector::allocSym(int count) +{ + keys.resize(count); +} + +void TPL_PatternCollector::readCmapOffsets(FILE *f) +{ + uint16_t cumsize, csize; + uint16_t i; + + /* Read the cmap table to find the start address of each segment */ + fseek(f, unitBase+cmap, SEEK_SET); + cumsize = 0; + csegIdx = 0; + for (i=cmap; i < pmap; i+=8) + { + readShort(f); /* Always 0 */ + csize = readShort(f); + if (csize == 0xFFFF) continue; /* Ignore the first one... unit init */ + csegoffs[csegIdx++] = cumsize; + cumsize += csize; + grab(f,4); + } +} + +void TPL_PatternCollector::enterSystemUnit(FILE *f) +{ + /* The system unit is special. The association between keywords and + pmap entries is not stored in the .tpl file (as far as I can tell). + So we hope that they are constant pmap entries. + */ + + fseek(f, 0x0C, SEEK_SET); + cmap = readShort(f); + pmap = readShort(f); + fseek(f, offStCseg, SEEK_SET); + csegBase = roundUp(readShort(f)); /* Round up to next 16 bdry */ + printf("CMAP table at %04X\n", cmap); + printf("PMAP table at %04X\n", pmap); + printf("Code seg base %04X\n", csegBase); + + readCmapOffsets(f); + + enterSym(f,"INITIALISE", 0x04); + enterSym(f,"UNKNOWN008", 0x08); + enterSym(f,"EXIT", 0x0C); + enterSym(f,"BlockMove", 0x10); + unknown(f,0x14, 0xC8); + enterSym(f,"PostIO", 0xC8); + enterSym(f,"UNKNOWN0CC", 0xCC); + enterSym(f,"STACKCHK", 0xD0); + enterSym(f,"UNKNOWN0D4", 0xD4); + enterSym(f,"WriteString", 0xD8); + enterSym(f,"WriteInt", 0xDC); + enterSym(f,"UNKNOWN0E0", 0xE0); + enterSym(f,"UNKNOWN0E4", 0xE4); + enterSym(f,"CRLF", 0xE8); + enterSym(f,"UNKNOWN0EC", 0xEC); + enterSym(f,"UNKNOWN0F0", 0xF0); + enterSym(f,"UNKNOWN0F4", 0xF4); + enterSym(f,"ReadEOL", 0xF8); + enterSym(f,"Read", 0xFC); + enterSym(f,"UNKNOWN100", 0x100); + enterSym(f,"UNKNOWN104", 0x104); + enterSym(f,"PostWrite", 0x108); + enterSym(f,"UNKNOWN10C", 0x10C); + enterSym(f,"Randomize", 0x110); + unknown(f,0x114, 0x174); + enterSym(f,"Random", 0x174); + unknown(f,0x178, 0x1B8); + enterSym(f,"FloatAdd", 0x1B8); /* A guess! */ + enterSym(f,"FloatSub", 0x1BC); /* disicx - dxbxax -> dxbxax*/ + enterSym(f,"FloatMult", 0x1C0); /* disicx * dxbxax -> dxbxax*/ + enterSym(f,"FloatDivide", 0x1C4); /* disicx / dxbxax -> dxbxax*/ + enterSym(f,"UNKNOWN1C8", 0x1C8); + enterSym(f,"DoubleToFloat",0x1CC); /* dxax to dxbxax */ + enterSym(f,"UNKNOWN1D0", 0x1D0); + enterSym(f,"WriteFloat", 0x1DC); + unknown(f,0x1E0, 0x200); + +} + +void TPL_PatternCollector::readString(FILE *f) +{ + uint8_t len; + + len = readByte(f); + grab(f,len); + buf[len] = '\0'; +} + +void TPL_PatternCollector::unknown(FILE *f, unsigned j, unsigned k) +{ + /* Mark calls j to k (not inclusive) as unknown */ + unsigned i; + + for (i=j; i < k; i+= 4) + { + sprintf((char *)buf, "UNKNOWN%03X", i); + enterSym(f,(char *)buf, i); + } +} + +void TPL_PatternCollector::nextUnit(FILE *f) +{ + /* Find the start of the next unit */ + + uint16_t dsegBase, sizeSyms, sizeOther1, sizeOther2; + + fseek(f, unitBase+offStCseg, SEEK_SET); + dsegBase = roundUp(readShort(f)); + sizeSyms = roundUp(readShort(f)); + sizeOther1 = roundUp(readShort(f)); + sizeOther2 = roundUp(readShort(f)); + + unitBase += dsegBase + sizeSyms + sizeOther1 + sizeOther2; + + fseek(f, unitBase, SEEK_SET); + if (fread(buf, 1, 4, f) == 4) + { + buf[4]='\0'; + printf("Start of unit: found %s\n", buf); + } + +} + +void TPL_PatternCollector::setVersionSpecifics() +{ + + version = buf[3]; /* The x of TPUx */ + + switch (version) + { + case '0': /* Version 4.0 */ + offStCseg = 0x14; /* Offset to the LL giving the Cseg start */ + charProc = 'T'; /* Indicates a proc in the dictionary */ + charFunc = 'U'; /* Indicates a function in the dictionary */ + skipPmap = 6; /* Bytes to skip after Func to get pmap offset */ + break; + + + case '5': /* Version 5.0 */ + offStCseg = 0x18; /* Offset to the LL giving the Cseg start */ + charProc = 'T'; /* Indicates a proc in the dictionary */ + charFunc = 'U'; /* Indicates a function in the dictionary */ + skipPmap = 1; /* Bytes to skip after Func to get pmap offset */ + break; + + default: + printf("Unknown version %c!\n", version); + exit(1); + + } + +} + +void TPL_PatternCollector::savePos(FILE *f) +{ + + if (positionStack.size() >= 20) + { + printf("Overflowed filePosn array\n"); + exit(1); + } + positionStack.push_back(ftell(f)); +} + +void TPL_PatternCollector::restorePos(FILE *f) +{ + if (positionStack.empty() == 0) + { + printf("Underflowed filePosn array\n"); + exit(1); + } + + fseek(f, positionStack.back(), SEEK_SET); + positionStack.pop_back(); +} + +void TPL_PatternCollector::enterUnitProcs(FILE *f) +{ + + uint16_t i, LL; + uint16_t hash, hsize, dhdr, pmapOff; + char cat; + char name[40]; + + fseek(f, unitBase+0x0C, SEEK_SET); + cmap = readShort(f); + pmap = readShort(f); + fseek(f, unitBase+offStCseg, SEEK_SET); + csegBase = roundUp(readShort(f)); /* Round up to next 16 bdry */ + printf("CMAP table at %04X\n", cmap); + printf("PMAP table at %04X\n", pmap); + printf("Code seg base %04X\n", csegBase); + + readCmapOffsets(f); + + fseek(f, unitBase+pmap, SEEK_SET); /* Go to first pmap entry */ + if (readShort(f) != 0xFFFF) /* FFFF means none */ + { + sprintf(name, "UNIT_INIT_%d", ++unitNum); + enterSym(f,name, 0); /* This is the unit init code */ + } + + fseek(f, unitBase+0x0A, SEEK_SET); + hash = readShort(f); + //printf("Hash table at %04X\n", hash); + fseek(f, unitBase+hash, SEEK_SET); + hsize = readShort(f); + //printf("Hash table size %04X\n", hsize); + for (i=0; i <= hsize; i+= 2) + { + dhdr = readShort(f); + if (dhdr) + { + savePos(f); + fseek(f, unitBase+dhdr, SEEK_SET); + do + { + LL = readShort(f); + readString(f); + strcpy(name, (char *)buf); + cat = readByte(f); + if ((cat == charProc) || (cat == charFunc)) + { + grab(f,skipPmap); /* Skip to the pmap */ + pmapOff = readShort(f); /* pmap offset */ + printf("pmap offset for %13s: %04X\n", name, pmapOff); + enterSym(f,name, pmapOff); + } + //printf("%13s %c ", name, cat); + if (LL) + { + //printf("LL seek to %04X\n", LL); + fseek(f, unitBase+LL, SEEK_SET); + } + } while (LL); + restorePos(f); + } + } + +} + +int TPL_PatternCollector::readSyms(FILE *f) +{ + grab(f,4); + if ((strncmp((char *)buf, "TPU0", 4) != 0) && ((strncmp((char *)buf, "TPU5", 4) != 0))) + { + printf("Not a Turbo Pascal version 4 or 5 library file\n"); + fclose(f); + exit(1); + } + + setVersionSpecifics(); + + enterSystemUnit(f); + unitBase = 0; + do + { + nextUnit(f); + if (feof(f)) break; + enterUnitProcs(f); + } while (1); + + return count; +} diff --git a/tools/makedsig/TPL_PatternCollector.h b/tools/makedsig/TPL_PatternCollector.h index 80a4f13..44e3d06 100644 --- a/tools/makedsig/TPL_PatternCollector.h +++ b/tools/makedsig/TPL_PatternCollector.h @@ -1,5 +1,38 @@ #ifndef TPL_PATTERNCOLLECTOR_H #define TPL_PATTERNCOLLECTOR_H +#include "PatternCollector.h" + +#include +#include +#include + +struct TPL_PatternCollector : public PatternCollector { +protected: + uint16_t cmap, pmap, csegBase, unitBase; + uint16_t offStCseg, skipPmap; + int count = 0; + int cAllocSym = 0; + int unitNum = 0; + char version, charProc, charFunc; + uint16_t csegoffs[100]; + uint16_t csegIdx; + std::vector positionStack; + + void enterSym(FILE *f,const char *name, uint16_t pmapOffset); + void allocSym(int count); + void readCmapOffsets(FILE *f); + void enterSystemUnit(FILE *f); + void readString(FILE *f); + void unknown(FILE *f,unsigned j, unsigned k); + void nextUnit(FILE *f); + void setVersionSpecifics(void); + void savePos(FILE *f); + void restorePos(FILE *f); + void enterUnitProcs(FILE *f); +public: + /* Read the .tpl file, and put the keys into the array *keys[]. Returns the count */ + int readSyms(FILE *f); +}; #endif // TPL_PATTERNCOLLECTOR_H