/trunk/CHANGELOG.md |
---|
3,6 → 3,7 |
## 1.7.0.21 [Work-In-Progress] |
- Read FFX file: Fixed buffer overflow when some strings (Title,Category,Author,Copyright,SliderNames) are too long |
- Read GUF file: Only the last part of the Category will be imported |
- Implemented reading of FFL files (actually, they will be only extracted, so they can be read afterwards) |
## 1.7.0.20 [21-Nov-2023] |
- Implemented "GIMP UserFilter" (GUF) file format. |
/trunk/README.md |
---|
64,9 → 64,10 |
|FFX |"Filters Unlimited" file. | | |Yes(2)| |
|TXT |A text file created by "Plugin Commander" or "FFDecomp". | |Yes(3)|Yes | |
|GUF |A filter file created by "GIMP UserFilter". | |Yes |Yes(2)| |
|FFL |"Filter Factory Library" by "Plugin Commander". | | |Yes(4)| |
|BIN or RSRC |Standalone filter created by Filter Factory/Foundry for Mac. | | |Yes | |
Currently not supported are FFL (Filter Library) files. |
Currently not supported are FFP (FilterMeister) files. |
(1) Loading is only possible if the 8BF file was created by Filter Factory, or by Filter Foundry without protection. |
74,7 → 75,9 |
(3) Title, Category, Author, Copyright, and Slider/Map names are left empty and must be added using a text editor. |
(4) The FFL files only be extracted, so they can be read afterwards. |
### Donation |
If you use this program and like it, the original author Toby Thain asks to donate to his PayPal (5 USD suggested or what you think it is worth): |
/trunk/TODO.md |
---|
30,8 → 30,6 |
Minor priority stuff or ideas |
----------------------------- |
* Import/Export FFL format (but which filter to select? We would need to show a select dialog...) |
* Import/Export FFP format |
* If controls are ambigous e.g. ctl(3+c), then you should be able to disable control in the "Make" dialog. Like in Filter Factory. |
/trunk/ff.h |
---|
143,6 → 143,7 |
Boolean readfile_8bf(StandardFileReply *sfr, TCHAR**reason); |
Handle readfileintohandle(FILEREF r); |
Boolean readfile_afs_pff(StandardFileReply* sfr, TCHAR** reason); |
Boolean readfile_ffl(StandardFileReply* sfr, TCHAR** reason); |
Boolean readfile_ffx(StandardFileReply* sfr, TCHAR** reason); |
Boolean readfile_picotxt_or_ffdecomp(StandardFileReply* sfr, TCHAR** reason); |
Boolean readfile_guf(StandardFileReply* sfr, TCHAR** reason); |
/trunk/language.h |
---|
264,6 → 264,14 |
#define MSG_INCOMPATIBLE_GUF_FILE_ENUS "Incompatible GIMP UserFilter File" |
#define MSG_INCOMPATIBLE_GUF_FILE_DEDE "Inkompatible GIMP UserFilter Datei" |
#define MSG_OPEN_FFL_ID 61 |
#define MSG_OPEN_FFL_ENUS "Filter Library" |
#define MSG_OPEN_FFL_DEDE "Filter Bibliothek" |
#define MSG_FFL_CONVERTED_ID 62 |
#define MSG_FFL_CONVERTED_ENUS "FFL file converted to TXT files. You can now open these TXT files." |
#define MSG_FFL_CONVERTED_DEDE "FFL Datei wurde in TXT Dateien extrahiert. Sie können diese TXT Dateien nun öffnen." |
void strcpy_advance_id(TCHAR** str, int msgid); |
int FF_GetMsg(TCHAR* ret, int MsgId); |
TCHAR* FF_GetMsg_Cpy(int MsgId); |
/trunk/language_mac.r |
---|
98,6 → 98,8 |
MSG_ABOUT_CONTACT_AUTHOR_ENUS, |
MSG_OPEN_GUF_ENUS, |
MSG_SAVE_GUF_ENUS, |
MSG_INCOMPATIBLE_GUF_FILE_ENUS |
MSG_INCOMPATIBLE_GUF_FILE_ENUS, |
MSG_OPEN_FFL_ENUS, |
MSG_FFL_CONVERTED_ENUS |
} |
} |
/trunk/language_win.rc |
---|
85,6 → 85,9 |
MSG_OPEN_GUF_ID, MSG_OPEN_GUF_ENUS |
MSG_SAVE_GUF_ID, MSG_SAVE_GUF_ENUS |
MSG_INCOMPATIBLE_GUF_FILE_ID, MSG_INCOMPATIBLE_GUF_FILE_ENUS |
MSG_OPEN_FFL_ID, MSG_OPEN_FFL_ENUS |
MSG_FFL_CONVERTED_ID, MSG_FFL_CONVERTED_ENUS |
} |
LANGUAGE LANG_GERMAN, SUBLANG_GERMAN |
152,5 → 155,7 |
MSG_OPEN_GUF_ID, MSG_OPEN_GUF_DEDE |
MSG_SAVE_GUF_ID, MSG_SAVE_GUF_DEDE |
MSG_INCOMPATIBLE_GUF_FILE_ID, MSG_INCOMPATIBLE_GUF_FILE_DEDE |
MSG_OPEN_FFL_ID, MSG_OPEN_FFL_DEDE |
MSG_FFL_CONVERTED_ID, MSG_FFL_CONVERTED_DEDE |
} |
/trunk/load_mac.c |
---|
97,6 → 97,15 |
} |
} |
// Try to read the file as FFL file |
if (*reason == NULL) { |
if (readfile_ffl(sfr,reason)) { |
gdata->parmloaded = false; |
gdata->obfusc = false; |
return true; |
} |
} |
// then try "Filters Unlimited" file (FFX) |
if (*reason == NULL) { |
if (readfile_ffx(sfr,reason)) { |
115,6 → 124,14 |
} |
} |
// Is it a "GIMP UserFilter (GUF)" file? (Only partially compatible with Filter Factory!!!) |
if (*reason == NULL) { |
if (readfile_guf(sfr,reason)) { |
gdata->parmloaded = true; |
return true; |
} |
} |
// Try Mac plugin resource |
if (*reason == NULL) { |
if (readmacplugin(sfr,reason)) { |
/trunk/load_win.c |
---|
103,6 → 103,15 |
} |
} |
// Try to read the file as FFL file |
if (reasonstr == NULL) { |
if (readfile_ffl(sfr, &reasonstr)) { |
gdata->obfusc = false; |
gdata->parmloaded = false; |
return true; |
} |
} |
// If that didn't work, try to load as Windows image file (Resource API for 8BF/PRM files) |
if (reasonstr == NULL) { |
if (hm = LoadLibraryEx(sfr->sfFile.szName, NULL, LOAD_LIBRARY_AS_DATAFILE)) { |
121,7 → 130,7 |
} |
} |
// Is it a "Filters Unlimited" filter? (Only partially compatible with Filter Factory!!!) |
// Is it a "Filters Unlimited" FFX filter? (Only partially compatible with Filter Factory!!!) |
if (reasonstr == NULL) { |
if (readfile_ffx(sfr, &reasonstr)) { |
gdata->parmloaded = true; |
129,7 → 138,7 |
} |
} |
// Is it a "Filters Unlimited" filter? (Only partially compatible with Filter Factory!!!) |
// Is it a "Filters Unlimited" TXT filter? (Only partially compatible with Filter Factory!!!) |
if (reasonstr == NULL) { |
if (readfile_picotxt_or_ffdecomp(sfr, &reasonstr)) { |
gdata->parmloaded = true; |
/trunk/read.c |
---|
38,8 → 38,8 |
Boolean readparams_afs_pff(Handle h, TCHAR**reason){ |
Boolean res = false; |
char linebuf[MAXLINE + 1] = { 0 }; |
char curexpr[MAXEXPR + 1] = { 0 }; |
char linebuf[MAXLINE] = { 0 }; |
char curexpr[MAXEXPR] = { 0 }; |
char *p, *dataend, *q; |
char c; |
int linecnt, lineptr, exprcnt; |
80,8 → 80,7 |
}else{ |
if(lineptr){ |
/* it's not an empty line; append it to current expr string */ |
if( q+lineptr > curexpr+MAXEXPR ){ |
// TODO: isn't the limit 1024?! (because we need to have the NUL too?) |
if( (q-curexpr) + lineptr >= MAXEXPR) { |
if (reason) *reason = FF_GetMsg_Cpy(MSG_EXPRESSION1024_FOUND_ID); |
break; |
} |
116,7 → 115,7 |
case 'r': |
#if WIN_ENV |
c = CR; |
if (lineptr < MAXLINE) |
if (lineptr < MAXLINE-1) |
linebuf[lineptr++] = c; |
c = LF; |
#else |
130,7 → 129,7 |
}//else if(alerts) alertuser((TCHAR*)TEXT("Warning:"),TEXT("truncated escape sequence ends input")); // TODO (Not so important): TRANSLATE |
} |
if(lineptr < MAXLINE) |
if(lineptr < MAXLINE-1) |
linebuf[lineptr++] = c; |
} |
} |
899,18 → 898,18 |
char out[256]; |
if (_gufReadProperty(q, count, "GUF", "Protocol", out, sizeof(out))) { |
if (strcmp(out, "1") != 0) { |
if (reason) *reason = FF_GetMsg_Cpy(MSG_INCOMPATIBLE_GUF_FILE_ID); |
PIUNLOCKHANDLE(h); |
PIDISPOSEHANDLE(h); |
FSClose(refnum); |
if (reason) *reason = FF_GetMsg_Cpy(MSG_INCOMPATIBLE_GUF_FILE_ID); |
return false; |
} |
} |
else { |
if (reason) *reason = FF_GetMsg_Cpy(MSG_INCOMPATIBLE_GUF_FILE_ID); |
PIUNLOCKHANDLE(h); |
PIDISPOSEHANDLE(h); |
FSClose(refnum); |
if (reason) *reason = FF_GetMsg_Cpy(MSG_INCOMPATIBLE_GUF_FILE_ID); |
return false; |
} |
if (_gufReadProperty(q, count, "Info", "Title", out, sizeof(out))) { |
1016,3 → 1015,188 |
return res; |
} |
Boolean readfile_ffl(StandardFileReply* sfr, TCHAR** reason) { |
FILEREF rTmp, refnum; |
Handle h, hTmp; |
Boolean res = false; |
StandardFileReply sfrTmp; |
OSErr e; |
char* p, * start; |
size_t est; |
if (!fileHasExtension(sfr, TEXT(".ffl"))) return false; |
if (FSpOpenDF(&sfr->sfFile, fsRdPerm, &refnum) == noErr) { |
if ((h = readfileintohandle(refnum))) { |
FILECOUNT count = (FILECOUNT)PIGETHANDLESIZE(h); |
char* q = PILOCKHANDLE(h, false); |
char* tmp_cur_filter_str[29]; |
int lineNumber = 0; |
int countFilters = 0; |
char* token = strtok(q, "\n"); |
while (token != NULL) { |
size_t i; |
char* token2 = my_strdup(token); |
for (i = 0; i < strlen(token2); i++) { |
if (token2[i] == '\r') token2[i] = '\0'; |
} |
if (lineNumber == 0) { |
if (strcmp(token2,"FFL1.0") != 0) { |
PIUNLOCKHANDLE(h); |
PIDISPOSEHANDLE(h); |
FSClose(refnum); |
//if (reason) *reason = TEXT("Invalid file signature"); // TODO: translate |
return false; |
} |
} |
else if (lineNumber == 1) { |
countFilters = atoi(token2); |
} |
else |
{ |
/* |
* 0 filter_file1.8bf |
* 1 Category 1 here |
* 2 Filter Name 1 here |
* 3 Autor 1 here |
* 4 Copyright 1 here |
* 5 Map1 name or empty line to disable map |
* 6 Map2 name or empty line to disable map |
* 7 Map3 name or empty line to disable map |
* 8 Map4 name or empty line to disable map |
* 9 Slider 1 |
* 10 Slider 2 |
* 11 Slider 3 |
* 12 Slider 4 |
* 13 Slider 5 |
* 14 Slider 6 |
* 15 Slider 7 |
* 16 Slider 8 |
* 17 100 |
* 18 110 |
* 19 120 |
* 20 130 |
* 21 140 |
* 22 150 |
* 23 160 |
* 24 170 |
* 25 r |
* 26 g |
* 27 b |
* 28 a |
*/ |
int filterLineNumber = (lineNumber - 2) % 29; |
tmp_cur_filter_str[filterLineNumber] = token2; |
if (filterLineNumber == 28) { |
TCHAR* curFileNameOrig; |
TCHAR* curFileName; |
curFileNameOrig = curFileName = (TCHAR*)malloc(MAX_PATH*sizeof(TCHAR)); |
strcpy_advance(&curFileName, sfr->sfFile.szName); |
curFileName -= 4; // remove ".ffl" extension |
*curFileName = (TCHAR)0; |
xstrcat(curFileNameOrig, TEXT("__")); |
curFileName += strlen("__"); |
strcpy_advance_a(&curFileName, tmp_cur_filter_str[0]); |
curFileName -= 4; // remove ".8bf" extension |
*curFileName = (TCHAR)0; |
#ifdef WIN_ENV |
xstrcat(curFileNameOrig, TEXT(".txt")); |
#endif |
sfrTmp.sfGood = true; |
sfrTmp.sfReplacing = true; |
sfrTmp.sfType = TEXT_FILETYPE; |
xstrcpy(sfrTmp.sfFile.szName, curFileNameOrig); |
#ifdef WIN_ENV |
sfrTmp.nFileExtension = (WORD)(xstrlen(curFileNameOrig) - strlen(".txt") + 1); |
#endif |
sfrTmp.sfScript = 0; // FIXME: is that ok? |
est = 16000; |
FSpDelete(&sfrTmp.sfFile); |
if (FSpCreate(&sfrTmp.sfFile, SIG_SIMPLETEXT, TEXT_FILETYPE, sfr->sfScript) == noErr) |
if (FSpOpenDF(&sfrTmp.sfFile, fsWrPerm, &rTmp) == noErr) { |
if ((hTmp = PINEWHANDLE(1))) { // don't set initial size to 0, since some hosts (e.g. GIMP/PSPI) are incompatible with that. |
PIUNLOCKHANDLE(hTmp); // should not be necessary |
if (!(e = PISETHANDLESIZE(hTmp, (int32)(est))) && (p = start = PILOCKHANDLE(hTmp, false))) { |
p += sprintf(p, "Category: %s\n", tmp_cur_filter_str[1]); |
p += sprintf(p, "Title: %s\n", tmp_cur_filter_str[2]); |
p += sprintf(p, "Copyright: %s\n", tmp_cur_filter_str[4]); |
p += sprintf(p, "Author: %s\n", tmp_cur_filter_str[3]); |
p += sprintf(p, "Filename: %s\n", tmp_cur_filter_str[0]); |
p += sprintf(p, "\n"); |
p += sprintf(p, "R:\n"); |
p += sprintf(p, "%s\n", tmp_cur_filter_str[25]); |
p += sprintf(p, "\n"); |
p += sprintf(p, "G:\n"); |
p += sprintf(p, "%s\n", tmp_cur_filter_str[26]); |
p += sprintf(p, "\n"); |
p += sprintf(p, "B:\n"); |
p += sprintf(p, "%s\n", tmp_cur_filter_str[27]); |
p += sprintf(p, "\n"); |
p += sprintf(p, "A:\n"); |
p += sprintf(p, "%s\n", tmp_cur_filter_str[28]); |
p += sprintf(p, "\n"); |
// Maps |
for (i = 0; i < 4; i++) { |
char* tmp = tmp_cur_filter_str[5 + i]; |
if (strcmp(tmp, "") != 0) { |
p += sprintf(p, "map[%d]: %s\n", i, tmp_cur_filter_str[5+i]); |
} |
} |
p += sprintf(p, "\n"); |
// Controls |
for (i = 0; i < 8; i++) { |
char* tmp = tmp_cur_filter_str[9 + i]; |
if (strcmp(tmp, "") != 0) { |
p += sprintf(p, "ctl[%d]: %s\n", i, tmp_cur_filter_str[9 + i]); |
} |
tmp = tmp_cur_filter_str[17 + i]; |
if (strcmp(tmp, "") != 0) { |
p += sprintf(p, "val[%d]: %s\n", i, tmp_cur_filter_str[17 + i]); |
} |
} |
p += sprintf(p, "\n"); |
PIUNLOCKHANDLE(hTmp); |
e = PISETHANDLESIZE(hTmp, (int32)(p - start)); // could ignore this error, maybe |
} |
savehandleintofile(hTmp, rTmp); |
PIDISPOSEHANDLE(hTmp); |
} |
FSClose(rTmp); |
} |
free(curFileNameOrig); |
for (i = 0; i < 29; i++) { |
free(tmp_cur_filter_str[i]); // free all "token2" |
} |
} |
} |
lineNumber++; |
token = strtok(NULL, "\n"); |
} |
PIUNLOCKHANDLE(h); |
PIDISPOSEHANDLE(h); |
} |
FSClose(refnum); |
} |
// TODO: show a different message when no filters were processed for some reason... |
// TODO: It's very confusing because this shows up as error message... |
if (reason) *reason = FF_GetMsg_Cpy(MSG_FFL_CONVERTED_ID); |
return false; |
} |
/trunk/testcases/import/example.ffl |
---|
0,0 → 1,89 |
FFL1.0 |
2 |
filter_file1.8bf |
Category 1 here |
Filter Name 1 here |
Autor 1 here |
Copyright 1 here |
Map1 name or empty line to disable map |
Map2 name or empty line to disable map |
Map3 name or empty line to disable map |
Map4 name or empty line to disable map |
Slider 1 |
Slider 2 |
Slider 3 |
Slider 4 |
Slider 5 |
Slider 6 |
Slider 7 |
Slider 8 |
100 |
110 |
120 |
130 |
140 |
150 |
160 |
170 |
d |
m |
b |
a |
filter_file2.8bf |
Category 2 here |
Filter Name 2 here |
Autor 2 here |
Copyright 2 here |
Map1 name or empty line to disable map |
Map2 name or empty line to disable map |
Map3 name or empty line to disable map |
Map4 name or empty line to disable map |
Slider 1 |
Slider 2 |
Slider 3 |
Slider 4 |
Slider 5 |
Slider 6 |
Slider 7 |
Slider 8 |
100 |
110 |
120 |
130 |
140 |
150 |
160 |
170 |
d |
m |
b |
a |
filter_file3.8bf |
Category 3 here |
Filter Name 3 here |
Autor 3 here |
Copyright 3 here |
Map1 name or empty line to disable map |
Map2 name or empty line to disable map |
Map3 name or empty line to disable map |
Map4 name or empty line to disable map |
Slider 1 |
Slider 2 |
Slider 3 |
Slider 4 |
Slider 5 |
Slider 6 |
Slider 7 |
Slider 8 |
100 |
110 |
120 |
130 |
140 |
150 |
160 |
170 |
d |
m |
b |
a |
/trunk/testcases/import/not_implemented/ffl/example.ffl |
---|
File deleted |
/trunk/testcases/import |
---|
Property changes: |
Added: svn:ignore |
+example__filter_file1.txt |
+example__filter_file2.txt |
+example__filter_file3.txt |
/trunk/ui.c |
---|
75,7 → 75,7 |
/* |
int i; |
char s[MAXEXPR+1]; |
char s[MAXEXPR]; |
for(i = 0; i < 8; ++i) |
slider[i] = (value_type)(GETSLIDERVALUE(dp,FIRSTCTLITEM+i)); |
83,7 → 83,7 |
if(!gdata->standalone) |
for(i = 0; i < 4; ++i){ |
// stash expression strings |
if(GETCTLTEXT(dp,FIRSTEXPRITEM+i,s,MAXEXPR)){ |
if(GETCTLTEXT(dp,FIRSTEXPRITEM+i,s,MAXEXPR)){ // cchMax: NULL is included, so MAXEXPR is correct |
if(expr[i]) |
free(expr[i]); |
expr[i] = _strdup(s); |
95,7 → 95,7 |
} |
struct node *updateexpr(DIALOGREF dp,int item){ |
char s[MAXEXPR+1]; |
char s[MAXEXPR]; |
int i; |
i = item - FIRSTEXPRITEM; |
103,7 → 103,7 |
freetree(tree[i]); |
if(!gdata->standalone){ |
GETCTLTEXT(dp,item,s,MAXEXPR); |
GETCTLTEXT(dp,item,s,MAXEXPR); // cchMax: NULL is included, so MAXEXPR is correct |
if(expr[i]) |
free(expr[i]); |
378,8 → 378,8 |
FF_GetMsg(title, MSG_LOAD_FILTER_SETTINGS_TITLE_ID); |
strcpy_advance_id(&tmp1, MSG_ALL_SUPPORTED_FILES_ID); |
strcpy_advance(&tmp1, (TCHAR*)TEXT(" (*.afs, *.8bf, *.pff, *.prm, *.bin, *.rsrc, *.txt, *.ffx, *.guf)")); tmp1++; |
strcpy_advance(&tmp1, (TCHAR*)TEXT("*.afs;*.8bf;*.pff;*.prm;*.bin;*.rsrc;*.txt;*.ffx;*.guf")); tmp1++; |
strcpy_advance(&tmp1, (TCHAR*)TEXT(" (*.afs, *.8bf, *.pff, *.prm, *.bin, *.rsrc, *.txt, *.ffx, *.ffl, *.guf)")); tmp1++; |
strcpy_advance(&tmp1, (TCHAR*)TEXT("*.afs;*.8bf;*.pff;*.prm;*.bin;*.rsrc;*.txt;*.ffx;*.ffl;*.guf")); tmp1++; |
strcpy_advance_id(&tmp1, MSG_OPEN_AFS_ID); |
strcpy_advance(&tmp1, (TCHAR*)TEXT(" (*.afs)")); tmp1++; |
409,6 → 409,10 |
strcpy_advance(&tmp1, (TCHAR*)TEXT(" (*.ffx)")); tmp1++; |
strcpy_advance(&tmp1, (TCHAR*)TEXT("*.ffx")); tmp1++; |
strcpy_advance_id(&tmp1, MSG_OPEN_FFL_ID); |
strcpy_advance(&tmp1, (TCHAR*)TEXT(" (*.ffl)")); tmp1++; |
strcpy_advance(&tmp1, (TCHAR*)TEXT("*.ffl")); tmp1++; |
strcpy_advance_id(&tmp1, MSG_OPEN_GUF_ID); |
strcpy_advance(&tmp1, (TCHAR*)TEXT(" (*.guf)")); tmp1++; |
strcpy_advance(&tmp1, (TCHAR*)TEXT("*.guf")); tmp1++; |