Let's start it all over from the beginning step by step. As an example, we want to write an ACE-reader which displays the descriptions before the final decompressing.
First depack the environment, set the paths correctly and copy the TEMPLATE from the indev directory to indef\arcace.
There you have to change the makefile, namely in this way:
dest = arcACE.dll ... arcace_desc = 'OpenCP .ACE Archive Reader (c) 1998 Felix Domke' arcace_objs = arcace.obj arcace_libs = cp.lib pfilesel.lib arcace_ver = 0.0 arcace.dll: $(dlldeps) arcace.exp $(arcace_objs) $(libdir)$(arcace_libs) $(makedll) copy arcace.dll \opencp\bin
You have to enter all destinations for dest, ie all DLLs which shall be created in the end.
arcace_desc is the description which will be printed.
arcace_objs are the object-files which shall be linked to the DLL.
Since there is an implicit-rule which says that WPP386 gets executed for CPPs for which OBJs with the same name are demanded, there also has to be an arcace.cpp with our code.
arcace_libs are the (import) libraries from which the imports shall be searched. Standard functions like sprintf_ etc. are exported in cp.lib. PFILESEL.LIB contains some functions we will need later.
arcace_ver is the version which gets displayed in the linkview of OPENCP. You compute it in a rather strange way. How exactly, I myself haven't understood, but usually you should convert the version from hex to decimal (eg00x024000 for 2.5.0) and then divide by 100. But someone must have erred.
The copy at the end copies the DLL right into the CP directory.
In general in an archive-read, two functions are passed, one to read the file names from the archive and ``tell'' them to the file selector via callback and another to call the unpacker to unpack a file from the archive.
To be concrete, it looks like the following:
An adbregstruct gets exported which looks like this:
struct adbregstruct { const char *ext; int (*Scan)(const char *path); int (*Call)(int act, const char *apath, const char *file, const char *dpath ); adbregstruct *next; };
ext is the extension, in our case .ace.
Scan is a function which gets called with the archive as parameter and tells it to the file selector by calling adbAdd().
Call will get executed then to unpack a file from the archive.
We leave out next because it will be set at runtime anyway.
Now let's export such a struct:
extern "C" { adbregstruct adbACEReg = {".ACE", adbACEScan, adbACECall}; char *dllinfo = "arcs _adbACEReg"; };
dllinfo makes an entry in cp.ini superfluous. It provides that CP takes notice of our archive-reader.
However, something like link=... arcace.dll has to be in cp.ini.
Our adbACEScan() looks like the following8.5:
static int adbACEScan(const char *path) /* "path" is the filename of the archive. If everything works, 1 will be returned, otherwise 0. */ { [...] sbinfile archive; /* "binfile" is the Library which is used in CP for (almost) all file accesses. I think it is EXTREMELY practical. It is actually quite self-explanatory. "sbinfile" is a normal file on disk. */ if(!archive.open(path, sbinfile::openro)) return(1); /* When we can't open the archive, we can exit at once :) */ unsigned short arcref; char arcname[12]; char ext[_MAX_EXT]; char name[_MAX_FNAME]; _splitpath(path, 0, 0, name, ext); fsConvFileName12(arcname, name, ext); arcentry a; memcpy(a.name, arcname, 12); a.size=archive.length(); a.flags=ADB_ARC; if (!adbAdd(a)) { archive.close(); return 0; } /* Here, the archive itself gets added to the file listing, namely by adbAdd(arcentry &). The struct which adbAdd expects looks like: struct arcentry { unsigned short flags; unsigned short parent; char name[12]; unsigned long size; }; "flags" is e.g. ADB_ARC for archives. "parent" is not so important at the moment. "name" is the file name in 8.3-format (fsConvFileName12 converts it pretty nicely :) (e.g. from "x.y" to "X .Y ") "size" is simply the length which will be displayed. */ arcref=adbFind(arcname); /* Aferwards we keep the "ref" in our mind so that we can enter it as "parent" afterwards. parent is the source-archive so that the files there will be unpacked from the right archive afterwards... (The file selector creates a list of all files in the directory and the files in the archives. For the last thing, it calls the archive-reader. If the user presses with "Enter" on a file afterwards, the file selector has to know what ARCer it has to call and most of all from which archive the files come. Therefore the parent has to be set. */ [...] /* Now let's deal with the ACE itself. The ACE format is very similar to the RAR one, a detailed @REF="format description":"coace.txt" In any case the function "ReadNextHeader" reads the header on the current position and skips additional bytes if they appear (i.e with files the packed data). In this way header comes after header, at least it appears. (In reality ReadNextHeader SEEKs at the beginning, not at the end, but that's not important now, don't get confused by that :) */ arcentry a; while(ReadNextHeader(archive)) { /* Here, the header gets read. */ char ext[_MAX_EXT]; char name[_MAX_FNAME]; [...] if(aheader->type==1) { /* ...if the type is 1 (file), the file name will be read... */ [...] char filename[_MAX_PATH]; memcpy(filename, afheader->filename, afheader->filenamesize); filename[afheader->filenamesize]=0; strupr(filename); _splitpath(filename, 0, 0, name, ext); /* ...splitted into "name" and "ext"... */ if(fsIsModule(ext)) /* ...and if "ext" is a module-extension (see CP.INI), then... */ { a.size=afheader->unpsize; a.parent=arcref; a.flags=0; fsConvFileName12(a.name, name, ext); /* ... this file will be added to the file list by adbAdd. */ if(!adbAdd(a)) { archive.close(); /* If the whole thing doesn't work, well, then it doesn't work. */ [...] return 0; } else { /* Otherwise, if wished, */ if(fsScanInArc&&[...]) { /* some blocks get be depacked (by the UNACE-routines in UAC_DCPR.*) */ dcpr_init_file(); int rd=dcpr_adds_blk(buf_wr, size_wrb); unsigned short fileref; fileref=mdbGetModuleReference(a.name, a.size); /* ...a reference for the just ADDed file will be retrieved... */ if((fileref!=0xFFFF)&&(!mdbInfoRead(fileref))) { moduleinfostruct ms; if(mdbGetModuleInfo(ms, fileref)) { mdbReadMemInfo(ms, (unsigned char*)buf_wr, rd); /* ...and then we e.g. look for the song name etc. by "mdbReadMemInfo". Just for explanation, again: "buf_wr" contains about the first unpacked 2-4kb of the actual file. Now "mdbReadMemInfo" tries to get to the infos in it with various, format-specific routines. */ mdbWriteModuleInfo(fileref, ms); /* These will also be set then. Another thing about the "mdbInfoRead" and "mdbGetModuleInfo": At first you have to read what is already known about the file, then actualize it and write it back. */ } } } } } [...] } } [...] archive.close(); return(1); }
Now let's come to adbACECall. This function is quite easy. Depending on act, it has to pack, unpack, move etc. the file file from the archive apath to dpath. To make it easier, only one act=="adbCallGet" is supported, ie unpacking.
static int adbACECall(int act, const char *apath, const char *file, const char *dpath) { switch (act) { case adbCallGet: { return !adbCallArc(cfGetProfileString("arcACE", "get", "ace e %a %n %d"), apath, file, dpath); /* "adbCallArc" is a nice function: it replaces something like %a, %n and %d with the passed arguments. The first argument has to be inserted for %a, the second for %n and the third for %d. Beforehand, "cfGetProfileString" gets from CP.INI whether the user wants to use another packer (XACE etc.) and that in the section "arcACE" and the key "get=". If the key hasn't been found, "ace e %a %n %d" will be taken by default, which, by the way, should be correct almost every time. :) */ } case adbCallPut: // not implemented break; case adbCallDelete: // not implemented break; case adbCallMoveTo: // not implemented break; case adbCallMoveFrom: // not implemented break; } return 0; }
Now a simple wmake compiles the whole thing presumed that a working makefile is available.