+ num_bytes += strlen(chunk_name);
+
+ if (chunk_size >= 0)
+ {
+ // write chunk size
+ if (file != NULL)
+ putFile32BitInteger(file, chunk_size, byte_order);
+
+ num_bytes += 4;
+ }
+
+ return num_bytes;
+}
+
+int getFileVersion(File *file)
+{
+ int version_super = getByteFromFile(file);
+ int version_major = getByteFromFile(file);
+ int version_minor = getByteFromFile(file);
+ int version_patch = getByteFromFile(file);
+
+ return VERSION_IDENT(version_super, version_major, version_minor,
+ version_patch);
+}
+
+int putFileVersion(FILE *file, int version)
+{
+ if (file != NULL)
+ {
+ int version_super = VERSION_SUPER(version);
+ int version_major = VERSION_MAJOR(version);
+ int version_minor = VERSION_MINOR(version);
+ int version_patch = VERSION_PATCH(version);
+
+ fputc(version_super, file);
+ fputc(version_major, file);
+ fputc(version_minor, file);
+ fputc(version_patch, file);
+ }
+
+ return 4;
+}
+
+void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes)
+{
+ int i;
+
+ for (i = 0; i < bytes && !checkEndOfFile(file); i++)
+ buffer[i] = getByteFromFile(file);
+}
+
+void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
+{
+ int i;
+
+ for (i = 0; i < bytes; i++)
+ fputc(buffer[i], file);
+}
+
+void ReadUnusedBytesFromFile(File *file, unsigned int bytes)
+{
+ while (bytes-- && !checkEndOfFile(file))
+ getByteFromFile(file);
+}
+
+void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
+{
+ while (bytes--)
+ fputc(0, file);
+}
+
+
+// ----------------------------------------------------------------------------
+// functions to convert between ISO-8859-1 and UTF-8
+// ----------------------------------------------------------------------------
+
+char *getUTF8FromLatin1(char *latin1)
+{
+ int max_utf8_size = 2 * strlen(latin1) + 1;
+ char *utf8 = checked_calloc(max_utf8_size);
+ unsigned char *src = (unsigned char *)latin1;
+ unsigned char *dst = (unsigned char *)utf8;
+
+ while (*src)
+ {
+ if (*src < 128) // pure 7-bit ASCII
+ {
+ *dst++ = *src;
+ }
+ else if (*src >= 160) // non-ASCII characters
+ {
+ *dst++ = 194 + (*src >= 192);
+ *dst++ = 128 + (*src & 63);
+ }
+ else // undefined in ISO-8859-1
+ {
+ *dst++ = '?';
+ }
+
+ src++;
+ }
+
+ // only use the smallest possible string buffer size
+ utf8 = checked_realloc(utf8, strlen(utf8) + 1);
+
+ return utf8;
+}
+
+char *getLatin1FromUTF8(char *utf8)
+{
+ int max_latin1_size = strlen(utf8) + 1;
+ char *latin1 = checked_calloc(max_latin1_size);
+ unsigned char *src = (unsigned char *)utf8;
+ unsigned char *dst = (unsigned char *)latin1;
+
+ while (*src)
+ {
+ if (*src < 128) // pure 7-bit ASCII
+ {
+ *dst++ = *src++;
+ }
+ else if (src[0] == 194 &&
+ src[1] >= 128 && src[1] < 192) // non-ASCII characters
+ {
+ *dst++ = src[1];
+ src += 2;
+ }
+ else if (src[0] == 195 &&
+ src[1] >= 128 && src[1] < 192) // non-ASCII characters
+ {
+ *dst++ = src[1] + 64;
+ src += 2;
+ }
+
+ // all other UTF-8 characters are undefined in ISO-8859-1
+
+ else if (src[0] >= 192 && src[0] < 224 &&
+ src[1] >= 128 && src[1] < 192)
+ {
+ *dst++ = '?';
+ src += 2;
+ }
+ else if (src[0] >= 224 && src[0] < 240 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192)
+ {
+ *dst++ = '?';
+ src += 3;
+ }
+ else if (src[0] >= 240 && src[0] < 248 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192)
+ {
+ *dst++ = '?';
+ src += 4;
+ }
+ else if (src[0] >= 248 && src[0] < 252 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192)
+ {
+ *dst++ = '?';
+ src += 5;
+ }
+ else if (src[0] >= 252 && src[0] < 254 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192 &&
+ src[5] >= 128 && src[5] < 192)
+ {
+ *dst++ = '?';
+ src += 6;
+ }
+ else
+ {
+ *dst++ = '?';
+ src++;
+ }
+ }
+
+ // only use the smallest possible string buffer size
+ latin1 = checked_realloc(latin1, strlen(latin1) + 1);
+
+ return latin1;
+}
+
+int getTextEncoding(char *text)
+{
+ unsigned char *src = (unsigned char *)text;
+ int encoding = TEXT_ENCODING_ASCII; // default: assume encoding is ASCII
+
+ while (*src)
+ {
+ if (*src >= 128)
+ encoding = TEXT_ENCODING_UTF_8; // non-ASCII character: assume UTF-8
+
+ if (*src < 128)
+ {
+ src++;
+ }
+ else if (src[0] >= 192 && src[0] < 224 &&
+ src[1] >= 128 && src[1] < 192)
+ {
+ src += 2;
+ }
+ else if (src[0] >= 224 && src[0] < 240 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192)
+ {
+ src += 3;
+ }
+ else if (src[0] >= 240 && src[0] < 248 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192)
+ {
+ src += 4;
+ }
+ else if (src[0] >= 248 && src[0] < 252 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192)
+ {
+ src += 5;
+ }
+ else if (src[0] >= 252 && src[0] < 254 &&
+ src[1] >= 128 && src[1] < 192 &&
+ src[2] >= 128 && src[2] < 192 &&
+ src[3] >= 128 && src[3] < 192 &&
+ src[4] >= 128 && src[4] < 192 &&
+ src[5] >= 128 && src[5] < 192)
+ {
+ src += 6;
+ }
+ else
+ {
+ return TEXT_ENCODING_UNKNOWN; // non-UTF-8 character: unknown encoding
+ }
+ }
+
+ return encoding;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions for JSON handling
+// ----------------------------------------------------------------------------
+
+char *getEscapedJSON(char *s)
+{
+ int max_json_size = 2 * strlen(s) + 1;
+ char *json = checked_calloc(max_json_size);
+ unsigned char *src = (unsigned char *)s;
+ unsigned char *dst = (unsigned char *)json;
+ char *escaped[256] =
+ {
+ ['\b'] = "\\b",
+ ['\f'] = "\\f",
+ ['\n'] = "\\n",
+ ['\r'] = "\\r",
+ ['\t'] = "\\t",
+ ['\"'] = "\\\"",
+ ['\\'] = "\\\\",
+ };
+
+ while (*src)
+ {
+ if (escaped[*src] != NULL)
+ {
+ char *esc = escaped[*src++];
+
+ while (*esc)
+ *dst++ = *esc++;
+ }
+ else
+ {
+ *dst++ = *src++;
+ }
+ }
+
+ // only use the smallest possible string buffer size
+ json = checked_realloc(json, strlen(json) + 1);
+
+ return json;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions to translate key identifiers between different format
+// ----------------------------------------------------------------------------
+
+#define TRANSLATE_KEYSYM_TO_KEYNAME 0
+#define TRANSLATE_KEYSYM_TO_X11KEYNAME 1
+#define TRANSLATE_KEYNAME_TO_KEYSYM 2
+#define TRANSLATE_X11KEYNAME_TO_KEYSYM 3
+
+static void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
+{
+ static struct
+ {
+ Key key;
+ char *x11name;
+ char *name;
+ } translate_key[] =
+ {
+ // return and escape keys
+ { KSYM_Return, "XK_Return", "return" },
+ { KSYM_Escape, "XK_Escape", "escape" },
+
+ // normal cursor keys
+ { KSYM_Left, "XK_Left", "cursor left" },
+ { KSYM_Right, "XK_Right", "cursor right" },
+ { KSYM_Up, "XK_Up", "cursor up" },
+ { KSYM_Down, "XK_Down", "cursor down" },
+
+ // keypad cursor keys
+#ifdef KSYM_KP_Left
+ { KSYM_KP_Left, "XK_KP_Left", "keypad left" },
+ { KSYM_KP_Right, "XK_KP_Right", "keypad right" },
+ { KSYM_KP_Up, "XK_KP_Up", "keypad up" },
+ { KSYM_KP_Down, "XK_KP_Down", "keypad down" },
+#endif
+
+ // other keypad keys
+#ifdef KSYM_KP_Enter
+ { KSYM_KP_Enter, "XK_KP_Enter", "keypad enter" },
+ { KSYM_KP_Add, "XK_KP_Add", "keypad +" },
+ { KSYM_KP_Subtract, "XK_KP_Subtract", "keypad -" },
+ { KSYM_KP_Multiply, "XK_KP_Multiply", "keypad mltply" },
+ { KSYM_KP_Divide, "XK_KP_Divide", "keypad /" },
+ { KSYM_KP_Separator,"XK_KP_Separator", "keypad ," },
+#endif
+
+ // modifier keys
+ { KSYM_Shift_L, "XK_Shift_L", "left shift" },
+ { KSYM_Shift_R, "XK_Shift_R", "right shift" },
+ { KSYM_Control_L, "XK_Control_L", "left control" },
+ { KSYM_Control_R, "XK_Control_R", "right control" },
+ { KSYM_Meta_L, "XK_Meta_L", "left meta" },
+ { KSYM_Meta_R, "XK_Meta_R", "right meta" },
+ { KSYM_Alt_L, "XK_Alt_L", "left alt" },
+ { KSYM_Alt_R, "XK_Alt_R", "right alt" },
+ { KSYM_Mode_switch, "XK_Mode_switch", "mode switch" }, // Alt-R
+ { KSYM_Multi_key, "XK_Multi_key", "multi key" }, // Ctrl-R
+
+ // some special keys
+ { KSYM_BackSpace, "XK_BackSpace", "backspace" },
+ { KSYM_Delete, "XK_Delete", "delete" },
+ { KSYM_Insert, "XK_Insert", "insert" },
+ { KSYM_Tab, "XK_Tab", "tab" },
+ { KSYM_Home, "XK_Home", "home" },
+ { KSYM_End, "XK_End", "end" },
+ { KSYM_Page_Up, "XK_Page_Up", "page up" },
+ { KSYM_Page_Down, "XK_Page_Down", "page down" },
+
+ { KSYM_Select, "XK_Select", "select" },
+ { KSYM_Menu, "XK_Menu", "menu" }, // menu key
+ { KSYM_Back, "XK_Back", "back" }, // back key
+ { KSYM_PlayPause, "XK_PlayPause", "play/pause" },
+#if defined(PLATFORM_ANDROID)
+ { KSYM_Rewind, "XK_Rewind", "rewind" },
+ { KSYM_FastForward, "XK_FastForward", "fast forward" },
+#endif
+
+ // ASCII 0x20 to 0x40 keys (except numbers)
+ { KSYM_space, "XK_space", "space" },
+ { KSYM_exclam, "XK_exclam", "!" },
+ { KSYM_quotedbl, "XK_quotedbl", "\"" },
+ { KSYM_numbersign, "XK_numbersign", "#" },
+ { KSYM_dollar, "XK_dollar", "$" },
+ { KSYM_percent, "XK_percent", "%" },
+ { KSYM_ampersand, "XK_ampersand", "&" },
+ { KSYM_apostrophe, "XK_apostrophe", "'" },
+ { KSYM_parenleft, "XK_parenleft", "(" },
+ { KSYM_parenright, "XK_parenright", ")" },
+ { KSYM_asterisk, "XK_asterisk", "*" },
+ { KSYM_plus, "XK_plus", "+" },
+ { KSYM_comma, "XK_comma", "," },
+ { KSYM_minus, "XK_minus", "-" },
+ { KSYM_period, "XK_period", "." },
+ { KSYM_slash, "XK_slash", "/" },
+ { KSYM_colon, "XK_colon", ":" },
+ { KSYM_semicolon, "XK_semicolon", ";" },
+ { KSYM_less, "XK_less", "<" },
+ { KSYM_equal, "XK_equal", "=" },
+ { KSYM_greater, "XK_greater", ">" },
+ { KSYM_question, "XK_question", "?" },
+ { KSYM_at, "XK_at", "@" },
+
+ // more ASCII keys
+ { KSYM_bracketleft, "XK_bracketleft", "[" },
+ { KSYM_backslash, "XK_backslash", "\\" },
+ { KSYM_bracketright,"XK_bracketright", "]" },
+ { KSYM_asciicircum, "XK_asciicircum", "^" },
+ { KSYM_underscore, "XK_underscore", "_" },
+ { KSYM_grave, "XK_grave", "grave" },
+ { KSYM_quoteleft, "XK_quoteleft", "quote left" },
+ { KSYM_braceleft, "XK_braceleft", "brace left" },
+ { KSYM_bar, "XK_bar", "bar" },
+ { KSYM_braceright, "XK_braceright", "brace right" },
+ { KSYM_asciitilde, "XK_asciitilde", "~" },
+
+ // special (non-ASCII) keys
+ { KSYM_degree, "XK_degree", "degree" },
+ { KSYM_Adiaeresis, "XK_Adiaeresis", "A umlaut" },
+ { KSYM_Odiaeresis, "XK_Odiaeresis", "O umlaut" },
+ { KSYM_Udiaeresis, "XK_Udiaeresis", "U umlaut" },
+ { KSYM_adiaeresis, "XK_adiaeresis", "a umlaut" },
+ { KSYM_odiaeresis, "XK_odiaeresis", "o umlaut" },
+ { KSYM_udiaeresis, "XK_udiaeresis", "u umlaut" },
+ { KSYM_ssharp, "XK_ssharp", "sharp s" },
+
+ // special (non-ASCII) keys (UTF-8, for reverse mapping only)
+ { KSYM_degree, "XK_degree", "\xc2\xb0" },
+ { KSYM_Adiaeresis, "XK_Adiaeresis", "\xc3\x84" },
+ { KSYM_Odiaeresis, "XK_Odiaeresis", "\xc3\x96" },
+ { KSYM_Udiaeresis, "XK_Udiaeresis", "\xc3\x9c" },
+ { KSYM_adiaeresis, "XK_adiaeresis", "\xc3\xa4" },
+ { KSYM_odiaeresis, "XK_odiaeresis", "\xc3\xb6" },
+ { KSYM_udiaeresis, "XK_udiaeresis", "\xc3\xbc" },
+ { KSYM_ssharp, "XK_ssharp", "\xc3\x9f" },
+
+ // other keys (for reverse mapping only)
+ { KSYM_space, "XK_space", " " },
+
+ // keypad keys are not in numerical order in SDL2
+ { KSYM_KP_0, "XK_KP_0", "keypad 0" },
+ { KSYM_KP_1, "XK_KP_1", "keypad 1" },
+ { KSYM_KP_2, "XK_KP_2", "keypad 2" },
+ { KSYM_KP_3, "XK_KP_3", "keypad 3" },
+ { KSYM_KP_4, "XK_KP_4", "keypad 4" },
+ { KSYM_KP_5, "XK_KP_5", "keypad 5" },
+ { KSYM_KP_6, "XK_KP_6", "keypad 6" },
+ { KSYM_KP_7, "XK_KP_7", "keypad 7" },
+ { KSYM_KP_8, "XK_KP_8", "keypad 8" },
+ { KSYM_KP_9, "XK_KP_9", "keypad 9" },
+
+ // end-of-array identifier
+ { 0, NULL, NULL }
+ };
+
+ int i;
+
+ if (mode == TRANSLATE_KEYSYM_TO_KEYNAME)
+ {
+ static char name_buffer[30];
+ Key key = *keysym;
+
+ if (key >= KSYM_A && key <= KSYM_Z)
+ sprintf(name_buffer, "%c", 'A' + (char)(key - KSYM_A));
+ else if (key >= KSYM_a && key <= KSYM_z)
+ sprintf(name_buffer, "%c", 'a' + (char)(key - KSYM_a));
+ else if (key >= KSYM_0 && key <= KSYM_9)
+ sprintf(name_buffer, "%c", '0' + (char)(key - KSYM_0));
+ else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
+ sprintf(name_buffer, "F%d", (int)(key - KSYM_FKEY_FIRST + 1));
+ else if (key == KSYM_UNDEFINED)
+ strcpy(name_buffer, "(undefined)");
+ else
+ {
+ i = 0;
+
+ do
+ {
+ if (key == translate_key[i].key)
+ {
+ strcpy(name_buffer, translate_key[i].name);
+ break;
+ }
+ }
+ while (translate_key[++i].name);
+
+ if (!translate_key[i].name)
+ strcpy(name_buffer, "(unknown)");
+ }
+
+ *name = name_buffer;
+ }
+ else if (mode == TRANSLATE_KEYSYM_TO_X11KEYNAME)
+ {
+ static char name_buffer[30];
+ Key key = *keysym;
+
+ if (key >= KSYM_A && key <= KSYM_Z)
+ sprintf(name_buffer, "XK_%c", 'A' + (char)(key - KSYM_A));
+ else if (key >= KSYM_a && key <= KSYM_z)
+ sprintf(name_buffer, "XK_%c", 'a' + (char)(key - KSYM_a));
+ else if (key >= KSYM_0 && key <= KSYM_9)
+ sprintf(name_buffer, "XK_%c", '0' + (char)(key - KSYM_0));
+ else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
+ sprintf(name_buffer, "XK_F%d", (int)(key - KSYM_FKEY_FIRST + 1));
+ else if (key == KSYM_UNDEFINED)
+ strcpy(name_buffer, "[undefined]");
+ else
+ {
+ i = 0;
+
+ do
+ {
+ if (key == translate_key[i].key)
+ {
+ strcpy(name_buffer, translate_key[i].x11name);
+ break;
+ }
+ }
+ while (translate_key[++i].x11name);
+
+ if (!translate_key[i].x11name)
+ sprintf(name_buffer, "0x%04x", (unsigned int)key);
+ }
+
+ *x11name = name_buffer;
+ }
+ else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM)
+ {
+ Key key = KSYM_UNDEFINED;
+ char *name_ptr = *name;
+
+ if (strlen(*name) == 1)
+ {
+ char c = name_ptr[0];
+
+ if (c >= 'A' && c <= 'Z')
+ key = KSYM_A + (Key)(c - 'A');
+ else if (c >= 'a' && c <= 'z')
+ key = KSYM_a + (Key)(c - 'a');
+ else if (c >= '0' && c <= '9')
+ key = KSYM_0 + (Key)(c - '0');
+ }
+
+ if (key == KSYM_UNDEFINED)
+ {
+ i = 0;
+
+ do
+ {
+ if (strEqual(translate_key[i].name, *name))
+ {
+ key = translate_key[i].key;
+ break;
+ }
+ }
+ while (translate_key[++i].x11name);
+ }
+
+ if (key == KSYM_UNDEFINED)
+ Warn("getKeyFromKeyName(): not completely implemented");
+
+ *keysym = key;
+ }
+ else if (mode == TRANSLATE_X11KEYNAME_TO_KEYSYM)
+ {
+ Key key = KSYM_UNDEFINED;
+ char *name_ptr = *x11name;
+
+ if (strPrefix(name_ptr, "XK_") && strlen(name_ptr) == 4)
+ {
+ char c = name_ptr[3];
+
+ if (c >= 'A' && c <= 'Z')
+ key = KSYM_A + (Key)(c - 'A');
+ else if (c >= 'a' && c <= 'z')
+ key = KSYM_a + (Key)(c - 'a');
+ else if (c >= '0' && c <= '9')
+ key = KSYM_0 + (Key)(c - '0');
+ }
+ else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
+ {
+ char c1 = name_ptr[4];
+ char c2 = name_ptr[5];
+ int d = 0;
+
+ if ((c1 >= '0' && c1 <= '9') &&
+ ((c2 >= '0' && c1 <= '9') || c2 == '\0'))
+ d = atoi(&name_ptr[4]);
+
+ if (d >= 1 && d <= KSYM_NUM_FKEYS)
+ key = KSYM_F1 + (Key)(d - 1);
+ }
+ else if (strPrefix(name_ptr, "XK_"))
+ {
+ i = 0;
+
+ do
+ {
+ if (strEqual(name_ptr, translate_key[i].x11name))
+ {
+ key = translate_key[i].key;
+ break;
+ }
+ }
+ while (translate_key[++i].x11name);
+ }
+ else if (strPrefix(name_ptr, "0x"))
+ {
+ unsigned int value = 0;
+
+ name_ptr += 2;
+
+ while (name_ptr)
+ {
+ char c = *name_ptr++;
+ int d = -1;
+
+ if (c >= '0' && c <= '9')
+ d = (int)(c - '0');
+ else if (c >= 'a' && c <= 'f')
+ d = (int)(c - 'a' + 10);
+ else if (c >= 'A' && c <= 'F')
+ d = (int)(c - 'A' + 10);
+
+ if (d == -1)
+ {
+ value = -1;
+ break;
+ }
+
+ value = value * 16 + d;
+ }
+
+ if (value != -1)
+ key = (Key)value;
+ }
+
+ *keysym = key;
+ }
+}
+
+char *getKeyNameFromKey(Key key)
+{
+ char *name;
+
+ translate_keyname(&key, NULL, &name, TRANSLATE_KEYSYM_TO_KEYNAME);
+ return name;
+}
+
+char *getX11KeyNameFromKey(Key key)
+{
+ char *x11name;