Next: Compiling OPENCP Up: Device Development Previous: the Internals Contents

## Our first Cubic-DLL

To create a DLL, you first have to create an environment where you can do this best. The easiest method is to install the source of OPENCP and put one's DLL into the makefile. However, since the directory gets messy soon, I recommend developing one's DLLs elsewhere. At first you should get all `.lib` and `.h` of OPENCP by compiling the player once.8.4

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.

#### Footnotes

... once.8.4
  Soon there is said to come an archive with all `.lib` and `.h` for those people who just want to develop something new.
... following 8.5
  The function has been heavily simplified and can't be run in this form, but all things concerning OPENCP archives are in it. Only the ACE-specifical things, most of all the thing with the SOLID-archives, are missing.

Next: Compiling OPENCP Up: Device Development Previous: the Internals Contents