Skip to content

feat(text): Load extra string files next to the string table - #3460

Open
tintinhamans wants to merge 4 commits into
TheSuperHackers:mainfrom
tintinhamans:arctic/feat/layered-string-files
Open

tintinhamans wants to merge 4 commits into
TheSuperHackers:mainfrom
tintinhamans:arctic/feat/layered-string-files

Conversation

@tintinhamans

@tintinhamans tintinhamans commented Oct 10, 2026 •

Copy link
Copy Markdown

Merge after #3458. Closes #255

Right now the game reads one string file per language, so changing two labels means shipping a full generals.csf.

With this, every other .str and .csf in data\<Language>\ and data\ loads too, loose or in a big. A file only needs the labels it changes.

Precedence is the rule modders already know from bigs: whatever sorts first wins. Language folder before data\, files in name order, .str before .csf of the same name, and the game's own string file last so it only fills in what nothing else set. 000_mymod.str beats zz_other.str the same way 000_mymod.big beats zz_other.big.

Retail 1.04 has nothing extra to load. Steam 1.05 has Data\Patch.str in PatchData.big (the two Custom Mission labels) which the 1.05 exe reads and this code never did. That gets picked up now.

Two files, same label Result
Same path, loose and in a big Only the loose one loads
Same path in two bigs Only the first big by name loads
data\<Language>\ vs data\ Language folder wins
Same folder, two names First by name, .str beats .csf of the same name
Any extra file vs generals.csf The extra file wins

The first two rows are just how the file system already works. map.str is untouched, it stays a separate table that is only checked for labels the main one doesn't have.

Three commits: the parsers first push into a vector instead of a pre-sized array, then the parsers get bounds, then the layering. The vector drops the second pass over every str file that only counted END lines, the 500 entry slack, and the separate map.str parser, which was a copy of the str parser with a language filter. Malformed files get dropped instead of corrupting memory. For a csf that is oversized labels or text, a header count the file can't hold, a truncated file, or one that stops short of its header count. For a str it is a line, string or speech name longer than the buffer, or a quote that never closes, which hangs startup on main. Every read is checked, so a bogus string count no longer spins. A Generals.str that fails to parse falls back to generals.csf.

Tested: INI CRC unchanged (FEAAE3F3 on main, with this, and with extra str files around). Same 11 labels out of LF, CRLF and mixed, loose and in a big, so #255's line ending concern doesn't apply. 22 scenarios in game, sheet below.

image

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 92c1917f-933e-4133-aa98-fe5e3945c806

📥 Commits

Reviewing files that changed from the base of the PR and between 224c143 and b7b7332.


📒 Files selected for processing (1)
  • Core/GameEngine/Source/Common/System/ArchiveFile.cpp

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dbd9df8e-572a-4a6e-b270-c1600bf66ff4






📥 Commits

Reviewing files that changed from the base of the PR and between a835031 and 224c143.







📒 Files selected for processing (1)
  • Core/GameEngine/Source/GameClient/GameText.cpp






Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.








Walkthrough

The text manager collects and merges entries from language-specific and neutral .str and .csf files. Parsers return records in vectors and validate input. Map string loading uses the shared STR parser. Directory traversal follows the recursive-search option.

Changes

Text file loading

Layer / File(s) Summary
File selection and entry merging
Core/GameEngine/Source/GameClient/GameText.cpp
The manager collects and orders language-specific and neutral .str and .csf files. It merges entries in order, keeping the first definition for each label, then tries the configured string file or CSF fallback.
Vector-based parsing and input bounds
Core/GameEngine/Source/GameClient/GameText.cpp
CSF and STR parsers return entries in vectors and validate input reads and lengths. Quote and wave parsing report incomplete or oversized input. Map loading uses the shared STR parser with language filtering enabled.
Recursive directory search
Core/GameEngine/Source/Common/System/ArchiveFile.cpp
Directory traversal skips child directories when recursive search is disabled.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant GameTextManager
  participant ArchiveFile
  participant parseStringFile
  participant parseCSF
  GameTextManager->>ArchiveFile: Collect STR and CSF files
  ArchiveFile-->>GameTextManager: Return directory file lists
  loop Each ordered file
    alt STR file
      GameTextManager->>parseStringFile: Parse file into output records
      parseStringFile-->>GameTextManager: Return parsed records
    else CSF file
      GameTextManager->>parseCSF: Parse file into output records
      parseCSF-->>GameTextManager: Return parsed records
    end
    GameTextManager->>GameTextManager: Keep first record for each label
  end
Loading

Suggested reviewers: xezon

Merge Risk: ⚪ Minimal · up to 224c1

The change loads extra string files with validated parsing. Malformed CSF headers and oversized wave names are now rejected instead of causing bad allocations or buffer overflows. No concrete merge-blocking risk remains in the reviewed changes.

Pre-merge checks | Passed 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Issue #255 requires reliable loading of plain .str files from .big archives. GameTextManager now discovers archive-backed .str files and parses them with bounded line and quote reads. The repo…
Out of Scope Changes check Passed The changes remain within the string-file loading scope. .csf layering, precedence handling, vector-based parser output, malformed-file checks, map-string parser reuse, and the archive directory tra…
Title check Passed The title clearly and concisely describes the main change: loading additional string files next to the primary string table.
Description check Passed The description is directly related to the changeset and explains file discovery, precedence, parser validation, fallback behavior, and testing.





  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 32e49e1c-a26d-4c7c-847c-55d9760cf9b9
📥 Commits

Reviewing files that changed from the base of the PR and between ab568af and ad6030d.

