Skip to content

Instantly share code, notes, and snippets.

@artchen
Created March 15, 2026 07:09
Show Gist options
  • Select an option

  • Save artchen/07a14bbc1d589196d0da65169a81bca3 to your computer and use it in GitHub Desktop.

Select an option

Save artchen/07a14bbc1d589196d0da65169a81bca3 to your computer and use it in GitHub Desktop.
Extract reverse 1999 summon url
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Add-Type -AssemblyName System.Web
$ProgressPreference = 'SilentlyContinue'
Write-Output "Preparing to extract Reverse: 1999 Summon URL from memory..."
$processName = "reverse1999"
$process = Get-Process -Name $processName -ErrorAction SilentlyContinue
if (-Not $process) {
Write-Output "Reverse: 1999 is not running!"
Write-Output "Please open the game and navigate to the 'Summon Review' history page first."
return
}
$process = $process[0]
Write-Output "Game process found (PID: $($process.Id))."
Write-Output "Warning: This script will temporarily create a very large memory dump file (several gigabytes)."
Write-Output "Please ensure you have sufficient free space on your C: drive."
# Dynamically generate class name so we can re-run script in same window
$ScannerClassName = "MemoryScanner_$([Guid]::NewGuid().ToString('N'))"
$MemoryScannerCode = @"
using System;
using System.Runtime.InteropServices;
using System.IO;
public class $ScannerClassName {
[DllImport("Dbghelp.dll", SetLastError = true)]
public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, IntPtr hFile, int DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
public static int IndexOf(byte[] haystack, int haystackLength, byte[] needle, int startIndex) {
if (needle.Length == 0 || haystackLength == 0 || haystackLength < needle.Length || startIndex >= haystackLength) return -1;
int[] badCharSkip = new int[256];
for (int i = 0; i < 256; i++) badCharSkip[i] = needle.Length;
for (int i = 0; i < needle.Length - 1; i++) badCharSkip[needle[i]] = needle.Length - 1 - i;
int offset = startIndex;
int maxOffset = haystackLength - needle.Length;
while (offset <= maxOffset) {
int scan = needle.Length - 1;
while (haystack[offset + scan] == needle[scan]) {
if (scan == 0) return offset;
scan--;
}
offset += badCharSkip[haystack[offset + needle.Length - 1]];
}
return -1;
}
}
"@
try {
Add-Type -TypeDefinition $MemoryScannerCode
} catch {
Write-Output "Failed to compile memory scanner tools."
return
}
$dumpPath = [IO.Path]::Combine([IO.Path]::GetTempPath(), "reverse1999_summon_dump.dmp")
Write-Output "Creating process memory dump to $dumpPath ..."
Write-Output "Depending on your hardware, this may take 10-30 seconds..."
$fsDump = $null
$dumpSuccess = $false
try {
$fsDump = [System.IO.File]::Create($dumpPath)
$hFile = $fsDump.SafeFileHandle.DangerousGetHandle()
$MiniDumpWithFullMemory = 2
$handle = $process.Handle
$type = ("$ScannerClassName" -as [type])
$dumpSuccess = $type::MiniDumpWriteDump($handle, $process.Id, $hFile, $MiniDumpWithFullMemory, [IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero)
} catch {
Write-Output ""
Write-Output "[ERROR] Access Denied: Could not read Reverse: 1999 process memory."
Write-Output "Please reopen PowerShell by right clicking and selecting 'Run as Administrator', then run the script again."
return
} finally {
if ($fsDump -ne $null) {
$fsDump.Close()
$fsDump.Dispose()
}
}
if (-Not $dumpSuccess) {
Write-Output "Failed to create memory dump."
Write-Output "Please ensure you run this script as Administrator."
return
}
Write-Output "Memory dump created successfully!"
Write-Output "Scanning binary dump for the summon URL... this may take a moment."
$fs = $null
try {
$fs = [System.IO.FileStream]::new($dumpPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
$bufferSize = 50 * 1024 * 1024 # 50 MB chunk sizes
$buffer = New-Object byte[] $bufferSize
# Depending on how the game stores strings in RAM, it could be ASCII/UTF-8 or UTF-16 (Unicode).
# We will search for both encodings.
$targetString = "com/query/summon"
$patternAscii = [System.Text.Encoding]::UTF8.GetBytes($targetString)
$patternUnicode = [System.Text.Encoding]::Unicode.GetBytes($targetString)
$overlap = [Math]::Max($patternAscii.Length, $patternUnicode.Length)
$bytesRead = 0
$foundUrl = $null
while (($bytesRead = $fs.Read($buffer, 0, $bufferSize)) -gt 0) {
$matchIdx = 0
while ($matchIdx -lt $bytesRead -and $matchIdx -ne -1) {
$type = ("$ScannerClassName" -as [type])
$matchIdxA = $type::IndexOf($buffer, $bytesRead, $patternAscii, $matchIdx)
$matchIdxU = $type::IndexOf($buffer, $bytesRead, $patternUnicode, $matchIdx)
$matchIdxTmp = -1
$isUnicode = $false
# Find the earliest match in the chunk
if ($matchIdxA -ne -1 -and ($matchIdxU -eq -1 -or $matchIdxA -lt $matchIdxU)) {
$matchIdxTmp = $matchIdxA
} elseif ($matchIdxU -ne -1) {
$matchIdxTmp = $matchIdxU
$isUnicode = $true
}
if ($matchIdxTmp -ne -1) {
# Expand a window around the match
$startWindow = [Math]::Max(0, $matchIdxTmp - 1000)
$endWindow = [Math]::Min($bytesRead, $matchIdxTmp + 2000)
$windowLen = $endWindow - $startWindow
$windowBytes = New-Object byte[] $windowLen
[Array]::Copy($buffer, $startWindow, $windowBytes, 0, $windowLen)
if ($isUnicode) {
$textWindow = [System.Text.Encoding]::Unicode.GetString($windowBytes)
} else {
$textWindow = [System.Text.Encoding]::UTF8.GetString($windowBytes)
}
# Regex capture for full https url terminating at whitespace/null
# Use single quotes to prevent PowerShell from trying to parse $ or other symbols inside the regex
if ($textWindow -match '(https?://[a-zA-Z0-9\-\.]*com/query/summon[a-zA-Z0-9\-\._~:/?#\[\]@!$&''()*+,;="%]+)') {
$foundUrl = $matches[1]
break
} else {
# Print a bit of the window for debugging if we found the anchor but regex failed
$cleanWindow = $textWindow -replace "[\0\r\n\t]", " "
$idx = $cleanWindow.IndexOf("com/query/summon")
if ($idx -gt 0) {
$startPrint = [Math]::Max(0, $idx - 100)
$lenPrint = [Math]::Min($cleanWindow.Length - $startPrint, 250)
Write-Output "[DEBUG] Found anchor but regex failed. Context:"
Write-Output $cleanWindow.Substring($startPrint, $lenPrint)
}
}
# Advance index past this match if it was a false positive
if ($isUnicode) {
$matchIdx = $matchIdxTmp + $patternUnicode.Length
} else {
$matchIdx = $matchIdxTmp + $patternAscii.Length
}
} else {
break
}
}
if ($foundUrl) { break }
if ($bytesRead -eq $bufferSize) {
$fs.Seek(-$overlap, [System.IO.SeekOrigin]::Current) | Out-Null
}
}
if ($foundUrl) {
try {
# Strip Unity HTML escape characters if present in raw memory
$foundUrl = $foundUrl -replace "&amp;", "&"
$uri = [Uri]$foundUrl
$query = [Web.HttpUtility]::ParseQueryString($uri.Query)
$keys = @($query.AllKeys)
foreach ($key in $keys) {
if ($key -eq "userId") { continue }
if ($key -eq "token") { continue }
$query.Remove($key)
}
$latest_url = $uri.Scheme + "://" + $uri.Host + $uri.AbsolutePath + "?" + $query.ToString()
Write-Output "`n[SUCCESS] Summon History URL Found!"
Write-Output $latest_url
Set-Clipboard -Value $latest_url
Write-Output "The URL has been cleanly formatted and saved to your clipboard!"
} catch {
Write-Output "Found a matching URL but failed to parse it: $foundUrl"
}
} else {
Write-Output "`nCould not locate Summon History URL in process memory."
Write-Output "Please make sure you have opened the 'Summon Review' page IN-GAME right before running this script."
}
} finally {
if ($fs -ne $null) {
$fs.Close()
$fs.Dispose()
}
if (Test-Path $dumpPath) {
Write-Output "Cleaning up multi-gigabyte temporary dump file..."
Remove-Item -Path $dumpPath -Force -ErrorAction SilentlyContinue
}
}

Comments are disabled for this gist.