Notes on the game
What's been worked out about Forza Horizon 2 on PC while building the mods so far. Addresses are offsets from the game's base address (RVAs), since wait_for_game() gives you the base. Everything here was found in the one build everyone plays; treat it as a starting point and check before you patch.
The build and Azimuth
Everyone plays the Xbox One release forza_durango_release_final.exe, built on 24 November 2015. Its PE header has timestamp 0x565501ED and image size 0x03A18000, which is how the platform recognises it in memory. Azimuth doesn't start it as a process. It maps the exe into azimuth.exe itself, at the preferred base 0x140000000, when the player presses Play, and translates the Xbox APIs underneath it. So your mod, the game and Azimuth all share one process.
Azimuth's settings are in data\azimuth.ini, a UTF-16 file. The [launcher] section holds the player's name (player_name) and graphics settings, and [title-1a96545b] folder= names the game folder (usually FH2). That folder's Mount subfolder is what the game sees as game:\. 1A96545B is FH2's title ID, and Fast & Furious is 5A9771A4. Saves are under data\titles\1A96545B\saves\player-1\User_...\revisions\<n>\data\. Azimuth checks those files' hashes, and Extra Options can turn that check off, but mods should never need to touch saves.
Files and Extra Options
The game reads its data from game:\media. Most of it is in zips, whose directories are listed in media\zipmanifest.xml; the game relies on those offsets, so don't edit the zips. Extra Options adds an override folder: a file at media\X.zip\path\file can be replaced by mediaoverride\X\path\file, and a loose file at media\path\file by mediaoverride\path\file. The priority is mediaoverride, then mediapc, then the original. Extra Options loads through winmm.dll, and its settings are in FH2_ExtraOptions.ini next to azimuth.exe. Gamerpic.png in the same folder is the player's avatar when CustomGamerpic=1.
The override folder
The game finds every file through its file system's resolver, which a vtable slot at game+0x2E6EF28 points to (game+0x11F49B0). The file system is a list of volumes, each mounted at a path with a priority; the game's own zips and folders are volumes under game:\media\. The game also has a folder volume class (constructor game+0x11E05A0, vtable game+0x2EAB918, its RTTI locator just before it) that serves the files in one folder. Mounting two of those, for game:\mediaoverride\ and game:\mediapc\, over game:\media\ at priorities above everything else (0x7FFFFFFE and 0x7FFFFFFD) is what makes mediaoverride work: the file system's vtable +0x1E8 mounts a volume (no hash-manifest check when its last three arguments are 0) and +0x1F8 sorts the volumes again. The folder volume's "does this file exist" method (+0xE8) is handed archive selectors such as <*>0 too, which aren't file names, so they have to be answered "no".
Extra Options does this from its winmm.dll. Azimuth 1.0.1 runs without Extra Options for now, so from 2.7.3 the platform does the same when Extra Options' winmm.dll isn't loaded: it swaps the resolver pointer as soon as Azimuth maps the game (before the game's code is executable) and mounts both folders on the first lookup. platform.log says so ("media overrides: ..."). It checks the game's bytes against the exe on disk first and leaves everything alone if they differ. When Extra Options is loaded the platform doesn't touch the resolver: Extra Options refuses all of its patches if any place it patches was changed first, and its EnableMediaOverrides setting decides. Azimuth 1.0.1's log lists the folder under "modifications" (the game folder's Mount\mediaoverride (loose files)); that's only a note of what's in the game folder, not Azimuth reading it.
Extra Options also patches game code: around 84 byte patches and 47 hooks, listed with their RVAs in patches.json in its repo. It applies them all at once, while Azimuth loads its TLS helper just after mapping the game, and only after checking that every one of those places looks as it expects. If any doesn't, it applies none of them. That's why the platform's hooks wait for wait_for_code, and refuse game code that no longer matches the exe on disk.
Text
See string-tables.md. The title screen's "PRESS (A) TO START" is two strings in Main.str: 0xB197 (PRESS) and 0x18F0 (TO START). TO START is its own text box, centred vertically on the button, so the line can grow sideways only about as far as the screen edge.
The game database
media\Stripped\gamedbRC.slt is a plain SQLite database, about 6 MB, with around 290 tables. The game opens it as game:\media\Stripped\gamedbRC.slt through its own file system, the one mediaoverride is mounted over, and reads it at startup. Tables worth knowing: Data_Car (every car, with DisplayName, BaseCost, PerformanceIndex, 0-60 times and handling values), Data_Engine, Events, EventCars, CarMeets, AIPlayers and DriverModels. Text columns hold string-table references like _&2149394699 rather than text, so names and descriptions are changed through the string tables. The exe also mentions media\db\gamedb.slt, which isn't in the shipped game.
Drivatars
The built-in Drivatars are in media\DrivatarGlobalCache\DefaultCache.bin. The file starts with u32 4 (version) and u32 125 (count), followed by 125 records of 0x2658 bytes each. They use only 25 different gamertags, five records each. A record's gamertag is UTF-16 in its first 32 bytes (15 characters and a NUL), and the Drivatar's id, 1 to 25 and the same for all five copies, is the u32 at +0x2650. The game reads the file once at startup (code at 0xA2D0E0) into a manager object, with the record count at +0x4D58 and a std::vector of records at +0x4D78. WheresMyBuddy replaces it through mediaoverride.
A championship copies the Drivatars it picked into the save, as SaveStates::ChampionshipDrivatarsState. The Storm Island championships appear to use the same class (saved as IslandChampionshipDrivatarsState). Its vtable is at 0x2D44F20, and slot 2 is Serialize(archive) at 0xA68870. The record count is at +0x1C of the object and the std::vector of records at +0x20. Records saved there are byte-for-byte copies of DefaultCache.bin records, id included. WheresMyBuddy swaps that vtable slot to rename them after loading.
Car meets and some road-trip traffic aren't Drivatars. They're named from AIPlayers.str, the 25 gamertags plus "HRZN Ben", through the game database's AIPlayers table (SELECT Name, DriverModelId FROM AIPlayers WHERE Id=%d).
Radio
FMOD Studio 1.x is linked into the game, and its low-level API is exported from the exe by name, for example FMOD::System::createSound at 0x1997740, FMOD::Sound::getSubSound at 0x199F510 and FMOD::Sound::getUserData at 0x199FBE0. Look them up through the export table rather than hard-coding them. The radio is the RadioStreamFmod class (vtable 0x2E76220), which plays songs from FMOD sound banks. FH2 Universal Radio redirects its createSound call at 0x1018A13 and its getSubSound call at 0x10221F2 to hand the game a stream of its own. Song samples are one of four classes, with vtables MusicSample 0x2E74EF8, DJSample 0x2E76570, IdentSample 0x2E76590 and RadioSample 0x2E74ED8. Each has a pointer to its properties at +0x08: sound name at +0x00, event name +0x20, display name +0x40, artist +0x60, length in ms +0x8C, markers +0x90. The radio station list is in RadioInfo_EN.xml.
Menus
The front end is XAML pages in media\UI.zip (a plain zip), drawn by AVUI, a C++ take on WPF with Binding, styles, templates and converters. Pages are under Scenes/Anthem/, shared styles under Resources/Anthem/. Each page is a C++ class too, which binds the XAML's PART_ elements and supplies the properties its bindings ask for. Which page opens when is decided by the state machine in media\stateflow.zip (ui_screens.xml and friends). Pause > Settings is A_PauseOptions.xaml, class PauseOptionsMenu, a 5 x 2 grid of tiles. Extra Options adds rows of its own to A_HUDOptions.xaml and A_VideoOptions.xaml, recognising them by their x:Class.
What the platform's Mods page uses, all in the 24 November 2015 build:
-
Properties. Bindings look properties up by name with
AVUI::Type::FindProperty(type, name)at0x157FF60. It walks the list attype+0x40(nodes{descriptor, next}), comparing the descriptor'schar*name at+0x40with the AVUI string's UTF-16 text (at+0x28), then tries the base type attype+0x80. A descriptor has its getter at+0x28, called(out, desc, object), which boxes the value into*out(ints with0x4702D0, bools with0x336BA0); its setter at+0x30,(desc, object, value), with an int value atvalue+0x28; and its value type at+0x48(bool is0x15C9550(0)). The HUD page'sEnableDrivatarLiveries(0xD58660returns it) is a typical On/Off one. A page tells its bindings a property changed with0xCF9C60(page, desc). Float properties, which the game'sSliderbinds itsValueto (the audio volumes, the controller dead zones), have the same layout with a float atvalue+0x28;0xED6E90returns one, and0x3F5990boxes a float. The slider's look isA_Style_Slider_OptionsinResources/Anthem/Shared_OptionsScreens.xaml, whose number is aNumUnitConverter(FractionDigits,ShowUnit,Scale;ConverterParameter=fracpercentageshows 0..1 as 0..100). Extra Options' drift camera sliders on the HUD page are built the same way. -
The Settings page. It's made once while the game boots and reused.
0xDC8A60wires its tiles with0xD9FC10(page, "PART_Button_X", &pmf), wherepmfis{handler, 0}and a name that isn't on the page is skipped. Its controller grid is a fixed 5 x 2 (set at0xDC873E): the navigation manager is atpage+0x410, with the column count at+0x9C, and the tile grid is rebuilt from that each time the page is wired. A tile's handler (HUD's is0xDC6C40) fires a state-machine event. -
Opening a page. A page is made from a scene record,
{id, name, L"Anthem\\A_X" (std::wstring at +0x28), factory at +0x48}. The HUD page's is built by0xB3A3E0(id0x49). Opening it builds the record twice, once to look it up and once inside the factory (0xB0B990), and the factory's copy goes into the new page at+0x228; that's the one its XAML is loaded from. Changing its path makes the same page class load a different file, and new files inmediaoverride\UI\are found like replaced ones. -
Message boxes.
0xD09D00(page, &title, &message, items)showsA_MessageBox.xamloverpageand returns it. Title and message are references toCLocStrings (vtable0x2EB0E30, text from the virtual at+0x38). The items are a collection made by0xC65230, holding items made by0xC65AB0(&item, &id, &label)(id at+0x68) and added through the collection's vtable+0x40(collection, index, item). The popup takes the collection's interface at+0x38.0xDD8820(popup, 6)sets its help bar.0xDD86F0(popup, 2, std::function<void()>, true)sets what happens when an item is chosen; the function is the MSVC layout ({space[0x18], impl*}) and taken by value.0xE01A60(popup)is the chosen item. -
The MODS tab's pages. Every page of the MODS tab, and every mod's page, is the HUD options page loading another file: the Marketplace tiles' Car Packs handler (
0xDBD720) fires thecar_packsevent, which the platform'spause.xmlsends to a node showing the HUD page, and the scene record is pointed atA_ModPage_<nn>. A page leaves only byback, topause_menu.enterback. Installed mods holds every view at once (the list, each mod's settings, each mod's pages), switched withFH2Mod_View_<k>properties. -
The Achievements tiles.
PART_Button_Achievements(handler0xDBD610, wired at0xDC7E5D) and the Storm Island tab'sPART_Button_Panama_Achievements(0xDBF150, which first checks the tile has the focus with0xDC17D0) both call0xCB47B0(page, &out, 0xFE), a UI command that opened the console's achievements app, at0xDBD635and0xDBF185. A page on that tile hooks those two calls. -
Tile text. The pause menu's icon tiles use
A_ControlTemplate_IconTile(Shared_TiledMenus.xaml): the icon, the title, and a notification badge fromlocal:BindingProxy.A(bound to the page'sNotificationBadge...properties). A tile'sTagtakes a binding too (the Marketplace tab's badge is one), so a copy of that template with one moreFZTextBlockbound to the tile'sTagshows a line of its own; the pause menu's resources haveA_Style_FZTextBlock_TileClubMemberCount, the club tile's orange count, before the menu items. Bindings on the pause menu page find the platform's properties through the sameFindPropertyhook as the options pages', from inside a template too (RelativeSource FindAncestor, AncestorType={x:Type Page}, as the game's own templates do). AColorproperty is typed and boxed as the game'sTeamColor(its descriptor at0x140C09BD0): type0x1415C8D80(0), value boxed by0x140BEA4E0(out, const float rgba[4]), red, green, blue and alpha from 0 to 1 (a packed0xAARRGGBBis unpacked that way at0x14195B21A). The wristbands' colours are inmedia\GameTunableSettings.zip'sPlayerXPLevels.xml, as signed ARGB ints:ColourNumfor the band,TextColourfor text on it. -
The pad in the menus. Pages get the pad as keys, WPF's numbering, from a table at
0x142254390(game control pressed, released, key): A is Space (18), B is B (45), X is X (67), Y is Y (68), the d-pad the arrows (23-26) and the number pad's 8/2/4/6 together, LB PageUp (19) and RB PageDown (20). The table also has the shifts (116, 117) and the controls (118, 119), which aren't the bumpers. On the HUD options page they reach the button handler at vtable+0x578(0xCEC3D0).
Finding more
Most of this came from the exe's own strings and RTTI. The exe keeps MSVC type names like .?AVChampionshipDrivatarsState@SaveStates@@, which lead to vtables. SQL in the exe shows which tables of the game database it reads, and how. When you find something, a pull request adding it here helps the next person.