# New School 99 log collector, version 3 (2026-10-27). Guide: https://newschool99.com/help/logs # It READS New School 99's own files and the crash records Windows keeps for the game, hides private details, # and writes ONE zip file on your Desktop. It sends nothing anywhere and changes nothing else on your PC. # You decide whether to post that zip in #bug-reports on the New School 99 Discord. if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { Write-Host '' Write-Host 'This PC does not let the log tool run (PowerShell is locked down here). You can still send your logs by hand:' -ForegroundColor Yellow Write-Host '1. Press the Windows key and R together, type %LOCALAPPDATA%\NewSchool99\logs and press Enter.' Write-Host '2. Drag client.log, client.1.log and launcher.log into a new post in #bug-reports on our Discord.' Write-Host ' Those files can show your Windows user name and other players'' names. Open them in Notepad first if that matters to you.' } else { & { $ErrorActionPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue' $ver = 3 $now = Get-Date $since = $now.AddDays(-30) $prof = $env:USERPROFILE $la = $env:LOCALAPPDATA $ns = Join-Path $prof '.newschool99' $lad = Join-Path $la 'NewSchool99' $app = Join-Path $la 'New School 99' $desk = [Environment]::GetFolderPath('Desktop') $files = New-Object Collections.ArrayList $seen = @{} $sys = New-Object Collections.ArrayList $glance = New-Object Collections.ArrayList $skippedHs = 0 # Names that must not leave this PC: the Windows user, the PC name (both the short COMPUTERNAME and the full # DNS name) and the game login the game remembers (read only to hide it; it is never copied into the zip). $hide = @{} $hide[$env:USERNAME] = '' $hide[$env:COMPUTERNAME] = '' $dns = [Net.Dns]::GetHostName() if ($dns) { $hide[$dns] = '' } $acct = (Get-ItemProperty -LiteralPath 'HKCU:\Software\JavaSoft\Prefs\com\darkan\newschool99\pregame').username if ($acct) { $hide[($acct -replace '/([A-Z])', '$1')] = '' } foreach ($f in (Join-Path $ns 'update-login.properties'), (Join-Path $prof '.darkanrs\update-login.properties')) { Get-Content -LiteralPath $f | ForEach-Object { if ($_ -match '^\s*username\s*[=:]\s*(.+)$') { $hide[($matches[1].Trim() -replace '\\(.)', '$1')] = '' } } } function Clean([string]$t) { if (-not $t) { return '' } $t = [regex]::Replace($t, '\x1B\[[0-9;?]*[ -/]*[@-~]', '') foreach ($h in @($prof, $prof.Replace('\', '/'), $prof.Replace('\', '\\'))) { if ($h.Length -gt 3) { $t = [regex]::Replace($t, [regex]::Escape($h), '%USERPROFILE%', 'IgnoreCase') } } $t = [regex]::Replace($t, '(?i)\b[a-z]:[\\/]+(?:users|documents and settings)[\\/]+[^\\/\r\n"<>|:*?]+?(?=[\\/])', '%USERPROFILE%') # URL-encoded profile path (X%3A%5CUsers%5CJane+Smith...) that the plain path rules and the name rule miss. $t = [regex]::Replace($t, '(?i)\b[a-z](?::|%3A)(?:[\\/]|%5C|%2F)+users(?:[\\/]|%5C|%2F)+(?:(?!%5C|%2F)[^\\/\s"<>|:*?])+', '%USERPROFILE%') foreach ($k in ($hide.Keys | Sort-Object Length -Descending)) { if ($k -and $k.Trim().Length -ge 3) { $p = [regex]::Escape($k.Trim()) -replace '(\\ |_)', '[ _]'; $t = [regex]::Replace($t, '(?i)(?$2') $t = [regex]::Replace($t, '(?m)(\[Combat\] auto-play .+? on ).+$', '$1') $t = [regex]::Replace($t, '(?') $t = [regex]::Replace($t, '(?') $t = [regex]::Replace($t, '(?i)(?') $t = [regex]::Replace($t, '(?i)(?') $t = [regex]::Replace($t, '(?i)\b(\w*(?:password|passwd|token|secret)\w*|pwd|api[_-]?key|session[_-]?(?:id|key)|cookie)\b(["'']?\s*[=:]\s*)("[^"]*"|''[^'']*''|\S+)', '$1$2') # Space-separated command-line form: --accessToken , --password , -secretKey . $t = [regex]::Replace($t, '(?i)--?\w*(?:token|password|secret|key)\w*\s+\S+', '') $t = [regex]::Replace($t, '(?i)\b(bearer|basic)\s+\S{8,}', '$1 ') # The launcher's relaunch blob is one URL-encoded argument list joined with spaces; remove the whole thing. $t = [regex]::Replace($t, '(--ns99-relaunch=|ns99\.launcher\.relaunch=)\S*(?: [^\s=:\\/]+)*', '$1') return $t } function S([string]$x) { [void]$sys.Add($x) } function G([string]$x) { [void]$glance.Add($x) } # What a crashing module most likely is, in plain words. function Kind([string]$m) { if ($m -match '(?i)^(nvoglv|nvd3dum|nvwgf|atio6axx|atioglxx|atig6pxx|amdxc|amdihk|ig\w*icd|igxelpicd|igc|opengl32|dxgi|d3d)') { return ' (graphics driver)' } if ($m -match '(?i)^lwjgl') { return ' (graphics library)' } if ($m -match '(?i)^(jni)?(avcodec|avformat|avutil|swscale|swresample)') { return ' (video player)' } if ($m -match '(?i)^(jvm|java)\.dll') { return ' (Java itself)' } return '' } # Text logs are in the Windows code page (the game's console log), UTF-8 (crash reports) or UTF-16 (Windows reports). $ansi = [Text.Encoding]::Default if ($ansi.CodePage -eq 65001) { $ansi = [Text.Encoding]::GetEncoding(28591) } function Decode([byte[]]$b) { if ($b.Length -ge 2 -and $b[0] -eq 0xFF -and $b[1] -eq 0xFE) { return ([Text.Encoding]::Unicode.GetString($b, 2, $b.Length - 2)).TrimStart([char]0xFEFF) } try { return ((New-Object Text.UTF8Encoding($false, $true)).GetString($b)).TrimStart([char]0xFEFF) } catch { return ($ansi.GetString($b)).TrimStart([char]0xFEFF) } } # These sections of a Java crash file can print raw memory and Java strings (including a live password). Removed by # content, so an hs_err embedded in an old -native crash report is trimmed too, wherever it sits. $hsHead = '(?im)^(?:A fatal error has been detected|#\s*A fatal error|Register to memory mapping|Stack slot to memory mapping|Top of Stack|Instructions)' function TrimHs([string]$t) { $t = [regex]::Replace($t, '(?ms)^Environment Variables:.*?(?:\r?\n[ \t]*\r?\n|\z)', "Environment Variables: (removed)`r`n`r`n") $t = [regex]::Replace($t, '(?ms)^Register to memory mapping:.*?(?=^-{10,}\s+P R O C E S S|\z)', "Register to memory mapping, top of stack, instructions: (removed)`r`n`r`n") $t = [regex]::Replace($t, '(?ms)^(Top of Stack|Instructions|Stack slot to memory mapping):.*?(?=^\r?$)', '$1: (removed)') # Dynamic libraries: keep the file name only for anything outside Windows, DriverStore, the NS99 folders and the Java runtime. $t = [regex]::Replace($t, '(?im)^(0x[0-9a-f]+\s+-\s+0x[0-9a-f]+\s+)(\S:[\\/]\S.*)$', { param($x) $path = $x.Groups[2].Value if ($path -match '(?i)\\(windows|system32|driverstore)\\|new ?school ?99|newschool99|\.newschool99|\\darkan|\\jdk|\\jre|\\runtime\\|\\java\b') { $x.Value } else { $x.Groups[1].Value + (Split-Path -Leaf $path) } }) return $t } # Report.wer lists every DLL in the game process and the OS geoid (country). Keep only an allow-list; LoadedModule by name. function TrimWer([string]$t) { $keep = New-Object Collections.ArrayList foreach ($line in ($t -split '\r?\n')) { if ($line -match '^(EventTime|EventType|FriendlyEventName|AppName|AppPath|OriginalFilename|Sig\[\d+\](?:\.\w+)?|DynamicSig\[\d+\](?:\.\w+)?)\s*=') { [void]$keep.Add($line) } elseif ($line -match '^(LoadedModule\[\d+\])\s*=\s*(.+)$') { [void]$keep.Add($matches[1] + '=' + (Split-Path -Leaf $matches[2].Trim())) } } return ($keep -join "`r`n") } # Reads one text file (with full sharing, so a running game's log still opens), keeps only the first 200 KB and the # last 2.5 MB of large files, and stores it with private details hidden. function Add([string]$src, [string]$rel, [switch]$hs, [switch]$wer) { if (-not $src -or $seen[$rel] -or -not (Test-Path -LiteralPath $src -PathType Leaf)) { return } try { $headN = 200000 $tailN = 2500000 $fs = [IO.File]::Open($src, 'Open', 'Read', 'ReadWrite, Delete') try { $len = $fs.Length if ($len -le ($headN + $tailN)) { $buf = New-Object byte[] $len [void]$fs.Read($buf, 0, $len) $t = Decode $buf } else { $head = New-Object byte[] $headN [void]$fs.Read($head, 0, $headN) $tail = New-Object byte[] $tailN [void]$fs.Seek($len - $tailN, 'Begin') [void]$fs.Read($tail, 0, $tailN) $t = (Decode $head) + "`r`n...[cut " + ($len - $headN - $tailN) + " bytes]...`r`n" + (Decode $tail) } } finally { $fs.Dispose() } if ($wer) { $t = TrimWer $t } elseif ($hs -or [regex]::IsMatch($t, $hsHead)) { $t = TrimHs $t } $i = Get-Item -LiteralPath $src -Force [void]$files.Add(@($rel, (Clean $t), ('{0} ({1} KB, changed {2})' -f $rel, [math]::Ceiling($i.Length / 1KB), $i.LastWriteTime.ToString('yyyy-MM-dd HH:mm')))) $seen[$rel] = $true } catch { } } # The newest files matching a name in some folders, changed in the last 30 days. function Newest($dirs, [string]$filter, [int]$n) { $dirs | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container) } | ForEach-Object { Get-ChildItem -LiteralPath $_ -Filter $filter -File -Force } | Where-Object { $_.LastWriteTime -gt $since } | Sort-Object FullName -Unique | Sort-Object LastWriteTime -Descending | Select-Object -First $n } # An hs_err file is ours only when its command line names the game. Other Java programs' crash files carry their own # command lines (which can include tokens) and are skipped. function OurHs($file) { try { $fs = [IO.File]::Open($file.FullName, 'Open', 'Read', 'ReadWrite, Delete') try { $n = [math]::Min($fs.Length, 60000); $buf = New-Object byte[] $n; [void]$fs.Read($buf, 0, $n) } finally { $fs.Dispose() } $h = Decode $buf $cmd = [regex]::Match($h, '(?im)^(?:Command Line|java_command):\s*(.+)$') if (-not $cmd.Success) { return $false } return ($cmd.Groups[1].Value -match '(?i)com\.darkan|new ?school ?99|newschool99|darkan') } catch { return $false } } # 1. The launcher's and the game's own logs, crash reports, crash notes and settings. foreach ($f in (Newest @(Join-Path $lad 'logs') '*.log' 8)) { Add $f.FullName ('launcher-logs/' + $f.Name) } Add (Join-Path $lad 'state.json') 'launcher-logs/state.json' $homes = @($ns, (Join-Path $prof '.darkanrs'), $env:NS99_HOME, $env:DARKAN_HOME) | Where-Object { $_ } | Select-Object -Unique $gl = @($homes | ForEach-Object { Join-Path $_ 'logs' }) foreach ($f in (Newest $gl 'crash-*.txt' 6)) { Add $f.FullName ('game-crash-reports/' + $f.Name) } foreach ($f in (Newest $gl 'report-*.txt' 3)) { Add $f.FullName ('game-crash-reports/' + $f.Name) } foreach ($f in (Newest $gl 'crashed-run-*.log' 3)) { Add $f.FullName ('game-crash-reports/' + $f.Name) } foreach ($f in (Newest $gl 'crash-history.txt' 1)) { Add $f.FullName 'game-crash-reports/crash-history.txt' } foreach ($f in (Newest $gl '.running-*' 5)) { Add $f.FullName ('game-state/' + $f.Name.TrimStart('.') + '.txt') } foreach ($n in 'crash-recovery.properties', 'graphics-tier.properties', 'mouselook.properties') { Add (Join-Path $ns $n) ('game-settings/' + $n) } Add (Join-Path $ns 'reshade\ReShade.log') 'game-settings/ReShade.log' foreach ($c in (Join-Path $app 'app\New School 99.cfg'), (Join-Path $env:ProgramFiles 'New School 99\app\New School 99.cfg')) { Add $c 'install/New School 99.cfg' } # 2. Java's own crash files (hs_err_pid*.log): only the game's, never another Java program's. Searched wherever Java may put them. $hsDirs = @($app, (Join-Path $app 'app'), $lad, (Join-Path $lad 'logs'), $ns, $env:TEMP, $prof, $desk, (Join-Path $env:ProgramFiles 'New School 99')) + @(Get-ChildItem -LiteralPath (Join-Path $lad 'versions') -Directory | ForEach-Object { $_.FullName }) $hsAll = @($hsDirs | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container) } | ForEach-Object { Get-ChildItem -LiteralPath $_ -Filter 'hs_err_pid*.log' -File -Force } | Where-Object { $_.LastWriteTime -gt $since } | Sort-Object FullName -Unique | Sort-Object LastWriteTime -Descending) $hsOurs = New-Object Collections.ArrayList foreach ($f in $hsAll) { if ((OurHs $f)) { if ($hsOurs.Count -lt 4) { [void]$hsOurs.Add($f) } } else { $skippedHs++ } } foreach ($f in $hsOurs) { Add $f.FullName ('java-crash-files/' + $f.Name) -hs } # 3. Windows Error Reporting's reports for the game only. $wer = foreach ($r in (Join-Path $env:ProgramData 'Microsoft\Windows\WER'), (Join-Path $la 'Microsoft\Windows\WER')) { foreach ($sub in 'ReportArchive', 'ReportQueue') { Get-ChildItem -LiteralPath (Join-Path $r $sub) -Directory -Force | Where-Object { $_.Name -match '^App(Crash|Hang)_New School' -and $_.LastWriteTime -gt $since } } } foreach ($w in ($wer | Sort-Object LastWriteTime -Descending | Select-Object -First 5)) { Add (Join-Path $w.FullName 'Report.wer') ('windows-crash-reports/' + $w.Name + '.txt') -wer } # 4. This PC's Windows version, processor, memory and graphics, and the crash records Windows keeps for the game. $os = Get-CimInstance Win32_OperatingSystem -OperationTimeoutSec 10 $cs = Get-CimInstance Win32_ComputerSystem -OperationTimeoutSec 10 $cpu = Get-CimInstance Win32_Processor -OperationTimeoutSec 10 | Select-Object -First 1 $cv = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $win = "$($os.Caption) $($cv.DisplayVersion), build $($os.BuildNumber).$($cv.UBR), $($os.OSArchitecture)" S '== Windows' S ("$win, language $((Get-Culture).Name), clock UTC$($now.ToString('zzz'))") S ("Processor: $("$($cpu.Name)".Trim()) ($($cpu.NumberOfCores) cores, $($cpu.NumberOfLogicalProcessors) threads)") $ram = 'Memory: {0:N1} GB installed, {1:N1} GB free now; commit limit {2:N1} GB, {3:N1} GB of it free' -f ($cs.TotalPhysicalMemory / 1GB), ($os.FreePhysicalMemory / 1MB), ($os.TotalVirtualMemorySize / 1MB), ($os.FreeVirtualMemory / 1MB) S $ram foreach ($p in (Get-CimInstance Win32_PageFileUsage -OperationTimeoutSec 10)) { S ("Page file: $($p.AllocatedBaseSize) MB, most ever used $($p.PeakUsage) MB, managed by Windows: $($cs.AutomaticManagedPagefile)") } $pt = @{ 1 = 'desktop'; 2 = 'laptop'; 3 = 'workstation' }[[int]$cs.PCSystemType] if (-not $pt) { $pt = 'other (' + $cs.PCSystemType + ')' } S ('PC type: ' + $pt) $dr = Split-Path -Qualifier $la $ld = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='$dr'" -OperationTimeoutSec 10 S ('Free space on {0} {1:N1} GB of {2:N0} GB' -f $dr, ($ld.FreeSpace / 1GB), ($ld.Size / 1GB)) G ("Windows: $win; $pt; " + ('{0:N0} GB memory' -f ($cs.TotalPhysicalMemory / 1GB))) S '' S '== Graphics' $cls = Get-ChildItem -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}' | Where-Object { $_.PSChildName -match '^\d{4}$' } | ForEach-Object { Get-ItemProperty -LiteralPath $_.PSPath } foreach ($g in (Get-CimInstance Win32_VideoController -OperationTimeoutSec 10)) { $r = $cls | Where-Object { $_.DriverDesc -eq $g.Name -and $_.DriverVersion -eq $g.DriverVersion } | Select-Object -First 1 $q = $r.'HardwareInformation.qwMemorySize' if ($q -is [byte[]]) { $q = [BitConverter]::ToUInt64($q, 0) } if (-not $q) { $q = $g.AdapterRAM } $v = '' if ($g.Name -match 'NVIDIA') { $d = $g.DriverVersion -replace '\D', ''; if ($d.Length -ge 5) { $d = $d.Substring($d.Length - 5); $v = " (NVIDIA $($d.Substring(0, 3)).$($d.Substring(3)))" } } if ($r.RadeonSoftwareVersion) { $v = " (AMD Software $($r.RadeonSoftwareVersion))" } $dd = $r.DriverDate if ($g.DriverDate) { $dd = $g.DriverDate.ToString('yyyy-MM-dd') } S ('{0}: driver {1}{2} from {3}, {4:N1} GB video memory, {5}x{6} at {7} Hz, status {8}' -f $g.Name, $g.DriverVersion, $v, $dd, ([double]$q / 1GB), $g.CurrentHorizontalResolution, $g.CurrentVerticalResolution, $g.CurrentRefreshRate, $g.Status) G ('Graphics: {0}, driver {1}{2} from {3}' -f $g.Name, $g.DriverVersion, $v, $dd) } $gp = Get-ItemProperty -LiteralPath 'HKCU:\Software\Microsoft\DirectX\UserGpuPreferences' if ($gp) { $gp.PSObject.Properties | Where-Object { $_.Name -match '(?i)new ?school ?99|newschool99|darkan' } | ForEach-Object { S ("Windows graphics setting for $($_.Name): $($_.Value)") } } S '' S '== New School 99 install' foreach ($e in (Join-Path $app 'New School 99.exe'), (Join-Path $env:ProgramFiles 'New School 99\New School 99.exe')) { if (Test-Path -LiteralPath $e) { S ("Installed: $e, version $((Get-Item -LiteralPath $e).VersionInfo.FileVersion)") } } S ('Game builds downloaded: ' + ((Get-ChildItem -LiteralPath (Join-Path $lad 'versions') -Directory | ForEach-Object { $_.Name }) -join ', ')) S '' S '== Windows crash records for New School 99 (Application log, last 30 days, newest first)' $x = "*[System[(Provider[@Name='Application Error'] or Provider[@Name='Windows Error Reporting'] or Provider[@Name='Application Hang']) and TimeCreated[timediff(@SystemTime) <= 2592000000]]]" # Only the game: New School 99.exe, or a java.exe/javaw.exe started from a New School 99 folder (never other Java programs). $ev = @(Get-WinEvent -LogName Application -FilterXPath $x -MaxEvents 3000 | Where-Object { if ($_.Id -eq 1001) { "$($_.Properties[5].Value)" -eq 'New School 99.exe' } else { $an = "$($_.Properties[0].Value)"; $ap = "$($_.Properties[10].Value)"; if ($_.Id -eq 1002) { $ap = "$($_.Properties[5].Value)" }; $an -eq 'New School 99.exe' -or ($an -match '^javaw?\.exe$' -and $ap -match '(?i)new ?school ?99|darkan') } } | Select-Object -First 25) if ($ev.Count -eq 0) { S 'none' } foreach ($e in $ev) { S ("--- $($e.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')) event $($e.Id) $($e.ProviderName)"); if ($e.Message) { S $e.Message.Trim() } else { S (($e.Properties | ForEach-Object { $_.Value }) -join ' | ') } } S '' S '== Graphics driver resets and low-memory warnings (System log, last 30 days, newest first)' $x = "*[System[(Provider[@Name='Display'] or Provider[@Name='nvlddmkm'] or Provider[@Name='amdkmdag'] or Provider[@Name='amdwddmg'] or Provider[@Name='igfx'] or Provider[@Name='igfxn'] or Provider[@Name='Microsoft-Windows-Resource-Exhaustion-Detector']) and TimeCreated[timediff(@SystemTime) <= 2592000000]]]" $ev2 = @(Get-WinEvent -LogName System -FilterXPath $x -MaxEvents 25) if ($ev2.Count -eq 0) { S 'none' } foreach ($e in $ev2) { if ($e.ProviderName -like '*Exhaustion*') { S ("--- $($e.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')) event $($e.Id) $($e.ProviderName): low virtual memory warning"); continue } $m = "$($e.Message)".Trim(); if (-not $m) { $m = (($e.Properties | ForEach-Object { "$($_.Value)" }) -join ' | ') } if ($m.Length -gt 600) { $m = $m.Substring(0, 600) } S ("--- $($e.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')) event $($e.Id) $($e.ProviderName): $m") } # 5. README.txt: what this is, what to do with it, the short version, what is inside and what was hidden. $c = @($ev | Where-Object { $_.Id -eq 1000 }) $hg = @($ev | Where-Object { $_.Id -eq 1002 }) $rd = New-Object Collections.ArrayList foreach ($l in @( 'NEW SCHOOL 99 LOGS', ('Made ' + $now.ToString('yyyy-MM-dd HH:mm') + ' (UTC' + $now.ToString('zzz') + ') by the New School 99 log collector, version ' + $ver + '.'), 'Guide: https://newschool99.com/help/logs', '', 'WHAT TO DO WITH THIS FILE', '1. Open #bug-reports on the New School 99 Discord (not a member yet? join at https://newschool99.com/discord).', '2. Click New Post, give it a short title, drag this zip file in and say what you were doing when it went wrong.', 'We send nothing anywhere. This file stays on your PC until you post it (a Desktop synced to OneDrive keeps a copy in your own OneDrive).', '', 'THE SHORT VERSION')) { [void]$rd.Add($l) } [void]$rd.Add("Crashes of the game that Windows recorded in the last 30 days: $($c.Count); times it stopped responding: $($hg.Count)") if ($c.Count -gt 0) { $p = $c[0].Properties; [void]$rd.Add(('Newest crash: {0}, {1} stopped in {2} version {3}{4}, code 0x{5}' -f $c[0].TimeCreated.ToString('yyyy-MM-dd HH:mm'), $p[0].Value, $p[3].Value, $p[4].Value, (Kind "$($p[3].Value)"), $p[6].Value)) } foreach ($l in $glance) { [void]$rd.Add($l) } foreach ($l in @('', 'WHAT IS INSIDE', ' README.txt (this file)', ' system.txt (Windows version and language, time zone, processor, memory, graphics card and driver, free disk space, and the crash records and graphics-driver resets Windows keeps for New School 99)')) { [void]$rd.Add($l) } foreach ($a in $files) { [void]$rd.Add(' ' + $a[2]) } if ($skippedHs -gt 0) { [void]$rd.Add(" ($skippedHs Java crash file(s) from other programs were left out.)") } foreach ($l in @( '', 'WHAT WAS HIDDEN', '- Your Windows user name is shown as , your PC name as and your user folder as %USERPROFILE%.', '- Your game login name is shown as , but only when the game has remembered it on this PC. The game never writes your password into these files.', '- Email addresses are shown as , IP addresses as , and passwords, tokens and keys as .', '- Java crash files lose their environment variables and memory dumps.', '- Other players named in the game log are shown as where the log line is known. Some may remain.', '', 'WHAT WAS NOT COLLECTED, AND WHAT WAS', 'Only New School 99''s own folders were read, plus the crash records Windows keeps for New School 99, graphics-driver', 'resets, low-memory warnings and the hardware summary in system.txt. The crash files and the Windows crash report do', 'list the file names of other programs that were loaded into the game (overlays or antivirus, because they often cause', 'crashes), but nothing else from other programs: no browser data, no documents, pictures or screenshots, no passwords,', 'no memory dumps. Nothing on your PC was changed: the only new file is this zip.')) { [void]$rd.Add($l) } # 6. One zip, standard / folder names (Compress-Archive in PS 5.1 writes \ names), built in memory. Written as a # .partial first and renamed on success, so a half-written or blocked write never leaves a broken zip claiming "Done". # If the Desktop is write-protected (OneDrive, Controlled Folder Access, another AV shield) it falls through to # Downloads, then %LOCALAPPDATA%\NewSchool99. Add-Type -AssemblyName System.IO.Compression $entries = @(@('README.txt', (Clean ($rd -join "`r`n"))), @('system.txt', (Clean ($sys -join "`r`n")))) + $files $name = 'NS99-logs-' + $now.ToString('yyyy-MM-dd-HHmm') + '.zip' $cands = New-Object Collections.ArrayList if ($global:NS99Out) { [void]$cands.Add($global:NS99Out) } else { [void]$cands.Add($desk) [void]$cands.Add((Join-Path $prof 'Downloads')) [void]$cands.Add($lad) [void]$cands.Add($prof) } $zip = $null $outDir = $null foreach ($d in $cands) { if (-not $d -or -not (Test-Path -LiteralPath $d -PathType Container)) { continue } $target = Join-Path $d $name $partial = $target + '.partial' $zs = $null $okWrite = $false try { $zs = [IO.File]::Open($partial, 'Create', 'ReadWrite', 'None') $za = New-Object IO.Compression.ZipArchive($zs, [IO.Compression.ZipArchiveMode]::Create) foreach ($a in $entries) { $w = New-Object IO.StreamWriter($za.CreateEntry($a[0], [IO.Compression.CompressionLevel]::Optimal).Open(), (New-Object Text.UTF8Encoding($false))); $w.Write($a[1]); $w.Dispose() } $za.Dispose(); $zs.Dispose(); $zs = $null [IO.File]::Delete($target) [IO.File]::Move($partial, $target) $okWrite = $true } catch { } finally { if ($zs) { $zs.Dispose() } } if ($okWrite) { $zip = $target; $outDir = $d; break } if (Test-Path -LiteralPath $partial) { [IO.File]::Delete($partial) } } Write-Host '' if ($zip -and (Test-Path -LiteralPath $zip) -and (Get-Item -LiteralPath $zip).Length -gt 100) { $where = 'on your Desktop' if ($outDir -ne $desk) { $where = 'in ' + $outDir } Write-Host ('Done! Your New School 99 logs are saved ' + $where + ' as:') -ForegroundColor Green Write-Host (' ' + (Split-Path -Leaf $zip)) -ForegroundColor Green Write-Host ('It holds ' + ($files.Count + 2) + ' files, with your private details hidden. We send nothing anywhere.') Write-Host 'Next: open #bug-reports on our Discord, click New Post, drag that file in and say what happened.' if ((Get-Item -LiteralPath $zip).Length -gt 9500000) { Write-Host 'This file is bigger than Discord allows (10 MB). Post your message anyway and a helper will tell you what to send.' -ForegroundColor Yellow } if (-not $global:NS99Out) { Start-Process explorer.exe -ArgumentList ('/select,"' + $zip + '"') } } else { Write-Host 'Sorry, the zip could not be made. Take a screenshot of this window and post it in #bug-reports on our Discord.' -ForegroundColor Red } } }