khuong wrote: ↑Sun Sep 24, 2023 3:53 am
how did you search for this?
I decompiled the contraband scanning script and figured out how it worked, then attacked it based on its logic.
This is the code it uses to determine what to do.
Code: Select all
Int contrabandStatus = playerShipRef.CheckContrabandStatus(True)
If contrabandStatus < 0 && droppedContraband == False
Self.HideContrabandScanWarning(False, True)
SQ_GuardShipsScanStatus.SetValueInt(1)
ElseIf contrabandStatus > 0 || droppedContraband
SQ_GuardShipsScanStatus.SetValueInt(0)
Self.HideContrabandScanWarning(False, False)
Self.SendSmugglingAlarm()
Else
Bool scanStatus = SQ_Parent.SmugglingMinigame(playerShipRef, Ship01.GetShipRef())
SQ_GuardShipsScanStatus.SetValueInt(scanStatus as Int)
Self.HideContrabandScanWarning(False, scanStatus)
If scanStatus
Else
Self.SendSmugglingAlarm()
EndIf
EndIf
I figured that CheckContrabandStatus was a good attack vector. So, then I had to find it. What I did, was I looked for CheckContrabandStatus as a string in IDA. Which lead me to this section of code:
The NativeFunctionVSpaceShipRef shit is coming from the
[Link] data, I have a plugin that scans the binary for it, and tries its best to reconstruct functions based on the virtual type inferences.
Just below it, you can see it load the address of the callback function that's used from the Papyrus engine, labeled here by IDA as sub_1428F7D60. This is just a trampoline function, not sure why the compiler did that? But anyway, it jumps to this function which I've properly labeled CheckContrabandStatus:
The parameters are the thisptr of the reference it's being ran against, and the boolean parameter we saw in the script. From here, it was just basic math to attack the game.
Because it checks if the contrabandStatus is < 0, I just returned a value (like -1) and it stopped the contraband scans altogether.
SQ_Parent has its own script, and I looked at SmugglingMinigame. SmugglingMinigame is really bog standard boilerplate code you'd expect, gets your chance to pass, generates a random number from 0 to 100, checks if you passed then returns accordingly.
Code: Select all
Bool Function SmugglingMinigame(spaceshipreference playerShipRef, spaceshipreference scanningShipRef)
Float realChance = Self.GetSmugglingChance(playerShipRef, scanningShipRef)
Float dieRoll = Utility.RandomFloat(1.0, 100.0)
Bool bSuccess = dieRoll <= realChance
If bSuccess
Game.AddAchievement(SmugglingAchievementID)
EndIf
Return bSuccess
EndFunction
However, GetSmugglingChance seems like they had some testing code left over? Or maybe they just knew people were going to hack it, or maybe they always scanned you but then later made it skip the scan if you weren't carrying contraband because it takes so long. IDK probably reading into it too much, but I don't see a legit scenario for why the contrabandStatus < 0 sets realChance to 100 when the ScanForContraband function that calls this already checks if CheckContrabandStatus is < 0.
Code: Select all
Float Function GetSmugglingChance(spaceshipreference playerShipRef, spaceshipreference scanningShipRef)
Int contrabandStatus = playerShipRef.CheckContrabandStatus(True)
Float realChance = 0.0
If contrabandStatus < 0
realChance = 100.0
ElseIf contrabandStatus > 0
realChance = 0.0
Else
Float contrabandWeight = playerShipRef.GetContrabandWeight(False)
Float contrabandWeightShip = playerShipRef.GetContrabandWeight(True)
Float contrabandCapacity = playerShipRef.GetValue(CarryWeightShielded)
Int playerSmugglingSkillValue = Math.Clamp(Game.GetPlayer().GetValueInt(PayloadLevel) as Float, 0.0, (PlayerSkillMults.Length - 1) as Float) as Int
Float playerSmugglingSkillBonus = PlayerSkillMults[playerSmugglingSkillValue]
Int playerScanJammerValue = Math.Clamp(playerShipRef.GetValueInt(SpaceshipScanJammer) as Float, 0.0, (ScanJammerMults.Length - 1) as Float) as Int
Float playerScanJammerBonus = ScanJammerMults[playerScanJammerValue]
Float scanningShipPerception = scanningShipRef.GetValue(Perception)
Float targetSkillFactor = fSmugglingTargetSkillMult * scanningShipPerception
Float contrabandWeightFactor = fSmugglingWeightMult * Math.pow(contrabandWeight, fSmugglingWeightPower) * contrabandWeight / contrabandCapacity
Float baseChance = fSmugglingBaseChance + targetSkillFactor + contrabandWeightFactor
realChance = baseChance * (1.0 + playerScanJammerBonus) * (1.0 + playerSmugglingSkillBonus)
realChance = Math.Max(realChance, fSmugglingMinChance)
realChance = Math.Min(realChance, fSmugglingMaxChance)
EndIf
Return realChance
EndFunction
That being said, I don't really know why this works. I didn't realize until just now that the function returns a float not an integer, and I'm basically returning a
[Link]. I have no idea why it works with -1 to do what I want, but with -5 it fails the check in ScanForContraband but passes in GetSmugglingChance. Undefined behavior, I suppose.