1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17// Loads StringTbl* and replaces $..$-strings by localized versions
18
19#include "C4Constants.h"
20#include "C4LangStringTable.h"
21#include "C4Log.h"
22
23#include <vector>
24
25struct C4StringTableEntry
26{
27 const char *pName, *pEntry;
28
29 C4StringTableEntry(const char *pName, const char *pEntry)
30 : pName(pName), pEntry(pEntry) {}
31};
32
33void C4LangStringTable::ReplaceStrings(const StdStrBuf &rBuf, StdStrBuf &rTarget, const char *szParentFilePath)
34{
35 if (!rBuf.getLength())
36 {
37 return;
38 }
39 // grab char ptr from buf
40 const char *Data = rBuf.getData();
41
42 // string table
43 std::vector<C4StringTableEntry> Entries;
44
45 // read string table
46 char *pStrTblBuf = nullptr;
47 if (GetData())
48 {
49 // copy data
50 pStrTblBuf = new char[GetDataSize() + 1];
51 SCopy(szSource: GetData(), sTarget: pStrTblBuf, iMaxL: GetDataSize());
52 // find entries
53 const char *pLine = pStrTblBuf;
54 bool found_eq = false;
55 for (char *pPos = pStrTblBuf; *pPos; pPos++)
56 if (*pPos == '\n' || *pPos == '\r')
57 {
58 found_eq = false;
59 *pPos = '\0'; pLine = pPos + 1;
60 }
61 else if (*pPos == '=' && !found_eq)
62 {
63 *pPos = '\0';
64 // We found an '=' sign, so parse everything to end of line from now on, ignoring more '=' signs. Bug #2327.
65 found_eq = true;
66 // add entry
67 Entries.push_back(x: C4StringTableEntry(pLine, pPos + 1));
68 }
69 }
70
71 // Find Replace Positions
72 auto iScriptLen = SLen(sptr: Data);
73 struct RP { const char *Pos, *String; unsigned int Len; RP *Next; } *pRPList = nullptr, *pRPListEnd = nullptr;
74 for (const char *pPos = SSearch(szString: Data, szIndex: "$"); pPos; pPos = SSearch(szString: pPos, szIndex: "$"))
75 {
76 // Get name
77 char szStringName[C4MaxName + 1];
78 SCopyUntil(szSource: pPos, sTarget: szStringName, cUntil: '$', iMaxL: C4MaxName); pPos += SLen(sptr: szStringName) + 1;
79 if (*(pPos - 1) != '$') continue;
80 // valid?
81 const char *pPos2 = szStringName;
82 while (*pPos2)
83 if (!IsIdentifier(cChar: *(pPos2++)))
84 break;
85 if (*pPos2) continue;
86 // check termination
87 // search in string table
88 const char *pStrTblEntry = nullptr;
89 for (unsigned int i = 0; i < Entries.size(); i++)
90 if (SEqual(szStr1: szStringName, szStr2: Entries[i].pName))
91 {
92 pStrTblEntry = Entries[i].pEntry; break;
93 }
94 // found?
95 if (!pStrTblEntry)
96 {
97 LogNTr(level: spdlog::level::warn, fmt: "{}: string table entry not found: \"{}\"", args: FilePath[0] ? FilePath : (szParentFilePath ? szParentFilePath : "Unknown"), args: +szStringName);
98 continue;
99 }
100 // add new replace-position entry
101 RP *pnRP = new RP;
102 pnRP->Pos = pPos - SLen(sptr: szStringName) - 2;
103 pnRP->String = pStrTblEntry;
104 pnRP->Len = SLen(sptr: szStringName) + 2;
105 pnRP->Next = nullptr;
106 pRPListEnd = (pRPListEnd ? pRPListEnd->Next : pRPList) = pnRP;
107 // calculate new script length
108 iScriptLen += SLen(sptr: pStrTblEntry) - pnRP->Len;
109 }
110 // Alloc new Buffer
111 char *pNewBuf;
112 StdStrBuf sNewBuf;
113 sNewBuf.SetLength(iScriptLen);
114 pNewBuf = sNewBuf.getMData();
115 // Copy data
116 const char *pRPos = Data; char *pWPos = pNewBuf;
117 for (RP *pRPPos = pRPList; pRPPos; pRPPos = pRPPos->Next)
118 {
119 // copy preceding string data
120 SCopy(szSource: pRPos, sTarget: pWPos, iMaxL: pRPPos->Pos - pRPos);
121 pWPos += pRPPos->Pos - pRPos;
122 // copy string
123 SCopyUntil(szSource: pRPPos->String, sTarget: pWPos, cUntil: '\n');
124 SReplaceChar(str: pWPos, fc: '\r', tc: '\0');
125 // advance
126 pRPos = pRPPos->Pos + pRPPos->Len;
127 pWPos += SLen(sptr: pWPos);
128 }
129 SCopy(szSource: pRPos, sTarget: pWPos);
130
131 while (pRPList)
132 {
133 RP *pRP = pRPList;
134 pRPList = pRP->Next;
135 delete pRP;
136 }
137
138 // free buffer
139 delete[] pStrTblBuf;
140
141 // assign this buf
142 rTarget.Clear();
143 rTarget.Take(Buf2&: sNewBuf);
144}
145
146void C4LangStringTable::ReplaceStrings(StdStrBuf &rBuf)
147{
148 ReplaceStrings(rBuf, rTarget&: rBuf, szParentFilePath: nullptr);
149}
150