Flight Simulator X Service Pack 2 available–and how to work around common problems

We shipped Service Pack 2 for Flight Simulator X on Friday. (Get the downloads from fsinsider.com; see Phil Taylor’s blog entries for more details.) Service Pack 2 contains the same set of fixes as in Acceleration, without Acceleration‘s new aircraft or missions.

We’re seeing a common problem come up: A message from the installer: “Microsoft Flight Simulator X Service Pack 2 requires the English version of Flight Simulator X.”

So far, the immediate cause is the same: missing data in the registry. During testing, the root cause was also the same: Copying the installed files from another partition or machine rather than using the installer. That’s not officially supported, of course, but given the size of Flight Simulator, it’s fairly common to want to move the installed files (to a bigger partition, for example) without reinstalling.

Service Pack 2 requires the registry data that the original RTM and SP1 installers write. (It uses the data in the registry to find the installation directory and the language.dll file. It checks the version information in language.dll to make sure it has the same language as the SP2 installer; otherwise, you’d end up with a broken mix of localized content.)

Unfortunately, the original RTM installer doesn’t rewrite those registry values when you do a repair, so what works is to uninstall and reinstall RTM, then SP1, then SP2.

That said, here’s a shortcut. Note that it involves editing the registry, which can be hazardous to your computer’s health. If you don’t feel comfortable doing this, uninstall and reinstall is a safe choice.

  1. Start RegEdit.
  2. On 32-bit operating systems, open the HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ microsoft games\ flight simulator\ 10.0 key in the left pane.
    On 64-bit operating systems, open the HKEY_LOCAL_MACHINE\ SOFTWARE\ Wow6432Node\ Microsoft\ Microsoft Games\ Flight Simulator\ 10.0 key in the left pane.
  3. Choose Edit|New|String Value.
  4. Replace New Value #1 by typing SetupPath.
  5. Double-click SetupPath and in the Value data box, type the full directory path where you installed or copied Flight Simulator X.

The SP2 installer should then be able to find language.dll and verify that it’s the right language for the version of SP2 you’re trying to install.

That’s the only common problem we’ve seen — as of today, anyway. I’ll update this blog entry with additional items if/when they come up.

Update #1, 17-Dec-07: Added registry key for x64 OSes.

Flight Simulator X: Acceleration releases to manufacturing

My second project at ACES Studio, the Flight Simulator X: Acceleration expansion pack, has gone gold. Well, 12.5 percent of it has: We sent the English release to manufacturing but we have seven more languages we’re shipping over the next couple of weeks. So while the extra-long days are a bit more normal now, the weekends are still fairly busy, coordinating builds and releases with our international folks in Dublin and Tokyo.

Acceleration was a “small” release, but only by the standards of a 14GB, two-DVD Flight Simulator X. Here are some mildly interesting statistics:

Number of DVDs 1
Number of files 7121
Compressed byte count 2,567,158,638
Uncompressed byte count 4,014,683,859
WiX version used to build installer 3.0.3304.0
Visual C++ version used to build setup UI and custom actions 8.0
Number of new custom actions 5 (plus rollback and scheduling CAs)
Number of new custom tables for data-driven deferred custom actions 2
Number of WiX custom actions consumed 2
Percentage of WiX custom action enhanced for Acceleration where enhancements were contributed back to WiX 100

Seven more languages to go!

Semi-custom actions

I was working on my current double-secret project at work and got a requirement that I knew would need a custom action. The requirement is to add entries to a configuration file without overwriting existing entries.

The configuration file (scenery.cfg, for FlightSim fans) is actually in .ini format. Naturally, I looked into using the standard IniFile table but it wasn’t sufficient for my needs. Scenery.cfg has entries that point to scenery data; each entry is numbered to control the priority of the scenery in rendering.

So, for example, given the following tail entries:

[Area.114]
Title=Propeller Objects
Local=Scenery\Props
Layer=114
Active=TRUE
Required=TRUE

