libgui 3.00
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiTextTranslator.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2009-2026
4 * GuiTextTranslator.cpp
5 ***************************************************************************/
6#include "GuiTextTranslator.h"
7
8#include <cstdlib>
9#include <cstring>
10#include <cstdio>
11
12#define HASHWORDBITS 32
13
14GuiTextTranslator::~GuiTextTranslator() {
15 clear();
16 magic = 0; // a lookup after this point is a lifetime bug: it will stop in checkIntegrity()
17}
18
19/* Never returns. __builtin_trap() raises a Program exception, and its crash screen shows the
20 * registers as they are when it fires, because this function does nothing before trapping:
21 * r3 = the translator r4 = the bad pointer (the list head, or some node's next)
22 * r5 = the node that held r4 (0 when r4 is the head itself)
23 * r6 = the translator's magic as read (0x47545254 when the object itself is intact)
24 * With an intact magic the object survived and only the list was damaged; a wrong magic means
25 * the translator itself was overwritten. Pair this with the build's ELF (crash-lookup.sh). */
26#if defined(__GNUC__) && (__GNUC__ >= 8)
27#define GUI_OPAQUE_CALL __attribute__((noipa, cold)) /* no inlining, cloning or argument pruning: the registers stay as passed */
28#else
29#define GUI_OPAQUE_CALL __attribute__((noinline, noclone, cold))
30#endif
31
32[[noreturn]] GUI_OPAQUE_CALL
33void GuiTextTranslator::fatalCorruption(const GuiTextTranslator*, const void*, const void*, uint32_t) {
34 __builtin_trap();
35}
36
37void GuiTextTranslator::checkIntegrity(const MSG* node, const MSG* prev) const {
38 if (magic != MAGIC)
39 fatalCorruption(this, node, prev, magic);
40
41 // every MSG comes from operator new, so a node pointer that is not aligned for MSG is not one
42 if (reinterpret_cast<uintptr_t>(node) & (alignof(MSG) - 1))
43 fatalCorruption(this, node, prev, magic);
44}
45
46/* Defines the so-called `hashpjw' function by P.J. Weinberger */
47uint32_t GuiTextTranslator::hashString(const char *str_param) {
48 uint32_t hval = 0, g;
49 const char *str = str_param;
50
51 while (*str != '\0') {
52 hval <<= 4;
53 hval += (uint8_t) * str++;
54 g = hval & ((uint32_t) 0xf << (HASHWORDBITS - 4));
55 if (g != 0) {
56 hval ^= g >> (HASHWORDBITS - 8);
57 hval ^= g;
58 }
59 }
60 return hval;
61}
62
63/* Expand some escape sequences found in the argument string. */
64char* GuiTextTranslator::expandEscape(const char *str) {
65 char *retval, *rp;
66 const char *cp = str;
67
68 retval = (char*) malloc(strlen(str) + 1);
69 if (retval == nullptr)
70 return nullptr;
71
72 rp = retval;
73
74 while (cp[0] != '\0' && cp[0] != '\\')
75 *rp++ = *cp++;
76
77 if (cp[0] == '\0')
78 goto terminate;
79
80 do {
81 /* Here cp[0] == '\\'. */
82 switch (*++cp) {
83 case '\"':
84 *rp++ = '\"';
85 ++cp;
86 break;
87 case 'a':
88 *rp++ = '\a';
89 ++cp;
90 break;
91 case 'b':
92 *rp++ = '\b';
93 ++cp;
94 break;
95 case 'f':
96 *rp++ = '\f';
97 ++cp;
98 break;
99 case 'n':
100 *rp++ = '\n';
101 ++cp;
102 break;
103 case 'r':
104 *rp++ = '\r';
105 ++cp;
106 break;
107 case 't':
108 *rp++ = '\t';
109 ++cp;
110 break;
111 case 'v':
112 *rp++ = '\v';
113 ++cp;
114 break;
115 case '\\':
116 *rp = '\\';
117 ++cp;
118 break;
119 case '0':
120 case '1':
121 case '2':
122 case '3':
123 case '4':
124 case '5':
125 case '6':
126 case '7': {
127 int ch = *cp++ - '0';
128 if (*cp >= '0' && *cp <= '7') {
129 ch *= 8;
130 ch += *cp++ - '0';
131
132 if (*cp >= '0' && *cp <= '7') {
133 ch *= 8;
134 ch += *cp++ - '0';
135 }
136 }
137 *rp = ch;
138 }
139 break;
140 default:
141 *rp = '\\';
142 break;
143 }
144
145 while (cp[0] != '\0' && cp[0] != '\\')
146 *rp++ = *cp++;
147
148 } while (cp[0] != '\0');
149
150 terminate: *rp = '\0';
151 return retval;
152}
153
154GuiTextTranslator::MSG* GuiTextTranslator::findMessage(uint32_t id) const {
155 checkIntegrity(head, nullptr);
156
157 const MSG *prev = nullptr;
158 for (MSG *msg = head; msg; prev = msg, msg = msg->next) {
159 checkIntegrity(msg, prev);
160 if (msg->id == id)
161 return msg;
162 }
163 return nullptr;
164}
165
166GuiTextTranslator::MSG* GuiTextTranslator::setMessage(const char *msgid, const char *msgstr) {
167 uint32_t id = hashString(msgid);
168 MSG *msg = findMessage(id);
169
170 if (!msg) {
171 msg = new MSG;
172 msg->id = id;
173 msg->msgstr = nullptr;
174 msg->next = head;
175 head = msg;
176 }
177
178 if (msg) {
179 if (msg->msgstr)
180 free(msg->msgstr);
181
182 if (msgstr)
183 msg->msgstr = expandEscape(msgstr);
184 }
185
186 return msg;
187}
188
190 checkIntegrity(head, nullptr);
191
192 while (head) {
193 checkIntegrity(head, nullptr);
194 MSG *nextMsg = head->next;
195 if (head->msgstr)
196 free(head->msgstr);
197 delete head;
198 head = nextMsg;
199 }
200}
201
202const char* GuiTextTranslator::memFGets(char *dst, int maxlen, const char *src, const char *eof) const {
203 if (!src || !dst || maxlen <= 0 || src >= eof)
204 return nullptr;
205
206 int len = 0;
207 while (src + len < eof && src[len] != '\n' && len < maxlen - 1) {
208 dst[len] = src[len];
209 len++;
210 }
211 dst[len] = '\0';
212
213 // Advance pointer past the newline if that's what stopped us
214 if (src + len < eof && src[len] == '\n') {
215 return src + len + 1;
216 }
217
218 return src + len;
219}
220
221bool GuiTextTranslator::loadLanguage(const uint8_t *buffer, size_t size) {
222 clear();
223
224 if (!buffer || size == 0)
225 return false;
226
227 char line[1024];
228 char *lastID = nullptr;
229 const char *ptr = reinterpret_cast<const char*>(buffer);
230 const char *eof = ptr + size;
231
232 while (ptr && ptr < eof) {
233 const char *nextPtr = memFGets(line, sizeof(line), ptr, eof);
234
235 if (!nextPtr && ptr == nextPtr)
236 break;
237
238 ptr = nextPtr;
239
240 // Lines starting with # are comments
241 if (line[0] == '#' || line[0] == '\0')
242 continue;
243
244 if (strncmp(line, "msgid \"", 7) == 0) {
245 if (lastID) {
246 free(lastID);
247 lastID = nullptr;
248 }
249
250 char *msgid = &line[7];
251 char *end = strrchr(msgid, '"');
252 if (end && (end - msgid) >= 0) {
253 *end = '\0';
254 lastID = strdup(msgid);
255 }
256 } else if (strncmp(line, "msgstr \"", 8) == 0) {
257 if (!lastID)
258 continue;
259
260 char *msgstr = &line[8];
261 char *end = strrchr(msgstr, '"');
262 if (end && (end - msgstr) >= 0) {
263 *end = '\0';
264 setMessage(lastID, msgstr);
265 }
266
267 free(lastID);
268 lastID = nullptr;
269 }
270 }
271
272 // Clean up if the file ended cleanly but we had an orphaned ID buffered
273 if (lastID)
274 free(lastID);
275
276 return true;
277}
278
279const char* GuiTextTranslator::getText(const char *msgid) const {
280 if (!msgid)
281 return nullptr;
282
283 MSG *msg = findMessage(hashString(msgid));
284
285 if (msg && msg->msgstr && msg->msgstr[0] != '\0') {
286 return msg->msgstr;
287 }
288
289 return msgid;
290}
Hashed msgid -> UTF-8 string table loaded from a single binary .lang blob (see loadLanguage()).
const char * getText(const char *msgid) const
Retrieves the translated UTF-8 string for a given ASCII message ID.
bool loadLanguage(const uint8_t *buffer, size_t size)
Loads and parses a translation file from a memory buffer.
void clear()
Clears the current translation dictionary and frees memory.