stringed_ingame.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. // Filename:- stringed_ingame.cpp
  2. //
  3. // This file is designed to be pasted into each game project that uses the StringEd package's files.
  4. // You can alter the way it does things by (eg) replacing STL with RATL, BUT LEAVE THE OVERALL
  5. // FUNCTIONALITY THE SAME, or if I ever make any funadamental changes to the way the package works
  6. // then you're going to be SOOL (shit out of luck ;-)...
  7. //
  8. //////////////////////////////////////////////////
  9. //
  10. // stuff common to all qcommon files...
  11. #include "../server/server.h"
  12. #include "../game/q_shared.h"
  13. #include "qcommon.h"
  14. #include "../qcommon/fixedmap.h"
  15. #include "../zlib/zlib.h"
  16. //
  17. //////////////////////////////////////////////////
  18. #pragma warning ( disable : 4511 ) // copy constructor could not be generated
  19. #pragma warning ( disable : 4512 ) // assignment operator could not be generated
  20. #pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah
  21. #include "stringed_ingame.h"
  22. #include "stringed_interface.h"
  23. // Needed for DWORD and XC_LANGUAGE defines:
  24. #include <xtl.h>
  25. ///////////////////////////////////////////////
  26. // some STL stuff...
  27. #include <string>
  28. using namespace std;
  29. ///////////////////////////////////////////////
  30. cvar_t *se_language = NULL;
  31. // Yeah, it's hardcoded. I don't give a shit.
  32. #define MAX_STRING_ENTRIES 4096
  33. typedef struct SE_Entry_s
  34. {
  35. string m_strString;
  36. } SE_Entry_t;
  37. //typedef map <string, SE_Entry_t> mapStringEntries_t;
  38. class CStringEdPackage
  39. {
  40. private:
  41. SE_BOOL m_bEndMarkerFound_ParseOnly;
  42. string m_strCurrentEntryRef_ParseOnly;
  43. string m_strCurrentEntryEnglish_ParseOnly;
  44. string m_strCurrentFileRef_ParseOnly;
  45. string m_strLoadingLanguage_ParseOnly; // eg "german"
  46. SE_BOOL m_bLoadingEnglish_ParseOnly;
  47. public:
  48. CStringEdPackage()
  49. {
  50. Z_PushNewDeleteTag( TAG_STRINGED );
  51. m_Strings = new VVFixedMap< char *, unsigned long >( MAX_STRING_ENTRIES );
  52. Z_PopNewDeleteTag();
  53. Clear( SE_FALSE );
  54. }
  55. ~CStringEdPackage()
  56. {
  57. Clear( SE_FALSE );
  58. }
  59. // Text entries, indexed by crc32 of reference:
  60. VVFixedMap< char *, unsigned long > *m_Strings;
  61. // mapStringEntries_t m_StringEntries; // needs to be in public space now
  62. void Clear( SE_BOOL bChangingLanguages );
  63. void SetupNewFileParse( LPCSTR psFileName );
  64. SE_BOOL ReadLine( LPCSTR &psParsePos, char *psDest );
  65. LPCSTR ParseLine( LPCSTR psLine );
  66. LPCSTR ExtractLanguageFromPath( LPCSTR psFileName );
  67. SE_BOOL EndMarkerFoundDuringParse( void )
  68. {
  69. return m_bEndMarkerFound_ParseOnly;
  70. }
  71. private:
  72. void AddEntry( LPCSTR psLocalReference );
  73. int GetNumStrings(void);
  74. void SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug );
  75. SE_BOOL SetReference( int iIndex, LPCSTR psNewString );
  76. LPCSTR GetCurrentFileName(void);
  77. LPCSTR GetCurrentReference_ParseOnly( void );
  78. SE_BOOL CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine);
  79. LPCSTR InsideQuotes( LPCSTR psLine );
  80. LPCSTR ConvertCRLiterals_Read( LPCSTR psString );
  81. void REMKill( char *psBuffer );
  82. char *Filename_PathOnly( LPCSTR psFilename );
  83. char *Filename_WithoutPath(LPCSTR psFilename);
  84. char *Filename_WithoutExt(LPCSTR psFilename);
  85. };
  86. CStringEdPackage TheStringPackage;
  87. void CStringEdPackage::Clear( SE_BOOL bChangingLanguages )
  88. {
  89. // m_StringEntries.clear();
  90. m_bEndMarkerFound_ParseOnly = SE_FALSE;
  91. m_strCurrentEntryRef_ParseOnly = "";
  92. m_strCurrentEntryEnglish_ParseOnly = "";
  93. //
  94. // the other vars are cleared in SetupNewFileParse(), and are ok to not do here.
  95. //
  96. }
  97. // loses anything after the path (if any), (eg) "dir/name.bmp" becomes "dir"
  98. // (copes with either slash-scheme for names)
  99. //
  100. // (normally I'd call another function for this, but this is supposed to be engine-independant,
  101. // so a certain amount of re-invention of the wheel is to be expected...)
  102. //
  103. char *CStringEdPackage::Filename_PathOnly(LPCSTR psFilename)
  104. {
  105. static char sString[ iSE_MAX_FILENAME_LENGTH ];
  106. strcpy(sString,psFilename);
  107. char *p1= strrchr(sString,'\\');
  108. char *p2= strrchr(sString,'/');
  109. char *p = (p1>p2)?p1:p2;
  110. if (p)
  111. *p=0;
  112. return sString;
  113. }
  114. // returns (eg) "dir/name" for "dir/name.bmp"
  115. // (copes with either slash-scheme for names)
  116. //
  117. // (normally I'd call another function for this, but this is supposed to be engine-independant,
  118. // so a certain amount of re-invention of the wheel is to be expected...)
  119. //
  120. char *CStringEdPackage::Filename_WithoutExt(LPCSTR psFilename)
  121. {
  122. static char sString[ iSE_MAX_FILENAME_LENGTH ];
  123. strcpy(sString,psFilename);
  124. char *p = strrchr(sString,'.');
  125. char *p2= strrchr(sString,'\\');
  126. char *p3= strrchr(sString,'/');
  127. // special check, make sure the first suffix we found from the end wasn't just a directory suffix (eg on a path'd filename with no extension anyway)
  128. //
  129. if (p &&
  130. (p2==0 || (p2 && p>p2)) &&
  131. (p3==0 || (p3 && p>p3))
  132. )
  133. *p=0;
  134. return sString;
  135. }
  136. // returns actual filename only, no path
  137. // (copes with either slash-scheme for names)
  138. //
  139. // (normally I'd call another function for this, but this is supposed to be engine-independant,
  140. // so a certain amount of re-invention of the wheel is to be expected...)
  141. //
  142. char *CStringEdPackage::Filename_WithoutPath(LPCSTR psFilename)
  143. {
  144. static char sString[ iSE_MAX_FILENAME_LENGTH ];
  145. LPCSTR psCopyPos = psFilename;
  146. while (*psFilename)
  147. {
  148. if (*psFilename == '/' || *psFilename == '\\')
  149. psCopyPos = psFilename+1;
  150. psFilename++;
  151. }
  152. strcpy(sString,psCopyPos);
  153. return sString;
  154. }
  155. LPCSTR CStringEdPackage::ExtractLanguageFromPath( LPCSTR psFileName )
  156. {
  157. return Filename_WithoutPath( Filename_PathOnly( psFileName ) );
  158. }
  159. void CStringEdPackage::SetupNewFileParse( LPCSTR psFileName )
  160. {
  161. char sString[ iSE_MAX_FILENAME_LENGTH ];
  162. strcpy(sString, Filename_WithoutPath( Filename_WithoutExt( psFileName ) ));
  163. Q_strupr(sString);
  164. m_strCurrentFileRef_ParseOnly = sString; // eg "OBJECTIVES"
  165. m_strLoadingLanguage_ParseOnly = ExtractLanguageFromPath( psFileName );
  166. m_bLoadingEnglish_ParseOnly = (!stricmp( m_strLoadingLanguage_ParseOnly.c_str(), "english" )) ? SE_TRUE : SE_FALSE;
  167. }
  168. // returns SE_TRUE if supplied keyword found at line start (and advances supplied ptr past any whitespace to next arg (or line end if none),
  169. //
  170. // else returns SE_FALSE...
  171. //
  172. SE_BOOL CStringEdPackage::CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine)
  173. {
  174. if (!Q_stricmpn(psKeyword, psLine, strlen(psKeyword)) )
  175. {
  176. psLine += strlen(psKeyword);
  177. // skip whitespace to arrive at next item...
  178. //
  179. while ( *psLine == '\t' || *psLine == ' ' )
  180. {
  181. psLine++;
  182. }
  183. return SE_TRUE;
  184. }
  185. return SE_FALSE;
  186. }
  187. // change "\n" to '\n' (i.e. 2-byte char-string to 1-byte ctrl-code)...
  188. // (or "\r\n" in editor)
  189. //
  190. LPCSTR CStringEdPackage::ConvertCRLiterals_Read( LPCSTR psString )
  191. {
  192. static string str;
  193. str = psString;
  194. int iLoc;
  195. while ( (iLoc = str.find("\\n")) != -1 )
  196. {
  197. str[iLoc ] = '\n';
  198. str.erase( iLoc+1,1 );
  199. }
  200. return str.c_str();
  201. }
  202. // kill off any "//" onwards part in the line, but NOT if it's inside a quoted string...
  203. //
  204. void CStringEdPackage::REMKill( char *psBuffer )
  205. {
  206. char *psScanPos = psBuffer;
  207. char *p;
  208. int iDoubleQuotesSoFar = 0;
  209. // scan forwards in case there are more than one (and the first is inside quotes)...
  210. //
  211. while ( (p=strstr(psScanPos,"//")) != NULL)
  212. {
  213. // count the number of double quotes before this point, if odd number, then we're inside quotes...
  214. //
  215. int iDoubleQuoteCount = iDoubleQuotesSoFar;
  216. for (int i=0; i<p-psScanPos; i++)
  217. {
  218. if (psScanPos[i] == '"')
  219. {
  220. iDoubleQuoteCount++;
  221. }
  222. }
  223. if (!(iDoubleQuoteCount&1))
  224. {
  225. // not inside quotes, so kill line here...
  226. //
  227. *p='\0';
  228. //
  229. // and remove any trailing whitespace...
  230. //
  231. if (psScanPos[0]) // any strlen? (else access violation with -1 below)
  232. {
  233. int iWhiteSpaceScanPos = strlen(psScanPos)-1;
  234. while (iWhiteSpaceScanPos>=0 && isspace(psScanPos[iWhiteSpaceScanPos]))
  235. {
  236. psScanPos[iWhiteSpaceScanPos--] = '\0';
  237. }
  238. }
  239. return;
  240. }
  241. else
  242. {
  243. // inside quotes (blast), oh well, skip past and keep scanning...
  244. //
  245. psScanPos = p+1;
  246. iDoubleQuotesSoFar = iDoubleQuoteCount;
  247. }
  248. }
  249. }
  250. // returns true while new lines available to be read...
  251. //
  252. SE_BOOL CStringEdPackage::ReadLine( LPCSTR &psParsePos, char *psDest )
  253. {
  254. if (psParsePos[0])
  255. {
  256. LPCSTR psLineEnd = strchr(psParsePos, '\n');
  257. if (psLineEnd)
  258. {
  259. int iCharsToCopy = (psLineEnd - psParsePos);
  260. strncpy(psDest, psParsePos, iCharsToCopy);
  261. psDest[iCharsToCopy] = '\0';
  262. psParsePos += iCharsToCopy;
  263. while (*psParsePos && strchr("\r\n",*psParsePos))
  264. {
  265. psParsePos++; // skip over CR or CR/LF pairs
  266. }
  267. }
  268. else
  269. {
  270. // last line...
  271. //
  272. strcpy(psDest, psParsePos);
  273. psParsePos += strlen(psParsePos);
  274. }
  275. // clean up the line...
  276. //
  277. if (psDest[0])
  278. {
  279. int iWhiteSpaceScanPos = strlen(psDest)-1;
  280. while (iWhiteSpaceScanPos>=0 && isspace(psDest[iWhiteSpaceScanPos]))
  281. {
  282. psDest[iWhiteSpaceScanPos--] = '\0';
  283. }
  284. REMKill( psDest );
  285. }
  286. return SE_TRUE;
  287. }
  288. return SE_FALSE;
  289. }
  290. // remove any outside quotes from this supplied line, plus any leading or trailing whitespace...
  291. //
  292. LPCSTR CStringEdPackage::InsideQuotes( LPCSTR psLine )
  293. {
  294. // I *could* replace this string object with a declared array, but wasn't sure how big to leave it, and it'd have to
  295. // be static as well, hence permanent. (problem on consoles?)
  296. //
  297. static string str;
  298. str = ""; // do NOT join to above line
  299. // skip any leading whitespace...
  300. //
  301. while (*psLine == ' ' || *psLine == '\t')
  302. {
  303. psLine++;
  304. }
  305. // skip any leading quote...
  306. //
  307. if (*psLine == '"')
  308. {
  309. psLine++;
  310. }
  311. // assign it...
  312. //
  313. str = psLine;
  314. if (psLine[0])
  315. {
  316. // lose any trailing whitespace...
  317. //
  318. while ( str.c_str()[ strlen(str.c_str()) -1 ] == ' ' ||
  319. str.c_str()[ strlen(str.c_str()) -1 ] == '\t'
  320. )
  321. {
  322. str.erase( strlen(str.c_str()) -1, 1);
  323. }
  324. // lose any trailing quote...
  325. //
  326. if (str.c_str()[ strlen(str.c_str()) -1 ] == '"')
  327. {
  328. str.erase( strlen(str.c_str()) -1, 1);
  329. }
  330. }
  331. // and return it...
  332. //
  333. return str.c_str();
  334. }
  335. // this copes with both foreigners using hi-char values (eg the french using 0x92 instead of 0x27
  336. // for a "'" char), as well as the fact that our buggy fontgen program writes out zeroed glyph info for
  337. // some fonts anyway (though not all, just as a gotcha).
  338. //
  339. // New bit, instead of static buffer (since XBox guys are desperately short of mem) I return a malloc'd buffer now,
  340. // so remember to free it!
  341. //
  342. static char *CopeWithDumbStringData( LPCSTR psSentence, LPCSTR psThisLanguage )
  343. {
  344. const int iBufferSize = strlen(psSentence)*3; // *3 to allow for expansion of anything even stupid string consisting entirely of elipsis chars
  345. char *psNewString = (char *) Z_Malloc(iBufferSize, TAG_TEMP_WORKSPACE, qfalse);
  346. Q_strncpyz(psNewString, psSentence, iBufferSize);
  347. // this is annoying, I have to just guess at which languages to do it for (ie NOT ASIAN/MBCS!!!) since the
  348. // string system was deliberately (and correctly) designed to not know or care whether it was doing SBCS
  349. // or MBCS languages, because it was never envisioned that I'd have to clean up other people's mess.
  350. //
  351. // Ok, bollocks to it, this will have to do. Any other languages that come later and have bugs in their text can
  352. // get fixed by them typing it in properly in the first place...
  353. //
  354. if (!stricmp(psThisLanguage,"ENGLISH") ||
  355. !stricmp(psThisLanguage,"FRENCH") ||
  356. !stricmp(psThisLanguage,"GERMAN") ||
  357. !stricmp(psThisLanguage,"ITALIAN") ||
  358. !stricmp(psThisLanguage,"SPANISH") ||
  359. !stricmp(psThisLanguage,"POLISH") ||
  360. !stricmp(psThisLanguage,"RUSSIAN")
  361. )
  362. {
  363. char *p;
  364. // strXLS_Speech.Replace(va("%c",0x92),va("%c",0x27)); // "'"
  365. while ((p=strchr(psNewString,0x92))!=NULL) // "rich" (and illegal) apostrophe
  366. {
  367. *p = 0x27;
  368. }
  369. // strXLS_Speech.Replace(va("%c",0x93),"\""); // smart quotes -> '"'
  370. while ((p=strchr(psNewString,0x93))!=NULL)
  371. {
  372. *p = '"';
  373. }
  374. // strXLS_Speech.Replace(va("%c",0x94),"\""); // smart quotes -> '"'
  375. while ((p=strchr(psNewString,0x94))!=NULL)
  376. {
  377. *p = '"';
  378. }
  379. // strXLS_Speech.Replace(va("%c",0x0B),"."); // full stop
  380. while ((p=strchr(psNewString,0x0B))!=NULL)
  381. {
  382. *p = '.';
  383. }
  384. // strXLS_Speech.Replace(va("%c",0x85),"..."); // "..."-char -> 3-char "..."
  385. while ((p=strchr(psNewString,0x85))!=NULL) // "rich" (and illegal) apostrophe
  386. {
  387. memmove(p+2,p,strlen(p));
  388. *p++ = '.';
  389. *p++ = '.';
  390. *p = '.';
  391. }
  392. // strXLS_Speech.Replace(va("%c",0x91),va("%c",0x27)); // "'"
  393. while ((p=strchr(psNewString,0x91))!=NULL)
  394. {
  395. *p = 0x27;
  396. }
  397. // strXLS_Speech.Replace(va("%c",0x96),va("%c",0x2D)); // "-"
  398. while ((p=strchr(psNewString,0x96))!=NULL)
  399. {
  400. *p = 0x2D;
  401. }
  402. // strXLS_Speech.Replace(va("%c",0x97),va("%c",0x2D)); // "-"
  403. while ((p=strchr(psNewString,0x97))!=NULL)
  404. {
  405. *p = 0x2D;
  406. }
  407. // bug fix for picky grammatical errors, replace "?." with "? "
  408. //
  409. while ((p=strstr(psNewString,"?."))!=NULL)
  410. {
  411. p[1] = ' ';
  412. }
  413. // StripEd and our print code don't support tabs...
  414. //
  415. while ((p=strchr(psNewString,0x09))!=NULL)
  416. {
  417. *p = ' ';
  418. }
  419. }
  420. return psNewString;
  421. }
  422. // return is either NULL for good else error message to display...
  423. //
  424. LPCSTR CStringEdPackage::ParseLine( LPCSTR psLine )
  425. {
  426. LPCSTR psErrorMessage = NULL;
  427. if (psLine)
  428. {
  429. if (CheckLineForKeyword( sSE_KEYWORD_VERSION, psLine ))
  430. {
  431. // VERSION "1"
  432. //
  433. LPCSTR psVersionNumber = InsideQuotes( psLine );
  434. int iVersionNumber = atoi( psVersionNumber );
  435. if (iVersionNumber != iSE_VERSION)
  436. {
  437. psErrorMessage = va("Unexpected version number %d, expecting %d!\n", iVersionNumber, iSE_VERSION);
  438. }
  439. }
  440. else
  441. if ( CheckLineForKeyword(sSE_KEYWORD_CONFIG, psLine)
  442. || CheckLineForKeyword(sSE_KEYWORD_FILENOTES, psLine)
  443. || CheckLineForKeyword(sSE_KEYWORD_NOTES, psLine)
  444. )
  445. {
  446. // not used ingame, but need to absorb the token
  447. }
  448. else
  449. if (CheckLineForKeyword(sSE_KEYWORD_REFERENCE, psLine))
  450. {
  451. // REFERENCE GUARD_GOOD_TO_SEE_YOU
  452. //
  453. LPCSTR psLocalReference = InsideQuotes( psLine );
  454. AddEntry( psLocalReference );
  455. }
  456. else
  457. if (CheckLineForKeyword(sSE_KEYWORD_ENDMARKER, psLine))
  458. {
  459. // ENDMARKER
  460. //
  461. m_bEndMarkerFound_ParseOnly = SE_TRUE; // the only major error checking I bother to do (for file truncation)
  462. }
  463. else
  464. if (!Q_stricmpn(sSE_KEYWORD_LANG, psLine, strlen(sSE_KEYWORD_LANG)))
  465. {
  466. // LANG_ENGLISH "GUARD: Good to see you, sir. Taylor is waiting for you in the clean tent. We need to get you suited up. "
  467. //
  468. LPCSTR psReference = GetCurrentReference_ParseOnly();
  469. if ( psReference[0] )
  470. {
  471. psLine += strlen(sSE_KEYWORD_LANG);
  472. // what language is this?...
  473. //
  474. LPCSTR psWordEnd = psLine;
  475. while (*psWordEnd && *psWordEnd != ' ' && *psWordEnd != '\t')
  476. {
  477. psWordEnd++;
  478. }
  479. char sThisLanguage[1024]={0};
  480. int iCharsToCopy = psWordEnd - psLine;
  481. if (iCharsToCopy > sizeof(sThisLanguage)-1)
  482. {
  483. iCharsToCopy = sizeof(sThisLanguage)-1;
  484. }
  485. strncpy(sThisLanguage, psLine, iCharsToCopy); // already declared as {0} so no need to zero-cap dest buffer
  486. psLine += strlen(sThisLanguage);
  487. LPCSTR _psSentence = ConvertCRLiterals_Read( InsideQuotes( psLine ) );
  488. // Dammit, I hate having to do crap like this just because other people mess up and put
  489. // stupid data in their text, so I have to cope with it.
  490. //
  491. // note hackery with _psSentence and psSentence because of const-ness. bleurgh. Just don't ask.
  492. //
  493. char *psSentence = CopeWithDumbStringData( _psSentence, sThisLanguage );
  494. if ( m_bLoadingEnglish_ParseOnly )
  495. {
  496. // if loading just "english", then go ahead and store it...
  497. //
  498. SetString( psReference, psSentence, SE_FALSE );
  499. }
  500. else
  501. {
  502. // if loading a foreign language...
  503. //
  504. SE_BOOL bSentenceIsEnglish = (!stricmp(sThisLanguage,"english")) ? SE_TRUE: SE_FALSE; // see whether this is the english master or not
  505. // this check can be omitted, I'm just being extra careful here...
  506. //
  507. if ( !bSentenceIsEnglish )
  508. {
  509. // basically this is just checking that an .STE file override is the same language as the .STR...
  510. //
  511. if (stricmp( m_strLoadingLanguage_ParseOnly.c_str(), sThisLanguage ))
  512. {
  513. psErrorMessage = va("Language \"%s\" found when expecting \"%s\"!\n", sThisLanguage, m_strLoadingLanguage_ParseOnly.c_str());
  514. }
  515. }
  516. if (!psErrorMessage)
  517. {
  518. SetString( psReference, psSentence, bSentenceIsEnglish );
  519. }
  520. }
  521. Z_Free( psSentence );
  522. }
  523. else
  524. {
  525. psErrorMessage = "Error parsing file: Unexpected \"" sSE_KEYWORD_LANG "\"\n";
  526. }
  527. }
  528. else
  529. {
  530. psErrorMessage = va("Unknown keyword at linestart: \"%s\"\n", psLine);
  531. }
  532. }
  533. return psErrorMessage;
  534. }
  535. // returns reference of string being parsed, else "" for none.
  536. //
  537. LPCSTR CStringEdPackage::GetCurrentReference_ParseOnly( void )
  538. {
  539. return m_strCurrentEntryRef_ParseOnly.c_str();
  540. }
  541. // add new string entry (during parse)
  542. //
  543. void CStringEdPackage::AddEntry( LPCSTR psLocalReference )
  544. {
  545. // the reason I don't just assign it anyway is because the optional .STE override files don't contain flags,
  546. // and therefore would wipe out the parsed flags of the .STR file...
  547. //
  548. /*
  549. mapStringEntries_t::iterator itEntry = m_StringEntries.find( va("%s_%s",m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) );
  550. if (itEntry == m_StringEntries.end())
  551. {
  552. SE_Entry_t SE_Entry;
  553. m_StringEntries[ va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ] = SE_Entry;
  554. }
  555. */
  556. m_strCurrentEntryRef_ParseOnly = psLocalReference;
  557. }
  558. void CStringEdPackage::SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug )
  559. {
  560. const char *ref = va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference);
  561. unsigned long refCrc = crc32( 0, (const Bytef *)ref, strlen(ref) );
  562. if ( bEnglishDebug )
  563. {
  564. // This is the leading english text of a foreign sentence pair (so it's the debug-key text):
  565. // don't store, just make a note in-case #same shows up:
  566. m_strCurrentEntryEnglish_ParseOnly = psNewString;
  567. }
  568. else if ( m_bLoadingEnglish_ParseOnly )
  569. {
  570. // It's the english text, and we're loading english. Add it!
  571. int len = strlen( psNewString );
  572. char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse );
  573. strcpy( strData, psNewString );
  574. m_Strings->Insert( strData, refCrc );
  575. }
  576. else
  577. {
  578. // It's foreign text, we're going to add it, but we need to check for #same
  579. if (!stricmp(psNewString, sSE_EXPORT_SAME))
  580. {
  581. // If it's #same, then copy the stored english version:
  582. int len = m_strCurrentEntryEnglish_ParseOnly.length();
  583. char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse );
  584. strcpy( strData, m_strCurrentEntryEnglish_ParseOnly.c_str() );
  585. m_Strings->Insert( strData, refCrc );
  586. }
  587. else
  588. {
  589. // Explicit foreign text. Add it!
  590. int len = strlen( psNewString );
  591. char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse );
  592. strcpy( strData, psNewString );
  593. m_Strings->Insert( strData, refCrc );
  594. }
  595. }
  596. }
  597. // filename is local here, eg: "strings/german/obj.str"
  598. //
  599. // return is either NULL for good else error message to display...
  600. //
  601. static LPCSTR SE_Load_Actual( LPCSTR psFileName, SE_BOOL bSpeculativeLoad )
  602. {
  603. LPCSTR psErrorMessage = NULL;
  604. unsigned char *psLoadedData = SE_LoadFileData( psFileName );
  605. if ( psLoadedData )
  606. {
  607. // now parse the data...
  608. //
  609. char *psParsePos = (char *) psLoadedData;
  610. TheStringPackage.SetupNewFileParse( psFileName );
  611. char sLineBuffer[16384]; // should be enough for one line of text (some of them can be BIG though)
  612. while ( !psErrorMessage && TheStringPackage.ReadLine((LPCSTR &) psParsePos, sLineBuffer ) )
  613. {
  614. if (strlen(sLineBuffer))
  615. {
  616. psErrorMessage = TheStringPackage.ParseLine( sLineBuffer );
  617. }
  618. }
  619. SE_FreeFileDataAfterLoad( psLoadedData);
  620. if (!psErrorMessage && !TheStringPackage.EndMarkerFoundDuringParse())
  621. {
  622. psErrorMessage = va("Truncated file, failed to find \"%s\" at file end!", sSE_KEYWORD_ENDMARKER);
  623. }
  624. }
  625. else
  626. {
  627. if ( bSpeculativeLoad )
  628. {
  629. // then it's ok to not find the file, so do nothing...
  630. }
  631. else
  632. {
  633. psErrorMessage = va("Unable to load \"%s\"!", psFileName);
  634. }
  635. }
  636. return psErrorMessage;
  637. }
  638. static LPCSTR SE_GetFoundFile( string &strResult )
  639. {
  640. static char sTemp[1024/*MAX_PATH*/];
  641. if (!strlen(strResult.c_str()))
  642. return NULL;
  643. strncpy(sTemp,strResult.c_str(),sizeof(sTemp)-1);
  644. sTemp[sizeof(sTemp)-1]='\0';
  645. char *psSemiColon = strchr(sTemp,';');
  646. if ( psSemiColon)
  647. {
  648. *psSemiColon = '\0';
  649. strResult.erase(0,(psSemiColon-sTemp)+1);
  650. }
  651. else
  652. {
  653. // no semicolon found, probably last entry? (though i think even those have them on, oh well)
  654. //
  655. strResult.erase();
  656. }
  657. // strlwr(sTemp); // just for consistancy and set<> -> set<> erasure checking etc
  658. return sTemp;
  659. }
  660. //////////// API entry points from rest of game.... //////////////////////////////
  661. // filename is local here, eg: "strings/german/obj.str"
  662. //
  663. // return is either NULL for good else error message to display...
  664. //
  665. LPCSTR SE_Load( LPCSTR psFileName, SE_BOOL bFailIsCritical = SE_TRUE )
  666. {
  667. ////////////////////////////////////////////////////
  668. //
  669. // ingame here tends to pass in names without paths, but I expect them when doing a language load, so...
  670. //
  671. char sTemp[1000]={0};
  672. if (!strchr(psFileName,'/'))
  673. {
  674. strcpy(sTemp,sSE_STRINGS_DIR);
  675. strcat(sTemp,"/");
  676. if (se_language)
  677. {
  678. strcat(sTemp,se_language->string);
  679. strcat(sTemp,"/");
  680. }
  681. }
  682. strcat(sTemp,psFileName);
  683. COM_DefaultExtension( sTemp, sizeof(sTemp), sSE_INGAME_FILE_EXTENSION);
  684. psFileName = &sTemp[0];
  685. //
  686. ////////////////////////////////////////////////////
  687. LPCSTR psErrorMessage = SE_Load_Actual( psFileName, SE_FALSE );
  688. // check for any corresponding / overriding .STE files and load them afterwards...
  689. //
  690. if ( !psErrorMessage )
  691. {
  692. char sFileName[ iSE_MAX_FILENAME_LENGTH ];
  693. strncpy( sFileName, psFileName, sizeof(sFileName)-1 );
  694. sFileName[ sizeof(sFileName)-1 ] = '\0';
  695. char *p = strrchr( sFileName, '.' );
  696. if (p && strlen(p) == strlen(sSE_EXPORT_FILE_EXTENSION))
  697. {
  698. strcpy( p, sSE_EXPORT_FILE_EXTENSION );
  699. psErrorMessage = SE_Load_Actual( sFileName, SE_TRUE );
  700. }
  701. }
  702. if (psErrorMessage)
  703. {
  704. if (bFailIsCritical)
  705. {
  706. // TheStringPackage.Clear(TRUE); // Will we want to do this? Any errors that arise should be fixed immediately
  707. Com_Error( ERR_DROP, "SE_Load(): Couldn't load \"%s\"!\n\nError: \"%s\"\n", psFileName, psErrorMessage );
  708. }
  709. else
  710. {
  711. Com_DPrintf(S_COLOR_YELLOW "SE_Load(): Couldn't load \"%s\"!\n", psFileName );
  712. }
  713. }
  714. return psErrorMessage;
  715. }
  716. // convenience-function for the main GetString call...
  717. //
  718. LPCSTR SE_GetString( LPCSTR psPackageReference, LPCSTR psStringReference)
  719. {
  720. char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long
  721. sprintf(sReference,"%s_%s", psPackageReference, psStringReference);
  722. return SE_GetString( Q_strupr(sReference) );
  723. }
  724. LPCSTR SE_GetString( LPCSTR psPackageAndStringReference )
  725. {
  726. int len = strlen( psPackageAndStringReference );
  727. char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long
  728. assert( len < sizeof(sReference) );
  729. Q_strncpyz( sReference, psPackageAndStringReference, sizeof(sReference) );
  730. Q_strupr( sReference );
  731. unsigned long refCrc = crc32( 0, (const Bytef *)sReference, len );
  732. char **strData = TheStringPackage.m_Strings->Find( refCrc );
  733. if( !strData )
  734. return "";
  735. else
  736. return *strData;
  737. }
  738. void SE_NewLanguage(void)
  739. {
  740. TheStringPackage.Clear( SE_TRUE );
  741. }
  742. // these two functions aren't needed other than to make Quake-type games happy and/or stop memory managers
  743. // complaining about leaks if they report them before the global StringEd package object calls it's own dtor.
  744. //
  745. // but here they are for completeness's sake I guess...
  746. //
  747. void SE_Init(void)
  748. {
  749. Z_PushNewDeleteTag( TAG_STRINGED );
  750. TheStringPackage.Clear( SE_FALSE );
  751. // se_language = Cvar_Get("se_language", "english", CVAR_ARCHIVE | CVAR_NORESTART);
  752. extern DWORD g_dwLanguage;
  753. switch( g_dwLanguage )
  754. {
  755. case XC_LANGUAGE_FRENCH:
  756. se_language = Cvar_Get("se_language", "french", CVAR_NORESTART);
  757. break;
  758. case XC_LANGUAGE_GERMAN:
  759. se_language = Cvar_Get("se_language", "german", CVAR_NORESTART);
  760. break;
  761. case XC_LANGUAGE_ENGLISH:
  762. default:
  763. se_language = Cvar_Get("se_language", "english", CVAR_NORESTART);
  764. break;
  765. }
  766. // Rather than calling SE_LoadLanguage directly, do this. Otherwise,
  767. // se_langauge->modified doesn't get cleared, and we parse the string files
  768. // twice. Gah.
  769. SE_CheckForLanguageUpdates();
  770. Z_PopNewDeleteTag();
  771. }
  772. // returns error message else NULL for ok.
  773. //
  774. // Any errors that result from this should probably be treated as game-fatal, since an asset file is fuxored.
  775. //
  776. LPCSTR SE_LoadLanguage( LPCSTR psLanguage )
  777. {
  778. LPCSTR psErrorMessage = NULL;
  779. if (psLanguage && psLanguage[0])
  780. {
  781. SE_NewLanguage();
  782. string strResults;
  783. /*int iFilesFound = */SE_BuildFileList(
  784. #ifdef _STRINGED
  785. va("C:\\Source\\Tools\\StringEd\\test_data\\%s",sSE_STRINGS_DIR)
  786. #else
  787. sSE_STRINGS_DIR
  788. #endif
  789. , strResults
  790. );
  791. LPCSTR p;
  792. while ( (p=SE_GetFoundFile (strResults)) != NULL && !psErrorMessage )
  793. {
  794. LPCSTR psThisLang = TheStringPackage.ExtractLanguageFromPath( p );
  795. if ( !stricmp( psLanguage, psThisLang ) )
  796. {
  797. psErrorMessage = SE_Load( p );
  798. }
  799. }
  800. }
  801. else
  802. {
  803. assert( 0 && "SE_LoadLanguage(): Bad language name!" );
  804. }
  805. return psErrorMessage;
  806. }
  807. // called in Com_Frame, so don't take up any time! (can also be called during dedicated)
  808. //
  809. // instead of re-loading just the files we've already loaded I'm going to load the whole language (simpler)
  810. //
  811. void SE_CheckForLanguageUpdates(void)
  812. {
  813. if (se_language && se_language->modified)
  814. {
  815. LPCSTR psErrorMessage = SE_LoadLanguage( se_language->string );
  816. if ( psErrorMessage )
  817. {
  818. Com_Error( ERR_DROP, psErrorMessage );
  819. }
  820. TheStringPackage.m_Strings->Sort();
  821. se_language->modified = SE_FALSE;
  822. }
  823. }
  824. ///////////////////////// eof //////////////////////////