+#define PO_HASHSIZE 16384
+typedef struct po_string_s
+{
+ char *key, *value;
+ struct po_string_s *nextonhashchain;
+}
+po_string_t;
+typedef struct po_s
+{
+ po_string_t *hashtable[PO_HASHSIZE];
+}
+po_t;
+void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize)
+{
+ for(;;)
+ {
+ switch(*in)
+ {
+ case 0:
+ *out++ = 0;
+ return;
+ case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break;
+ case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break;
+ case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break;
+ case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break;
+ case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break;
+ case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break;
+ case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break;
+ default:
+ if(*in >= 0 && *in <= 0x1F)
+ {
+ if(outsize >= 4)
+ {
+ *out++ = '\\';
+ *out++ = '0' + ((*in & 0700) >> 6);
+ *out++ = '0' + ((*in & 0070) >> 3);
+ *out++ = '0' + ((*in & 0007));
+ outsize -= 4;
+ }
+ }
+ else
+ {
+ if(outsize >= 1)
+ {
+ *out++ = *in;
+ outsize -= 1;
+ }
+ }
+ break;
+ }
+ ++in;
+ }
+}
+void PRVM_PO_ParseString(char *out, const char *in, size_t outsize)
+{
+ for(;;)
+ {
+ switch(*in)
+ {
+ case 0:
+ *out++ = 0;
+ return;
+ case '\\':
+ ++in;
+ switch(*in)
+ {
+ case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break;
+ case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break;
+ case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break;
+ case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break;
+ case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break;
+ case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break;
+ case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break;
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
+ if(outsize > 0)
+ *out = *in - '0';
+ ++in;
+ if(*in >= '0' && *in <= '7')
+ {
+ if(outsize > 0)
+ *out = (*out << 3) | (*in - '0');
+ ++in;
+ }
+ if(*in >= '0' && *in <= '7')
+ {
+ if(outsize > 0)
+ *out = (*out << 3) | (*in - '0');
+ ++in;
+ }
+ --in;
+ if(outsize > 0)
+ {
+ ++out;
+ --outsize;
+ }
+ break;
+ default:
+ if(outsize > 0) { *out++ = *in; --outsize; }
+ break;
+ }
+ break;
+ default:
+ if(outsize > 0)
+ {
+ *out++ = *in;
+ --outsize;
+ }
+ break;
+ }
+ ++in;
+ }
+}
+po_t *PRVM_PO_Load(const char *filename, mempool_t *pool)
+{
+ po_t *po;
+ const char *p, *q;
+ int mode;
+ char inbuf[MAX_INPUTLINE];
+ char decodedbuf[MAX_INPUTLINE];
+ size_t decodedpos;
+ int hashindex;
+ po_string_t thisstr;
+ const char *buf = (const char *) FS_LoadFile(filename, pool, true, NULL);
+
+ if(!buf)
+ return NULL;
+
+ memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning
+
+ po = (po_t *)Mem_Alloc(pool, sizeof(*po));
+ memset(po, 0, sizeof(*po));
+
+ p = buf;
+ while(*p)
+ {
+ if(*p == '#')
+ {
+ // skip to newline
+ p = strchr(p, '\n');
+ if(!p)
+ break;
+ ++p;
+ continue;
+ }
+ if(*p == '\r' || *p == '\n')
+ {
+ ++p;
+ continue;
+ }
+ if(!strncmp(p, "msgid \"", 7))
+ {
+ mode = 0;
+ p += 6;
+ }
+ else if(!strncmp(p, "msgstr \"", 8))
+ {
+ mode = 1;
+ p += 7;
+ }
+ else
+ {
+ p = strchr(p, '\n');
+ if(!p)
+ break;
+ ++p;
+ continue;
+ }
+ decodedpos = 0;
+ while(*p == '"')
+ {
+ ++p;
+ q = strchr(p, '\n');
+ if(!q)
+ break;
+ if(*(q-1) == '\r')
+ --q;
+ if(*(q-1) != '"')
+ break;
+ if((size_t)(q - p) >= (size_t) sizeof(inbuf))
+ break;
+ strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL
+ PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos);
+ decodedpos += strlen(decodedbuf + decodedpos);
+ if(*q == '\r')
+ ++q;
+ if(*q == '\n')
+ ++q;
+ p = q;
+ }
+ if(mode == 0)
+ {
+ if(thisstr.key)
+ Mem_Free(thisstr.key);
+ thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1);
+ memcpy(thisstr.key, decodedbuf, decodedpos + 1);
+ }
+ else if(decodedpos > 0 && thisstr.key) // skip empty translation results
+ {
+ thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1);
+ memcpy(thisstr.value, decodedbuf, decodedpos + 1);
+ hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE;
+ thisstr.nextonhashchain = po->hashtable[hashindex];
+ po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr));
+ memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr));
+ memset(&thisstr, 0, sizeof(thisstr));
+ }
+ }
+
+ Mem_Free((char *) buf);
+ return po;
+}
+const char *PRVM_PO_Lookup(po_t *po, const char *str)
+{
+ int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE;
+ po_string_t *p = po->hashtable[hashindex];
+ while(p)
+ {
+ if(!strcmp(str, p->key))
+ return p->value;
+ p = p->nextonhashchain;
+ }
+ return NULL;
+}
+void PRVM_PO_Destroy(po_t *po)
+{
+ int i;
+ for(i = 0; i < PO_HASHSIZE; ++i)
+ {
+ po_string_t *p = po->hashtable[i];
+ while(p)
+ {
+ po_string_t *q = p;
+ p = p->nextonhashchain;
+ Mem_Free(q->key);
+ Mem_Free(q->value);
+ Mem_Free(q);
+ }
+ }
+ Mem_Free(po);
+}
+