Compare commits
	
		
			No commits in common. "master" and "master" have entirely different histories.
		
	
	
		
	
		
					 65 changed files with 0 additions and 11479 deletions
				
			
		|  | @ -1,369 +0,0 @@ | ||||||
| # Callbacks |  | ||||||
| 
 |  | ||||||
| They are called from python with: |  | ||||||
| ```python |  | ||||||
| SAct.SetAct("EventFunc","!<CallbackName>") |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Note the `!` symbol in front of the callback name |  | ||||||
| 
 |  | ||||||
| ## OG |  | ||||||
| 
 |  | ||||||
| | Callback setup address | Callback name                | Callback funcion                       | Callback address | |  | ||||||
| |------------------------|------------------------------|----------------------------------------|------------------| |  | ||||||
| | `00486de0`             | "LaserRelease"               | `callbacks.LaserRelease`               | `00486e00`       | |  | ||||||
| | `0051f610`             | "ScorerSpriteFadeIn"         | `callbacks.ScorerSpriteFadeIn`         | `0051f630`       | |  | ||||||
| | `004d3920`             | "BackDoorSlot"               | `callbacks.BackDoorSlot`               | `004d3940`       | |  | ||||||
| | `0048aa20`             | "SwarmPress"                 | `callbacks.SwarmPress`                 | `0048aa40`       | |  | ||||||
| | `004bbb00`             | "BertoActionEnd"             | `callbacks.BertoActionEnd`             | `004bbb20`       | |  | ||||||
| | `0048f9b0`             | "VulcanCreate"               | `callbacks.VulcanCreate`               | `0048f9d0`       | |  | ||||||
| | `004bfea0`             | "DtritusHitEvent"            | `callbacks.DtritusHitEvent`            | `004bfec0`       | |  | ||||||
| | `004c3600`             | "GearInitAction"             | `callbacks.GearInitAction`             | `004c3620`       | |  | ||||||
| | `00491bb0`             | "TakeLife"                   | `callbacks.TakeLife`                   | `00491bd0`       | |  | ||||||
| | `00488300`             | "HookPlugFX"                 | `callbacks.HookPlugFX`                 | `00488320`       | |  | ||||||
| | `005800a0`             | "AnmSetAction"               | `callbacks.AnmSetAction`               | `005800c0`       | |  | ||||||
| | `004c8540`             | "NurseTryHammerAgain"        | `callbacks.NurseTryHammerAgain`        | `004c8560`       | |  | ||||||
| | `004d3080`             | "AcceleratorOnEnter"         | `callbacks.AcceleratorOnEnter`         | `004d30a0`       | |  | ||||||
| | `004bb670`             | "FXBertoAttack"              | `callbacks.FXBertoAttack`              | `004bb690`       | |  | ||||||
| | `00580fe0`             | "WarningPolice"              | `callbacks.WarningPolice`              | `00581000`       | |  | ||||||
| | `0058f690`             | "EndOfLaugh"                 | `callbacks.EndOfLaugh`                 | `0058f6b0`       | |  | ||||||
| | `0048dce0`             | "TeslaRelease"               | `callbacks.TeslaRelease`               | `0048dd00`       | |  | ||||||
| | `004ba1d0`             | "BankMasterActionPress"      | `callbacks.MessengerActionPress`       | `004bdff0`       | |  | ||||||
| | `004c9730`             | "ListeningPolice"            | `callbacks.ListeningPolice`            | `004c9750`       | |  | ||||||
| | `004d32f0`             | "AutoDoorSlot"               | `callbacks.AutoDoorSlot`               | `004d3310`       | |  | ||||||
| | `00483be0`             | "InfernoRelease"             | `callbacks.InfernoRelease`             | `00483c00`       | |  | ||||||
| | `004c3850`             | "GearEndAction"              | `callbacks.GearEndAction`              | `004c3870`       | |  | ||||||
| | `0051ce00`             | "EditText"                   | `callbacks.EditText`                   | `0051ce20`       | |  | ||||||
| | `00580910`             | "AnmSoundLoopEvent"          | `callbacks.AnmSoundLoopEvent`          | `00580930`       | |  | ||||||
| | `004c8780`             | "NurseActionPress"           | `callbacks.NurseActionPress`           | `004c87a0`       | |  | ||||||
| | `0051f9c0`             | "BoostScorer"                | `callbacks.BoostScorer`                | `0051f9e0`       | |  | ||||||
| | `004c5da0`             | "MessengerAttack"            | `callbacks.MessengerAttack`            | `004c5dc0`       | |  | ||||||
| | `0051f730`             | "ScorerSpriteFadeOut"        | `callbacks.ScorerSpriteFadeOut`        | `0051f750`       | |  | ||||||
| | `004ba010`             | "BankMasterAttack"           | `callbacks.BankMasterAttack`           | `004ba030`       | |  | ||||||
| | `004913d0`             | "VulcanFire"                 | `callbacks.VulcanFire`                 | `004913f0`       | |  | ||||||
| | `00483fc0`             | "InfernoOnStart"             | `callbacks.InfernoOnStart`             | `00483fe0`       | |  | ||||||
| | `00489010`             | "HookActivate"               | `callbacks.HookActivate`               | `00489030`       | |  | ||||||
| | `0048f7c0`             | "VulcanRelease"              | `callbacks.VulcanRelease`              | `0048f7e0`       | |  | ||||||
| | `004d3860`             | "AutoDoorUsrSlot"            | `callbacks.AutoDoorUsrSlot`            | `004d3880`       | |  | ||||||
| | `00481d70`             | "DevastatorFX"               | `callbacks.DevastatorFX`               | `00481d90`       | |  | ||||||
| | `0048aff0`             | "SwarmOnStart"               | `callbacks.SwarmOnStart`               | `0048b010`       | |  | ||||||
| | `004bd6c0`             | "BishopAttack"               | `callbacks.BishopAttack`               | `004bd6e0`       | |  | ||||||
| | `00485f50`             | "InfernoFX"                  | `callbacks.InfernoFX`                  | `00485f70`       | |  | ||||||
| | `0048ac10`             | "SwarmRelease"               | `callbacks.SwarmRelease`               | `0048ac30`       | |  | ||||||
| | `004c8300`             | "NurseInitHammerLoop"        | `callbacks.NurseInitHammerLoop`        | `004c8320`       | |  | ||||||
| | `00482670`             | "EMIOnHit"                   | `callbacks.EMIOnHit`                   | `00482690`       | |  | ||||||
| | `004bb1d0`             | "BertoActionPress"           | `callbacks.MessengerActionPress`       | `004bdff0`       | |  | ||||||
| | `00483dd0`             | "InfernoOnEnd"               | `callbacks.InfernoOnEnd`               | `00483df0`       | |  | ||||||
| | `0048e540`             | "TeslaFire"                  | `callbacks.TeslaFire`                  | `0048e560`       | |  | ||||||
| | `004bb010`             | "BertoAttack"                | `callbacks.BertoAttack`                | `004bb030`       | |  | ||||||
| | `0048c8a0`             | "SwarmClientFire"            | `callbacks.SwarmClientFire`            | `0048c8c0`       | |  | ||||||
| | `004818a0`             | "DevastatorFire"             | `callbacks.DevastatorFire`             | `004818c0`       | |  | ||||||
| | `0047e620`             | "ATPCFX"                     | `callbacks.ATPCFX`                     | `0047e640`       | |  | ||||||
| | `004bf230`             | "FXDesktopFallEnd"           | `callbacks.FXDesktopFallEnd`           | `004bf250`       | |  | ||||||
| | `004d38a0`             | "CloseDoorSlot"              | `callbacks.CloseDoorSlot`              | `004d38c0`       | |  | ||||||
| | `004d38e0`             | "FrontDoorSlot"              | `callbacks.FrontDoorSlot`              | `004d3900`       | |  | ||||||
| | `0051f250`             | "ScorerMetroUsrHere"         | `FUN.0051f270`                         | `0051f270`       | |  | ||||||
| | `004c2150`             | "GearShootLoad"              | `callbacks.GearShootLoad`              | `004c2170`       | |  | ||||||
| | `00580bc0`             | "AnmSoundEvent"              | `callbacks.AnmSoundEvent`              | `00580be0`       | |  | ||||||
| | `004b9650`             | "BankDirectorActionPress"    | `callbacks.BankDirectorActionPress`    | `004b9670`       | |  | ||||||
| | `0047cf00`             | "ATPCOnEnd"                  | `callbacks.ATPCOnEnd`                  | `0047cf20`       | |  | ||||||
| | `00580df0`             | "AnmSound2DEvent"            | `callbacks.AnmSound2DEvent`            | `00580e10`       | |  | ||||||
| | `004c5220`             | "FXMercenaryDrink"           | `callbacks.FXMercenaryDrink`           | `004c5240`       | |  | ||||||
| | `004c1640`             | "GearHit"                    | `callbacks.GearHit`                    | `004c1660`       | |  | ||||||
| | `004c9f30`             | "FXPoliceGearConversion"     | `callbacks.FXPoliceGearConversion`     | `004c9f50`       | |  | ||||||
| | `004ca230`             | "PoliceBossAction"           | `callbacks.PoliceBossAction`           | `004ca250`       | |  | ||||||
| | `004850e0`             | "InfernoCreate"              | `callbacks.InfernoCreate`              | `00485100`       | |  | ||||||
| | `0047fb50`             | "DevastatorRelease"          | `callbacks.DevastatorRelease`          | `0047fb70`       | |  | ||||||
| | `004c0500`             | "DtritusEndEvent"            | `callbacks.DtritusEndEvent`            | `004c0520`       | |  | ||||||
| | `004922b0`             | "TakeEner"                   | `callbacks.TakeEner`                   | `004922d0`       | |  | ||||||
| | `004249e0`             | "FXCharacterBlaBliBla"       | `callbacks.FXCharacterBlaBliBla`       | `00424a00`       | |  | ||||||
| | `004c80e0`             | "NurseBackHitControl"        | `callbacks.NurseBackHitControl`        | `004c8100`       | |  | ||||||
| | `004c4270`             | "FXMaintenanceWork"          | `callbacks.FXMaintenanceWork`          | `004c4290`       | |  | ||||||
| | `004898b0`             | "SonicFire"                  | `callbacks.SonicFire`                  | `004898d0`       | |  | ||||||
| | `004c8fa0`             | "PoliceRestoreFreeTurn"      | `callbacks.PoliceRestoreFreeTurn`      | `004c8fc0`       | |  | ||||||
| | `0048cf90`             | "SwarmFX"                    | `callbacks.SwarmFX`                    | `0048cfb0`       | |  | ||||||
| | `004b9380`             | "BankerAttack"               | `callbacks.BankerAttack`               | `004b93a0`       | |  | ||||||
| | `004ba920`             | "BankMasterActionEnd"        | `callbacks.BankMasterActionEnd`        | `004ba940`       | |  | ||||||
| | `00487cf0`             | "LaserFire"                  | `callbacks.LaserFire`                  | `00487d10`       | |  | ||||||
| | `004cb570`             | "FXRustyHit"                 | `FUN.004cb590`                         | `004cb590`       | |  | ||||||
| | `0047d0f0`             | "ATPCOnStart"                | `callbacks.ATPCOnStart`                | `0047d110`       | |  | ||||||
| | `00488010`             | "LaserFX"                    | `callbacks.LaserFX`                    | `00488030`       | |  | ||||||
| | `00581080`             | "StreamingVoice"             | `callbacks.StreamingVoice`             | `005810a0`       | |  | ||||||
| | `004828c0`             | "EMIFire"                    | `callbacks.EMIFire`                    | `004828e0`       | |  | ||||||
| | `0048c900`             | "SwarmFire"                  | `callbacks.SwarmFire`                  | `0048c920`       | |  | ||||||
| | `00489780`             | "SonicXplde"                 | `callbacks.SonicXplde`                 | `004897a0`       | |  | ||||||
| | `004cc7f0`             | "SentinelActionPress"        | `callbacks.SentinelActionPress`        | `004cc810`       | |  | ||||||
| | `004be470`             | "FXBishopSellLife"           | `callbacks.FXBishopSellLife`           | `004be490`       | |  | ||||||
| | `004b9b20`             | "BankDirectorEndActionInit"  | `callbacks.BankDirectorEndActionInit`  | `004b9b40`       | |  | ||||||
| | `004cc250`             | "FXSebastianFlash"           | `callbacks.FXSebastianFlash`           | `004cc270`       | |  | ||||||
| | `004847c0`             | "InfernoXplosionCall"        | `callbacks.InfernoXplosionCall`        | `004847e0`       | |  | ||||||
| | `00485870`             | "InfernoClientFire"          | `callbacks.InfernoClientFire`          | `00485890`       | |  | ||||||
| | `0047e0e0`             | "ATPCFire"                   | `callbacks.ATPCFire`                   | `0047e100`       | |  | ||||||
| | `0048da40`             | "TeslaPress"                 | `callbacks.TeslaPress`                 | `0048da60`       | |  | ||||||
| | `004c9b70`             | "FXPoliceSteal"              | `callbacks.FXPoliceSteal`              | `004c9b90`       | |  | ||||||
| | `004c4720`             | "MayorEndAction"             | `callbacks.MayorEndAction`             | `004c4740`       | |  | ||||||
| | `004c1b70`             | "GearShoot"                  | `callbacks.GearShoot`                  | `004c1b90`       | |  | ||||||
| | `00480120`             | "DevastatorCreate"           | `callbacks.DevastatorCreate`           | `00480140`       | |  | ||||||
| | `004dee00`             | "FXMeetingPointCountDown"    | `callbacks.FXMeetingPointCountDown`    | `004dee20`       | |  | ||||||
| | `004c63b0`             | "MessengerActionPress"       | `callbacks.MessengerActionPress`       | `004bdff0`       | |  | ||||||
| | `004c4d50`             | "ListeningMayor"             | `callbacks.ListeningMayor`             | `004c4d70`       | |  | ||||||
| | `005875f0`             | "Police2GearEnd"             | `callbacks.Police2GearEnd`             | `00587610`       | |  | ||||||
| | `004bb200`             | "BertoHitEvent"              | `callbacks.BertoHitEvent`              | `004bb220`       | |  | ||||||
| | `004858d0`             | "InfernoFire"                | `callbacks.InfernoFire`                | `004858f0`       | |  | ||||||
| | `004bc130`             | "BettyActionPress"           | `callbacks.BettyActionPress`           | `004bc150`       | |  | ||||||
| | `004c67e0`             | "NurseActionRelease"         | `callbacks.NurseActionRelease`         | `004c6800`       | |  | ||||||
| | `0047d330`             | "ATPCXplosionCall"           | `callbacks.ATPCXplosionCall`           | `0047d350`       | |  | ||||||
| | `00486fd0`             | "LaserCreate"                | `callbacks.LaserCreate`                | `00486ff0`       | |  | ||||||
| | `00484710`             | "InfernoHit"                 | `callbacks.InfernoHit`                 | `00484730`       | |  | ||||||
| | `004bdfd0`             | "BishopActionPress"          | `callbacks.MessengerActionPress`       | `004bdff0`       | |  | ||||||
| | `004e5040`             | "FXBossShieldSet"            | `callbacks.FXBossShieldSet`            | `004e5060`       | |  | ||||||
| | `00489200`             | "HookDeActivate"             | `callbacks.HookDeActivate`             | `00489220`       | |  | ||||||
| | `0051f420`             | "ScorerSpriteGlow"           | `callbacks.ScorerSpriteGlow`           | `0051f440`       | |  | ||||||
| | `004d3960`             | "Usr0AutoDoorSlot"           | `callbacks.Usr0AutoDoorSlot`           | `004d3980`       | |  | ||||||
| | `005802c0`             | "AnmStopChan"                | `callbacks.AnmStopChan`                | `005802e0`       | |  | ||||||
| | `004bcec0`             | "FXBettyAttack"              | `callbacks.FXBettyAttack`              | `004bcee0`       | |  | ||||||
| | `0051f4f0`             | "ScorerSpriteFlash"          | `callbacks.ScorerSpriteFlash`          | `0051f510`       | |  | ||||||
| | `0047fd60`             | "DevastatorXplosionCall"     | `callbacks.DevastatorXplosionCall`     | `0047fd80`       | |  | ||||||
| | `004886b0`             | "HookFire"                   | `callbacks.HookFire`                   | `004886d0`       | |  | ||||||
| | `004be170`             | "BishopEndActionEnd"         | `callbacks.BishopEndActionEnd`         | `004be190`       | |  | ||||||
| | `004e5270`             | "FXBossOnDamage"             | `callbacks.FXBossOnDamage`             | `004e5290`       | |  | ||||||
| | `00484e40`             | "InfernoOnObjSlot"           | `callbacks.InfernoOnObjSlot`           | `00484e60`       | |  | ||||||
| | `00405790`             | "PrintMemBlockData"          | `thunk.FUN.00405760`                   | `004057b0`       | |  | ||||||
| | `004bfd40`             | "DtritusActionPress"         | `callbacks.DtritusActionPress`         | `004bfd60`       | |  | ||||||
| | `0057fb80`             | "AnmChangeRandomAnm"         | `callbacks.AnmChangeRandomAnm`         | `0057fba0`       | |  | ||||||
| | `00486bb0`             | "LaserPress"                 | `callbacks.LaserPress`                 | `00486bd0`       | |  | ||||||
| | `004839f0`             | "InfernoPress"               | `callbacks.InfernoPress`               | `00483a10`       | |  | ||||||
| | `004c3a00`             | "GearEndActionEnd"           | `callbacks.GearEndActionEnd`           | `004c3a20`       | |  | ||||||
| | `00491810`             | "VulcanFX"                   | `callbacks.VulcanFX`                   | `00491830`       | |  | ||||||
| | `004c33f0`             | "GearActionPress"            | `callbacks.GearActionPress`            | `004c3410`       | |  | ||||||
| | `004b98d0`             | "BankDirectorActionRelease"  | `callbacks.BankDirectorActionRelease`  | `004b98f0`       | |  | ||||||
| | `005811a0`             | "StopStreamingVoice"         | `callbacks.StopStreamingVoice`         | `005811c0`       | |  | ||||||
| | `004cca10`             | "SentinelActionRelease"      | `callbacks.SentinelActionRelease`      | `004cca30`       | |  | ||||||
| | `004ba200`             | "BankMasterHitEvent"         | `callbacks.BankMasterHitEvent`         | `004ba220`       | |  | ||||||
| | `004bebd0`             | "DesktopActionPress"         | `callbacks.DesktopActionPress`         | `004bebf0`       | |  | ||||||
| | `0048b750`             | "SwarmHit"                   | `callbacks.SwarmHit`                   | `0048b770`       | |  | ||||||
| | `00475840`             | "MapElementsActivate"        | `callbacks.MapElementsActivate`        | `00475860`       | |  | ||||||
| | `00491ed0`             | "TakePart"                   | `callbacks.TakePart`                   | `00491ef0`       | |  | ||||||
| | `0047d7a0`             | "ATPCCreate"                 | `callbacks.ATPCCreate`                 | `0047d7c0`       | |  | ||||||
| | `004bb8d0`             | "BertoActionInit"            | `callbacks.BertoActionInit`            | `004bb8f0`       | |  | ||||||
| | `004bca70`             | "BettyEndEvent"              | `callbacks.BettyEndEvent`              | `004bca90`       | |  | ||||||
| | `004c0760`             | "DtritusRestartEvent"        | `callbacks.DtritusRestartEvent`        | `004c0780`       | |  | ||||||
| | `0048f4e0`             | "VulcanPress"                | `callbacks.VulcanPress`                | `0048f500`       | |  | ||||||
| | `00492690`             | "TakeMiss"                   | `callbacks.TakeMiss`                   | `004926b0`       | |  | ||||||
| | `0051f850`             | "ScorerSpriteFadeOutFinal"   | `callbacks.ScorerSpriteFadeOutFinal`   | `0051f870`       | |  | ||||||
| | `004c5d70`             | "MessengerTakeDataPack"      | `callbacks.MessengerTakeDataPack`      | `004c5880`       | |  | ||||||
| | `0057fe80`             | "AnmSetDefaultLoop"          | `callbacks.AnmSetDefaultLoop`          | `0057fea0`       | |  | ||||||
| | `004cc530`             | "SentinelPoint"              | `callbacks.SentinelPoint`              | `004cc550`       | |  | ||||||
| | `004c7150`             | "NurseDoHammerHit"           | `callbacks.NurseDoHammerHit`           | `004c7170`       | |  | ||||||
| | `004855f0`             | "InfernoSetUp"               | `callbacks.InfernoSetUp`               | `00485610`       | |  | ||||||
| | `004be790`             | "FXCrazyGamblerSetHeadModel" | `callbacks.FXCrazyGamblerSetHeadModel` | `004be7b0`       | |  | ||||||
| | `00427340`             | "FXCharacterTeleport"        | `callbacks.FXCharacterTeleport`        | `00427360`       | |  | ||||||
| | `0047ede0`             | "CloudFire"                  | `callbacks.CloudFire`                  | `0047ee00`       | |  | ||||||
| | `0048def0`             | "TeslaCreate"                | `callbacks.TeslaCreate`                | `0048df10`       | |  | ||||||
| | `00424470`             | "FXCharacterDazed"           | `callbacks.FXCharacterDazed`           | `00424490`       | |  | ||||||
| | `0048c0b0`             | "SwarmCreate"                | `callbacks.SwarmCreate`                | `0048c0d0`       | |  | ||||||
| | `004bab10`             | "FXBankMasterAttack"         | `callbacks.FXBankMasterAttack`         | `004bab30`       | |  | ||||||
| | `004bcc80`             | "BettyCameraSet"             | `callbacks.BettyCameraSet`             | `004bcca0`       | |  | ||||||
| | `004d6000`             | "FXPoliceAlarmEnd"           | `callbacks.FXPoliceAlarmEnd`           | `004d6020`       | |  | ||||||
| | `00484bd0`             | "InfernoServerHit"           | `callbacks.InfernoServerHit`           | `00484bf0`       | |  | ||||||
| | `004c63e0`             | "MessengerEndActionEnd"      | `callbacks.MessengerEndActionEnd`      | `004c6400`       | |  | ||||||
| | `0048c620`             | "SwarmSetUp"                 | `callbacks.SwarmSetUp`                 | `0048c640`       | |  | ||||||
| | `0058ebb0`             | "IncrementHits"              | `callbacks.IncrementHits`              | `0058ebd0`       | |  | ||||||
| | `0047f8f0`             | "DevastatorPress"            | `callbacks.DevastatorPress`            | `0047f910`       | |  | ||||||
| | `0047eb70`             | "CloudOnHit"                 | `callbacks.CloudOnHit`                 | `0047eb90`       | |  | ||||||
| | `004c09f0`             | "FXDTritusAttack"            | `callbacks.FXDTritusAttack`            | `004c0a10`       | |  | ||||||
| | `0048ae00`             | "SwarmOnEnd"                 | `callbacks.SwarmOnEnd`                 | `0048ae20`       | |  | ||||||
| | `004d5f70`             | "FXPoliceAlarmInit"          | `callbacks.FXPoliceAlarmInit`          | `004d5f90`       | |  | ||||||
| | `0048b7f0`             | "SwarmServerHit"             | `callbacks.SwarmServerHit`             | `0048b810`       | |  | ||||||
| | `00580700`             | "AnmSetDisplAng"             | `callbacks.AnmSetDisplAng`             | `00580720`       | |  | ||||||
| | `004bc2d0`             | "BettyHitEvent"              | `callbacks.BettyHitEvent`              | `004bc2f0`       | |  | ||||||
| | `004cad70`             | "FXPoliceBossBrup"           | `callbacks.FXPoliceBossBrup`           | `004cad90`       | |  | ||||||
| | `004c8cb0`             | "PoliceActionPress"          | `callbacks.PoliceActionPress`          | `004c8cd0`       | |  | ||||||
| | `004bc810`             | "BettyRestoreTime"           | `callbacks.BettyRestoreTime`           | `004bc830`       | |  | ||||||
| | `004ca430`             | "PoliceBossBrup"             | `callbacks.PoliceBossBrup`             | `004ca450`       | |  | ||||||
| | `005804e0`             | "AnmCommand"                 | `callbacks.AnmCommand`                 | `00580500`       | |  | ||||||
| | `0048ea20`             | "TeslaFX"                    | `callbacks.TeslaFX`                    | `0048ea40`       | |  | ||||||
| | `004ba6f0`             | "BankMasterActionInit"       | `callbacks.BankMasterActionInit`       | `004ba710`       | |  | ||||||
| | `004d3ba0`             | "ChangeDoorState"            | `callbacks.ChangeDoorState`            | `004d3bc0`       | |  | ||||||
| | `004c91c0`             | "PoliceEndActionMoney"       | `callbacks.PoliceEndActionMoney`       | `004c91e0`       | |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| ## Remaster |  | ||||||
| 
 |  | ||||||
| | Callback setup address | Callback name                | Callback funcion | Callback address | |  | ||||||
| |------------------------|------------------------------|------------------|------------------| |  | ||||||
| | `004419e0`             | "BertoActionEnd"             | `FUN.004c20d0`   | `004c20d0`       | |  | ||||||
| | `004424a0`             | "FXCrazyGamblerSetHeadModel" | `FUN.004c47a0`   | `004c47a0`       | |  | ||||||
| | `00443300`             | "GearShootLoad"              | `FUN.004c7bb0`   | `004c7bb0`       | |  | ||||||
| | `00443280`             | "GearEndActionEnd"           | `FUN.004c6ee0`   | `004c6ee0`       | |  | ||||||
| | `0044aaf0`             | "InfernoOnStart"             | `FUN.005224e0`   | `005224e0`       | |  | ||||||
| | `00444900`             | "SentinelActionPress"        | `FUN.004cfa30`   | `004cfa30`       | |  | ||||||
| | `0044c5d0`             | "SwarmServerHit"             | `FUN.00527430`   | `00527430`       | |  | ||||||
| | `004416e0`             | "BankMasterAttack"           | `FUN.004c19b0`   | `004c19b0`       | |  | ||||||
| | `00441ec0`             | "FXBettyAttack"              | `FUN.004c35a0`   | `004c35a0`       | |  | ||||||
| | `004441e0`             | "FXPoliceSteal"              | `FUN.004ccd00`   | `004ccd00`       | |  | ||||||
| | `00454040`             | "ScorerSpriteFadeIn"         | `FUN.00583da0`   | `00583da0`       | |  | ||||||
| | `0044aa70`             | "InfernoFire"                | `FUN.005217b0`   | `005217b0`       | |  | ||||||
| | `00453e40`             | "EditText"                   | `FUN.00582be0`   | `00582be0`       | |  | ||||||
| | `00444de0`             | "FXPoliceAlarmInit"          | `FUN.004d8050`   | `004d8050`       | |  | ||||||
| | `00444920`             | "SentinelActionRelease"      | `FUN.004cfb30`   | `004cfb30`       | |  | ||||||
| | `00450b10`             | "IncrementHits"              | `FUN.00555300`   | `00555300`       | |  | ||||||
| | `0044cbc0`             | "TeslaFire"                  | `FUN.005285e0`   | `005285e0`       | |  | ||||||
| | `00444660`             | "FXPoliceBossBrup"           | `FUN.004cdcd0`   | `004cdcd0`       | |  | ||||||
| | `0043e8c0`             | "AnmSetAction"               | `FUN.00464f70`   | `00464f70`       | |  | ||||||
| | `00449730`             | "DevastatorCreate"           | `FUN.0051e250`   | `0051e250`       | |  | ||||||
| | `00454080`             | "ScorerSpriteFadeOutFinal"   | `FUN.00583f80`   | `00583f80`       | |  | ||||||
| | `004428e0`             | "FXDTritusAttack"            | `FUN.004c6800`   | `004c6800`       | |  | ||||||
| | `00444940`             | "SentinelPoint"              | `FUN.004cfc90`   | `004cfc90`       | |  | ||||||
| | `0044c530`             | "SwarmHit"                   | `FUN.00526aa0`   | `00526aa0`       | |  | ||||||
| | `00441e60`             | "BettyEndEvent"              | `FUN.004c3030`   | `004c3030`       | |  | ||||||
| | `004428c0`             | "DtritusRestartEvent"        | `FUN.004c66a0`   | `004c66a0`       | |  | ||||||
| | `00445360`             | "ChangeDoorState"            | `FUN.004f03c0`   | `004f03c0`       | |  | ||||||
| | `00443d20`             | "MessengerEndActionEnd"      | `FUN.004ca940`   | `004ca940`       | |  | ||||||
| | `004432e0`             | "GearShoot"                  | `FUN.004c74a0`   | `004c74a0`       | |  | ||||||
| | `0044aad0`             | "InfernoOnObjSlot"           | `FUN.005223d0`   | `005223d0`       | |  | ||||||
| | `0044c4f0`             | "SwarmFX"                    | `FUN.005262f0`   | `005262f0`       | |  | ||||||
| | `004438c0`             | "FXMercenaryDrink"           | `FUN.004c9fe0`   | `004c9fe0`       | |  | ||||||
| | `00448ca0`             | "ATPCFire"                   | `FUN.0051d140`   | `0051d140`       | |  | ||||||
| | `004435a0`             | "FXMaintenanceWork"          | `FUN.004c95b0`   | `004c95b0`       | |  | ||||||
| | `00441ea0`             | "BettyRestoreTime"           | `FUN.004c3440`   | `004c3440`       | |  | ||||||
| | `00442460`             | "BishopEndActionEnd"         | `FUN.004c4440`   | `004c4440`       | |  | ||||||
| | `0044b430`             | "LaserRelease"               | `FUN.005254d0`   | `005254d0`       | |  | ||||||
| | `0044aa50`             | "InfernoFX"                  | `FUN.005212f0`   | `005212f0`       | |  | ||||||
| | `00441e80`             | "BettyHitEvent"              | `FUN.004c31f0`   | `004c31f0`       | |  | ||||||
| | `00451340`             | "EndOfLaugh"                 | `FUN.00557e00`   | `00557e00`       | |  | ||||||
| | `00445320`             | "AutoDoorUsrSlot"            | `FUN.004f0380`   | `004f0380`       | |  | ||||||
| | `0044ab70`             | "InfernoSetUp"               | `FUN.005228f0`   | `005228f0`       | |  | ||||||
| | `00445c00`             | "TakeEner"                   | `FUN.004fe9c0`   | `004fe9c0`       | |  | ||||||
| | `0043e8e0`             | "AnmSetDefaultLoop"          | `FUN.00465060`   | `00465060`       | |  | ||||||
| | `00449770`             | "DevastatorFire"             | `FUN.0051ff30`   | `0051ff30`       | |  | ||||||
| | `0044ab30`             | "InfernoRelease"             | `FUN.005226b0`   | `005226b0`       | |  | ||||||
| | `004413a0`             | "BankDirectorActionRelease"  | `FUN.004c0ed0`   | `004c0ed0`       | |  | ||||||
| | `0044b790`             | "SonicXplde"                 | `FUN.00525a40`   | `00525a40`       | |  | ||||||
| | `004453a0`             | "FrontDoorSlot"              | `FUN.004f0560`   | `004f0560`       | |  | ||||||
| | `00448d00`             | "ATPCXplosionCall"           | `FUN.0051d8d0`   | `0051d8d0`       | |  | ||||||
| | `0044cba0`             | "TeslaFX"                    | `FUN.005282f0`   | `005282f0`       | |  | ||||||
| | `00445380`             | "CloseDoorSlot"              | `FUN.004f0540`   | `004f0540`       | |  | ||||||
| | `0043e880`             | "AnmChangeRandomAnm"         | `FUN.00464c80`   | `00464c80`       | |  | ||||||
| | `00449750`             | "DevastatorFX"               | `FUN.0051fc50`   | `0051fc50`       | |  | ||||||
| | `0044b390`             | "HookPlugFX"                 | `FUN.00523d30`   | `00523d30`       | |  | ||||||
| | `0044d3b0`             | "VulcanCreate"               | `FUN.00528fb0`   | `00528fb0`       | |  | ||||||
| | `0044ab90`             | "InfernoXplosionCall"        | `FUN.00522d70`   | `00522d70`       | |  | ||||||
| | `004432c0`             | "GearInitAction"             | `FUN.004c7230`   | `004c7230`       | |  | ||||||
| | `00445c20`             | "TakeLife"                   | `FUN.004feb50`   | `004feb50`       | |  | ||||||
| | `004497d0`             | "DevastatorXplosionCall"     | `FUN.005205c0`   | `005205c0`       | |  | ||||||
| | `00454060`             | "ScorerSpriteFadeOut"        | `FUN.00583e90`   | `00583e90`       | |  | ||||||
| | `0044b350`             | "HookDeActivate"             | `FUN.00523350`   | `00523350`       | |  | ||||||
| | `0044c550`             | "SwarmOnEnd"                 | `FUN.005270d0`   | `005270d0`       | |  | ||||||
| | `0044d410`             | "VulcanPress"                | `FUN.0052b2e0`   | `0052b2e0`       | |  | ||||||
| | `0044b3d0`             | "LaserFX"                    | `FUN.00524d30`   | `00524d30`       | |  | ||||||
| | `0043e980`             | "AnmSoundLoopEvent"          | `FUN.00465910`   | `00465910`       | |  | ||||||
| | `0043e9e0`             | "WarningPolice"              | `FUN.00465c30`   | `00465c30`       | |  | ||||||
| | `00444680`             | "PoliceBossAction"           | `FUN.004ce5f0`   | `004ce5f0`       | |  | ||||||
| | `0044c4d0`             | "SwarmCreate"                | `FUN.005260c0`   | `005260c0`       | |  | ||||||
| | `004448e0`             | "FXSebastianFlash"           | `FUN.004cf900`   | `004cf900`       | |  | ||||||
| | `00443f60`             | "NurseActionRelease"         | `FUN.004cbf00`   | `004cbf00`       | |  | ||||||
| | `00444fc0`             | "FXMeetingPointCountDown"    | `FUN.004e2250`   | `004e2250`       | |  | ||||||
| | `0044d3d0`             | "VulcanFX"                   | `FUN.0052ac00`   | `0052ac00`       | |  | ||||||
| | `00444240`             | "PoliceEndActionMoney"       | `FUN.004cd690`   | `004cd690`       | |  | ||||||
| | `00456c00`             | "FXCharacterDazed"           | `FUN.005b4250`   | `005b4250`       | |  | ||||||
| | `00442880`             | "DtritusEndEvent"            | `FUN.004c61e0`   | `004c61e0`       | |  | ||||||
| | `00441680`             | "BankMasterActionEnd"        | `FUN.004c1660`   | `004c1660`       | |  | ||||||
| | `00443260`             | "GearEndAction"              | `FUN.004c6d50`   | `004c6d50`       | |  | ||||||
| | `00442660`             | "DesktopActionPress"         | `FUN.004c4a00`   | `004c4a00`       | |  | ||||||
| | `004432a0`             | "GearHit"                    | `FUN.004c7070`   | `004c7070`       | |  | ||||||
| | `0044b410`             | "LaserPress"                 | `FUN.005253e0`   | `005253e0`       | |  | ||||||
| | `0044fb50`             | "Police2GearEnd"             | `FUN.0054de60`   | `0054de60`       | |  | ||||||
| | `00445340`             | "BackDoorSlot"               | `FUN.004f03a0`   | `004f03a0`       | |  | ||||||
| | `0044b3f0`             | "LaserFire"                  | `FUN.00524fb0`   | `00524fb0`       | |  | ||||||
| | `0043e8a0`             | "AnmCommand"                 | `FUN.00464e80`   | `00464e80`       | |  | ||||||
| | `00443f40`             | "NurseActionPress"           | `FUN.004cbe10`   | `004cbe10`       | |  | ||||||
| | `004453c0`             | "Usr0AutoDoorSlot"           | `FUN.004f0790`   | `004f0790`       | |  | ||||||
| | `00441380`             | "BankDirectorActionPress"    | `FUN.004c0d70`   | `004c0d70`       | |  | ||||||
| | `004416c0`             | "BankMasterActionPress"      | `FUN.004c18d0`   | `004c18d0`       | |  | ||||||
| | `00443fc0`             | "NurseInitHammerLoop"        | `FUN.004cc9b0`   | `004cc9b0`       | |  | ||||||
| | `00443d40`             | "MessengerTakeDataPack"      | `FUN.004caad0`   | `004caad0`       | |  | ||||||
| | `00449df0`             | "EMIOnHit"                   | `FUN.00520cc0`   | `00520cc0`       | |  | ||||||
| | `0044aa10`             | "InfernoClientFire"          | `FUN.00520f40`   | `00520f40`       | |  | ||||||
| | `00444820`             | "FXRustyHit"                 | `FUN.004cea30`   | `004cea30`       | |  | ||||||
| | `0044aa30`             | "InfernoCreate"              | `FUN.005210d0`   | `005210d0`       | |  | ||||||
| | `00444dc0`             | "FXPoliceAlarmEnd"           | `FUN.004d7fe0`   | `004d7fe0`       | |  | ||||||
| | `0044ab50`             | "InfernoServerHit"           | `FUN.00522780`   | `00522780`       | |  | ||||||
| | `0044d430`             | "VulcanRelease"              | `FUN.0052b3a0`   | `0052b3a0`       | |  | ||||||
| | `0044b330`             | "HookActivate"               | `FUN.005232c0`   | `005232c0`       | |  | ||||||
| | `0044c570`             | "SwarmOnStart"               | `FUN.005271a0`   | `005271a0`       | |  | ||||||
| | `004540e0`             | "BoostScorer"                | `FUN.00584230`   | `00584230`       | |  | ||||||
| | `004540c0`             | "ScorerSpriteGlow"           | `FUN.00584190`   | `00584190`       | |  | ||||||
| | `00441a40`             | "BertoAttack"                | `FUN.004c23c0`   | `004c23c0`       | |  | ||||||
| | `0044cb80`             | "TeslaCreate"                | `FUN.00528040`   | `00528040`       | |  | ||||||
| | `00442420`             | "BishopActionPress"          | `FUN.004c3800`   | `004c3800`       | |  | ||||||
| | `00448c80`             | "ATPCFX"                     | `FUN.0051cea0`   | `0051cea0`       | |  | ||||||
| | `00444220`             | "PoliceActionPress"          | `FUN.004cd290`   | `004cd290`       | |  | ||||||
| | `0043e920`             | "AnmStopChan"                | `FUN.00465240`   | `00465240`       | |  | ||||||
| | `00444f80`             | "FXBossOnDamage"             | `FUN.004e2090`   | `004e2090`       | |  | ||||||
| | `00448fe0`             | "CloudOnHit"                 | `FUN.0051dff0`   | `0051dff0`       | |  | ||||||
| | `00455530`             | "PrintMemBlockData"          | `&LAB.005928d0`  | `005928d0`       | |  | ||||||
| | `00448cc0`             | "ATPCOnEnd"                  | `FUN.0051d6e0`   | `0051d6e0`       | |  | ||||||
| | `00442860`             | "DtritusActionPress"         | `FUN.004c5bd0`   | `004c5bd0`       | |  | ||||||
| | `0044ab10`             | "InfernoPress"               | `FUN.005225e0`   | `005225e0`       | |  | ||||||
| | `00442680`             | "FXDesktopFallEnd"           | `FUN.004c50d0`   | `004c50d0`       | |  | ||||||
| | `00444260`             | "PoliceRestoreFreeTurn"      | `FUN.004cdbd0`   | `004cdbd0`       | |  | ||||||
| | `0044aab0`             | "InfernoOnEnd"               | `FUN.00522300`   | `00522300`       | |  | ||||||
| | `0043e900`             | "AnmSetDisplAng"             | `FUN.00465150`   | `00465150`       | |  | ||||||
| | `0044b770`             | "SonicFire"                  | `FUN.005256e0`   | `005256e0`       | |  | ||||||
| | `004413e0`             | "BankerAttack"               | `FUN.004c1120`   | `004c1120`       | |  | ||||||
| | `00441e40`             | "BettyCameraSet"             | `FUN.004c2ab0`   | `004c2ab0`       | |  | ||||||
| | `004497b0`             | "DevastatorRelease"          | `FUN.00520470`   | `00520470`       | |  | ||||||
| | `004416a0`             | "BankMasterActionInit"       | `FUN.004c17d0`   | `004c17d0`       | |  | ||||||
| | `00441a80`             | "FXBertoAttack"              | `FUN.004c27e0`   | `004c27e0`       | |  | ||||||
| | `004452e0`             | "AcceleratorOnEnter"         | `FUN.004efdf0`   | `004efdf0`       | |  | ||||||
| | `0043e940`             | "AnmSound2DEvent"            | `FUN.004654f0`   | `004654f0`       | |  | ||||||
| | `00441700`             | "BankMasterHitEvent"         | `FUN.004c1e00`   | `004c1e00`       | |  | ||||||
| | `004446a0`             | "PoliceBossBrup"             | `FUN.004ce710`   | `004ce710`       | |  | ||||||
| | `00443fe0`             | "NurseTryHammerAgain"        | `FUN.004ccac0`   | `004ccac0`       | |  | ||||||
| | `00449790`             | "DevastatorPress"            | `FUN.005203b0`   | `005203b0`       | |  | ||||||
| | `0044c4b0`             | "SwarmClientFire"            | `FUN.00525f50`   | `00525f50`       | |  | ||||||
| | `00442480`             | "FXBishopSellLife"           | `FUN.004c45d0`   | `004c45d0`       | |  | ||||||
| | `00441a20`             | "BertoActionPress"           | `FUN.004c22e0`   | `004c22e0`       | |  | ||||||
| | `0043e9a0`             | "StopStreamingVoice"         | `FUN.00465ac0`   | `00465ac0`       | |  | ||||||
| | `0044cc00`             | "TeslaRelease"               | `FUN.00528ca0`   | `00528ca0`       | |  | ||||||
| | `00443240`             | "GearActionPress"            | `FUN.004c6b90`   | `004c6b90`       | |  | ||||||
| | `00443f80`             | "NurseBackHitControl"        | `FUN.004cc7b0`   | `004cc7b0`       | |  | ||||||
| | `00444200`             | "ListeningPolice"            | `FUN.004ccfc0`   | `004ccfc0`       | |  | ||||||
| | `00442440`             | "BishopAttack"               | `FUN.004c38e0`   | `004c38e0`       | |  | ||||||
| | `0044c5f0`             | "SwarmSetUp"                 | `FUN.00527920`   | `00527920`       | |  | ||||||
| | `0044c510`             | "SwarmFire"                  | `FUN.005267b0`   | `005267b0`       | |  | ||||||
| | `00443ce0`             | "MessengerActionPress"       | `FUN.004ca160`   | `004ca160`       | |  | ||||||
| | `0044aa90`             | "InfernoHit"                 | `FUN.00521ae0`   | `00521ae0`       | |  | ||||||
| | `004540a0`             | "ScorerSpriteFlash"          | `FUN.00584090`   | `00584090`       | |  | ||||||
| | `0044c5b0`             | "SwarmRelease"               | `FUN.00527360`   | `00527360`       | |  | ||||||
| | `0044d3f0`             | "VulcanFire"                 | `FUN.0052ae30`   | `0052ae30`       | |  | ||||||
| | `0044b370`             | "HookFire"                   | `FUN.00523420`   | `00523420`       | |  | ||||||
| | `00444fa0`             | "FXBossShieldSet"            | `FUN.004e2140`   | `004e2140`       | |  | ||||||
| | `00456be0`             | "FXCharacterBlaBliBla"       | `FUN.005b3910`   | `005b3910`       | |  | ||||||
| | `00441a00`             | "BertoActionInit"            | `FUN.004c21e0`   | `004c21e0`       | |  | ||||||
| | `00443d00`             | "MessengerAttack"            | `FUN.004ca240`   | `004ca240`       | |  | ||||||
| | `00454020`             | "ScorerMetroUsrHere"         | `FUN.00583c10`   | `00583c10`       | |  | ||||||
| | `004428a0`             | "DtritusHitEvent"            | `FUN.004c6440`   | `004c6440`       | |  | ||||||
| | `00448ce0`             | "ATPCOnStart"                | `FUN.0051d7b0`   | `0051d7b0`       | |  | ||||||
| | `00445c40`             | "TakeMiss"                   | `FUN.004fecb0`   | `004fecb0`       | |  | ||||||
| | `00456c20`             | "FXCharacterTeleport"        | `FUN.005b4810`   | `005b4810`       | |  | ||||||
| | `0043e9c0`             | "StreamingVoice"             | `FUN.00465b10`   | `00465b10`       | |  | ||||||
| | `00441e20`             | "BettyActionPress"           | `FUN.004c2910`   | `004c2910`       | |  | ||||||
| | `00443680`             | "ListeningMayor"             | `FUN.004c97c0`   | `004c97c0`       | |  | ||||||
| | `00443fa0`             | "NurseDoHammerHit"           | `FUN.004cc8a0`   | `004cc8a0`       | |  | ||||||
| | `00441720`             | "FXBankMasterAttack"         | `FUN.004c1fa0`   | `004c1fa0`       | |  | ||||||
| | `004441c0`             | "FXPoliceGearConversion"     | `FUN.004ccbd0`   | `004ccbd0`       | |  | ||||||
| | `00441a60`             | "BertoHitEvent"              | `FUN.004c2670`   | `004c2670`       | |  | ||||||
| | `0044c590`             | "SwarmPress"                 | `FUN.00527290`   | `00527290`       | |  | ||||||
| | `00449dd0`             | "EMIFire"                    | `FUN.005209a0`   | `005209a0`       | |  | ||||||
| | `00445300`             | "AutoDoorSlot"               | `FUN.004f0070`   | `004f0070`       | |  | ||||||
| | `00445c60`             | "TakePart"                   | `FUN.004fee40`   | `004fee40`       | |  | ||||||
| | `00457de0`             | "MapElementsActivate"        | `FUN.00604960`   | `00604960`       | |  | ||||||
| | `0044b3b0`             | "LaserCreate"                | `FUN.00523fc0`   | `00523fc0`       | |  | ||||||
| | `004436a0`             | "MayorEndAction"             | `FUN.004c9ae0`   | `004c9ae0`       | |  | ||||||
| | `00448fc0`             | "CloudFire"                  | `FUN.0051dcf0`   | `0051dcf0`       | |  | ||||||
| | `0043e960`             | "AnmSoundEvent"              | `FUN.00465720`   | `00465720`       | |  | ||||||
| | `0044cbe0`             | "TeslaPress"                 | `FUN.00528bd0`   | `00528bd0`       | |  | ||||||
| | `00448c60`             | "ATPCCreate"                 | `FUN.0051c3e0`   | `0051c3e0`       | |  | ||||||
| | `004413c0`             | "BankDirectorEndActionInit"  | `FUN.004c1000`   | `004c1000`       | |  | ||||||
|  | @ -8,7 +8,6 @@ | ||||||
|     - [Netplay](./Netplay.md) |     - [Netplay](./Netplay.md) | ||||||
|     - [Python API](./Python_API.md) |     - [Python API](./Python_API.md) | ||||||
|       - [Modules](./Python_Modules.md) |       - [Modules](./Python_Modules.md) | ||||||
|       - [Callbacks](./Callbacks.md) |  | ||||||
|     - [File Formats](./File_Formats.md) |     - [File Formats](./File_Formats.md) | ||||||
|       - [Chunked Formats](./Chunked.md) |       - [Chunked Formats](./Chunked.md) | ||||||
|       - [Packed](./Packed.md) |       - [Packed](./Packed.md) | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								scrapper_web/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								scrapper_web/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,24 +0,0 @@ | ||||||
| # Logs |  | ||||||
| logs |  | ||||||
| *.log |  | ||||||
| npm-debug.log* |  | ||||||
| yarn-debug.log* |  | ||||||
| yarn-error.log* |  | ||||||
| pnpm-debug.log* |  | ||||||
| lerna-debug.log* |  | ||||||
| 
 |  | ||||||
| node_modules |  | ||||||
| dist |  | ||||||
| dist-ssr |  | ||||||
| *.local |  | ||||||
| 
 |  | ||||||
| # Editor directories and files |  | ||||||
| .vscode/* |  | ||||||
| !.vscode/extensions.json |  | ||||||
| .idea |  | ||||||
| .DS_Store |  | ||||||
| *.suo |  | ||||||
| *.ntvs* |  | ||||||
| *.njsproj |  | ||||||
| *.sln |  | ||||||
| *.sw? |  | ||||||
							
								
								
									
										3
									
								
								scrapper_web/.vscode/extensions.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								scrapper_web/.vscode/extensions.json
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +0,0 @@ | ||||||
| { |  | ||||||
|   "recommendations": ["svelte.svelte-vscode"] |  | ||||||
| } |  | ||||||
|  | @ -1,47 +0,0 @@ | ||||||
| # Svelte + Vite |  | ||||||
| 
 |  | ||||||
| This template should help get you started developing with Svelte in Vite. |  | ||||||
| 
 |  | ||||||
| ## Recommended IDE Setup |  | ||||||
| 
 |  | ||||||
| [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). |  | ||||||
| 
 |  | ||||||
| ## Need an official Svelte framework? |  | ||||||
| 
 |  | ||||||
| Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. |  | ||||||
| 
 |  | ||||||
| ## Technical considerations |  | ||||||
| 
 |  | ||||||
| **Why use this over SvelteKit?** |  | ||||||
| 
 |  | ||||||
| - It brings its own routing solution which might not be preferable for some users. |  | ||||||
| - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. |  | ||||||
| 
 |  | ||||||
| This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. |  | ||||||
| 
 |  | ||||||
| Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. |  | ||||||
| 
 |  | ||||||
| **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** |  | ||||||
| 
 |  | ||||||
| Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. |  | ||||||
| 
 |  | ||||||
| **Why include `.vscode/extensions.json`?** |  | ||||||
| 
 |  | ||||||
| Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. |  | ||||||
| 
 |  | ||||||
| **Why enable `checkJs` in the JS template?** |  | ||||||
| 
 |  | ||||||
| It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration. |  | ||||||
| 
 |  | ||||||
| **Why is HMR not preserving my local component state?** |  | ||||||
| 
 |  | ||||||
| HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). |  | ||||||
| 
 |  | ||||||
| If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. |  | ||||||
| 
 |  | ||||||
| ```js |  | ||||||
| // store.js |  | ||||||
| // An extremely simple external store |  | ||||||
| import { writable } from 'svelte/store' |  | ||||||
| export default writable(0) |  | ||||||
| ``` |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en"> |  | ||||||
|   <head> |  | ||||||
|     <meta charset="UTF-8" /> |  | ||||||
|     <link rel="icon" type="image/svg+xml" href="/vite.svg" /> |  | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |  | ||||||
|     <title>Vite + Svelte</title> |  | ||||||
|   </head> |  | ||||||
|   <body> |  | ||||||
|     <div id="app"></div> |  | ||||||
|     <script type="module" src="/src/main.js"></script> |  | ||||||
|   </body> |  | ||||||
| </html> |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| { |  | ||||||
|   "compilerOptions": { |  | ||||||
|     "moduleResolution": "Node", |  | ||||||
|     "target": "ESNext", |  | ||||||
|     "module": "ESNext", |  | ||||||
|     /** |  | ||||||
|      * svelte-preprocess cannot figure out whether you have |  | ||||||
|      * a value or a type, so tell TypeScript to enforce using |  | ||||||
|      * `import type` instead of `import` for Types. |  | ||||||
|      */ |  | ||||||
|     "importsNotUsedAsValues": "error", |  | ||||||
|     "isolatedModules": true, |  | ||||||
|     "resolveJsonModule": true, |  | ||||||
|     /** |  | ||||||
|      * To have warnings / errors of the Svelte compiler at the |  | ||||||
|      * correct position, enable source maps by default. |  | ||||||
|      */ |  | ||||||
|     "sourceMap": true, |  | ||||||
|     "esModuleInterop": true, |  | ||||||
|     "skipLibCheck": true, |  | ||||||
|     "forceConsistentCasingInFileNames": true, |  | ||||||
|     /** |  | ||||||
|      * Typecheck JS in `.svelte` and `.js` files by default. |  | ||||||
|      * Disable this if you'd like to use dynamic types. |  | ||||||
|      */ |  | ||||||
|     "checkJs": false |  | ||||||
|   }, |  | ||||||
|   /** |  | ||||||
|    * Use global.d.ts instead of compilerOptions.types |  | ||||||
|    * to avoid limiting type declarations. |  | ||||||
|    */ |  | ||||||
|   "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] |  | ||||||
| } |  | ||||||
|  | @ -1,26 +0,0 @@ | ||||||
| { |  | ||||||
|   "name": "scrapper_web", |  | ||||||
|   "private": true, |  | ||||||
|   "version": "0.0.0", |  | ||||||
|   "type": "module", |  | ||||||
|   "scripts": { |  | ||||||
|     "dev": "vite", |  | ||||||
|     "build": "wasm-pack build ./scrapper -t web && vite build", |  | ||||||
|     "preview": "vite preview" |  | ||||||
|   }, |  | ||||||
|   "devDependencies": { |  | ||||||
|     "@sveltejs/vite-plugin-svelte": "^2.0.2", |  | ||||||
|     "@tailwindcss/forms": "^0.5.3", |  | ||||||
|     "autoprefixer": "^10.4.13", |  | ||||||
|     "cssnano": "^5.1.14", |  | ||||||
|     "cssnano-preset-advanced": "^5.3.9", |  | ||||||
|     "daisyui": "^2.50.0", |  | ||||||
|     "filedrop-svelte": "^0.1.2", |  | ||||||
|     "postcss": "^8.4.21", |  | ||||||
|     "svelte": "^3.55.1", |  | ||||||
|     "svelte-preprocess": "^5.0.1", |  | ||||||
|     "tailwindcss": "^3.2.4", |  | ||||||
|     "vite": "^4.1.0", |  | ||||||
|     "vite-plugin-wasm-pack": "^0.1.12" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										1777
									
								
								scrapper_web/pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1777
									
								
								scrapper_web/pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,11 +0,0 @@ | ||||||
| let cssnano_plugin = {}; |  | ||||||
| if (process.env.NODE_ENV === "production") { |  | ||||||
|   cssnano_plugin = { cssnano: { preset: "advanced" } }; |  | ||||||
| } |  | ||||||
| module.exports = { |  | ||||||
|   plugins: { |  | ||||||
|     tailwindcss: {}, |  | ||||||
|     autoprefixer: {}, |  | ||||||
|     ...cssnano_plugin, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> |  | ||||||
| Before Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										14
									
								
								scrapper_web/scrapper/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								scrapper_web/scrapper/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,14 +0,0 @@ | ||||||
| # Generated by Cargo |  | ||||||
| # will have compiled files and executables |  | ||||||
| debug/ |  | ||||||
| target/ |  | ||||||
| 
 |  | ||||||
| # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries |  | ||||||
| # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html |  | ||||||
| Cargo.lock |  | ||||||
| 
 |  | ||||||
| # These are backup files generated by rustfmt |  | ||||||
| **/*.rs.bk |  | ||||||
| 
 |  | ||||||
| # MSVC Windows builds of rustc generate these, which store debugging information |  | ||||||
| *.pdb |  | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| [package] |  | ||||||
| name = "scrapper" |  | ||||||
| version = "0.1.0" |  | ||||||
| authors = [] |  | ||||||
| edition = "2021" |  | ||||||
| 
 |  | ||||||
| [lib] |  | ||||||
| crate-type = ["cdylib", "rlib"] |  | ||||||
| 
 |  | ||||||
| [profile.release] |  | ||||||
| lto = true |  | ||||||
| 
 |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |  | ||||||
| 
 |  | ||||||
| [dependencies] |  | ||||||
| aes = "0.8.2" |  | ||||||
| anyhow = "1.0.69" |  | ||||||
| binrw = "0.11.1" |  | ||||||
| cbc = "0.1.2" |  | ||||||
| console_error_panic_hook = "0.1.7" |  | ||||||
| derivative = "2.2.0" |  | ||||||
| js-sys = "0.3.61" |  | ||||||
| pelite = "0.10.0" |  | ||||||
| serde = { version = "1.0.152", features = ["derive"] } |  | ||||||
| serde-wasm-bindgen = "0.4.5" |  | ||||||
| wasm-bindgen = "0.2.83" |  | ||||||
| wasm-bindgen-file-reader = "1.0.0" |  | ||||||
| web-sys = { version = "0.3.61", features = ["File", "BlobPropertyBag", "Blob", "Url"] } |  | ||||||
| 
 |  | ||||||
| [package.metadata.wasm-pack.profile.release] |  | ||||||
| wasm-opt = ["-O4"] |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| # scrapper |  | ||||||
| 
 |  | ||||||
| ## Usage |  | ||||||
| 
 |  | ||||||
| [rsw-rs doc](https://github.com/lencx/rsw-rs) |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| # install rsw |  | ||||||
| cargo install rsw |  | ||||||
| 
 |  | ||||||
| # --- help --- |  | ||||||
| # rsw help |  | ||||||
| rsw -h |  | ||||||
| # new help |  | ||||||
| rsw new -h |  | ||||||
| 
 |  | ||||||
| # --- usage --- |  | ||||||
| # dev |  | ||||||
| rsw watch |  | ||||||
| 
 |  | ||||||
| # production |  | ||||||
| rsw build |  | ||||||
| ``` |  | ||||||
|  | @ -1,155 +0,0 @@ | ||||||
| use binrw::{binread, BinReaderExt}; |  | ||||||
| use serde::Serialize; |  | ||||||
| use std::collections::BTreeMap; |  | ||||||
| use std::io::{Read, Seek, SeekFrom}; |  | ||||||
| use wasm_bindgen::prelude::*; |  | ||||||
| use wasm_bindgen_file_reader::WebSysFile; |  | ||||||
| use web_sys::{Blob, File}; |  | ||||||
| 
 |  | ||||||
