add Rust ScrapHacks prototype and network sniffer/parser
This commit is contained in:
		
							parent
							
								
									58407ecc9f
								
							
						
					
					
						commit
						63962c95cc
					
				
					 27 changed files with 5008 additions and 0 deletions
				
			
		
							
								
								
									
										2
									
								
								tools/remaster/scraphacks_rs/.cargo/config.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tools/remaster/scraphacks_rs/.cargo/config.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| [build] | ||||
| target = "i686-pc-windows-msvc" | ||||
							
								
								
									
										1624
									
								
								tools/remaster/scraphacks_rs/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1624
									
								
								tools/remaster/scraphacks_rs/Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										45
									
								
								tools/remaster/scraphacks_rs/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								tools/remaster/scraphacks_rs/Cargo.toml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| [package] | ||||
| name = "scraphacks_rs" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| [profile.release] | ||||
| debug = 0 | ||||
| 
 | ||||
| [lib] | ||||
| crate-type = ["dylib"] | ||||
| 
 | ||||
| [features] | ||||
| console=[] | ||||
| 
 | ||||
| [dependencies] | ||||
| anyhow = "1.0.68" | ||||
| comma = "1.0.0" | ||||
| custom-print = "0.1.0" | ||||
| derivative = "2.2.0" | ||||
| detour3 = "0.1.0" | ||||
| discord-sdk = "0.3.2" | ||||
| futures = "0.3.25" | ||||
| hex = "0.4.3" | ||||
| iced-x86 = "1.18.0" | ||||
| mlua = { version = "0.8.7", features = ["luajit", "vendored", "macros", "serialize", "mlua_derive"] } | ||||
| nom = "7.1.3" | ||||
| nom-greedyerror = "0.5.0" | ||||
| nom-supreme = "0.8.0" | ||||
| nom_locate = "4.1.0" | ||||
| num-traits = "0.2.15" | ||||
| once_cell = "1.17.0" | ||||
| parse_int = "0.6.0" | ||||
| pelite = "0.10.0" | ||||
| region = "3.0.0" | ||||
| rhexdump = "0.1.1" | ||||
| rustc-hash = "1.1.0" | ||||
| shadow-rs = "0.21.0" | ||||
| struct_layout = "0.1.0" | ||||
| tokio = "1.24.2" | ||||
| viable = "0.2.0" | ||||
| winsafe = { version = "0.0.15", features = ["kernel", "user", "dshow"] } | ||||
| 
 | ||||
| [build-dependencies] | ||||
| shadow-rs = "0.21.0" | ||||
							
								
								
									
										11
									
								
								tools/remaster/scraphacks_rs/Pipfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tools/remaster/scraphacks_rs/Pipfile
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| [[source]] | ||||
| url = "https://pypi.org/simple" | ||||
| verify_ssl = true | ||||
| name = "pypi" | ||||
| 
 | ||||
| [packages] | ||||
| 
 | ||||
| [dev-packages] | ||||
| 
 | ||||
| [requires] | ||||
| python_version = "3.9" | ||||
							
								
								
									
										242
									
								
								tools/remaster/scraphacks_rs/Save0.sav.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								tools/remaster/scraphacks_rs/Save0.sav.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,242 @@ | |||
