Advertisement
Guest User

Instapasses

a guest
Sep 23rd, 2015
1,339
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.43 KB | None | 0 0
  1. How instapasses work and how to find them in game script
  2.  
  3. I'll start with basic info on game script (main.scm) and how it works.
  4. The most important for this trick part of game script is threads code. Basically script is split into a pretty big number of "threads" - which run in quasiparallel mode. Game updates everything once per frame, including all the script threads. When main loop gives control to script processor, it starts interpreting each running thread of current gamestate. Game keeps instruction pointer (which point a thread should be executed from), local variables and 2 timers in memory and also in a save if the game is saved. The processor executes instructions starting from saved instruction pointer until it reaches "wait" opcode. "wait" has a parameter, time during which game doesn't execute any instructions of given thread. If it is 0, then game will continue executing it during next frame processing.
  5.  
  6. Some of these threads are unusual, they are espescially marked as mission threads. They are created by special command, that differs from regular thread creation. And as it was found out that game only keeps 1 mission thread in memory, a fact which will be used a bit later.
  7.  
  8. So how a thread look like? Luckily we know exactly how developers saw it. Original script files were left over in mobile version of GTA III, so here they are:
  9. http://gtamodding.ru/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F%3A%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8
  10. Basically, usual mission script looks like a bunch of loops that end on some events (mission targets) with creation of objects between them and with checks inside them. Mission ends with three labels: mission_failed, mission_passed and mission_cleanup. Last of them is supposed to be executed in any case, as well as one of first two. Mission starting code is very small. It calls main mission code, which basically is all mission logic, and receives control back in one of two cases. First one is "return" instruction of the main mission code and second one happens if player was busted or wasted and this script is marked as active mission thread (there is only one such a thread at any point of the game). So game checks if player was busted or wasted, calls mission_failed subroutine and then in any case makes a cleanup.
  11.  
  12. Now let's get to the point. If a mission is started while the other mission is still running, an interesting thing happens. As stated above, any thread keeps instruction pointer. It keeps an offset from the beginning of its thread at least for mission threads. Mission thread just adds the offset to current active mission base address (start address). Then during one of frames a new mission has started. The active mission base address has changed and now game will try to execute the instruction at the same offset as it was originally for old mission, but the base address will now be different, so it will execute instructions of newly run mission.
  13.  
  14. Let's get to examples. We'll take Taxi Driver instapass, which was found by Powdinet. I use SCRLog to get log of the script to show what had happened.
  15.  
  16. http://i.imgur.com/ySTQmz0.png
  17.  
  18. This is a script log from a last frame that was still running Cipriani's Chauffeur. It starts with offset 6064 as we see for the first instruction at the left. Luckily for us, Sanny Builder shows the label as offsets from the start of the thread. Let's see how it looks like there. We can use Lightnat0r's version with variable names from original script.
  19. https://raw.githubusercontent.com/Lighnat0r/GTA-III-SCM-Converted/master/GTA%203%20Main%20Variables%20Named.txt
  20.  
  21. Here is the fragment that starts at the offset 6008:
  22.  
  23. :JOEY4_6008
  24. 00D6: if or
  25. 81A0: not player $PLAYER_CHAR stopped $BLOB_FLAG 1215.0 -326.875 25.0 1220.188 -330.5 27.0
  26. 010F: player $PLAYER_CHAR wanted_level > 0
  27. 80DC: not is_player_in_car $PLAYER_CHAR car $TONIS_RIDE
  28. 004D: goto_if_false @JOEY4_6683
  29. 0001: wait 0 ms
  30. 00D6: if
  31. 0118: actor $TONI_CIPRIANI dead
  32. 004D: goto_if_false @JOEY4_6102
  33.  
  34. What do we see? The "wait 0" command, that stopped previous script execution is obviously is this fragment. By the starting condition we can guess that that this is the loop that waits for player to be at the ending marker in Toni's car with no wanted level. But let's find this loop in the original script to make it clear.
  35.  
  36. WHILE NOT IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D player 1215.0 -326.9 25.0 1220.2 -330.5 27.0 blob_flag
  37. OR IS_WANTED_LEVEL_GREATER Player 0
  38. OR NOT IS_PLAYER_IN_CAR player tonis_ride
  39. WAIT 0
  40.  
  41. IF IS_CHAR_DEAD toni
  42. PRINT_NOW ( JM4_8 ) 5000 1
  43. GOTO mission_joey4_failed
  44. ENDIF
  45.  
  46. //...
  47.  
  48. Indeed it is. So this is the loop after cutscene at laudromat.
  49. The loop stops at the same offset, since player still didn't arrive.
  50.  
  51. What happens next? We start the mission, and here is what the log says:
  52.  
  53. http://i.imgur.com/8TJ5TNw.png
  54.  
  55. So, game launched mission 14 (which is Taxi Driver). It changes the active mission script. Let's see what happens with our copy.
  56.  
  57. http://i.imgur.com/6v49lP2.png
  58.  
  59. Well, the offset is the same, but the instruction is different. It's actually an unknown instruction which is interpreted as "wait 0" command. Let's omit some unknown instructions until something relevant.
  60.  
  61. http://i.imgur.com/BqCaaSw.png
  62.  
  63. This makes more sense. "player_made_progress" implies that this is Taxi Driver passing section. Let's have a look on its script.
  64.  
  65. :TAXI_5987
  66. 0109: player $PLAYER_CHAR money += $SCORE_FOR_THIS_FARE
  67. 01E3: text_1number_styled 'TSCORE2' number $SCORE_FOR_THIS_FARE duration 6000 ms style 6 // $~1~
  68. 018C: play_sound 94 at 0.0 0.0 0.0
  69. 0058: $TAXI_SCORE += $SCORE_FOR_THIS_FARE
  70. 0008: $TAXI_MISSION_DELIVERIES += 1
  71. 0315: increment_taxi_dropoffs
  72. 0008: $TAXI_PASSED_THIS_SHOT += 1
  73. 00D6: if and
  74. 0038: $NEW_TAXI_CREATED_BEFORE == 0
  75. 0038: $TAXI_MISSION_DELIVERIES == 100
  76. 004D: goto_if_false @TAXI_6110
  77. 014D: text_pager 'NEW_TAX' 140 2 0 // BIGGER! FASTER! HARDER! new Borgnine taxis open for business in Harwood. Call 555-BORGNINE today!
  78. 014C: set_parked_car_generator $SWANK_TAXI cars_to_generate_to 101
  79. 030C: set_mission_points += 1
  80. 0004: $NEW_TAXI_CREATED_BEFORE = 1
  81.  
  82. :TAXI_6110
  83. 0008: $TAXI_COUNTDOWN += 10000
  84. 0050: gosub @TAXI_6484
  85. 00D6: if
  86. 003A: $TAXI_PASSED_THIS_SHOT == $IN_A_ROW_NUMBER
  87. 004D: goto_if_false @TAXI_6196
  88. 036D: text_2numbers_styled 'IN_ROW' numbers $TAXI_PASSED_THIS_SHOT $IN_A_ROW_CASH duration 5000 ms style 6 // ~1~ IN A ROW bonus! $~1~
  89. 0109: player $PLAYER_CHAR money += $IN_A_ROW_CASH
  90. 0058: $TAXI_SCORE += $IN_A_ROW_CASH
  91. 0008: $IN_A_ROW_NUMBER += 5
  92. 0008: $IN_A_ROW_CASH += 2000
  93.  
  94. So we started at "004D: goto_if_false @TAXI_6110" instruction. So we skipped the check for number of deliveries and started right from the point of reward. Nice!
  95.  
  96. Now to the explaination of how to find new instapasses. I will explain on trivial ways of doing so. The example is "Ride in the Park" instapass.
  97.  
  98. Let's start with script of this mission, a part that rewards player.
  99.  
  100.  
  101. :T4X4_2_3159
  102. 00D6: if
  103. 0038: $A_RIDE_IN_THE_PARK_COMPLETED == 0
  104. 004D: goto_if_false @T4X4_2_3203
  105. 0004: $A_RIDE_IN_THE_PARK_BEST_TIME = 120000
  106. 0060: $A_RIDE_IN_THE_PARK_BEST_TIME -= $TIMER_4X4
  107. 0014: $A_RIDE_IN_THE_PARK_BEST_TIME /= 1000
  108.  
  109. :T4X4_2_3203
  110. 00D6: if
  111. 0038: $A_RIDE_IN_THE_PARK_COMPLETED == 1
  112. 004D: goto_if_false @T4X4_2_3274
  113. 0004: $RECORD_TEMP = 120000
  114. 0060: $RECORD_TEMP -= $TIMER_4X4
  115. 0014: $RECORD_TEMP /= 1000
  116. 00D6: if
  117. 001C: $A_RIDE_IN_THE_PARK_BEST_TIME > $RECORD_TEMP
  118. 004D: goto_if_false @T4X4_2_3274
  119. 0084: $A_RIDE_IN_THE_PARK_BEST_TIME = $RECORD_TEMP
  120.  
  121. :T4X4_2_3274
  122. 01E3: text_1number_styled 'M_PASS' number 30000 duration 5000 ms style 1 // MISSION PASSED! $~1~
  123. 0394: play_mission_passed_music 1
  124. 0110: clear_player $PLAYER_CHAR wanted_level
  125. 0109: player $PLAYER_CHAR money += 30000
  126. 03FE: save_offroadII_time $A_RIDE_IN_THE_PARK_BEST_TIME
  127. 00D6: if
  128. 0038: $A_RIDE_IN_THE_PARK_COMPLETED == 0
  129. 004D: goto_if_false @T4X4_2_3353
  130. 0318: set_latest_mission_passed 'T4X4_2' // 'A RIDE IN THE PARK'
  131. 0004: $A_RIDE_IN_THE_PARK_COMPLETED = 1
  132. 030C: set_mission_points += 1
  133.  
  134. :T4X4_2_3353
  135.  
  136. Okay, so for mission to count (opcode 0318 for AM, 030C for 100%) we need to find a loop at approximately 3159 to 3330 offset. Let's find suspicious loops.
  137.  
  138. Here is first assumption: a loop in Arms Shortage. Here we go:
  139.  
  140. :RAY2_3142
  141. 00D6: if
  142. 001A: 12 > $COUNTER_DEAD_VARMINTS
  143. 004D: goto_if_false @RAY2_4907
  144. 0001: wait 0 ms
  145. 01BD: $TIMER_NOW_RM2 = current_time_in_ms
  146. 0084: $TIMER_DIF_RM2 = $TIMER_NOW_RM2
  147. 0060: $TIMER_DIF_RM2 -= $TIMER_START_RM2
  148. 0050: gosub @RAY2_9333
  149. 00D6: if and
  150. 0018: $TIMER_DIF_RM2 > 2000
  151. 0038: $FLAG_SENTINEL_CREATED == 0
  152. 004D: goto_if_false @RAY2_3347
  153. 00A5: $SENTINEL1_RM2 = create_car #COLUMB at $VARMINT_GEN1_X $VARMINT_GEN1_Y -100.0
  154. 02AA: set_car $SENTINEL1_RM2 immune_to_nonplayer 1
  155. 020A: set_car $SENTINEL1_RM2 door_status_to 2
  156. 0186: $BLIP_SENTINEL1 = create_marker_above_car $SENTINEL1_RM2
  157. 0129: $VARMINT_1 = create_actor 12 #GANG12 in_car $SENTINEL1_RM2 driverseat
  158. 01C8: $VARMINT_2 = create_actor 12 model #GANG12 in_car $SENTINEL1_RM2 passenger_seat 0
  159. 01C8: $VARMINT_3 = create_actor 12 model #GANG12 in_car $SENTINEL1_RM2 passenger_seat 1
  160. 01C8: $VARMINT_4 = create_actor 12 model #GANG11 in_car $SENTINEL1_RM2 passenger_seat 2
  161. 00AD: set_car_cruise_speed $SENTINEL1_RM2 to 10.0
  162. 00AE: set_car_driving_style $SENTINEL1_RM2 to 3
  163. 02C2: car $SENTINEL1_RM2 drive_to_point $STAGE_1_X $STAGE_1_Y 11.5625
  164. 0004: $FLAG_SENTINEL_CREATED = 1
  165.  
  166. Easily it is recognised as a loop that waits until we kill all enemies after their spawn. Its "wait 0" is somewhere after 3159, so let's have a look...
  167.  
  168. Game crashed. Why did it happen?
  169. This time I omit normal old mission's loop, will only note that the offset was 3164 - kinda what we needed. But unfortunately there is not an opcode at this offset in the SCM, it's just a data interpreted as opcode, much like in Taxi Driver instapass, but with worse circumstances.
  170.  
  171. [0200] LOCATE_PLAYER_ON_FOOT_CAR_3D [UNKNOWN] 4 -725.375 22.75 -1.#QNAN 0
  172.  
  173. This "unknown" ruins it all. Game tries to read a parameter which it interprets as a string. It uses its address as a index in players' array and goes way out of bounds, out of its own memory. RIP :(
  174.  
  175. I will also add that this mission is instapassed with Grand Theft Aero from this loop:
  176.  
  177. :LOVE4_3219
  178. 00D6: if
  179. 80DC: not is_player_in_car $PLAYER_CHAR car $WINGLESS_CESSNA
  180. 004D: goto_if_false @LOVE4_3325
  181. 0001: wait 0 ms
  182. 00D6: if
  183. 0119: car $WINGLESS_CESSNA wrecked
  184. 004D: goto_if_false @LOVE4_3280
  185. 00BC: print_now 'LOVE4_9' duration 5000 ms flag 1 // ~r~The plane has been destroyed!
  186. 0002: goto @LOVE4_9982
  187.  
  188. Which is somewhere at 3240, which is also is within our bounds. Instapass works.
  189.  
  190. This is how it is possible to look for obvious instapasses. They work exactly the same way in VC. It is also possible to instapass missions that are further is mission code if loop offset is further that the last instruction of new mission.
  191.  
  192. Also there may be skips of some missions' parts, if we hit right into loop of a new mission and there will be no conflict (like not created objects). In some cases it will still be instapass, like Gone Fishing. This instapass actually jumps right into one of instructions sequence after which enters a loop. It checks for a boat to be destroyed. However since it was never created, it decides that mission objective is passed and completes the mission. This is not so obvious instapass and isn't very easy to look for.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement