1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2019, 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/* string table: holds all strings used by script engine */
18
19#include <C4Include.h>
20#include <C4StringTable.h>
21
22#include <C4Group.h>
23#include <C4Components.h>
24#include <C4Aul.h>
25
26// *** C4String
27
28C4String::C4String(StdStrBuf &&strString, C4StringTable *pnTable)
29 : iRefCnt(0), Hold(false), iEnumID(-1), pTable(nullptr)
30{
31 // take string
32 Data.Take(Buf2&: strString);
33 // reg
34 Reg(pTable: pnTable);
35}
36
37C4String::C4String(const char *strString, C4StringTable *pnTable)
38 : iRefCnt(0), Hold(false), iEnumID(-1), pTable(nullptr)
39{
40 // copy string
41 Data = strString;
42 // reg
43 Reg(pTable: pnTable);
44}
45
46C4String::~C4String()
47{
48 // unreg
49 iRefCnt = 1;
50 if (pTable) UnReg();
51}
52
53void C4String::IncRef()
54{
55 ++iRefCnt;
56}
57
58void C4String::DecRef()
59{
60 --iRefCnt;
61
62 // delete if ref cnt is 0 and the Hold-Flag isn't set
63 if (iRefCnt <= 0 && !Hold)
64 delete this;
65}
66
67void C4String::Reg(C4StringTable *pnTable)
68{
69 if (pTable) UnReg();
70
71 // add string to tail of table
72 Prev = pnTable->Last;
73 Next = nullptr;
74
75 if (Prev)
76 Prev->Next = this;
77 else
78 pnTable->First = this;
79 pnTable->Last = this;
80
81 pTable = pnTable;
82}
83
84void C4String::UnReg()
85{
86 if (!pTable) return;
87
88 if (Next)
89 Next->Prev = Prev;
90 else
91 pTable->Last = Prev;
92 if (Prev)
93 Prev->Next = Next;
94 else
95 pTable->First = Next;
96
97 pTable = nullptr;
98
99 // delete hold flag if table is lost and check for delete
100 Hold = false;
101 if (iRefCnt <= 0)
102 delete this;
103}
104
105// *** C4StringTable
106
107C4StringTable::C4StringTable()
108 : First(nullptr), Last(nullptr) {}
109
110C4StringTable::~C4StringTable()
111{
112 // unreg all remaining strings
113 // (hold strings will delete themselves)
114 while (First) First->UnReg();
115}
116
117void C4StringTable::Clear()
118{
119 bool bContinue;
120 do
121 {
122 bContinue = false;
123 // find string to delete / unreg
124 for (C4String *pAct = First; pAct; pAct = pAct->Next)
125 if (pAct->Hold)
126 {
127 pAct->UnReg();
128 bContinue = true;
129 break;
130 }
131 } while (bContinue);
132}
133
134int C4StringTable::EnumStrings()
135{
136 int iCurrID = 0;
137 for (C4String *pAct = First; pAct; pAct = pAct->Next)
138 {
139 if (!pAct->Hold || pAct->iRefCnt)
140 {
141 C4String *same;
142 if ((same = FindSaveString(pString: pAct)) == pAct)
143 {
144 pAct->iEnumID = iCurrID++;
145 }
146 else
147 {
148 pAct->iEnumID = same->iEnumID;
149 }
150 }
151 else
152 {
153 pAct->iEnumID = -1;
154 }
155 }
156 return iCurrID;
157}
158
159C4String *C4StringTable::RegString(const char *strString)
160{
161 return new C4String(strString, this);
162}
163
164C4String *C4StringTable::FindString(const char *strString)
165{
166 for (C4String *pAct = First; pAct; pAct = pAct->Next)
167 if (SEqual(szStr1: pAct->Data.getData(), szStr2: strString))
168 return pAct;
169 return nullptr;
170}
171
172C4String *C4StringTable::FindString(C4String *pString)
173{
174 for (C4String *pAct = First; pAct; pAct = pAct->Next)
175 if (pAct == pString)
176 return pAct;
177 return nullptr;
178}
179
180C4String *C4StringTable::FindString(int iEnumID)
181{
182 for (C4String *pAct = First; pAct; pAct = pAct->Next)
183 if (pAct->iEnumID == iEnumID)
184 return pAct;
185 return nullptr;
186}
187
188C4String *C4StringTable::FindSaveString(C4String *pString)
189{
190 for (C4String *pAct = First; pAct; pAct = pAct->Next)
191 {
192 if (SEqual(szStr1: pAct->Data.getData(), szStr2: pString->Data.getData()) && (!pAct->Hold || pAct->iRefCnt))
193 {
194 return pAct;
195 }
196 }
197
198 return nullptr;
199}
200
201bool C4StringTable::Load(C4Group &ParentGroup)
202{
203 // read data
204 char *pData;
205 if (!ParentGroup.LoadEntry(C4CFN_Strings, lpbpBuf: &pData, ipSize: nullptr, iAppendZeros: 1))
206 return false;
207 // read all strings
208 char strBuf[C4AUL_MAX_String + 1];
209 for (int i = 0; SCopySegment(fstr: pData, segn: i, tstr: strBuf, sepa: 0x0A, C4AUL_MAX_String); i++)
210 {
211 SReplaceChar(str: strBuf, fc: 0x0D, tc: 0x00);
212 // add string to list
213 C4String *pnString;
214 if (!(pnString = FindString(strString: strBuf)))
215 pnString = RegString(strString: strBuf);
216 pnString->iEnumID = i;
217 }
218 // delete data
219 delete[] pData;
220 return true;
221}
222
223bool C4StringTable::Save(C4Group &ParentGroup)
224{
225 // no tbl entries?
226 if (!First) return true;
227
228 // calc needed space for string table
229 int iTableSize = 1;
230 C4String *pAct;
231 for (pAct = First; pAct; pAct = pAct->Next)
232 {
233 if (pAct->iEnumID > -1 && FindSaveString(pString: pAct) == pAct)
234 {
235 iTableSize += SLen(sptr: pAct->Data.getData()) + 2;
236 }
237 }
238
239 // no entries?
240 if (iTableSize <= 1) return true;
241
242 char *pData = new char[iTableSize], *pPos = pData;
243 *pData = 0;
244 for (pAct = First; pAct; pAct = pAct->Next)
245 {
246 if (pAct->iEnumID > -1 && FindSaveString(pString: pAct) == pAct)
247 {
248 SCopy(szSource: pAct->Data.getData(), sTarget: pPos);
249 if (strchr(s: pPos, c: 10) || strchr(s: pPos, c: 13))
250 {
251 // delete feeds
252 char *pCharPos = pPos;
253 while (pCharPos = strchr(s: pCharPos, c: 10)) memmove(dest: pCharPos, src: pCharPos + 1, n: SLen(sptr: pCharPos + 1) + 1);
254 // and replace breaks (by C4Script-"escapes")
255 SReplaceChar(str: pPos, fc: 13, tc: '|');
256 }
257 SAppendChar(cChar: 0xD, szStr: pPos);
258 SAppendChar(cChar: 0xA, szStr: pPos);
259 pPos += SLen(sptr: pPos);
260 }
261 }
262
263 // write in group
264 return !!ParentGroup.Add(C4CFN_Strings, pBuffer: pData, iSize: iTableSize - 1, fChild: false, fHoldBuffer: true);
265}
266