├── Images ├── fread.png ├── gulf2.png ├── lb1A6.png ├── lb53A.png ├── nc1F6.png ├── nc5CF.png ├── nc607.png ├── nc_wld.png ├── pg70A.png ├── pg_wld.png ├── step2.png ├── step5.png ├── step5b.png ├── egame_010.png ├── egame_011.png ├── egame_012.png ├── egame_013.png ├── egame_014.png ├── gulf6D2.png ├── ida_46DB.png ├── ida_7CB1.png ├── ida_graph.png ├── libya502.png ├── startBF2A.png ├── start_000.png ├── start_001.png ├── start_002.png ├── start_003.png ├── start_010.png ├── start_011.png ├── start_012.png ├── start_013.png ├── start_014.png ├── start_020.png ├── start_021.png ├── start_030.png ├── start_031.png ├── vgame_000.png ├── vgame_001.png ├── vgame_010.png ├── vgame_011.png ├── vgame_012.png ├── vgame_013.png ├── vgame_014.png ├── vgame_015.png └── player_000.png ├── LICENSE ├── Part4.md ├── Part2.md ├── README.md └── Part3.md /Images/fread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/fread.png -------------------------------------------------------------------------------- /Images/gulf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/gulf2.png -------------------------------------------------------------------------------- /Images/lb1A6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/lb1A6.png -------------------------------------------------------------------------------- /Images/lb53A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/lb53A.png -------------------------------------------------------------------------------- /Images/nc1F6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/nc1F6.png -------------------------------------------------------------------------------- /Images/nc5CF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/nc5CF.png -------------------------------------------------------------------------------- /Images/nc607.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/nc607.png -------------------------------------------------------------------------------- /Images/nc_wld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/nc_wld.png -------------------------------------------------------------------------------- /Images/pg70A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/pg70A.png -------------------------------------------------------------------------------- /Images/pg_wld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/pg_wld.png -------------------------------------------------------------------------------- /Images/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/step2.png -------------------------------------------------------------------------------- /Images/step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/step5.png -------------------------------------------------------------------------------- /Images/step5b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/step5b.png -------------------------------------------------------------------------------- /Images/egame_010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/egame_010.png -------------------------------------------------------------------------------- /Images/egame_011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/egame_011.png -------------------------------------------------------------------------------- /Images/egame_012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/egame_012.png -------------------------------------------------------------------------------- /Images/egame_013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/egame_013.png -------------------------------------------------------------------------------- /Images/egame_014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/egame_014.png -------------------------------------------------------------------------------- /Images/gulf6D2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/gulf6D2.png -------------------------------------------------------------------------------- /Images/ida_46DB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/ida_46DB.png -------------------------------------------------------------------------------- /Images/ida_7CB1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/ida_7CB1.png -------------------------------------------------------------------------------- /Images/ida_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/ida_graph.png -------------------------------------------------------------------------------- /Images/libya502.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/libya502.png -------------------------------------------------------------------------------- /Images/startBF2A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/startBF2A.png -------------------------------------------------------------------------------- /Images/start_000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_000.png -------------------------------------------------------------------------------- /Images/start_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_001.png -------------------------------------------------------------------------------- /Images/start_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_002.png -------------------------------------------------------------------------------- /Images/start_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_003.png -------------------------------------------------------------------------------- /Images/start_010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_010.png -------------------------------------------------------------------------------- /Images/start_011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_011.png -------------------------------------------------------------------------------- /Images/start_012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_012.png -------------------------------------------------------------------------------- /Images/start_013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_013.png -------------------------------------------------------------------------------- /Images/start_014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_014.png -------------------------------------------------------------------------------- /Images/start_020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_020.png -------------------------------------------------------------------------------- /Images/start_021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_021.png -------------------------------------------------------------------------------- /Images/start_030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_030.png -------------------------------------------------------------------------------- /Images/start_031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/start_031.png -------------------------------------------------------------------------------- /Images/vgame_000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_000.png -------------------------------------------------------------------------------- /Images/vgame_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_001.png -------------------------------------------------------------------------------- /Images/vgame_010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_010.png -------------------------------------------------------------------------------- /Images/vgame_011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_011.png -------------------------------------------------------------------------------- /Images/vgame_012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_012.png -------------------------------------------------------------------------------- /Images/vgame_013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_013.png -------------------------------------------------------------------------------- /Images/vgame_014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_014.png -------------------------------------------------------------------------------- /Images/vgame_015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/vgame_015.png -------------------------------------------------------------------------------- /Images/player_000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugcom/Hacking-F117A/HEAD/Images/player_000.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 debugcom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Part4.md: -------------------------------------------------------------------------------- 1 | # Hacking-F117A - Part 4 2 | 3 | We ended Part 3 with secret airstrip missions being offered and playable in Libya, the Persian Gulf, and the North Cape, after patching F-117A. This time, we will do the same for F-19. Since most of the analysis has already been done in F-117A, this article might be a bit shorter than the others, but we're including it here for completeness. 4 | 5 | 6 | ### Revisiting ~~pg.wld~~ gulf.wld 7 | 8 | The Persian Gulf world data file is named gulf.wld in F-19, instead of pg.wld like in F-117A. When we track down how this file is being read from, we see that the file format is slightly smaller than F-117A. In particular, the 3 consecutive 80-byte reads have now been replaced by 3 consecutive 64-byte reads: 9 | ``` 10 | address file offset value/size purpose 11 | --- --- --- --- 12 | fread(ca3e, 2, 1) 0 584e 13 | fread(c964, 2, 1) 2 40 wld_max_targets 14 | fread(993a, 2, 1) 4 19 15 | fread(d04a, 2, 1) 6 2b 16 | fread(b37e, 10, c964) 8 400 b wld_target_data 17 | fread(ca52, 2, 1) 408 c 18 | fread(bb68, 24, ca52) 40a 1b0 b 19 | fread(c9ce, 64, 1) 5ba 64 b 20 | fread(c966, 64, 1) 61e 64 b 21 | fread(c150, 64, 1) 682 64 b wld_obj_table 22 | fread(b832, 1, 100) 6e6 100 b 23 | fread(cb24, 1, 2ee) 7e6 2ee b names 24 | ``` 25 | 26 | 27 | ### Patching the files 28 | 29 | Recall from Part 2 that the secret airstrips in the Persian Gulf have slightly different values in F-19, so the data file offsets will be different. However, the location is easily recognizable from the pair of 9s pattern, and values are still object 50. 30 | 31 | ![Screenshot](Images/gulf2.png) 32 | 33 | Therefore wld_obj_table[50] is at file offset 682 + 50 = 6D2. Inspecting gulf.wld at file offset 6D2, we see there is indeed a 1 here. So far so good. 34 | 35 | ![Screenshot](Images/gulf6D2.png) 36 | 37 | Let's expand the table of file offsets to include Libya (libya.wld) and the North Cape (nc.wld). 38 | ``` 39 | address file offset value/size purpose lb off lb v/s nc off nc v/s 40 | --- --- --- --- --- --- --- --- 41 | fread(ca3e, 2, 1) 0 584e 0 584a 0 4e42 42 | fread(c964, 2, 1) 2 40 wld_max_targets 2 25 2 2e 43 | fread(993a, 2, 1) 4 19 4 14 4 12 44 | fread(d04a, 2, 1) 6 2b 6 21 6 20 45 | fread(b37e, 10, c964) 8 400 b wld_target_data 8 250 b 8 2e0 b 46 | fread(ca52, 2, 1) 408 c 258 b 2e8 d 47 | fread(bb68, 24, ca52) 40a 1b0 b 25a 18c b 2ea 1d4 b 48 | fread(c9ce, 64, 1) 5ba 64 b 3e6 64 b 4be 64 b 49 | fread(c966, 64, 1) 61e 64 b 44a 64 b 522 64 b 50 | fread(c150, 64, 1) 682 64 b wld_obj_table 4ae 64 b 586 64 b 51 | fread(b832, 1, 100) 6e6 100 b 512 100 b 5ea 100 b 52 | fread(cb24, 1, 2ee) 7e6 2ee b names 612 2ee b 6ea 2ee b 53 | ``` 54 | The secret airstrips in Libya still reference object 54 55 | 56 | ![Screenshot](Images/lb1A6.png) 57 | 58 | so libya.wld at file offset 4AE + 54 = 502 looks like this, after changing the 0 to a 1 (highlighted in red). 59 | 60 | ![Screenshot](Images/libya502.png) 61 | 62 | Similarly for the North Cape, the secret airstrips still reference object 49. Note that I've also changed the 8s to 9s (highlighted in red) for the reasons explained in Part 2. 63 | 64 | ![Screenshot](Images/nc1F6.png) 65 | 66 | So nc.wld at file offset 586 + 49 = 5CF looks like this (after changing the 0 to a 1). 67 | 68 | ![Screenshot](Images/nc5CF.png) 69 | 70 | I managed to rediscover what tools I used to unpack the executables: for F-117A, I used [unlzexe](https://files.shikadi.net/keenwiki/tools/t.unlzexe_v.0.81_win_32-bit.gerstrong.2010-07-04.zip) and for F-19, I used [upackexe](https://bellard.org/lzexe/lzexe91.zip). The packed/unpacked sizes are 47100/90047 and 87599/106288 respectively. When we inspect the mission table hardcoded in the executable, we see that we don't need to patch it, because in F-19 the theater bitmask already allows all 3 theaters to work, with a value of 0xF. What this means is that the code already supported generating secret airstrip missions for Libya and the North Cape, but because the object reference was missing in each of libya.wld and nc.wld, the missions could not be picked (bug or deliberate?). 71 | 72 | ![Screenshot](Images/step5b.png) 73 | 74 | 75 | ### Conclusion 76 | 77 | Now we are ready - let's load up our patched game and see what happens. Don't forget to set opponent quality to regular or higher. In Libya, we are offered a secret airstrip mission at ONC VC43, and in the North Cape, we are offered one at ONC XX20. 78 | 79 | ![Screenshot](Images/start_030.png) 80 | 81 | ![Screenshot](Images/start_031.png) 82 | 83 | Mission accomplished! By changing 4 bytes in 2 files, we can get the secret airstrip missions in Libya and the North Cape as it was originally documented. 84 | -------------------------------------------------------------------------------- /Part2.md: -------------------------------------------------------------------------------- 1 | # Hacking-F117A - Part 2 2 | 3 | [Part 3 - Really fixing the bug](./Part3.md) 4 | 5 | We ended Part 1 with a fully functional set of secret airstrip missions for Libya and the Persian Gulf, but the North Cape was not quite working. This time around I will investigate mission generation in F-19 (v435.04) for comparison, and as we shall see, there are a lot of similarities to F-117A. Oh, I will also fix a 34 year old bug in both games - sort of. 6 | 7 | 8 | ### Not so different as we were led to believe, so much the better 9 | 10 | Following the same methodology as last time, we use Cheat Engine to find the following hex values for the secret airstrips: 11 | ``` 12 | Libya 19 and 1A 13 | Persian Gulf 19 and 1A 14 | North Cape 1E and 1F 15 | ``` 16 | This is almost identical to F-117A, except for the Persian Gulf values. 17 | 18 | The mission numbers seem unchanged, but their descriptions are worded a little differently - so the first 4 missions now look like this: 19 | ``` 20 | mission 0 - landing - "friendly agents have captured equipment ..." 21 | mission 1 - landing - "guerilla freedom fighters require stinger missiles ..." 22 | mission 2 - supply drop - "covert agents lost their arms and equipment ..." 23 | mission 3 - supply drop - "guerilla group needs more weapons and explosives ..." 24 | ``` 25 | Using debug.com, and our previous experience as a guide, it was pretty easy to find the addresses for the mission parameters: 26 | ``` 27 | ds:B936 Primary Mission Type 28 | ds:B938 Primary Mission Target 29 | ds:B93A Takeoff Target 30 | ds:B93C Primary Mission Code 31 | ds:B93E Primary Mission Number 32 | 33 | ds:B948 Secondary Mission Type 34 | ds:B94A Secondary Mission Target 35 | ds:B94C Landing Target 36 | ds:B94E Secondary Mission Code 37 | ds:B950 Secondary Mission Number 38 | ``` 39 | Notice the memory layout for the mission parameters is identical to F-117A. 40 | 41 | Using IDA I found the following code at cs:7CB1, which is very similar to that of F-117A: 42 | 43 | ![Screenshot](Images/ida_7CB1.png) 44 | 45 | The lookup table is still 12 bytes wide, indexed by the mission number [bp-2A]. The table starts at address 4B02h, and the first word contains the mission type, which is stored in B936. Meanwhile, the mission number itself is stored in B93E, and the mission code is stored in B93C. By inspecting the lookup table, we find that the mission code is still 2F for landing missions, and 3A for supply drops. So the complete mission parameters list in the Persian Gulf is very similar to F-117A: 46 | ``` 47 | ds:B936 04, 19, any, 2F, 00 48 | ds:B936 04, 1A, any, 2F, 00 49 | ds:B936 04, 19, any, 2F, 01 50 | ds:B936 04, 1A, any, 2F, 01 51 | ds:B936 03, 19, any, 3A, 02 52 | ds:B936 03, 1A, any, 3A, 02 53 | ds:B936 03, 19, any, 3A, 03 54 | ds:B936 03, 1A, any, 3A, 03 55 | ``` 56 | Using our past experience with F-117A, I settled on address cs:7EAF to put a breakpoint, where we can edit the mission parameters at runtime. After resuming execution, we get a valid secret airstrip mission - no surprises here. And just like in F-117A, this works for Libya and the Persian Gulf, but not for the North Cape. Here are some screenshots comparing F-19 and F-117A: 57 | 58 | ![Screenshot](Images/start_010.png) 59 | 60 | ![Screenshot](Images/egame_010.png) 61 | 62 | ![Screenshot](Images/egame_011.png) 63 | 64 | ![Screenshot](Images/start_012.png) 65 | 66 | ![Screenshot](Images/vgame_010.png) 67 | 68 | ![Screenshot](Images/vgame_011.png) 69 | 70 | ![Screenshot](Images/start_011.png) 71 | 72 | ![Screenshot](Images/egame_012.png) 73 | 74 | ![Screenshot](Images/egame_013.png) 75 | 76 | ![Screenshot](Images/start_013.png) 77 | 78 | ![Screenshot](Images/vgame_012.png) 79 | 80 | ![Screenshot](Images/vgame_013.png) 81 | 82 | So far, our debugging techniques are the same for both games, and the results show that the underlying mission structure is unchanged. My instincts told me that everything I had done so far was valid, and that the only reason we were unable to get credit for successfully completing the secret airstrip mission in the North Cape was due to a missing property in the airstrip objects themselves. In other words, the problem was in the game data, not the game code. 83 | 84 | 85 | ### Fixing the bug 86 | 87 | Going back to F-117A, and assuming the North Cape data was bad, let's substitute data that is known to be good - for example, the Persian Gulf. By inspecting the game files, I guessed that we can ignore the 3D and sprite files, leaving the only choice possible: nc.wld. I then copied pg.wld on top of nc.wld, ran the game, and loaded the North Cape. I hit the breakpoint at cs:487D (from Part 1 of this article), and made the following mission parameter edit [4, 18, 18, 2f, 0]. Notice I used the hex values for the Persian Gulf airstrip rather than the North Cape, then resumed execution and got the following result: 88 | 89 | ![Screenshot](Images/start_014.png) 90 | 91 | Hmm, the airstrip appears to be in the middle of the water off the coast of Norway, and the map sector WX67 is incorrect. However, if this was the Persian Gulf map, the location of the airstrip at JZ67 would be right there in the upper left corner. If we accept the mission, we end up sitting in the middle of the water, but we are able to complete the primary objective anyway (note I had No Crashes enabled). 92 | 93 | ![Screenshot](Images/vgame_014.png) 94 | 95 | What this tells me is that as long as we have good data, the secret airstrip mission can be completed in the North Cape. So now we have to find and modify the airstrip data in the North Cape. Let's have a look at pg.wld and nc.wld, to see what patterns emerge: 96 | 97 | ![Screenshot](Images/pg_wld.png) 98 | 99 | ![Screenshot](Images/nc_wld.png) 100 | 101 | Upon casual inspection, the data appears to be in 16 byte rows, possibly indexed by target number. Recall that the hex values for the secret airstrips in the Persian Gulf are 18 and 19, whereas in the North Cape they are 1E and 1F. In pg.wld, lines 190 and 1A0 appear to be different from the surrounding lines, while in nc.wld, lines 1F0 and 200 appear different. Could it be as easy as copying a few bytes from one file to the other? Yes! It turns out that changing the 08 to 09 at offset 1F1 and 201 in nc.wld is all we need to do - a 2 byte fix! A quick inspection of the Libya lb.wld file confirms the value should be 09. 102 | 103 | 104 | ### Conclusion 105 | 106 | With our patch in place (for both games), we can now get credit for landing on the secret airstrips in the North Cape. 107 | 108 | ![Screenshot](Images/egame_014.png) 109 | 110 | ![Screenshot](Images/vgame_015.png) 111 | 112 | Although I fixed the bug in the North Cape data, in truth it may be a masked bug because I don't recall ever being offered a secret airstrip mission for the North Cape in the first place. 113 | 114 | [Part 3 - Really fixing the bug](./Part3.md) 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hacking-F117A 2 | 3 | My investigation into mission generation in the Stealth Fighter games by Microprose. 4 | 5 | [Part 1 - This document](./README.md) May 11 2022 6 | [Part 2 - Fixing the bug (sort of)](./Part2.md) May 24 2022 7 | [Part 3 - Really fixing the bug](./Part3.md) Jun 20 2024 8 | [Part 4 - Fixing F-19 as well](./Part4.md) Sep 17 2024 9 | 10 | ![Screenshot](Images/player_000.png) 11 | 12 | 13 | ### Background 14 | 15 | Two of my favorite DOS games are F-19 Stealth Fighter and its 1991 remake F-117A Nighthawk Stealth Fighter 2.0. Both games come in heavy boxes, each packed with a thick manual offering a wealth of detailed information. As a pilot, you can choose to fly air-to-air missions or strike missions, and the manual hints at a special type of strike mission - the secret airstrip. The objective is to land at a secret airstrip behind enemy lines to drop off or pickup equipment, then safely fly home. This is an excerpt from the manual, which can be found here [F-19](https://archive.org/stream/f19stealthfightermanual/F19-StealthFighter-Manual_djvu.txt) and 16 | here [F-117A](https://archive.org/stream/f117Astealthfightermanual/F-117AStealthFighter-Manual_djvu.txt). 17 | 18 | > Instead, a secret airstrip challenges your flying skill. You must manage a landing without an ILS to guide you. Worse, the strip is only half the length of a normal runway. You must land gently, at low speed (under 160 kts, preferably), and touchdown near the start. Otherwise you'll roll off the other end and crash! To make matters worse, the strip's lights are only for a limited time. 19 | 20 | The secret airstrips are documented in the F-19 manual in 3 theaters: Libya, Persian Gulf, and North Cape. 21 | 22 | > Secret Bases: Western intelligence operatives in Libya are preparing two secret, hard-pack surfaces suitable for aircraft landings. One is at Al Mukhayli (ONC VC43), the other is on the edge of the great ergs, at Yafran (ONC TC93). 23 | 24 | > Secret Bases: Western intelligence operatives in Iran are preparing two secret, hard-pack surfaces suitable for aircraft landings. One is in the Shalamzar valley (JZ67) in the Zagros Mountains, the other in the mountains south of Kerman, at KZ82. 25 | 26 | > Secret Bases: Western intelligence operatives in this area have secret created two hard-frozen airstrips suitable for landings, one at XW57, the other at XX20. 27 | 28 | However, in the F-117A manual, the locations of the secret airstrips have been omitted entirely. 29 | 30 | 31 | ### Hacking the mission 32 | 33 | The missions are generated randomly, and if your intention is to fly the secret airstrip, you would normally have to decline the offered mission repeatedly until the right combination is presented. As I found out, it appears to be quite rare (or else I was extremely unlucky), because I was not offered this mission after declining repeatedly (upwards of 20 minutes) - long enough for me to feel bored and sleepy. What would it take to reliably generate the secret airstrip mission, so you could play it on demand? 34 | 35 | Of the two games, I started hacking around with F-117A (v473.03), since it was the newer and prettier of the two. The first step was to hack the memory contents using Cheat Engine. With careful planning and scanning of memory addresses, declining the mission, then scanning again, I was able to identify two potential locations called the Primary Target and Secondary Target. By sequentially incrementing the byte at these two locations, and then observing the effects in-game, I was able to identify the hex values for the secret airstrips: 36 | ``` 37 | Libya 19 and 1A 38 | Persian Gulf 18 and 19 39 | North Cape 1E and 1F 40 | ``` 41 | However, simply plugging these hex values into the Primary or Secondary Target addresses was not good enough. When you do so, you often end up seeing missions like "Photograph the Airstrip" or "Destroy the Airstrip". Clearly there are other mission parameters which control the overall mission objectives. After more scanning of changed memory values, I found additional addresses which contributed in some way to the overall mission: 42 | ``` 43 | Primary Mission Type - values 1-4, see mission_type below 44 | Primary Mission Target - hex value for the primary target 45 | Takeoff Target - the starting point for your mission 46 | Primary Mission Number - description and background info for your mission 47 | 48 | Secondary Mission Type - values 1-4, see mission_type below 49 | Secondary Mission Target - hex value for the secondary target 50 | Landing Target - the ending point for your mission 51 | Secondary Mission Number - description and background info for your mission 52 | 53 | enum mission_type { 54 | photograph = 1, 55 | destroy = 2, 56 | supply_drop = 3, 57 | landing = 4, 58 | }; 59 | ``` 60 | Now that the mission parameters are more fully fleshed out, we see that there are 4 mission types: you can either photograph or destroy a target. For the special case of secret airstrips, you can also fly over it to drop supplies, or for the ultimate thrill, land on it. This is represented by the C-style enum mission_type, with possible values from 1 to 4. Notice we can potentially have a landing mission for both the primary and secondary objectives. Normally the game logic would prevent this outcome by automatically selecting a different mission for the secondary objective. 61 | 62 | Another interesting point is the takeoff and landing targets - normally you would start and end the missions on friendly airstrips. But by specifying the correct hex values for takeoff and landing, you can actually start the mission already in enemy territory! Furthermore, if you specify the secret airstrips as your takeoff and landing targets, you could potentially accomplish a primary or secondary objective without even lifting off! But we're getting a little ahead of ourselves. 63 | 64 | Finally, there is the mission number, which is an index to a lookup table. Here you will receive your mission orders, often with background info, to set the mood. By sequentially incrementing the value here and observing the effects in-game, I estimate there to be roughly 40 or 50 different missions. However, the first 4 missions are special: 65 | ``` 66 | mission 0 - landing - "our agents have captured enemy equipment ..." 67 | mission 1 - landing - "guerilla freedom fighters need stinger missiles ..." 68 | mission 2 - supply drop - "covert agents lost their arms and equipment ..." 69 | mission 3 - supply drop - "guerilla group needs weapons and explosives ..." 70 | ``` 71 | In practice, editing the mission targets with Cheat Engine after the mission has already been generated doesn't quite produce the desired results. For example, even though I specified mission type 4, mission target 18, and mission number 0 in the Persian Gulf, the mission description is not quite right. 72 | 73 | ![Screenshot](Images/start_000.png) 74 | 75 | Here we see the map sector for the airstrip doesn't match the published location JZ67. Also note in the next screen, the wrong weapon was auto-selected to fill the cargo bay. 76 | 77 | ![Screenshot](Images/start_001.png) 78 | 79 | 80 | ### Under the hood 81 | 82 | In order to fully understand what's going on, we need to load the game in a debugger. Using the old 16-bit DOS debugger debug.com, I stepped over function calls until I narrowed down the following stack trace: 83 | ``` 84 | cs:028D call 0FA3 - enter briefing room 85 | cs:0FA9 call 0F4E - show flight plan 86 | cs:411E call 4239 - get new missions 87 | ``` 88 | Now we're getting close - when function 4239 returns, new missions will have been generated and offered. So we just need to look closer at function 4239 to see where we might override the mission parameters. After stepping through 4239, I came across the following interesting instructions: 89 | ``` 90 | cs:42E9 call 51E8 91 | cs:42EE mov [E060], ax 92 | 93 | cs:43C2 call 512E 94 | cs:43C8 mov [E072], ax 95 | ``` 96 | What's happening here is that a target is generated at 51E8, returned in ax, then stored in address E060. Later, another target is generated at 512E, then stored in address E072. In fact, what we have here is the location of the Primary and Secondary Targets - hurrah! Now that we have real addresses worth mentioning, I can annotate the mission parameters from above like so: 97 | ``` 98 | ds:E05E Primary Mission Type 99 | ds:E060 Primary Mission Target 100 | ds:E062 Takeoff Target 101 | ds:E064 ?? 102 | ds:E066 Primary Mission Number 103 | 104 | ds:E070 Secondary Mission Type 105 | ds:E072 Secondary Mission Target 106 | ds:E074 Landing Target 107 | ds:E076 ?? 108 | ds:E078 Secondary Mission Number 109 | ``` 110 | First, I tried modifying functions 51E8 and 512E to return 18 and 19 respectively, to see what would happen. The game logic would later validate these targets, and ultimately reject one of the airstrip targets by substituting a different target. In this way, you end up with only one landing mission, and the other mission was typically a photograph or destroy mission. Presumably this was done to ensure variety in the randomly generated missions. Even though my modification forced a secret airstrip as a mission target, in practice I observed that the game would almost always choose mission type 3 (supply drop) for it, rather than the desired type 4. 111 | 112 | Also, although my modification was partially successful for Libya and the Persian Gulf, it failed miserably in the North Cape. What ended up happening in the North Cape was that function 4239 would go into an infinite loop. I think the reason might be that it was unable to find a substitute target to satisfy the mission requirements - my question was, why was the North Cape theater the only one affected? I didn't really have an answer, but we will come back to this question later. 113 | 114 | So directly hacking functions 51E8 and 512E didn't seem like a viable approach, and I needed a better way. There is a special relationship between the mission type, mission target, and the mission number. Since missions 0-3 involve secret airstrips, that limits the allowed mission types and mission targets. For example, missions 0 and 1 are both landing missions, so the mission type must be 4. Furthermore, the mission target must be either 18 or 19 in the Persian Gulf. Since missions 2 and 3 are supply drop missions, the mission type must be 3. So the list of possible Primary mission parameters for the Persian Gulf goes something like this (ignoring any takeoff target): 115 | ``` 116 | ds:E05E 04, 18, any, ??, 00 117 | ds:E05E 04, 19, any, ??, 00 118 | ds:E05E 04, 18, any, ??, 01 119 | ds:E05E 04, 19, any, ??, 01 120 | ds:E05E 03, 18, any, ??, 02 121 | ds:E05E 03, 19, any, ??, 02 122 | ds:E05E 03, 18, any, ??, 03 123 | ds:E05E 03, 19, any, ??, 03 124 | ``` 125 | To gain a better understanding of the disassembly, I used an old tool called IDA Freeware Version 5.0, which supports 16-bit DOS targets - their most recent free offerings do not support 16-bit anymore. I searched for interesting instructions that wrote to address E05E or E066, and found the following code at cs:46DB 126 | 127 | ![Screenshot](Images/ida_46DB.png) 128 | 129 | Bingo! What we have here is a lookup table with rows 12 bytes wide, indexed by the mission number var_CC. The lookup table starts at address 11DEh, and the first word contains the mission type, which is eventually stored in E05E. Meanwhile, the mission number itself is stored in E066, and something I'll call a mission code is stored in E064. Finally we can talk about the mission code, denoted by double question marks ?? above. What is it? It's a number that is specific to each mission, and the value is not calculated - rather, it's fetched from a table. I believe this mission code is a set of bit flags used in the simulation portion of the game, and these flags indicate how a successful mission is determined. By inspecting the lookup table, I found that the mission code is 2F for landing missions, and 3A for supply drops. So the complete mission parameters list now looks like this: 130 | ``` 131 | ds:E05E 04, 18, any, 2F, 00 132 | ds:E05E 04, 19, any, 2F, 00 133 | ds:E05E 04, 18, any, 2F, 01 134 | ds:E05E 04, 19, any, 2F, 01 135 | ds:E05E 03, 18, any, 3A, 02 136 | ds:E05E 03, 19, any, 3A, 02 137 | ds:E05E 03, 18, any, 3A, 03 138 | ds:E05E 03, 19, any, 3A, 03 139 | ``` 140 | If I modify any of the memory above to set the mission parameters that I want, or even just change the values at runtime using the debugger, the same thing happens as before: the game logic later validates the missions and eventually substitutes a different mission target - how annoying. What I need to do is modify the mission parameters AFTER all the validation and substitution has already been done. The graph view of IDA is useful to get a bird's eye view of the program flow control, so here's the graph of function 4239: 141 | 142 | ![Screenshot](Images/ida_graph.png) 143 | 144 | For reference, I've highlighted in green 3 of the interesting code blocks. Near the top of the graph, E060 is written to at address cs:42EE. A little bit lower down, E072 is written to at address cs:43C8. Almost midway down the graph, E05E, E066, and E064 are written to at address cs:46DB. You can see by all the lines leading back to the top of the function how missions are validated, and targets can be substituted. 145 | 146 | By a little trial and error, I settled on the circled region as the earliest point at which the code doesn't jump back on itself to ruin my carefully chosen mission parameters. This region is at address cs:487D. If I simply set a breakpoint at cs:487D, then modify the contents of ds:E05E to ds:E066 at runtime with the values [04, 18, 18, 2F, 00], then resume program execution, I get the following results: 147 | 148 | ![Screenshot](Images/start_002.png) 149 | 150 | ![Screenshot](Images/start_003.png) 151 | 152 | Now the location of the secret airstrip is shown as JZ67, and on the next screen, the cargo bay has been reserved for the special equipment that will be loaded aboard. If you accept the mission and enter the simulation, the primary mission in yellow does indeed call for landing supplies at the airstrip: 153 | 154 | ![Screenshot](Images/vgame_000.png) 155 | 156 | When you land on the secret airstrip, thereby completing the primary objective, it looks like this: 157 | 158 | ![Screenshot](Images/vgame_001.png) 159 | 160 | 161 | ### Conclusion 162 | 163 | Success! Repeating the experiment with all 4 secret airstrips in Libya and the Persian Gulf gives the same results - I can now choose any of these landing missions on demand. 164 | 165 | What about the North Cape? The landing mission simply will not work, even though everything appears ok - the mission descriptions are correct, the special equipment is reserved in the cargo bay, and the mission orders in the simulation are correct. However, no matter how hard I try, I can never get credit for completing the objective. After repeated attempts, I'm starting to suspect that this is a bug in the game. My prevailing theory is that there is a missing property on the 2 secret airstrips in the North Cape, which prevent the simulation from detecting when a mission objective has been completed. Are there any Microprose alumni out there who are able to confirm? 166 | 167 | [Part 1 - This document](./README.md) 168 | [Part 2 - Fixing the bug (sort of)](./Part2.md) 169 | [Part 3 - Really fixing the bug](./Part3.md) 170 | [Part 4 - Fixing F-19 as well](./Part4.md) 171 | -------------------------------------------------------------------------------- /Part3.md: -------------------------------------------------------------------------------- 1 | # Hacking-F117A - Part 3 2 | 3 | [Part 4 - Fixing F-19 as well](./Part4.md) 4 | 5 | We ended Part 2 with the ability to manually insert a set of secret airstrip mission parameters via a debugger, and this allowed us to play and complete the mission objectives in all 3 theaters where the secret airstrip was documented. However, we were not able to figure out why the missions were not being offered in the first place - until now. We will finally address this problem once and for all. 6 | 7 | 8 | ### A flash of inspiration 9 | 10 | This project hasn't seen love for over a year, until I recently came across [neuviemeporte's journal](https://neuviemeporte.github.io/category/f15-se2.html) 2 months ago. Based on neuviemeporte's description of the monster routine for generating missions in F-15 Strike Eagle II, I had no doubt that we were looking at essentially the same piece of code present in F-19 and F-117A. After reading that someone is actively working on one of Microprose's games, I was inspired to get back into this project. Also, ever since Parts 1 and 2 were first written 2 years ago, I've now switched from using debug.com to using the DOSBox debugger (highly recommended), and I've added Ghidra to my toolbox. Ghidra's decompiler window is invaluable for seeing how the code is organized at a higher level than simply looking at the disassembly. I felt ready to tackle the challenge. 11 | 12 | 13 | ### Our first stop, pg.wld 14 | 15 | Let's start by taking a deeper look at the world data of the only confirmed theater to offer a secret airstrip mission: the Persian Gulf. Recall the beginning of pg.wld looks like this: 16 | 17 | ![Screenshot](Images/pg_wld.png) 18 | 19 | How do we make sense of this data? My first thought was to track down where this file was being read from, and then see how the data is used. After browsing around, I managed to find the code where this file is loaded. Conveniently enough, the file is sequentially read multiple times within a single routine at 54BB - here's the decompiled output. 20 | 21 | ![Screenshot](Images/fread.png) 22 | 23 | It appears F-117A was written using Microsoft C 6.0, and the documentation from 1990 gives the function prototype as fread( void *buffer, size_t size, size_t count, FILE *stream ). We can see the file is read from 4 times in a row, reading a single 2-byte value each time. This is followed by reading d33c chunks of 0x10 bytes each, and so on until closing the file. I made a table for easy reference, so I didn't have to recalculate the file offsets every time I wanted to look at the raw data file. 24 | ``` 25 | address file offset value/size purpose 26 | --- --- --- --- 27 | fread(d88c, 2, 1) 0 584e 28 | fread(d33c, 2, 1) 2 40 wld_max_targets 29 | fread(cace, 2, 1) 4 18 30 | fread(e084, 2, 1) 6 2a wld_reg_targets 31 | fread(cbde, 10, d33c) 8 400 bytes wld_target_data 32 | fread(d890, 2, 1) 408 c 33 | fread(d3be, 24, d890) 40a 1b0 bytes 34 | fread(d7f6, 80, 1) 5ba 80 bytes 35 | fread(d33e, 80, 1) 63a 80 bytes 36 | fread(d2a6, 80, 1) 6ba 80 bytes wld_obj_table 37 | fread(cad8, 1, 100) 73a 100 bytes 38 | fread(dd70, 1, 2ee) 83a 2ee bytes names 39 | ``` 40 | This means at runtime, CBDE points to 10 x 40 = 400 bytes of data, which corresponds to file offset 8. Likewise, D2A6 points to 80 bytes corresponding to the data found at file offset 6BA. Of course, the file offsets are different for each theater (and we will address them later) but for now, this mapping will do. I've given names to some of the pointers in the last column under purpose, based on my best guess. 41 | 42 | 43 | ### Next up, revisiting function 4239 44 | 45 | Understanding function 4239 through the decompiler is so much easier than my previous attempts in Parts 1 and 2. Now we can easily see the code structure and flow control. In fact, since the routine is so large and convoluted, I'll condense it down (a lot) to the following bare bones summary: 46 | ``` 47 | do { 48 | /* 4256 49 | Step 1. Initialize target list */ 50 | if (0x32 < local_4) { 51 | for (local_46 = 0; local_46 < 0x80; local_46 = local_46 + 1) { 52 | } 53 | do { 54 | } while (*(char *)((char)world_object_not15 + 0xd2a6) == '\x15'); 55 | } 56 | 57 | 58 | /* Step 2. Pick primary target */ 59 | do { 60 | if ((*(byte *)((int)*(undefined4 *)0xcaca + 0x3c) & 1) == 0) { 61 | do { 62 | } while ((*(uint *)(local_40[3] * 0x10 + 0xcbe6) & 0xd01) != 1); 63 | *(int *)0xe060 = local_40[3]; 64 | } 65 | else { 66 | do { 67 | iVar7 = FUN_1000_51e8(1,(int)(char)world_object_not15); 68 | *(int *)0xe060 = iVar7; 69 | if (iVar7 == -1) goto LAB_1000_4256; 70 | if (((*(uint *)(iVar7 * 0x10 + 0xcbe6) & 0x801) == 1) || 71 | ((*(byte *)(*(int *)0xe060 * 0x10 + 0xcbec) & 0x7f) != world_object_not15)) { 72 | local_2c = '\0'; 73 | } 74 | else { 75 | local_2c = '\x01'; 76 | } 77 | } while (local_2c == '\0'); 78 | } 79 | 80 | 81 | /* 4331 82 | Step 3. Pick secondary target */ 83 | do { 84 | do { 85 | } while ((*(byte *)(((int)local_40[3] >> 0xb) + (local_44 >> 0xb) * 0x10 + 0xcad8) & 3) != 0); 86 | iVar7 = FUN_1000_512e(local_40[3],local_44,2); 87 | *(int *)0xe072 = iVar7; 88 | if (100 < local_46) goto LAB_1000_4256; 89 | } while (local_2c == '\0'); 90 | } while (local_30 == '\0'); 91 | 92 | 93 | /* 4455 94 | Step 4. Pick takeoff/landing targets */ 95 | for (current_mission = 0; current_mission < 2; current_mission = current_mission + 1) { 96 | for (local_46 = *(int *)0xe084; local_46 < *(int *)0xd33c; local_46 = local_46 + 1) { 97 | if (local_4a != '\0') { 98 | if ((int)local_40[2] < 0x7fff) { 99 | if ((int)(iVar7 + local_40[2]) < 100 | (int)((-(uint)((*(byte *)((int)local_2e + 9) & 2) != 0) & 0xc80) + 101 | local_40[current_mission])) { 102 | *(int *)(current_mission * 0x12 + 0xe062) = local_46; 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | 110 | /* 465c 111 | Step 5. Get compatible mission */ 112 | for (local_46 = 0; uVar8 = local_40[0], local_46 < 2; local_46 = local_46 + 1) { 113 | for (local_d0 = 0; local_d0 < 2; local_d0 = local_d0 + 1) { 114 | for (current_mission = 0; current_mission < 0x52; current_mission = current_mission + 1) { 115 | iVar7 = current_mission * 0xc; 116 | iVar9 = (int)*(undefined4 *)0xcaca; 117 | /* 5a: if (mis_table_11da[current_mission].glob_settings.theater).bitmask and */ 118 | /* 5b: (mis_table_11dc[current_mission].glob_settings.tension).bitmask and */ 119 | /* 5c: wld_obj_table_6ba[wld_target_data_008[e060/e072].object_016] == mis_table_11e0[] */ 120 | /* 5d: and (first loop || primary mission != current mission) */ 121 | if (((((*(uint *)(iVar7 + 0x11da) & 1 << (*(byte *)(iVar9 + 0x38) & 0x1f)) != 0) && 122 | ((*(uint *)(iVar7 + 0x11dc) & 1 << (*(byte *)(iVar9 + 0x3a) & 0x1f)) != 0)) && 123 | ((int)*(char *)((*(byte *)(*(int *)(local_46 * 0x12 + 0xe060) * 0x10 + 0xcbec) & 124 | 0x7f) + 0xd2a6) == *(int *)(iVar7 + 0x11e0))) && 125 | ((local_46 == 0 || (*(int *)0xe066 != current_mission)))) { 126 | if ((local_d0 != 0) && (local_24 == mission_count)) { 127 | iVar10 = current_mission * 0xc; 128 | iVar9 = local_46 * 0x12; 129 | /* e05e_mission_type = mis_table_11de[current_mission].mission_type */ 130 | *(undefined2 *)(iVar9 + 0xe05e) = *(undefined2 *)(iVar10 + 0x11de); 131 | /* e066_mission_number = current_mission */ 132 | *(int *)(iVar9 + 0xe066) = current_mission; 133 | iVar7 = *(int *)(iVar10 + 0x11e2); 134 | /* e064_mission_code = mis_table_11e2[current_mission].mission_code */ 135 | *(int *)(iVar9 + 0xe064) = iVar7; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | 143 | /* 47bf 144 | Step 6. Final check for mission compatibility */ 145 | /* 6a: if glob_settings.mission == air or 146 | 0 < mission_type < 5 */ 147 | if (((((*(byte *)(*(int *)0xcaca + 0x3c) & 1) == 0) || 148 | ((*(int *)0xe05e < 5 && (*(int *)0xe070 < 5)))) && (*(int *)0xe05e != 0)) && 149 | (*(int *)0xe070 != 0)) { 150 | /* 6b: if mission_numbers are not the same */ 151 | if (*(int *)0xe066 != *(int *)0xe078) { 152 | /* 6c: if secondary.mission_code == 0 */ 153 | if (((*(byte *)0xe076 & 2) == 0) || ((*(byte *)0xe076 & 2) == 0)) { 154 | /* 6d: if secondary_mission_type < 5 or > 8 and */ 155 | /* 6e: primary_mission_type != 4 or global_settings.quality != green and */ 156 | /* 6f: one of the mission_codes != secret airstrip */ 157 | if (((iVar7 < 5) || (iVar7 != 8 && 2 < iVar7 + -5)) && 158 | (((*(int *)0xe05e != 4 || (*(int *)0xcabe != 0)) && 159 | (((*(byte *)0xe064 & 8) == 0 || ((*(byte *)0xe076 & 8) == 0)))))) { 160 | /* 487d 161 | Goal reached, no further mission changes, as per Part 1 */ 162 | return; 163 | } 164 | } 165 | } 166 | } 167 | } while( true ); 168 | ``` 169 | In step 1, we construct a dynamic array of targets from the wld_obj_table. Recall from our file offsets table above that this section is 80 bytes long, which exactly matches the for loop here. We basically loop through all 80 bytes, looking for non-zero entries, and if found, add that index to the target array. In the while loop, we randomly pick one of these entries. So from step 1 we already have an important clue as to how a target is included for random selection - it must have a non-zero value in the wld_obj_table. This will become important later as we solve the riddle of secret airstrip missions in the North Cape (and Libya). 170 | 171 | In step 2, the goal is to pick a target and save it to (our friend from Part 1) E060 - the primary target. Depending on the value of *(*CACA + 3C), we take 1 of 2 branches. However, both branches end with assigning a target to E060. Notice the else branch contains a call to function 51E8 before assignment: this was covered in Part 1. What is CACA all about? It turns out, by randomly inserting values here via the debugger, and observing what happens in-game, I found out this is the global settings area. 172 | ``` 173 | offset setting zero-based enumeration 174 | --- --- --- 175 | 38 theater libya, persian gulf, north cape, central europe, vietnam, middle east, desert storm, cuba, korea 176 | 3a tension cold, limited, conventional 177 | 3c mission air, strike, air training, strike training 178 | 3e quality green, regular, veteran, elite 179 | 40 landing no crashes, easy landings, realistic landings 180 | 42 realism microprose, lockheed 181 | ``` 182 | So for example, if you've chosen the Persian Gulf, *(*CACA + 38) would be 1, or if you've chosen elite opponents, *(*CACA + 3E) would be 3. For the record, my own settings for the bulk of the research here is the following: persian gulf, cold war, strike mission, green opponents, no crashes, and microprose realism. So back in the code, *(*CACA + 3C) is checking what type of mission has been selected. If it's an air-to-air mission, take the first branch, otherwise take the else branch. If a target couldn't be found (iVar7 == -1), goto the top of the loop. Let's examine the expression ((*(iVar7 * 0x10 + 0xcbe6) & 0x801) == 1). Address CBE6 is only 8 bytes away from CBDE (wld_target_data), which we know is 40 lines of 10 bytes each. This means CBDE is the base pointer to an array of (40) structures (each of size 10), and CBE6 points to one of the members of the struct at offset 8, which I named target_flags. 183 | ``` 184 | offset address purpose 185 | --- --- --- 186 | 0 cbde 187 | 2 cbe0 wld_x_coord 188 | 4 cbe2 wld_y_coord 189 | 8 cbe6 target_flags 190 | e cbec object 191 | ``` 192 | This means the C equivalent to the expression above is something like wld_target_data[iVar7].target_flags & 801 == 1. Its purpose is to filter out targets that are air-to-air specific (because this is a strike mission remember?). The expression ((*(*0xe060 * 0x10 + 0xcbec) & 0x7f) != world_object_not15) similarly checks one of the members of the struct at offset 0xe, to make sure the object there equals the randomly picked one in step 1. Essentially, each target in the wld_target_data area only works with a specific object in the wld_obj_table area, so if your randomly picked target doesn't match what the mission supports, then you must choose again. That means the secret airstrips in the Persian Gulf, whose target values are 18 and 19, only work with the object at index 50 as shown here. Again, this will be important later to solve the riddle in the North Cape. 193 | 194 | ![Screenshot](Images/step2.png) 195 | 196 | In step 3, there's nothing important here that I want to comment on. Let's just say that a random target is selected at function 512E and saved to E072, the secondary target. If we've exhausted all options, goto the top of the loop and start over, otherwise keep looking for a candidate target. 197 | 198 | In step 4, the goal is to save the takeoff and landing targets to E062 and E074 respectively. Again, there's nothing special here that I want to comment on. 199 | 200 | In step 5, we have a triple for loop and a pair of if statements, with lots of interesting things going on. This section deals with picking the mission type, number, and code. We touched on this section in Part 1, but at the time, we only focused on how the values were pulled from a table. This time, we will examine the surrounding if statement to understand what's really going on. I've already included some comments to make it easier to follow. The inner for loop goes through each mission (up to 0x52 of them so my estimate of 40-50 missions from Part 1 seems a bit low), and checks it against a table via this expression ((*(iVar7 + 0x11da) & 1 << (*(iVar9 + 0x38) & 0x1f)) != 0). What's this table at 11DA all about? This is the mission table which specifies which missions are valid for which theater, among other things. A snippet of the table (which is 0xc bytes wide) is shown here, followed by my interpretation of the offsets. 201 | 202 | ![Screenshot](Images/step5.png) 203 | 204 | ``` 205 | offset address purpose 206 | --- --- --- 207 | 0 11da theater bitmask 208 | 2 11dc tension bitmask 209 | 4 11de mission_type 210 | 6 11e0 object_flag 211 | 8 11e2 mission_code 212 | ``` 213 | So basically the expression is checking the table whether the current mission supports the chosen theater via bitmask, as indicated by my clumsy comment "5a if (mis_table_11da[current_mission].glob_settings.theater).bitmask". Since we are currently in the Persian Gulf, that means the theater value is 1, and 1 left shifted by 1 equals 2. This bit position is consistent with the value 2 in the mission table at offset 0, therefore mission 0 (the secret airstrip mission) is valid for the Persian Gulf. Likewise, the table values at offsets 0xc, 0x18, and 0x24 are all 2, indicating that missions 1, 2, and 3 are also valid for the Persian Gulf. This is evidence that the secret airstrip missions are offered only in the Persian Gulf and nowhere else. The next line at 5b is similar - the bit position of the current tension (cold, limited, conventional war) needs to be consistent with the value at offset 2, which happens to be 7. This means that the secret airstrip mission is supported in all 3 tension types (because 1 left shifted by 0, 1, or 2 are all consistent with the value 7). 214 | 215 | The next line 5c is a little convoluted, but it goes something like this: lookup the primary or secondary mission in wld_target_data, and find the object there at offset 0xe (wld_target_data[e060/e072].object). Using this object value, lookup wld_obj_table and make sure this is equal to the object_flag at address 11E0 in the mission table. For example, suppose the target is 18. Lookup wld_target_data[18] offset by 0xe, which happens to be at file offset 196. This would be the circled 50 from a couple of screenshots above. Now lookup 50 in wld_obj_data, and make sure the value there equals the value of mission_table[mission 0].object_flag, which in this case has the value 1 for the first 4 missions. If they're equal, then this mission is suitable for inclusion. Lastly, the next line 5d simply makes sure that the primary mission details are filled in on the first loop, and the secondary mission details are filled in on the next loop (as long as the missions are not the same). If all the above conditions are met, we can go ahead and pull the relevant mission parameters from the table and assign them as appropriate. 216 | 217 | In step 6, we perform the final checks on mission compatibility with 4 nested if statements. At 6a, if the mission is air-to-air, or if the mission_type is within the bounds 0 and 5, we can proceed. At 6b, as long the primary and secondary mission numbers are not the same (E066 != E078), we can proceed. At 6c, if the secondary mission_code is 0, we can proceed. This rule effectively prevents the secondary objective from being the secret airstrip, because the mission_codes for the airstrips are 2F and 3A. 6d is almost a duplicate of 6a, so let's ignore. At 6e, the primary mission_type != 4 or global_settings.quality != green. So either the mission is not a secret landing, or if it is, then your opponents better be higher than green. This explains why I was having trouble getting secret landing missions, because my normal test settings were using green opponents. Why does CABE point to opponent quality? Shouldn't we be looking at *(*CACA + 3E) instead? It turns out that CABE is a duplicate, and whatever is stored in (*CACA + 3E) is also stored at CABE during runtime. Lastly at 6f, at least one of the primary or secondary mission_codes better not be a secret airstrip mission. There seem to be a lot of checks against the secret airstrip missions, doesn't it? Anyway, after passing all these checks, we finally make it to address 487D, which as we recall from Part 1, means there are no further changes to the selected mission objectives. 218 | 219 | 220 | ### Putting it all together, and patching the files 221 | 222 | From step 1, we know we need a non-zero value in the wld_obj_table. Which entry, and what value? From step 2, we know the object associated with the secret airstrips is at index 50. 223 | 224 | ![Screenshot](Images/step2.png) 225 | 226 | That means wld_obj_table[50] needs a certain value. From step 5c, we know the value must equal 1, as indicated by mission_table[0].object_flag. For the Persian Gulf, wld_obj_table[50] is at file offset 6BA + 50 = 70A. Inspecting pg.wld at file offset 70A, we see there is indeed a 1 here (no surprise). 227 | 228 | ![Screenshot](Images/pg70A.png) 229 | 230 | Let's expand the table of file offsets to include Libya (lb.wld) and the North Cape (nc.wld). 231 | ``` 232 | address file offset value/size purpose lb off lb v/s nc off nc v/s 233 | --- --- --- --- --- --- --- --- 234 | fread(d88c, 2, 1) 0 584e 0 584a 0 4e42 235 | fread(d33c, 2, 1) 2 40 wld_max_targets 2 25 2 2e 236 | fread(cace, 2, 1) 4 18 4 14 4 13 237 | fread(e084, 2, 1) 6 2a wld_reg_targets 6 21 6 24 238 | fread(cbde, 10, d33c) 8 400 bytes wld_target_data 8 250 b 8 2e0 b 239 | fread(d890, 2, 1) 408 c 258 b 2e8 d 240 | fread(d3be, 24, d890) 40a 1b0 bytes 25a 18c b 2ea 1d4 b 241 | fread(d7f6, 80, 1) 5ba 80 bytes 3e6 80 b 4be 80 b 242 | fread(d33e, 80, 1) 63a 80 bytes 466 80 b 53e 80 b 243 | fread(d2a6, 80, 1) 6ba 80 bytes wld_obj_table 4e6 80 b 5be 80 b 244 | fread(cad8, 1, 100) 73a 100 bytes 566 100 b 63e 100 b 245 | fread(dd70, 1, 2ee) 83a 2ee bytes names 666 2ee b 73e 2ee b 246 | ``` 247 | The secret airstrips in Libya reference object 54 248 | 249 | ![Screenshot](Images/lb1A6.png) 250 | 251 | so lb.wld at file offset 4E6 + 54 = 53A looks like this, after changing the 0 to a 1 (highlighted in red). 252 | 253 | ![Screenshot](Images/lb53A.png) 254 | 255 | Similarly for the North Cape, the secret airstrips reference object 49. Note that I've also changed the 8s to 9s (highlighted in red) for the reasons explained in Part 2. 256 | 257 | ![Screenshot](Images/nc1F6.png) 258 | 259 | So nc.wld at file offset 5BE + 49 = 607 looks like this (after changing the 0 to a 1). 260 | 261 | ![Screenshot](Images/nc607.png) 262 | 263 | We still have the issue of the mission table to deal with, because the theater bitmask only includes the Persian Gulf. In order to allow all 3 theaters to work, we need to change the bitmask from 2 to 7. Where is this mission table? It's actually hardcoded in the executable itself. However, since the executable is packed (with lzexe I believe) you need to find an unpacker. I did so a long time ago, but now I've lost my notes on what I used, so all I can tell you is that the packed size of start.exe is 47100 bytes, and the unpacked size is 90047 bytes. Searching through the unpacked start.exe for 2 0 7 0 4 yields a hit at BF2A, and here's what it looks like after changing the 2s to 7s for the first 4 table entries. 264 | 265 | ![Screenshot](Images/startBF2A.png) 266 | 267 | 268 | ### Conclusion 269 | 270 | Now we are ready - let's load up our patched game and see what happens. Don't forget to set opponent quality to regular or higher. In Libya, we are offered a secret airstrip mission at ONC VC43, and in the North Cape, we are offered one at ONC XX20. 271 | 272 | ![Screenshot](Images/start_020.png) 273 | 274 | ![Screenshot](Images/start_021.png) 275 | 276 | Mission accomplished! It looks like Microprose deliberately removed the secret airstrip missions from Libya and the North Cape, but by changing 8 bytes in 3 files, we can get the option again. 277 | 278 | [Part 4 - Fixing F-19 as well](./Part4.md) 279 | --------------------------------------------------------------------------------