| { | ||||
|     "id": "Outskirts - 07:41:13", | ||||
|     "title": "Scrapland savegame", | ||||
|     "data": { | ||||
|         "CTFFinishPlayerLoosesTextCad": "Mission_CTFAgainstBankers_RemoteMessage3", | ||||
|         "CTFFinishPlayerWinsTextCad": "Mission_CTFAgainstBankers_RemoteMessage2", | ||||
|         "CTFStartTextCad": "Mission_CTFAgainstBankers_RemoteMessage1", | ||||
|         "CTFOnDeathSpawnTime": "5.0", | ||||
|         "CTFFriendProfile": "BankersCTFFriends", | ||||
|         "CTFEnemyProfile": "BankersCTFEnemies", | ||||
|         "CTFFriendHead": "Functionary", | ||||
|         "CTFFriendType": "Functionary", | ||||
|         "CTFEnemyType": "BankDirector", | ||||
|         "CTFNumFriends": "5", | ||||
|         "CTFNumEnemies": "5", | ||||
|         "CTFFlags": "5", | ||||
|         "CombatFriendProfile": "ArrangeBankersCombatFriends", | ||||
|         "CombatFriendType": "Police", | ||||
|         "CombatNumFriends": "4", | ||||
|         "CombatNumEnemies": "5", | ||||
|         "PlayerWinsCombat": "1", | ||||
|         "OnVictory": "import SaveGame; SaveGame.QWayPoint();import MissionsFuncs; MissionsFuncs.SetNextMission(\"Mission_BackFromMortalRace\", \"GamblinDen\");Scrap.SetSaveVar(\"Map\", \"Levels/GamblinDen\");import SaveGame; SaveGame.QLoad(\"Levels/GamblinDen\");Scrap.SetSaveVar('MapPress_BureauExists','1')", | ||||
|         "OnAbort": "import MissionsFuncs; MissionsFuncs.EndOfSuperDeal('SuperDeal_Faliure_SystemMessage1')", | ||||
|         "CombatFinishPlayerLoosesTextCad": "SuperDeal_First_RemoteMessage3", | ||||
|         "CombatFinishPlayerWinsTextCad": "SuperDeal_First_RemoteMessage2", | ||||
|         "CombatStartTextCad": "SuperDeal_First_RemoteMessage1", | ||||
|         "CombatEnemyProfile": "SuperDealFirstElite", | ||||
|         "CombatEnemyTypeHead": "CrazyGambler", | ||||
|         "CombatEnemyType": "BankDirector", | ||||
|         "CombatDeaths": "5", | ||||
|         "SuperDealType": "", | ||||
|         "IgorFirstContactMissionState": "TalkToMercenaries", | ||||
|         "Stats.MadHunter": "10", | ||||
|         "Stats.Nurse.Dazed": "2", | ||||
|         "Stats.BankMaster": "6758", | ||||
|         "Bank.Circuit.36": "0", | ||||
|         "Bank.Circuit.35": "0", | ||||
|         "Bank.Circuit.34": "0", | ||||
|         "Bank.Circuit.33": "0", | ||||
|         "Bank.Circuit.32": "0", | ||||
|         "Bank.Circuit.31": "0", | ||||
|         "Bank.Circuit.30": "0", | ||||
|         "Bank.Circuit.29": "0", | ||||
|         "Bank.Circuit.28": "0", | ||||
|         "Bank.Circuit.27": "0", | ||||
|         "Bank.Circuit.26": "0", | ||||
|         "Bank.Circuit.25": "0", | ||||
|         "Bank.Circuit.24": "0", | ||||
|         "Bank.Circuit.23": "0", | ||||
|         "Bank.Circuit.22": "0", | ||||
|         "Bank.Circuit.21": "0", | ||||
|         "Bank.Circuit.20": "0", | ||||
|         "Bank.Circuit.19": "0", | ||||
|         "Bank.Circuit.18": "0", | ||||
|         "Bank.Circuit.17": "0", | ||||
|         "Bank.Circuit.16": "0", | ||||
|         "Bank.Circuit.15": "0", | ||||
|         "Bank.Circuit.14": "0", | ||||
|         "Bank.Circuit.13": "0", | ||||
|         "Bank.Circuit.12": "0", | ||||
|         "Bank.Circuit.11": "0", | ||||
|         "Bank.Circuit.10": "0", | ||||
|         "Bank.Circuit.9": "0", | ||||
|         "Bank.Circuit.8": "0", | ||||
|         "Bank.Circuit.7": "0", | ||||
|         "Bank.Circuit.6": "0", | ||||
|         "Bank.Circuit.5": "0", | ||||
|         "Bank.Circuit.4": "0", | ||||
|         "Bank.Circuit.3": "0", | ||||
|         "Bank.Circuit.2": "0", | ||||
|         "Bank.Circuit.1": "0", | ||||
|         "Bank.Circuit.0": "0", | ||||
|         "Stats.Mosquito": "116", | ||||
|         "PoliceBossAtTownHall": "0", | ||||
|         "Stats.Parking": "18", | ||||
|         "Police.FicusDeath": "1", | ||||
|         "CostumeAtPolice": "0", | ||||
|         "Hangar.HangarShip10": "SLifeBoat<-<-<-<-<-0,0,0,0,0,0,1<-30<-0,0,0,0,0,0", | ||||
|         "Hangar.iHangarShips": "8", | ||||
|         "AutoSaveGameOnLoad": "0", | ||||
|         "GameAct": "3rdMurder", | ||||
|         "BankDebt": "0", | ||||
|         "Map": "Levels/Outskirts", | ||||
|         "Mission.Library": "Mission_DestroyBadDebtors", | ||||
|         "EnergyBarActive": "1", | ||||
|         "SpecialActionActive": "2", | ||||
|         "CrazyWing.Status": "1", | ||||
|         "Conversor.ActiveConversors": "1", | ||||
|         "Hangar.iHangarShip": "0", | ||||
|         "Player.NumLives": "100", | ||||
|         "Hangar.shipsToEditList": "['SPoli1', 'SPoli2', 'SPoli3', 'SPoli4', 'SPoli5', 'SPoliBoss1', 'SMerc1', 'SMerc2', 'SMerc3', 'SMayor1', 'SBanker1', 'SBankMaster1', 'SBishop1', 'SArchbishop1', 'SFunc1', 'SBerto1', 'SBetty1', 'SHump1', 'SBoss1', 'SPoli4']", | ||||
|         "Hangar.availableEnginesList": "['MPOLI4', 'MPOLI5', 'MPOLIBOSS1', 'MPOLI2', 'MBERTO1', 'MBETTY1', 'MPOLI1', 'MMERC1', 'MMERC2', 'MPOLI3', 'MMAYOR1', 'MFUNC1', 'MBANKER1', 'MBANKMASTER1', 'MBISHOP1', 'MARCHBISHOP1', 'MHUMP1', 'MBOSS1', 'MMERC3']", | ||||
|         "Hangar.availableWeaponsList": "['Vulcan', 'Devastator', 'Swarm', 'Inferno', 'Tesla', 'ATPC', 'Swarm', 'Devastator']", | ||||
|         "Hangar.availableUpgradesList": "[\"VulcanUpgrade1\", \"VulcanUpgrade2\", \"DevastatorUpgrade1\", \"DevastatorUpgrade2\", \"SwarmUpgrade1\", \"SwarmUpgrade2\", \"InfernoUpgrade1\", \"InfernoUpgrade2\", \"TeslaUpgrade1\", \"TeslaUpgrade2\", \"ATPCUpgrade1\", \"ATPCUpgrade2\"]", | ||||
|         "JackInTheBox.Active": "1", | ||||
|         "Parked.Police": "['SPOLI1', 'SPOLI2', 'SPOLI3', 'SPOLI4']", | ||||
|         "Parked.Mercs": "['SMERC1', 'SMERC2']", | ||||
|         "Parked.TownHall": "['SFUNC1']", | ||||
|         "Parked.Bank": "['SBANKER1']", | ||||
|         "Parked.Press": "['SBERTO1', 'SBETTY1', 'SHUMP1']", | ||||
|         "Parked.Temple": "['SBISHOP1']", | ||||
|         "PoliceBlueprints.Ships": "['SPoli2', 'SPoli3', 'SPoli5', 'SPoliBoss1']", | ||||
|         "PoliceBlueprints.Engines": "['MPoli2', 'MPoli3', 'MPoli4', 'MPoli5', 'MPoliBoss1']", | ||||
|         "PressBlueprints.Ships": "['SBerto1', 'SBetty1', 'SHump1']", | ||||
|         "PressBlueprints.Engines": "['MBerto1', 'MBetty1', 'MHump1']", | ||||
|         "MayorAtGambinDen": "0", | ||||
|         "PolicesAtGambinDen": "1", | ||||
|         "PoliceBossAtPolice": "0", | ||||
|         "CrazyGamblerAtGambinDen": "1", | ||||
|         "FunctionaryTwinsAtGambinDen": "1", | ||||
|         "BankersAtGambinDen": "0", | ||||
|         "BishopsAtGambinDen": "0", | ||||
|         "GameSkill": "0", | ||||
|         "CreateBertos": "0", | ||||
|         "MercsHelpDtritus": "0", | ||||
|         "RobotsControlledByBoss": "0", | ||||
|         "Player.InfiniteLives": "0", | ||||
|         "PrevMap": "Levels/GamblinDen", | ||||
|         "Spawn": "DM_Player_Spawn_GamblinDen", | ||||
|         "Char": "Dtritus", | ||||
|         "ComeFrom": "DoorElevator", | ||||
|         "AlarmActive": "0", | ||||
|         "AlarmStatus": "0.0", | ||||
|         "Money": "2147483647", | ||||
|         "Challenge": "1", | ||||
|         "Challenge.Type": "", | ||||
|         "Challenge.Foe": "", | ||||
|         "Challenge.NumEnemies": "", | ||||
|         "Challenge.Money": "0", | ||||
|         "Revenge.Type": "", | ||||
|         "Revenge.Foe": "", | ||||
|         "Revenge.NumEnemies": "", | ||||
|         "Revenge.LastWinner": "", | ||||
|         "Mission.Map": "Outskirts", | ||||
|         "BadReputation": "3", | ||||
|         "GameplayTime": "27673.5857997", | ||||
|         "LonelyMercInGamblinDen": "0", | ||||
|         "LonelyMercLairActive": "1", | ||||
|         "LonelyMercDataActive": "0", | ||||
|         "ComSatsMissionsMapsFinished": "[]", | ||||
|         "Conversor.AvailableChars": "['Police', 'Nurse', 'BankDirector', 'Desktop', 'Sentinel', 'Gear', 'Bishop', 'Messenger', 'Functionary', 'Betty', 'Berto', 'BankMaster', 'Dtritus']", | ||||
|         "Batteries": "5", | ||||
|         "AcBatteries": "2", | ||||
|         "PrimaryMissionDesc": "Ich muss den Bankdirektor besuchen. Er treibt gerade ausstehende Kreditzahlungen mit seinem Kampfraumschiff ein. Ich sehe mal, ob ich ihm helfen kann.", | ||||
|         "SecondaryMissionDesc": "", | ||||
|         "TakePhotoMsg": "0", | ||||
|         "Race.Num": "3", | ||||
|         "Race.FirstTime": "0", | ||||
|         "Race.Profile": "Pilot", | ||||
|         "Combat.FirstTime": "1", | ||||
|         "Combat.Profile": "Rookie", | ||||
|         "Traffic.AcShips": "[\"SPoli6\", \"SMerc1\",\"SMerc2\",\"SBanker1\"]", | ||||
|         "IsSecondMission": "0", | ||||
|         "CrazyDeal.1.Var": "Stats.Dtritus", | ||||
|         "CrazyDeal.1.Tgt": "5", | ||||
|         "CrazyDeal.2.Var": "Stats.Parking", | ||||
|         "CrazyDeal.2.Tgt": "3", | ||||
|         "CrazyDeal.3.Var": "Stats.Battery", | ||||
|         "CrazyDeal.3.Tgt": "5", | ||||
|         "SuperDeal.Map": "FZ", | ||||
|         "SuperDeal.Library": "SuperDeal_Second", | ||||
|         "SuperDeal.Type": "MortalRace", | ||||
|         "CrazyWing.List": "['Sentinel', 'Betty', 'CrazyGambler', 'Functionary', 'Bishop']", | ||||
|         "Journalist.Humphrey_Defaut": "NoPlace", | ||||
|         "Journalist.Betty_Defaut": "NoPlace", | ||||
|         "Journalist.Berto_Defaut": "Press", | ||||
|         "Conversor.FirstConversion": "0", | ||||
|         "Conversor.FirstPossession": "0", | ||||
|         "WindowsError": "0", | ||||
|         "Orbit.Decontaminated": "yes", | ||||
|         "MercFriends.MercenaryA_Smartie": "0", | ||||
|         "MercFriends.MercenaryC_Brutus": "0", | ||||
|         "MercFriends.MercenaryB_Dumber": "0", | ||||
|         "StdShipAIProfile": "Elite", | ||||
|         "usrShip.Ammo00": "1000.0", | ||||
|         "usrShip.Ammo01": "500.0", | ||||
|         "usrShip.Ammo02": "1500.0", | ||||
|         "usrShip.AcWeap": "6", | ||||
|         "Parking.Desolate": "0", | ||||
|         "Hangar.HangarShip1": "SBoss1<-MBOSS1<-MBOSS1<-<-<-15,15,15,15,15,15,1<-187<-1,8,6,9,11,3", | ||||
|         "Hangar.HangarShip2": "", | ||||
|         "Hangar.HangarShip3": "", | ||||
|         "Hangar.HangarShip4": "", | ||||
|         "Hangar.HangarShip5": "", | ||||
|         "Hangar.HangarShip6": "", | ||||
|         "Hangar.HangarShip7": "", | ||||
|         "Hangar.HangarShip8": "", | ||||
|         "Hangar.HangarShip9": "", | ||||
|         "Hangar.HangarShipAux": "SLifeBoat<-<-<-<-<-0,0,0,0,0,0,1<-50<-0,0,0,0,0,0", | ||||
|         "Hangar.DestroyedShips": "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", | ||||
|         "NewBluePrintAvaliable": "11", | ||||
|         "DebugSave": "1", | ||||
|         "OutMusicRelax": "141", | ||||
|         "MayorAtTownHall": "1", | ||||
|         "GamblinMusic": "1", | ||||
|         "OutMusicAction": "122", | ||||
|         "Stats.Traffic": "767", | ||||
|         "Stats.OutPolice": "110", | ||||
|         "MapPress_HiAlarm": "0", | ||||
|         "MapPress_BureauExists": "1", | ||||
|         "Stats.PossessBerto": "1", | ||||
|         "Stats.ConvertIntoDtritus": "1", | ||||
|         "Stats.WinHumphreyRace": "1", | ||||
|         "Stats.Possession": "57", | ||||
|         "Stats.Betty": "49", | ||||
|         "Stats.Killer": "119", | ||||
|         "Stats.Jump": "1", | ||||
|         "Stats.Jump.Police": "1", | ||||
|         "Stats.Bishop": "10", | ||||
|         "Stats.Battery": "0", | ||||
|         "Stats.Dtritus": "0", | ||||
|         "Stats.Race.Press": "1", | ||||
|         "Stats.TotalRaces.Press": "1", | ||||
|         "BankMasterAtBank": "1", | ||||
|         "DM_ExtraLife_00": "-60", | ||||
|         "DM_ExtraLife_01": "-60", | ||||
|         "DM_ExtraLife_02": "-60", | ||||
|         "DM_ExtraLife_03": "-60", | ||||
|         "DM_ExtraLife_04": "16294.7919922", | ||||
|         "Stats.TempleLife": "4", | ||||
|         "Mission_CatchTrurl_Data": "[]", | ||||
|         "Mission_CatchTrurl_MapsPos": "[]", | ||||
|         "Mission_CatchTrurl_NumMapsDropped": "0", | ||||
|         "Mission_CatchTrurl_NumMapsTaken": "0", | ||||
|         "GDB.BishopsMsg": "1", | ||||
|         "Stats.GDB": "6", | ||||
|         "LonelyMercActive": "0", | ||||
|         "Stats.Messenger": "2", | ||||
|         "MortalRaceRace": "[((71611.59375, 18231.6992188, 93232.796875), 422.337219238), ((45388.4140625, 14599.3476563, 79622.6640625), 400.984222412), ((25194.9804688, 18783.4863281, 59759.296875), 404.390136719), ((-433.664245605, 26340.1289063, 34561.0898438), 409.718261719), ((-38229.3671875, 26457.5292969, 679.068054199), 449.472442627), ((-107464.132813, 19331.875, 3288.328125), 528.452758789), ((-113911.117188, 14331.4462891, 40812.9414063), 558.054199219), ((-102532.132813, 11236.1474609, 75072.375), 630.567077637), ((-58177.6289063, 6282.20654297, 74209.3515625), 673.615478516), ((-24157.5449219, 7054.30419922, 43223.1679688), 630.510681152), ((33550.1445313, 15480.2402344, 41122.5820313), -55.4565696716), ((56201.6054688, 15587.5126953, 24649.8496094), 23.7488441467), ((26343.9511719, 22077.8789063, -32317.0292969), 90.5086135864), ((-13835.4755859, 26276.8730469, -31975.1582031), 145.932754517), ((-29244.3652344, 26745.4667969, -2544.81738281), -892.995666504), ((-23808.9570313, 27246.9980469, 32018.1816406), -819.383483887), ((21584.3066406, 29452.4667969, 41221.6171875), -822.313781738), ((54026.796875, 24611.7421875, 42694.0898438), -802.188171387), ((95732.015625, 16516.8085938, 36323.546875), -872.699890137), ((113450.46875, 12325.5195313, 77796.75), -969.003662109)]", | ||||
|         "MortalRaceWaypoints": "20", | ||||
|         "MortalRaceRacers": "['ArchBishop', 'BankDirector', 'Functionary', 'MercenaryA', 'MercenaryB']", | ||||
|         "MortalRaceRacersProfile": "MortalRace", | ||||
|         "MortalRaceFightingFreezeControlAim": "4", | ||||
|         "MortalRaceRespawnTime": "5.0", | ||||
|         "MortalRaceStartTextCad": "Mission_WinMortalRace_RemoteMessage1", | ||||
|         "MortalRaceFinishPlayerLoosesTextCad": "Mission_WinMortalRace_RemoteMessage2", | ||||
|         "MortalRaceFinishPlayerLoosesTextFoe": "Messenger", | ||||
|         "MortalRaceFinishPlayerWinsTextCad": "Mission_WinMortalRace_RemoteMessage3", | ||||
|         "MortalRaceFinishPlayerWinsTextFoe": "Messenger", | ||||
|         "MortalRaceAutoRestart": "1" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3
									
								
								tools/remaster/scraphacks_rs/build.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tools/remaster/scraphacks_rs/build.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| fn main() -> shadow_rs::SdResult<()> { | ||||
|     shadow_rs::new() | ||||
| } | ||||
							
								
								
									
										4
									
								
								tools/remaster/scraphacks_rs/build.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tools/remaster/scraphacks_rs/build.sh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| impo | ||||
| cargo b -r | ||||
| cp D:/devel/Git_Repos/Scrapland-RE/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/release/scraphacks_rs.dll E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd | ||||
| # x32dbg E:/Games/Steam/steamapps/common/Scrapland/Bin/Scrap.unpacked.exe "-debug:10 -console" | ||||
							
								
								
									
										16
									
								
								tools/remaster/scraphacks_rs/notes.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tools/remaster/scraphacks_rs/notes.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| # Notes | ||||
| 
 | ||||
| ## Snippets | ||||
| 
 | ||||
| Map name: `Scrap.GetLangStr("Station_" + Scrap.GetLevelPath()[7:])` | ||||
| 
 | ||||
| ## Run | ||||
| 
 | ||||
| `steam://run/897610/` | ||||
| 
 | ||||
| ## Signatures | ||||
| 
 | ||||
| - World pointer: `a3 *{'} e8 ? ? ? ? 6a 00 68 *"World initialized"` | ||||
| - print: `6a0068 *{"Scrap engine"} 6a?e8 $'` | ||||
| - console handler: `68 *{"import Viewer"} e8 $'` | ||||
| - DirectX Device: `` | ||||
							
								
								
									
										34
									
								
								tools/remaster/scraphacks_rs/run.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								tools/remaster/scraphacks_rs/run.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| import subprocess as SP | ||||
| import shutil as sh | ||||
| import json | ||||
| from pathlib import Path | ||||
| import psutil | ||||
| import os | ||||
| import sys | ||||
| os.environ['DISCORD_INSTANCE_ID']='1' | ||||
| SP.check_call(["cargo","b","-r"]) | ||||
| info=[json.loads(line) for line in SP.check_output(["cargo","b", "-r" ,"-q","--message-format=json"]).splitlines()] | ||||
| dll_path=None | ||||
| for line in info: | ||||
|     if line.get('reason')=="compiler-artifact" and ("dylib" in line.get("target",{}).get("crate_types",[])): | ||||
|         dll_path=Path(line['filenames'][0]) | ||||
| 
 | ||||
| sh.copy(dll_path,"E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd") | ||||
| 
 | ||||
| if "--run" not in sys.argv[1:]: | ||||
|     exit(0) | ||||
| 
 | ||||
| os.startfile("steam://run/897610/") | ||||
| pid=None | ||||
| while pid is None: | ||||
|     for proc in psutil.process_iter(): | ||||
|         try: | ||||
|             if proc.name()=="Scrap.exe": | ||||
|                 pid=proc.pid | ||||
|         except: | ||||
|             pass | ||||
| print(f"PID: {pid:x}") | ||||
| if "--dbg" in sys.argv[1:]: | ||||
|     SP.run(["x32dbg","-p",str(pid)]) | ||||
| # cp D:/devel/Git_Repos/Scrapland-RE/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/release/scraphacks_rs.dll E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd | ||||
| # x32dbg E:/Games/Steam/steamapps/common/Scrapland/Bin/Scrap.unpacked.exe "-debug:10 -console" | ||||
							
								
								
									
										16
									
								
								tools/remaster/scraphacks_rs/scrap.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tools/remaster/scraphacks_rs/scrap.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| #include <stdint.h> | ||||
| struct HashTable { | ||||
|     uint32_t num_slots; | ||||
|     struct HashTableEntry **chains; | ||||
| }; | ||||
| 
 | ||||
| struct HashTableEntry { | ||||
|     void *data; | ||||
|     const char *name; | ||||
|     HashTableEntry *next; | ||||
| }; | ||||
| 
 | ||||
| struct World { | ||||
|     void** VMT; | ||||
|     HashTable *entities; | ||||
| }; | ||||
							
								
								
									
										7
									
								
								tools/remaster/scraphacks_rs/src/config.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tools/remaster/scraphacks_rs/src/config.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| enum FilePatch { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| pub struct Config { | ||||
|     file_patches: FxHashMap<PathBuf,FilePatch> | ||||
| } | ||||
							
								
								
									
										94
									
								
								tools/remaster/scraphacks_rs/src/discord.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								tools/remaster/scraphacks_rs/src/discord.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | |||
| use std::{num::NonZeroU32, thread::JoinHandle, time::SystemTime}; | ||||
| 
 | ||||
| use crate::{cdbg, ceprintln, cprint, cprintln}; | ||||
| use anyhow::{bail, Result}; | ||||
| use discord_sdk::{ | ||||
|     activity::{ActivityBuilder, Assets, PartyPrivacy, Secrets}, | ||||
|     registration::{register_app, Application, LaunchCommand}, | ||||
|     wheel::Wheel, | ||||
|     Discord, DiscordApp, Subscriptions, | ||||
| }; | ||||
| const APP_ID: discord_sdk::AppId = 1066820570097930342; | ||||
| const STEAM_APP_ID: u32 = 897610; | ||||
| pub struct Client { | ||||
|     pub discord: discord_sdk::Discord, | ||||
|     pub user: discord_sdk::user::User, | ||||
|     pub wheel: discord_sdk::wheel::Wheel, | ||||
| } | ||||
| 
 | ||||
| impl Client { | ||||
|     pub fn run() -> Result<JoinHandle<Result<()>>> { | ||||
|         let rt = tokio::runtime::Builder::new_multi_thread() | ||||
|             .enable_all() | ||||
|             .build()?; | ||||
|         register_app(Application { | ||||
|             id: APP_ID, | ||||
|             name: Some("Scrapland Remastered".to_owned()), | ||||
|             command: LaunchCommand::Steam(STEAM_APP_ID), | ||||
|         })?; | ||||
|         Ok(std::thread::spawn(move || rt.block_on(Self::run_async()))) | ||||
|     } | ||||
|     async fn run_async() -> Result<()> { | ||||
|         let (wheel, handler) = Wheel::new(Box::new(|err| { | ||||
|             ceprintln!("Encountered an error: {}", err); | ||||
|         })); | ||||
|         let mut user = wheel.user(); | ||||
|         let discord = Discord::new( | ||||
|             DiscordApp::PlainId(APP_ID), | ||||
|             Subscriptions::ACTIVITY, | ||||
|             Box::new(handler), | ||||
|         )?; | ||||
|         user.0.changed().await?; | ||||
|         let user = match &*user.0.borrow() { | ||||
|             discord_sdk::wheel::UserState::Connected(user) => user.clone(), | ||||
|             discord_sdk::wheel::UserState::Disconnected(err) => { | ||||
|                 ceprintln!("Failed to connect to Discord: {err}"); | ||||
|                 bail!("{}", err); | ||||
|             } | ||||
|         }; | ||||
|         let uid = user.id; | ||||
|         cprintln!( | ||||
|             "Logged in as: {user}#{discriminator}", | ||||
|             user = user.username, | ||||
|             discriminator = user | ||||
|                 .discriminator | ||||
|                 .map(|d| d.to_string()) | ||||
|                 .unwrap_or_else(|| "????".to_owned()) | ||||
|         ); | ||||
|         let mut activity = ActivityBuilder::new() | ||||
|             .state("Testing") | ||||
|             .assets(Assets::default().large("scrap_logo", Some("Testing"))) | ||||
|             .timestamps(Some(SystemTime::now()), Option::<SystemTime>::None) | ||||
|             .details("Testing ScrapHack"); | ||||
|         if false { | ||||
|             // (SCRAP.is_server()||SCRAP.is_client())
 | ||||
|             let players = 1; | ||||
|             let capacity = 32; | ||||
|             activity = activity | ||||
|                 .instance(true) | ||||
|                 .party( | ||||
|                     "Testt", | ||||
|                     NonZeroU32::new(players), | ||||
|                     NonZeroU32::new(capacity), | ||||
|                     if false { | ||||
|                         PartyPrivacy::Private | ||||
|                     } else { | ||||
|                         PartyPrivacy::Public | ||||
|                     } | ||||
|                 ) | ||||
|                 .secrets(Secrets { | ||||
|                     r#match: Some("MATCH".to_owned()),     // Use server_ip+port
 | ||||
|                     join: Some("JOIN".to_owned()),         // Use server_ip+port
 | ||||
|                     spectate: Some("SPECTATE".to_owned()), // Use server_ip+port
 | ||||
|                 }); | ||||
|         } | ||||
| 
 | ||||
|         discord.update_activity(activity).await?; | ||||
|         loop { | ||||
|             if let Ok(req) = wheel.activity().0.try_recv() { | ||||
|                 cprintln!("Got Join request: {req:?}"); | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										96
									
								
								tools/remaster/scraphacks_rs/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								tools/remaster/scraphacks_rs/src/lib.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | |||
| #![feature(abi_thiscall)] | ||||
| #![feature(c_variadic)] | ||||
| mod discord; | ||||
| mod lua; | ||||
| mod mem; | ||||
| mod parser; | ||||
| mod scrap; | ||||
| use std::ffi::{c_char, c_void, CString}; | ||||
| use anyhow::Result; | ||||
| use crate::mem::search; | ||||
| use crate::scrap::SCRAP; | ||||
| use shadow_rs::shadow; | ||||
| use winsafe::{co::{MB, CS, WS}, prelude::*, HWND, WNDCLASSEX, RegisterClassEx, WString}; | ||||
| 
 | ||||
| shadow!(build); | ||||
| 
 | ||||
| custom_print::define_macros!({cprint, cprintln, cdbg}, fmt, |value: &str| {crate::scrap::SCRAP.print(value)}); | ||||
| custom_print::define_macros!({ceprint, ceprintln}, fmt, |value: &str| {crate::scrap::SCRAP.print_c(0x800000,value)}); | ||||
| 
 | ||||
| #[allow(clippy::single_component_path_imports)] | ||||
| pub(crate) use {cdbg, cprint, cprintln}; | ||||
| #[warn(clippy::single_component_path_imports)] | ||||
| pub(crate) use {ceprint, ceprintln}; | ||||
| 
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug)] | ||||
| struct PyMethodDef { | ||||
|     name: *const c_char, | ||||
|     func: *const (*const c_void, *const c_void), | ||||
|     ml_flags: i32, | ||||
|     doc: *const c_char, | ||||
| } | ||||
| 
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug)] | ||||
| struct PyModuleDef { | ||||
|     name: *const c_char, | ||||
|     methods: *const PyMethodDef, | ||||
| } | ||||
| 
 | ||||
| fn init_py_mod() { | ||||
|     let py_init_module: fn( | ||||
|         *const c_char,      // name
 | ||||
|         *const PyMethodDef, // methods
 | ||||
|         *const c_char,      // doc
 | ||||
|         *const (),          // passthrough
 | ||||
|         i32,                // module_api_version
 | ||||
|     ) -> *const () = | ||||
|         unsafe { std::mem::transmute(search("68 *{\"Scrap\" 00} e8 ${'}", 1, None).unwrap_or_default()) }; | ||||
|     let name = CString::new("ScrapHack").unwrap_or_default(); | ||||
|     let desc = CString::new("ScrapHack Rust version").unwrap_or_default(); | ||||
|     let methods: &[PyMethodDef] = &[PyMethodDef { | ||||
|         name: 0 as _, | ||||
|         func: 0 as _, | ||||
|         ml_flags: 0, | ||||
|         doc: 0 as _, | ||||
|     }]; | ||||
|     assert!( | ||||
|         !py_init_module(name.as_ptr(), methods.as_ptr(), desc.as_ptr(), 0 as _, 1007).is_null() | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[no_mangle] | ||||
| pub extern "system" fn initScrapHack() { | ||||
|     #[cfg(feature = "console")] | ||||
|     unsafe { | ||||
|         AllocConsole(); | ||||
|     } | ||||
|     std::panic::set_hook(Box::new(|info| { | ||||
|         ceprintln!("ScrapHacks: {info}"); | ||||
|         HWND::DESKTOP | ||||
|             .MessageBox(&format!("{info}"), "ScrapHacks error", MB::ICONERROR) | ||||
|             .unwrap(); | ||||
|         std::process::exit(1); | ||||
|     })); | ||||
|     init_py_mod(); | ||||
|     print_version_info(); | ||||
|     cprintln!("{SCRAP:#x?}"); | ||||
| } | ||||
| 
 | ||||
| #[no_mangle] | ||||
| pub extern "system" fn DllMain(_inst: isize, _reason: u32, _: *const u8) -> u32 { | ||||
|     1 | ||||
| } | ||||
| 
 | ||||
| fn print_version_info() { | ||||
|     cprintln!( | ||||
|         "{} v{} ({} {}), built for {} by {}.", | ||||
|         build::PROJECT_NAME, | ||||
|         build::PKG_VERSION, | ||||
|         build::SHORT_COMMIT, | ||||
|         build::BUILD_TIME, | ||||
|         build::BUILD_TARGET, | ||||
|         build::RUST_VERSION | ||||
|     ); | ||||
| } | ||||
							
								
								
									
										204
									
								
								tools/remaster/scraphacks_rs/src/lua.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								tools/remaster/scraphacks_rs/src/lua.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,204 @@ | |||
| use std::{path::PathBuf, sync::Arc}; | ||||
| 
 | ||||
| use crate::{ | ||||
|     cprintln, | ||||
|     mem::{get_module, get_modules}, | ||||
|     parser::Cmd, | ||||
| }; | ||||
| use anyhow::{anyhow, bail, Result}; | ||||
| use detour3::GenericDetour; | ||||
| use mlua::{prelude::*, Variadic}; | ||||
| use pelite::pattern; | ||||
| use pelite::pe32::{Pe, PeObject}; | ||||
| use rustc_hash::FxHashMap; | ||||
| use winsafe::{prelude::*, HINSTANCE}; | ||||
| 
 | ||||
| struct Ptr(*const ()); | ||||
| 
 | ||||
| impl LuaUserData for Ptr { | ||||
|     fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { | ||||
|         methods.add_meta_method(LuaMetaMethod::ToString, |_, this, _: ()| { | ||||
|             Ok(format!("{:p}", this.0)) | ||||
|         }); | ||||
|         methods.add_method("read", |_, this, (size,): (usize,)| { | ||||
|             let addr = this.0 as u32; | ||||
|             let ptr = this.0 as *const u8; | ||||
|             let info = region::query(ptr).map_err(mlua::Error::external)?; | ||||
|             let end = info.as_ptr_range::<()>().end as u32; | ||||
|             let size = ((end - addr) as usize).min(size); | ||||
|             if !info.is_readable() { | ||||
|                 return Err(LuaError::external(anyhow!("No read permission on page"))); | ||||
|             } | ||||
|             let data = unsafe { std::slice::from_raw_parts(ptr, size) }; | ||||
|             Ok(data.to_vec()) | ||||
|         }); | ||||
|         methods.add_method("write", |_, this, data: Vec<u8>| { | ||||
|             let data = data.as_slice(); | ||||
|             let addr = this.0 as *const u8; | ||||
|             unsafe { | ||||
|                 let handle = region::protect_with_handle( | ||||
|                     addr, | ||||
|                     data.len(), | ||||
|                     region::Protection::READ_WRITE_EXECUTE, | ||||
|                 ) | ||||
|                 .map_err(mlua::Error::external)?; | ||||
|                 std::ptr::copy(data.as_ptr(), addr as *mut u8, data.len()); | ||||
|                 drop(handle); | ||||
|             }; | ||||
|             Ok(()) | ||||
|         }); | ||||
|         // methods.add_method("hook", |_, this, func: LuaFunction| -> LuaResult<()> {
 | ||||
|         //     let addr = this.0;
 | ||||
|         //     cprintln!("Hook: {func:?} @ {addr:p}");
 | ||||
|         //     let dt = unsafe { GenericDetour::<extern "thiscall" fn(*const (), (u32,u32,u32)) -> u32>::new(std::mem::transmute(addr), hook_func) }.unwrap();
 | ||||
|         //     Err(LuaError::external(anyhow!("TODO: hook")))
 | ||||
|         // });
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // extern "thiscall" fn hook_func(this: *const (), args: (u32,u32,u32)) -> u32 {
 | ||||
| //     return 0;
 | ||||
| // }
 | ||||
| 
 | ||||
| pub(crate) fn init() -> Result<Lua> { | ||||
|     let lua = unsafe { Lua::unsafe_new() }; | ||||
|     { | ||||
|         let globals = lua.globals(); | ||||
|         globals.set("scan", lua.create_function(lua_scan)?)?; | ||||
|         globals.set("print", lua.create_function(lua_print)?)?; | ||||
|         globals.set("hook", lua.create_function(lua_hook)?)?; | ||||
|         globals.set("imports", lua.create_function(lua_imports)?)?; | ||||
|         globals.set( | ||||
|             "ptr", | ||||
|             lua.create_function(|_, addr: u32| Ok(Ptr(addr as _)))?, | ||||
|         )?; | ||||
|         globals.set( | ||||
|             "ptr", | ||||
|             lua.create_function(lua_alloc)?, | ||||
|         )?; | ||||
|     } | ||||
|     Ok(lua) | ||||
| } | ||||
| 
 | ||||
| fn lua_val_to_string(val: &LuaValue) -> LuaResult<String> { | ||||
|     Ok(match val { | ||||
|         LuaNil => "Nil".to_owned(), | ||||
|         LuaValue::Boolean(b) => format!("{b}"), | ||||
|         LuaValue::LightUserData(u) => format!("{u:?}"), | ||||
|         LuaValue::Integer(i) => format!("{i}"), | ||||
|         LuaValue::Number(n) => format!("{n}"), | ||||
|         LuaValue::String(s) => (s.to_str()?).to_string(), | ||||
|         LuaValue::Table(t) => { | ||||
|             let mut vals = vec![]; | ||||
|             for res in t.clone().pairs() { | ||||
|                 let (k, v): (LuaValue, LuaValue) = res?; | ||||
|                 vals.push(format!( | ||||
|                     "{k} = {v}", | ||||
|                     k = lua_val_to_string(&k)?, | ||||
|                     v = lua_val_to_string(&v)? | ||||
|                 )); | ||||
|             } | ||||
|             format!("{{{vals}}}", vals = vals.join(", ")) | ||||
|         } | ||||
|         LuaValue::Function(f) => format!("{f:?}"), | ||||
|         LuaValue::Thread(t) => format!("{t:?}"), | ||||
|         LuaValue::UserData(u) => format!("{u:?}"), | ||||
|         LuaValue::Error(e) => format!("{e:?}"), | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| fn lua_alloc(lua: &Lua, size: usize) -> LuaResult<Ptr> { | ||||
|     let data = vec![0u8;size].into_boxed_slice(); | ||||
|     Ok(Ptr(Box::leak(data).as_ptr() as _)) | ||||
| } | ||||
| 
 | ||||
| fn lua_hook(lua: &Lua, (addr, func): (u32, LuaFunction)) -> LuaResult<()> { | ||||
|     cprintln!("Hook: {func:?} @ {addr:08x}"); | ||||
|     Err(LuaError::external(anyhow!("TODO: hook"))) | ||||
| } | ||||
| 
 | ||||
| fn lua_imports(lua: &Lua, (): ()) -> LuaResult<()> { | ||||
|     Err(LuaError::external(anyhow!("TODO: imports"))) | ||||
| } | ||||
| 
 | ||||
| fn lua_print(lua: &Lua, args: Variadic<LuaValue>) -> LuaResult<()> { | ||||
|     let msg: Vec<String> = args | ||||
|         .into_iter() | ||||
|         .map(|v| lua_val_to_string(&v)) | ||||
|         .collect::<LuaResult<Vec<String>>>()?; | ||||
|     cprintln!("{}", msg.join(" ")); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| enum ScanMode { | ||||
|     MainModule, | ||||
|     Modules(Vec<String>), | ||||
|     All, | ||||
| } | ||||
| 
 | ||||
| impl FromLua<'_> for ScanMode { | ||||
|     fn from_lua<'lua>(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> { | ||||
|         match &lua_value { | ||||
|             LuaValue::Nil => Ok(ScanMode::MainModule), | ||||
|             LuaValue::Boolean(true) => Ok(ScanMode::All), | ||||
|             LuaValue::Table(t) => Ok(ScanMode::Modules(FromLua::from_lua(lua_value, lua)?)), | ||||
|             _ => Err(LuaError::FromLuaConversionError { | ||||
|                 from: lua_value.type_name(), | ||||
|                 to: "scan_mode", | ||||
|                 message: None, | ||||
|             }), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn lua_scan(lua: &Lua, (pattern, scan_mode): (String, ScanMode)) -> LuaResult<LuaTable> { | ||||
|     let pat = pattern::parse(&pattern).map_err(mlua::Error::external)?; | ||||
|     let mut ret = FxHashMap::default(); | ||||
|     let modules = match scan_mode { | ||||
|         ScanMode::MainModule => vec![get_module(None).map_err(mlua::Error::external)?], | ||||
|         ScanMode::Modules(modules) => modules | ||||
|             .into_iter() | ||||
|             .map(|m| get_module(Some(m))) | ||||
|             .collect::<Result<_>>() | ||||
|             .map_err(mlua::Error::external)?, | ||||
|         ScanMode::All => get_modules().map_err(mlua::Error::external)?, | ||||
|     }; | ||||
|     'outer: for module in modules { | ||||
|         let regions = region::query_range(module.image().as_ptr(), module.image().len()) | ||||
|             .map_err(mlua::Error::external)?; | ||||
|         for region in regions { | ||||
|             let Ok(region)=region else { | ||||
|                 continue 'outer; | ||||
|             }; | ||||
|             if !region.is_readable() { | ||||
|                 continue 'outer; | ||||
|             } | ||||
|         } | ||||
|         let h_module = unsafe { HINSTANCE::from_ptr(module.image().as_ptr() as _) }; | ||||
|         let module_name = PathBuf::from( | ||||
|             h_module | ||||
|                 .GetModuleFileName() | ||||
|                 .map_err(mlua::Error::external)?, | ||||
|         ) | ||||
|         .file_name() | ||||
|         .unwrap() | ||||
|         .to_str() | ||||
|         .unwrap() | ||||
|         .to_owned(); | ||||
|         if let Ok(res) = crate::mem::scan(&pat, &module) { | ||||
|             if !res.is_empty() { | ||||
|                 let res: Vec<Vec<Ptr>> = res | ||||
|                     .into_iter() | ||||
|                     .map(|res| res.into_iter().map(|a| Ptr(a as _)).collect()) | ||||
|                     .collect(); | ||||
|                 ret.insert(module_name, res); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     lua.create_table_from(ret.into_iter()) | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn exec(chunk: &str) -> Result<()> { | ||||
|     Ok(init()?.load(chunk).set_name("ScrapLua")?.exec()?) | ||||
| } | ||||
							
								
								
									
										94
									
								
								tools/remaster/scraphacks_rs/src/mem.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								tools/remaster/scraphacks_rs/src/mem.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | |||
| use std::str::FromStr; | ||||
| 
 | ||||
| use anyhow::anyhow; | ||||
| use anyhow::bail; | ||||
| use anyhow::Result; | ||||
| use pelite::pattern::parse; | ||||
| use pelite::pattern::save_len; | ||||
| use pelite::pattern::Atom; | ||||
| use pelite::pe32::{Pe, PeView}; | ||||
| use winsafe::co::TH32CS; | ||||
| use winsafe::prelude::*; | ||||
| use winsafe::HINSTANCE; | ||||
| use winsafe::HPROCESSLIST; | ||||
| pub(crate) struct Pattern(Vec<Atom>, usize); | ||||
| 
 | ||||
| impl Pattern { | ||||
|     pub(crate) fn set_index(mut self, idx: usize) -> Self { | ||||
|         self.1 = idx; | ||||
|         self | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for Pattern { | ||||
|     type Err = anyhow::Error; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(Self(parse(s)?, 0)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Pattern { | ||||
|     pub(crate) fn scan(&self, module: Option<String>) -> Result<u32> { | ||||
|         let pe = get_module(module)?; | ||||
|         let scan = pe.scanner(); | ||||
|         let mut save = vec![0u32; save_len(&self.0)]; | ||||
|         if !scan.finds(&self.0, 0..u32::MAX, &mut save) { | ||||
|             bail!("Pattern not found"); | ||||
|         } | ||||
|         save.get(self.1) | ||||
|             .ok_or_else(|| anyhow!("Result index out of range")) | ||||
|             .and_then(|r| pe.rva_to_va(*r).map_err(|e| e.into())) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn get_modules() -> Result<Vec<PeView<'static>>> { | ||||
|     let mut res = vec![]; | ||||
|     let pid = std::process::id(); | ||||
|     let mut h_snap = HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?; | ||||
|     for module in h_snap.iter_modules() { | ||||
|         res.push(unsafe { PeView::module(module?.hModule.as_ptr() as *const u8) }); | ||||
|     } | ||||
|     Ok(res) | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn get_module(module: Option<String>) -> Result<PeView<'static>> { | ||||
|     let hmodule = HINSTANCE::GetModuleHandle(module.as_deref())?; | ||||
|     Ok(unsafe { PeView::module(hmodule.as_ptr() as *const u8) }) | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn scan(pat: &[Atom], pe: &PeView) -> Result<Vec<Vec<u32>>> { | ||||
|     let mut ret = vec![]; | ||||
|     let scan = pe.scanner(); | ||||
|     let mut m = scan.matches(pat, 0..u32::MAX); | ||||
|     let mut save = vec![0u32; save_len(pat)]; | ||||
|     while m.next(&mut save) { | ||||
|         ret.push( | ||||
|             save.iter() | ||||
|                 .map(|rva| pe.rva_to_va(*rva).map_err(|e| e.into())) | ||||
|                 .collect::<Result<Vec<u32>>>()?, | ||||
|         ); | ||||
|     } | ||||
|     Ok(ret) | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn search(pat: &str, idx: usize, module: Option<String>) -> Result<u32> { | ||||
|     pat.parse::<Pattern>()?.set_index(idx).scan(module) | ||||
| } | ||||
| 
 | ||||
| fn addr_info(addr: u32) -> Result<()> { | ||||
|     let pid = std::process::id(); | ||||
|     let mut h_snap = HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?; | ||||
|     for module in h_snap.iter_modules() { | ||||
|         let module = module?; | ||||
|         let module_name = module.szModule(); | ||||
|         if module_name.to_lowercase() == "kernel32.dll" { | ||||
|             continue; | ||||
|         } | ||||
|         let mod_range = | ||||
|             unsafe { module.modBaseAddr..module.modBaseAddr.offset(module.modBaseSize as isize) }; | ||||
|         println!("{module_name}: {mod_range:?}"); | ||||
|         // let module = unsafe { PeView::module(module.modBaseAddr as *const u8) };
 | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										177
									
								
								tools/remaster/scraphacks_rs/src/parser.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								tools/remaster/scraphacks_rs/src/parser.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,177 @@ | |||
| // use crate::{cdbg, ceprintln, cprint, cprintln};
 | ||||
| use std::path::PathBuf; | ||||
| use std::str::FromStr; | ||||
| use anyhow::{anyhow, Result}; | ||||
| use nom::branch::alt; | ||||
| use nom::bytes::complete::{take_till, take_while1}; | ||||
| use nom::character::complete::{digit1, hex_digit1}; | ||||
| use nom::character::streaming::char; | ||||
| use nom::combinator::{eof, opt, rest}; | ||||
| use nom::sequence::{separated_pair, tuple}; | ||||
| use nom::{IResult, Parser}; | ||||
| use nom_locate::LocatedSpan; | ||||
| use nom_supreme::error::ErrorTree; | ||||
| use nom_supreme::final_parser::final_parser; | ||||
| use nom_supreme::tag::complete::{tag, tag_no_case}; | ||||
| use nom_supreme::ParserExt; | ||||
| use pelite::pattern::{self, Atom}; | ||||
| 
 | ||||
| type Span<'a> = LocatedSpan<&'a str>; | ||||
| 
 | ||||
| type ParseResult<'a, 'b, T> = IResult<Span<'a>, T, ErrorTree<Span<'b>>>; | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum Cmd { | ||||
|     Imports, | ||||
|     Read(u32, usize), | ||||
|     ReadPE(u32, usize), | ||||
|     Write(u32, Vec<u8>), | ||||
|     Disams(u32, usize), | ||||
|     Info(Option<u32>), | ||||
|     Script(PathBuf), | ||||
|     Unload, | ||||
|     ScanModule(Vec<Atom>, Option<String>), | ||||
|     Lua(String), | ||||
| } | ||||
| 
 | ||||
| impl FromStr for Cmd { | ||||
|     type Err = anyhow::Error; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self> { | ||||
|         match parse(s) { | ||||
|             Ok(cmd) => Ok(cmd), | ||||
|             Err(err) => Err(anyhow!("{}", err)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn ws(input: Span) -> ParseResult<()> { | ||||
|     take_while1(|c: char| c.is_whitespace()) | ||||
|         .value(()) | ||||
|         .context("Whitepace") | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| //
 | ||||
| 
 | ||||
| /// Test
 | ||||
| 
 | ||||
| fn hex_bytes(input: Span) -> ParseResult<Vec<u8>> { | ||||
|     hex_digit1 | ||||
|         .map_res_cut(hex::decode) | ||||
|         .context("Hex string") | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn num(input: Span) -> ParseResult<usize> { | ||||
|     digit1 | ||||
|         .map_res_cut(|n: Span| parse_int::parse(&n)) | ||||
|         .context("Number") | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn address(input: Span) -> ParseResult<u32> { | ||||
|     tag_no_case("0x") | ||||
|         .precedes(hex_digit1) | ||||
|         .recognize() | ||||
|         .map_res_cut(|addr: Span| parse_int::parse::<u32>(&addr)) | ||||
|         .context("Memory address") | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_read_pe(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("read_pe") | ||||
|         .precedes(ws) | ||||
|         .precedes(separated_pair(address, ws, num.opt())) | ||||
|         .map(|(addr, size)| Cmd::ReadPE(addr, size.unwrap_or(0x100))) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_read(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("read") | ||||
|         .precedes(ws) | ||||
|         .precedes(separated_pair(address, ws, num.opt())) | ||||
|         .map(|(addr, size)| Cmd::Read(addr, size.unwrap_or(0x100))) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_disasm(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("disasm") | ||||
|         .precedes(ws) | ||||
|         .precedes(separated_pair(address, ws, num.opt())) | ||||
|         .map(|(addr, size)| Cmd::Disams(addr, size.unwrap_or(50))) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_write(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("write") | ||||
|         .precedes(ws) | ||||
|         .precedes(separated_pair(address, ws, hex_bytes)) | ||||
|         .map(|(addr, data)| Cmd::Write(addr, data)) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_info(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("info") | ||||
|         .precedes(eof) | ||||
|         .value(Cmd::Info(None)) | ||||
|         .or(tag("info") | ||||
|             .precedes(ws) | ||||
|             .precedes(address) | ||||
|             .map(|addr| Cmd::Info(Some(addr)))) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_scan(input: Span) -> ParseResult<Cmd> { | ||||
|     let (input, _) = tag("scan").parse(input)?; | ||||
|     let (input, module) = | ||||
|         opt(tuple((char(':'), take_till(|c: char| c.is_whitespace())))).parse(input)?; | ||||
|     let module = module.map(|(_, module)| module.fragment().to_string()); | ||||
|     let (input, _) = ws.parse(input)?; | ||||
|     let (input, pattern) = rest | ||||
|         .map_res(|pat: Span| pattern::parse(&pat)) | ||||
|         .parse(input)?; | ||||
|     Ok((input, Cmd::ScanModule(pattern, module))) | ||||
| } | ||||
| 
 | ||||
| fn parse_unload(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("unload").value(Cmd::Unload).parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_imports(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("imports").value(Cmd::Imports).parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_lua(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("lua") | ||||
|         .precedes(ws) | ||||
|         .precedes(rest) | ||||
|         .map(|s| Cmd::Lua(s.fragment().to_string())) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse_script(input: Span) -> ParseResult<Cmd> { | ||||
|     tag("script") | ||||
|         .precedes(ws) | ||||
|         .precedes(rest) | ||||
|         .map(|s| Cmd::Script(PathBuf::from(s.fragment()))) | ||||
|         .parse(input) | ||||
| } | ||||
| 
 | ||||
| fn parse(input: &str) -> Result<Cmd, ErrorTree<Span<'_>>> { | ||||
|     final_parser( | ||||
|         alt(( | ||||
|             parse_imports, | ||||
|             parse_unload, | ||||
|             parse_scan, | ||||
|             parse_info, | ||||
|             parse_write, | ||||
|             parse_read, | ||||
|             parse_read_pe, | ||||
|             parse_script, | ||||
|             parse_disasm, | ||||
|             parse_lua, | ||||
|         )) | ||||
|         .context("command"), | ||||
|     )(Span::new(input)) | ||||
| } | ||||
							
								
								
									
										381
									
								
								tools/remaster/scraphacks_rs/src/scrap.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								tools/remaster/scraphacks_rs/src/scrap.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,381 @@ | |||
| use crate::{ | ||||
|     cdbg, ceprintln, cprint, cprintln, lua, | ||||
|     mem::{get_module, scan, search}, | ||||
|     parser::Cmd, discord, | ||||
| }; | ||||
| use anyhow::{bail, Result}; | ||||
| use derivative::Derivative; | ||||
| use detour3::GenericDetour; | ||||
| use futures::executor::block_on; | ||||
| use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter}; | ||||
| use once_cell::sync::Lazy; | ||||
| use pelite::{pe::PeView, pe32::Pe}; | ||||
| use std::{ | ||||
|     collections::HashMap, | ||||
|     ffi::{c_char, CStr, CString}, | ||||
|     fmt::Debug, | ||||
|     ptr, | ||||
|     thread::JoinHandle, | ||||
|     time::Duration, | ||||
| }; | ||||
| use winsafe::HINSTANCE; | ||||
| use winsafe::{co::TH32CS, prelude::*, HPROCESSLIST}; | ||||
| 
 | ||||
| const POINTER_SIZE: usize = std::mem::size_of::<*const ()>(); | ||||
| 
 | ||||
| #[repr(C)] | ||||
| struct VirtualMethodTable(*const *const ()); | ||||
| 
 | ||||
| impl Debug for VirtualMethodTable { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         let mut methods = vec![]; | ||||
|         for idx in 0.. { | ||||
|             let ptr = self.get::<()>(idx); | ||||
|             if ptr.is_null() | ||||
|                 || !region::query(ptr) | ||||
|                     .map(|r| r.is_executable()) | ||||
|                     .unwrap_or(false) | ||||
|             { | ||||
|                 break; | ||||
|             } | ||||
|             methods.push(ptr); | ||||
|         } | ||||
|         f.debug_tuple("VMT").field(&methods).finish() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl VirtualMethodTable { | ||||
|     fn get<T>(&self, offset: usize) -> *const T { | ||||
|         unsafe { self.0.add(POINTER_SIZE * offset).read() as *const T } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Derivative)] | ||||
| #[derivative(Debug)] | ||||
| pub struct Scrap { | ||||
|     print: extern "C" fn(u32, *const c_char, u8), | ||||
|     console_detour: GenericDetour<extern "C" fn(*const c_char)>, | ||||
|     world: WorldPointer, | ||||
|     discord_thread_handle: JoinHandle<Result<()>>, | ||||
| } | ||||
| 
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug)] | ||||
| struct Entity { | ||||
|     vmt: VirtualMethodTable, | ||||
| } | ||||
| 
 | ||||
| #[repr(C)] | ||||
| #[derive(Debug)] | ||||
| struct HashTableEntry<T> { | ||||
|     data: *const T, | ||||
|     name: *const c_char, | ||||
|     next: *const Self, | ||||
| } | ||||
| 
 | ||||
| #[repr(C)] | ||||
| struct HashTable<T> { | ||||
|     num_slots: u32, | ||||
|     chains: *const *const HashTableEntry<T>, | ||||
| } | ||||
| 
 | ||||
| fn try_read<T>(ptr: *const T) -> Option<T> { | ||||
|     (!ptr.is_null()).then(|| unsafe { ptr.read() }) | ||||
| } | ||||
| 
 | ||||
| impl<T: std::fmt::Debug> std::fmt::Debug for HashTable<T> { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         let mut entries: HashMap<String, Option<T>> = HashMap::default(); | ||||
|         for offset in 0..self.num_slots { | ||||
|             let offset = offset as _; | ||||
|             // let chain=vec![];
 | ||||
|             let mut chain_ptr = unsafe { self.chains.offset(offset).read() }; | ||||
|             while !chain_ptr.is_null() { | ||||
|                 let entry = unsafe { chain_ptr.read() }; | ||||
|                 let data = try_read(entry.data); | ||||
|                 let key = unsafe { CStr::from_ptr(entry.name) } | ||||
|                     .to_str() | ||||
|                     .unwrap() | ||||
|                     .to_owned(); | ||||
|                 chain_ptr = entry.next; | ||||
|                 entries.insert(key, data); | ||||
|             } | ||||
|         } | ||||
|         f.debug_struct(&format!("HashTable @ {self:p} ")) | ||||
|             .field("num_slots", &self.num_slots) | ||||
|             .field("entries", &entries) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[repr(C)] | ||||
| struct World { | ||||
|     vmt: VirtualMethodTable, | ||||
|     entities: HashTable<Entity>, | ||||
| } | ||||
| 
 | ||||
| impl Debug for World { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         f.debug_struct("World") | ||||
|             .field("vmt", &self.vmt) | ||||
|             .field("entities", &self.entities) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct WorldPointer(u32); | ||||
| 
 | ||||
| impl std::fmt::Debug for WorldPointer { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         let ptr = self.ptr(); | ||||
|         let world = unsafe { ptr.read() }; | ||||
|         f.debug_tuple(&format!("WorldPointer @ {ptr:p} ")) | ||||
|             .field(&world) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl WorldPointer { | ||||
|     fn ptr(&self) -> *const World { | ||||
|         let ptr = self.0 as *const *const World; | ||||
|         unsafe { ptr.read() } | ||||
|     } | ||||
|     
 | ||||
|     fn get_hashtable(&self) { | ||||
|         // let ents = unsafe { self.ptr().read().entities.read() };
 | ||||
|         // cprintln!("Ents: {ents:?}");
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) static SCRAP: Lazy<Scrap> = | ||||
|     Lazy::new(|| Scrap::init().expect("Failed to initialize Scrap data structure")); | ||||
| 
 | ||||
| impl Scrap { | ||||
|     const PRINT_PATTERN: &str = r#"6a0068 *{"Scrap engine"} 6a?e8 $'"#; | ||||
|     const PY_EXEC: &str = r#"68 *{"import Viewer"} e8 $'"#; | ||||
|     const WORLD_PATTERN: &str = r#"8b 0d *{'} 68 *"CTFFriends""#; | ||||
|     fn init() -> Result<Self> { | ||||
|         let scrap = unsafe { | ||||
|             Self { | ||||
|                 world: WorldPointer(search(Self::WORLD_PATTERN, 1, None)? as _), | ||||
|                 print: std::mem::transmute(search(Self::PRINT_PATTERN, 1, None)?), | ||||
|                 console_detour: GenericDetour::<extern "C" fn(*const c_char)>::new( | ||||
|                     std::mem::transmute(search(Self::PY_EXEC, 1, None)?), | ||||
|                     Self::console_input, | ||||
|                 )?, | ||||
|                 discord_thread_handle: discord::Client::run()?, | ||||
|             } | ||||
|         }; | ||||
|         unsafe { scrap.console_detour.enable()? } | ||||
|         Ok(scrap) | ||||
|     } | ||||
| 
 | ||||
|     extern "C" fn console_input(orig_line: *const c_char) { | ||||
|         let line = unsafe { CStr::from_ptr(orig_line) }.to_str(); | ||||
|         let Ok(line) = line else { | ||||
|             return SCRAP.console_detour.call(orig_line); | ||||
|         }; | ||||
|         if let Some(cmd) = line.strip_prefix('$') { | ||||
|             let res = cmd.parse().and_then(|cmd: Cmd| cmd.exec()); | ||||
|             if let Err(err) = res { | ||||
|                 ceprintln!("Error: {err}"); | ||||
|             } | ||||
|             return; | ||||
|         }; | ||||
|         SCRAP.console_detour.call(orig_line) | ||||
|     } | ||||
| 
 | ||||
|     pub fn println(&self, msg: &str) { | ||||
|         self.println_c(0x008000, msg) | ||||
|     } | ||||
| 
 | ||||
|     pub fn print(&self, msg: &str) { | ||||
|         self.print_c(0x008000, msg) | ||||
|     } | ||||
| 
 | ||||
|     pub fn print_c(&self, col: u32, msg: &str) { | ||||
|         let col = (col & 0xffffff).swap_bytes() >> 8; // 0xRRGGBB -> 0xBBGGRR
 | ||||
|         let msg = CString::new(msg.to_string()).unwrap(); | ||||
|         (self.print)(col, msg.as_ptr(), 0); | ||||
|     } | ||||
| 
 | ||||
|     pub fn println_c(&self, col: u32, msg: &str) { | ||||
|         let msg = msg.to_owned() + "\n"; | ||||
|         self.print_c(col, &msg) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Cmd { | ||||
|     pub(crate) fn exec(&self) -> Result<()> { | ||||
|         let pe = get_module(None)?; | ||||
|         match self { | ||||
|             Cmd::Imports => { | ||||
|                 for import in pe.imports()? { | ||||
|                     let iat = import.iat()?; | ||||
|                     let int = import.int()?; | ||||
|                     for (func, imp) in iat.zip(int) { | ||||
|                         let imp = imp?; | ||||
|                         cprintln!( | ||||
|                             "{addr:p}: {name} {imp:?}", | ||||
|                             name = import.dll_name()?, | ||||
|                             addr = func | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Cmd::Read(addr, size) => { | ||||
|                 let ptr = *addr as *const u8; | ||||
|                 let info = region::query(ptr)?; | ||||
|                 let end = info.as_ptr_range::<()>().end as u32; | ||||
|                 let size = ((end - addr) as usize).min(*size); | ||||
|                 if !info.is_readable() { | ||||
|                     bail!("No read permission on page"); | ||||
|                 } | ||||
|                 let data = unsafe { std::slice::from_raw_parts(ptr, size) }; | ||||
|                 cprintln!("{}", &rhexdump::hexdump_offset(data, *addr)); | ||||
|             } | ||||
|             Cmd::Disams(addr, size) => { | ||||
|                 let ptr = *addr as *const u8; | ||||
|                 let info = region::query(ptr)?; | ||||
|                 let end = info.as_ptr_range::<()>().end as u32; | ||||
|                 let size = ((end - addr) as usize).min(*size); | ||||
|                 if !info.is_readable() { | ||||
|                     bail!("No read permission on page"); | ||||
|                 } | ||||
|                 let data = unsafe { std::slice::from_raw_parts(ptr, size) }; | ||||
|                 let mut decoder = Decoder::with_ip(32, data, *addr as u64, DecoderOptions::NONE); | ||||
|                 let mut instruction = Instruction::default(); | ||||
|                 let mut output = String::new(); | ||||
|                 let mut formatter = NasmFormatter::new(); | ||||
|                 while decoder.can_decode() { | ||||
|                     decoder.decode_out(&mut instruction); | ||||
|                     output.clear(); | ||||
|                     formatter.format(&instruction, &mut output); | ||||
|                     cprint!("{:016X} ", instruction.ip()); | ||||
|                     let start_index = (instruction.ip() - (*addr as u64)) as usize; | ||||
|                     let instr_bytes = &data[start_index..start_index + instruction.len()]; | ||||
|                     for b in instr_bytes.iter() { | ||||
|                         cprint!("{:02X}", b); | ||||
|                     } | ||||
|                     cprintln!(" {}", output); | ||||
|                 } | ||||
|             } | ||||
|             Cmd::Write(addr, data) => { | ||||
|                 let data = data.as_slice(); | ||||
|                 let addr = *addr as *const u8; | ||||
|                 unsafe { | ||||
|                     let handle = region::protect_with_handle( | ||||
|                         addr, | ||||
|                         data.len(), | ||||
|                         region::Protection::READ_WRITE_EXECUTE, | ||||
|                     )?; | ||||
|                     std::ptr::copy(data.as_ptr(), addr as *mut u8, data.len()); | ||||
|                     drop(handle); | ||||
|                 }; | ||||
|             } | ||||
|             Cmd::ReadPE(addr, size) => { | ||||
|                 if !region::query(*addr as *const ())?.is_readable() { | ||||
|                     bail!("No read permission for 0x{addr:x}"); | ||||
|                 } | ||||
|                 let data = pe.read_bytes(*addr)?; | ||||
|                 cprintln!("{}", &rhexdump::hexdump_offset(&data[..*size], *addr)); | ||||
|             } | ||||
|             Cmd::Info(None) => { | ||||
|                 let regions = region::query_range(ptr::null::<()>(), usize::MAX)?; | ||||
|                 for region in regions.flatten() { | ||||
|                     cprintln!( | ||||
|                         "{:?}: {}", | ||||
|                         region.as_ptr_range::<*const ()>(), | ||||
|                         region.protection() | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|             Cmd::Info(Some(addr)) => { | ||||
|                 let info = region::query(*addr as *const ())?; | ||||
|                 cprintln!( | ||||
|                     "{:?}: {}", | ||||
|                     info.as_ptr_range::<*const ()>(), | ||||
|                     info.protection() | ||||
|                 ); | ||||
|             } | ||||
|             Cmd::ScanModule(pat, module) => { | ||||
|                 cprintln!("{:?}", pat); | ||||
|                 let mut total_hits = 0; | ||||
|                 let mut modules = vec![]; | ||||
|                 let is_wildcard = matches!(module.as_deref(), Some("*")); | ||||
|                 if is_wildcard { | ||||
|                     let pid = std::process::id(); | ||||
|                     let mut h_snap = | ||||
|                         HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?; | ||||
|                     for module in h_snap.iter_modules() { | ||||
|                         let module = module?; | ||||
|                         let module_name = module.szModule(); | ||||
|                         let module_addr = module.hModule.as_ptr() as *const u8; | ||||
|                         let module = region::query_range(module_addr, module.modBaseSize as usize)? | ||||
|                             .all(|m| m.ok().map(|m| m.is_readable()).unwrap_or(false)) | ||||
|                             .then(|| unsafe { PeView::module(module_addr) }); | ||||
|                         if let Some(module) = module { | ||||
|                             modules.push((module_name, module)); | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     let module = HINSTANCE::GetModuleHandle(module.as_deref())?; | ||||
|                     let module_name = module.GetModuleFileName()?; | ||||
|                     let module_addr = module.as_ptr() as *const u8; | ||||
|                     let module = region::query(module_addr) | ||||
|                         .map(|m| m.is_readable()) | ||||
|                         .unwrap_or(false) | ||||
|                         .then(|| unsafe { PeView::module(module_addr) }); | ||||
|                     if let Some(module) = module { | ||||
|                         modules.push((module_name, module)); | ||||
|                     }; | ||||
|                 } | ||||
|                 for (module_name, pe) in modules { | ||||
|                     let res = scan(pat, &pe)?; | ||||
|                     if res.is_empty() { | ||||
|                         continue; | ||||
|                     } | ||||
|                     total_hits += res.len(); | ||||
|                     cprintln!("Module: {module_name}"); | ||||
|                     let sections = pe.section_headers(); | ||||
|                     for hit in &res { | ||||
|                         for (idx, addr) in hit.iter().enumerate() { | ||||
|                             let mut section_name = String::from("<invalid address>"); | ||||
|                             if let Ok(section_rva) = pe.va_to_rva(*addr) { | ||||
|                                 if let Some(section) = sections.by_rva(section_rva) { | ||||
|                                     section_name = match section.name() { | ||||
|                                         Ok(name) => name.to_string(), | ||||
|                                         Err(name_bytes) => format!("{name_bytes:?}"), | ||||
|                                     }; | ||||
|                                 } else { | ||||
|                                     section_name = String::from("<invalid section>"); | ||||
|                                 } | ||||
|                             }; | ||||
|                             if let Ok(region) = region::query(addr) { | ||||
|                                 cprintln!( | ||||
|                                     "\t{}: {:?} {} [{}] {:p}", | ||||
|                                     idx, | ||||
|                                     region.as_ptr_range::<()>(), | ||||
|                                     region.protection(), | ||||
|                                     section_name, | ||||
|                                     addr | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 cprintln!("Results: {total_hits}"); | ||||
|             } | ||||
|             Cmd::Lua(code) => { | ||||
|                 lua::exec(code)?; | ||||
|             } | ||||
|             Cmd::Script(path) => { | ||||
|                 for line in std::fs::read_to_string(path)?.lines() { | ||||
|                     line.parse().and_then(|cmd: Cmd| cmd.exec())?; | ||||
|                 } | ||||
|             } | ||||
|             other => bail!("Not implemented: {other:?}"), | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1
									
								
								tools/remaster/scraphacks_rs/target/.rustc_info.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tools/remaster/scraphacks_rs/target/.rustc_info.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| {"rustc_fingerprint":18143952876974389501,"outputs":{"16636649553340150347":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Earthnuker\\scoop\\persist\\rustup-msvc\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.70.0-nightly (9df3a39fb 2023-04-11)\nbinary: rustc\ncommit-hash: 9df3a39fb30575d808e70800f9fad5362aac57a2\ncommit-date: 2023-04-11\nhost: x86_64-pc-windows-msvc\nrelease: 1.70.0-nightly\nLLVM version: 16.0.2\n","stderr":""},"1185988223601034215":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Earthnuker\\scoop\\persist\\rustup-msvc\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}} | ||||
							
								
								
									
										3
									
								
								tools/remaster/scraphacks_rs/target/CACHEDIR.TAG
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								tools/remaster/scraphacks_rs/target/CACHEDIR.TAG
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| Signature: 8a477f597d28d172789f06886806bc55 | ||||
| # This file is a cache directory tag created by cargo. | ||||
| # For information about cache directory tags see https://bford.info/cachedir/ | ||||
|  | @ -0,0 +1,3 @@ | |||
| Signature: 8a477f597d28d172789f06886806bc55 | ||||
| # This file is a cache directory tag created by cargo. | ||||
| # For information about cache directory tags see https://bford.info/cachedir/ | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue