Subversion Repositories filter_foundry

Rev

Rev 526 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.     This file is part of "Filter Foundry", a filter plugin for Adobe Photoshop
  3.     Copyright (C) 2003-2009 Toby Thain, toby@telegraphics.net
  4.     Copyright (C) 2018-2022 Daniel Marschall, ViaThinkSoft
  5.  
  6.     This program is free software; you can redistribute it and/or modify
  7.     it under the terms of the GNU General Public License as published by
  8.     the Free Software Foundation; either version 2 of the License, or
  9.     (at your option) any later version.
  10.  
  11.     This program is distributed in the hope that it will be useful,
  12.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.     GNU General Public License for more details.
  15.  
  16.     You should have received a copy of the GNU General Public License
  17.     along with this program; if not, write to the Free Software
  18.     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19. */
  20.  
  21. #include <stddef.h>
  22. #include <stdint.h>
  23. #include <assert.h>
  24. #include <time.h>
  25.  
  26. #include "ff.h"
  27. #include "symtab.h"
  28.  
  29. #include "scripting.h"
  30. #include "PIActions.h"
  31. #include "PITerminology.h"
  32.  
  33. #include "compat_string.h"
  34.  
  35. //#define PRINTABLE_HASH_FF16
  36. #define ENABLE_APPLESCRIPT
  37.  
  38. /*
  39. Find a printable 4-character key, remembering (see Photoshop API guide):
  40. - All IDs starting with an uppercase letter are reserved by Adobe.
  41. - All IDs that are all uppercase are reserved by Apple.
  42. - All IDs that are all lowercase are reserved by Apple.
  43. - This leaves all IDs that begin with a lowercase letter and have at least
  44.   one uppercase letter for you and other plug-in developers.
  45. Note: It is questionable if "a!!!" or "A!!!" are also reseved by Apple. We don't risk it.
  46. */
  47. unsigned long printablehash(unsigned long hash) {
  48.         #ifdef PRINTABLE_HASH_FF16
  49.  
  50.         // FilterFoundry version 1.6 hashing by Toby Thain
  51.         // Only accepts upper-case at the last character
  52.         // 0       = 'a  A'
  53.         // 6100899 = 'z~~Z'
  54.         unsigned long key = 'a' + (hash % 26);  hash /= 26; // first lower-case
  55.         key = (key << 8) | (' ' + (hash % 95)); hash /= 95; // any printable
  56.         key = (key << 8) | (' ' + (hash % 95)); hash /= 95; // any printable
  57.         return  (key << 8) | ('A' + (hash % 26));           // last upper-case
  58.  
  59.         #else
  60.  
  61.         // FilterFoundry version 1.7 hashing by Daniel Marschall
  62.         // Accepts upper-case at character 2, 3 or 4
  63.         // Spaces are only set the right as padding to make a code shorter
  64.         // 0        = 'aA  '
  65.         // 13530139 = 'zZZZ'
  66.         // The key-space is ~2.22 times larger
  67.         long lowlim;
  68.         long uplim;
  69.         int upperCaseInfo;
  70.         int length;
  71.         int lastThreeCharInfo;
  72.         int firstChar;
  73.         unsigned long key;
  74.         int found;
  75.         int i,j,k;
  76.  
  77.         uplim = 0;
  78.         for (k=1; k<4; k++) {
  79.                 int mask = 0;
  80.                 if (k == 1) mask = 1;//0b1;
  81.                 if (k == 2) mask = 2;//0b11;
  82.                 if (k == 3) mask = 4;//0b111;
  83.                 for (i=1; i<=mask; i++) {
  84.                         // 'k' characters
  85.                         long test = 1;
  86.                         for (j=0; j<k; ++j) {
  87.                                 test *= ((i&(1<<j)) != 0) ? 26 : 94-26;
  88.                         }
  89.                         uplim += test;
  90.                 }
  91.         }
  92.  
  93.         lastThreeCharInfo = hash % uplim; hash /= uplim;
  94.         firstChar = hash%26; hash /= 26;
  95.  
  96.         lowlim = 0;
  97.         uplim = 0;
  98.         found = 0;
  99.         length = -1; // avoid compiler warning
  100.         upperCaseInfo = -1; // avoid compiler warning
  101.         for (k=1; k<4; k++) {
  102.                 int mask;
  103.                 if (k == 1) mask = 1;//0b1;
  104.                 if (k == 2) mask = 2;//0b11;
  105.                 if (k == 3) mask = 4;//0b111;
  106.                 if (!found) for (i=1; i<=mask; i++) {
  107.                         // 'k' characters
  108.                         long test = 1;
  109.                         for (j=0; j<k; ++j) {
  110.                                 test *= ((i&(1<<j)) != 0) ? 26 : 94-26;
  111.                         }
  112.                         uplim += test;
  113.                         if ((lastThreeCharInfo >= lowlim) && (lastThreeCharInfo < uplim)) {
  114.                                 lastThreeCharInfo -= lowlim;
  115.                                 found = 1;
  116.                                 length = k;
  117.                                 upperCaseInfo = i;
  118.                                 break;
  119.                         }
  120.                         lowlim = uplim;
  121.                 }
  122.         }
  123.  
  124.         key = ('a' + firstChar) << 24; // first char is lower-case
  125.         for (i=0; i<length; ++i) {
  126.                 char res;
  127.                 if ((upperCaseInfo&(1<<i)) == 0) {
  128.                         res = '!' + (lastThreeCharInfo % (94-26));
  129.                         if (res >= 'A') res += 26;
  130.                         lastThreeCharInfo /= (94-26);
  131.                 } else {
  132.                         res = 'A' + (lastThreeCharInfo % 26);
  133.                         lastThreeCharInfo /= 26;
  134.                 }
  135.                 key |= res << (8*(i+(4-length-1))); // 2nd, 3rd, 4th char are either upper-case or any printable char
  136.         }
  137.         if (length == 1) {
  138.                 key &= 0xFFFF0000;
  139.                 key |= ' ' << 8;
  140.                 key |= ' ';
  141.         }
  142.         if (length == 2) {
  143.                 key &= 0xFFFFFF00;
  144.                 key |= ' ';
  145.         }
  146.         return key;
  147.  
  148.         #endif
  149. }
  150.  
  151. size_t roundToNext4(size_t x) {
  152.         int pad = 4 - (x % 4);
  153.         if (pad == 0) pad = 4;
  154.         return x + pad;
  155. }
  156.  
  157. size_t fixpipl(PIPropertyList *pipl, size_t origsize, char* title, char* component, char* category, long *event_id) {
  158.         PIProperty *prop;
  159.         char *p;
  160.         struct hstm_data {
  161.                 /* this structure must be 14+1 bytes long, to match PiPL structure */
  162.                 long version; /* = 0 */
  163.                 long class_id;
  164.                 long event_id;
  165.                 short aete_resid;
  166.                 char scope[1];
  167.         };
  168.         struct hstm_data *hstm;
  169.         int scopelen;
  170.         unsigned long hash;
  171.         size_t realLength;
  172.         size_t roundedLength;
  173.         unsigned long componentVersion;
  174.         time_t curTime;
  175.         char szScope[0x300], szOID[0x50];
  176.  
  177.         pipl->count += 5; // 5 more keys in PiPL: catg, name, cmpt, hstm, ObId
  178.  
  179.         p = (char*)pipl + origsize;
  180.         prop = (PIProperty*)p;
  181.  
  182.         /* Important note about propertyLength:
  183.  
  184.                 In regards propertyLength, Adobe writes in SPPiPL.h:
  185.                 "Number of characters in the data array. Rounded to a multiple of 4."
  186.  
  187.                 On the other hand, the 1997 PICA documentation(page 23) and
  188.                 1996 "Cross-Application Plug-in Development Resource Guide" describes :
  189.                 "[propertyLength] contains the length of the propertyData field. It does not include any padding bytes after
  190.                 propertyData to achieve four byte alignment.This field may be zero."
  191.  
  192.                 I think this is not correct, since even official plugins of Adobe(e.g. "3D Transform.8bf") and cnvtpipl
  193.                 are rounding the length to a multiple of 4 (actually, rounding to the next possible multiple 4,
  194.                 so that padding is always guaranteed).
  195.                 Photoshop (tested with Photoshop 7) will crash if the propertyLength follows the definition of PICA.
  196.         */
  197.  
  198.         /* add Category property key */
  199.  
  200.         prop->vendorID = kPhotoshopSignature;
  201.         prop->propertyKey = PICategoryProperty;
  202.         prop->propertyID = 0;
  203.         prop->propertyLength = (SPInt32)roundToNext4(strlen(category) + 1);
  204.         memset(prop->propertyData, 0x00, prop->propertyLength); // fill padding with 00h bytes (cosmetics)
  205.         myc2pstrcpy((StringPtr)prop->propertyData, category);
  206.         p += offsetof(PIProperty, propertyData) + prop->propertyLength; // skip past new property record, and any padding
  207.         prop = (PIProperty*)p;
  208.  
  209.         /* add Title/Name property key */
  210.  
  211.         prop->vendorID = kPhotoshopSignature;
  212.         prop->propertyKey = PINameProperty;
  213.         prop->propertyID = 0;
  214.         prop->propertyLength = (SPInt32)roundToNext4(strlen(title) + 1);
  215.         memset(prop->propertyData, 0x00, prop->propertyLength); // fill padding with 00h bytes (cosmetics)
  216.         myc2pstrcpy((StringPtr)prop->propertyData, title);
  217.         p += offsetof(PIProperty, propertyData) + prop->propertyLength; // skip past new property record, and any padding
  218.         prop = (PIProperty*)p;
  219.  
  220.         /* add Component property key */
  221.  
  222.         prop->vendorID = kPhotoshopSignature;
  223.         prop->propertyKey = PIComponentProperty;
  224.         prop->propertyID = 0;
  225.         time(&curTime);
  226.         componentVersion = (unsigned long)(curTime - (time_t)946681200)/*01.Jan.2000 00:00:00*/;
  227.         prop->propertyLength = (SPInt32)roundToNext4(strlen(component) + 1 + sizeof(componentVersion));
  228.         memset(prop->propertyData, 0x00, prop->propertyLength); // fill padding with 00h bytes (cosmetics)
  229.         memcpy(prop->propertyData, &componentVersion, sizeof(componentVersion));
  230.         strcpy((char*)(prop->propertyData+sizeof(componentVersion)), component);
  231.         p += offsetof(PIProperty, propertyData) + prop->propertyLength; // skip past new property record, and any padding
  232.         prop = (PIProperty*)p;
  233.  
  234.         /* add OID property key */
  235.  
  236.         sprintf(szScope, "%s %s", category, component);
  237.         hash = djb2(szScope);
  238.         // max 47 chars ("1.3.6.1.4.1.37476.2.72.1.4294967295.4294967295\0")
  239.         sprintf(szOID, "1.3.6.1.4.1.37476.2.72.1.%lu.%lu", hash, componentVersion);
  240.  
  241.         prop->vendorID = kViaThinkSoftSignature;
  242.         prop->propertyKey = PIOIDProperty;
  243.         prop->propertyID = 0;
  244.         prop->propertyLength = (SPInt32)roundToNext4(strlen(szOID) + 1);
  245.         memset(prop->propertyData, 0x00, prop->propertyLength); // fill padding with 00h bytes (cosmetics)
  246.         strcpy((char*)prop->propertyData, szOID);
  247.         p += offsetof(PIProperty, propertyData) + prop->propertyLength; // skip past new property record, and any padding
  248.         prop = (PIProperty*)p;
  249.  
  250.         /* add HasTerminology property key */
  251.  
  252.         hstm = (struct hstm_data*)prop->propertyData;
  253.  
  254.         #ifdef ENABLE_APPLESCRIPT
  255.         // If the uniqueString/scope is set, the plugin will only communicate with Photoshop.
  256.         // Otherwise it can be accessed with AppleScript, but the AETE keys need to be unique then.
  257.         // This is achieved with getAeteKey().
  258.         scopelen = 0;
  259.         hstm->scope[0] = '\0';
  260.         #else
  261.         // Construct scope string by concatenating Category and Title - hopefully unique!
  262.         // Note: In doresources() we malloc'ed 300h additional bytes,
  263.         // and in the build dialog, title and category are size-limited,
  264.         // so we can write here without malloc.
  265.         scopelen = strlen(&szScope);
  266.         memcpy(&hstm->scope, &szScope, scopelen);
  267.         #endif
  268.  
  269.         /* make up a new event ID for this aete, based on printable base-95 hash of scope */
  270.         *event_id = printablehash(hash); /* this is used by aete_generate() later... */
  271.  
  272.         prop->vendorID = kPhotoshopSignature;
  273.         prop->propertyKey = PIHasTerminologyProperty;
  274.         prop->propertyID = 0;
  275.  
  276.         realLength = offsetof(struct hstm_data, scope) + scopelen + 1/*null-term*/;
  277.         roundedLength = roundToNext4(realLength);
  278.         prop->propertyLength = (SPInt32)roundedLength;
  279.         memset(prop->propertyData + realLength, 0x00, roundedLength - realLength); // fill padding with 00h bytes (cosmetics)
  280.  
  281.         hstm->version = 0;
  282.         hstm->class_id = plugInClassID;
  283.         hstm->event_id = *event_id;
  284.         hstm->aete_resid = AETE_ID;
  285.  
  286.         p += offsetof(PIProperty, propertyData) + prop->propertyLength;
  287.  
  288.         return p - (char*)pipl;  // figure how many bytes were added
  289. }
  290.  
  291. void _aete_write_byte(void** aeteptr, uint8_t val) {
  292.         uint8_t* tmp = *((uint8_t**)aeteptr);
  293.         *tmp = val;
  294.         *aeteptr = (void*)((unsigned char*)tmp + 1);
  295. }
  296. #define AETE_WRITE_BYTE(i) _aete_write_byte(&aeteptr, (i));
  297.  
  298. void _aete_write_word(void** aeteptr, uint16_t val) {
  299.         uint16_t* tmp = *((uint16_t**)aeteptr);
  300.         *tmp = val;
  301.         *aeteptr = (void*)((unsigned char*)tmp + 2);
  302. }
  303. #define AETE_WRITE_WORD(i) _aete_write_word(&aeteptr, (i));
  304.  
  305. void _aete_write_dword(void** aeteptr, uint32_t val) {
  306.         uint32_t* tmp = *((uint32_t**)aeteptr);
  307.         *tmp = val;
  308.         *aeteptr = (void*)((unsigned char*)tmp + 4);
  309. }
  310. #define AETE_WRITE_DWORD(i) _aete_write_dword(&aeteptr, (i));
  311.  
  312. void _aete_write_c2pstr(void** aeteptr, char* str) {
  313.         char* tmp;
  314.  
  315.         assert(strlen(str) <= 255);
  316.  
  317.         _aete_write_byte(aeteptr, (uint8_t)strlen(str));
  318.  
  319.         tmp = *((char**)aeteptr);
  320.         strcpy(tmp, str);
  321.         *aeteptr = (void*)((unsigned char*)tmp + strlen(str));
  322. }
  323. #define AETE_WRITE_C2PSTR(s) _aete_write_c2pstr(&aeteptr, (char*)(s));
  324.  
  325. void _aete_write_p2pstr(void** aeteptr, char* str) {
  326.         char* tmp;
  327.  
  328.         if (strlen(str) == 0) {
  329.                 _aete_write_byte(aeteptr, 0);
  330.         } else {
  331.                 tmp = *((char**)aeteptr);
  332.                 strcpy(tmp, str);
  333.                 *aeteptr = (void*)((unsigned char*)tmp + strlen(str));
  334.         }
  335. }
  336. #define AETE_WRITE_P2PSTR(s) _aete_write_p2pstr(&aeteptr, (s));
  337.  
  338. void _aete_align_word(void** aeteptr) {
  339.         #ifdef MAC_ENV
  340.         unsigned char* tmp = *((unsigned char**)aeteptr);
  341.         tmp += (intptr_t)tmp & 1;
  342.         *aeteptr = (void*)tmp;
  343.         #else
  344.         UNREFERENCED_PARAMETER(aeteptr);
  345.         #endif
  346. }
  347. #define AETE_ALIGN_WORD() _aete_align_word(&aeteptr);
  348.  
  349. void* _aete_property(void* aeteptr, PARM_T *pparm, int ctlidx, int mapidx, OSType key) {
  350.         char tmp[256];
  351.  
  352.         if (pparm->ctl_used[ctlidx] || pparm->map_used[mapidx]) {
  353.                 if (pparm->map_used[mapidx]) {
  354.                         if (ctlidx & 1) {
  355.                                 sprintf(tmp, "... %s", pparm->szMap[mapidx]);
  356.                         } else {
  357.                                 sprintf(tmp, "%s ...", pparm->szMap[mapidx]);
  358.                         }
  359.                         AETE_WRITE_C2PSTR(tmp);
  360.                 } else {
  361.                         AETE_WRITE_C2PSTR(pparm->szCtl[ctlidx]);
  362.                 }
  363.                 AETE_ALIGN_WORD();
  364.                 AETE_WRITE_DWORD(key);
  365.                 AETE_WRITE_DWORD(typeSInt32);
  366.                 AETE_WRITE_C2PSTR("");
  367.                 AETE_ALIGN_WORD();
  368.                 AETE_WRITE_WORD(0x8000); /* FLAGS_1_OPT_PARAM / flagsOptionalSingleParameter */
  369.         }
  370.  
  371.         return aeteptr;
  372. }
  373.  
  374. size_t aete_generate(void* aeteptr, PARM_T *pparm, long event_id) {
  375.         int numprops;
  376.         void *beginptr = aeteptr;
  377.  
  378.         // Attention!
  379.         // - On some systems (e.g. ARM based CPUs) this will cause an unaligned memory access exception.
  380.         //   For X86, memory access just becomes slower.
  381.         // - If you change something here, please also change it in scripting.rc (Windows) and scripting.r (Mac OS)
  382.  
  383.         // Note:
  384.         // - The 'aete' resource for Mac OS has word alignments after strings (but not if the next element is also a string)
  385.         //   see https://developer.apple.com/library/archive/documentation/mac/pdf/Interapplication_Communication/AE_Term_Resources.pdf page 8-9
  386.  
  387.         #ifdef WIN_ENV
  388.         AETE_WRITE_WORD(0x0001); /* Reserved (for Photoshop) */
  389.         #endif
  390.  
  391.         AETE_WRITE_BYTE(0x01); /* aete version */
  392.         AETE_WRITE_BYTE(0x00); /* aete version */
  393.         AETE_WRITE_WORD(english); /* language specifiers */
  394.         AETE_WRITE_WORD(roman);
  395.         AETE_WRITE_WORD(1); /* 1 suite */
  396.         {
  397.                 AETE_WRITE_C2PSTR(pparm->szAuthor); /* vendor suite name */
  398.                 AETE_WRITE_C2PSTR(""); /* optional description */
  399.                 AETE_ALIGN_WORD();
  400.                 AETE_WRITE_DWORD(plugInSuiteID); /* suite ID */
  401.                 AETE_WRITE_WORD(1); /* suite code, must be 1. Attention: Filters like 'Pointillize' have set this to 0! */
  402.                 AETE_WRITE_WORD(1); /* suite level, must be 1. Attention: Filters like 'Pointillize' have set this to 0! */
  403.                 AETE_WRITE_WORD(1); /* 1 event (structure for filters) */
  404.                 {
  405.                         AETE_WRITE_C2PSTR(pparm->szTitle); /* event name */
  406.                         AETE_WRITE_C2PSTR(""); /* event description */
  407.                         AETE_ALIGN_WORD();
  408.                         AETE_WRITE_DWORD(plugInClassID); /* event class */
  409.                         AETE_WRITE_DWORD(/*plugInEventID*/event_id); /* event ID */
  410.                         /* NO_REPLY: */
  411.                         AETE_WRITE_DWORD(noReply); /* noReply='null' */
  412.                         AETE_WRITE_C2PSTR(""); /* reply description */
  413.                         AETE_ALIGN_WORD();
  414.                         AETE_WRITE_WORD(0);
  415.                         /* IMAGE_DIRECT_PARAM: */
  416.                         AETE_WRITE_DWORD(typeImageReference); /* typeImageReference='#ImR' */
  417.                         AETE_WRITE_C2PSTR(""); /* direct parm description */
  418.                         AETE_ALIGN_WORD();
  419.                         AETE_WRITE_WORD(0xB000);
  420.  
  421.                         numprops = 0;
  422.                         if (pparm->ctl_used[0] || pparm->map_used[0]) numprops++;
  423.                         if (pparm->ctl_used[1] || pparm->map_used[0]) numprops++;
  424.                         if (pparm->ctl_used[2] || pparm->map_used[1]) numprops++;
  425.                         if (pparm->ctl_used[3] || pparm->map_used[1]) numprops++;
  426.                         if (pparm->ctl_used[4] || pparm->map_used[2]) numprops++;
  427.                         if (pparm->ctl_used[5] || pparm->map_used[2]) numprops++;
  428.                         if (pparm->ctl_used[6] || pparm->map_used[3]) numprops++;
  429.                         if (pparm->ctl_used[7] || pparm->map_used[3]) numprops++;
  430.                         AETE_WRITE_WORD(numprops);
  431.                         {
  432.                                 // Standalone filters don't need RGBA expressions
  433.                                 /*
  434.                                 AETE_WRITE_C2PSTR("R");
  435.                                 AETE_ALIGN_WORD();
  436.                                 AETE_WRITE_DWORD(PARAM_R_KEY);
  437.                                 AETE_WRITE_DWORD(typeText);
  438.                                 AETE_WRITE_C2PSTR("R channel expression");
  439.                                 AETE_ALIGN_WORD();
  440.                                 AETE_WRITE_WORD(0x8000);
  441.  
  442.                                 AETE_WRITE_C2PSTR("G");
  443.                                 AETE_ALIGN_WORD();
  444.                                 AETE_WRITE_DWORD(PARAM_G_KEY);
  445.                                 AETE_WRITE_DWORD(typeText);
  446.                                 AETE_WRITE_C2PSTR("G channel expression");
  447.                                 AETE_ALIGN_WORD();
  448.                                 AETE_WRITE_WORD(0x8000);
  449.  
  450.                                 AETE_WRITE_C2PSTR("B");
  451.                                 AETE_ALIGN_WORD();
  452.                                 AETE_WRITE_DWORD(PARAM_B_KEY);
  453.                                 AETE_WRITE_DWORD(typeText);
  454.                                 AETE_WRITE_C2PSTR("B channel expression");
  455.                                 AETE_ALIGN_WORD();
  456.                                 AETE_WRITE_WORD(0x8000);
  457.  
  458.                                 AETE_WRITE_C2PSTR("A");
  459.                                 AETE_ALIGN_WORD();
  460.                                 AETE_WRITE_DWORD(PARAM_A_KEY);
  461.                                 AETE_WRITE_DWORD(typeText);
  462.                                 AETE_WRITE_C2PSTR("A channel expression");
  463.                                 AETE_ALIGN_WORD();
  464.                                 AETE_WRITE_WORD(0x8000);
  465.                                 */
  466.  
  467.                                 aeteptr = _aete_property(aeteptr, pparm, 0, 0, getAeteKey('0', pparm));
  468.                                 aeteptr = _aete_property(aeteptr, pparm, 1, 0, getAeteKey('1', pparm));
  469.                                 aeteptr = _aete_property(aeteptr, pparm, 2, 1, getAeteKey('2', pparm));
  470.                                 aeteptr = _aete_property(aeteptr, pparm, 3, 1, getAeteKey('3', pparm));
  471.                                 aeteptr = _aete_property(aeteptr, pparm, 4, 2, getAeteKey('4', pparm));
  472.                                 aeteptr = _aete_property(aeteptr, pparm, 5, 2, getAeteKey('5', pparm));
  473.                                 aeteptr = _aete_property(aeteptr, pparm, 6, 3, getAeteKey('6', pparm));
  474.                                 aeteptr = _aete_property(aeteptr, pparm, 7, 3, getAeteKey('7', pparm));
  475.                         }
  476.                 }
  477.  
  478.                 /* non-filter plug-in class here */
  479.                 AETE_WRITE_WORD(0); /* 0 classes */
  480.                 {}
  481.                 AETE_WRITE_WORD(0); /* 0 comparison ops (not supported) */
  482.                 {}
  483.                 AETE_WRITE_WORD(0); /* 0 enumerations */
  484.                 {}
  485.         }
  486.         AETE_WRITE_DWORD(0); /* padding (FIXME: do we need that? Adobe's Windows filters don't) */
  487.  
  488.         return (unsigned char*)aeteptr - (unsigned char*)beginptr; // length of stuff written
  489. }
  490.