[Area.115]
Title=Addon Scenery
Local=Addon Scenery
Layer=115
Active=TRUE
Required=FALSE

New entries would start out like this:

[Area.116]
Title=Test1
Local=MyScenery\Test1
Layer=116
Active=TRUE
Required=FALSE

Unfortunately, scenery.cfg is different among the various editions and languages of FlightSim, so the entry numbers aren’t static. (Also, users can add third-party scenery and we can’t overwrite those entries.) Though the IniFile table supports formatted strings, adding scenery entries would require string manipulation and math operators. While those might make for interesting MSI features, they don’t exist today.

So I started planning to write a deferred custom action and its matching rollback CA. Neither is too difficult — Win32 still supports API functions to add and remove .ini file entries. (Amusingly, the SDK doc groups them together with Registry Functions.)

Still, I’m a strong believer in not doing more work than necessary. (I prefer to think of it as efficiency rather than laziness.) MSI already handles the deferred and rollback aspects of modifying .ini files via the IniFile table and WriteIniValues and RemoveIniValues standard actions. If I could plug in dynamic data, I’d be able to write a simpler immediate CA rather than two more complicated deferred/rollback CAs.

Efficiency through transience

As you probably suspect by now, MSI has just such support. Immediate CAs can add temporary tables, rows, and columns to the active database. By adding temporary rows to standard tables, immediate CAs can determine at install time what data should be installed.

It’s important to note that temporary rows are…well…temporary. There’s not a whole lot of doc in the MSI SDK about temporary rows in general but one mention points out their temporary nature:

A custom action can be used to add rows to the Registry table during an installation, uninstallation, or repair transaction. These rows do not persist in the Registry table and the information is only available during the current transaction. The custom action must therefore be run in every installation, uninstallation, or repair transaction that requires the information in these additional rows. The custom action must come before the RemoveRegistryValues and WriteRegistryValues actions in the action sequence.

So that’s the recipe: An immediate CA runs, reads whatever data it needs from a custom table, using component states to decide what data should be written to temporary rows, and writes the rows (to the Registry or IniFile tables, for example). No deferred or rollback CAs are required, because MSI handles that.

WiX’s wcautil library makes this kind of CA easy to write. The WcaOpenExecuteView function executes a query and returns a view handle. WcaFetchRecord fetches the next record from the view. The WcaGetRecordInteger, WcaGetRecordString, and WcaGetRecordFormattedString functions get column values from the record. The best is WcaAddTempRecord which does all the work of adding a temporary row. It’s a fairly straightforward function but as no other CA in WiX uses it, here’s an example:

hr = WcaAddTempRecord(&hIniTableView, &hIniColumns,
// the table
L”IniFile”,
// the column number of the key we want “uniquified”
1,
// the number of columns we’re adding
8,
// primary key
L”AcesSceneryConfig”,
// FileName — always scenery.cfg
L”scenery.cfg”,
// DirProperty — set by AppSearch in extension .wixlib
L”ACESSCENERYCFGDIR”,
// Section — [Area.<n>]
pwzArea,
// Key
L”Title”,
// Value
wzTitle,
// Action
msidbIniFileActionAddLine,
// Component_
wzComponent);

The first two parameters are pointers to MSIHANDLEs. You initialize them to NULL and the first time you call WcaAddTempRecord, it initializes them. The first handle is to a view on the table you’re inserting into. The second is a handle to information about the columns in the table. By passing them in and out as arguments, you can let WcaAddTempRecord initialize them once then re-use the same handles. WcaAddTempRecord doesn’t know when you’re done adding records, so you’re responsible for calling MsiCloseHandle on both handles.

The fourth parameter is named uiUniquifyColumn and is the column number of a column that must have a unique value, like the primary key of a table. Yes, somebody made uniquify a verb; as far as I can tell, Rob‘s to blame. Anyway, what uiUniquifyColumn does is add a semi-random value to the value you pass in as the value for the specified column. (Remember that column numbers start at one, not zero.) That ensures the temporary rows you’re adding won’t conflict with existing rows. As the rows are temporary, there’s no harm in using non-deterministic IDs.