| type JsResult<T> = Result<T,JsValue>; |  | ||||||
| 
 |  | ||||||
| #[binread] |  | ||||||
| #[derive(Serialize, Debug)] |  | ||||||
| struct ScrapFile { |  | ||||||
|     #[br(temp)] |  | ||||||
|     name_len: u32, |  | ||||||
|     #[br(count = name_len)] |  | ||||||
|     #[br(map = |s: Vec<u8>| String::from_utf8_lossy(&s).to_string())] |  | ||||||
|     path: String, |  | ||||||
|     size: u32, |  | ||||||
|     offset: u32, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[binread] |  | ||||||
| #[br(magic = b"BFPK", little)] |  | ||||||
| #[derive(Serialize, Debug)] |  | ||||||
| struct PackedHeader { |  | ||||||
|     version: u32, |  | ||||||
|     #[br(temp)] |  | ||||||
|     num_files: u32, |  | ||||||
|     #[br(count= num_files)] |  | ||||||
|     files: Vec<ScrapFile>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Serialize, Debug)] |  | ||||||
| #[serde(tag = "type", rename_all = "snake_case")] |  | ||||||
| enum DirectoryTree { |  | ||||||
|     File { |  | ||||||
|         size: u32, |  | ||||||
|         offset: u32, |  | ||||||
|         file_index: u8, |  | ||||||
|     }, |  | ||||||
|     Directory { |  | ||||||
|         entries: BTreeMap<String, DirectoryTree>, |  | ||||||
|     }, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[wasm_bindgen(inspectable)] |  | ||||||
| pub struct MultiPack { |  | ||||||
|     files: Vec<(String,WebSysFile)>, |  | ||||||
|     tree: DirectoryTree, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn blob_url(buffer: &[u8]) -> JsResult<String> { |  | ||||||
|     let uint8arr = |  | ||||||
|         js_sys::Uint8Array::new(&unsafe { js_sys::Uint8Array::view(buffer) }.into()); |  | ||||||
|     let array = js_sys::Array::new(); |  | ||||||
|     array.push(&uint8arr.buffer()); |  | ||||||
|     let blob = Blob::new_with_u8_array_sequence_and_options( |  | ||||||
|         &array, |  | ||||||
|         web_sys::BlobPropertyBag::new().type_("application/octet-stream"), |  | ||||||
|     ) |  | ||||||
|     .unwrap(); |  | ||||||
|     web_sys::Url::create_object_url_with_blob(&blob) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[wasm_bindgen] |  | ||||||
| impl MultiPack { |  | ||||||
|     #[wasm_bindgen(constructor)] |  | ||||||
|     pub fn parse(files: Vec<File>) -> Self { |  | ||||||
|         let mut tree = DirectoryTree::default(); |  | ||||||
|         let mut web_files = vec![]; |  | ||||||
|         for (file_index, file) in files.into_iter().enumerate() { |  | ||||||
|             let file_name = file.name(); |  | ||||||
|             let mut fh = WebSysFile::new(file); |  | ||||||
|             let header = fh.read_le::<PackedHeader>().unwrap(); |  | ||||||
|             tree.merge(&header.files, file_index.try_into().unwrap()); |  | ||||||
|             web_files.push((file_name,fh)); |  | ||||||
|         } |  | ||||||
|         Self { |  | ||||||
|             tree, |  | ||||||
|             files: web_files, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[wasm_bindgen] |  | ||||||
|     pub fn tree(&self) -> JsValue { |  | ||||||
|         serde_wasm_bindgen::to_value(&self.tree).unwrap() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[wasm_bindgen] |  | ||||||
|     pub fn download( |  | ||||||
|         &mut self, |  | ||||||
|         file_index: u8, |  | ||||||
|         offset: u32, |  | ||||||
|         size: u32, |  | ||||||
|     ) -> Result<JsValue, JsValue> { |  | ||||||
|         let Some((_,file)) = self.files.get_mut(file_index as usize) else { |  | ||||||
|             return Err("File not found".into()); |  | ||||||
|         }; |  | ||||||
|         let mut buffer = vec![0u8; size as usize]; |  | ||||||
|         file.seek(SeekFrom::Start(offset as u64)) |  | ||||||
|             .map_err(|e| format!("Failed to seek file: {e}"))?; |  | ||||||
|         file.read(&mut buffer) |  | ||||||
|             .map_err(|e| format!("Failed to read from file: {e}"))?; |  | ||||||
|         Ok(blob_url(&buffer)?.into()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Default for DirectoryTree { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::Directory { |  | ||||||
|             entries: Default::default(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl DirectoryTree { |  | ||||||
|     fn add_child(&mut self, name: &str, node: Self) -> &mut Self { |  | ||||||
|         match self { |  | ||||||
|             Self::File { .. } => panic!("Can't add child to file!"), |  | ||||||
|             Self::Directory { |  | ||||||
|                 entries |  | ||||||
|             } => entries.entry(name.to_owned()).or_insert(node), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn merge(&mut self, files: &[ScrapFile], file_index: u8) { |  | ||||||
|         for file in files { |  | ||||||
|             let mut folder = &mut *self; |  | ||||||
|             let path: Vec<_> = file.path.split('/').collect(); |  | ||||||
|             if let Some((filename, path)) = path.as_slice().split_last() { |  | ||||||
|                 for part in path { |  | ||||||
|                     let DirectoryTree::Directory { entries } = folder else { |  | ||||||
|                             unreachable!(); |  | ||||||
|                         }; |  | ||||||
|                     folder = entries.entry(part.to_string()).or_default(); |  | ||||||
|                 } |  | ||||||
|                 folder.add_child( |  | ||||||
|                     filename, |  | ||||||
|                     DirectoryTree::File { |  | ||||||
|                         size: file.size, |  | ||||||
|                         offset: file.offset, |  | ||||||
|                         file_index, |  | ||||||
|                     }, |  | ||||||
|                 ); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[wasm_bindgen(start)] |  | ||||||
| pub fn main() -> Result<(), JsValue> { |  | ||||||
|     console_error_panic_hook::set_once(); |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| <script> |  | ||||||
|   import Explorer from "./lib/Explorer.svelte"; |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <main> |  | ||||||
|   <div> |  | ||||||
|     <h1>Scrapland .packed explorer</h1> |  | ||||||
|     <Explorer /> |  | ||||||
|   </div> |  | ||||||
| </main> |  | ||||||
| 
 |  | ||||||
| <style> |  | ||||||
| </style> |  | ||||||
|  | @ -1,109 +0,0 @@ | ||||||
| :root { |  | ||||||
|   font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; |  | ||||||
|   line-height: 1.5; |  | ||||||
|   font-weight: 400; |  | ||||||
| 
 |  | ||||||
|   color-scheme: light dark; |  | ||||||
|   color: rgba(255, 255, 255, 0.87); |  | ||||||
|   background-color: #242424; |  | ||||||
| 
 |  | ||||||
|   font-synthesis: none; |  | ||||||
|   text-rendering: optimizeLegibility; |  | ||||||
|   -webkit-font-smoothing: antialiased; |  | ||||||
|   -moz-osx-font-smoothing: grayscale; |  | ||||||
|   -webkit-text-size-adjust: 100%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| a { |  | ||||||
|   font-weight: 500; |  | ||||||
|   color: #646cff; |  | ||||||
|   text-decoration: inherit; |  | ||||||
| } |  | ||||||
| a:hover { |  | ||||||
|   color: #535bf2; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| body { |  | ||||||
|   margin: 0; |  | ||||||
|   display: flex; |  | ||||||
|   place-items: center; |  | ||||||
|   min-width: 320px; |  | ||||||
|   min-height: 100vh; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h1 { |  | ||||||
|   font-size: 3.2em; |  | ||||||
|   line-height: 1.1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .card { |  | ||||||
|   padding: 2em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #app { |  | ||||||
|   max-width: 1280px; |  | ||||||
|   margin: 0 auto; |  | ||||||
|   padding: 2rem; |  | ||||||
|   text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| li { |  | ||||||
|   text-align: left; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| button { |  | ||||||
|   border-radius: 8px; |  | ||||||
|   border: 1px solid transparent; |  | ||||||
|   padding: 0.6em 1.2em; |  | ||||||
|   font-size: 1em; |  | ||||||
|   font-weight: 500; |  | ||||||
|   font-family: inherit; |  | ||||||
|   background-color: #1a1a1a; |  | ||||||
|   cursor: pointer; |  | ||||||
|   transition: border-color 0.25s; |  | ||||||
| } |  | ||||||
| button:hover { |  | ||||||
|   border-color: #646cff; |  | ||||||
| } |  | ||||||
| button:focus, |  | ||||||
| button:focus-visible { |  | ||||||
|   outline: 4px auto -webkit-focus-ring-color; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @media (prefers-color-scheme: light) { |  | ||||||
|   :root { |  | ||||||
|     color: #213547; |  | ||||||
|     background-color: #ffffff; |  | ||||||
|   } |  | ||||||
|   a:hover { |  | ||||||
|     color: #747bff; |  | ||||||
|   } |  | ||||||
|   button { |  | ||||||
|     background-color: #f9f9f9; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .lds-dual-ring { |  | ||||||
|   display: inline-block; |  | ||||||
|   width: 80px; |  | ||||||
|   height: 80px; |  | ||||||
| } |  | ||||||
| .lds-dual-ring:after { |  | ||||||
|   content: " "; |  | ||||||
|   display: block; |  | ||||||
|   width: 64px; |  | ||||||
|   height: 64px; |  | ||||||
|   margin: 8px; |  | ||||||
|   border-radius: 50%; |  | ||||||
|   border: 6px solid #fff; |  | ||||||
|   border-color: #fff transparent #fff transparent; |  | ||||||
|   animation: lds-dual-ring 1.2s linear infinite; |  | ||||||
| } |  | ||||||
| @keyframes lds-dual-ring { |  | ||||||
|   0% { |  | ||||||
|     transform: rotate(0deg); |  | ||||||
|   } |  | ||||||
|   100% { |  | ||||||
|     transform: rotate(360deg); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -1,52 +0,0 @@ | ||||||
| <script> |  | ||||||
|   import { onMount } from "svelte"; |  | ||||||
|   import TreeView from "./TreeView.svelte"; |  | ||||||
|   import ScrapWorker from "../scrapper.worker?worker"; |  | ||||||
|   let worker; |  | ||||||
|   let tree; |  | ||||||
|   let busy; |  | ||||||
|   busy = false; |  | ||||||
|   onMount(async () => { |  | ||||||
|     worker = new ScrapWorker(); |  | ||||||
|     worker.onmessage = (msg) => { |  | ||||||
|       console.log({ msg }); |  | ||||||
|       if (msg.data) { |  | ||||||
|         if (msg.data.parse) { |  | ||||||
|           tree = msg.data.parse; |  | ||||||
|           busy = false; |  | ||||||
|         } |  | ||||||
|         if (msg.data.download) { |  | ||||||
|           let [file_name, url] = msg.data.download; |  | ||||||
|           let dl = document.createElement("a"); |  | ||||||
|           dl.href = url; |  | ||||||
|           dl.download = file_name; |  | ||||||
|           dl.click(); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
|   }); |  | ||||||
|   let files; |  | ||||||
|   function process() { |  | ||||||
|     console.log({ files }); |  | ||||||
|     busy = true; |  | ||||||
|     worker.postMessage({ parse: files }); |  | ||||||
|   } |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <div class:lds-dual-ring={busy}> |  | ||||||
|   <input |  | ||||||
|     type="file" |  | ||||||
|     multiple |  | ||||||
|     accept=".packed" |  | ||||||
|     class="file-input file-input-bordered w-full max-w-xs" |  | ||||||
|     disabled={busy} |  | ||||||
|     bind:files |  | ||||||
|     on:change={process} |  | ||||||
|   /> |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| {#if tree} |  | ||||||
|   {#each [...tree.entries] as [name, child]} |  | ||||||
|     <TreeView scrap={worker} label={name} tree={child} /> |  | ||||||
|   {/each} |  | ||||||
| {/if} |  | ||||||
|  | @ -1,56 +0,0 @@ | ||||||
| 
 |  | ||||||
| <script> |  | ||||||
|   export let tree; |  | ||||||
|   export let scrap; |  | ||||||
|   export let label=undefined; |  | ||||||
|   let expanded = false; |  | ||||||
|   function toggleExpansion() { |  | ||||||
|     expanded = !expanded; |  | ||||||
|   }; |  | ||||||
|   function download() { |  | ||||||
|     console.log({label,tree}); |  | ||||||
|     scrap.postMessage({download:{label,...tree}}); |  | ||||||
|     console.log(tree); |  | ||||||
|   } |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <ul> |  | ||||||
|   <li> |  | ||||||
|     {#if tree.type == "directory" && tree.entries} |  | ||||||
|       <span on:click={toggleExpansion} on:keydown={toggleExpansion}> |  | ||||||
|         {#if expanded} |  | ||||||
|             <span class="arrow">[-]</span> |  | ||||||
|         {:else} |  | ||||||
|             <span class="arrow">[+]</span> |  | ||||||
|         {/if} |  | ||||||
|         {label} |  | ||||||
|       </span> |  | ||||||
|       {#if tree.entries && expanded} |  | ||||||
|         {#each [...tree.entries] as [name, child]} |  | ||||||
|           <svelte:self {scrap} label={name} tree={child} /> |  | ||||||
|         {/each} |  | ||||||
|       {/if} |  | ||||||
|     {:else} |  | ||||||
|       <span> |  | ||||||
|         <span class="no-arrow" /> |  | ||||||
|         <a href="#download" title="{tree.size} bytes" on:click={download}>{label}</a> |  | ||||||
|       </span> |  | ||||||
|     {/if} |  | ||||||
|   </li> |  | ||||||
| </ul> |  | ||||||
| 
 |  | ||||||
| <style> |  | ||||||
|   ul { |  | ||||||
|     margin: 0; |  | ||||||
|     list-style: none; |  | ||||||
|     padding-left: 1.2rem; |  | ||||||
|     user-select: none; |  | ||||||
|   } |  | ||||||
|   .no-arrow { |  | ||||||
|     padding-left: 1rem; |  | ||||||
|   } |  | ||||||
|   .arrow { |  | ||||||
|     cursor: pointer; |  | ||||||
|     display: inline-block; |  | ||||||
|   } |  | ||||||
| </style> |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| import './app.pcss' |  | ||||||
| import App from './App.svelte' |  | ||||||
| 
 |  | ||||||
| export default new App({ |  | ||||||
|   target: document.getElementById('app'), |  | ||||||
| }); |  | ||||||
|  | @ -1,28 +0,0 @@ | ||||||
| import wasm, { MultiPack } from "scrapper"; |  | ||||||
| 
 |  | ||||||
| async function initialize() { |  | ||||||
|   await wasm(); |  | ||||||
|   let pack; |  | ||||||
|   let handlers = { |  | ||||||
|     parse(data) { |  | ||||||
|       pack = new MultiPack(data); |  | ||||||
|       return pack.tree(); |  | ||||||
|     }, |  | ||||||
|     download(data) { |  | ||||||
|       if (pack) { |  | ||||||
|         let { label, file_index, offset, size } = data; |  | ||||||
|         return [label, pack.download(file_index, offset, size)]; |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|   }; |  | ||||||
|   self.onmessage = (event) => { |  | ||||||
|     for (var [name, func] of Object.entries(handlers)) { |  | ||||||
|       let data = event.data[name]; |  | ||||||
|       if (data) { |  | ||||||
|         postMessage(Object.fromEntries([[name, func(data)]])); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| initialize(); |  | ||||||
							
								
								
									
										2
									
								
								scrapper_web/src/vite-env.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								scrapper_web/src/vite-env.d.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -1,2 +0,0 @@ | ||||||
| /// <reference types="svelte" />
 |  | ||||||
| /// <reference types="vite/client" />
 |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' |  | ||||||
| export default { |  | ||||||
|   // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
 |  | ||||||
|   // for more information about preprocessors
 |  | ||||||
|   preprocess: vitePreprocess(), |  | ||||||
| } |  | ||||||
|  | @ -1,36 +0,0 @@ | ||||||
| module.exports = { |  | ||||||
|   content: ["./src/**/*.{svelte,js,ts}"], |  | ||||||
|   plugins: [require("@tailwindcss/forms"),require("daisyui")], |  | ||||||
|   theme: { |  | ||||||
|     container: { |  | ||||||
|       center: true, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   daisyui: { |  | ||||||
|     styled: true, |  | ||||||
|     themes: true, |  | ||||||
|     base: true, |  | ||||||
|     utils: true, |  | ||||||
|     logs: true, |  | ||||||
|     rtl: false, |  | ||||||
|     prefix: "", |  | ||||||
|     darkTheme: "scraptool", |  | ||||||
|     themes: [ |  | ||||||
|       { |  | ||||||
|         scraptool: { |  | ||||||
|           primary: "#F28C18", |  | ||||||
|           secondary: "#b45309", |  | ||||||
|           accent: "#22d3ee", |  | ||||||
|           neutral: "#1B1D1D", |  | ||||||
|           "base-100": "#212121", |  | ||||||
|           info: "#2463EB", |  | ||||||
|           success: "#16A249", |  | ||||||
|           warning: "#DB7706", |  | ||||||
|           error: "#DC2828", |  | ||||||
|           // "--rounded-box": "0.4rem",
 |  | ||||||
|           // "--rounded-btn": "0.2rem"
 |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| import { defineConfig } from 'vite' |  | ||||||
| import { svelte } from '@sveltejs/vite-plugin-svelte' |  | ||||||
| import wasmPack from 'vite-plugin-wasm-pack'; |  | ||||||
| import preprocess from 'svelte-preprocess'; |  | ||||||
| 
 |  | ||||||
| export default defineConfig({ |  | ||||||
|   plugins: [wasmPack("./scrapper/"),svelte({ |  | ||||||
|     preprocess: preprocess({ postcss: true })  |  | ||||||
|   })] |  | ||||||
| }); |  | ||||||
|  | @ -1,134 +0,0 @@ | ||||||
| 
 |  | ||||||
| try: |  | ||||||
|     import ghidra_bridge |  | ||||||
|     has_bridge=True |  | ||||||
| except ImportError: |  | ||||||
|     has_bridge=False |  | ||||||
| 
 |  | ||||||
| from contextlib import contextmanager |  | ||||||
| 
 |  | ||||||
| if has_bridge: |  | ||||||
|     import ghidra_bridge |  | ||||||
|     b = ghidra_bridge.GhidraBridge(namespace=globals(), hook_import=True) |  | ||||||
|     @contextmanager |  | ||||||
|     def transaction(): |  | ||||||
|         start() |  | ||||||
|         try: |  | ||||||
|             yield |  | ||||||
|         except Exception as e: |  | ||||||
|             end(False) |  | ||||||
|             raise e |  | ||||||
|         end(True) |  | ||||||
| else: |  | ||||||
|     @contextmanager |  | ||||||
|     def transaction(): |  | ||||||
|         yield |  | ||||||
| 
 |  | ||||||
| import ghidra.program.model.symbol.SymbolType as SymbolType |  | ||||||
| import ghidra.program.model.symbol.SourceType as SourceType |  | ||||||
| from ghidra.app.cmd.label import CreateNamespacesCmd |  | ||||||
| from ghidra.program.model.data.DataUtilities import createData |  | ||||||
| from ghidra.program.model.data.DataUtilities import ClearDataMode |  | ||||||
| from ghidra.program.model.listing.CodeUnit import PLATE_COMMENT |  | ||||||
| def make_namespace(parts): |  | ||||||
|     ns_cmd = CreateNamespacesCmd("::".join(parts), SourceType.USER_DEFINED) |  | ||||||
|     ns_cmd.applyTo(currentProgram) |  | ||||||
|     return ns_cmd.getNamespace() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| callback_refs = [ref.fromAddress for ref in getReferencesTo(toAddr(0x590C70)).tolist()] |  | ||||||
| engine_var_refs = [ |  | ||||||
|     ref.fromAddress for ref in getReferencesTo(toAddr(0x5319D0)).tolist() |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| dtm = currentProgram.getDataTypeManager() |  | ||||||
| engine_var_dt = dtm.getDataType("/EngineVar") |  | ||||||
| callback_dt = dtm.getDataType("/CCallback") |  | ||||||
| 
 |  | ||||||
| def create_data(addr,dtype): |  | ||||||
|     return createData(currentProgram,addr,dtype,0,False,ClearDataMode.CLEAR_ALL_CONFLICT_DATA) |  | ||||||
| 
 |  | ||||||
| def create_str(addr): |  | ||||||
|     str_len = (findBytes(addr, b"\0").offset - addr.offset) + 1 |  | ||||||
|     clearListing(addr, addr.add(str_len)) |  | ||||||
|     return createAsciiString(addr) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def make_namespace(parts): |  | ||||||
|     ns_cmd = CreateNamespacesCmd("::".join(parts), SourceType.USER_DEFINED) |  | ||||||
|     ns_cmd.applyTo(currentProgram) |  | ||||||
|     return ns_cmd.getNamespace() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_call_obj(addr): |  | ||||||
|     func = getFunctionContaining(addr) |  | ||||||
|     if func is None: |  | ||||||
|         disassemble(addr) |  | ||||||
|         func = createFunction(addr,None) |  | ||||||
|     call_obj = {"this": None, "stack": []} |  | ||||||
|     for inst in currentProgram.listing.getInstructions(func.body, True): |  | ||||||
|         affected_objs = [r.toString() for r in inst.resultObjects.tolist()] |  | ||||||
|         inst_name = inst.getMnemonicString() |  | ||||||
|         if inst_name == "PUSH": |  | ||||||
|             val=inst.getScalar(0) |  | ||||||
|             if val is not None: |  | ||||||
|                 call_obj["stack"].insert(0, toAddr(val.getValue()).toString()) |  | ||||||
|         elif inst_name == "MOV" and "ECX" in affected_objs: |  | ||||||
|             this = inst.getScalar(1) |  | ||||||
|             if this is not None: |  | ||||||
|                 call_obj["this"] = toAddr(this.getValue()).toString() |  | ||||||
|         elif inst_name == "CALL": |  | ||||||
|             break |  | ||||||
|     return func, call_obj |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| with transaction(): |  | ||||||
|     for ref in callback_refs: |  | ||||||
|         register_callback, call_obj = get_call_obj(ref) |  | ||||||
|         name, addr = call_obj["stack"] |  | ||||||
|         this = toAddr(call_obj["this"]) |  | ||||||
|         addr = toAddr(addr) |  | ||||||
|         name = create_str(toAddr(name)).getValue() |  | ||||||
|         callback_ns = make_namespace(["Callbacks"]) |  | ||||||
|         ns = make_namespace(["Callbacks", name]) |  | ||||||
|         clearListing(addr) |  | ||||||
|         disassemble(addr) |  | ||||||
|         func = createFunction(addr,None) |  | ||||||
|         print(name,func) |  | ||||||
|         createLabel(addr, name, callback_ns, True, SourceType.USER_DEFINED) |  | ||||||
|         createLabel( |  | ||||||
|             register_callback.getEntryPoint(), |  | ||||||
|             "register", |  | ||||||
|             ns, |  | ||||||
|             True, |  | ||||||
|             SourceType.USER_DEFINED, |  | ||||||
|         ) |  | ||||||
|         createLabel(this, name, None, True, SourceType.USER_DEFINED) |  | ||||||
|         create_data(this,callback_dt) |  | ||||||
| 
 |  | ||||||
|     for ref in engine_var_refs: |  | ||||||
|         register_engine_var, call_obj = get_call_obj(ref) |  | ||||||
|         engine_var = call_obj['this'] |  | ||||||
|         try: |  | ||||||
|             name,flags,desc = call_obj['stack'][:3] |  | ||||||
|         except ValueError: |  | ||||||
|             continue |  | ||||||
|         name=create_str(toAddr(name)).getValue() |  | ||||||
|         desc=create_str(toAddr(desc)).getValue() |  | ||||||
|         print(name,ref) |  | ||||||
|         ev_ns = make_namespace(["EngineVars"]) |  | ||||||
|         ns = make_namespace(["EngineVars", name]) |  | ||||||
|         clearListing(toAddr(engine_var)) |  | ||||||
|         create_data(toAddr(engine_var),engine_var_dt).setComment(PLATE_COMMENT,desc) |  | ||||||
|         createLabel(toAddr(engine_var), name, ev_ns, True, SourceType.USER_DEFINED) |  | ||||||
|         clearListing(register_engine_var.getEntryPoint()) |  | ||||||
|         createLabel(register_engine_var.getEntryPoint(), "register", ns, True, SourceType.USER_DEFINED) |  | ||||||
| 
 |  | ||||||
| # listing = currentProgram.getListing() |  | ||||||
| # codeUnit = listing.getCodeUnitAt(minAddress) |  | ||||||
| # codeUnit.setComment(codeUnit.PLATE_COMMENT, "AddCommentToProgramScript - This is an added comment!") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # dtm = currentProgram.getDataTypeManager() |  | ||||||
| # dt_engine_var = dtm.getDataType("/EngineVar") |  | ||||||
| # dt_engine_ptr = dtm.getPointer(dt_engine_var) |  | ||||||
|  | @ -1,58 +0,0 @@ | ||||||
| from ghidra.app.decompiler import DecompileOptions |  | ||||||
| from ghidra.app.decompiler import DecompInterface |  | ||||||
| from ghidra.util.task import ConsoleTaskMonitor |  | ||||||
| 
 |  | ||||||
| TARGET_FUNC = "add_callback" |  | ||||||
| 
 |  | ||||||
| def xref_params(target_func): |  | ||||||
|     target_addr = 0 |  | ||||||
|     callers = [] |  | ||||||
|     funcs = getGlobalFunctions(target_func) |  | ||||||
|     for func in funcs: |  | ||||||
|         if func.getName() == target_func: |  | ||||||
|             target_addr = func.getEntryPoint() |  | ||||||
|             references = getReferencesTo(target_addr) |  | ||||||
|             for xref in references: |  | ||||||
|                 call_addr = xref.getFromAddress() |  | ||||||
|                 caller = getFunctionContaining(call_addr) |  | ||||||
|                 callers.append(caller) |  | ||||||
|             break |  | ||||||
|     callers = list(set(callers)) |  | ||||||
|     options = DecompileOptions() |  | ||||||
|     monitor = ConsoleTaskMonitor() |  | ||||||
|     ifc = DecompInterface() |  | ||||||
|     ifc.setOptions(options) |  | ||||||
|     ifc.openProgram(currentProgram) |  | ||||||
|     with open("callbacks.md", "w") as file: |  | ||||||
|         res = "|Callback setup address|Callback name|Callback funcion|Callback address|" |  | ||||||
|         print(res) |  | ||||||
|         file.write(res + "\n") |  | ||||||
|         res = "|-----|----|----|--------|" |  | ||||||
|         print(res) |  | ||||||
|         file.write(res + "\n") |  | ||||||
|         for caller in callers: |  | ||||||
|             callback_setup_addr = caller.getEntryPoint() |  | ||||||
|             res = ifc.decompileFunction(caller, 60, monitor) |  | ||||||
|             code = str(res.getDecompiledFunction().getC()) |  | ||||||
|             code = code.split(target_func)[1] |  | ||||||
|             code = code.split(';')[0] |  | ||||||
|             code = code.strip() |  | ||||||
|             code = code.split(',') |  | ||||||
|             callback_name = code[1].strip() |  | ||||||
|             callback_func = code[2].strip()[:-1].strip().replace('_', '.') |  | ||||||
|             res = ifc.decompileFunction(caller, 60, monitor) |  | ||||||
|             hf = res.getHighFunction() |  | ||||||
|             opiter = hf.getPcodeOps() |  | ||||||
|             callback_addr = "not found" |  | ||||||
|             while opiter.hasNext(): |  | ||||||
|                 op = opiter.next() |  | ||||||
|                 mnemonic = op.getMnemonic() |  | ||||||
|                 if mnemonic == "CALL": |  | ||||||
|                     core_func = op.getInput(3) |  | ||||||
|                     callback_addr = toAddr(core_func.getDef().getInput(1).getOffset()) |  | ||||||
|             res = "|`{}`|{}|`{}`|`{}`|".format(callback_setup_addr, callback_name, callback_func, callback_addr) |  | ||||||
|             print(res) |  | ||||||
|             file.write(res + "\n") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| xref_params(TARGET_FUNC) |  | ||||||
|  | @ -1,125 +0,0 @@ | ||||||
| import time |  | ||||||
| try: |  | ||||||
|     import ghidra_bridge |  | ||||||
|     has_bridge=True |  | ||||||
| except ImportError: |  | ||||||
|     has_bridge=False |  | ||||||
| 
 |  | ||||||
| from contextlib import contextmanager |  | ||||||
| 
 |  | ||||||
| if has_bridge: |  | ||||||
|     import ghidra_bridge |  | ||||||
|     b = ghidra_bridge.GhidraBridge(namespace=globals(), hook_import=True) |  | ||||||
|     @contextmanager |  | ||||||
|     def transaction(): |  | ||||||
|         start() |  | ||||||
|         try: |  | ||||||
|             yield |  | ||||||
|         except Exception as e: |  | ||||||
|             end(False) |  | ||||||
|             raise e |  | ||||||
|         end(True) |  | ||||||
| else: |  | ||||||
|     @contextmanager |  | ||||||
|     def transaction(): |  | ||||||
|         yield |  | ||||||
| 
 |  | ||||||
| import ghidra.program.model.symbol.SymbolType as SymbolType |  | ||||||
| import ghidra.program.model.symbol.SourceType as SourceType |  | ||||||
| from ghidra.app.cmd.label import CreateNamespacesCmd |  | ||||||
| from ghidra.program.model.data.DataUtilities import createData |  | ||||||
| from ghidra.program.model.data.DataUtilities import ClearDataMode |  | ||||||
| from ghidra.program.model.listing.CodeUnit import PLATE_COMMENT |  | ||||||
| 
 |  | ||||||
| listing = currentProgram.getListing() |  | ||||||
| dtm = currentProgram.getDataTypeManager() |  | ||||||
| py_mod = dtm.getDataType("/PyModuleDef") |  | ||||||
| py_meth = dtm.getDataType("/PyMethodDef") |  | ||||||
| 
 |  | ||||||
| NULL=toAddr(0) |  | ||||||
| 
 |  | ||||||
| def make_namespace(parts): |  | ||||||
|     ns_cmd = CreateNamespacesCmd("::".join(parts), SourceType.USER_DEFINED) |  | ||||||
|     ns_cmd.applyTo(currentProgram) |  | ||||||
|     return ns_cmd.getNamespace() |  | ||||||
| 
 |  | ||||||
| def create_data(addr,dtype): |  | ||||||
|     return createData(currentProgram,addr,dtype,0,False,ClearDataMode.CLEAR_ALL_CONFLICT_DATA) |  | ||||||
| 
 |  | ||||||
| def create_str(addr): |  | ||||||
|     if addr.equals(NULL): |  | ||||||
|         return None |  | ||||||
|     str_len = (findBytes(addr, b"\0").offset - addr.offset) + 1 |  | ||||||
|     clearListing(addr, addr.add(str_len)) |  | ||||||
|     return createAsciiString(addr) |  | ||||||
| 
 |  | ||||||
| def get_call_obj(addr): |  | ||||||
|     func = getFunctionContaining(addr) |  | ||||||
|     if func is None: |  | ||||||
|         disassemble(addr) |  | ||||||
|         func = createFunction(addr,None) |  | ||||||
|     call_obj = {"this": None, "stack": []} |  | ||||||
|     for inst in currentProgram.listing.getInstructions(func.body, True): |  | ||||||
|         affected_objs = [r.toString() for r in inst.resultObjects.tolist()] |  | ||||||
|         inst_name = inst.getMnemonicString() |  | ||||||
|         if inst_name == "PUSH": |  | ||||||
|             val=inst.getScalar(0) |  | ||||||
|             if val is not None: |  | ||||||
|                 call_obj["stack"].insert(0, toAddr(val.getValue()).toString()) |  | ||||||
|         elif inst_name == "MOV" and "ECX" in affected_objs: |  | ||||||
|             this = inst.getScalar(1) |  | ||||||
|             if this is not None: |  | ||||||
|                 call_obj["this"] = toAddr(this.getValue()).toString() |  | ||||||
|         elif inst_name == "CALL": |  | ||||||
|             break |  | ||||||
|     func=func.symbol.address |  | ||||||
|     return func, call_obj |  | ||||||
| 
 |  | ||||||
| def data_to_dict(data): |  | ||||||
|     ret={} |  | ||||||
|     for idx in range(data.dataType.getNumComponents()): |  | ||||||
|         name=data.dataType.getComponent(idx).getFieldName() |  | ||||||
|         value=data.getComponent(idx).getValue() |  | ||||||
|         ret[name]=value |  | ||||||
|     return ret |  | ||||||
| 
 |  | ||||||
| def try_create_str(addr): |  | ||||||
|     ret=create_str(addr) |  | ||||||
|     if ret: |  | ||||||
|         return ret.getValue() |  | ||||||
| 
 |  | ||||||
| with transaction(): |  | ||||||
|     PyInitModule=getSymbolAt(toAddr("006f31c0")) |  | ||||||
|     for ref in getReferencesTo(PyInitModule.address).tolist(): |  | ||||||
|         func,args=get_call_obj(ref.fromAddress) |  | ||||||
|         print(func,args) |  | ||||||
|         module_name=create_str(toAddr(args['stack'][0])).getValue() |  | ||||||
|         methods=toAddr(args['stack'][1]) |  | ||||||
|         module_doc=create_str(toAddr(args['stack'][2])) |  | ||||||
|         if module_doc: |  | ||||||
|             module_doc=module_doc.getValue() |  | ||||||
|         print(methods,module_name,module_doc) |  | ||||||
|         mod_ns = make_namespace(["Python", module_name]) |  | ||||||
|         createLabel(func, "__init__", mod_ns, True, SourceType.USER_DEFINED) |  | ||||||
|         if module_doc: |  | ||||||
|             listing.getCodeUnitAt(func).setComment(PLATE_COMMENT,module_doc) |  | ||||||
|         while True: |  | ||||||
|             mod_data=data_to_dict(create_data(methods,py_meth)) |  | ||||||
|             if mod_data['name'] is None: |  | ||||||
|                 clearListing(methods, methods.add(16)) |  | ||||||
|                 break |  | ||||||
|             mod_data['name']=try_create_str(mod_data['name']) |  | ||||||
|             try: |  | ||||||
|                 mod_data['doc']=try_create_str(mod_data['doc']) |  | ||||||
|             except: |  | ||||||
|                 mod_data['doc']=None |  | ||||||
|             print(mod_data) |  | ||||||
|             createLabel(mod_data['ml_method'], mod_data['name'], mod_ns, True, SourceType.USER_DEFINED) |  | ||||||
|             if mod_data['doc']: |  | ||||||
|                 listing.getCodeUnitAt(mod_data['ml_method']).setComment(PLATE_COMMENT,module_doc) |  | ||||||
|             methods=methods.add(16) |  | ||||||
|             try: |  | ||||||
|                 if getBytes(methods,4).tolist()==[0,0,0,0]: |  | ||||||
|                     break |  | ||||||
|             except: |  | ||||||
|                 break |  | ||||||
							
								
								
									
										2
									
								
								tools/remaster/scrap_net/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								tools/remaster/scrap_net/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,2 +0,0 @@ | ||||||
| /target |  | ||||||
| /.history |  | ||||||
							
								
								
									
										1015
									
								
								tools/remaster/scrap_net/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1015
									
								
								tools/remaster/scrap_net/Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,28 +0,0 @@ | ||||||
| [package] |  | ||||||
| name = "scrap_net" |  | ||||||
| version = "0.1.0" |  | ||||||
| edition = "2021" |  | ||||||
| authors = ["Daniel Seiller <earthnuker@gmail.com>"] |  | ||||||
| description = "Scrapland Remastered network sniffer, proxy (and soon hopefully parser)" |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |  | ||||||
| 
 |  | ||||||
| [dependencies] |  | ||||||
| chacha20 = { version = "0.9", features = ["std"] } |  | ||||||
| poly1305 = { version = "0.8", features = ["std"] } |  | ||||||
| rhexdump = "0.1" |  | ||||||
| tokio = { version = "1.21", features = ["full"] } |  | ||||||
| clap = {version = "4.0", features = ["derive"]} |  | ||||||
| rand = "0.8" |  | ||||||
| dialoguer = "0.10" |  | ||||||
| binrw = "0.11" |  | ||||||
| modular-bitfield = "0.11" |  | ||||||
| hex = "0.4" |  | ||||||
| lazy_static = "1.4.0" |  | ||||||
| rustyline-async = "0.3" |  | ||||||
| futures-util = "0.3.24" |  | ||||||
| itertools = "0.10.5" |  | ||||||
| anyhow = "1.0.68" |  | ||||||
| 
 |  | ||||||
| [profile.release] |  | ||||||
| lto="fat" |  | ||||||
| opt-level = 3 |  | ||||||
|  | @ -1,22 +0,0 @@ | ||||||
| from distutils.command.install_data import install_data |  | ||||||
| import winreg as reg |  | ||||||
| import vdf |  | ||||||
| from pathlib import Path |  | ||||||
| import pefile |  | ||||||
| app_id="897610" |  | ||||||
| try: |  | ||||||
|     key = reg.OpenKey(reg.HKEY_LOCAL_MACHINE,"SOFTWARE\\Valve\\Steam") |  | ||||||
| except FileNotFoundError: |  | ||||||
|     key = reg.OpenKey(reg.HKEY_LOCAL_MACHINE,"SOFTWARE\\Wow6432Node\\Valve\\Steam") |  | ||||||
| path=Path(reg.QueryValueEx(key,"InstallPath")[0]) |  | ||||||
| libraryfolders=vdf.load((path/"steamapps"/"libraryfolders.vdf").open("r"))['libraryfolders'] |  | ||||||
| for folder in libraryfolders.values(): |  | ||||||
|     path=Path(folder['path']) |  | ||||||
|     if app_id in folder['apps']: |  | ||||||
|         install_dir = vdf.load((path/"steamapps"/f"appmanifest_{app_id}.acf").open("r"))['AppState']['installdir'] |  | ||||||
|         install_dir=path/"steamapps"/"common"/install_dir |  | ||||||
|         for file in install_dir.glob("**/*.exe"): |  | ||||||
|             pe = pefile.PE(file, fast_load=True) |  | ||||||
|             entry = pe.OPTIONAL_HEADER.AddressOfEntryPoint |  | ||||||
|             if pe.get_dword_at_rva(entry) == 0xE8: |  | ||||||
|                 print(file) |  | ||||||
|  | @ -1,93 +0,0 @@ | ||||||
| use itertools::Itertools; |  | ||||||
| use std::fmt::Display; |  | ||||||
| use std::ops::{Deref, DerefMut}; |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, PartialEq, Eq)] |  | ||||||
| enum HexII { |  | ||||||
|     Ascii(char), |  | ||||||
|     Byte(u8), |  | ||||||
|     Null, |  | ||||||
|     Full, |  | ||||||
|     Eof, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<&u8> for HexII { |  | ||||||
|     fn from(v: &u8) -> Self { |  | ||||||
|         match v { |  | ||||||
|             0x00 => Self::Null, |  | ||||||
|             0xFF => Self::Full, |  | ||||||
|             c if c.is_ascii_graphic() => Self::Ascii(*c as char), |  | ||||||
|             v => Self::Byte(*v), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Display for HexII { |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         match self { |  | ||||||
|             HexII::Ascii(v) => write!(f, ".{}", v)?, |  | ||||||
|             HexII::Byte(v) => write!(f, "{:02x}", v)?, |  | ||||||
|             HexII::Null => write!(f, "  ")?, |  | ||||||
|             HexII::Full => write!(f, "##")?, |  | ||||||
|             HexII::Eof => write!(f, " ]")?, |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| struct HexIILine(Vec<HexII>); |  | ||||||
| 
 |  | ||||||
| impl Display for HexIILine { |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         for (i, v) in self.0.iter().enumerate() { |  | ||||||
|             if i != 0 { |  | ||||||
|                 write!(f, " ")?; |  | ||||||
|             } |  | ||||||
|             write!(f, "{}", v)?; |  | ||||||
|         } |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<&[u8]> for HexIILine { |  | ||||||
|     fn from(l: &[u8]) -> Self { |  | ||||||
|         Self(l.iter().map(HexII::from).collect()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Deref for HexIILine { |  | ||||||
|     type Target = Vec<HexII>; |  | ||||||
|     fn deref(&self) -> &Self::Target { |  | ||||||
|         &self.0 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl DerefMut for HexIILine { |  | ||||||
|     fn deref_mut(&mut self) -> &mut Self::Target { |  | ||||||
|         &mut self.0 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn hex_ii_dump<T: Iterator<Item = u8>>(data: T, base_offset: usize, total: usize) { |  | ||||||
|     const CHUNK_SIZE: usize = 0x10; |  | ||||||
|     let mut num_digits = (std::mem::size_of_val(&total) * 8) - (total.leading_zeros() as usize); |  | ||||||
|     if (num_digits % 8) != 0 { |  | ||||||
|         num_digits += 8 - (num_digits % 8) |  | ||||||
|     } |  | ||||||
|     num_digits >>= 2; |  | ||||||
|     for (mut offset, line) in data.chunks(CHUNK_SIZE).into_iter().enumerate() { |  | ||||||
|         offset += base_offset; |  | ||||||
|         let mut line = HexIILine::from(line.collect::<Vec<_>>().as_slice()); |  | ||||||
|         if line.len() < CHUNK_SIZE { |  | ||||||
|             line.push(HexII::Eof); |  | ||||||
|         } |  | ||||||
|         while line.len() < CHUNK_SIZE { |  | ||||||
|             line.push(HexII::Null); |  | ||||||
|         } |  | ||||||
|         if line.iter().all(|v| v == &HexII::Null) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|         let offset = format!("{:digits$x}", offset * CHUNK_SIZE, digits = num_digits); |  | ||||||
|         println!("{} | {:<16} |", offset, line); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,640 +0,0 @@ | ||||||
| use anyhow::{bail, ensure, Result}; |  | ||||||
| use binrw::BinReaderExt; |  | ||||||
| use binrw::{BinRead, NullString}; |  | ||||||
| use chacha20::cipher::KeyInit; |  | ||||||
| use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek}; |  | ||||||
| use chacha20::ChaCha20; |  | ||||||
| use clap::Parser; |  | ||||||
| use dialoguer::theme::ColorfulTheme; |  | ||||||
| use dialoguer::Select; |  | ||||||
| use futures_util::FutureExt; |  | ||||||
| use poly1305::Poly1305; |  | ||||||
| use rand::{thread_rng, Rng}; |  | ||||||
| use rhexdump::hexdump; |  | ||||||
| use rustyline_async::{Readline, ReadlineError, SharedWriter}; |  | ||||||
| use std::collections::BTreeMap; |  | ||||||
| use std::error::Error; |  | ||||||
| use std::fmt::Display; |  | ||||||
| use std::io::Cursor; |  | ||||||
| use std::io::Write; |  | ||||||
| use std::iter; |  | ||||||
| use std::net::SocketAddr; |  | ||||||
| use std::net::ToSocketAddrs; |  | ||||||
| use std::net::{IpAddr, Ipv4Addr}; |  | ||||||
| use std::path::PathBuf; |  | ||||||
| use std::time::{Duration, Instant}; |  | ||||||
| use tokio::io::AsyncBufReadExt; |  | ||||||
| use tokio::net::UdpSocket; |  | ||||||
| use tokio::time; |  | ||||||
| 
 |  | ||||||
| mod hex_ii; |  | ||||||
| mod parser; |  | ||||||
| 
 |  | ||||||
| const KEY: &[u8; 32] = b"\x02\x04\x06\x08\x0a\x0c\x0e\x10\x12\x14\x16\x18\x1a\x1c\x1e\x20\x22\x24\x26\x28\x2a\x2c\x2e\x30\x32\x34\x36\x38\x3a\x3c\x3e\x40"; |  | ||||||
| const INFO_PACKET: &[u8] = b"\x7f\x01\x00\x00\x07"; |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| struct ServerFlags { |  | ||||||
|     dedicated: bool, |  | ||||||
|     force_vehicle: bool, |  | ||||||
|     _rest: u8, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Display for ServerFlags { |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         let force_vehicle = if self.force_vehicle { "F" } else { " " }; |  | ||||||
|         let dedicated = if self.dedicated { "D" } else { " " }; |  | ||||||
|         write!(f, "{}{}", force_vehicle, dedicated)?; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl From<u8> for ServerFlags { |  | ||||||
|     fn from(v: u8) -> Self { |  | ||||||
|         ServerFlags { |  | ||||||
|             dedicated: v & 0b1 != 0, |  | ||||||
|             force_vehicle: v & 0b10 != 0, |  | ||||||
|             _rest: (v & 0b11111100) >> 2, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone, BinRead)] |  | ||||||
| #[br(little, magic = b"\xba\xce", import(rtt: Duration, addr: SocketAddr))] |  | ||||||
| pub struct Server { |  | ||||||
|     #[br(calc=addr)] |  | ||||||
|     addr: SocketAddr, |  | ||||||
|     #[br(calc=rtt)] |  | ||||||
|     rtt: Duration, |  | ||||||
|     #[br(map = |v: (u8,u8)| format!("{}.{}",v.0,v.1))] |  | ||||||
|     version: String, |  | ||||||
|     port: u16, |  | ||||||
|     max_players: u16, |  | ||||||
|     cur_players: u16, |  | ||||||
|     #[br(map = u8::into)] |  | ||||||
|     flags: ServerFlags, |  | ||||||
|     #[br(pad_size_to(0x20), map = |s :NullString| s.to_string())] |  | ||||||
|     name: String, |  | ||||||
|     #[br(pad_size_to(0x10), map = |s :NullString| s.to_string())] |  | ||||||
|     mode: String, |  | ||||||
|     #[br(pad_size_to(0x20), map = |s :NullString| s.to_string())] |  | ||||||
|     map: String, |  | ||||||
|     _pad: u8, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn pad_copy(d: &[u8], l: usize) -> Vec<u8> { |  | ||||||
|     let diff = d.len() % l; |  | ||||||
|     if diff != 0 { |  | ||||||
|         d.iter() |  | ||||||
|             .copied() |  | ||||||
|             .chain(iter::repeat(0).take(l - diff)) |  | ||||||
|             .collect() |  | ||||||
|     } else { |  | ||||||
|         d.to_vec() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn pad(d: &mut Vec<u8>, l: usize) { |  | ||||||
|     let diff = d.len() % l; |  | ||||||
|     if diff != 0 { |  | ||||||
|         d.extend(iter::repeat(0).take(l - diff)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| struct Packet { |  | ||||||
|     nonce: Vec<u8>, |  | ||||||
|     data: Vec<u8>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Packet { |  | ||||||
|     fn encrypt(data: &[u8]) -> Packet { |  | ||||||
|         let mut data: Vec<u8> = data.to_vec(); |  | ||||||
|         let mut rng = thread_rng(); |  | ||||||
|         let mut nonce = vec![0u8; 12]; |  | ||||||
|         rng.fill(nonce.as_mut_slice()); |  | ||||||
|         let mut cipher = ChaCha20::new(KEY.into(), nonce.as_slice().into()); |  | ||||||
|         cipher.seek(KEY.len() + 32); |  | ||||||
|         cipher.apply_keystream(&mut data); |  | ||||||
|         Packet { nonce, data } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn get_tag(&self) -> Vec<u8> { |  | ||||||
|         let mut sign_data = vec![]; |  | ||||||
|         sign_data.extend(pad_copy(&self.nonce, 16).iter()); |  | ||||||
|         sign_data.extend(pad_copy(&self.data, 16).iter()); |  | ||||||
|         sign_data.extend((self.nonce.len() as u64).to_le_bytes().iter()); |  | ||||||
|         sign_data.extend((self.data.len() as u64).to_le_bytes().iter()); |  | ||||||
|         let mut cipher = ChaCha20::new(KEY.into(), self.nonce.as_slice().into()); |  | ||||||
|         let mut poly_key = *KEY; |  | ||||||
|         cipher.apply_keystream(&mut poly_key); |  | ||||||
|         let signer = Poly1305::new(&poly_key.into()); |  | ||||||
|         signer.compute_unpadded(&sign_data).into_iter().collect() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn bytes(&self) -> Vec<u8> { |  | ||||||
|         let mut data = vec![]; |  | ||||||
|         data.extend(pad_copy(&self.nonce, 16).iter()); |  | ||||||
|         data.extend(pad_copy(&self.data, 16).iter()); |  | ||||||
|         data.extend((self.nonce.len() as u64).to_le_bytes().iter()); |  | ||||||
|         data.extend((self.data.len() as u64).to_le_bytes().iter()); |  | ||||||
|         data.extend(self.get_tag().iter()); |  | ||||||
|         data |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn decrypt(&self) -> Result<Vec<u8>> { |  | ||||||
|         let mut data = self.data.clone(); |  | ||||||
|         let mut sign_data = data.clone(); |  | ||||||
|         pad(&mut sign_data, 16); |  | ||||||
|         let mut nonce = self.nonce.clone(); |  | ||||||
|         pad(&mut nonce, 16); |  | ||||||
|         let sign_data = nonce |  | ||||||
|             .iter() |  | ||||||
|             .chain(sign_data.iter()) |  | ||||||
|             .chain((self.nonce.len() as u64).to_le_bytes().iter()) |  | ||||||
|             .chain((self.data.len() as u64).to_le_bytes().iter()) |  | ||||||
|             .copied() |  | ||||||
|             .collect::<Vec<u8>>(); |  | ||||||
|         let mut poly_key = *KEY; |  | ||||||
|         let mut cipher = ChaCha20::new(KEY.into(), self.nonce.as_slice().into()); |  | ||||||
|         cipher.apply_keystream(&mut poly_key); |  | ||||||
|         let signer = Poly1305::new(&poly_key.into()); |  | ||||||
|         let signature: Vec<u8> = signer.compute_unpadded(&sign_data).into_iter().collect(); |  | ||||||
| 
 |  | ||||||
|         if signature != self.get_tag() { |  | ||||||
|             bail!("Invalid signature!"); |  | ||||||
|         }; |  | ||||||
|         cipher.seek(poly_key.len() + 32); |  | ||||||
|         cipher.apply_keystream(&mut data); |  | ||||||
|         Ok(data) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl TryFrom<&[u8]> for Packet { |  | ||||||
|     type Error = anyhow::Error; |  | ||||||
|     fn try_from(data: &[u8]) -> Result<Self> { |  | ||||||
|         let (mut nonce, data) = data.split_at(16); |  | ||||||
|         let (mut data, tag) = data.split_at(data.len() - 16); |  | ||||||
|         let nonce_len = u64::from_le_bytes(data[data.len() - 16..][..8].try_into()?) as usize; |  | ||||||
|         let data_len = u64::from_le_bytes(data[data.len() - 8..].try_into()?) as usize; |  | ||||||
|         data = &data[..data_len]; |  | ||||||
|         nonce = &nonce[..nonce_len]; |  | ||||||
|         let pkt = Packet { |  | ||||||
|             nonce: nonce.into(), |  | ||||||
|             data: data.into(), |  | ||||||
|         }; |  | ||||||
|         if pkt.get_tag() != tag { |  | ||||||
|             bail!("Invalid signature!"); |  | ||||||
|         } |  | ||||||
|         Ok(pkt) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub enum ServerEntry { |  | ||||||
|     Alive(Server), |  | ||||||
|     Dead { addr: SocketAddr, reason: String }, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Display for ServerEntry { |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         match self { |  | ||||||
|             ServerEntry::Alive(srv) => write!( |  | ||||||
|                 f, |  | ||||||
|                 "[{}] {} ({} {}/{} Players on {}) version {} [{}] RTT: {:?}", |  | ||||||
|                 srv.addr, |  | ||||||
|                 srv.name, |  | ||||||
|                 srv.mode, |  | ||||||
|                 srv.cur_players, |  | ||||||
|                 srv.max_players, |  | ||||||
|                 srv.map, |  | ||||||
|                 srv.version, |  | ||||||
|                 srv.flags, |  | ||||||
|                 srv.rtt |  | ||||||
|             ), |  | ||||||
|             ServerEntry::Dead { addr, reason } => write!(f, "[{}] (error: {})", addr, reason), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn encrypt(data: &[u8]) -> Vec<u8> { |  | ||||||
|     Packet::encrypt(data).bytes() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn decrypt(data: &[u8]) -> Result<Vec<u8>> { |  | ||||||
|     Packet::try_from(data)?.decrypt() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn recv_from_timeout( |  | ||||||
|     sock: &UdpSocket, |  | ||||||
|     buf: &mut [u8], |  | ||||||
|     timeout: f64, |  | ||||||
| ) -> Result<(usize, SocketAddr)> { |  | ||||||
|     Ok(time::timeout(Duration::from_secs_f64(timeout), sock.recv_from(buf)).await??) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn query_server<'a>(addr: SocketAddr) -> Result<Server> { |  | ||||||
|     let mut buf = [0; 32 * 1024]; |  | ||||||
|     let socket = UdpSocket::bind("0.0.0.0:0").await?; |  | ||||||
|     socket.connect(addr).await?; |  | ||||||
|     let msg = encrypt(INFO_PACKET); |  | ||||||
|     let t_start = Instant::now(); |  | ||||||
|     socket.send(&msg).await?; |  | ||||||
|     let size = recv_from_timeout(&socket, &mut buf, 5.0).await?.0; |  | ||||||
|     let rtt = t_start.elapsed(); |  | ||||||
|     let data = decrypt(&buf[..size])?; |  | ||||||
|     if !data.starts_with(&[0xba, 0xce]) { |  | ||||||
|         // Server Info
 |  | ||||||
|         bail!("Invalid response"); |  | ||||||
|     } |  | ||||||
|     let mut cur = Cursor::new(&data); |  | ||||||
|     let info: Server = cur.read_le_args((rtt, addr))?; |  | ||||||
|     if info.port != addr.port() { |  | ||||||
|         eprint!("[WARN] Port differs for {}: {}", addr, info.port); |  | ||||||
|     } |  | ||||||
|     if cur.position() != (data.len() as u64) { |  | ||||||
|         bail!("Leftover data"); |  | ||||||
|     } |  | ||||||
|     Ok(info) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn get_servers(master_addr: &str) -> Result<(Duration, Vec<ServerEntry>)> { |  | ||||||
|     let master_addr: SocketAddr = master_addr.to_socket_addrs()?.next().unwrap(); |  | ||||||
|     let mut rtt = std::time::Duration::from_secs_f32(0.0); |  | ||||||
|     let mut servers = vec![]; |  | ||||||
|     let mut buf = [0; 32 * 1024]; |  | ||||||
|     let master = UdpSocket::bind("0.0.0.0:0").await?; |  | ||||||
|     master.connect(master_addr).await?; |  | ||||||
|     for n in 0..(256 / 32) { |  | ||||||
|         let data = format!("Brw={},{}\0", n * 32, (n + 1) * 32); |  | ||||||
|         let data = &encrypt(data.as_bytes()); |  | ||||||
|         let t_start = Instant::now(); |  | ||||||
|         master.send(data).await?; |  | ||||||
|         let size = master.recv(&mut buf).await?; |  | ||||||
|         rtt += t_start.elapsed(); |  | ||||||
|         let data = decrypt(&buf[..size])?; |  | ||||||
|         if data.starts_with(b"\0\0\0\0}") { |  | ||||||
|             for chunk in data[5..].chunks(6) { |  | ||||||
|                 if chunk.iter().all(|v| *v == 0) { |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 let port = u16::from_le_bytes(chunk[chunk.len() - 2..].try_into()?); |  | ||||||
|                 let addr = SocketAddr::from(([chunk[0], chunk[1], chunk[2], chunk[3]], port)); |  | ||||||
|                 let server = match query_server(addr).await { |  | ||||||
|                     Ok(server) => ServerEntry::Alive(server), |  | ||||||
|                     Err(err) => ServerEntry::Dead { |  | ||||||
|                         addr, |  | ||||||
|                         reason: err.to_string(), |  | ||||||
|                     }, |  | ||||||
|                 }; |  | ||||||
|                 servers.push(server); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     rtt = Duration::from_secs_f64(rtt.as_secs_f64() / ((256 / 32) as f64)); |  | ||||||
|     Ok((rtt, servers)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn indent_hexdump(data: &[u8], indentation: usize, label: &str) -> String { |  | ||||||
|     let mut out = String::new(); |  | ||||||
|     let indent = " ".repeat(indentation); |  | ||||||
|     out.push_str(&indent); |  | ||||||
|     out.push_str(label); |  | ||||||
|     out.push('\n'); |  | ||||||
|     for line in rhexdump::hexdump(data).lines() { |  | ||||||
|         out.push_str(&indent); |  | ||||||
|         out.push_str(line); |  | ||||||
|         out.push('\n'); |  | ||||||
|     } |  | ||||||
|     out.trim_end().to_owned() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Default, Debug)] |  | ||||||
| struct State { |  | ||||||
|     client: BTreeMap<usize, BTreeMap<u8, usize>>, |  | ||||||
|     server: BTreeMap<usize, BTreeMap<u8, usize>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl State { |  | ||||||
|     fn update_client(&mut self, data: &[u8]) { |  | ||||||
|         data.iter().enumerate().for_each(|(pos, b)| { |  | ||||||
|             *self.client.entry(pos).or_default().entry(*b).or_default() += 1; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|     fn update_server(&mut self, data: &[u8]) { |  | ||||||
|         data.iter().enumerate().for_each(|(pos, b)| { |  | ||||||
|             *self.server.entry(pos).or_default().entry(*b).or_default() += 1; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, PartialEq, Eq, Copy, Clone)] |  | ||||||
| enum Direction { |  | ||||||
|     Client, |  | ||||||
|     Server, |  | ||||||
|     Both, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug)] |  | ||||||
| enum CmdResult { |  | ||||||
|     Exit, |  | ||||||
|     Packet { |  | ||||||
|         data: Vec<u8>, |  | ||||||
|         direction: Direction, |  | ||||||
|     }, |  | ||||||
|     Fuzz { |  | ||||||
|         direction: Direction, |  | ||||||
|         start: usize, |  | ||||||
|         end: usize, |  | ||||||
|         chance: (u32, u32), |  | ||||||
|     }, |  | ||||||
|     NoFuzz, |  | ||||||
|     Log(bool), |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn handle_line( |  | ||||||
|     line: &str, |  | ||||||
|     state: &State, |  | ||||||
|     stdout: &mut SharedWriter, |  | ||||||
| ) -> Result<Option<CmdResult>> { |  | ||||||
|     use CmdResult::*; |  | ||||||
|     let cmd: Vec<&str> = line.trim().split_ascii_whitespace().collect(); |  | ||||||
|     match cmd[..] { |  | ||||||
|         ["log", "off"] => Ok(Some(Log(false))), |  | ||||||
|         ["log", "on"] => Ok(Some(Log(true))), |  | ||||||
|         ["state", pos] => { |  | ||||||
|             let pos = pos.parse()?; |  | ||||||
|             writeln!(stdout, "Client: {:?}", state.client.get(&pos))?; |  | ||||||
|             writeln!(stdout, "Server: {:?}", state.server.get(&pos))?; |  | ||||||
|             Ok(None) |  | ||||||
|         } |  | ||||||
|         [dir @ ("client" | "server"), ref args @ ..] => { |  | ||||||
|             let mut data: Vec<u8> = vec![]; |  | ||||||
|             for args in args.iter() { |  | ||||||
|                 let args = hex::decode(args)?; |  | ||||||
|                 data.extend(args); |  | ||||||
|             } |  | ||||||
|             Ok(Some(CmdResult::Packet { |  | ||||||
|                 data, |  | ||||||
|                 direction: match dir { |  | ||||||
|                     "client" => Direction::Client, |  | ||||||
|                     "server" => Direction::Server, |  | ||||||
|                     _ => unreachable!(), |  | ||||||
|                 }, |  | ||||||
|             })) |  | ||||||
|         } |  | ||||||
|         ["fuzz", dir @ ("client" | "server" | "both"), start, end, chance_num, chance_den] => { |  | ||||||
|             let direction = match dir { |  | ||||||
|                 "client" => Direction::Client, |  | ||||||
|                 "server" => Direction::Server, |  | ||||||
|                 "both" => Direction::Both, |  | ||||||
|                 _ => unreachable!(), |  | ||||||
|             }; |  | ||||||
|             let start = start.parse()?; |  | ||||||
|             let end = end.parse()?; |  | ||||||
|             if start > end { |  | ||||||
|                 bail!("Fuzz start>end"); |  | ||||||
|             } |  | ||||||
|             let res = CmdResult::Fuzz { |  | ||||||
|                 direction, |  | ||||||
|                 start, |  | ||||||
|                 end, |  | ||||||
|                 chance: (chance_num.parse()?, chance_den.parse()?), |  | ||||||
|             }; |  | ||||||
|             Ok(Some(res)) |  | ||||||
|         } |  | ||||||
|         ["fuzz", "off"] => Ok(Some(CmdResult::NoFuzz)), |  | ||||||
|         ["exit"] => Ok(Some(CmdResult::Exit)), |  | ||||||
|         [""] => Ok(None), |  | ||||||
|         _ => bail!("Unknown command: {:?}", line), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn run_proxy( |  | ||||||
|     remote_addr: &SocketAddr, |  | ||||||
|     local_addr: &SocketAddr, |  | ||||||
|     logfile: &Option<PathBuf>, |  | ||||||
| ) -> Result<()> { |  | ||||||
|     let mut print_log = false; |  | ||||||
|     let mut state = State::default(); |  | ||||||
|     let mut logfile = match logfile { |  | ||||||
|         Some(path) => Some(std::fs::File::create(path)?), |  | ||||||
|         None => None, |  | ||||||
|     }; |  | ||||||
|     let mut fuzz = None; |  | ||||||
|     let mut rng = thread_rng(); |  | ||||||
|     let mut client_addr: Option<SocketAddr> = None; |  | ||||||
|     let local = UdpSocket::bind(local_addr).await?; |  | ||||||
|     let remote = UdpSocket::bind("0.0.0.0:0").await?; |  | ||||||
|     remote.connect(remote_addr).await?; |  | ||||||
|     let mut local_buf = vec![0; 32 * 1024]; |  | ||||||
|     let mut remote_buf = vec![0; 32 * 1024]; |  | ||||||
|     println!("Proxy listening on {}", local_addr); |  | ||||||
|     let (mut rl, mut stdout) = Readline::new(format!("{}> ", remote_addr)).unwrap(); |  | ||||||
|     loop { |  | ||||||
|         tokio::select! { |  | ||||||
|             line = rl.readline().fuse() => { |  | ||||||
|                 match line { |  | ||||||
|                     Ok(line) => { |  | ||||||
|                         let line=line.trim(); |  | ||||||
|                         rl.add_history_entry(line.to_owned()); |  | ||||||
|                         match  handle_line(line, &state, &mut stdout).await { |  | ||||||
|                             Ok(Some(result)) => { |  | ||||||
|                                 match result { |  | ||||||
|                                         CmdResult::Packet{data,direction} => { |  | ||||||
|                                             let data=encrypt(&data); |  | ||||||
|                                             match direction { |  | ||||||
|                                                 Direction::Client => { |  | ||||||
|                                                     if client_addr.is_some() { |  | ||||||
|                                                         local |  | ||||||
|                                                             .send_to(&data, client_addr.unwrap()) |  | ||||||
|                                                             .await?; |  | ||||||
|                                                     } else { |  | ||||||
|                                                         writeln!(stdout,"Error: No client address")?; |  | ||||||
|                                                     } |  | ||||||
|                                                 }, |  | ||||||
|                                                 Direction::Server => { |  | ||||||
|                                                     remote.send(&data).await?; |  | ||||||
|                                                 } |  | ||||||
|                                                 Direction::Both => unreachable!() |  | ||||||
|                                             }; |  | ||||||
|                                         } |  | ||||||
|                                         CmdResult::Log(log) => { |  | ||||||
|                                             print_log=log; |  | ||||||
|                                         } |  | ||||||
|                                         CmdResult::Exit => break Ok(()), |  | ||||||
|                                         CmdResult::NoFuzz => { |  | ||||||
|                                             fuzz=None; |  | ||||||
|                                         } |  | ||||||
|                                         CmdResult::Fuzz { .. } => { |  | ||||||
|                                             fuzz=Some(result) |  | ||||||
|                                         }, |  | ||||||
|                                     } |  | ||||||
|                             }, |  | ||||||
|                             Ok(None) => (), |  | ||||||
|                             Err(msg) => { |  | ||||||
|                                 writeln!(stdout, "Error: {}", msg)?; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     Err(ReadlineError::Eof) =>{ writeln!(stdout, "Exiting...")?; break Ok(()) }, |  | ||||||
|                     Err(ReadlineError::Interrupted) => { |  | ||||||
|                         writeln!(stdout, "^C")?; |  | ||||||
|                         break Ok(()); |  | ||||||
|                     }, |  | ||||||
|                     Err(err) => { |  | ||||||
|                         writeln!(stdout, "Received err: {:?}", err)?; |  | ||||||
|                         writeln!(stdout, "Exiting...")?; |  | ||||||
|                         break Ok(()); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             local_res = local.recv_from(&mut local_buf) => { |  | ||||||
|                 let (size, addr) = local_res?; |  | ||||||
|                 client_addr.get_or_insert(addr); |  | ||||||
|                 let mut data = Packet::try_from(&local_buf[..size])?.decrypt()?; |  | ||||||
|                 state.update_client(&data); |  | ||||||
|                 if print_log { |  | ||||||
|                     writeln!(stdout,"{}", indent_hexdump(&data, 0, &format!("OUT: {}", addr)))?; |  | ||||||
|                 } |  | ||||||
|                 if let Some(lf) = logfile.as_mut() { |  | ||||||
|                     writeln!(lf, ">{:?} {} {}", addr, data.len(), hex::encode(&data))?; |  | ||||||
|                 }; |  | ||||||
|                 if let Some(CmdResult::Fuzz{direction,start,end,chance}) = fuzz { |  | ||||||
|                     if (direction==Direction::Server || direction==Direction::Both) && rng.gen_ratio(chance.0,chance.1) { |  | ||||||
|                         rng.fill(&mut data[start..end]); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 remote.send(&encrypt(&data)).await?; |  | ||||||
|             } |  | ||||||
|             remote_res = remote.recv_from(&mut remote_buf) => { |  | ||||||
|                 let (size, addr) = remote_res?; |  | ||||||
|                 let mut data = Packet::try_from(&remote_buf[..size])?.decrypt()?; |  | ||||||
|                 state.update_server(&data); |  | ||||||
|                 if print_log { |  | ||||||
|                     writeln!(stdout,"\r{}", indent_hexdump(&data, 5, &format!("IN: {}", addr)))?; |  | ||||||
|                 } |  | ||||||
|                 if let Some(lf) = logfile.as_mut() { |  | ||||||
|                     writeln!(lf, "<{:?} {} {}", addr, data.len(), hex::encode(&data))?; |  | ||||||
|                 }; |  | ||||||
|                 if client_addr.is_some() { |  | ||||||
|                     if let Some(CmdResult::Fuzz{direction,start,end,chance}) = &fuzz { |  | ||||||
|                         if (*direction==Direction::Client || *direction==Direction::Both) && rng.gen_ratio(chance.0,chance.1) { |  | ||||||
|                             rng.fill(&mut data[*start..*end]); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     local |  | ||||||
|                         .send_to(&encrypt(&data), client_addr.unwrap()) |  | ||||||
|                         .await?; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn send_master_cmd(sock: &UdpSocket, cmd: &str) -> Result<Vec<u8>> { |  | ||||||
|     let mut buf = [0; 32 * 1024]; |  | ||||||
|     let mut data: Vec<u8> = cmd.as_bytes().to_vec(); |  | ||||||
|     data.push(0); |  | ||||||
|     let data = &encrypt(&data); |  | ||||||
|     sock.send(data).await?; |  | ||||||
|     let size = recv_from_timeout(sock, &mut buf, 5.0).await?.0; |  | ||||||
|     decrypt(&buf[..size]) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn run_master_shell(master_addr: &str) -> Result<()> { |  | ||||||
|     let master = UdpSocket::bind("0.0.0.0:0").await?; |  | ||||||
|     master.connect(master_addr).await?; |  | ||||||
|     let (mut rl, mut stdout) = Readline::new(format!("{}> ", master_addr)).unwrap(); |  | ||||||
|     loop { |  | ||||||
|         tokio::select! { |  | ||||||
|             line = rl.readline().fuse() => { |  | ||||||
|                 match line { |  | ||||||
|                     Ok(line) => { |  | ||||||
|                         let line=line.trim(); |  | ||||||
|                         rl.add_history_entry(line.to_owned()); |  | ||||||
|                         writeln!(stdout,"[CMD] {line}")?; |  | ||||||
|                         match send_master_cmd(&master,line).await { |  | ||||||
|                             Ok(data) => writeln!(stdout,"{}",hexdump(&data))?, |  | ||||||
|                             Err(e) => writeln!(stdout,"Error: {e}")? |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     Err(ReadlineError::Eof) =>{ writeln!(stdout, "Exiting...")?; break Ok(()) }, |  | ||||||
|                     Err(ReadlineError::Interrupted) => { |  | ||||||
|                         writeln!(stdout, "^C")?; |  | ||||||
|                         break Ok(()); |  | ||||||
|                     }, |  | ||||||
|                     Err(err) => { |  | ||||||
|                         writeln!(stdout, "Receive error: {err}")?; |  | ||||||
|                         break Err(err.into()); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Parser, Debug)] |  | ||||||
| #[clap(author, version, about, long_about = None)] |  | ||||||
| struct Args { |  | ||||||
|     /// Server to connect to (if unspecified will query the master server)
 |  | ||||||
|     server: Option<SocketAddr>, |  | ||||||
|     /// Only list servers without starting proxy
 |  | ||||||
|     #[clap(short, long, action)] |  | ||||||
|     list: bool, |  | ||||||
|     /// Local Address to bind to
 |  | ||||||
|     #[clap(short,long, default_value_t = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 28086))] |  | ||||||
|     addr: SocketAddr, |  | ||||||
|     /// Master server to query for running games
 |  | ||||||
|     #[clap(short, long, default_value = "scrapland.mercurysteam.com:5000")] |  | ||||||
|     master: String, |  | ||||||
|     /// Path of file to log decrypted packets to
 |  | ||||||
|     #[clap(short = 'f', long)] |  | ||||||
|     logfile: Option<PathBuf>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[tokio::main] |  | ||||||
| async fn main() -> Result<()> { |  | ||||||
|     let args = Args::parse(); |  | ||||||
|     if args.list && args.server.is_some() { |  | ||||||
|         let addr = args.server.unwrap(); |  | ||||||
|         let server = match query_server(addr).await { |  | ||||||
|             Ok(server) => ServerEntry::Alive(server), |  | ||||||
|             Err(msg) => ServerEntry::Dead { |  | ||||||
|                 addr, |  | ||||||
|                 reason: msg.to_string(), |  | ||||||
|             }, |  | ||||||
|         }; |  | ||||||
|         println!("{}", server); |  | ||||||
|         return Ok(()); |  | ||||||
|     } |  | ||||||
|     if let Some(server) = args.server { |  | ||||||
|         run_proxy(&server, &args.addr, &args.logfile).await?; |  | ||||||
|         return Ok(()); |  | ||||||
|     } |  | ||||||
|     loop { |  | ||||||
|         let (rtt, servers) = get_servers(&args.master).await?; |  | ||||||
|         println!("Master RTT: {:?}", rtt); |  | ||||||
|         if args.list { |  | ||||||
|             for server in servers { |  | ||||||
|                 println!("{}", server); |  | ||||||
|             } |  | ||||||
|             return Ok(()); |  | ||||||
|         } |  | ||||||
|         let selection = Select::with_theme(&ColorfulTheme::default()) |  | ||||||
|             .items(&servers) |  | ||||||
|             .with_prompt("Select server (press Esc to drop into master server command shell)") |  | ||||||
|             .interact_opt()? |  | ||||||
|             .map(|v| &servers[v]); |  | ||||||
|         match selection { |  | ||||||
|             Some(ServerEntry::Dead { addr, reason }) => { |  | ||||||
|                 eprintln!("{:?} returned an error: {}", addr, reason) |  | ||||||
|             } |  | ||||||
|             Some(ServerEntry::Alive(srv)) => { |  | ||||||
|                 return run_proxy(&srv.addr, &args.addr, &args.logfile).await; |  | ||||||
|             } |  | ||||||
|             None => { |  | ||||||
|                 return run_master_shell(&args.master).await; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,151 +0,0 @@ | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::error::Error; |  | ||||||
| 
 |  | ||||||
| use crate::hex_ii::hex_ii_dump; |  | ||||||
| use crate::ServerFlags; |  | ||||||
| use binrw::BinReaderExt; |  | ||||||
| use binrw::{binread, BinRead, NullString}; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 00000000: 7f 4c 00 00 06 ba ce 01 01 06 63 61 63 6f 74 61 | .L........cacota |  | ||||||
| 00000010: 10 5b 42 4a 5a 5d 20 45 61 72 74 68 6e 75 6b 65 | .[BJZ].Earthnuke |  | ||||||
| 00000020: 72 06 53 50 6f 6c 69 31 37 00 08 50 5f 50 6f 6c | r.SPoli17..P_Pol |  | ||||||
| 00000030: 69 63 65 06 4d 50 4f 4c 49 31 00 00 00 0d 30 2c | ice.MPOLI1....0, |  | ||||||
| 00000040: 30 2c 30 2c 31 2c 30 2c 30 2c 31 00 00 00 00    | 0,0,1,0,0,1.... |  | ||||||
| 
 |  | ||||||
| 00000000: 7f 49 00 00 06 ba ce 01 01 06 63 61 63 6f 74 61 | .I........cacota |  | ||||||
| 00000010: 0e 55 6e 6e 61 6d 65 64 20 50 6c 61 79 65 72 07 | .Unnamed.Player. |  | ||||||
| 00000020: 53 42 65 74 74 79 31 50 00 07 50 5f 42 65 74 74 | SBetty1P..P_Bett |  | ||||||
| 00000030: 79 07 4d 42 65 74 74 79 31 00 00 00 0b 31 2c 31 | y.MBetty1....1,1 |  | ||||||
| 00000040: 2c 30 2c 31 2c 33 2c 30 00 00 00 00             | ,0,1,3,0.... |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone, BinRead)] |  | ||||||
| #[br(big)] |  | ||||||
| #[br(magic = b"\xba\xce")] |  | ||||||
| struct ServerInfoJoin { |  | ||||||
|     #[br(map = |v: (u8,u8)| format!("{}.{}",v.0,v.1))] |  | ||||||
|     version: String, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| struct Data { |  | ||||||
|     player_id: u32, |  | ||||||
|     num_vals: u32, |  | ||||||
|     pos: [f32; 3], |  | ||||||
|     player_index: u32, |  | ||||||
|     rtt: u32, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[binread] |  | ||||||
| #[br(big)] |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| enum PacketData { |  | ||||||
|     #[br(magic = b"\x7f")] |  | ||||||
|     PlayerJoin { |  | ||||||
|         data_len: u8, |  | ||||||
|         _1: u8, |  | ||||||
|         cur_players: u8, |  | ||||||
|         max_players: u8, |  | ||||||
|         info: ServerInfoJoin, |  | ||||||
|         #[br(temp)] |  | ||||||
|         pw_len: u8, |  | ||||||
|         #[br(count = pw_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         password: String, |  | ||||||
|         #[br(temp)] |  | ||||||
|         player_name_len: u8, |  | ||||||
|         #[br(count = player_name_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         player_name: String, |  | ||||||
|         #[br(temp)] |  | ||||||
|         ship_model_len: u8, |  | ||||||
|         #[br(count = ship_model_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         ship_model: String, |  | ||||||
|         #[br(little)] |  | ||||||
|         max_health: u16, |  | ||||||
|         #[br(temp)] |  | ||||||
|         pilot_model_len: u8, |  | ||||||
|         #[br(count = pilot_model_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         pilot_model: String, |  | ||||||
|         #[br(temp)] |  | ||||||
|         engine_model_r_len: u8, |  | ||||||
|         #[br(count = engine_model_r_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         engine_model_r: String, |  | ||||||
|         #[br(temp)] |  | ||||||
|         engine_model_l_len: u8, |  | ||||||
|         #[br(count = engine_model_r_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         engine_model_l: String, |  | ||||||
|         _2: u16, |  | ||||||
|         #[br(temp)] |  | ||||||
|         loadout_len: u8, |  | ||||||
|         #[br(count = loadout_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         loadout: String, |  | ||||||
|         team_number: u16, |  | ||||||
|         padding: [u8; 2], |  | ||||||
|     }, |  | ||||||
|     #[br(magic = b"\x80\x15")] |  | ||||||
|     MapInfo { |  | ||||||
|         #[br(temp)] |  | ||||||
|         map_len: u32, |  | ||||||
|         #[br(count = map_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         map: String, |  | ||||||
|         #[br(temp)] |  | ||||||
|         mode_len: u8, |  | ||||||
|         #[br(count = mode_len, map = |bytes: Vec<u8>| String::from_utf8_lossy(&bytes).into_owned())] |  | ||||||
|         mode: String, |  | ||||||
|         _2: u16, |  | ||||||
|         item_count: u8, |  | ||||||
|         // _3: u32,
 |  | ||||||
|         // #[br(count = item_count)]
 |  | ||||||
|         // items: Vec<[u8;0x11]>
 |  | ||||||
|     }, |  | ||||||
|     #[br(magic = b"\xba\xce")] |  | ||||||
|     ServerInfo { |  | ||||||
|         #[br(map = |v: (u8,u8)| format!("{}.{}",v.1,v.0))] |  | ||||||
|         version: String, |  | ||||||
|         port: u16, |  | ||||||
|         max_players: u16, |  | ||||||
|         cur_players: u16, |  | ||||||
|         #[br(map = u8::into)] |  | ||||||
|         flags: ServerFlags, |  | ||||||
|         #[br(pad_size_to(0x20), map=|s: NullString| s.to_string())] |  | ||||||
|         name: String, |  | ||||||
|         #[br(pad_size_to(0x10), map=|s: NullString| s.to_string())] |  | ||||||
|         mode: String, |  | ||||||
|         #[br(pad_size_to(0x20), map=|s: NullString| s.to_string())] |  | ||||||
|         map: String, |  | ||||||
|         _pad: u8, |  | ||||||
|     }, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn parse(data: &[u8]) -> Result<(PacketData, Vec<u8>), Box<dyn Error>> { |  | ||||||
|     use std::io::Cursor; |  | ||||||
|     let mut rdr = Cursor::new(data); |  | ||||||
|     let pkt: PacketData = rdr.read_le()?; |  | ||||||
|     let rest = data[rdr.position() as usize..].to_vec(); |  | ||||||
|     println!("{}", rhexdump::hexdump(data)); |  | ||||||
|     Ok((pkt, rest)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[test] |  | ||||||
| fn test_parser() { |  | ||||||
|     let log = include_str!("../test_.log").lines(); |  | ||||||
|     let mut hm = HashMap::new(); |  | ||||||
|     for line in log { |  | ||||||
|         let data = line.split_ascii_whitespace().nth(1).unwrap(); |  | ||||||
|         let data = hex::decode(data).unwrap(); |  | ||||||
|         *hm.entry(data[0..1].to_vec()).or_insert(0usize) += 1; |  | ||||||
|         match parse(&data) { |  | ||||||
|             Ok((pkt, rest)) => { |  | ||||||
|                 println!("{:#x?}", pkt); |  | ||||||
|             } |  | ||||||
|             Err(e) => (), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     let mut hm: Vec<(_, _)> = hm.iter().collect(); |  | ||||||
|     hm.sort_by_key(|(_, v)| *v); |  | ||||||
|     for (k, v) in hm { |  | ||||||
|         let k = k.iter().map(|v| format!("{:02x}", v)).collect::<String>(); |  | ||||||
|         println!("{} {}", k, v); |  | ||||||
|     } |  | ||||||
|     // println!("{:#x?}",parse("8015000000094c6576656c732f465a08466c616748756e7400000100000000000000000000000000000000000004105feb0006003e1125f3bc1300000019007e9dfa0404d5f9003f00000000000000000000"));
 |  | ||||||
|     // println!("{:#x?}",parse("8015000000094c6576656c732f465a08466c616748756e7400002000000000000000000000000000000000000004105feb0006003e1125f3bc1300000019007e9dfa0404d5f9003f000000000000000000001f020b0376a8e2475b6e5b467c1e99461e020903982d14c5ec79cb45b2ee96471d020e03b29dbc46caa433464a28a0c71c020603aa80514658b8ab458db025c71b020803ce492f4658b8ab4514d320c71a02070344532f4658b8ab4587cf16c7190205031b3a0d4658b8ab459eaf25c7180206030ac34c4669e1fd469891ca47170208032e8c2a4669e1fd465500cd4716020703a4952a4669e1fd461b02d247150205037b7c084669e1fd460f92ca4714020603da6b7ec714aa3746b77c5a4713020803c87c83c714aa3746305a5f47120207039a7b83c714aa3746bd5d694711020503bfbe87c714aa3746a67d5a4710020803c5c719474ad5d445a7b3d2c60f0206037c5522474ad5d4459a6edcc60e02070323ca19474ad5d4458dacbec60d020503d84311474ad5d445bb6cdcc60c020603a9b16b47d52d974602dd15470b020803f2236347d52d97467bba1a470a02070350266347d52d974608be24470902050305a05a47d52d9746f1dd1547080206031f4066c6384b9c46955bd345070208037e3b84c6384b9c466147fa4506020703c33684c6384b9c46e431254605020503574395c6384b9c461063d34504020603ba349bc77a60294640f387c103020803957b9fc77a602946658f994402020703677a9fc77a60294680006d45010205038cbda3c77a602946807880c1"));
 |  | ||||||
| } |  | ||||||
							
								
								
									
										177
									
								
								tools/remaster/scrap_parse/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										177
									
								
								tools/remaster/scrap_parse/.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,177 +0,0 @@ | ||||||
| # Generated by Cargo |  | ||||||
| # will have compiled files and executables |  | ||||||
| debug/ |  | ||||||
| target/ |  | ||||||
| 
 |  | ||||||
| # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries |  | ||||||
| # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html |  | ||||||
| Cargo.lock |  | ||||||
| 
 |  | ||||||
| # These are backup files generated by rustfmt |  | ||||||
| **/*.rs.bk |  | ||||||
| 
 |  | ||||||
| # MSVC Windows builds of rustc generate these, which store debugging information |  | ||||||
| *.pdb |  | ||||||
| 
 |  | ||||||
| # Byte-compiled / optimized / DLL files |  | ||||||
| __pycache__/ |  | ||||||
| *.py[cod] |  | ||||||
| *$py.class |  | ||||||
| 
 |  | ||||||
| # C extensions |  | ||||||
| *.so |  | ||||||
| 
 |  | ||||||
| # Distribution / packaging |  | ||||||
| .Python |  | ||||||
| build/ |  | ||||||
| develop-eggs/ |  | ||||||
| dist/ |  | ||||||
| downloads/ |  | ||||||
| eggs/ |  | ||||||
| .eggs/ |  | ||||||
| lib/ |  | ||||||
| lib64/ |  | ||||||
| parts/ |  | ||||||
| sdist/ |  | ||||||
| var/ |  | ||||||
| wheels/ |  | ||||||
| share/python-wheels/ |  | ||||||
| *.egg-info/ |  | ||||||
| .installed.cfg |  | ||||||
| *.egg |  | ||||||
| MANIFEST |  | ||||||
| 
 |  | ||||||
| # PyInstaller |  | ||||||
| #  Usually these files are written by a python script from a template |  | ||||||
| #  before PyInstaller builds the exe, so as to inject date/other infos into it. |  | ||||||
| *.manifest |  | ||||||
| *.spec |  | ||||||
| 
 |  | ||||||
| # Installer logs |  | ||||||
| pip-log.txt |  | ||||||
| pip-delete-this-directory.txt |  | ||||||
| 
 |  | ||||||
| # Unit test / coverage reports |  | ||||||
| htmlcov/ |  | ||||||
| .tox/ |  | ||||||
| .nox/ |  | ||||||
| .coverage |  | ||||||
| .coverage.* |  | ||||||
| .cache |  | ||||||
| nosetests.xml |  | ||||||
| coverage.xml |  | ||||||
| *.cover |  | ||||||
| *.py,cover |  | ||||||
| .hypothesis/ |  | ||||||
| .pytest_cache/ |  | ||||||
| cover/ |  | ||||||
| 
 |  | ||||||
| # Translations |  | ||||||
| *.mo |  | ||||||
| *.pot |  | ||||||
| 
 |  | ||||||
| # Django stuff: |  | ||||||
| *.log |  | ||||||
| local_settings.py |  | ||||||
| db.sqlite3 |  | ||||||
| db.sqlite3-journal |  | ||||||
| 
 |  | ||||||
| # Flask stuff: |  | ||||||
| instance/ |  | ||||||
| .webassets-cache |  | ||||||
| 
 |  | ||||||
| # Scrapy stuff: |  | ||||||
| .scrapy |  | ||||||
| 
 |  | ||||||
| # Sphinx documentation |  | ||||||
| docs/_build/ |  | ||||||
| 
 |  | ||||||
| # PyBuilder |  | ||||||
| .pybuilder/ |  | ||||||
| target/ |  | ||||||
| 
 |  | ||||||
| # Jupyter Notebook |  | ||||||
| .ipynb_checkpoints |  | ||||||
| 
 |  | ||||||
| # IPython |  | ||||||
| profile_default/ |  | ||||||
| ipython_config.py |  | ||||||
| 
 |  | ||||||
| # pyenv |  | ||||||
| #   For a library or package, you might want to ignore these files since the code is |  | ||||||
| #   intended to run in multiple environments; otherwise, check them in: |  | ||||||
| # .python-version |  | ||||||
| 
 |  | ||||||
| # pipenv |  | ||||||
| #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. |  | ||||||
| #   However, in case of collaboration, if having platform-specific dependencies or dependencies |  | ||||||
| #   having no cross-platform support, pipenv may install dependencies that don't work, or not |  | ||||||
| #   install all needed dependencies. |  | ||||||
| #Pipfile.lock |  | ||||||
| 
 |  | ||||||
| # poetry |  | ||||||
| #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. |  | ||||||
| #   This is especially recommended for binary packages to ensure reproducibility, and is more |  | ||||||
| #   commonly ignored for libraries. |  | ||||||
| #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control |  | ||||||
| #poetry.lock |  | ||||||
| 
 |  | ||||||
| # pdm |  | ||||||
| #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. |  | ||||||
| #pdm.lock |  | ||||||
| #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it |  | ||||||
| #   in version control. |  | ||||||
| #   https://pdm.fming.dev/#use-with-ide |  | ||||||
| .pdm.toml |  | ||||||
| 
 |  | ||||||
| # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm |  | ||||||
| __pypackages__/ |  | ||||||
| 
 |  | ||||||
| # Celery stuff |  | ||||||
| celerybeat-schedule |  | ||||||
| celerybeat.pid |  | ||||||
| 
 |  | ||||||
| # SageMath parsed files |  | ||||||
| *.sage.py |  | ||||||
| 
 |  | ||||||
| # Environments |  | ||||||
| .env |  | ||||||
| .venv |  | ||||||
| env/ |  | ||||||
| venv/ |  | ||||||
| ENV/ |  | ||||||
| env.bak/ |  | ||||||
| venv.bak/ |  | ||||||
| 
 |  | ||||||
| # Spyder project settings |  | ||||||
| .spyderproject |  | ||||||
| .spyproject |  | ||||||
| 
 |  | ||||||
| # Rope project settings |  | ||||||
| .ropeproject |  | ||||||
| 
 |  | ||||||
| # mkdocs documentation |  | ||||||
| /site |  | ||||||
| 
 |  | ||||||
| # mypy |  | ||||||
| .mypy_cache/ |  | ||||||
| .dmypy.json |  | ||||||
| dmypy.json |  | ||||||
| 
 |  | ||||||
| # Pyre type checker |  | ||||||
| .pyre/ |  | ||||||
| 
 |  | ||||||
| # pytype static type analyzer |  | ||||||
| .pytype/ |  | ||||||
| 
 |  | ||||||
| # Cython debug symbols |  | ||||||
| cython_debug/ |  | ||||||
| 
 |  | ||||||
| # PyCharm |  | ||||||
| #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can |  | ||||||
| #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore |  | ||||||
| #  and can be added to the global gitignore or merged into this file.  For a more nuclear |  | ||||||
| #  option (not recommended) you can uncomment the following to ignore the entire idea folder. |  | ||||||
| #.idea/ |  | ||||||
| 
 |  | ||||||
| *.pkl.gz |  | ||||||
							
								
								
									
										1275
									
								
								tools/remaster/scrap_parse/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1275
									
								
								tools/remaster/scrap_parse/Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,26 +0,0 @@ | ||||||
| [package] |  | ||||||
| name = "scrap_parse" |  | ||||||
| version = "0.1.0" |  | ||||||
| edition = "2021" |  | ||||||
| 
 |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |  | ||||||
| 
 |  | ||||||
| [dependencies] |  | ||||||
| anyhow = "1.0.69" |  | ||||||
| binrw = "0.11.1" |  | ||||||
| chrono = { version = "0.4.23", features = ["serde"] } |  | ||||||
| # chrono-humanize = "0.2.2" |  | ||||||
| clap = { version = "4.1.6", features = ["derive"] } |  | ||||||
| configparser = { version = "3.0.2", features = ["indexmap"] } |  | ||||||
| flate2 = "1.0.25" |  | ||||||
| fs-err = "2.9.0" |  | ||||||
| indexmap = { version = "1.9.2", features = ["serde"] } |  | ||||||
| # memmap2 = "0.5.10" |  | ||||||
| modular-bitfield = "0.11.2" |  | ||||||
| rhexdump = "0.1.1" |  | ||||||
| serde = { version = "1.0.152", features = ["derive"] } |  | ||||||
| serde-pickle = "1.1.1" |  | ||||||
| serde_json = { version = "1.0.95", features = ["preserve_order", "unbounded_depth"] } |  | ||||||
| steamlocate = "1.1.0" |  | ||||||
| walkdir = "2.3.3" |  | ||||||
| obj = "0.10.2" |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| import pickle |  | ||||||
| import subprocess as SP |  | ||||||
| 
 |  | ||||||
| from . import packed_browser |  | ||||||
| from . import level_import |  | ||||||
| 
 |  | ||||||
| def scrap_bridge(*cmd): |  | ||||||
|     cmd=["scrap_parse",*cmd] |  | ||||||
|     proc=SP.Popen(cmd,stderr=None,stdin=None,stdout=SP.PIPE,shell=True,text=False) |  | ||||||
|     stdout,stderr=proc.communicate() |  | ||||||
|     code=proc.wait() |  | ||||||
|     if code: |  | ||||||
|         raise RuntimeError(str(stderr,"utf8")) |  | ||||||
|     return pickle.loads(stdout) |  | ||||||
| 
 |  | ||||||
| def register(): |  | ||||||
|     packed_browser.register() |  | ||||||
|     level_import.regiser() |  | ||||||
| 
 |  | ||||||
| def unregister(): |  | ||||||
|     packed_browser.unregister() |  | ||||||
|     level_import.unregister() |  | ||||||
| 
 |  | ||||||
|  | @ -1,510 +0,0 @@ | ||||||
| import bpy |  | ||||||
| import sys |  | ||||||
| import os |  | ||||||
| import re |  | ||||||
| import gzip |  | ||||||
| import pickle |  | ||||||
| import argparse |  | ||||||
| from glob import glob |  | ||||||
| from mathutils import Vector |  | ||||||
| from pathlib import Path |  | ||||||
| import numpy as np |  | ||||||
| import itertools as ITT |  | ||||||
| from pprint import pprint |  | ||||||
| # from .. import scrap_bridge |  | ||||||
| import bmesh |  | ||||||
| from bpy.props import StringProperty, BoolProperty |  | ||||||
| from bpy_extras.io_utils import ImportHelper |  | ||||||
| from bpy.types import Operator |  | ||||||
| 
 |  | ||||||
| cmdline = None |  | ||||||
| if "--" in sys.argv: |  | ||||||
|     args = sys.argv[sys.argv.index("--") + 1 :] |  | ||||||
|     parser = argparse.ArgumentParser() |  | ||||||
|     parser.add_argument("--save", action="store_true") |  | ||||||
|     parser.add_argument("file_list", nargs="+") |  | ||||||
|     cmdline = parser.parse_args(args) |  | ||||||
| 
 |  | ||||||
| class ScrapImporter(object): |  | ||||||
|     def __init__(self, options): |  | ||||||
|         self.unhandled = set() |  | ||||||
|         filepath = options.pop("filepath") |  | ||||||
|         self.options = options |  | ||||||
|         self.model_scale = 1000.0 |  | ||||||
|         self.spawn_pos = {} |  | ||||||
|         self.objects = {} |  | ||||||
|         # print("Loading", filepath) |  | ||||||
|         # scrapland_path=scrap_bridge("find-scrapland") |  | ||||||
|         # print(scrapland_path) |  | ||||||
|         # packed_data=scrap_bridge("parse-packed",scrapland_path) |  | ||||||
|         # print(packed_data) |  | ||||||
|         # get_output(["scrap_parse","parse-file","--stdout",scrapland_path,"levels/temple"]) |  | ||||||
|         # raise NotImplementedError() |  | ||||||
|         with gzip.open(filepath, "rb") as fh: |  | ||||||
|             data = pickle.load(fh) |  | ||||||
|         self.path = data.pop("path") |  | ||||||
|         self.root = data.pop("root") |  | ||||||
|         self.config = data.pop("config") |  | ||||||
|         self.dummies = data.pop("dummies")["dummies"] |  | ||||||
|         self.moredummies = data.pop("moredummies") |  | ||||||
|         self.emi = data.pop("emi") |  | ||||||
|         self.sm3 = data.pop("sm3") |  | ||||||
| 
 |  | ||||||
|     def make_empty(self, name, pos, rot=None): |  | ||||||
|         empty = bpy.data.objects.new(name, None) |  | ||||||
|         empty.empty_display_type = "PLAIN_AXES" |  | ||||||
|         empty.empty_display_size = 0.1 |  | ||||||
|         empty.location = Vector(pos).xzy / self.model_scale |  | ||||||
|         if rot: |  | ||||||
|             empty.rotation_euler = Vector(rot).xzy |  | ||||||
|         empty.name = name |  | ||||||
|         empty.show_name = True |  | ||||||
|         bpy.context.scene.collection.objects.link(empty) |  | ||||||
| 
 |  | ||||||
|     def create_tracks(self): |  | ||||||
|         points = {} |  | ||||||
|         for dummy in self.dummies: |  | ||||||
|             if dummy["name"].startswith("DM_Track"): |  | ||||||
|                 try: |  | ||||||
|                     _, name, idx = dummy["name"].split("_") |  | ||||||
|                 except ValueError: |  | ||||||
|                     continue |  | ||||||
|                 pos = Vector(dummy["pos"]).xzy / self.model_scale |  | ||||||
|                 points.setdefault(name, []).append((int(idx), pos)) |  | ||||||
|         self.dummies=[d for d in self.dummies if not d["name"].startswith("DM_Track")] |  | ||||||
|         for name, points in points.items(): |  | ||||||
|             crv = bpy.data.curves.new(name, "CURVE") |  | ||||||
|             crv.dimensions = "3D" |  | ||||||
|             crv.path_duration = ( |  | ||||||
|                 (bpy.context.scene.frame_end - bpy.context.scene.frame_start) + 1 |  | ||||||
|             ) |  | ||||||
|             crv.twist_mode = "Z_UP" |  | ||||||
|             crv.twist_smooth = 1.0 |  | ||||||
|             spline = crv.splines.new(type="NURBS") |  | ||||||
|             spline.points.add(len(points) - 1) |  | ||||||
|             spline.use_endpoint_u = True |  | ||||||
|             spline.use_cyclic_u = True |  | ||||||
|             spline.use_endpoint_v = True |  | ||||||
|             spline.use_cyclic_v = True |  | ||||||
|             points.sort() |  | ||||||
|             for p, (_, co) in zip(spline.points, points): |  | ||||||
|                 p.co = list(co) + [1.0] |  | ||||||
|             obj = bpy.data.objects.new(name, crv) |  | ||||||
|             bpy.context.scene.collection.objects.link(obj) |  | ||||||
| 
 |  | ||||||
|     def create_dummies(self): |  | ||||||
|         for dummy in self.dummies: |  | ||||||
|             self.make_empty(dummy["name"], dummy["pos"], dummy["rot"]) |  | ||||||
|             if dummy["name"].startswith("DM_Player_Spawn"): |  | ||||||
|                 self.spawn_pos[dummy["name"]] = dummy["pos"] |  | ||||||
|         for name, dummy in self.moredummies.items(): |  | ||||||
|             if not "Pos" in dummy: |  | ||||||
|                 continue |  | ||||||
|             pos = list(float(v) for v in dummy["Pos"]) |  | ||||||
|             rot = [0, 0, 0] |  | ||||||
|             if "Rot" in dummy: |  | ||||||
|                 rot = list(float(v) for v in dummy["Rot"]) |  | ||||||
|             self.make_empty(name, pos, rot) |  | ||||||
| 
 |  | ||||||
|     def add_light(self, name, node): |  | ||||||
|         light = bpy.data.lights.new(name, "POINT") |  | ||||||
|         light.energy = 100 |  | ||||||
|         r = node["unk_10"][0] / 255  # *(node['unk_10'][3]/255) |  | ||||||
|         g = node["unk_10"][1] / 255  # *(node['unk_10'][3]/255) |  | ||||||
|         b = node["unk_10"][2] / 255  # *(node['unk_10'][3]/255) |  | ||||||
|         light.color = (r, g, b) |  | ||||||
|         light = bpy.data.objects.new(name, light) |  | ||||||
|         light.location = Vector(node["pos"]).xzy / self.model_scale |  | ||||||
|         light.rotation_euler = Vector(node["rot"]).xzy |  | ||||||
|         bpy.context.scene.collection.objects.link(light) |  | ||||||
| 
 |  | ||||||
|     def create_nodes(self): |  | ||||||
|         for node in self.sm3["scene"].get("nodes",[]): |  | ||||||
|             node_name = node["name"] |  | ||||||
|             node = node.get("content", {}) |  | ||||||
|             if not node: |  | ||||||
|                 continue |  | ||||||
|             if node["type"] == "Camera": |  | ||||||
|                 print(f"CAM:{node_name}") |  | ||||||
|                 pprint(node) |  | ||||||
|             elif node["type"] == "Light": |  | ||||||
|                 print(f"LIGHT:{node_name}") |  | ||||||
|                 # self.add_light(node_name, node) |  | ||||||
| 
 |  | ||||||
|     def run(self): |  | ||||||
|         self.import_emi() |  | ||||||
|         self.join_objects(self.options['merge_objects']) |  | ||||||
|         if self.options['create_tracks']: |  | ||||||
|             self.create_tracks() |  | ||||||
|         if self.options['create_dummies']: |  | ||||||
|             self.create_dummies() |  | ||||||
|         if self.options['create_nodes']: |  | ||||||
|             self.create_nodes() |  | ||||||
|         if self.unhandled: |  | ||||||
|             print("Unhandled textures:",self.unhandled) |  | ||||||
| 
 |  | ||||||
|     def join_objects(self, do_merge=False): |  | ||||||
|         bpy.ops.object.select_all(action="DESELECT") |  | ||||||
|         ctx = {} |  | ||||||
|         for name, objs in self.objects.items(): |  | ||||||
|             if len(objs) <= 1: |  | ||||||
|                 continue |  | ||||||
|             ctx = { |  | ||||||
|                 "active_object": objs[0], |  | ||||||
|                 "object": objs[0], |  | ||||||
|                 "selected_objects": objs, |  | ||||||
|                 "selected_editable_objects": objs, |  | ||||||
|             } |  | ||||||
|             with bpy.context.temp_override(**ctx): |  | ||||||
|                 if do_merge: |  | ||||||
|                     bpy.ops.object.join() |  | ||||||
|                     objs[0].name=name |  | ||||||
|                 else: |  | ||||||
|                     coll=bpy.data.collections.new(name) |  | ||||||
|                     bpy.context.scene.collection.children.link(coll) |  | ||||||
|                     for n,obj in enumerate(objs): |  | ||||||
|                         obj.name=f"{name}_{n:04}" |  | ||||||
|                         coll.objects.link(obj) |  | ||||||
|             bpy.ops.object.select_all(action="DESELECT") |  | ||||||
| 
 |  | ||||||
|     def import_emi(self): |  | ||||||
|         mats = {0: None} |  | ||||||
|         maps = {0: None} |  | ||||||
|         for mat in self.emi["materials"]: |  | ||||||
|             mats[mat[0]] = mat[1] |  | ||||||
|         for tex_map in self.emi["maps"]: |  | ||||||
|             maps[tex_map["key"]] = tex_map["data"] |  | ||||||
|         for tri in self.emi["tri"]: |  | ||||||
|             name = tri["name"] |  | ||||||
|             if tri["data"]: |  | ||||||
|                 tris = tri["data"]["tris"] |  | ||||||
|                 for n, verts in enumerate( |  | ||||||
|                     [tri["data"]["verts_1"], tri["data"]["verts_2"]], 1 |  | ||||||
|                 ): |  | ||||||
|                     if not (tris and verts): |  | ||||||
|                         continue |  | ||||||
|                     self.create_mesh( |  | ||||||
|                         name=f"{name}_{n}", |  | ||||||
|                         verts=verts, |  | ||||||
|                         faces=tris, |  | ||||||
|                         m_map=(tri["data"]["map_key"], maps[tri["data"]["map_key"]]), |  | ||||||
|                         m_mat=(tri["data"]["mat_key"], mats[tri["data"]["mat_key"]]), |  | ||||||
|                     ) |  | ||||||
| 
 |  | ||||||
|     def normalize_path(self, path): |  | ||||||
|         return path.replace("\\", os.sep).replace("/", os.sep) |  | ||||||
| 
 |  | ||||||
|     def resolve_path(self, path): |  | ||||||
|         file_extensions = [".png", ".bmp", ".dds", ".tga", ".alpha.dds"] |  | ||||||
|         root_path = Path(self.normalize_path(self.root).lower()) |  | ||||||
|         start_folder = Path(self.normalize_path(self.path).lower()).parent |  | ||||||
|         try: |  | ||||||
|             texture_path = Path(self.config["model"]["texturepath"] + "/") |  | ||||||
|         except KeyError: |  | ||||||
|             texture_path = None |  | ||||||
|         path = Path(path.replace("/", os.sep).lower()) |  | ||||||
|         if texture_path: |  | ||||||
|             folders = ITT.chain( |  | ||||||
|                 [start_folder], |  | ||||||
|                 start_folder.parents, |  | ||||||
|                 [texture_path], |  | ||||||
|                 texture_path.parents, |  | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             folders = ITT.chain([start_folder], start_folder.parents) |  | ||||||
|         folders=list(folders) |  | ||||||
|         print(f"Looking for {path} in {folders}") |  | ||||||
|         for folder in folders: |  | ||||||
|             for suffix in file_extensions: |  | ||||||
|                 for dds in [".", "dds"]: |  | ||||||
|                     resolved_path = ( |  | ||||||
|                         root_path / folder / path.parent / dds / path.name |  | ||||||
|                     ).with_suffix(suffix) |  | ||||||
|                     if resolved_path.exists(): |  | ||||||
|                         return str(resolved_path) |  | ||||||
|         print(f"Failed to resolve {path}") |  | ||||||
|         return None |  | ||||||
| 
 |  | ||||||
|     def get_input(self, node, name, dtype): |  | ||||||
|         return list(filter(lambda i: (i.type, i.name) == (dtype, name), node.inputs)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     def build_material(self, mat_key, mat_def, map_def): |  | ||||||
|         mat_props = dict(m.groups() for m in re.finditer(r"\(\+(\w+)(?::(\w*))?\)",mat_key)) |  | ||||||
|         for k,v in mat_props.items(): |  | ||||||
|             mat_props[k]=v or True |  | ||||||
|         skip_names = ["entorno", "noise_trazado", "noise128", "pbasicometal"] |  | ||||||
|         overrides = { |  | ||||||
|             "zonaautoiluminada-a.dds" : { |  | ||||||
|                 # "light" |  | ||||||
|             }, |  | ||||||
|             "flecha.000.dds": { |  | ||||||
|                 "shader": "hologram" |  | ||||||
|             }, |  | ||||||
|             "mayor.000.dds": { |  | ||||||
|                 "shader": "hologram" |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|         settings = { |  | ||||||
|             "Emission Strength": 10.0, |  | ||||||
|             "Specular": 0.0, |  | ||||||
|             "Roughness": 0.0, |  | ||||||
|             "Metallic": 0.0, |  | ||||||
|         } |  | ||||||
|         transparent_settings = { |  | ||||||
|             "Transmission": 1.0, |  | ||||||
|             "Transmission Roughness": 0.0, |  | ||||||
|             "IOR": 1.0, |  | ||||||
|         } |  | ||||||
|         glass_settings = { |  | ||||||
|             "Base Color": ( .8, .8, .8, 1.0), |  | ||||||
|             "Metallic": 0.2, |  | ||||||
|             "Roughness": 0.0, |  | ||||||
|             "Specular": 0.2, |  | ||||||
|         } |  | ||||||
|         tex_slot_map={ |  | ||||||
|             "base": "Base Color", |  | ||||||
|             "metallic":"Metallic", |  | ||||||
|             "unk_1":None, # "Clearcoat" ? env map? |  | ||||||
|             "bump":"Normal", |  | ||||||
|             "glow":"Emission" |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         mat = bpy.data.materials.new(mat_key) |  | ||||||
|         mat.use_nodes = True |  | ||||||
|         node_tree = mat.node_tree |  | ||||||
|         nodes = node_tree.nodes |  | ||||||
|         imgs = {} |  | ||||||
|         animated_textures={} |  | ||||||
|         is_transparent = True |  | ||||||
|         print(map_def) |  | ||||||
|         if map_def[0]: |  | ||||||
|             print("=== MAP[0]:",self.resolve_path(map_def[0])) |  | ||||||
|         if map_def[2]: |  | ||||||
|             print("=== MAP[2]:",self.resolve_path(map_def[2])) |  | ||||||
|         for slot,tex in mat_def["maps"].items(): |  | ||||||
|             slot=tex_slot_map.get(slot) |  | ||||||
|             if (slot is None)  and tex: |  | ||||||
|                 self.unhandled.add(tex["texture"]) |  | ||||||
|                 print(f"Don't know what to do with {tex}") |  | ||||||
|             if not (tex and slot): |  | ||||||
|                 continue |  | ||||||
|             tex_file = self.resolve_path(tex["texture"]) |  | ||||||
|             if tex_file is None: |  | ||||||
|                 continue |  | ||||||
|             tex_name = os.path.basename(tex_file) |  | ||||||
|             if ".000." in tex_name: |  | ||||||
|                 animated_textures[slot]=len(glob(tex_file.replace(".000.",".*."))) |  | ||||||
|             mat_props.update(overrides.get(tex_name,{})) |  | ||||||
|             if any( |  | ||||||
|                 tex_name.find(fragment) != -1 |  | ||||||
|                 for fragment in skip_names |  | ||||||
|             ): |  | ||||||
|                 continue |  | ||||||
|             else: |  | ||||||
|                 is_transparent = False |  | ||||||
|             imgs[slot]=bpy.data.images.load(tex_file,check_existing=True) |  | ||||||
|         for n in nodes: |  | ||||||
|             nodes.remove(n) |  | ||||||
|         out = nodes.new("ShaderNodeOutputMaterial") |  | ||||||
|         out.name = "Output" |  | ||||||
|         shader = nodes.new("ShaderNodeBsdfPrincipled") |  | ||||||
|         is_transparent|=mat_props.get("shader")=="glass" |  | ||||||
|         is_transparent|=mat_props.get("transp") in {"premult","filter"} |  | ||||||
|         if is_transparent: |  | ||||||
|             settings.update(transparent_settings) |  | ||||||
|         if mat_props.get("shader")=="glass": |  | ||||||
|             settings.update(glass_settings) |  | ||||||
|         for name, value in settings.items(): |  | ||||||
|             shader.inputs[name].default_value = value |  | ||||||
|         for socket,img in imgs.items(): |  | ||||||
|             img_node = nodes.new("ShaderNodeTexImage") |  | ||||||
|             img_node.name = img.name |  | ||||||
|             img_node.image = img |  | ||||||
|             if socket in animated_textures: |  | ||||||
|                 img.source="SEQUENCE" |  | ||||||
|                 num_frames=animated_textures[socket] |  | ||||||
|                 fps_div = 2 # TODO: read from .emi file |  | ||||||
|                 drv=img_node.image_user.driver_add("frame_offset") |  | ||||||
|                 drv.driver.type="SCRIPTED" |  | ||||||
|                 drv.driver.expression=f"((frame/{fps_div})%{num_frames})-1" |  | ||||||
|                 img_node.image_user.frame_duration=1 |  | ||||||
|                 img_node.image_user.use_cyclic=True |  | ||||||
|                 img_node.image_user.use_auto_refresh=True |  | ||||||
|             tex_mix_node = nodes.new("ShaderNodeMixRGB") |  | ||||||
|             tex_mix_node.blend_type = "MULTIPLY" |  | ||||||
|             tex_mix_node.inputs["Fac"].default_value = 0.0 |  | ||||||
|             node_tree.links.new( |  | ||||||
|                 img_node.outputs["Color"], tex_mix_node.inputs["Color1"] |  | ||||||
|             ) |  | ||||||
|             node_tree.links.new( |  | ||||||
|                 img_node.outputs["Alpha"], tex_mix_node.inputs["Color2"] |  | ||||||
|             ) |  | ||||||
|             imgs[socket] = tex_mix_node |  | ||||||
|             output_node = tex_mix_node.outputs["Color"] |  | ||||||
|             print(img.name, "->", socket) |  | ||||||
|             if socket == "Normal": |  | ||||||
|                 normal_map = nodes.new("ShaderNodeNormalMap") |  | ||||||
|                 node_tree.links.new(output_node, normal_map.inputs["Color"]) |  | ||||||
|                 output_node = normal_map.outputs["Normal"] |  | ||||||
|                 normal_map.inputs["Strength"].default_value = 0.4 |  | ||||||
|             node_tree.links.new(output_node, shader.inputs[socket]) |  | ||||||
|         shader_out=shader.outputs["BSDF"] |  | ||||||
|         if mat_props.get("shader")=="hologram": |  | ||||||
|             mix_shader = nodes.new("ShaderNodeMixShader") |  | ||||||
|             transp_shader = nodes.new("ShaderNodeBsdfTransparent") |  | ||||||
|             mix_in_1 = self.get_input(mix_shader,"Shader","SHADER")[0] |  | ||||||
|             mix_in_2 = self.get_input(mix_shader,"Shader","SHADER")[1] |  | ||||||
|             node_tree.links.new(transp_shader.outputs["BSDF"], mix_in_1) |  | ||||||
|             node_tree.links.new(shader.outputs["BSDF"], mix_in_2) |  | ||||||
|             node_tree.links.new(imgs["Base Color"].outputs["Color"],mix_shader.inputs["Fac"]) |  | ||||||
|             node_tree.links.new(imgs["Base Color"].outputs["Color"],shader.inputs["Emission"]) |  | ||||||
|             shader.inputs["Emission Strength"].default_value=50.0 |  | ||||||
|             shader_out=mix_shader.outputs["Shader"] |  | ||||||
|         if settings.get("Transmission",0.0)>0.0: |  | ||||||
|             light_path = nodes.new("ShaderNodeLightPath") |  | ||||||
|             mix_shader = nodes.new("ShaderNodeMixShader") |  | ||||||
|             transp_shader = nodes.new("ShaderNodeBsdfTransparent") |  | ||||||
|             mix_in_1 = self.get_input(mix_shader,"Shader","SHADER")[0] |  | ||||||
|             mix_in_2 = self.get_input(mix_shader,"Shader","SHADER")[1] |  | ||||||
|             node_tree.links.new(shader.outputs["BSDF"], mix_in_1) |  | ||||||
|             node_tree.links.new(transp_shader.outputs["BSDF"], mix_in_2) |  | ||||||
|             node_tree.links.new(light_path.outputs["Is Shadow Ray"], mix_shader.inputs["Fac"]) |  | ||||||
|             if mat_props.get("transp")=="filter" or mat_props.get("shader")=="glass": |  | ||||||
|                 node_tree.links.new(imgs["Base Color"].outputs["Color"],transp_shader.inputs["Color"]) |  | ||||||
|             shader_out=mix_shader.outputs["Shader"] |  | ||||||
|         node_tree.links.new(shader_out, out.inputs["Surface"]) |  | ||||||
|         # try: |  | ||||||
|         #     bpy.ops.node.button() |  | ||||||
|         # except: |  | ||||||
|         #     pass |  | ||||||
|         return mat |  | ||||||
| 
 |  | ||||||
|     def apply_maps(self, ob, m_mat, m_map): |  | ||||||
|         mat_key, m_mat = m_mat |  | ||||||
|         map_key, m_map = m_map |  | ||||||
|         if mat_key == 0: |  | ||||||
|             return |  | ||||||
|         mat_name = m_mat.get("name", f"MAT:{mat_key:08X}") |  | ||||||
|         if mat_name not in bpy.data.materials: |  | ||||||
|             ob.active_material = self.build_material(mat_name, m_mat, m_map) |  | ||||||
|         else: |  | ||||||
|             ob.active_material = bpy.data.materials[mat_name] |  | ||||||
| 
 |  | ||||||
|     def create_mesh(self, name, verts, faces, m_mat, m_map): |  | ||||||
|         if not verts["inner"]: |  | ||||||
|             return |  | ||||||
|         me = bpy.data.meshes.new(name) |  | ||||||
|         me.use_auto_smooth = True |  | ||||||
|         pos = np.array([Vector(v["xyz"]).xzy for v in verts["inner"]["data"]]) |  | ||||||
|         pos /= self.model_scale |  | ||||||
|         me.from_pydata(pos, [], faces) |  | ||||||
|         normals = [v["normal"] for v in verts["inner"]["data"]] |  | ||||||
|         vcols = [v["diffuse"] for v in verts["inner"]["data"]] |  | ||||||
|         if all(normals): |  | ||||||
|             normals = np.array(normals) |  | ||||||
|             me.vertices.foreach_set("normal", normals.flatten()) |  | ||||||
|         if not me.vertex_colors: |  | ||||||
|             me.vertex_colors.new() |  | ||||||
|         for (vcol, vert) in zip(vcols, me.vertex_colors[0].data): |  | ||||||
|             vert.color = [vcol["r"], vcol["g"], vcol["b"], vcol["a"]] |  | ||||||
|         uvlayers = {} |  | ||||||
|         tex = [f"tex_{n+1}" for n in range(8)] |  | ||||||
|         for face in me.polygons: |  | ||||||
|             for vert_idx, loop_idx in zip(face.vertices, face.loop_indices): |  | ||||||
|                 vert = verts["inner"]["data"][vert_idx] |  | ||||||
|                 for tex_name in tex: |  | ||||||
|                     if not vert[tex_name]: |  | ||||||
|                         continue |  | ||||||
|                     if not tex_name in uvlayers: |  | ||||||
|                         uvlayers[tex_name] = me.uv_layers.new(name=tex_name) |  | ||||||
|                     u, v = vert[tex_name] |  | ||||||
|                     uvlayers[tex_name].data[loop_idx].uv = (u, 1.0 - v) |  | ||||||
|         bm = bmesh.new() |  | ||||||
|         bm.from_mesh(me) |  | ||||||
|         if self.options['remove_dup_verts']: |  | ||||||
|             bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001) |  | ||||||
|         bm.to_mesh(me) |  | ||||||
|         me.update(calc_edges=True) |  | ||||||
|         bm.clear() |  | ||||||
|         for poly in me.polygons: |  | ||||||
|             poly.use_smooth = True |  | ||||||
|         ob = bpy.data.objects.new(name, me) |  | ||||||
|         self.apply_maps(ob, m_mat, m_map) |  | ||||||
|         bpy.context.scene.collection.objects.link(ob) |  | ||||||
|         self.objects.setdefault(name.split("(")[0], []).append(ob) |  | ||||||
|         return ob |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Scrap_Load(Operator, ImportHelper): |  | ||||||
| 
 |  | ||||||
|     bl_idname = "scrap_utils.import_pickle" |  | ||||||
|     bl_label = "Import Pickle" |  | ||||||
| 
 |  | ||||||
|     filename_ext = ".pkl.gz" |  | ||||||
|     filter_glob: StringProperty(default="*.pkl.gz", options={"HIDDEN"}) |  | ||||||
|      |  | ||||||
|     create_dummies: BoolProperty( |  | ||||||
|         name="Import dummies", |  | ||||||
|         default=True |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     create_nodes: BoolProperty( |  | ||||||
|         name="Import nodes (lights, cameras, etc)", |  | ||||||
|         default=True |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     create_tracks: BoolProperty( |  | ||||||
|         name="Create track curves", |  | ||||||
|         default=True |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     merge_objects: BoolProperty( |  | ||||||
|         name="Merge objects by name", |  | ||||||
|         default=False |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     remove_dup_verts: BoolProperty( |  | ||||||
|         name="Remove overlapping vertices\nfor smoother meshes", |  | ||||||
|         default=True |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     def execute(self, context): |  | ||||||
|         bpy.ops.preferences.addon_enable(module = "node_arrange") |  | ||||||
|         bpy.ops.outliner.orphans_purge(do_recursive=True) |  | ||||||
|         importer = ScrapImporter(self.as_keywords()) |  | ||||||
|         importer.run() |  | ||||||
|         dg = bpy.context.evaluated_depsgraph_get() |  | ||||||
|         dg.update() |  | ||||||
|         return {"FINISHED"} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def register(): |  | ||||||
|     bpy.utils.register_class(Scrap_Load) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def unregister(): |  | ||||||
|     bpy.utils.unregister_class(Scrap_Load) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     if cmdline is None or not cmdline.file_list: |  | ||||||
|         register() |  | ||||||
|         bpy.ops.scrap_utils.import_pickle("INVOKE_DEFAULT") |  | ||||||
|     else: |  | ||||||
|         for file in cmdline.file_list: |  | ||||||
|             bpy.context.preferences.view.show_splash = False |  | ||||||
|             objs = bpy.data.objects |  | ||||||
|             for obj in objs.keys(): |  | ||||||
|                 objs.remove(objs[obj], do_unlink=True) |  | ||||||
|             cols=bpy.data.collections |  | ||||||
|             for col in cols: |  | ||||||
|                 cols.remove(col) |  | ||||||
|             importer = ScrapImporter(file) |  | ||||||
|             importer.run() |  | ||||||
|             if cmdline.save: |  | ||||||
|                 targetpath = Path(file).name.partition(".")[0] + ".blend" |  | ||||||
|                 targetpath = os.path.abspath(targetpath) |  | ||||||
|                 print("Saving", targetpath) |  | ||||||
|                 bpy.ops.wm.save_as_mainfile(filepath=targetpath) |  | ||||||
|  | @ -1,118 +0,0 @@ | ||||||
| import sys |  | ||||||
| from .. import scrap_bridge |  | ||||||
| from bpy.props import (StringProperty, BoolProperty, CollectionProperty, |  | ||||||
|                        IntProperty) |  | ||||||
| 
 |  | ||||||
| bl_info = { |  | ||||||
|     "name": "Packed Archive File", |  | ||||||
|     "blender": (2, 71, 0), |  | ||||||
|     "location": "File > Import", |  | ||||||
|     "description": "Import data from Scrapland .packed Archive", |  | ||||||
|     "category": "Import-Export"} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ImportFilearchives(bpy.types.Operator): |  | ||||||
|     """Import whole filearchives directory.""" |  | ||||||
|     bl_idname = "import_scene.packed" |  | ||||||
|     bl_label = 'Import Scrapland .packed' |  | ||||||
|      |  | ||||||
|     directory = StringProperty(name="'Scrapland' folder", |  | ||||||
|                                subtype="DIR_PATH", options={'HIDDEN'}) |  | ||||||
|     filter_folder = BoolProperty(default=True, options={'HIDDEN'}) |  | ||||||
|     filter_glob = StringProperty(default="", options={'HIDDEN'}) |  | ||||||
|      |  | ||||||
|     def invoke(self, context, event): |  | ||||||
|         context.window_manager.fileselect_add(self) |  | ||||||
|         return {'RUNNING_MODAL'} |  | ||||||
| 
 |  | ||||||
|     def execute(self, context): |  | ||||||
|         # TODO: Validate filepath |  | ||||||
|         bpy.ops.ui.packed_browser('INVOKE_DEFAULT',filepath=self.directory) |  | ||||||
|         return {'FINISHED'} |  | ||||||
|      |  | ||||||
| 
 |  | ||||||
| class PackedFile(bpy.types.PropertyGroup): |  | ||||||
|     path = bpy.props.StringProperty() |  | ||||||
|     packed_file = bpy.props.StringProperty() |  | ||||||
|     selected = bpy.props.BoolProperty(name="") |  | ||||||
|     offset = bpy.props.IntProperty() |  | ||||||
|     size = bpy.props.IntProperty() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| archive = None |  | ||||||
| class PackedBrowser(bpy.types.Operator): |  | ||||||
|     bl_idname = "ui.packed_browser" |  | ||||||
|     bl_label = "Packed Browser" |  | ||||||
|     bl_options = {'INTERNAL'} |  | ||||||
|      |  | ||||||
|     files = CollectionProperty(type=PackedFile) |  | ||||||
|     selected_index = IntProperty(default=0) |  | ||||||
|      |  | ||||||
|     def invoke(self, context, event): |  | ||||||
|         scrapland_path=scrap_bridge("find-scrapland") |  | ||||||
|         print(scrapland_path) |  | ||||||
|         packed_data=scrap_bridge("parse-packed",scrapland_path) |  | ||||||
|         print(packed_data) |  | ||||||
|         self.packed_data=packed_data |  | ||||||
|         return context.window_manager.invoke_props_dialog(self) |  | ||||||
|      |  | ||||||
|     def draw(self, context): |  | ||||||
|         if self.selected_index != -1: |  | ||||||
|             print("new selected_index: " + str(self.selected_index)) |  | ||||||
|             self.files.clear() |  | ||||||
|             for packed_name,files in self.archive: |  | ||||||
|                 for file in files: |  | ||||||
|                     entry = self.files.add() |  | ||||||
|                     entry.packed_file = packed_name |  | ||||||
|                     [entry.path,entry.offset,entry.size]=file |  | ||||||
|             self.selected_index = -1 |  | ||||||
|         self.layout.template_list("PackedDirList", "", self, "current_dir", self, "selected_index") |  | ||||||
|      |  | ||||||
|     def execute(self, context): |  | ||||||
|         print("execute") |  | ||||||
|         return {'FINISHED'} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PackedDirList(bpy.types.UIList): |  | ||||||
|     def draw_item(self, context, layout, data, item, icon, active_data, active_propname): |  | ||||||
|         operator = data |  | ||||||
|         packed_entry = item |  | ||||||
|          |  | ||||||
|         if self.layout_type in {'DEFAULT', 'COMPACT'}: |  | ||||||
|             layout.prop(packed_entry, "name", text="", emboss=False, icon_value=icon) |  | ||||||
|             layout.prop(packed_entry, "selected") |  | ||||||
|         elif self.layout_type in {'GRID'}: |  | ||||||
|             layout.alignment = 'CENTER' |  | ||||||
|             layout.label(text="", icon_value=icon) |  | ||||||
|          |  | ||||||
| 
 |  | ||||||
| def menu_func_import(self, context): |  | ||||||
|     self.layout.operator(ImportFilearchives.bl_idname, text="Scrapland .packed") |  | ||||||
| 
 |  | ||||||
| classes=[ |  | ||||||
|     PackedFile, |  | ||||||
|     PackedDirList, |  | ||||||
|     PackedBrowser, |  | ||||||
|     ImportFilearchives, |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| def register(): |  | ||||||
|     for cls in classes: |  | ||||||
|         bpy.utils.regiser_class(cls) |  | ||||||
|     bpy.types.INFO_MT_file_import.append(menu_func_import) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def unregister(): |  | ||||||
|     for cls in reversed(classes): |  | ||||||
|         bpy.utils.unregister_class(cls) |  | ||||||
|     bpy.types.INFO_MT_file_import.remove(menu_func_import) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     import imp |  | ||||||
|     imp.reload(sys.modules[__name__]) |  | ||||||
|     for cls in classes: |  | ||||||
|         bpy.utils.regiser_class(cls) |  | ||||||
| 
 |  | ||||||
|  | @ -1,66 +0,0 @@ | ||||||
| 
 |  | ||||||
| int _D3DXGetFVFVertexSize(uint fvf) |  | ||||||
| 
 |  | ||||||
| { |  | ||||||
|   uint uVar1; |  | ||||||
|   uint uVar2; |  | ||||||
|   uint uVar3; |  | ||||||
|   int vert_size; |  | ||||||
|    |  | ||||||
|   uVar1 = fvf & 0xe; |  | ||||||
|   vert_size = 0; |  | ||||||
|   if (uVar1 == 2) { |  | ||||||
|     vert_size = 0xc; |  | ||||||
|   } |  | ||||||
|   else if ((uVar1 == 4) || (uVar1 == 6)) { |  | ||||||
|     vert_size = 0x10; |  | ||||||
|   } |  | ||||||
|   else if (uVar1 == 8) { |  | ||||||
|     vert_size = 0x14; |  | ||||||
|   } |  | ||||||
|   else if (uVar1 == 0xa) { |  | ||||||
|     vert_size = 0x18; |  | ||||||
|   } |  | ||||||
|   else if (uVar1 == 0xc) { |  | ||||||
|     vert_size = 0x1c; |  | ||||||
|   } |  | ||||||
|   else if (uVar1 == 0xe) { |  | ||||||
|     vert_size = 0x20; |  | ||||||
|   } |  | ||||||
|   if ((fvf & 0x10) != 0) { |  | ||||||
|     vert_size += 0xc; |  | ||||||
|   } |  | ||||||
|   if ((fvf & 0x20) != 0) { |  | ||||||
|     vert_size += 4; |  | ||||||
|   } |  | ||||||
|   if ((fvf & 0x40) != 0) { |  | ||||||
|     vert_size += 4; |  | ||||||
|   } |  | ||||||
|   if (fvf < '\0') { |  | ||||||
|     vert_size += 4; |  | ||||||
|   } |  | ||||||
|   uVar1 = fvf >> 8 & 0xf; |  | ||||||
|   uVar3 = fvf >> 16; |  | ||||||
|   if (uVar3 == 0) { |  | ||||||
|     vert_size += uVar1 * 8; |  | ||||||
|   } |  | ||||||
|   else { |  | ||||||
|     for (; uVar1 != 0; uVar1 -= 1) { |  | ||||||
|       uVar2 = uVar3 & 3; |  | ||||||
|       if (uVar2 == 0) { |  | ||||||
|         vert_size += 8; |  | ||||||
|       } |  | ||||||
|       else if (uVar2 == 1) { |  | ||||||
|         vert_size += 0xc; |  | ||||||
|       } |  | ||||||
|       else if (uVar2 == 2) { |  | ||||||
|         vert_size += 0x10; |  | ||||||
|       } |  | ||||||
|       else if (uVar2 == 3) { |  | ||||||
|         vert_size += 4; |  | ||||||
|       } |  | ||||||
|       uVar3 >>= 2; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return vert_size; |  | ||||||
| } |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| use std::path::PathBuf; |  | ||||||
| 
 |  | ||||||
| use steamlocate::SteamDir; |  | ||||||
| use anyhow::{bail,Result}; |  | ||||||
| const APP_ID: u32 = 897610; |  | ||||||
| 
 |  | ||||||
| pub(crate) fn get_executable() -> Result<PathBuf> { |  | ||||||
|     let Some(mut steam) = SteamDir::locate() else { |  | ||||||
|         bail!("Failed to find steam folder"); |  | ||||||
|     }; |  | ||||||
|     let Some(app) = steam.app(&APP_ID) else { |  | ||||||
|         bail!("App {APP_ID} is not installed!"); |  | ||||||
|     }; |  | ||||||
|     Ok(app.path.clone()) |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,30 +0,0 @@ | ||||||
| // https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx9-graphics-reference-asm-ps-1-x
 |  | ||||||
| //
 |  | ||||||
| // ################################################
 |  | ||||||
| // 
 |  | ||||||
| // #[derive(Debug)]
 |  | ||||||
| // enum VecArg {
 |  | ||||||
| //     Tex(f32,f32,f32,f32),
 |  | ||||||
| //     Reg(f32,f32,f32,f32),
 |  | ||||||
| //     Ver(f32,f32,f32,f32),
 |  | ||||||
| //     Col(f32,f32,f32,f32),
 |  | ||||||
| //     Vec(f32,f32,f32,f32),
 |  | ||||||
| // }
 |  | ||||||
| 
 |  | ||||||
| // struct Arg {
 |  | ||||||
| //     arg: VecArg,
 |  | ||||||
| //     idx: Option<usize>
 |  | ||||||
| // }
 |  | ||||||
| 
 |  | ||||||
| // #[derive(Debug)]
 |  | ||||||
| // enum Inst {
 |  | ||||||
| //     Tex(Arg),
 |  | ||||||
| //     Add(Arg,Arg,Arag),
 |  | ||||||
| //     Sub(Arg,Arg,Arag),
 |  | ||||||
| //     Mul(Arg,Arg,Arag),
 |  | ||||||
| //     Mov(Arg,Arg),
 |  | ||||||
| // }
 |  | ||||||
| 
 |  | ||||||
| fn parse(path: &str) { |  | ||||||
|     
 |  | ||||||
| } |  | ||||||
|  | @ -1,2 +0,0 @@ | ||||||
| [build] |  | ||||||
| target = "i686-pc-windows-msvc" |  | ||||||
							
								
								
									
										1624
									
								
								tools/remaster/scraphacks_rs/Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1624
									
								
								tools/remaster/scraphacks_rs/Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,45 +0,0 @@ | ||||||
| [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" |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| [[source]] |  | ||||||
| url = "https://pypi.org/simple" |  | ||||||
| verify_ssl = true |  | ||||||
| name = "pypi" |  | ||||||
| 
 |  | ||||||
| [packages] |  | ||||||
| 
 |  | ||||||
| [dev-packages] |  | ||||||
| 
 |  | ||||||
| [requires] |  | ||||||
| python_version = "3.9" |  | ||||||
|  | @ -1,242 +0,0 @@ | ||||||
| { |  | ||||||
|     "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" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| fn main() -> shadow_rs::SdResult<()> { |  | ||||||
|     shadow_rs::new() |  | ||||||
| } |  | ||||||
|  | @ -1,4 +0,0 @@ | ||||||
| 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" |  | ||||||
|  | @ -1,16 +0,0 @@ | ||||||
| # 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: `` |  | ||||||
|  | @ -1,34 +0,0 @@ | ||||||
| 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" |  | ||||||
|  | @ -1,16 +0,0 @@ | ||||||
| #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; |  | ||||||
| }; |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| enum FilePatch { |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub struct Config { |  | ||||||
|     file_patches: FxHashMap<PathBuf,FilePatch> |  | ||||||
| } |  | ||||||
|  | @ -1,94 +0,0 @@ | ||||||
| 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(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,96 +0,0 @@ | ||||||
| #![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 |  | ||||||
|     ); |  | ||||||
| } |  | ||||||
|  | @ -1,204 +0,0 @@ | ||||||
| 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()?) |  | ||||||
| } |  | ||||||
|  | @ -1,94 +0,0 @@ | ||||||
| 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(()) |  | ||||||
| } |  | ||||||
|  | @ -1,177 +0,0 @@ | ||||||
| // 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)) |  | ||||||
| } |  | ||||||
|  | @ -1,381 +0,0 @@ | ||||||
| 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 +0,0 @@ | ||||||
| {"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":{}} |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| Signature: 8a477f597d28d172789f06886806bc55 |  | ||||||
| # This file is a cache directory tag created by cargo. |  | ||||||
| # For information about cache directory tags see https://bford.info/cachedir/ |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| 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