OpenClonk
C4Language.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk 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 /*
18  Language module
19  - handles external language packs
20  - provides info on selectable languages by scanning string tables
21  - loads and sets a language string table (ResStrTable) based on a specified language sequence
22 
23 */
24 
25 #include "C4Include.h"
26 #include "c4group/C4Language.h"
27 
28 #include "game/C4Application.h"
29 #include "c4group/C4Components.h"
30 
31 template<size_t iBufferSize>
32 static bool GetRelativePath(const char *strPath, const char *strRelativeTo, char(&strBuffer)[iBufferSize])
33 {
34  // Specified path is relative to base path
35  // Copy relative section
36  const char *szCpy;
37  SCopy(szCpy = GetRelativePathS(strPath, strRelativeTo), strBuffer, iBufferSize);
38  // return whether it was made relative
39  return szCpy != strPath;
40 }
41 
43 
45 {
46  Infos = nullptr;
47  PackGroupLocation[0] = 0;
48 }
49 
51 {
52  Clear();
53 }
54 
56 {
57  // Clear (to allow clean re-init)
58  Clear();
59 
60  // Make sure Language.ocg is unpacked (TODO: This won't work properly if Language.ocg is in system data path)
61  // Assume for now that Language.ocg is either at a writable location or unpacked already.
62  // TODO: Use all Language.c4gs that we find, and merge them.
63  // TODO: Use gettext instead?
64  StdStrBuf langPath;
65  C4Reloc::iterator iter;
66  for(iter = Reloc.begin(); iter != Reloc.end(); ++iter)
67  {
68  langPath.Copy(iter->strBuf + DirSep + C4CFN_Languages);
69  if(ItemExists(langPath.getData()))
70  {
71  if(DirectoryExists(langPath.getData()))
72  break;
73  if(C4Group_UnpackDirectory(langPath.getData()))
74  break;
75  }
76  }
77 
78  // Break if no language.ocg found
79  if(iter != Reloc.end())
80  {
81  // Look for available language packs in Language.ocg
82  C4Group *pPack;
83  char strPackFilename[_MAX_FNAME_LEN], strEntry[_MAX_FNAME_LEN];
84  if (PackDirectory.Open(langPath.getData()))
85  {
86  while (PackDirectory.FindNextEntry("*.ocg", strEntry))
87  {
88  sprintf(strPackFilename, "%s" DirSep "%s", C4CFN_Languages, strEntry);
89  pPack = new C4Group();
90  if (pPack->Open(strPackFilename))
91  {
92  Packs.RegisterGroup(*pPack, true, C4GSCnt_Language, false);
93  }
94  else
95  {
96  delete pPack;
97  }
98  }
99  }
100 
101  // Now create a pack group for each language pack (these pack groups are child groups
102  // that browse along each pack to access requested data)
103  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
104  PackGroups.RegisterGroup(*(new C4Group), true, C4GSPrio_Base, C4GSCnt_Language);
105  }
106 
107  // Load language infos by scanning string tables (the engine doesn't really need this at the moment)
108  InitInfos();
109 
110  // Done
111  return true;
112 }
113 
115 {
116  // Clear pack groups
117  PackGroups.Clear();
118  // Clear packs
119  Packs.Clear();
120  // Close pack directory
121  PackDirectory.Close();
122  // Clear infos
123  C4LanguageInfo* pNext;
124  while (Infos)
125  {
126  pNext = Infos->Next;
127  delete Infos;
128  Infos = pNext;
129  }
130  Infos = nullptr;
131 }
132 
134 {
135  return Packs.GetGroupCount();
136 }
137 
139 {
140  int iCount = 0;
141  for (C4LanguageInfo *pInfo = Infos; pInfo; pInfo = pInfo->Next)
142  iCount++;
143  return iCount;
144 }
145 
146 // Returns a set of groups at the specified relative path within all open language packs.
147 
149 {
150  // Build a group set containing the provided group and
151  // alternative groups for cross-loading from a language pack
152  char strRelativePath[_MAX_PATH_LEN];
153  char strTargetLocation[_MAX_PATH_LEN];
154  char strPackPath[_MAX_PATH_LEN];
155  char strPackGroupLocation[_MAX_PATH_LEN];
156  char strAdvance[_MAX_PATH_LEN];
157 
158  // Store wanted target location
159  SCopy(Config.AtRelativePath(hGroup.GetFullName().getData()), strRelativePath, _MAX_PATH);
160  SCopy(strRelativePath, strTargetLocation, _MAX_PATH);
161 
162  // Adjust location by scenario origin
164  {
165  const char *szScenarioRelativePath = GetRelativePathS(strRelativePath, Config.AtRelativePath(Game.ScenarioFilename));
166  if (szScenarioRelativePath != strRelativePath)
167  {
168  // this is a path within the scenario! Change to origin.
169  size_t iRestPathLen = SLen(szScenarioRelativePath);
170  if (Game.C4S.Head.Origin.getLength() + 1 + iRestPathLen <= _MAX_PATH)
171  {
172  SCopy(Game.C4S.Head.Origin.getData(), strTargetLocation);
173  if (iRestPathLen)
174  {
175  SAppendChar(DirectorySeparator, strTargetLocation);
176  SAppend(szScenarioRelativePath, strTargetLocation);
177  }
178  }
179  }
180  }
181 
182  // Process all language packs (and their respective pack groups)
183  C4Group *pPack, *pPackGroup;
184  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)) && (pPackGroup = PackGroups.GetGroup(iPack)); iPack++)
185  {
186  // Get current pack group position within pack
187  SCopy(pPack->GetFullName().getData(), strPackPath, _MAX_PATH);
188  GetRelativePath(pPackGroup->GetFullName().getData(), strPackPath, strPackGroupLocation);
189 
190  // Pack group is at correct position within pack: continue with next pack
191  if (SEqualNoCase(strPackGroupLocation, strTargetLocation))
192  continue;
193 
194  // Try to backtrack until we can reach the target location as a relative child
195  while ( strPackGroupLocation[0]
196  && !GetRelativePath(strTargetLocation, strPackGroupLocation, strAdvance)
197  && pPackGroup->OpenMother() )
198  {
199  // Update pack group location
200  GetRelativePath(pPackGroup->GetFullName().getData(), strPackPath, strPackGroupLocation);
201  }
202 
203  // We can reach the target location as a relative child
204  if (strPackGroupLocation[0] && GetRelativePath(strTargetLocation, strPackGroupLocation, strAdvance))
205  {
206  // Advance pack group to relative child
207  pPackGroup->OpenChild(strAdvance);
208  }
209 
210  // Cannot reach by advancing: need to close and reopen (rewinding group file)
211  else
212  {
213  // Close pack group (if it is open at all)
214  pPackGroup->Close();
215  // Reopen pack group to relative position in language pack if possible
216  pPackGroup->OpenAsChild(pPack, strTargetLocation);
217  }
218 
219  }
220 
221  // Store new target location
222  SCopy(strTargetLocation, PackGroupLocation, _MAX_FNAME);
223 
224  C4GroupSet r;
225  // Provided group gets highest priority
226  r.RegisterGroup(hGroup, false, 1000, C4GSCnt_Component);
227  // register currently open pack groups
228  r.RegisterGroups(PackGroups, C4GSCnt_Language);
229  return r;
230 }
231 
232 bool C4Language::LoadComponentHost(C4ComponentHost *host, C4Group &hGroup, const char *szFilename, const char *szLanguage)
233 {
234  assert(host);
235  if (!host) return false;
236  C4GroupSet hGroups = ::Languages.GetPackGroups(hGroup);
237  return host->Load(hGroups, szFilename, szLanguage);
238 }
239 
240 void C4Language::InitInfos()
241 {
242  C4Group hGroup;
243  // First, look in System.ocg
244  if (Reloc.Open(hGroup, C4CFN_System))
245  {
246  LoadInfos(hGroup);
247  hGroup.Close();
248  }
249  // Now look through the registered packs
250  C4Group *pPack;
251  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
252  // Does it contain a System.ocg child group?
253  if (hGroup.OpenAsChild(pPack, C4CFN_System))
254  {
255  LoadInfos(hGroup);
256  hGroup.Close();
257  }
258 }
259 
260 namespace
261 {
262  std::string GetResStr(const char *id, const char *stringtbl)
263  {
264  // The C++11 standard does not specify whether $ and ^ match
265  // the beginning or end of a line, respectively, and it seems
266  // like in some implementations they only match the beginning
267  // or end of the whole string. See also #1127.
268  static std::regex line_pattern("(?:\n|^)([^=]+)=(.*?)\r?(?=\n|$)", static_cast<std::regex::flag_type>(std::regex_constants::optimize | std::regex_constants::ECMAScript));
269 
270  assert(stringtbl);
271  if (!stringtbl)
272  {
273  return std::string();
274  }
275 
276  // Get beginning and end iterators of stringtbl
277  const char *begin = stringtbl;
278  const char *end = begin + std::char_traits<char>::length(begin);
279 
280  for (auto it = std::cregex_iterator(begin, end, line_pattern); it != std::cregex_iterator(); ++it)
281  {
282  assert(it->size() == 3);
283  if (it->size() != 3)
284  continue;
285 
286  std::string key = (*it)[1];
287  if (key != id)
288  continue;
289 
290  std::string val = (*it)[2];
291  return val;
292  }
293 
294  // If we get here, there was no such string in the string table
295  // return the input string so there's at least *something*
296  return id;
297  }
298 
299  template<size_t N>
300  void CopyResStr(const char *id, const char *stringtbl, char (&dest)[N])
301  {
302  std::string value = GetResStr(id, stringtbl);
303  std::strncpy(dest, value.c_str(), N);
304  dest[N - 1] = '\0';
305  }
306 }
307 
308 void C4Language::LoadInfos(C4Group &hGroup)
309 {
310  char strEntry[_MAX_FNAME_LEN];
311  char *strTable;
312  // Look for language string tables
313  hGroup.ResetSearch();
314  while (hGroup.FindNextEntry(C4CFN_Language, strEntry))
315  // For now, we will only load info on the first string table found for a given
316  // language code as there is currently no handling for selecting different string tables
317  // of the same code - the system always loads the first string table found for a given code
318  if (!FindInfo(GetFilenameOnly(strEntry) + SLen(GetFilenameOnly(strEntry)) - 2))
319  // Load language string table
320  if (hGroup.LoadEntry(strEntry, &strTable, nullptr, 1))
321  {
322  // New language info
323  C4LanguageInfo *pInfo = new C4LanguageInfo;
324  // Get language code by entry name
325  SCopy(GetFilenameOnly(strEntry) + SLen(GetFilenameOnly(strEntry)) - 2, pInfo->Code, 2);
326  SCapitalize(pInfo->Code);
327  // Get language name, info, fallback from table
328  CopyResStr("IDS_LANG_NAME", strTable, pInfo->Name);
329  CopyResStr("IDS_LANG_INFO", strTable, pInfo->Info);
330  CopyResStr("IDS_LANG_FALLBACK", strTable, pInfo->Fallback);
331  // Safety: pipe character is not allowed in any language info string
332  SReplaceChar(pInfo->Name, '|', ' ');
333  SReplaceChar(pInfo->Info, '|', ' ');
334  SReplaceChar(pInfo->Fallback, '|', ' ');
335  // Delete table
336  delete [] strTable;
337  // Add info to list
338  pInfo->Next = Infos;
339  Infos = pInfo;
340  }
341 }
342 
344 {
345  for (C4LanguageInfo *pInfo = Infos; pInfo; pInfo = pInfo->Next)
346  if (iIndex <= 0)
347  return pInfo;
348  else
349  iIndex--;
350  return nullptr;
351 }
352 
354 {
355  for (C4LanguageInfo *pInfo = Infos; pInfo; pInfo = pInfo->Next)
356  if (SEqualNoCase(pInfo->Code, strCode, 2))
357  return pInfo;
358  return nullptr;
359 }
360 
361 bool C4Language::LoadLanguage(const char *strLanguages)
362 {
363  // Clear old string table
364  ClearLanguage();
365  // Try to load string table according to language sequence
366  char strLanguageCode[2 + 1];
367  for (int i = 0; SCopySegment(strLanguages, i, strLanguageCode, ',', 2, true); i++)
368  if (InitStringTable(strLanguageCode))
369  return true;
370  // No matching string table found: hardcoded fallback to US
371  if (InitStringTable("US"))
372  return true;
373  // No string table present: this is really bad
374  Log("Error loading language string table.");
375  return false;
376 }
377 
378 bool C4Language::InitStringTable(const char *strCode)
379 {
380  C4Group hGroup;
381  // First, look in System.ocg
382  if (LoadStringTable(Application.SystemGroup, strCode))
383  return true;
384  // Now look through the registered packs
385  C4Group *pPack;
386  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
387  // Does it contain a System.ocg child group?
388  if (hGroup.OpenAsChild(pPack, C4CFN_System))
389  {
390  if (LoadStringTable(hGroup, strCode))
391  { hGroup.Close(); return true; }
392  hGroup.Close();
393  }
394  // No matching string table found
395  return false;
396 }
397 
398 bool C4Language::LoadStringTable(C4Group &hGroup, const char *strCode)
399 {
400  // Compose entry name
401  char strEntry[_MAX_FNAME_LEN];
402  sprintf(strEntry, "Language%s.txt", strCode); // ...should use C4CFN_Language here
403  // Load string table
404  if (!C4LangStringTable::GetSystemStringTable().Load(hGroup, strEntry))
405  return false;
406  // Success
407  return true;
408 }
409 
411 {
412  // Clear resource string table
414 }
415 
416 // Closes any open language pack that has the specified path.
417 
418 bool C4Language::CloseGroup(const char *strPath)
419 {
420  // Check all open language packs
421  C4Group *pPack;
422  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
423  if (ItemIdentical(strPath, pPack->GetFullName().getData()))
424  {
425  Packs.UnregisterGroup(iPack);
426  return true;
427  }
428  // No pack of that path
429  return false;
430 }
#define C4CFN_Languages
Definition: C4Components.h:32
#define C4CFN_System
Definition: C4Components.h:29
#define C4CFN_Language
Definition: C4Components.h:138
C4Config Config
Definition: C4Config.cpp:930
C4Game Game
Definition: C4Globals.cpp:52
C4Application Application
Definition: C4Globals.cpp:44
bool C4Group_UnpackDirectory(const char *filename)
Definition: C4Group.cpp:401
#define C4GSCnt_Component
Definition: C4GroupSet.h:41
#define C4GSPrio_Base
Definition: C4GroupSet.h:23
#define C4GSCnt_Language
Definition: C4GroupSet.h:40
C4Language Languages
Definition: C4Language.cpp:42
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
C4Reloc Reloc
Definition: C4Reloc.cpp:21
#define _MAX_FNAME
#define DirectorySeparator
#define _MAX_PATH
#define _MAX_PATH_LEN
#define DirSep
#define _MAX_FNAME_LEN
void SReplaceChar(char *str, char fc, char tc)
Definition: Standard.cpp:354
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:279
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
void SAppendChar(char cChar, char *szStr)
Definition: Standard.cpp:271
void SCapitalize(char *str)
Definition: Standard.cpp:360
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:263
#define sprintf
Definition: Standard.h:162
size_t SLen(const char *sptr)
Definition: Standard.h:74
const char * GetRelativePathS(const char *strPath, const char *strRelativeTo)
Definition: StdFile.cpp:208
bool DirectoryExists(const char *szFilename)
Definition: StdFile.cpp:708
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:118
const char * GetFilenameOnly(const char *strFilename)
Definition: StdFile.cpp:57
bool ItemIdentical(const char *szFilename1, const char *szFilename2)
Definition: StdFile.cpp:879
bool ItemExists(const char *szItemName)
Definition: StdFile.h:75
C4Group SystemGroup
Definition: C4Application.h:40
bool Load(C4Group &hGroup, const char *szFilename, const char *szLanguage=nullptr)
const char * AtRelativePath(const char *filename)
Definition: C4Config.cpp:741
C4Scenario C4S
Definition: C4Game.h:74
char ScenarioFilename[_MAX_PATH_LEN]
Definition: C4Game.h:102
bool FindNextEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr, bool start_at_filename=false)
Definition: C4Group.cpp:2217
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2638
bool LoadEntry(const char *entry_name, char **buffer, size_t *size_info=nullptr, int zeros_to_append=0)
Definition: C4Group.cpp:2375
bool OpenAsChild(C4Group *mother, const char *entry_name, bool is_exclusive=false, bool do_create=false)
Definition: C4Group.cpp:1952
bool OpenChild(const char *entry_name)
Definition: C4Group.cpp:2765
void ResetSearch(bool reload_contents=false)
Definition: C4Group.cpp:1316
bool Close()
Definition: C4Group.cpp:971
bool OpenMother()
Definition: C4Group.cpp:2803
bool Open(const char *group_name, bool do_create=false)
Definition: C4Group.cpp:660
void Clear()
Definition: C4GroupSet.cpp:50
bool UnregisterGroup(int32_t iIndex)
Definition: C4GroupSet.cpp:274
bool RegisterGroup(C4Group &rGroup, bool fOwnGrp, int32_t Priority, int32_t Contents, bool fCheckContent=true)
Definition: C4GroupSet.cpp:88
C4Group * GetGroup(int32_t iIndex)
Definition: C4GroupSet.cpp:259
bool RegisterGroups(const C4GroupSet &rCopy, int32_t Contents, const char *szFilename=nullptr, int32_t iMaxSkipID=0)
Definition: C4GroupSet.cpp:128
int32_t GetGroupCount()
Definition: C4GroupSet.cpp:251
static C4LangStringTable & GetSystemStringTable()
C4LanguageInfo * FindInfo(const char *strCode)
Definition: C4Language.cpp:353
C4GroupSet GetPackGroups(C4Group &)
Definition: C4Language.cpp:148
void ClearLanguage()
Definition: C4Language.cpp:410
int GetInfoCount()
Definition: C4Language.cpp:138
bool Init()
Definition: C4Language.cpp:55
bool CloseGroup(const char *strPath)
Definition: C4Language.cpp:418
int GetPackCount()
Definition: C4Language.cpp:133
static bool LoadComponentHost(C4ComponentHost *host, C4Group &hGroup, const char *szFilename, const char *szLanguage)
Definition: C4Language.cpp:232
void Clear()
Definition: C4Language.cpp:114
C4LanguageInfo * GetInfo(int iIndex)
Definition: C4Language.cpp:343
bool LoadLanguage(const char *strLanguages)
Definition: C4Language.cpp:361
char Name[C4MaxLanguageInfo+1]
Definition: C4Language.h:33
char Code[2+1]
Definition: C4Language.h:32
char Fallback[C4MaxLanguageInfo+1]
Definition: C4Language.h:35
char Info[C4MaxLanguageInfo+1]
Definition: C4Language.h:34
iterator end() const
Definition: C4Reloc.cpp:149
bool Open(C4Group &group, const char *filename) const
Definition: C4Reloc.cpp:156
iterator begin() const
Definition: C4Reloc.cpp:142
StdCopyStrBuf Origin
Definition: C4Scenario.h:80
C4SHead Head
Definition: C4Scenario.h:232
const char * getData() const
Definition: StdBuf.h:442
void Copy()
Definition: StdBuf.h:467
size_t getLength() const
Definition: StdBuf.h:445
StdCopyStrBuf strBuf
Definition: C4Reloc.h:33