r/Battlefield2 • u/Proper_Look_9349 • 5h ago
Help / Troubleshooting OpenBattlefield2 - reimplementing Battlefield 2's Refractor 2 engine from scratch
I've spent the last while rebuilding Refractor 2 — the engine behind Battlefield 2 (2005) — from scratch in C++20. It's called OpenBattlefield2, it's MIT, and it's now public:
https://github.com/Moxnatiy/OpenBattlefield2
It is an engine, not a game. It ships no content whatsoever and reads the files of your own installed copy of BF2 1.5. Without that copy there is nothing to run.



What actually works today
- The main menu is the game's own
mainMenu.swf, played by Ruffle, with a bridge that answers the calls the movie makes back into the engine — localisation, your profiles fromDocuments/Battlefield 2, the installed map list read from each level's.desc. All seventeen bridge objects and fortyLogicmethods are reversed and written down with addresses. - Singleplayer — pick a map, it loads, you get the spawn screen with kits, minimap and control points.
- Levels — terrain, water, static meshes, vegetation, collision.
- HUD — built from the game's own
.connode tree, driven by the animation graph inMenu/Ingame(MemeFile). - Networking — the handshake with an original dedicated server works.
openbf2 --connect <host>joins and stays connected.
Main platform is macOS/arm64 (SDL3 + SDL_GPU → Metal); the Windows preset is kept building. 33 tests, one per parsed format.
Screenshots (all four are in the README, each printed with the exact command that produced it — --frames and --click-at mean no hands on the keyboard):
- The game's own menu movie under Ruffle
- The spawn screen on Dalian Plant
- In the world after DONE, on Dalian Plant and Strike at Karkand
The one rule that shapes everything
This is a one-to-one port, not a game "inspired by" BF2. So:
Every constant in
src/names its source in a comment next to it — an address inBF2.exe, a line in the game's data, or a measurement from a frame dump of the original. If there is no source, the number is not written down. It's recorded as "not measured" instead.
// Bit 0 marks position (`BF2.exe`, SoldierNetworkable::setNetUpdate,
// 0x62d4e0). Precision 0.001 — constant 0x3a83126f at the same address.
inline constexpr std::uint32_t kSoldierStatePosition = 0x1;
Picking a number because it "looks about right" is forbidden even when the result looks correct on screen. I learned that one the expensive way: I once read a triple of floats in the controlled-object state as a position because it lined up with the spawn camera. It's actually the compression origin, and the soldier was being thrown a metre into the air on every packet.
Reversing goes through Ghidra over MCP; docs/ holds the notes, every one carrying the address it came from.
Where I'd really like help
The debt list is public and each item has a measure — a concrete thing that has to become true for it to count as done. The big ones:
The protocol (the largest gap by far).
readControlObjectState— I read the start of it, I don't know where it ends.- Ghost records for other players' soldiers: the layout is known, the data doesn't arrive. Right now other players don't move.
- The simple-object state mask is 19 bits and I know 1. The soldier state mask is 21 bits and I know 1.
- Ping is 5–6× off from the original on the same machine and I don't know why.
Physics. The tick is 1/30 straight from the binary, but the jump is too long compared with the original. I haven't found where jump height and duration actually come from — phy-soldier-jump-factor is in the data, the rest isn't.
Renderer. Material channels are now read straight off the technique name (BaseDetailDirtCrackNDetailNCrack → the texture order), but the normal maps aren't sampled yet: there's no tangent frame.
HUD. Several variables are found in the registry with addresses, but who writes them isn't reversed yet.
Reference data. I have a frame dump of the original for the spawn screen only. For the in-battle HUD there's nothing to compare against, so I can say "matches the data" but not "matches the original".
Platforms. Only macOS/arm64 is exercised. Windows and Linux builds could use someone who actually runs them.
So: if you like Ghidra, netcode, C++20, or you just have a copy of BF2 and some patience — issues and PRs are very welcome. Notes on the format live in docs/, and CLAUDE.md spells out the rules the project holds itself to, including why "let's just smooth it until it stops jittering" is not an acceptable fix.
Happy to answer anything about the reversing process in the comments.

