|
|
|
@ -18,6 +18,7 @@
|
|
|
|
|
#include "../table/control_codes.h"
|
|
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <exception>
|
|
|
|
|
|
|
|
|
|
#if (!defined(WIN32) && !defined(WIN64)) || defined(__CYGWIN__)
|
|
|
|
|
#include <unistd.h>
|
|
|
|
@ -48,8 +49,6 @@ struct Case {
|
|
|
|
|
static bool _translated; ///< Whether the current language is not the master language
|
|
|
|
|
static bool _translation; ///< Is the current file actually a translation or not
|
|
|
|
|
static const char *_file = "(unknown file)"; ///< The filename of the input, so we can refer to it in errors/warnings
|
|
|
|
|
static FILE *_output_file = NULL; ///< The file we are currently writing output to
|
|
|
|
|
static const char *_output_filename = NULL; ///< The filename of the output, so we can delete it if compilation fails
|
|
|
|
|
static int _cur_line; ///< The current line we're parsing in the input file
|
|
|
|
|
static int _errors, _warnings, _show_todo;
|
|
|
|
|
|
|
|
|
@ -165,12 +164,7 @@ void NORETURN CDECL error(const char *s, ...)
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
|
fprintf(stderr, LINE_NUM_FMT("warning"), _file, _cur_line, "language is not compiled");
|
|
|
|
|
#endif
|
|
|
|
|
/* We were writing output to a file, remove it. */
|
|
|
|
|
if (_output_file != NULL) {
|
|
|
|
|
fclose(_output_file);
|
|
|
|
|
unlink(_output_filename);
|
|
|
|
|
}
|
|
|
|
|
exit(1);
|
|
|
|
|
throw std::exception();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void PutByte(byte c)
|
|
|
|
@ -907,59 +901,134 @@ bool CompareFiles(const char *n1, const char *n2)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Base class for writing data to disk. */
|
|
|
|
|
struct FileWriter {
|
|
|
|
|
FILE *fh; ///< The file handle we're writing to.
|
|
|
|
|
const char *filename; ///< The file name we're writing to.
|
|
|
|
|
|
|
|
|
|
static void WriteStringsH(const char *filename)
|
|
|
|
|
{
|
|
|
|
|
int next = -1;
|
|
|
|
|
/**
|
|
|
|
|
* Open a file to write to.
|
|
|
|
|
* @param filename The file to open.
|
|
|
|
|
*/
|
|
|
|
|
FileWriter(const char *filename)
|
|
|
|
|
{
|
|
|
|
|
this->filename = strdup(filename);
|
|
|
|
|
this->fh = fopen(this->filename, "wb");
|
|
|
|
|
|
|
|
|
|
_output_filename = "tmp.xxx";
|
|
|
|
|
_output_file = fopen(_output_filename, "w");
|
|
|
|
|
if (_output_file == NULL) error("can't open tmp.xxx");
|
|
|
|
|
if (this->fh == NULL) {
|
|
|
|
|
error("Could not open %s", this->filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(_output_file, "/* This file is automatically generated. Do not modify */\n\n");
|
|
|
|
|
fprintf(_output_file, "#ifndef TABLE_STRINGS_H\n");
|
|
|
|
|
fprintf(_output_file, "#define TABLE_STRINGS_H\n");
|
|
|
|
|
/** Finalise the writing. */
|
|
|
|
|
void Finalise()
|
|
|
|
|
{
|
|
|
|
|
fclose(this->fh);
|
|
|
|
|
this->fh = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i != lengthof(_strings); i++) {
|
|
|
|
|
if (_strings[i] != NULL) {
|
|
|
|
|
if (next != i) fprintf(_output_file, "\n");
|
|
|
|
|
fprintf(_output_file, "static const StringID %s = 0x%X;\n", _strings[i]->name, i);
|
|
|
|
|
next = i + 1;
|
|
|
|
|
/** Make sure the file is closed. */
|
|
|
|
|
virtual ~FileWriter()
|
|
|
|
|
{
|
|
|
|
|
printf("close %s %p\n", this->filename, this->fh);
|
|
|
|
|
/* If we weren't closed an exception was thrown, so remove the termporary file. */
|
|
|
|
|
if (fh != NULL) {
|
|
|
|
|
fclose(this->fh);
|
|
|
|
|
unlink(this->filename);
|
|
|
|
|
}
|
|
|
|
|
free(this->filename);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fprintf(_output_file, "\nstatic const StringID STR_LAST_STRINGID = 0x%X;\n\n", next - 1);
|
|
|
|
|
/** Base class for writing the header. */
|
|
|
|
|
struct HeaderWriter {
|
|
|
|
|
/**
|
|
|
|
|
* Write the string ID.
|
|
|
|
|
* @param name The name of the string.
|
|
|
|
|
* @param stringid The ID of the string.
|
|
|
|
|
*/
|
|
|
|
|
virtual void WriteStringID(const char *name, int stringid) = 0;
|
|
|
|
|
|
|
|
|
|
/* Find the plural form with the most amount of cases. */
|
|
|
|
|
int max_plural_forms = 0;
|
|
|
|
|
for (uint i = 0; i < lengthof(_plural_forms); i++) {
|
|
|
|
|
max_plural_forms = max(max_plural_forms, _plural_forms[i].plural_count);
|
|
|
|
|
/**
|
|
|
|
|
* Finalise writing the file.
|
|
|
|
|
*/
|
|
|
|
|
virtual void Finalise() = 0;
|
|
|
|
|
|
|
|
|
|
/** Especially destroy the subclasses. */
|
|
|
|
|
virtual ~HeaderWriter() {};
|
|
|
|
|
|
|
|
|
|
/** Write the header information. */
|
|
|
|
|
void WriteHeader()
|
|
|
|
|
{
|
|
|
|
|
int last = 0;
|
|
|
|
|
for (int i = 0; i != lengthof(_strings); i++) {
|
|
|
|
|
if (_strings[i] != NULL) {
|
|
|
|
|
this->WriteStringID(_strings[i]->name, i);
|
|
|
|
|
last = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->WriteStringID("STR_LAST_STRINGID", last);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fprintf(_output_file,
|
|
|
|
|
"static const uint LANGUAGE_PACK_VERSION = 0x%X;\n"
|
|
|
|
|
"static const uint LANGUAGE_MAX_PLURAL = %d;\n"
|
|
|
|
|
"static const uint LANGUAGE_MAX_PLURAL_FORMS = %d;\n\n",
|
|
|
|
|
(uint)_hash, (uint)lengthof(_plural_forms), max_plural_forms
|
|
|
|
|
);
|
|
|
|
|
struct HeaderFileWriter : HeaderWriter, FileWriter {
|
|
|
|
|
/** The real file name we eventually want to write to. */
|
|
|
|
|
const char *real_filename;
|
|
|
|
|
/** The previous string ID that was printed. */
|
|
|
|
|
int prev;
|
|
|
|
|
|
|
|
|
|
fprintf(_output_file, "#endif /* TABLE_STRINGS_H */\n");
|
|
|
|
|
/**
|
|
|
|
|
* Open a file to write to.
|
|
|
|
|
* @param filename The file to open.
|
|
|
|
|
*/
|
|
|
|
|
HeaderFileWriter(const char *filename) : FileWriter("tmp.xxx"),
|
|
|
|
|
real_filename(strdup(filename)), prev(0)
|
|
|
|
|
{
|
|
|
|
|
fprintf(this->fh, "/* This file is automatically generated. Do not modify */\n\n");
|
|
|
|
|
fprintf(this->fh, "#ifndef TABLE_STRINGS_H\n");
|
|
|
|
|
fprintf(this->fh, "#define TABLE_STRINGS_H\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fclose(_output_file);
|
|
|
|
|
_output_file = NULL;
|
|
|
|
|
void WriteStringID(const char *name, int stringid)
|
|
|
|
|
{
|
|
|
|
|
if (prev + 1 != stringid) fprintf(this->fh, "\n");
|
|
|
|
|
fprintf(this->fh, "static const StringID %s = 0x%X;\n", name, stringid);
|
|
|
|
|
prev = stringid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CompareFiles(_output_filename, filename)) {
|
|
|
|
|
/* files are equal. tmp.xxx is not needed */
|
|
|
|
|
unlink(_output_filename);
|
|
|
|
|
} else {
|
|
|
|
|
/* else rename tmp.xxx into filename */
|
|
|
|
|
#if defined(WIN32) || defined(WIN64)
|
|
|
|
|
unlink(filename);
|
|
|
|
|
#endif
|
|
|
|
|
if (rename(_output_filename, filename) == -1) error("rename() failed");
|
|
|
|
|
void Finalise()
|
|
|
|
|
{
|
|
|
|
|
/* Find the plural form with the most amount of cases. */
|
|
|
|
|
int max_plural_forms = 0;
|
|
|
|
|
for (uint i = 0; i < lengthof(_plural_forms); i++) {
|
|
|
|
|
max_plural_forms = max(max_plural_forms, _plural_forms[i].plural_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(this->fh,
|
|
|
|
|
"\n"
|
|
|
|
|
"static const uint LANGUAGE_PACK_VERSION = 0x%X;\n"
|
|
|
|
|
"static const uint LANGUAGE_MAX_PLURAL = %d;\n"
|
|
|
|
|
"static const uint LANGUAGE_MAX_PLURAL_FORMS = %d;\n\n",
|
|
|
|
|
(uint)_hash, (uint)lengthof(_plural_forms), max_plural_forms
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
fprintf(this->fh, "#endif /* TABLE_STRINGS_H */\n");
|
|
|
|
|
|
|
|
|
|
this->FileWriter::Finalise();
|
|
|
|
|
|
|
|
|
|
if (CompareFiles(this->filename, this->real_filename)) {
|
|
|
|
|
/* files are equal. tmp.xxx is not needed */
|
|
|
|
|
unlink(this->filename);
|
|
|
|
|
} else {
|
|
|
|
|
/* else rename tmp.xxx into filename */
|
|
|
|
|
#if defined(WIN32) || defined(WIN64)
|
|
|
|
|
unlink(filename);
|
|
|
|
|
#endif
|
|
|
|
|
if (rename(this->filename, this->real_filename) == -1) error("rename() failed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_output_filename = NULL;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int TranslateArgumentIdx(int argidx, int offset)
|
|
|
|
|
{
|
|
|
|
@ -1034,130 +1103,182 @@ static void PutCommandString(const char *str)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void WriteLength(FILE *f, uint length)
|
|
|
|
|
{
|
|
|
|
|
if (length < 0xC0) {
|
|
|
|
|
fputc(length, f);
|
|
|
|
|
} else if (length < 0x4000) {
|
|
|
|
|
fputc((length >> 8) | 0xC0, f);
|
|
|
|
|
fputc(length & 0xFF, f);
|
|
|
|
|
} else {
|
|
|
|
|
error("string too long");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Base class for all language writers. */
|
|
|
|
|
struct LanguageWriter {
|
|
|
|
|
/**
|
|
|
|
|
* Write the header metadata. The multi-byte integers are already converted to
|
|
|
|
|
* the little endian format.
|
|
|
|
|
* @param header The header to write.
|
|
|
|
|
*/
|
|
|
|
|
virtual void WriteHeader(const LanguagePackHeader *header) = 0;
|
|
|
|
|
|
|
|
|
|
static void WriteLangfile(const char *filename)
|
|
|
|
|
{
|
|
|
|
|
uint in_use[32];
|
|
|
|
|
/**
|
|
|
|
|
* Write a number of bytes.
|
|
|
|
|
* @param buffer The buffer to write.
|
|
|
|
|
* @param length The amount of byte to write.
|
|
|
|
|
*/
|
|
|
|
|
virtual void Write(const byte *buffer, size_t length) = 0;
|
|
|
|
|
|
|
|
|
|
_output_filename = filename;
|
|
|
|
|
_output_file = fopen(filename, "wb");
|
|
|
|
|
if (_output_file == NULL) error("can't open %s", filename);
|
|
|
|
|
/**
|
|
|
|
|
* Finalise writing the file.
|
|
|
|
|
*/
|
|
|
|
|
virtual void Finalise() = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i != 32; i++) {
|
|
|
|
|
uint n = CountInUse(i);
|
|
|
|
|
/** Especially destroy the subclasses. */
|
|
|
|
|
virtual ~LanguageWriter() {}
|
|
|
|
|
|
|
|
|
|
in_use[i] = n;
|
|
|
|
|
_lang.offsets[i] = TO_LE16(n);
|
|
|
|
|
/**
|
|
|
|
|
* Write the length as a simple gamma.
|
|
|
|
|
* @param length The number to write.
|
|
|
|
|
*/
|
|
|
|
|
void WriteLength(uint length)
|
|
|
|
|
{
|
|
|
|
|
char buffer[2];
|
|
|
|
|
int offs = 0;
|
|
|
|
|
if (length >= 0x4000) {
|
|
|
|
|
error("string too long");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (uint j = 0; j != in_use[i]; j++) {
|
|
|
|
|
const LangString *ls = _strings[(i << 11) + j];
|
|
|
|
|
if (ls != NULL && ls->translated == NULL) _lang.missing++;
|
|
|
|
|
if (length >= 0xC0) {
|
|
|
|
|
buffer[offs++] = (length >> 8) | 0xC0;
|
|
|
|
|
}
|
|
|
|
|
buffer[offs++] = length & 0xFF;
|
|
|
|
|
this->Write((byte*)buffer, offs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_lang.ident = TO_LE32(LanguagePackHeader::IDENT);
|
|
|
|
|
_lang.version = TO_LE32(_hash);
|
|
|
|
|
_lang.missing = TO_LE16(_lang.missing);
|
|
|
|
|
_lang.winlangid = TO_LE16(_lang.winlangid);
|
|
|
|
|
|
|
|
|
|
fwrite(&_lang, sizeof(_lang), 1, _output_file);
|
|
|
|
|
/**
|
|
|
|
|
* Actually write the language.
|
|
|
|
|
*/
|
|
|
|
|
void WriteLang()
|
|
|
|
|
{
|
|
|
|
|
uint in_use[32];
|
|
|
|
|
for (int i = 0; i != 32; i++) {
|
|
|
|
|
uint n = CountInUse(i);
|
|
|
|
|
|
|
|
|
|
in_use[i] = n;
|
|
|
|
|
_lang.offsets[i] = TO_LE16(n);
|
|
|
|
|
|
|
|
|
|
for (uint j = 0; j != in_use[i]; j++) {
|
|
|
|
|
const LangString *ls = _strings[(i << 11) + j];
|
|
|
|
|
if (ls != NULL && ls->translated == NULL) _lang.missing++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i != 32; i++) {
|
|
|
|
|
for (uint j = 0; j != in_use[i]; j++) {
|
|
|
|
|
const LangString *ls = _strings[(i << 11) + j];
|
|
|
|
|
const Case *casep;
|
|
|
|
|
const char *cmdp;
|
|
|
|
|
_lang.ident = TO_LE32(LanguagePackHeader::IDENT);
|
|
|
|
|
_lang.version = TO_LE32(_hash);
|
|
|
|
|
_lang.missing = TO_LE16(_lang.missing);
|
|
|
|
|
_lang.winlangid = TO_LE16(_lang.winlangid);
|
|
|
|
|
|
|
|
|
|
/* For undefined strings, just set that it's an empty string */
|
|
|
|
|
if (ls == NULL) {
|
|
|
|
|
WriteLength(_output_file, 0);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
this->WriteHeader(&_lang);
|
|
|
|
|
|
|
|
|
|
_cur_ident = ls->name;
|
|
|
|
|
_cur_line = ls->line;
|
|
|
|
|
for (int i = 0; i != 32; i++) {
|
|
|
|
|
for (uint j = 0; j != in_use[i]; j++) {
|
|
|
|
|
const LangString *ls = _strings[(i << 11) + j];
|
|
|
|
|
const Case *casep;
|
|
|
|
|
const char *cmdp;
|
|
|
|
|
|
|
|
|
|
/* Produce a message if a string doesn't have a translation. */
|
|
|
|
|
if (_show_todo > 0 && ls->translated == NULL) {
|
|
|
|
|
if ((_show_todo & 2) != 0) {
|
|
|
|
|
strgen_warning("'%s' is untranslated", ls->name);
|
|
|
|
|
/* For undefined strings, just set that it's an empty string */
|
|
|
|
|
if (ls == NULL) {
|
|
|
|
|
this->WriteLength(0);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ((_show_todo & 1) != 0) {
|
|
|
|
|
const char *s = "<TODO> ";
|
|
|
|
|
while (*s != '\0') PutByte(*s++);
|
|
|
|
|
|
|
|
|
|
_cur_ident = ls->name;
|
|
|
|
|
_cur_line = ls->line;
|
|
|
|
|
|
|
|
|
|
/* Produce a message if a string doesn't have a translation. */
|
|
|
|
|
if (_show_todo > 0 && ls->translated == NULL) {
|
|
|
|
|
if ((_show_todo & 2) != 0) {
|
|
|
|
|
strgen_warning("'%s' is untranslated", ls->name);
|
|
|
|
|
}
|
|
|
|
|
if ((_show_todo & 1) != 0) {
|
|
|
|
|
const char *s = "<TODO> ";
|
|
|
|
|
while (*s != '\0') PutByte(*s++);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Extract the strings and stuff from the english command string */
|
|
|
|
|
ExtractCommandString(&_cur_pcs, ls->english, false);
|
|
|
|
|
/* Extract the strings and stuff from the english command string */
|
|
|
|
|
ExtractCommandString(&_cur_pcs, ls->english, false);
|
|
|
|
|
|
|
|
|
|
if (ls->translated_case != NULL || ls->translated != NULL) {
|
|
|
|
|
casep = ls->translated_case;
|
|
|
|
|
cmdp = ls->translated;
|
|
|
|
|
} else {
|
|
|
|
|
casep = NULL;
|
|
|
|
|
cmdp = ls->english;
|
|
|
|
|
}
|
|
|
|
|
if (ls->translated_case != NULL || ls->translated != NULL) {
|
|
|
|
|
casep = ls->translated_case;
|
|
|
|
|
cmdp = ls->translated;
|
|
|
|
|
} else {
|
|
|
|
|
casep = NULL;
|
|
|
|
|
cmdp = ls->english;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_translated = cmdp != ls->english;
|
|
|
|
|
|
|
|
|
|
if (casep != NULL) {
|
|
|
|
|
const Case *c;
|
|
|
|
|
uint num;
|
|
|
|
|
|
|
|
|
|
/* Need to output a case-switch.
|
|
|
|
|
* It has this format
|
|
|
|
|
* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
|
|
|
|
|
* Each LEN is printed using 2 bytes in big endian order. */
|
|
|
|
|
PutUtf8(SCC_SWITCH_CASE);
|
|
|
|
|
/* Count the number of cases */
|
|
|
|
|
for (num = 0, c = casep; c; c = c->next) num++;
|
|
|
|
|
PutByte(num);
|
|
|
|
|
|
|
|
|
|
/* Write each case */
|
|
|
|
|
for (c = casep; c != NULL; c = c->next) {
|
|
|
|
|
uint pos;
|
|
|
|
|
|
|
|
|
|
PutByte(c->caseidx);
|
|
|
|
|
/* Make some space for the 16-bit length */
|
|
|
|
|
pos = _put_pos;
|
|
|
|
|
PutByte(0);
|
|
|
|
|
PutByte(0);
|
|
|
|
|
/* Write string */
|
|
|
|
|
PutCommandString(c->string);
|
|
|
|
|
PutByte(0); // terminate with a zero
|
|
|
|
|
/* Fill in the length */
|
|
|
|
|
_put_buf[pos + 0] = GB(_put_pos - (pos + 2), 8, 8);
|
|
|
|
|
_put_buf[pos + 1] = GB(_put_pos - (pos + 2), 0, 8);
|
|
|
|
|
_translated = cmdp != ls->english;
|
|
|
|
|
|
|
|
|
|
if (casep != NULL) {
|
|
|
|
|
const Case *c;
|
|
|
|
|
uint num;
|
|
|
|
|
|
|
|
|
|
/* Need to output a case-switch.
|
|
|
|
|
* It has this format
|
|
|
|
|
* <0x9E> <NUM CASES> <CASE1> <LEN1> <STRING1> <CASE2> <LEN2> <STRING2> <CASE3> <LEN3> <STRING3> <STRINGDEFAULT>
|
|
|
|
|
* Each LEN is printed using 2 bytes in big endian order. */
|
|
|
|
|
PutUtf8(SCC_SWITCH_CASE);
|
|
|
|
|
/* Count the number of cases */
|
|
|
|
|
for (num = 0, c = casep; c; c = c->next) num++;
|
|
|
|
|
PutByte(num);
|
|
|
|
|
|
|
|
|
|
/* Write each case */
|
|
|
|
|
for (c = casep; c != NULL; c = c->next) {
|
|
|
|
|
uint pos;
|
|
|
|
|
|
|
|
|
|
PutByte(c->caseidx);
|
|
|
|
|
/* Make some space for the 16-bit length */
|
|
|
|
|
pos = _put_pos;
|
|
|
|
|
PutByte(0);
|
|
|
|
|
PutByte(0);
|
|
|
|
|
/* Write string */
|
|
|
|
|
PutCommandString(c->string);
|
|
|
|
|
PutByte(0); // terminate with a zero
|
|
|
|
|
/* Fill in the length */
|
|
|
|
|
_put_buf[pos + 0] = GB(_put_pos - (pos + 2), 8, 8);
|
|
|
|
|
_put_buf[pos + 1] = GB(_put_pos - (pos + 2), 0, 8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cmdp != NULL) PutCommandString(cmdp);
|
|
|
|
|
if (cmdp != NULL) PutCommandString(cmdp);
|
|
|
|
|
|
|
|
|
|
WriteLength(_output_file, _put_pos);
|
|
|
|
|
fwrite(_put_buf, 1, _put_pos, _output_file);
|
|
|
|
|
_put_pos = 0;
|
|
|
|
|
this->WriteLength(_put_pos);
|
|
|
|
|
this->Write(_put_buf, _put_pos);
|
|
|
|
|
_put_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fputc(0, _output_file);
|
|
|
|
|
fclose(_output_file);
|
|
|
|
|
/** Class for writing a language to disk. */
|
|
|
|
|
struct LanguageFileWriter : LanguageWriter, FileWriter {
|
|
|
|
|
/**
|
|
|
|
|
* Open a file to write to.
|
|
|
|
|
* @param filename The file to open.
|
|
|
|
|
*/
|
|
|
|
|
LanguageFileWriter(const char *filename) : FileWriter(filename)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_output_file = NULL;
|
|
|
|
|
_output_filename = NULL;
|
|
|
|
|
}
|
|
|
|
|
void WriteHeader(const LanguagePackHeader *header)
|
|
|
|
|
{
|
|
|
|
|
this->Write((const byte *)header, sizeof(*header));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Finalise()
|
|
|
|
|
{
|
|
|
|
|
fputc(0, this->fh);
|
|
|
|
|
this->FileWriter::Finalise();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Write(const byte *buffer, size_t length)
|
|
|
|
|
{
|
|
|
|
|
if (fwrite(buffer, sizeof(*buffer), length, this->fh) != length) {
|
|
|
|
|
error("Could not write to %s", this->filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Multi-OS mkdirectory function */
|
|
|
|
|
static inline void ottd_mkdir(const char *directory)
|
|
|
|
@ -1314,49 +1435,59 @@ int CDECL main(int argc, char *argv[])
|
|
|
|
|
|
|
|
|
|
if (dest_dir == NULL) dest_dir = src_dir; // if dest_dir is not specified, it equals src_dir
|
|
|
|
|
|
|
|
|
|
/* strgen has two modes of operation. If no (free) arguments are passed
|
|
|
|
|
* strgen generates strings.h to the destination directory. If it is supplied
|
|
|
|
|
* with a (free) parameter the program will translate that language to destination
|
|
|
|
|
* directory. As input english.txt is parsed from the source directory */
|
|
|
|
|
if (mgo.numleft == 0) {
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
|
|
|
|
|
|
|
|
|
|
/* parse master file */
|
|
|
|
|
ParseFile(pathbuf, true);
|
|
|
|
|
MakeHashOfStrings();
|
|
|
|
|
if (_errors != 0) return 1;
|
|
|
|
|
|
|
|
|
|
/* write strings.h */
|
|
|
|
|
ottd_mkdir(dest_dir);
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), dest_dir, "strings.h");
|
|
|
|
|
WriteStringsH(pathbuf);
|
|
|
|
|
} else if (mgo.numleft == 1) {
|
|
|
|
|
char *r;
|
|
|
|
|
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
|
|
|
|
|
|
|
|
|
|
/* parse master file and check if target file is correct */
|
|
|
|
|
ParseFile(pathbuf, true);
|
|
|
|
|
MakeHashOfStrings();
|
|
|
|
|
ParseFile(replace_pathsep(mgo.argv[0]), false); // target file
|
|
|
|
|
if (_errors != 0) return 1;
|
|
|
|
|
|
|
|
|
|
/* get the targetfile, strip any directories and append to destination path */
|
|
|
|
|
r = strrchr(mgo.argv[0], PATHSEPCHAR);
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), dest_dir, (r != NULL) ? &r[1] : mgo.argv[0]);
|
|
|
|
|
|
|
|
|
|
/* rename the .txt (input-extension) to .lng */
|
|
|
|
|
r = strrchr(pathbuf, '.');
|
|
|
|
|
if (r == NULL || strcmp(r, ".txt") != 0) r = strchr(pathbuf, '\0');
|
|
|
|
|
ttd_strlcpy(r, ".lng", (size_t)(r - pathbuf));
|
|
|
|
|
WriteLangfile(pathbuf);
|
|
|
|
|
|
|
|
|
|
/* if showing warnings, print a summary of the language */
|
|
|
|
|
if ((_show_todo & 2) != 0) {
|
|
|
|
|
fprintf(stdout, "%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
|
|
|
|
|
try {
|
|
|
|
|
/* strgen has two modes of operation. If no (free) arguments are passed
|
|
|
|
|
* strgen generates strings.h to the destination directory. If it is supplied
|
|
|
|
|
* with a (free) parameter the program will translate that language to destination
|
|
|
|
|
* directory. As input english.txt is parsed from the source directory */
|
|
|
|
|
if (mgo.numleft == 0) {
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
|
|
|
|
|
|
|
|
|
|
/* parse master file */
|
|
|
|
|
ParseFile(pathbuf, true);
|
|
|
|
|
MakeHashOfStrings();
|
|
|
|
|
if (_errors != 0) return 1;
|
|
|
|
|
|
|
|
|
|
/* write strings.h */
|
|
|
|
|
ottd_mkdir(dest_dir);
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), dest_dir, "strings.h");
|
|
|
|
|
|
|
|
|
|
HeaderFileWriter writer(pathbuf);
|
|
|
|
|
writer.WriteHeader();
|
|
|
|
|
writer.Finalise();
|
|
|
|
|
} else if (mgo.numleft == 1) {
|
|
|
|
|
char *r;
|
|
|
|
|
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), src_dir, "english.txt");
|
|
|
|
|
|
|
|
|
|
/* parse master file and check if target file is correct */
|
|
|
|
|
ParseFile(pathbuf, true);
|
|
|
|
|
MakeHashOfStrings();
|
|
|
|
|
ParseFile(replace_pathsep(mgo.argv[0]), false); // target file
|
|
|
|
|
if (_errors != 0) return 1;
|
|
|
|
|
|
|
|
|
|
/* get the targetfile, strip any directories and append to destination path */
|
|
|
|
|
r = strrchr(mgo.argv[0], PATHSEPCHAR);
|
|
|
|
|
mkpath(pathbuf, lengthof(pathbuf), dest_dir, (r != NULL) ? &r[1] : mgo.argv[0]);
|
|
|
|
|
|
|
|
|
|
/* rename the .txt (input-extension) to .lng */
|
|
|
|
|
r = strrchr(pathbuf, '.');
|
|
|
|
|
if (r == NULL || strcmp(r, ".txt") != 0) r = strchr(pathbuf, '\0');
|
|
|
|
|
ttd_strlcpy(r, ".lng", (size_t)(r - pathbuf));
|
|
|
|
|
|
|
|
|
|
LanguageFileWriter writer(pathbuf);
|
|
|
|
|
writer.WriteLang();
|
|
|
|
|
writer.Finalise();
|
|
|
|
|
|
|
|
|
|
/* if showing warnings, print a summary of the language */
|
|
|
|
|
if ((_show_todo & 2) != 0) {
|
|
|
|
|
fprintf(stdout, "%d warnings and %d errors for %s\n", _warnings, _errors, pathbuf);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Invalid arguments\n");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Invalid arguments\n");
|
|
|
|
|
} catch (...) {
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|