Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
186 commits
Select commit Hold shift + click to select a range
18230b2
Read the coarse clock from std::chrono instead of winmm
mischa85 Sep 2, 2026
e7e1600
Mark the timer resolution calls as the Windows ones they are
mischa85 Sep 2, 2026
bea22b6
Include <cstddef> where size_t is used
mischa85 Sep 2, 2026
1d3d408
Define the angle macros outside the MSVC block
mischa85 Sep 2, 2026
db83320
Guard MSVC's low-level I/O header
mischa85 Sep 2, 2026
9be425b
Include <new> rather than MSVC's <new.h>
mischa85 Sep 2, 2026
df91738
Guard MSVC's source-annotation header
mischa85 Sep 2, 2026
d5ddb53
Define O_BINARY where the platform has no text mode
mischa85 Sep 2, 2026
af9b3e4
Name the comparison type where the enum is not an int
mischa85 Sep 2, 2026
477d903
Answer the SAL annotation where sal.h is absent
mischa85 Sep 2, 2026
dd5823a
Include <unistd.h> for the descriptor calls off Windows
mischa85 Sep 2, 2026
2159e67
Let a native toolchain configure
mischa85 Sep 2, 2026
82ed29c
Scope the Windows-only build steps to Windows
mischa85 Sep 2, 2026
0ac74ac
Separate the language include with a forward slash
mischa85 Sep 2, 2026
1ffc9d3
Enable the Microsoft extensions the tree relies on
mischa85 Sep 2, 2026
75cf54c
Document the native build
mischa85 Sep 2, 2026
1506c52
fixup! Include <cstddef> where size_t is used
mischa85 Sep 3, 2026
9ac3071
Vendor the LZO 2.10 library and build it from thirdparty
gunnarbeutner Sep 7, 2026
afe2c28
Decompress through the vendored LZO with bounds checking
gunnarbeutner Sep 7, 2026
fef9fed
Add a saved game container that does not depend on COM
gunnarbeutner Sep 6, 2026
93613db
Keep saved games in the engine's own container instead of OLE storage
gunnarbeutner Sep 6, 2026
0349e01
Document the saved game format
gunnarbeutner Sep 6, 2026
f43aae0
Create persistent objects from a class table instead of COM
gunnarbeutner Sep 6, 2026
bb2da2a
Hold locomotors by unique pointer instead of reference count
gunnarbeutner Sep 6, 2026
0fc7c47
Drop IUnknown from the object model and locomotors
gunnarbeutner Sep 6, 2026
b75aa00
Free a save record whose class is not the tactical map
ZivDero Sep 7, 2026
b8f0253
Give persistent classes an identifier of the engine's own
gunnarbeutner Sep 7, 2026
6a11983
Return plain results from the persistence interfaces
gunnarbeutner Sep 7, 2026
24c2e85
Stop narrowing pointers through 32-bit integers
tinix0 Aug 28, 2026
3b01d74
Update FileClass code to build and work on Unix.
daliborfox Sep 7, 2026
5d52b9f
Merge branch 'pr-109' into phase-a-native-arm64
Sep 8, 2026
627a0d8
Merge branch 'pr-145' into phase-a-native-arm64
Sep 8, 2026
d1c7389
Merge branch 'pr-143' into phase-a-native-arm64
Sep 8, 2026
0b1c53a
Merge branch 'pr-110' into phase-a-native-arm64
Sep 8, 2026
717e1b8
Merge branch 'pr-113' into phase-a-native-arm64
Sep 8, 2026
a4f04ea
build: guard MSVC-only flags, import libraries and post-build steps
Sep 8, 2026
47fd5ec
spike: add a Win32 type shim to measure the native port surface
Sep 8, 2026
8ae2896
spike: extend the Win32 shim to cover COM, GDI and CRT symbols
Sep 8, 2026
96d5fba
Merge branch 'pr-140' into phase-a-native-arm64
Sep 8, 2026
e503a02
Pin the packed file and packet structures with size guards
Sep 8, 2026
dfa25a0
Keep the Windows defines off a native build
Sep 8, 2026
8217b49
State the width of the swizzle identity in a save record
Sep 8, 2026
bb37e30
Fix the ADDPLAYER payload size at its wire offset
Sep 8, 2026
d29e555
Resolve isometric sub-tile offsets on access
Sep 8, 2026
8a85546
Supply the MSVC runtime spellings a native build lacks
Sep 8, 2026
b661033
Wait and measure elapsed time through the host clock
Sep 8, 2026
34de2c1
Substitute the Win32 runtime services each subsystem uses
Sep 8, 2026
4d79345
Read and write the save container through standard file calls
Sep 8, 2026
b182f97
Scan the game directories through the portable file layer
Sep 8, 2026
8cf3911
Name the running program without the module handle
Sep 8, 2026
975ef35
Report no display modes where the host cannot enumerate them
Sep 8, 2026
35079ff
Measure free disk space through the standard filesystem library
Sep 8, 2026
818e05f
Add SDL3 as the non-Windows window and event backend
Sep 8, 2026
3b29294
Declare the Win32 window surface the native build has to supply
Sep 8, 2026
acd7254
Supply the window, message loop and cursor from SDL
Sep 8, 2026
4bac245
Load a named module under the host's own library naming
Sep 8, 2026
f3cd577
Back a surface with plain memory where there is no GDI
Sep 8, 2026
0a5fa45
Report window positions and sizes in physical pixels throughout
Sep 8, 2026
d1a407a
Record where a non-Windows build gets its window
Sep 8, 2026
35e1757
Generate the string table where no resource compiler builds it
Sep 8, 2026
7349a43
Fill every path component _splitpath was asked for
Sep 8, 2026
24b4f86
Put the debug log inside its directory rather than beside it
Sep 8, 2026
176e487
Probe for the deployment file with a portable separator
Sep 8, 2026
0bcbcab
build: add a Steam asset fetcher for the game data
Sep 8, 2026
638a9e9
fix: verify only the archives that must be their own files
Sep 8, 2026
4bd241a
fix: hold the SHA digest in 32-bit words
Sep 8, 2026
3d30e0a
fix: write a debug log record straight through
Sep 8, 2026
bc83714
fix: walk the module headers only where a PE image has them
Sep 8, 2026
e6c2584
fix: hold the isometric blit source addresses at pointer width
Sep 8, 2026
a0d0472
build: keep the Windows defines off a native VQA player build
Sep 8, 2026
2351d52
Build a host cursor from the icon the game hands over
Sep 8, 2026
caa7c30
Hold the cursor pixels at 32 bits and decode a compressed shape
Sep 8, 2026
9d13d8c
End the program when the window is asked to close
Sep 8, 2026
ce28f80
Show a pointer while the game has the mouse released
Sep 8, 2026
c0ba2ef
build: vendor RmlUi, FreeType and Dear ImGui
Sep 8, 2026
b462d1f
feat(ui): add the UI shell over RmlUi and Dear ImGui
Sep 8, 2026
8e86ad9
fix(ui): translate overlay geometry and keep its submission order
Sep 8, 2026
e8b4e04
docs: record what step 2 of the UI migration left for later
Sep 8, 2026
a129f2c
fix(ui): keep the overlay from marking the game's frame dirty
Sep 8, 2026
8bfd92f
feat(ui): add the LegacyDialogs key that selects a screen's view
Sep 8, 2026
c9d0a51
feat(ui): give a modal document an exclusive input scope
Sep 8, 2026
6f2ae56
feat(ui): show the version information through RmlUi
Sep 8, 2026
0cb7e43
docs: record step 3 of the UI migration
Sep 8, 2026
c06042c
feat(ui): resolve a document's string names from language.rc
Sep 9, 2026
4aabb8b
docs(manual): classify the UI shell's modifier reads and document Leg…
Sep 9, 2026
b919274
feat(ui): show the message and wait boxes through RmlUi
Sep 9, 2026
95aa3d3
docs: record step 4 of the UI migration
Sep 9, 2026
1fab643
fix(net): hear a local peer whose UDP port differs from ours
Sep 9, 2026
2efced7
refactor(ui): put the sound screen's behaviour behind a presenter
Sep 9, 2026
574db0b
feat(ui): show the sound controls through RmlUi
Sep 9, 2026
5820ea4
fix(ui): define the sound screen's volume step count
Sep 9, 2026
a83ff24
docs: record step 5 of the UI migration
Sep 9, 2026
331beee
feat(ui): show engine-drawn pixels through a <surface> element
Sep 9, 2026
c74e053
refactor(progress): announce loading milestones outside the draw path
Sep 9, 2026
983e6de
feat(ui): show the progress and wait box through RmlUi
Sep 9, 2026
8b38239
fix(ui): paint the wait box before the operation it stands over
Sep 9, 2026
ce4f174
docs: record step 6 of the UI migration
Sep 9, 2026
7e69cd3
refactor(ui): put the in-game options screen behind a presenter
Sep 9, 2026
f533cbe
refactor(ui): put the abort and surrender screen behind a presenter
Sep 9, 2026
32213b0
refactor(ui): put the game controls screen behind a presenter
Sep 9, 2026
cd5fcc5
docs: record how far the options family extraction has come
Sep 9, 2026
af3c329
refactor(ui): put the display mode trial behind a presenter
Sep 9, 2026
3042ace
refactor(ui): put the display options screen behind a presenter
Sep 9, 2026
70ab4ed
refactor(ui): put the main options screen behind a presenter
Sep 9, 2026
645566e
refactor(ui): put the keyboard screen behind a presenter
Sep 9, 2026
f7c48e4
feat(ui): let a view step aside for a screen it opens
Sep 9, 2026
1c755a0
feat(ui): show the in-game options and abort box through RmlUi
Sep 9, 2026
d4aaadf
feat(ui): show the game controls through RmlUi
Sep 9, 2026
091295b
feat(ui): show the main options, display and mode trial through RmlUi
Sep 9, 2026
c916aad
docs: record the options family's screens and views
Sep 9, 2026
1250baa
fix(ui): map the whole keyboard between Win32 and RmlUi
Sep 9, 2026
ed5a8e8
fix(platform): report the host's display modes
Sep 9, 2026
aa5870a
feat(ui): show the keyboard screen through RmlUi
Sep 9, 2026
f0b3aa0
docs: record the options family as migrated
Sep 9, 2026
0c9afee
refactor(ui): put the main menu behind a presenter
Sep 9, 2026
3404188
refactor(ui): put the game type and multiplayer choices behind presen…
Sep 9, 2026
cfb837e
fix(ui): let the runner return for a screen that steps aside
Sep 9, 2026
c38d754
feat(ui): show the main menu and campaign choice through RmlUi
Sep 9, 2026
b8a1e88
feat(ui): show the game type and multiplayer choices through RmlUi
Sep 9, 2026
e5607e6
fix(ui): bind each variant document to its own data model
Sep 9, 2026
12455d1
fix(ui): type the cheat words again after a modified key
Sep 9, 2026
9c09499
docs: record the main menu family as migrated
Sep 9, 2026
e8e4fd8
refactor(ui): put the save game browser behind a presenter
Sep 9, 2026
ecf4591
feat(ui): show the save game browser through RmlUi
Sep 9, 2026
751bc66
docs: record the save game browser as migrated
Sep 9, 2026
19adfdc
refactor(ui): put skirmish and map selection behind presenters
Sep 9, 2026
b4e16fc
feat(ui): show map selection and its preview through RmlUi
Sep 9, 2026
5b740d7
docs: record map selection as migrated
Sep 9, 2026
a77f403
feat(ui): show the skirmish setup screen through RmlUi
Sep 9, 2026
6d8fd18
docs: record the skirmish screen as migrated
Sep 9, 2026
7c3e527
refactor(ui): put the lobby's game list behind a presenter
Sep 9, 2026
c73a36b
refactor(ui): put the lobby's guest screen behind the same presenter
Sep 9, 2026
f15bc85
docs: record where the lobby family stands
Sep 9, 2026
8295823
fix(ui): drop two members a paste left in the lobby's user row
Sep 9, 2026
736b9a8
refactor(ui): put the lobby's host screen behind the same presenter
Sep 9, 2026
93574d4
feat(ui): show the network lobby's three screens through RmlUi
Sep 9, 2026
c14e06c
fix(ui): answer the lobby's runner and rebuild its rosters off the wi…
Sep 9, 2026
d5e8459
docs: record the network lobby family as migrated
Sep 9, 2026
5e1d858
fix(net): validate a lobby packet's strings on arrival
Sep 9, 2026
713bdeb
fix(net): bound the lobby's copies of a peer's name and serial
Sep 9, 2026
0433612
fix(net): bound what a game-options packet writes into the session
Sep 9, 2026
31a326e
fix(net): keep a peer's game name out of a format string
Sep 9, 2026
62550eb
fix(net): keep a scenario download inside its reassembly buffer
Sep 9, 2026
962393b
fix(net): check a downloaded map preview against its own extents
Sep 9, 2026
65b4559
fix(net): refuse a lobby seat outside the house and color tables
Sep 9, 2026
6d17dae
fix(net): admit a lobby peer once when it answers from two addresses
Sep 9, 2026
dec555d
fix(ui): show the lobby screen the join protocol moved to
Sep 9, 2026
0b97b97
docs: record the lobby's packet checks and its two join faults
Sep 9, 2026
1c648ab
fix(ui): let the lobby's guest leave the screen the network answered
Sep 9, 2026
effba5d
docs(manual): classify the UI shell's key sites as the source now nam…
Sep 9, 2026
842a46d
feat(ui): give the out-of-sync screen a presenter and both documents
Sep 9, 2026
40bfdd0
docs(manual): say what ScreenWidth enlarges and what it does not
Sep 9, 2026
082b242
docs: record the out-of-sync screen and the lobby's network answer
Sep 9, 2026
ba340cc
feat(ui): give the reconnect screen a presenter and its document
Sep 9, 2026
b185d2c
fix(ui): give the out-of-sync seat row a positioning context
Sep 9, 2026
3d9dadc
fix(ui): register the message list's type so the binding takes
Sep 9, 2026
5d84361
fix(ui): resample the map preview instead of blitting it
Sep 9, 2026
d6d6961
feat(ui): give the map generator a presenter and its three documents
Sep 9, 2026
414e973
docs: record the reconnect and map generator screens
Sep 9, 2026
cfaed03
feat(ui): give the lobby's message box the migrated screen
Sep 9, 2026
651b1a3
fix(ui): give the pointer back to the host while a document is shown
Sep 9, 2026
151e25d
refactor(ui): delete the legacy view behind every migrated screen
Sep 9, 2026
edaace9
refactor(ui): retire the OwnerDraw dialog system
Sep 9, 2026
65fc399
refactor(ui): drop the dialog templates from the resource script
Sep 9, 2026
441b9b2
refactor(ui): remove the LegacyDialogs key
Sep 9, 2026
bdaa3ca
docs: record the retirement of OwnerDraw
Sep 9, 2026
b6e3b78
fix(engine): hold the frame loop to 60 fps at game speed zero
Sep 9, 2026
8861e08
feat(video): put the window into real fullscreen and offer it in disp…
Sep 9, 2026
ca90b5b
fix(skirmish): start the session speed at the saved game speed
Sep 9, 2026
8cb3a54
fix(vqa): keep the frame loader's result in a type that holds VQAERR_…
Sep 9, 2026
301c940
fix(movie): size the movie filename buffer for the names the registry…
Sep 9, 2026
e2330c4
feat(ui): read the game's PCX pictures and SHP frames for documents
Sep 9, 2026
a0c30a9
feat(ui): blit the original dialog artwork in the RmlUi screens
Sep 9, 2026
96a14c0
fix(ui): center the captions the owner-draw controls centered
Sep 9, 2026
e8919f4
fix(dialog): load the dialog pictures the surface cache lost
Sep 9, 2026
9c8ff25
fix(video): build the blend masks when the video mode comes up
Sep 9, 2026
e656905
fix(dialog): index the remap font sheets through their own pointers
Sep 9, 2026
fe940be
feat(ui): draw the campaign dialog's text with the dlgsys sheets
Sep 9, 2026
477f644
feat(ui): point sample the overlay textures
Sep 9, 2026
c898cfb
feat(ui): draw every dialog's text with the dlgsys sheets
Sep 9, 2026
f2e31ad
fix(ui): give the font sheets back before RmlUi drops its render manager
Sep 9, 2026
d969826
Merge upstream main into the macOS and iOS port
Sep 9, 2026
f2c64ef
Point the command catalogue at the code that survived OwnerDraw
Sep 9, 2026
26f5c4d
Document the macOS data script, build and run steps
Sep 9, 2026
9aaa70b
Re-anchor the manual's source contracts on the ported code
Sep 9, 2026
f30fcf2
Discover the UI screens' keys and restore the menu shortcuts
Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/engine-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ jobs:
cp thirdparty/miniaudio/LICENSE artifact/OpenTS_THIRD_PARTY_LICENSES/miniaudio.txt
cp thirdparty/licenses/stb-vorbis.txt artifact/OpenTS_THIRD_PARTY_LICENSES/stb-vorbis.txt
cp thirdparty/licenses/lzo.txt artifact/OpenTS_THIRD_PARTY_LICENSES/lzo.txt
cp thirdparty/RmlUi/LICENSE.txt artifact/OpenTS_THIRD_PARTY_LICENSES/rmlui.txt
cp thirdparty/freetype/docs/FTL.TXT artifact/OpenTS_THIRD_PARTY_LICENSES/freetype.txt
cp thirdparty/licenses/zlib.txt artifact/OpenTS_THIRD_PARTY_LICENSES/zlib.txt
cp thirdparty/imgui/LICENSE.txt artifact/OpenTS_THIRD_PARTY_LICENSES/dear-imgui.txt
cat thirdparty/licenses/khronos-vulkan-notice.txt \
thirdparty/bgfx.cmake/bimg/3rdparty/astc-encoder/LICENSE.txt \
> artifact/OpenTS_THIRD_PARTY_LICENSES/khronos-vulkan.txt
Expand Down
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,15 @@
[submodule "thirdparty/miniaudio"]
path = thirdparty/miniaudio
url = https://github.com/mackron/miniaudio.git
[submodule "thirdparty/SDL"]
path = thirdparty/SDL
url = https://github.com/libsdl-org/SDL.git
[submodule "thirdparty/RmlUi"]
path = thirdparty/RmlUi
url = https://github.com/mikke89/RmlUi.git
[submodule "thirdparty/freetype"]
path = thirdparty/freetype
url = https://github.com/freetype/freetype.git
[submodule "thirdparty/imgui"]
path = thirdparty/imgui
url = https://github.com/ocornut/imgui.git
20 changes: 18 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ option(OPENTS_OFFICIAL_BUILD "Build as an official release of the declared versi

option(OPENTS_EXPERIMENTAL_CLANG_CL "Build with clang-cl using the MSVC ABI" OFF)
option(OPENTS_EXPERIMENTAL_X64 "Configure an unsupported 64-bit Windows build" OFF)
option(OPENTS_EXPERIMENTAL_NATIVE "Configure a native build for the host platform" OFF)

if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
Expand All @@ -34,14 +35,24 @@ elseif(MSVC)
"writes are not interchangeable with a supported 32-bit build's, and nothing in "
"the version stamp distinguishes them.")
endif()
elseif(OPENTS_EXPERIMENTAL_NATIVE)
message(STATUS "OpenTS: configuring an unsupported native build for the host platform.")

