/* KITTENC - compile kitten files and attach this to executables in a way that the kitten library retrieves the language resources, in the same way that catgets/kittengets ever did */ /* This software is free software; free to use, modify, pass to others, whatever use it at your own risk */ /* Minor modifications to compile on UNIX host */ #include #include #include #include #include #include "kitten.h" #include "kittenc.h" #include "kitten.c" // Small hack to keep everything in one file void usage() { printf(kittengets(1,0,"KITTENC - KITTEN compiler\n")); printf(kittengets(1,1,"usage\n")); printf(kittengets(1,2,"KITTENC program.exe ATTACH NLS\\program.??\n")); printf(kittengets(1,3,"KITTENC program.exe ATTACH NLS\\program.DE\n")); printf(kittengets(1,4,"KITTENC program.exe ATTACH NLS\\program.DE NLS\\program.fr\n")); printf(kittengets(1,5,"KITTENC program.exe INFO : show info about language resources\n")); printf(kittengets(1,6,"KITTENC program.exe DUMP : recreate language resources\n")); printf(kittengets(1,7,"KITTENC program.exe TRUNCATE : delete attached resources\n")); } /** * Parse a string that represents an unsigned integer. * Returns -1 if an error is found. The first size * chars of the string are parsed. */ int mystrtoul (char *src, int base, int size) { int ret = 0; for (; size > 0; size--) { int digit; int ch = *src; src++; if (ch >= '0' && ch <= '9') digit = ch - '0'; else if (ch >= 'A' && ch <= 'Z') digit = ch - 'A' + 10; else if (ch >= 'a' && ch <= 'z') digit = ch - 'a' + 10; else return -1; if (digit >= base) return -1; ret = ret * base + digit; } /* for */ return ret; } /** * Process strings, converting \n, \t, \v, \b, \r, \f, \\, * \ddd, \xdd and \x0dd to actual chars. * (Note: \x is an extension to support hexadecimal) * This is used to allow the messages to use C escape sequences. * Modifies the line in-place (always same size or shorter). * Returns a pointer to input string. */ char special[] = "\\nrtvbafx"; char translated[]= "\\\n\r\t\v\b\a\fx"; char * processEscChars (char *line) { /* used when converting \xdd and \ddd (hex or octal) characters */ char ch; char *src = line; char *dst = line; /* possible as dst is shorter than src */ char *s; int chx; if (line == NULL) return line; /* cycle through copying characters, except when a \ is encountered. */ while (*src != '\0') { ch = *src; src++; if (ch != '\\') { *dst++ = ch; continue; } ch = *src; /* what follows slash? */ src++; s = strchr(special, ch); /* is it a special character?? */ if (s == NULL) /* no. store the character as is */ { *dst++ = ch; continue; } ch = translated[s - special]; if (ch != 'x') /* newline, formfeed etc. */ { *dst++ = ch; continue; } chx = mystrtoul (src, 16, 2); /* get value */ if (chx >= 0) { /* store character */ *dst = chx; dst++; src += 2; } else /* error so just store x (loose slash) */ { *dst = *src; dst++; } } /* while */ /* ensure '\0' terminated */ *dst = '\0'; return line; } // // process a single FIND.DE file // int process_language_file(FILE *fd, char *filename) { FILE *fdres; char linebuff[512]; char msg[512]; int id1, id2; struct message_header message_header; int len; int linecount = 0; if ((fdres = fopen(filename, "r")) == 0) { printf("can't open %s\n", filename); return 0; } while (fgets(linebuff, sizeof(linebuff), fdres)) { char *s = linebuff; linecount++; for (len = strlen(s); len > 0; ) // delete trailing white space { // from line len--; if (strchr(" \t\r\n", s[len]) == NULL) break; s[len] = '\0'; } if (len == 0) continue; if (linebuff[0] == '#') { // Skip comment line } else if (sscanf(linebuff, " %u . %u :%s", &id1, &id2, msg) != 3) { if (sscanf(linebuff, " %c", &id1) == 0 || // empty line id1 == '#') // # comment line continue; printf("%3u:%10.10s: missing 'num, num :' \n", linecount, linebuff); continue; } s = strchr(linebuff, ':'); if (s == NULL) continue; s++; processEscChars(s); if (strlen(s) > 250) { printf("%10.10s: lines must not be longer then 250 characters, is %u\n", linebuff, strlen(s)); s[250] = 0; } /* now here we have destilled id1, id2, message s */ message_header.len = strlen(s) // the message + 1 // the trailing \0 + sizeof(message_header); message_header.id = (id1 << 8) + id2; fwrite(&message_header, 1, sizeof(message_header), fd); fwrite(s, strlen(s)+1,1,fd); } message_header.len = 0; // END message message_header.id = 0; fwrite(&message_header, 1, sizeof(message_header), fd); return linecount; } // this is the 'compiler' part of KITTENC // // attach("FIND.EXE", "NLS\\FIND.??", 1) // attach("FIND.EXE", "NLS\\FIND.DE", NLS\\FIND.ES", 2) // int attach(char *progname, char *args[], int argc) { int i; FILE *fd; struct _finddata_t fileinfo; long search_handle; char *s; char *filename; char path_buffer[_MAX_PATH]; struct content content[KITTEN_MAX_RESOURCES]; struct message_end message_end; int linecount; int resourcecount = 0; if ((fd = fopen(progname, "r+b")) == NULL) { printf(kittengets(2,1, "executable file <%s> can't be opened. because <%s>\n"), progname, strerror( errno )); return 1; } fseek(fd, 0, SEEK_END); for (i = 0; i < argc; i++) { filename = args[i]; #ifdef __UNIX__ /* no need to expand wildcards on unix */ strcpy(path_buffer, filename); #else if ((search_handle = _findfirst(args[i], &fileinfo)) == 0) { printf(kittengets(2,2,"%s: no such file\n"), args[i]); return 1; } do { /* the name is name only, without the path */ if ((s = strrchr(filename, '\\')) != NULL || (s = strrchr(filename, '/')) != NULL || (s = strrchr(filename, ':')) != NULL) { memcpy(path_buffer, filename, s-filename+1); strcpy(path_buffer + (s-filename) +1, fileinfo.name); } else strcpy(path_buffer, fileinfo.name); #endif printf("%s:", path_buffer); if ((s = strrchr(path_buffer, '.')) == NULL || strlen(path_buffer) - (s - path_buffer) != 3) { printf(kittengets(2,3,"file names MUST end with .EN\n")); continue; } if (resourcecount > KITTEN_MAX_RESOURCES) { printf(kittengets(2,4,"too many resourcefiles (max %d)\n"), KITTEN_MAX_RESOURCES); continue; } content[resourcecount].filepos_start = ftell(fd); strcpy(content[resourcecount].language,s+1); linecount = process_language_file(fd, path_buffer); content[resourcecount].filepos_end = ftell(fd); content[resourcecount].reserved = 0; printf("%u messages, %lu byte\n", linecount, content[resourcecount].filepos_end - content[resourcecount].filepos_start); resourcecount++; #ifndef __UNIX__ } while (_findnext(search_handle, &fileinfo) == 0); #endif } message_end.filepos = ftell(fd); /* make all absolute filepositions relative to file end to make UPX support easier */ fwrite(&content, sizeof(content[0]),resourcecount,fd); message_end.resource_count = resourcecount; memcpy(message_end.ID, "KITTENC",8); message_end.fileend_orig = ftell(fd) + sizeof(message_end); fwrite(&message_end, sizeof(message_end),1,fd); fclose(fd); return 0; } enum { KITTEN_TRUNCATE, KITTEN_INFO, KITTEN_DUMP }; do_kitten_stuff(char *progname, int mode) { int fd; struct message_end message_end; struct content content[KITTEN_MAX_RESOURCES]; int i; long fileendnow, seekoffset; if ((fd = open(progname, _O_RDWR | _O_BINARY)) < 0) { printf("can't open %s because<%s>\n", progname, strerror(errno)); return 0; } fileendnow = lseek(fd, 0, SEEK_END); lseek(fd, - (int)sizeof(struct message_end), SEEK_END); read(fd, &message_end, sizeof(struct message_end)); if (memcmp(message_end.ID, "KITTENC", 8) != 0) { printf("no KITTENC record found\n"); goto RETURN; } if (message_end.resource_count > KITTEN_MAX_RESOURCES) { printf("resource > %d\n", KITTEN_MAX_RESOURCES); goto RETURN; } seekoffset = fileendnow - message_end.fileend_orig; if (lseek(fd, message_end.filepos+seekoffset, SEEK_SET) != message_end.filepos+seekoffset) { printf("can't seek 1\n"); goto RETURN; } read(fd, &content, sizeof(content[0]) * message_end.resource_count); if (mode == KITTEN_TRUNCATE) { _chsize(fd, content[0].filepos_start); // truncate file goto RETURN; } if (mode == KITTEN_INFO) { printf("%s: %u languages supported\n", progname, message_end.resource_count); for (i = 0; i < message_end.resource_count; i++) { printf("%2.2s %lu byte\n", content[i].language, content[i].filepos_end - content[i].filepos_start); } goto RETURN; } if (mode == KITTEN_DUMP) { char resname[_MAX_PATH],*s; char outname[_MAX_PATH]; FILE *fdout; int i; int linecount; for (i = 0; i < message_end.resource_count; i++) // for each language { _splitpath(progname, NULL,NULL, resname, NULL); // reconstruct KITTENC.DE sprintf(outname, "%s.%c%c", resname, content[i].language[0], content[i].language[1]);; if ((fdout = fopen(outname, "w")) == NULL) // create KITTENC.DE { printf("can't write resourcefile %s\n", outname); goto RETURN; } lseek(fd, content[i].filepos_start+seekoffset, SEEK_SET); printf("%s : ", outname); linecount= 0; while (1) { short len, id; char c; read(fd, &len, sizeof(short)); if (len == 0) break; read(fd,&id, sizeof(short)); fprintf(fdout, "%u,%u:", id >> 8, id & 0xff); while (read(fd, &c, 1) == 1 && c != 0) { if (c >= 0x20) fputc(c, fdout); else { if ((s = strchr(translated, c)) != NULL) /* \t \n \f */ { fprintf(fdout, "\\%c", special[s - translated]); } else fprintf(fdout, "\\x%02x", c); } } // next character fprintf(fdout, "\n"); linecount++; } // next line printf("%u messages\n", linecount); fclose(fdout); } // next language goto RETURN; } RETURN: close(fd); return 0; } main(int argc, char *argv[]) { char *progname; char buffer[_MAX_PATH]; kittenopen(argv[0]); printf("KITTENC 1.0\n"); //printf(kittengets(0,0,"hallo world\n")); if (argc < 3) { usage(); exit(1); } progname = argv[1]; #ifndef __UNIX__ strupr(progname); if (strstr(progname, ".EXE") == NULL && strstr(progname, ".COM") == NULL) { sprintf(buffer,"%s.EXE", progname); progname = buffer; } #endif if (stricmp(argv[2], "ATTACH") == 0) { do_kitten_stuff(progname, KITTEN_TRUNCATE); return attach(progname, argv+3, argc-3); } else if (stricmp(argv[2], "TRUNCATE") == 0) { return do_kitten_stuff(progname, KITTEN_TRUNCATE); } else if (stricmp(argv[2], "INFO") == 0) { return do_kitten_stuff(progname, KITTEN_INFO); } else if (stricmp(argv[2], "DUMP") == 0) { return do_kitten_stuff(progname, KITTEN_DUMP); } else { printf("'%s' is wrong %s %s %s\n", argv[2], argv[0], argv[1], argv[2]); usage(); exit(1); } }