The other parameters are straightforward: the table name, the number of columns, and the value of each column. Note that by default the number of columns you pass in must match the number of columns in the table. If you want to pass in fewer than the actual number of columns, you need to pass in your own view of the table, via a query that selects just the columns you’re interested in.

Risks

Because the data you’re adding is processed during the install transaction, then dropped, a bug in the CA could orphan the data those rows represent during uninstallation. The easiest way to mitigate this risk is to run the immediate CA during every transaction and use the install state and action state of a component to determine whether to write the temporary rows. You can write the temporary rows only when the component state is changing (i.e., being installed, being removed, being repaired). For example:

er = ::MsiGetComponentStateW(WcaGetInstallHandle(), wzComponent, &isInstalled, &isAction);
if (WcaIsInstalling(isInstalled, isAction) || WcaIsReInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction))
{ … }

By triggering off any change in component state, the CA supports installation, uninstallation, repair, and also is smart enough to not do anything when the component isn’t being installed. That lets you avoid adding any conditions to the CA scheduling itself.

Extra credit

Naturally, a WiX compiler extension lets users easily author data into the custom table. Also, there’s no reason you have to limit yourself to standard tables: You can just easily add temporary rows to the XmlConfig table to have the WiX XmlConfig custom action modify XML files in ways you couldn’t with just XPath and formatted properties alone.

Flight Simulator X Service Pack 1 ships

The project that has kept me busy since moving to the ACES Studio at the beginning of the year is finally out the door! Flight Simulator X Service Pack 1 is now available from fsinsider.com. Also available is Flight Simulator X Software Development Kit SP1A.

I’ll be discussing both projects in more detail in future posts, but for now, here’s a quick summary:

  • FSX SP1 is delivered in multiple per-language self-extractors, each of which contains two patches, one for each of the two editions Flight Simulator X is available in (Standard and Deluxe). Of the approximately 42,000 files(!) in RTM, only a couple of hundred were patched or added in SP1.Because of how the FSX RTM setup was built, it wasn’t feasible to ship one patch for all the editions and languages. I built the patch-creation packages (.pcp files) in WiX v3 and built the patches using MsiMsp and PatchWiz 3.1. However, it wasn’t otherwise a “typical” patch build; the RTM setup auto-generated most of its 42,000 files so file IDs and component GUIDs and IDs weren’t stable. That’s why I needed to build 13 distinct patches (two each for English, French, German, Italian, Polish, and Spanish and one for Japanese). It also meant that I couldn’t follow the typical approach of rebuilding the upgrade MSI packages — the GUIDs and IDs kept changing. The story of building custom upgrade packages deserves its own series of posts.
  • FSX SDK SP1A is delivered in one self-extractor. It’s available only in English and only for users of the Deluxe editions, so we needed to deliver only one package. Unlike the game itself, the SDK update is delivered as a new product, not a patch. I investigated delivering a patch for the SDK too, but ran into the same issues with changing IDs as with the game. Another complication was the release of SDK SP1 (without the “A” suffix); it also shipped as a product but with custom code that manually updated and deleted files in the RTM SDK. Both issues combined, a patch wasn’t something I could pull off in a reasonable timeframe. So I created a new SDK product using WiX v3. The self-extractor is the WiX v3 setup.exe chainer. By dropping the auto-generation, I had more work to keep the setup current but we’ll be in a great position for future patches.

The entire ACES Studio can be justifiably proud of the tremendous effort that went into FSX SP1 and SDK SP1A. Naturally, I just wrote above about the work that went into patching and setup, because that’s what I worked on. But a huge amount of work went into performance enhancements and content improvements. I hope every FSX user enjoys it!

Now on to the expansion pack and DirectX 10 update