# The tree declares imports and exports with __declspec throughout, which clang only
# accepts with the Microsoft extensions enabled.
add_compile_options(-fms-extensions)
else()
message(FATAL_ERROR
"OpenTS requires the Visual Studio 2022 MSVC toolchain. "
"For the unsupported clang-cl experiment, configure with "
"-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake.")
"-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake. "
"For the unsupported native build, configure with "
"-DOPENTS_EXPERIMENTAL_NATIVE=ON.")
endif()

enable_language(RC)
if(WIN32)
enable_language(RC)
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand Down Expand Up @@ -83,6 +94,11 @@ add_custom_target(OpenTSBuildStamp ALL
)

add_subdirectory(thirdparty)

if(NOT WIN32)
add_subdirectory(platform)
endif()

add_subdirectory(code)
add_subdirectory(code/language)

Expand Down
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ OpenTS is a community-led, open-source reconstruction of *Command & Conquer:
Tiberian Sun*. Instead of patching or extending the retail executable, it
rebuilds the engine as a standalone program.

This fork carries a native Apple Silicon macOS build of that engine. See
[Running on macOS](#running-on-macos) for the data script, the build and how to
start it. Everything below describes OpenTS itself and applies here too.

OpenTS gives equal weight to two goals: maintaining a playable engine and
providing a capable platform for modding and engine development. Work on one
goal should not come at the expense of the other.
Expand Down Expand Up @@ -70,6 +74,59 @@ UTF-8 code page, which needs Windows 10 version 1903 or newer. Older Windows
keeps its own code page, so game text still shows, but a path or file name
holding a character that code page lacks may fail.

## Running on macOS

This fork builds and runs the engine natively on Apple Silicon. The game data
still comes from a copy of Tiberian Sun you own.

Install the tools:

```bash
brew install cmake ninja
brew install --cask steamcmd
```

Fetch the game data from your own Steam account:

```bash
./scripts/get-assets.sh <your_steam_username>
```

The script downloads app 2229880 into `Run/`, skips the Windows executables the
engine replaces, and checks that the archives startup needs actually arrived.
Steam Guard prompts for a code on first login. **Quit the Steam desktop client
first**: steamcmd shares its data directory, and a running client holds a lock
that makes steamcmd hang after "Verifying installation..." with no error. Set
`OPENTS_GAME_DIR` to put the data somewhere other than `Run/`.

Build the engine:

```bash
git submodule update --init --recursive
cmake -S . -B build/native -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DOPENTS_EXPERIMENTAL_NATIVE=ON
cmake --build build/native --target OpenTS
```

Build the `OpenTS` target rather than everything: the C++ test harnesses pass
MSVC-only flags and link `kernel32`, so they do not configure here.

Run it:

```bash
build/native/bin/Game -DATADIR=Run -USERDIR=build/native/user
```

`-USERDIR` is where saves, `SUN.INI` and logs are written, so pointing it at a
scratch directory leaves an existing install untouched. Full screen is a
setting in the display options screen.

Windows supplies the window, message loop and cursor itself; every other
platform gets them from `platform/win32compat`, which serves the Win32 surface
the engine is written against out of [SDL](https://github.com/libsdl-org/SDL).
[Building OpenTS](docs/BUILDING.md) covers the build in full.

## Documentation

The [OpenTS manual](https://opents-developers.github.io/OpenTS/) documents
Expand Down
4 changes: 4 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ remains under its own license and copyright notices.
| [miniaudio](https://github.com/mackron/miniaudio) | Audio device output, resampling, and WAV, FLAC, and MP3 decoding | MIT-0 or Unlicense |
| [stb_vorbis](https://github.com/nothings/stb) | Ogg Vorbis decoding, bundled with miniaudio | MIT or Unlicense |
| [LZO](https://www.oberhumer.com/opensource/lzo/) | LZO1X compression for maps, saves, and network blocks | GPL-2.0-or-later |
| [RmlUi](https://github.com/mikke89/RmlUi) | User interface documents, styling, and layout | MIT |
| [FreeType](https://freetype.org/) | Glyph rasterization for the RmlUi font engine | FTL |
| [zlib](https://zlib.net/) | Compressed font stream support, bundled with FreeType | zlib |
| [Dear ImGui](https://github.com/ocornut/imgui) | Developer tooling on the UI shell | MIT |

The source checkout keeps the license texts under `thirdparty/`. Binary
packages reproduce the license texts for the components used by OpenTS under
Expand Down
183 changes: 183 additions & 0 deletions cmake/StringTable.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Builds the portable string table from the Windows resource script.
#
# The resource script is the only place the strings live, and only the RC compiler turns it
# into a module resource. A host without an RC compiler still needs the same strings, so this
# script reads the STRINGTABLE blocks out of the script, resolves each symbolic identifier
# through the header the script itself includes, and writes a flat data file the game reads
# at runtime. The Windows build is untouched and keeps using the compiled resource.
#
# Run with:
# cmake -DRC_FILE=<language.rc> -DHEADER_FILE=<language.h> -DOUTPUT=<file> -P StringTable.cmake
#
# NAME_TABLE names a second output, the table that turns a string's symbolic name back into
# its identifier. UI documents reference a string by name, and this keeps the resource script
# the only place a name and a number are paired.
#
# Encoding: the resource script carries "#pragma code_page(65001)", so its bytes are already
# UTF-8 and are copied through unchanged. The data file is UTF-8 for the same reason.

if(NOT DEFINED RC_FILE OR NOT DEFINED HEADER_FILE)
message(FATAL_ERROR "StringTable.cmake needs RC_FILE and HEADER_FILE")
endif()

if(NOT DEFINED OUTPUT AND NOT DEFINED NAME_TABLE)
message(FATAL_ERROR "StringTable.cmake needs OUTPUT, NAME_TABLE, or both")
endif()

# A semicolon separates list elements everywhere in this language, and at least one string
# contains one. It is carried as a marker through every step that treats text as a list and
# put back only when the record is written. No string contains "@", which is what makes the
# marker safe to pick.
set(SEMICOLON_MARK "@OPENTS_SEMI@")
set(BACKSLASH_MARK "@OPENTS_BSLASH@")

# ---------------------------------------------------------------------------------------------
# The identifiers. The resource script names its strings symbolically and the header gives each
# name its number.
# ---------------------------------------------------------------------------------------------

file(READ "${HEADER_FILE}" HEADER_TEXT)
string(REGEX MATCHALL "#define[ \t]+[A-Za-z_][A-Za-z0-9_]*[ \t]+[0-9]+" HEADER_DEFINES "${HEADER_TEXT}")

foreach(define IN LISTS HEADER_DEFINES)
string(REGEX MATCH "#define[ \t]+([A-Za-z_][A-Za-z0-9_]*)[ \t]+([0-9]+)" ignored "${define}")
set("ID_${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}")
endforeach()

list(LENGTH HEADER_DEFINES DEFINE_COUNT)

# ---------------------------------------------------------------------------------------------
# The strings. Only the STRINGTABLE blocks are read; the dialog templates in the same script
# hold quoted text that is not a string resource.
# ---------------------------------------------------------------------------------------------

file(READ "${RC_FILE}" RC_TEXT)
string(REPLACE ";" "${SEMICOLON_MARK}" RC_TEXT "${RC_TEXT}")
string(REPLACE "\r" "" RC_TEXT "${RC_TEXT}")
string(REPLACE "\n" ";" RC_LINES "${RC_TEXT}")

set(IN_TABLE FALSE)
set(IN_BODY FALSE)
set(PENDING_NAME "")
set(RECORD_COUNT 0)
set(RECORDS "")
set(NAMES "")
set(MISSING "")

foreach(line IN LISTS RC_LINES)
if(NOT IN_TABLE)
if(line MATCHES "^STRINGTABLE([ \t]|$)")
set(IN_TABLE TRUE)
endif()
continue()
endif()

if(NOT IN_BODY)
if(line MATCHES "^BEGIN[ \t]*$")
set(IN_BODY TRUE)
endif()
continue()
endif()

if(line MATCHES "^END[ \t]*$")
set(IN_TABLE FALSE)
set(IN_BODY FALSE)
set(PENDING_NAME "")
continue()
endif()

set(name "")
set(raw "")
set(have_string FALSE)

if(line MATCHES "^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]+\"(.*)\"[ \t]*$")
set(name "${CMAKE_MATCH_1}")
set(raw "${CMAKE_MATCH_2}")
set(have_string TRUE)
elseif(line MATCHES "^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*$")
set(PENDING_NAME "${CMAKE_MATCH_1}")
continue()
elseif(line MATCHES "^[ \t]*\"(.*)\"[ \t]*$")
set(name "${PENDING_NAME}")
set(raw "${CMAKE_MATCH_1}")
set(have_string TRUE)
set(PENDING_NAME "")
elseif(line MATCHES "^[ \t]*$")
continue()
else()
message(FATAL_ERROR "StringTable.cmake did not understand a STRINGTABLE line: ${line}")
endif()

if(NOT have_string)
continue()
endif()

if(name STREQUAL "")
message(FATAL_ERROR "StringTable.cmake found a string with no identifier: ${line}")
endif()

if(NOT DEFINED "ID_${name}")
list(APPEND MISSING "${name}")
continue()
endif()

# The escapes the resource compiler understands, in the order that keeps an escaped
# backslash from being read twice.
string(REPLACE "\\\\" "${BACKSLASH_MARK}" raw "${raw}")
string(REPLACE "\\n" "\n" raw "${raw}")
string(REPLACE "\\r" "\r" raw "${raw}")
string(REPLACE "\\t" "\t" raw "${raw}")
string(REPLACE "\\\"" "\"" raw "${raw}")
string(REPLACE "\"\"" "\"" raw "${raw}")
string(REPLACE "${BACKSLASH_MARK}" "\\" raw "${raw}")
string(REPLACE "${SEMICOLON_MARK}" ";" raw "${raw}")

string(LENGTH "${raw}" length)

# Length-prefixed, so a string that contains a newline needs no escaping of its own.
string(APPEND RECORDS "${ID_${name}} ${length}\n${raw}\n")
string(APPEND NAMES "OPENTS_STRING_NAME(\"${name}\", ${ID_${name}})\n")
math(EXPR RECORD_COUNT "${RECORD_COUNT} + 1")
endforeach()

if(IN_TABLE)
message(FATAL_ERROR "StringTable.cmake reached the end of ${RC_FILE} inside a STRINGTABLE")
endif()

if(MISSING)
list(REMOVE_DUPLICATES MISSING)
string(REPLACE ";" ", " MISSING_TEXT "${MISSING}")
message(FATAL_ERROR "StringTable.cmake found no identifier in ${HEADER_FILE} for: ${MISSING_TEXT}")
endif()

if(RECORD_COUNT EQUAL 0)
message(FATAL_ERROR "StringTable.cmake found no strings in ${RC_FILE}")
endif()

if(DEFINED OUTPUT)
get_filename_component(OUTPUT_DIR "${OUTPUT}" DIRECTORY)
if(OUTPUT_DIR)
file(MAKE_DIRECTORY "${OUTPUT_DIR}")
endif()

file(WRITE "${OUTPUT}" "OPENTS-STRINGS 1\n${RECORD_COUNT}\n${RECORDS}")

message(STATUS "String table: ${RECORD_COUNT} strings from ${DEFINE_COUNT} identifiers -> ${OUTPUT}")
endif()

# The name table is a list of invocations rather than a declaration, so the including file
# decides what a pair becomes and the header carries no storage of its own.
if(DEFINED NAME_TABLE)
get_filename_component(NAME_TABLE_DIR "${NAME_TABLE}" DIRECTORY)
if(NAME_TABLE_DIR)
file(MAKE_DIRECTORY "${NAME_TABLE_DIR}")
endif()

file(WRITE "${NAME_TABLE}"
"// Generated from language.rc by cmake/StringTable.cmake. Do not edit.\n"
"// Each line pairs a string resource's symbolic name with its identifier.\n"
"\n"
"${NAMES}")

message(STATUS "String names: ${RECORD_COUNT} names -> ${NAME_TABLE}")
endif()
Loading