Amineri Posted April 3, 2013 Share Posted April 3, 2013 (edited) So, looking through the 2K forums, and then looking through the code itself, I've come to realize that there is a fairly glaring logical bug in the game.Essentially, if you abort a mission (have some squad members in the Skyranger evac area and choose abort), any squad members left behind will be marked as m_bLeftBehind in the tactical game, and will then be marked as m_bMIA upon returning to base. Any such MIA soldiers have their equipment destroyed -- it does not return to the barracks.However, if the mission is entirely lost, the squad members are NOT marked as LeftBehind, do NOT get marked as MIA, and consequently none of their gear is lost. It's a terrible thing!I think this should be a fixable thing by modding -- and then it could be incorporated into any mods (or distributed as a standalone for those that don't wish to wait for Firaxis to patch it).UPDATE: A fix has been found to correct this issue. I've made a version of the mod that is compatible with the new version 1.02 of ToolBoks (am so very glad that bokauk is back and about :smile:UPDATE2: The 1.0 version accidentally had hex code that would cause the game to hang upon starting a new mission! (a Notepad++ glitch caused me to copy out the wrong chunk of hex code ~_~). The new version 1.1 (updated below) should be correct. MOD_NAME=Mission Loss No Equipment Loss Bug FixAUTHOR=amineriUPK_FILE=XComGame.upkDESCRIPTION=Unofficial fix - If mission is failed due to all XCOM dead or critically wounded, all equipment equipped on the squad will be lost when the skyranger returns to base. Version: 1.1 Works with XCOM Enemy Unknown version: Patch 4 ( Wednesday, February 20, 2013 12:47 PM - Changelist: 356266 ) [FIND]EA 9E 00 00 50 55 00 00 00 00 00 00 CB 9E 00 00 00 00 00 00 00 00 00 00 D5 9E 00 00 00 00 00 00 BD 02 00 00 29 53 00 00 3E 05 00 00 D2 03 00 00 0F 00 D1 9E 00 00 19 1B 00 31 00 00 00 00 00 00 16 0A 00 56 94 00 00 00 1B C5 34 00 00 00 00 00 00 16 07 0A 01 77 00 D1 9E 00 00 2A 16 0F 00 D4 9E 00 00 25 07 0A 01 96 00 D4 9E 00 00 19 00 D1 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 16 0F 00 D3 9E 00 00 19 00 D1 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 FC 00 82 82 77 00 D3 9E 00 00 2A 16 18 20 00 19 00 D3 9E 00 00 0A 00 FF 32 00 00 00 1B D1 3C 00 00 00 00 00 00 16 16 18 20 00 19 00 D3 9E 00 00 0A 00 CD B5 00 00 00 1B 97 3E 00 00 00 00 00 00 16 16 04 28 A5 00 D4 9E 00 00 16 06 44 00 19 19 01 E9 F9 FF FF 0A 00 9B F9 FF FF 00 1C 67 FA FF FF 16 1D 00 00 00 00 00 00 1C AF FA FF FF 20 A2 0A 00 00 27 00 D0 9E 00 00 16 58 00 D0 9E 00 00 00 CF 9E 00 00 00 4A 98 01 07 97 01 19 2E A2 0A 00 00 00 CF 9E 00 00 0A 00 A0 0A 00 00 00 2D 01 A0 0A 00 00 30 04 27 31 30 0F 00 D2 9E 00 00 19 1B AE 32 00 00 00 00 00 00 16 0A 00 56 94 00 00 00 1B C5 34 00 00 00 00 00 00 16 07 32 03 9A 19 01 A3 9C 00 00 09 00 B0 9F 00 00 00 01 B0 9F 00 00 2C 08 16 0F 00 D4 9E 00 00 25 07 32 03 96 00 D4 9E 00 00 19 00 D2 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 24 03 82 77 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 2A 16 18 55 00 9A 35 37 0E 00 00 3D 0E 00 00 00 00 19 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 09 00 79 A3 00 00 00 01 79 A3 00 00 2C 04 16 16 07 21 03 84 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 18 20 00 19 00 D3 9E 00 00 0A 00 B9 32 00 00 00 1B 19 3D 00 00 00 00 00 00 16 16 04 27 06 32 03 A5 00 D4 9E 00 00 16 06 F3 01 0F 00 D4 9E 00 00 25 07 5A 04 96 00 D4 9E 00 00 19 00 D2 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 4C 04 82 82 81 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 16 18 22 00 81 19 00 D3 9E 00 00 0A 00 F6 30 00 00 00 2D 01 F6 30 00 00 16 16 18 22 00 81 19 00 D3 9E 00 00 0A 00 B9 32 00 00 00 1B 19 3D 00 00 00 00 00 00 16 16 16 07 4A 04 77 19 00 D3 9E 00 00 0A 00 7E B6 00 00 00 1B 25 34 00 00 00 00 00 00 16 2A 16 55 00 CE 9E 00 00 0A 00 00 D3 9E 00 00 16 06 4C 04 04 28 A5 00 D4 9E 00 00 16 06 3D 03 07 7F 04 9A 19 00 D2 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 25 16 07 31 05 97 36 00 CE 9E 00 00 25 16 58 00 CE 9E 00 00 00 D3 9E 00 00 00 4A 2E 05 19 19 00 D3 9E 00 00 0A 00 12 33 00 00 00 1B EA 33 00 00 00 00 00 00 16 5C 00 00 00 00 00 00 1B 1D 69 00 00 00 00 00 00 12 20 5C 4A 00 00 3D 00 45 4A 00 00 00 1B 51 13 00 00 00 00 00 00 1D 88 13 00 00 00 D3 9E 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 20 AC 4A 00 00 4A 4A 16 16 31 30 04 28 04 27 04 3A D5 9E 00 00 53 [REPLACE]EA 9E 00 00 50 55 00 00 00 00 00 00 CB 9E 00 00 00 00 00 00 00 00 00 00 D5 9E 00 00 00 00 00 00 BD 02 00 00 29 53 00 00 6A 05 00 00 D2 03 00 00 0F 00 D1 9E 00 00 19 1B 00 31 00 00 00 00 00 00 16 09 00 E3 93 00 00 00 01 E3 93 00 00 07 08 01 77 00 D1 9E 00 00 2A 16 0F 00 D4 9E 00 00 25 07 08 01 96 00 D4 9E 00 00 19 00 D1 9E 00 00 09 00 35 AC 00 00 00 01 35 AC 00 00 16 0F 00 D3 9E 00 00 19 00 D1 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 FA 00 82 82 77 00 D3 9E 00 00 2A 16 18 20 00 19 00 D3 9E 00 00 0A 00 FF 32 00 00 00 1B D1 3C 00 00 00 00 00 00 16 16 18 20 00 19 00 D3 9E 00 00 0A 00 CD B5 00 00 00 1B 97 3E 00 00 00 00 00 00 16 16 04 28 A5 00 D4 9E 00 00 16 06 43 00 19 19 01 E9 F9 FF FF 0A 00 9B F9 FF FF 00 1C 67 FA FF FF 16 1D 00 00 00 00 00 00 1C AF FA FF FF 20 A2 0A 00 00 27 00 D0 9E 00 00 16 58 00 D0 9E 00 00 00 CF 9E 00 00 00 4A 96 01 07 95 01 19 2E A2 0A 00 00 00 CF 9E 00 00 0A 00 A0 0A 00 00 00 2D 01 A0 0A 00 00 30 04 27 31 30 0F 00 D2 9E 00 00 19 1B AE 32 00 00 00 00 00 00 16 09 00 E3 93 00 00 00 01 E3 93 00 00 0F 01 65 9E 00 00 19 00 D2 9E 00 00 09 00 35 AC 00 00 00 01 35 AC 00 00 07 1D 03 9A 19 01 A3 9C 00 00 09 00 B0 9F 00 00 00 01 B0 9F 00 00 2C 08 16 0F 00 D4 9E 00 00 25 07 1D 03 96 00 D4 9E 00 00 01 65 9E 00 00 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 0F 03 82 77 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 2A 16 18 55 00 9A 35 37 0E 00 00 3D 0E 00 00 00 00 19 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 09 00 79 A3 00 00 00 01 79 A3 00 00 2C 04 16 16 07 0C 03 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 04 27 06 1D 03 A5 00 D4 9E 00 00 16 06 18 02 07 2C 03 2D 01 68 9E 00 00 04 27 0F 00 D4 9E 00 00 25 07 18 04 96 00 D4 9E 00 00 01 65 9E 00 00 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 0A 04 82 81 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 16 18 22 00 81 19 00 D3 9E 00 00 0A 00 B9 32 00 00 00 1B 19 3D 00 00 00 00 00 00 16 16 16 07 08 04 77 19 00 D3 9E 00 00 0A 00 7E B6 00 00 00 1B 25 34 00 00 00 00 00 00 16 2A 16 55 00 CE 9E 00 00 0A 00 00 D3 9E 00 00 16 06 0A 04 04 28 A5 00 D4 9E 00 00 16 06 37 03 07 C7 04 97 36 00 CE 9E 00 00 25 16 58 00 CE 9E 00 00 00 D3 9E 00 00 00 4A C4 04 19 19 00 D3 9E 00 00 0A 00 12 33 00 00 00 1B EA 33 00 00 00 00 00 00 16 5C 00 00 00 00 00 00 1B 1D 69 00 00 00 00 00 00 12 20 5C 4A 00 00 3D 00 45 4A 00 00 00 1B 51 13 00 00 00 00 00 00 2C FF 00 D3 9E 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 20 AC 4A 00 00 4A 4A 16 16 31 30 04 28 0F 00 D4 9E 00 00 25 07 4A 05 96 00 D4 9E 00 00 01 65 9E 00 00 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 14 19 00 D3 9E 00 00 0A 00 92 AF 00 00 00 2D 01 92 AF 00 00 27 A5 00 D4 9E 00 00 16 06 D2 04 0F 01 65 9E 00 00 25 04 27 0B 0B 0B 0B 0B 0B 0B 0B 04 3A D5 9E 00 00 53 So far the mod has been verified to correctly:1) Destroy equipment when a squad is completely lost2) Behave correctly on Temple Ship mission -- mission is lost if volunteer diesNeeds to be verified:1) Terror mission loss due to civilians all dead does not cause equipment loss2) Council mission loss due to VIP dead does not cause equipment loss3) Soldiers that escape to dropzone if mission is aborted do not get equipment loss---------------------------------------------------------------------------------------------------(old stuff)I've been monkeying in this area (in order to make SHIVs weapons follow the same rules as soldier gear in terms of destruction). I know that anUser has been messing with this code also, in order to make the small items consumable. I see two possible ways to correct this.1) When a mission is failed, all soldiers are marked as m_bLeftBehind. They will then be marked as MIA and their gear destroyed. I think this is the preferred option, but I'm not 100% sure where the change should be made. The best guess I have so far is in the XGSummaryUI().Init(). This appears to be where the alien artifacts are collected, and data is transferred into the StrategyGameTransport to be shuttled back to the strategy game.2) Upon returning, if the mission was failed, all soldiers are marked as MIA. This would have to happen in XGFacility_Barracks().ClearSquad().Any suggestions would be appreciated. :smile: Edited April 27, 2013 by Amineri Link to comment Share on other sites More sharing options...
anUser Posted April 3, 2013 Share Posted April 3, 2013 I think this "mission is entirely lost" or "mission failed" need a clarification, because you can loose terror mossions if every civilian is killed, or if you fail to save the VIP or defuse the bomb, so the check shouldn't be for "objective failed" but rather for "every soldier is dead"... if that's what you meant then it makes complete sense to mark them as MIA since there's nobody left to pick the bodies. Link to comment Share on other sites More sharing options...
Amineri Posted April 3, 2013 Author Share Posted April 3, 2013 Right .... That's a good point -- I hadn't considered other mission failure modes. In particular, if you fail the mission because you abort, you don't want to overwrite ALL of the soldiers as MIA. In the mission abort case, soldiers are already correctly marked. However, both missions that are failed due to all soldiers being dead, and missions failed due to aborting end up with the same m_iMissionResult = 2 assignment. I'm not sure about the special mission results. Will have to look into those, too. Link to comment Share on other sites More sharing options...
Amineri Posted April 4, 2013 Author Share Posted April 4, 2013 I've getting closer to a fix, but could use some additional eyes on the problem :smile:. Here is the issue:1) When aborting a mission, there is a quick loop over all XCOM units. This loop: a) Sets the unit's m_bOffTheBattlefield = true; b) Sets the unit's m_bLeftBehind = !m_bInDropShip;2) It then goes on to call XGBattle_SP.CheckForVictory(), which checks both IsVictory() and IsDefeat() conditions, which is the same check used to determine defeat-via-death. This means that aborted missions and defeat-via-death end up looking very similar after the IsDefeat() call is made. The only distinction is that the XGBattle_SP class boolean m_bAbandoned is true in one case, and false in the other. Unfortunately, the code immediately jumps to the XGBattle.'Done' state after IsDefeat() returns true, leaving very little room to fix things. So, I started looking at the IsDefeat() call itself, to see if it could mark the m_bLeftBehind flag under the correct circumstances. IsDefeat() does the following, in order:1) If any alien is still alive and "busy", returns false2) Checks for special-mission-failure via if(SeqAct_LoseBattle(kSeq).bLoseMission) -- returns true if special mission failed3) If on the temple mission, checks the volunteer XCOM soldier. If that unit is dead or critically wounded, returns true.4) Loops over all XCOM units and returns false if any unit meets all of the following: a) Unit is not dead b) Unit is not off the battlefield c) Unit is not critically wounded d) Unit is not possessed (mind controlled)5) Loops over all possessed XCOM units and kills them6) returns true by default After step 4 is passed, the mission is failed -- either all units are dead/etc, or the mission is aborted. Special mission failures will cause the function to return after step 2. Since IsDefeat() is a XGBattle_SP class function, the boolean m_bAbandoned is available, so the all-dead/etc case can be separated from the aborted case. I don't think there is any other point in the code where this distinction can be made. If something like the following code snippet could be inserted after step 5, but before the function returns, I think it should correctly set the m_bLeftBehind flag for all XCOM units, only in the case that the mission was failed due to all units being dead/critically-wounded/mind-controlled. if(!m_bAbandoned) -- 10 bytes{ I= 0; -- 8 bytes J0x33d: // End:0x45a Loop:Trueif(I< kSquad.GetNumPermanentMembers()) -- 33 bytes{kUnit = kSquad.GetPermanentMemberAt(I); -- 34 byteskUnit.m_bLeftBehind = true; -- 21 bytes++ I; -- 1 + 5 + 1 = 7 bytes// This is an implied JumpToken; Continue!goto J0x33d; -- 3 bytes}} This comes out to a total of 116 bytes that would have to be added. Unfortunately, I don't see where the space can come from. There is an extra call "if(kSquad.GetNumPermanentMembers() == 0) { }", but it only frees up 29 bytes, well short of what is needed. I'm not sure where to come up with the extra space... Link to comment Share on other sites More sharing options...
anUser Posted April 4, 2013 Share Posted April 4, 2013 I've checked adjacent functions in the hex (IsDeadOrDying and IsDefensiveAbility according to UE Explorer Table view) in case a portion of them could be stolen for this function (that's something we'll eventualy have to learn how to do, if it's possible at all) but they are quite short and they seem pretty straight-forward so no luck there. I'll throw in a different approach, I let it to you to judge it convinient or not... since the porpouse of this changes (ie marking the units as MIA if they all die) is to remove equipment, and presumably it will only affect soldier's equipment (it's not expected to have any effect on the memorial or anything else) maybe you could jump straight to where equipment is removed and perform a custom check. I'm thinking of XGFacility_Barracks.UnloadSoldier() ... there is a check to see whether the unit is in the Skyranger, ... assuming this function is called for every unit sent to battle or it can be made so, I see an else statement calling RemoveLoadout(kSoldier). Link to comment Share on other sites More sharing options...
Amineri Posted April 5, 2013 Author Share Posted April 5, 2013 I've checked adjacent functions in the hex (IsDeadOrDying and IsDefensiveAbility according to UE Explorer Table view) in case a portion of them could be stolen for this function (that's something we'll eventualy have to learn how to do, if it's possible at all) but they are quite short and they seem pretty straight-forward so no luck there. I'll throw in a different approach, I let it to you to judge it convinient or not... since the porpouse of this changes (ie marking the units as MIA if they all die) is to remove equipment, and presumably it will only affect soldier's equipment (it's not expected to have any effect on the memorial or anything else) maybe you could jump straight to where equipment is removed and perform a custom check. I'm thinking of XGFacility_Barracks.UnloadSoldier() ... there is a check to see whether the unit is in the Skyranger, ... assuming this function is called for every unit sent to battle or it can be made so, I see an else statement calling RemoveLoadout(kSoldier). Thanks for the idea, anUser. If I understand your suggestion, I don't think I can make it work. The squad unload function is called in two cases : (1) a mission has just been completed (2) the user has cancelled out of the "squad select" screen. When returning from a mission, every soldier is still in the StrategyGameTransport structure. Their equipment is not removed from the STORAGE() when it is assigned -- it is merely claimed. The UnloadSoldier() function is what does whatever cleanup / garbage-collection is necessary for returning soldiers. Healthy soldiers are returned to the barracks as-is, dead and wounded soldiers have their equipment unequipped and unclaimed, MIA soldiers (or dead soldiers if Total Loss is enabled) have their equipment unequipped, unclaimed, and deleted from storage. I'm not sure how to distinguish -at that point- between a dead soldier that is "dead normally" and one that is "dead because of mission fail because of complete squad destruction". I think I figured out how to squeeze the code in. Next post has details! Link to comment Share on other sites More sharing options...
Amineri Posted April 5, 2013 Author Share Posted April 5, 2013 (edited) I think I've managed to squeeze out the necessary bytes. A whole host of little changes (some as small as saving 2 bytes!) that add up to the (I think/hope/pray) necessary number. I went a bit over, so there should be a bit of slop. Here is what I'm doing to save space: Total Space freed up -- 132 bytes consolidating checks to kSquad.GetNumPermanentMembers() -- 25 bytesif(I < kSquad.GetNumPermanentMembers()) will be called three times (will be three loops)setting kSquad.GetNumPermanentMembers to a local variable and using that will save the bytesremoving extraneous call -- 29 bytescompletely unnecessary coderemove volunteer is critical check -- 28 bytesin OnTakeDamage(), the volunteer is prevented from being critically wounded, so this check can be removedskip abandon/off-battlefield check -- 26 bytesremove m_bOffTheBattlefield check -- earlier, directly check m_bAbandonedfactoring boolean -- 2 byteschange : !A && !B to !(A || B) which is logically identicalreplace possessed check -- 3 bytescan simplify (kUnit.GetPossessedBy() != none) (26 bytes) to (kUnit.IsPossessed())change 5000 damage to 255 damage -- 3 bytespossessed units at end are killed with 5000 damage -- changing to 255 damage saves 3 bytesreplace GetNumPermanentMembers() call with direct access -- 8 byteskSquad.GetNumPermanentMembers() with kSquad.m_iNumPermanentUnitsreplace GetSquad() call with direct access -- 8 bytesGetSquad() with m_kSquad This should free up enough space for the 117 bytes needed for the original code. Here is what my revised function looks like: function bool IsDefeat(){ local int I; local XGUnit kUnit; local XGSquad kSquad, kAISquad; local array<SequenceObject> akSequences; local SequenceObject kSeq; local array<XGUnit> arrMindControlledXcoms; kAISquad = GetAIPlayer().m_kSquad; // End:0x10A if(kAISquad != none) { I = 0; J0x44: // End:0x10A [Loop If] if(I < kAISquad.m_iNumPermanentUnits) { kUnit = kAISquad.GetPermanentMemberAt(I); // End:0xFC if(((kUnit != none) && kUnit.IsAliveAndWell()) && kUnit.IsUnitBusy()) { return false; } ++ I; // [Loop Continue] goto J0x44; } } WorldInfo.GetGameSequence().FindSeqObjectsByClass(class'SeqAct_LoseBattle', true, akSequences); // End:0x198 foreach akSequences(kSeq) { // End:0x197 if(SeqAct_LoseBattle(kSeq).bLoseMission) { return true; } } kSquad = GetHumanPlayer().m_kSquad; m_iResult = kSquad.GetNumPermanentMembers(); // End:0x332 if(m_kDesc.m_iMissionType == 8 ) { I = 0; J0x1F3: // End:0x332 [Loop If] if(I < m_iResult) { kUnit = kSquad.GetPermanentMemberAt(I); // End:0x324 if((XGCharacter_Soldier(kUnit.GetCharacter()) != none) && XGCharacter_Soldier(kUnit.GetCharacter()).m_kSoldier.iPsiRank == 4) { // End:0x321 if(kUnit.IsDead()) { return true; } // [Explicit Break] goto J0x332; } ++ I; J0x332: // [Loop Continue] goto J0x1F3; } } if(m_bAbandoned) { return true; } I = 0; J0x33D: // End:0x45A [Loop If] if(I < m_iResult) { kUnit = kSquad.GetPermanentMemberAt(I); // End:0x44C if((!(kUnit.IsDead() || kUnit.IsCriticallyWounded())) { // End:0x44A if(kUnit.IsPossessed) { arrMindControlledXcoms.AddItem(kUnit); } // End:0x44C else { return false; } } ++ I; // [Loop Continue] goto J0x33D; } // End:0x47F // End:0x531 if(arrMindControlledXcoms.Length > 0) { // End:0x52E foreach arrMindControlledXcoms(kUnit) { kUnit.GetPawn().TakeDirectDamage(class'XComDamageType'.static.CreateEvent(255, kUnit, vect(0.0, 0.0, 0.0), vect(0.0, 0.0, 0.0), class'XComDamageType_Psionic')); } return false; } I = 0; J0x555: // End:0x45a Loop:True if(I < m_iResult) { kUnit = kSquad.GetPermanentMemberAt(I); kUnit.m_bLeftBehind = true; ++ I; // This is an implied JumpToken; Continue! goto J0x555; } m_iResult = 0 ; return true; //return ReturnValue; } Here is the original decompiled code:function bool IsDefeat(){ local int I; local XGUnit kUnit; local XGSquad kSquad, kAISquad; local array<SequenceObject> akSequences; local SequenceObject kSeq; local array<XGUnit> arrMindControlledXcoms; kAISquad = GetAIPlayer().GetSquad(); // End:0x10A if(kAISquad != none) { I = 0; J0x44: // End:0x10A [Loop If] if(I < kAISquad.GetNumPermanentMembers()) { kUnit = kAISquad.GetPermanentMemberAt(I); // End:0xFC if(((kUnit != none) && kUnit.IsAliveAndWell()) && kUnit.IsUnitBusy()) { return false; } ++ I; // [Loop Continue] goto J0x44; } } WorldInfo.GetGameSequence().FindSeqObjectsByClass(class'SeqAct_LoseBattle', true, akSequences); // End:0x198 foreach akSequences(kSeq) { // End:0x197 if(SeqAct_LoseBattle(kSeq).bLoseMission) { return true; } } kSquad = GetHumanPlayer().GetSquad(); // End:0x332 if(m_kDesc.m_iMissionType == 8) { I = 0; J0x1F3: // End:0x332 [Loop If] if(I < kSquad.GetNumPermanentMembers()) { kUnit = kSquad.GetPermanentMemberAt(I); // End:0x324 if((XGCharacter_Soldier(kUnit.GetCharacter()) != none) && XGCharacter_Soldier(kUnit.GetCharacter()).m_kSoldier.iPsiRank == 4) { // End:0x321 if(kUnit.IsDead() || kUnit.IsCriticallyWounded()) { return true; } // [Explicit Break] goto J0x332; } ++ I; J0x332: // [Loop Continue] goto J0x1F3; } } I = 0; J0x33D: // End:0x45A [Loop If] if(I < kSquad.GetNumPermanentMembers()) { kUnit = kSquad.GetPermanentMemberAt(I); // End:0x44C if((!kUnit.IsDead() && !kUnit.m_bOffTheBattlefield) && !kUnit.IsCriticallyWounded()) { // End:0x44A if(kUnit.GetPossessedBy() != none) { arrMindControlledXcoms.AddItem(kUnit); } // End:0x44C else { return false; } } ++ I; // [Loop Continue] goto J0x33D; } // End:0x47F if(kSquad.GetNumPermanentMembers() == 0) { } // End:0x531 if(arrMindControlledXcoms.Length > 0) { // End:0x52E foreach arrMindControlledXcoms(kUnit) { kUnit.GetPawn().TakeDirectDamage(class'XComDamageType'.static.CreateEvent(5000, kUnit, vect(0.0, 0.0, 0.0), vect(0.0, 0.0, 0.0), class'XComDamageType_Psionic')); } return false; } return true; //return ReturnValue; }I'm still working out the hex code replacement (and all the jump/optimization token offsets), but wanted to put this up in case anyone sees anything that I missed. Edited April 5, 2013 by Amineri Link to comment Share on other sites More sharing options...
Amineri Posted April 6, 2013 Author Share Posted April 6, 2013 I have something that works, although it needs a bit more testing before I'd feel comfortable with it going to the masses. A few lessons learned on this:1) foreach statements also have offsets, but they are at the end of the line, and point to the last byte of code that is in the scope of the foreach statement2) writing to certain class variables can apparently trigger code executionmy original version of the code used m_iResult (what stores the battle result on the tactical side) as a temporary variable inside IsDefeat(). This had the effect of causing the camera to move very slowly, and I was unable to move any units. After selecting the unit, its movement map would flash up and immediately disappear. Simply changing the temporary variable from m_iResult to m_iCurePoison resolved the issue. (this will have the side effect of rendering it impossible to get the "get poisoned five times in one battle" achievement) I did not end up using the "boolean factorization" and "IsPossessed" optimizations, as I could not get them to decompile satisfactorily, and I had 8 extra bytes without utilizing it. Here is the hex change (it's large -- I had to rebuild the entire function and every jump statement)Changes applied to XcomGame.upk >> XGBattle_SP.IsDefeat before:EA 9E 00 00 50 55 00 00 00 00 00 00 CB 9E 00 00 00 00 00 00 00 00 00 00 D5 9E 00 00 00 00 00 00 BD 02 00 00 29 53 00 00 3E 05 00 00 D2 03 00 00 0F 00 D1 9E 00 00 19 1B 00 31 00 00 00 00 00 00 16 0A 00 56 94 00 00 00 1B C5 34 00 00 00 00 00 00 16 07 0A 01 77 00 D1 9E 00 00 2A 16 0F 00 D4 9E 00 00 25 07 0A 01 96 00 D4 9E 00 00 19 00 D1 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 16 0F 00 D3 9E 00 00 19 00 D1 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 FC 00 82 82 77 00 D3 9E 00 00 2A 16 18 20 00 19 00 D3 9E 00 00 0A 00 FF 32 00 00 00 1B D1 3C 00 00 00 00 00 00 16 16 18 20 00 19 00 D3 9E 00 00 0A 00 CD B5 00 00 00 1B 97 3E 00 00 00 00 00 00 16 16 04 28 A5 00 D4 9E 00 00 16 06 44 00 19 19 01 E9 F9 FF FF 0A 00 9B F9 FF FF 00 1C 67 FA FF FF 16 1D 00 00 00 00 00 00 1C AF FA FF FF 20 A2 0A 00 00 27 00 D0 9E 00 00 16 58 00 D0 9E 00 00 00 CF 9E 00 00 00 4A 98 01 07 97 01 19 2E A2 0A 00 00 00 CF 9E 00 00 0A 00 A0 0A 00 00 00 2D 01 A0 0A 00 00 30 04 27 31 30 0F 00 D2 9E 00 00 19 1B AE 32 00 00 00 00 00 00 16 0A 00 56 94 00 00 00 1B C5 34 00 00 00 00 00 00 16 07 32 03 9A 19 01 A3 9C 00 00 09 00 B0 9F 00 00 00 01 B0 9F 00 00 2C 08 16 0F 00 D4 9E 00 00 25 07 32 03 96 00 D4 9E 00 00 19 00 D2 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 24 03 82 77 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 2A 16 18 55 00 9A 35 37 0E 00 00 3D 0E 00 00 00 00 19 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 09 00 79 A3 00 00 00 01 79 A3 00 00 2C 04 16 16 07 21 03 84 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 18 20 00 19 00 D3 9E 00 00 0A 00 B9 32 00 00 00 1B 19 3D 00 00 00 00 00 00 16 16 04 27 06 32 03 A5 00 D4 9E 00 00 16 06 F3 01 0F 00 D4 9E 00 00 25 07 5A 04 96 00 D4 9E 00 00 19 00 D2 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 4C 04 82 82 81 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 16 18 22 00 81 19 00 D3 9E 00 00 0A 00 F6 30 00 00 00 2D 01 F6 30 00 00 16 16 18 22 00 81 19 00 D3 9E 00 00 0A 00 B9 32 00 00 00 1B 19 3D 00 00 00 00 00 00 16 16 16 07 4A 04 77 19 00 D3 9E 00 00 0A 00 7E B6 00 00 00 1B 25 34 00 00 00 00 00 00 16 2A 16 55 00 CE 9E 00 00 0A 00 00 D3 9E 00 00 16 06 4C 04 04 28 A5 00 D4 9E 00 00 16 06 3D 03 07 7F 04 9A 19 00 D2 9E 00 00 0A 00 87 AC 00 00 00 1B AD 33 00 00 00 00 00 00 16 25 16 07 31 05 97 36 00 CE 9E 00 00 25 16 58 00 CE 9E 00 00 00 D3 9E 00 00 00 4A 2E 05 19 19 00 D3 9E 00 00 0A 00 12 33 00 00 00 1B EA 33 00 00 00 00 00 00 16 5C 00 00 00 00 00 00 1B 1D 69 00 00 00 00 00 00 12 20 5C 4A 00 00 3D 00 45 4A 00 00 00 1B 51 13 00 00 00 00 00 00 1D 88 13 00 00 00 D3 9E 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 20 AC 4A 00 00 4A 4A 16 16 31 30 04 28 04 27 04 3A D5 9E 00 00 53 after:EA 9E 00 00 50 55 00 00 00 00 00 00 CB 9E 00 00 00 00 00 00 00 00 00 00 D5 9E 00 00 00 00 00 00 BD 02 00 00 29 53 00 00 6A 05 00 00 D2 03 00 00 0F 00 D1 9E 00 00 19 1B 00 31 00 00 00 00 00 00 16 09 00 E3 93 00 00 00 01 E3 93 00 00 07 08 01 77 00 D1 9E 00 00 2A 16 0F 00 D4 9E 00 00 25 07 08 01 96 00 D4 9E 00 00 19 00 D1 9E 00 00 09 00 35 AC 00 00 00 01 35 AC 00 00 16 0F 00 D3 9E 00 00 19 00 D1 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 FA 00 82 82 77 00 D3 9E 00 00 2A 16 18 20 00 19 00 D3 9E 00 00 0A 00 FF 32 00 00 00 1B D1 3C 00 00 00 00 00 00 16 16 18 20 00 19 00 D3 9E 00 00 0A 00 CD B5 00 00 00 1B 97 3E 00 00 00 00 00 00 16 16 04 28 A5 00 D4 9E 00 00 16 06 43 00 19 19 01 E9 F9 FF FF 0A 00 9B F9 FF FF 00 1C 67 FA FF FF 16 1D 00 00 00 00 00 00 1C AF FA FF FF 20 A2 0A 00 00 27 00 D0 9E 00 00 16 58 00 D0 9E 00 00 00 CF 9E 00 00 00 4A 96 01 07 95 01 19 2E A2 0A 00 00 00 CF 9E 00 00 0A 00 A0 0A 00 00 00 2D 01 A0 0A 00 00 30 04 27 31 30 0F 00 D2 9E 00 00 19 1B AE 32 00 00 00 00 00 00 16 09 00 E3 93 00 00 00 01 E3 93 00 00 0F 01 65 9E 00 00 19 00 D2 9E 00 00 09 00 35 AC 00 00 00 01 35 AC 00 00 07 1D 03 9A 19 01 A3 9C 00 00 09 00 B0 9F 00 00 00 01 B0 9F 00 00 2C 08 16 0F 00 D4 9E 00 00 25 07 1D 03 96 00 D4 9E 00 00 01 65 9E 00 00 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 0F 03 82 77 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 2A 16 18 55 00 9A 35 37 0E 00 00 3D 0E 00 00 00 00 19 2E B4 A3 00 00 19 00 D3 9E 00 00 0A 00 63 B4 00 00 00 1B 7B 31 00 00 00 00 00 00 16 09 00 79 A3 00 00 00 01 79 A3 00 00 2C 04 16 16 07 0C 03 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 04 27 06 1D 03 A5 00 D4 9E 00 00 16 06 18 02 07 2C 03 2D 01 68 9E 00 00 04 27 0F 00 D4 9E 00 00 25 07 18 04 96 00 D4 9E 00 00 01 65 9E 00 00 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 07 0A 04 82 81 19 00 D3 9E 00 00 0A 00 78 33 00 00 00 1B 25 3D 00 00 00 00 00 00 16 16 18 22 00 81 19 00 D3 9E 00 00 0A 00 B9 32 00 00 00 1B 19 3D 00 00 00 00 00 00 16 16 16 07 08 04 77 19 00 D3 9E 00 00 0A 00 7E B6 00 00 00 1B 25 34 00 00 00 00 00 00 16 2A 16 55 00 CE 9E 00 00 0A 00 00 D3 9E 00 00 16 06 0A 04 04 28 A5 00 D4 9E 00 00 16 06 37 03 07 C7 04 97 36 00 CE 9E 00 00 25 16 58 00 CE 9E 00 00 00 D3 9E 00 00 00 4A C4 04 19 19 00 D3 9E 00 00 0A 00 12 33 00 00 00 1B EA 33 00 00 00 00 00 00 16 5C 00 00 00 00 00 00 1B 1D 69 00 00 00 00 00 00 12 20 5C 4A 00 00 3D 00 45 4A 00 00 00 1B 51 13 00 00 00 00 00 00 2C FF 00 D3 9E 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 20 AC 4A 00 00 4A 4A 16 16 31 30 04 28 0F 00 D4 9E 00 00 25 07 4A 05 96 00 D4 9E 00 00 01 65 9E 00 00 16 0F 00 D3 9E 00 00 19 00 D2 9E 00 00 13 00 92 AC 00 00 00 1B 06 34 00 00 00 00 00 00 00 D4 9E 00 00 16 14 19 00 D3 9E 00 00 0A 00 92 AF 00 00 00 2D 01 92 AF 00 00 27 A5 00 D4 9E 00 00 16 06 D2 04 0F 01 65 9E 00 00 25 04 27 0B 0B 0B 0B 0B 0B 0B 0B 04 3A D5 9E 00 00 53 The final decompiled code flow goes like so:function bool IsDefeat(){ local int I; local XGUnit kUnit; local XGSquad kSquad, kAISquad; local array<SequenceObject> akSequences; local SequenceObject kSeq; local array<XGUnit> arrMindControlledXcoms; kAISquad = GetAIPlayer().m_kSquad; // End:0x108 if(kAISquad != none) { I = 0; J0x43: // End:0x108 [Loop If] if(I < kAISquad.m_iNumPermanentUnits) { kUnit = kAISquad.GetPermanentMemberAt(I); // End:0xFA if(((kUnit != none) && kUnit.IsAliveAndWell()) && kUnit.IsUnitBusy()) { return false; } ++ I; // [Loop Continue] goto J0x43; } } WorldInfo.GetGameSequence().FindSeqObjectsByClass(class'SeqAct_LoseBattle', true, akSequences); // End:0x196 foreach akSequences(kSeq) { // End:0x195 if(SeqAct_LoseBattle(kSeq).bLoseMission) { return true; } } kSquad = GetHumanPlayer().m_kSquad; m_iCurePoison = kSquad.m_iNumPermanentUnits; // End:0x31D if(m_kDesc.m_iMissionType == 8) { I = 0; J0x218: // End:0x31D [Loop If] if(I < m_iCurePoison) { kUnit = kSquad.GetPermanentMemberAt(I); // End:0x30F if((XGCharacter_Soldier(kUnit.GetCharacter()) != none) && XGCharacter_Soldier(kUnit.GetCharacter()).m_kSoldier.iPsiRank == 4) { // End:0x30C if(kUnit.IsDead()) { return true; } // [Explicit Break] goto J0x31D; } ++ I; J0x31D: // [Loop Continue] goto J0x218; } } // End:0x32C if(m_bAbandoned) { return true; } I = 0; J0x337: // End:0x418 [Loop If] if(I < m_iCurePoison) { kUnit = kSquad.GetPermanentMemberAt(I); // End:0x40A if(!kUnit.IsDead() && !kUnit.IsCriticallyWounded()) { // End:0x408 if(kUnit.GetPossessedBy() != none) { arrMindControlledXcoms.AddItem(kUnit); } // End:0x40A else { return false; } } ++ I; // [Loop Continue] goto J0x337; } // End:0x4C7 if(arrMindControlledXcoms.Length > 0) { // End:0x4C4 foreach arrMindControlledXcoms(kUnit) { kUnit.GetPawn().TakeDirectDamage(class'XComDamageType'.static.CreateEvent(255, kUnit, vect(0.0, 0.0, 0.0), vect(0.0, 0.0, 0.0), class'XComDamageType_Psionic')); } return false; } I = 0; J0x4D2: // End:0x54A [Loop If] if(I < m_iCurePoison) { kUnit = kSquad.GetPermanentMemberAt(I); kUnit.m_bLeftBehind = true; ++ I; // [Loop Continue] goto J0x4D2; } m_iCurePoison = 0; return true; //return ReturnValue; } Here are the items that need to be verified are still working correctly:1) Soldier items are lost if mission failed due to all soldiers dead -- VERIFIED in abduction mission2) Soldiers that escape via Dropship if mission aborted do not lose items3) Temple ship mission always fails if volunteer is reduced to 0 health (volunteer should be unable to become critically wounded) Known side effects:1) Player is unable to get Achievement 27, Poison Control -- this may not be an issue, as this achievement appears to only be available to PS3 / Xbox360 players. Link to comment Share on other sites More sharing options...
anUser Posted April 6, 2013 Share Posted April 6, 2013 Well done. Truth is this is quite breath-taking, I wouldn't like to have to make all this changes by hand when a new patch comes out. I take note of your discoveries, that's going to the wiki (can you believe I hadn't noticed foreach loops before? I just assumed they'd be just another (if I < arr.len) do stuff; i++; jump back). Btw, how do you negate a condition !(x), by comparing to false? Side note, In another code of yours I've seen this A < B < C, can you really write it like that? 'cause I only managed to write it like (A < B) && (B < C). That UnloadSoldier function being called when cancelling the in the select squad screen is terrible news for spendable grenades. I haven't had time to test it yet, so I'd like to know from your discoveries in this field. ps: just went to check, hex byte 81 is !, so I assume that's how you negate conditions, right? Do they use endParam 16 as any other operator? Link to comment Share on other sites More sharing options...
johnnylump Posted April 6, 2013 Share Posted April 6, 2013 Now here's a challenge along these lines: the weapons from shot-down interceptors and firestorms should be removed from the player inventory, but they are instead returned to be mounted on another craft. XComStrategyGame >> XGInterception >> CompleteEngagement () -- Checks if interceptor hp < 0 and if so calls OnInterceptorDestroyed()XGFacility_Hangar >> OnInterceptorDestroyed() -- Some bookkeepingXGFacility_Hangar >> RemoveInterceptor() -- Used if interceptor is destroyed OR dismissed, it sends the interceptor's weapon back to the storage lockers. Link to comment Share on other sites More sharing options...
Recommended Posts