📒 Files selected for processing (2)
  • Core/GameEngine/Source/Common/System/ArchiveFile.cpp
  • Core/GameEngine/Source/GameClient/GameText.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread Core/GameEngine/Source/GameClient/GameText.cpp Outdated
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch 4 times, most recently from 7ea64ce to a93f4a7 Compare October 10, 2026 06:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟠 Major · Validate the CSF label count before allocation. · GameText.cpp:443

Core/GameEngine/Source/GameClient/GameText.cpp:443
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate the CSF label count before allocation.

getCSFInfo accepts a signed num_labels after checking only the header ID. mergeStringFile then passes that value to NEW StringInfo[capacity] before parseCSF can reject excess records. A negative or file-inconsistent count can request an invalid or excessive allocation and prevent text initialization.

Reject negative counts and counts larger than the remaining file bytes divided by the minimum label-record size.

🐛 Suggested fix
 			if ( header.id == CSF_ID )
 			{
-				textCount = header.num_labels;
+				const Int remainingBytes = file->size() - file->position();
+				const Int minLabelBytes = sizeof(Int) * 3;
+
+				if ( remainingBytes < 0 || header.num_labels < 0 ||
+					header.num_labels > remainingBytes / minLabelBytes )
+				{
+					break;
+				}
+
+				textCount = header.num_labels;
 
 				if ( header.version >= 2 )
 				{
 					language = (LanguageID) header.langid;

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b595e7f8-c6f3-4828-a350-957039cab174
📥 Commits

Reviewing files that changed from the base of the PR and between 3f9d3b2 and a93f4a7.

📒 Files selected for processing (1)
  • Core/GameEngine/Source/GameClient/GameText.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch 2 times, most recently from 01f10f4 to c79f7c4 Compare October 10, 2026 06:45
@tintinhamans
tintinhamans marked this pull request as ready for review October 10, 2026 06:54
Comment thread Core/GameEngine/Source/GameClient/GameText.cpp
@greptile-apps

greptile-apps Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with a non-blocking check needed for negative CSF string counts.

Findings

  1. P2 Damaged files hide working text ▶

Summary

Loads extra STR and CSF files before the game's main string table.

  • Extra string files can add labels before the main game table loads.
  • String files build their tables as entries are read.

The four earlier threads were manually resolved without explanatory replies. They were unnumbered, so no numbered previous-finding entries are recorded.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Language-folder extras] --> B[Data-folder extras]
  B --> C{Main STR loads?}
  C -- Yes --> E[Build text lookup]
  C -- No --> D[Load main CSF]
  D --> E
  F[First definition of each label wins] --- A
  F --- B
  F --- D
Loading

Reviews (4) · Last reviewed commit: "feat(text): Load extra string files next..." · Reviewed by Greptile

Comment thread Core/GameEngine/Source/GameClient/GameText.cpp Outdated
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch 2 times, most recently from 99ffeae to b754b90 Compare October 10, 2026 07:01
@tintinhamans
tintinhamans marked this pull request as draft October 10, 2026 07:03
Comment thread Core/GameEngine/Source/GameClient/GameText.cpp Outdated
Comment thread Core/GameEngine/Source/GameClient/GameText.cpp Outdated
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch from b754b90 to 01eb4bd Compare October 10, 2026 07:15
@tintinhamans tintinhamans changed the title feat(text): Load extra string files next to the string table, first file wins feat(text): Load extra string files next to the string table Oct 10, 2026
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch from 01eb4bd to bd77ebd Compare October 10, 2026 07:32
@tintinhamans
tintinhamans marked this pull request as ready for review October 10, 2026 07:36
Comment thread Core/GameEngine/Source/GameClient/GameText.cpp
@tintinhamans
tintinhamans marked this pull request as draft October 10, 2026 07:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bb49ef62-8a47-4902-a46f-615802606014
📥 Commits

Reviewing files that changed from the base of the PR and between 01eb4bd and bd77ebd.

📒 Files selected for processing (1)
  • Core/GameEngine/Source/GameClient/GameText.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Comment thread Core/GameEngine/Source/GameClient/GameText.cpp
Comment thread Core/GameEngine/Source/GameClient/GameText.cpp
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch 2 times, most recently from d03882c to a835031 Compare October 10, 2026 08:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8daf08b2-bd6d-478d-91d3-dd2b5548bd16
📥 Commits

Reviewing files that changed from the base of the PR and between bd77ebd and a835031.

📒 Files selected for processing (1)
  • Core/GameEngine/Source/GameClient/GameText.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Comment thread Core/GameEngine/Source/GameClient/GameText.cpp
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch from a835031 to 224c143 Compare October 10, 2026 08:13
@tintinhamans
tintinhamans force-pushed the arctic/feat/layered-string-files branch from 224c143 to b7b7332 Compare October 10, 2026 08:19
@tintinhamans
tintinhamans marked this pull request as ready for review October 10, 2026 08:20
Comment on lines +958 to +961
if ( file->read ( &num_strings, sizeof ( Int ) ) != (Int)(sizeof ( Int )) )
{
goto quit;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Damaged files hide working text

parseCSF checks whether num_strings was read, but still accepts a negative value. The string loop then does nothing, and the file contributes a label with empty text. An extra CSF can therefore hide a working label from generals.csf instead of being skipped as malformed. Reject negative string counts before accepting the label.

Suggested change
if ( file->read ( &num_strings, sizeof ( Int ) ) != (Int)(sizeof ( Int )) )
{
goto quit;
}
if ( file->read ( &num_strings, sizeof ( Int ) ) != (Int)(sizeof ( Int )) || num_strings < 0 )
{
goto quit;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plain string files cannot be read from within .big files

1 participant