8634792e2b
gregCore CI / build (push) Has been cancelled
feat(CommonShop): Add custom shop item registration and checkout handling feat(CommonShop): Create CustomShopItem class for better item management feat(CommonShop): Develop ShopAPI for injecting custom items into the shop feat(FasterSFP): Introduce faster SFP modules with custom speeds and prices feat(NoMoreEOL): Add patch to control visibility of error warning signs feat(QoL): Implement various quality of life improvements including shop layout fixes and item deletion refactor(Sdk): Create legacy event dispatcher for backward compatibility
511 lines
24 KiB
PowerShell
511 lines
24 KiB
PowerShell
# Generates greg_hooks.json + gregCore/framework/harmony/*.cs from Il2CppInterop C# sources
|
|
# (stand-in when repo root MergedCode.md is absent). Re-run after game / interop updates.
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..')
|
|
$assemblyRoot = Join-Path $repoRoot 'gregReferences\Assembly-CSharp'
|
|
$harmonyPatches = Join-Path $repoRoot 'gregCore\framework\ModLoader\HarmonyPatches.cs'
|
|
$outJsonRoot = Join-Path $repoRoot 'greg_hooks.json'
|
|
$outJsonFramework = Join-Path $repoRoot 'gregCore\framework\greg_hooks.json'
|
|
$outHooksDir = Join-Path $repoRoot 'gregCore\framework\harmony'
|
|
|
|
if (-not (Test-Path $assemblyRoot)) { throw "Missing assembly source root: $assemblyRoot" }
|
|
New-Item -ItemType Directory -Force -Path $outHooksDir | Out-Null
|
|
|
|
function Get-HarmonyExclusions([string]$path) {
|
|
$set = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
|
|
if (-not (Test-Path $path)) { return ,$set }
|
|
$text = Get-Content $path -Raw
|
|
foreach ($m in [regex]::Matches($text, 'typeof\((\w+)\)\s*,\s*nameof\(\w+\.(\w+)\)')) {
|
|
[void]$set.Add("$($m.Groups[1].Value)|$($m.Groups[2].Value)")
|
|
}
|
|
foreach ($m in [regex]::Matches($text, 'typeof\((\w+)\)\s*,\s*"(\w+)"')) {
|
|
[void]$set.Add("$($m.Groups[1].Value)|$($m.Groups[2].Value)")
|
|
}
|
|
return ,$set
|
|
}
|
|
|
|
$excluded = Get-HarmonyExclusions $harmonyPatches
|
|
[void]$excluded.Add('NetworkMap|RemapDeviceId')
|
|
[void]$excluded.Add('NetworkMap|RemoveIsolatedDevice')
|
|
[void]$excluded.Add('Technician|CacheDeviceBounds')
|
|
|
|
# Curated game surface for stable compile-time Harmony class generation.
|
|
$harmonyEmitClasses = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
|
|
@(
|
|
'Player', 'PlayerManager', 'PlayerHit', 'PlayerData',
|
|
'Server', 'MainGameManager', 'ComputerShop', 'HRSystem', 'SaveSystem', 'CustomerBase',
|
|
'CablePositions', 'CableLink', 'Rack', 'NetworkMap', 'BalanceSheet', 'MainMenu',
|
|
'TimeController', 'TechnicianManager', 'Technician', 'Objectives',
|
|
'PacketSpawnerSystem', 'NetworkSwitch', 'SFPModule', 'SFPBox', 'PatchPanel',
|
|
'AutoDisable', 'Benchmark01', 'Benchmark02', 'Benchmark03', 'Benchmark04',
|
|
'GetCurrentVersion', 'LocalisedText', 'MouseLook', 'ObjectSpin', 'PositionIndicator',
|
|
'RayLookAt', 'SimpleScript', 'TextConsoleSimulator', 'TextMeshProFloatingText', 'TextMeshSpawner',
|
|
'VertexJitter', 'VertexShakeA', 'VertexShakeB', 'VertexZoom', 'WarpTextExample',
|
|
'AICharacterControl', 'AICharacterExpressions', 'CameraController', 'CarController',
|
|
'CheckIfTouchingWall', 'FirstPersonController', 'InputManager', 'ObjImporter',
|
|
'GODMOD', 'RackMount', 'AssetManagement', 'AssetManagementDeviceLine', 'AudioManager',
|
|
'FootSteps', 'OSK_Keyboard', 'OSK_KeySounds', 'SettingsVolume', 'ThirdPersonCharacter',
|
|
'OSK_AccentConsole', 'OSK_GamepadHelper', 'OSK_UI_InputReceiver', 'OSK_UI_Keyboard',
|
|
'SettingsControls', 'SettingsGraphics', 'ShopCartItem', 'SkewTextExample', 'StaticUIElements',
|
|
'TerrainDetector', 'UsableObject', 'viperInput', 'WaypointInitializationSystem',
|
|
'_PrivateImplementationDetails_', 'ModLoader', 'CommandCenter'
|
|
) | ForEach-Object { [void]$harmonyEmitClasses.Add($_) }
|
|
|
|
function Test-SkipTypeName([string]$n) {
|
|
if ($n -match 'd__\d+$') { return $true }
|
|
if ($n -match 'b__\d+$') { return $true }
|
|
if ($n -eq 'TypeHandle') { return $true }
|
|
if ($n.StartsWith('__')) { return $true }
|
|
if ($n.StartsWith('_PrivateImplementationDetails_')) { return $true }
|
|
return $false
|
|
}
|
|
|
|
function Get-GregDomain([string]$className) {
|
|
$c = $className
|
|
if ($c -match '^(Player|PlayerData|PlayerManager|ObjectInHand)') { return 'Player' }
|
|
if ($c -match '^(Technician|Employee|Staff|HRSystem)') { return 'Employee' }
|
|
if ($c -match '^(Customer|Contract|Client|SLA)') { return 'Customer' }
|
|
if ($c -match '^(Server|Hardware|Component)') { return 'Server' }
|
|
if ($c -match '^(Rack|RackSlot|RackUnit)') { return 'Rack' }
|
|
if ($c -match '^(Network|Switch|Cable|Packet|Port|SFP)') { return 'Network' }
|
|
if ($c -match '^(Power|UPS|PDU|Grid|Energy)') { return 'Power' }
|
|
if ($c -match '^(Job|Task|Objective|Quest|Mission)') { return 'Gameplay' }
|
|
if ($c -match '^(UI|Menu|Screen|Panel|Overlay|HUD|Notification|Tutorial|Pause|Loading|MainMenu|Settings|BalanceSheet|Chat|Tooltip|KeyHint|Rebind)') { return 'Ui' }
|
|
if ($c -match '^(GameManager|MainGameManager|SaveManager|LoadManager|SceneManager|SaveData|ModLoader|SteamManager|TimeController|AudioManager|Waypoint)') { return 'System' }
|
|
return 'System'
|
|
}
|
|
|
|
function Get-SemanticAction([string]$className, [string]$methodName) {
|
|
$key = "$className|$methodName"
|
|
$map = @{
|
|
'Player|UpdateCoin' = 'MoneyChanged'
|
|
'Player|UpdateReputation' = 'ReputationChanged'
|
|
'Player|UpdateXP' = 'XpChanged'
|
|
'Player|WarpPlayer' = 'Warped'
|
|
'Player|DropAllItems' = 'DroppedAllItems'
|
|
'Player|LoadPlayer' = 'Loaded'
|
|
'Player|Start' = 'ComponentInitialized'
|
|
'TechnicianManager|AddTechnician' = 'Hired'
|
|
'TechnicianManager|FireTechnician' = 'Fired'
|
|
'TechnicianManager|SendTechnician' = 'Dispatched'
|
|
'TechnicianManager|EnqueueDispatch' = 'JobQueued'
|
|
'TechnicianManager|Awake' = 'ComponentInitialized'
|
|
'TechnicianManager|RestoreJobQueue' = 'JobQueueLoaded'
|
|
'TechnicianManager|RequestNextJob' = 'NextJobRequested'
|
|
'PacketSpawnerSystem|SpawnPacket' = 'PacketSpawned'
|
|
}
|
|
if ($map.ContainsKey($key)) { return $map[$key] }
|
|
|
|
$m = $methodName
|
|
if ($m -eq 'Awake' -or $m -eq 'Start' -or $m -eq 'OnEnable') { return 'ComponentInitialized' }
|
|
if ($m -eq 'OnDisable') { return 'ComponentDisabled' }
|
|
if ($m.StartsWith('Add')) { return ($m.Substring(3) + 'Added') }
|
|
if ($m.StartsWith('Remove')) { return ($m.Substring(6) + 'Removed') }
|
|
if ($m.StartsWith('Update')) { return ($m.Substring(6) + 'Changed') }
|
|
if ($m.StartsWith('Spawn')) { return ($m.Substring(5) + 'Spawned') }
|
|
if ($m.StartsWith('Fire')) { return ($m.Substring(4) + 'Fired') }
|
|
if ($m.StartsWith('Hire')) { return ($m.Substring(4) + 'Hired') }
|
|
if ($m.StartsWith('Buy')) { return ($m.Substring(3) + 'Purchased') }
|
|
if ($m.StartsWith('Sell')) { return ($m.Substring(4) + 'Sold') }
|
|
if ($m.StartsWith('Place')) { return ($m.Substring(5) + 'Placed') }
|
|
if ($m.StartsWith('Load')) { return ($m.Substring(4) + 'Loaded') }
|
|
if ($m.StartsWith('Save')) { return ($m.Substring(4) + 'Saved') }
|
|
if ($m.StartsWith('Warp')) { return ($m.Substring(4) + 'Warped') }
|
|
if ($m.StartsWith('Repair')) { return ($m.Substring(6) + 'Repaired') }
|
|
if ($m.StartsWith('Install')) { return ($m.Substring(7) + 'Installed') }
|
|
if ($m.StartsWith('Break')) { return ($m.Substring(5) + 'Broken') }
|
|
if ($m.StartsWith('Send')) { return ($m.Substring(4) + 'Dispatched') }
|
|
if ($m.StartsWith('Enqueue')) { return ($m.Substring(8) + 'JobQueued') }
|
|
if ($m.StartsWith('Set')) { return ($m.Substring(3) + 'Set') }
|
|
if ($m.StartsWith('Drop')) { return ($m.Substring(4) + 'Dropped') }
|
|
return $m
|
|
}
|
|
|
|
function Get-HookStrategy([string]$methodName, [string]$returnType) {
|
|
if ($methodName -in @('Update', 'FixedUpdate', 'LateUpdate')) { return 'None' }
|
|
if ($methodName -eq 'OnUpdate') { return 'None' }
|
|
# Emit Postfix only: generated Prefix stubs were non-functional and broke Harmony expectations.
|
|
return 'Postfix'
|
|
}
|
|
|
|
function Test-SkipClass([string]$className, [string]$baseClause) {
|
|
if ($className -in @('ModLoader', 'UnitySourceGeneratedAssemblyMonoScriptTypes_v1')) { return $true }
|
|
if ($baseClause -match 'SystemBase') { return $true }
|
|
return $false
|
|
}
|
|
|
|
function Get-SourceDirectories([string]$rootPath) {
|
|
$dirs = [System.Collections.Generic.List[string]]::new()
|
|
|
|
$includePatterns = @('Il2Cpp*', 'Unity*', 'UnityEngine*')
|
|
foreach ($pattern in $includePatterns) {
|
|
foreach ($dir in (Get-ChildItem -Path $rootPath -Directory -Filter $pattern -ErrorAction SilentlyContinue)) {
|
|
[void]$dirs.Add($dir.FullName)
|
|
}
|
|
}
|
|
|
|
if ($dirs.Count -eq 0) {
|
|
throw "No source directories matched under $rootPath (expected Il2Cpp*/Unity*)."
|
|
}
|
|
|
|
return $dirs
|
|
}
|
|
|
|
function Get-AssemblyNameFromPath([string]$filePath, [string]$rootPath) {
|
|
$relative = [System.IO.Path]::GetRelativePath($rootPath, $filePath)
|
|
if ([string]::IsNullOrWhiteSpace($relative)) { return 'Assembly-CSharp' }
|
|
|
|
$parts = $relative -split '[\\/]'
|
|
$first = if ($parts.Length -gt 0) { $parts[0] } else { '' }
|
|
if ([string]::IsNullOrWhiteSpace($first)) { return 'Assembly-CSharp' }
|
|
|
|
return $first
|
|
}
|
|
|
|
function Test-SkipInteropSignature([string]$returnType, [string]$argList) {
|
|
$blob = "$returnType $argList"
|
|
if ($blob -match 'EntityCommandBuffer|SystemState|BlobArray|ComponentLookup|BufferLookup') { return $true }
|
|
if ($blob -match '\bEntity\b') { return $true }
|
|
if ($blob -match 'RaycastHit|TextMeshProUGUI|Il2CppStructArray|HashSet<') { return $true }
|
|
return $false
|
|
}
|
|
|
|
function Normalize-HookParamType([string]$pt) {
|
|
$t = $pt.Trim()
|
|
if ($t.StartsWith('Il2CppSystem.Collections.Generic.')) { return $t }
|
|
$t = $t -replace '(?<![\w.])List<', 'Il2CppSystem.Collections.Generic.List<'
|
|
$t = $t -replace '(?<![\w.])Dictionary<', 'Il2CppSystem.Collections.Generic.Dictionary<'
|
|
return $t
|
|
}
|
|
|
|
function Test-ShouldEmitHook([string]$methodName, [string]$returnType) {
|
|
if ($methodName -in @('Update', 'FixedUpdate', 'LateUpdate', 'OnUpdate')) { return $false }
|
|
if ($methodName -eq '.ctor' -or $methodName.StartsWith('op_')) { return $false }
|
|
if ($methodName.Contains('__') -or $methodName.Contains('codegen') -or $methodName.Contains('MethodInternalStatic')) { return $false }
|
|
if ($methodName.StartsWith('get_') -or $methodName.StartsWith('set_')) { return $false }
|
|
$rt = $returnType.Trim()
|
|
if ($rt -eq 'IEnumerator' -or $rt.StartsWith('IEnumerator')) { return $false }
|
|
if ($methodName.StartsWith('Get') -and $rt -ne 'void') {
|
|
if ($methodName -in @('GetQueuedJobs', 'GetActiveJobs')) { return $false }
|
|
}
|
|
return $true
|
|
}
|
|
|
|
function Split-ArgSegments([string]$argListText) {
|
|
$segments = [System.Collections.Generic.List[string]]::new()
|
|
if ([string]::IsNullOrWhiteSpace($argListText)) { return $segments }
|
|
$depth = 0
|
|
$sb = [System.Text.StringBuilder]::new()
|
|
for ($i = 0; $i -lt $argListText.Length; $i++) {
|
|
$c = $argListText[$i]
|
|
if ($c -eq '<') { $depth++ }
|
|
elseif ($c -eq '>') { $depth-- }
|
|
elseif ($c -eq ',' -and $depth -eq 0) {
|
|
[void]$segments.Add($sb.ToString().Trim())
|
|
[void]$sb.Clear()
|
|
continue
|
|
}
|
|
[void]$sb.Append($c)
|
|
}
|
|
if ($sb.Length -gt 0) { [void]$segments.Add($sb.ToString().Trim()) }
|
|
return $segments
|
|
}
|
|
|
|
function Get-Il2CppPatchSignature([string]$className, [string]$methodName, [string]$argListText) {
|
|
if ([string]::IsNullOrWhiteSpace($argListText)) { return "Il2Cpp.$className::$methodName()" }
|
|
$parts = Split-ArgSegments $argListText
|
|
$types = foreach ($p in $parts) {
|
|
$t = ($p -replace '\s*=\s*[^,)]+$', '').Trim()
|
|
if ($t.Length -eq 0) { continue }
|
|
$t = $t -replace '^\s*(ref|out|in|readonly)\s+', ''
|
|
$sp = $t -split '\s+'
|
|
if ($sp.Length -lt 2) { continue }
|
|
($sp[0..($sp.Length - 2)] -join ' ').Trim()
|
|
}
|
|
return "Il2Cpp.$className::$methodName($($types -join ', '))"
|
|
}
|
|
|
|
function Split-MethodLine([string]$line, [ref]$isStatic) {
|
|
$isStatic.Value = $false
|
|
$m = [regex]::Match($line, '^\s+(?:public|private|protected|internal)\s+(?<mods>(?:(?:static|unsafe|virtual|override|abstract|sealed|new|extern|partial)\s+)*)?(?<sig>.+?)\s*\((?<args>[^\)]*)\)\s*$')
|
|
if (-not $m.Success) { return $null }
|
|
|
|
$mods = $m.Groups['mods'].Value
|
|
if ($mods -match '\bstatic\b') { $isStatic.Value = $true }
|
|
|
|
$sig = $m.Groups['sig'].Value.Trim()
|
|
$argList = $m.Groups['args'].Value.Trim()
|
|
$idx = $sig.LastIndexOf(' ')
|
|
if ($idx -lt 0) { return $null }
|
|
$ret = $sig.Substring(0, $idx).Trim()
|
|
$name = $sig.Substring($idx + 1).Trim()
|
|
return [pscustomobject]@{ ReturnType = $ret; Name = $name; ArgList = $argList }
|
|
}
|
|
|
|
function Build-HarmonyParamDecl([string]$className, [bool]$isStatic, [string]$argList, [bool]$includeResultRef) {
|
|
$plist = @()
|
|
if (-not $isStatic) { $plist += "$className __instance" }
|
|
if ($includeResultRef) { $plist += 'ref bool __result' }
|
|
if (-not [string]::IsNullOrWhiteSpace($argList)) {
|
|
foreach ($segment in (Split-ArgSegments $argList)) {
|
|
$t = ($segment -replace '\s*=\s*[^,)]+$', '').Trim()
|
|
if ($t.Length -eq 0) { continue }
|
|
$tokens = @($t -split '\s+' | Where-Object { $_ -notin @('ref', 'out', 'in', 'readonly') })
|
|
if ($tokens.Length -lt 2) { continue }
|
|
$pn = $tokens[-1]
|
|
$pt = Normalize-HookParamType (($tokens[0..($tokens.Length - 2)] -join ' '))
|
|
$plist += "$pt $pn"
|
|
}
|
|
}
|
|
return ($plist -join ', ')
|
|
}
|
|
|
|
function Build-EmitAnonymousBody([string]$className, [bool]$isStatic) {
|
|
if ($className -eq 'Player' -and -not $isStatic) {
|
|
return @'
|
|
money = __instance.money,
|
|
reputation = __instance.reputation,
|
|
xp = __instance.xp,
|
|
'@
|
|
}
|
|
if (-not $isStatic) {
|
|
return ' instance = __instance,'
|
|
}
|
|
return ' type = typeof(' + $className + '),'
|
|
}
|
|
|
|
$hooks = [System.Collections.Generic.List[object]]::new()
|
|
$byDomain = @{}
|
|
$scanDirectories = Get-SourceDirectories $assemblyRoot
|
|
$sourceFiles = $scanDirectories | ForEach-Object { Get-ChildItem -Path $_ -Filter '*.cs' -File -Recurse }
|
|
|
|
foreach ($sourceFile in $sourceFiles) {
|
|
$assemblyName = Get-AssemblyNameFromPath $sourceFile.FullName $assemblyRoot
|
|
$lines = Get-Content $sourceFile.FullName
|
|
$harmonyOverloadFirst = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
|
|
$currentClass = $null
|
|
$currentBase = ''
|
|
$classRegex = [regex]'^\s*(?:public|private|protected|internal)\s+(?:sealed\s+)?class\s+(?<name>[A-Za-z0-9_]+)(\s*:\s*(?<base>[^\r\n{]+))?'
|
|
|
|
foreach ($line in $lines) {
|
|
$cm = $classRegex.Match($line)
|
|
if ($cm.Success) {
|
|
$cn = $cm.Groups['name'].Value
|
|
$cb = $cm.Groups['base'].Value.Trim()
|
|
$currentClass = $cn
|
|
$currentBase = $cb
|
|
continue
|
|
}
|
|
if ($null -eq $currentClass) { continue }
|
|
|
|
$staticFlag = $false
|
|
$parsed = Split-MethodLine $line ([ref]$staticFlag)
|
|
if ($null -eq $parsed) { continue }
|
|
|
|
$ret = $parsed.ReturnType
|
|
$mn = $parsed.Name
|
|
$argList = $parsed.ArgList
|
|
|
|
$domain = Get-GregDomain $currentClass
|
|
$action = Get-SemanticAction $currentClass $mn
|
|
$strategy = 'Postfix'
|
|
|
|
$hookName = "greg.$($domain.ToUpperInvariant()).$action"
|
|
$patchTarget = Get-Il2CppPatchSignature $currentClass $mn $argList
|
|
|
|
$payload = [ordered]@{ }
|
|
if ($currentClass -eq 'Player' -and $mn -in @('UpdateCoin', 'UpdateReputation', 'UpdateXP', 'WarpPlayer', 'DropAllItems', 'LoadPlayer', 'Start')) {
|
|
if ($mn -eq 'UpdateCoin') { $payload['coinChangeAmount'] = 'float'; $payload['withoutSound'] = 'bool'; $payload['newBalance'] = 'float'; $payload['accepted'] = 'bool' }
|
|
elseif ($mn -eq 'UpdateReputation') { $payload['amount'] = 'float'; $payload['reputation'] = 'float' }
|
|
elseif ($mn -eq 'UpdateXP') { $payload['amount'] = 'float'; $payload['xp'] = 'float'; $payload['accepted'] = 'bool' }
|
|
elseif ($mn -eq 'WarpPlayer') { $payload['position'] = 'Vector3'; $payload['rotation'] = 'Quaternion' }
|
|
elseif ($mn -eq 'LoadPlayer') { $payload['data'] = 'PlayerData' }
|
|
else { $payload['money'] = 'float'; $payload['reputation'] = 'float'; $payload['xp'] = 'float' }
|
|
}
|
|
elseif ($currentClass -eq 'TechnicianManager') {
|
|
if ($mn -eq 'AddTechnician') { $payload['technician'] = 'Technician' }
|
|
elseif ($mn -eq 'FireTechnician') { $payload['technicianID'] = 'int' }
|
|
elseif ($mn -eq 'SendTechnician') { $payload['networkSwitch'] = 'NetworkSwitch'; $payload['server'] = 'Server' }
|
|
elseif ($mn -eq 'EnqueueDispatch') { $payload['job'] = 'TechnicianManager.RepairJob' }
|
|
elseif ($mn -eq 'RestoreJobQueue') { $payload['savedJobs'] = 'List<RepairJobSaveData>' }
|
|
elseif ($mn -eq 'RequestNextJob') { $payload['technician'] = 'Technician' }
|
|
elseif ($mn -eq 'IsDeviceAlreadyAssigned') { $payload['networkSwitch'] = 'NetworkSwitch'; $payload['server'] = 'Server'; $payload['assigned'] = 'bool' }
|
|
elseif ($mn -eq 'Awake') { $payload['queuedJobCount'] = 'int' }
|
|
}
|
|
elseif ($currentClass -eq 'PacketSpawnerSystem' -and $mn -eq 'SpawnPacket') {
|
|
$payload['ecb'] = 'EntityCommandBuffer'
|
|
$payload['spawner'] = 'PacketSpawnerComponent'
|
|
$payload['spawnerIndex'] = 'int'
|
|
$payload['waypoints'] = 'BlobArray<float3>'
|
|
}
|
|
else {
|
|
$payload['method'] = 'string'
|
|
}
|
|
|
|
[void]$hooks.Add([ordered]@{
|
|
name = $hookName
|
|
legacy = $null
|
|
patchTarget = $patchTarget
|
|
assembly = $assemblyName
|
|
strategy = $(if ($strategy -eq 'PrefixPostfix') { 'Prefix+Postfix' } else { 'Postfix' })
|
|
description = "Auto-generated from IL2CPP sources: $assemblyName/$currentClass.$mn"
|
|
payloadSchema = $payload
|
|
})
|
|
|
|
$harmonySignatureCompatible = $line -match '^\s+public unsafe(?:\s+static)?\s+'
|
|
$allowHarmonyEmit = $harmonyEmitClasses.Contains($currentClass) -and -not (Test-SkipTypeName $currentClass) -and -not (Test-SkipClass $currentClass $currentBase) -and $harmonySignatureCompatible
|
|
if ($allowHarmonyEmit -and (Test-ShouldEmitHook $mn $ret) -and -not (Test-SkipInteropSignature $ret $argList) -and -not $excluded.Contains("$currentClass|$mn")) {
|
|
$harmonyOvKey = "$currentClass|$mn"
|
|
if ($harmonyOverloadFirst.Contains($harmonyOvKey)) { continue }
|
|
[void]$harmonyOverloadFirst.Add($harmonyOvKey)
|
|
|
|
$harmonyStrategy = Get-HookStrategy $mn $ret
|
|
if ($harmonyStrategy -eq 'None') { continue }
|
|
|
|
if (-not $byDomain.ContainsKey($domain)) { $byDomain[$domain] = [System.Collections.Generic.List[object]]::new() }
|
|
[void]$byDomain[$domain].Add([ordered]@{
|
|
Class = $currentClass
|
|
Method = $mn
|
|
Ret = $ret
|
|
Args = $argList
|
|
Action = $action
|
|
Strategy = $harmonyStrategy
|
|
HookName = $hookName
|
|
IsStatic = $staticFlag
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
$seen = @{}
|
|
$unique = [System.Collections.Generic.List[object]]::new()
|
|
foreach ($h in $hooks) {
|
|
$k = "$($h.assembly)|$([string]$h.patchTarget)"
|
|
if ($seen.ContainsKey($k)) { continue }
|
|
$seen[$k] = $true
|
|
[void]$unique.Add($h)
|
|
}
|
|
|
|
$doc = [ordered]@{
|
|
version = 2
|
|
description = 'Canonical greg hook registry. Schema: greg.<DOMAIN>.<Action>. Generated from Il2Cpp C# unpack; regenerate with gregCore/scripts/Generate-GregHooksFromIl2CppDump.ps1 when MergedCode.md / interop changes.'
|
|
generatedFrom = 'gregReferences/Assembly-CSharp/{Il2Cpp*,Unity*,UnityEngine*}/**/*.cs'
|
|
legacyPrefixes = @()
|
|
hooks = $unique
|
|
}
|
|
|
|
$json = $doc | ConvertTo-Json -Depth 20
|
|
[System.IO.File]::WriteAllText($outJsonRoot, $json, [System.Text.UTF8Encoding]::new($false))
|
|
[System.IO.File]::WriteAllText($outJsonFramework, $json, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "Wrote $($unique.Count) hooks to $outJsonRoot"
|
|
|
|
$domainEnumMap = @{
|
|
Player = 'Player'; Employee = 'Employee'; Customer = 'Customer'; Server = 'Server'
|
|
Rack = 'Rack'; Network = 'Network'; Power = 'Power'; Gameplay = 'Gameplay'; Ui = 'Ui'; System = 'System'
|
|
}
|
|
|
|
foreach ($kv in $byDomain.GetEnumerator()) {
|
|
$d = $kv.Key
|
|
$items = $kv.Value
|
|
if ($items.Count -eq 0) { continue }
|
|
|
|
$gregDomain = $domainEnumMap[$d]
|
|
$className = "Greg$($d)Hooks"
|
|
$sb = [System.Text.StringBuilder]::new()
|
|
[void]$sb.AppendLine('using System;')
|
|
[void]$sb.AppendLine('using HarmonyLib;')
|
|
[void]$sb.AppendLine('using greg.Core;')
|
|
[void]$sb.AppendLine('using greg.Sdk;')
|
|
[void]$sb.AppendLine('using Il2Cpp;')
|
|
[void]$sb.AppendLine('using Il2CppSystem.Collections.Generic;')
|
|
[void]$sb.AppendLine('using Il2CppInterop.Runtime.InteropTypes.Arrays;')
|
|
[void]$sb.AppendLine('using MelonLoader;')
|
|
[void]$sb.AppendLine('using UnityEngine;')
|
|
[void]$sb.AppendLine('')
|
|
[void]$sb.AppendLine('namespace gregFramework.Hooks;')
|
|
[void]$sb.AppendLine('')
|
|
[void]$sb.AppendLine('/// <summary>')
|
|
[void]$sb.AppendLine("/// Harmony hooks for domain $d (generated from Il2Cpp unpack).")
|
|
[void]$sb.AppendLine('/// </summary>')
|
|
[void]$sb.AppendLine('[HarmonyPatch]')
|
|
[void]$sb.AppendLine("internal static class $className")
|
|
[void]$sb.AppendLine('{')
|
|
|
|
foreach ($it in $items) {
|
|
$cn = $it.Class
|
|
$mn = $it.Method
|
|
$argList = $it.Args
|
|
$action = $it.Action
|
|
$strat = $it.Strategy
|
|
$isSt = [bool]$it.IsStatic
|
|
$handler = "On$cn$mn"
|
|
|
|
[void]$sb.AppendLine(" // $cn.$mn")
|
|
[void]$sb.AppendLine(" [HarmonyPatch(typeof($cn), nameof($cn.$mn))]")
|
|
|
|
if ($strat -eq 'PrefixPostfix') {
|
|
$pfxParams = Build-HarmonyParamDecl $cn $isSt $argList $true
|
|
$postParams = Build-HarmonyParamDecl $cn $isSt $argList $true
|
|
[void]$sb.AppendLine(' [HarmonyPrefix]')
|
|
[void]$sb.AppendLine(" private static void ${handler}_Prefix($pfxParams)")
|
|
[void]$sb.AppendLine(' {')
|
|
[void]$sb.AppendLine(' // Prefix slot for cancellable bool flows; return false from a conditional transpiler if you skip the original.')
|
|
[void]$sb.AppendLine(' }')
|
|
[void]$sb.AppendLine('')
|
|
[void]$sb.AppendLine(' [HarmonyPostfix]')
|
|
[void]$sb.AppendLine(" private static void ${handler}_Postfix($postParams)")
|
|
}
|
|
else {
|
|
$postParams = Build-HarmonyParamDecl $cn $isSt $argList $false
|
|
[void]$sb.AppendLine(' [HarmonyPostfix]')
|
|
[void]$sb.AppendLine(" private static void $handler($postParams)")
|
|
}
|
|
|
|
$emitBody = Build-EmitAnonymousBody $cn $isSt
|
|
[void]$sb.AppendLine(' {')
|
|
[void]$sb.AppendLine(' try')
|
|
[void]$sb.AppendLine(' {')
|
|
[void]$sb.AppendLine(' gregEventDispatcher.Emit(')
|
|
[void]$sb.AppendLine(" gregHookName.Create(GregDomain.$gregDomain, `"$action`"),")
|
|
[void]$sb.AppendLine(' new')
|
|
[void]$sb.AppendLine(' {')
|
|
[void]$sb.AppendLine($emitBody)
|
|
[void]$sb.AppendLine(' });')
|
|
[void]$sb.AppendLine(' }')
|
|
[void]$sb.AppendLine(' catch (System.Exception ex)')
|
|
[void]$sb.AppendLine(' {')
|
|
[void]$sb.AppendLine(' MelonLogger.Warning($"[gregCore] Hook ' + $handler + ' failed: {ex.Message}");')
|
|
[void]$sb.AppendLine(' }')
|
|
[void]$sb.AppendLine(' }')
|
|
[void]$sb.AppendLine('')
|
|
}
|
|
|
|
[void]$sb.AppendLine('}')
|
|
$outFile = Join-Path $outHooksDir "Greg$($d)Hooks.cs"
|
|
[System.IO.File]::WriteAllText($outFile, $sb.ToString(), [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "Wrote $outFile ($($items.Count) patches)"
|
|
}
|
|
|
|
# Power domain stub if absent
|
|
$powerFile = Join-Path $outHooksDir 'GregPowerHooks.cs'
|
|
if (-not $byDomain.ContainsKey('Power') -or $byDomain['Power'].Count -eq 0) {
|
|
$stub = @'
|
|
using HarmonyLib;
|
|
|
|
namespace gregFramework.Hooks;
|
|
|
|
/// <summary>
|
|
/// Reserved for Power / UPS / PDU hooks once matching Il2Cpp surface is classified.
|
|
/// </summary>
|
|
[HarmonyPatch]
|
|
internal static class GregPowerHooks
|
|
{
|
|
}
|
|
'@
|
|
[System.IO.File]::WriteAllText($powerFile, $stub, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host "Wrote stub $powerFile"
|
|
}
|
|
|
|
|