Files
sasheg dev 450bdecc34 Клиент говорит с сервером по нашему протоколу
Раньше клиент умел только показывать меню. Теперь он заходит на сервер,
получает наш мир и рисует его.

Как это устроено:

- Наш сетевой слой (net/rustnotmc/client/net) читает наш протокол и
  переводит его пакеты в пакеты игры. Игра остаётся игрой: у неё есть
  мир, игрок, сущности и рендер, и все они работают. Не переписывать их
  ради того, чтобы нарисовать тот же мир иначе, незачем.
- Пакеты игры, которые не имеют соответствия у нас, не отправляются, а
  наоборот: перемещение игрока и ломание блока переводятся в наши.
- Экран входа игры (GuiConnecting) заменён наш: он спрашивает у лаунчера,
  кем пришёл игрок, и подставляет это в рукопожатие.
- Соединение качается из тика игры, а не из экрана. Рукопожатие, которое
  зависит от того, что экран ещё на экране, ломается alt-tab'ом.

Вход теперь через лаунчер, а не внутри игры:

- Лаунчер уже умеет логинить игрока и отдаёт игре имя, uuid и токен
  (--username, --uuid, --accessToken). Это та же роль, что у Yggdrasil в
  ванилле, только лаунчер наш.
- Сервер спрашивает лаунчер, один из ли наш этот игрок, и заодно получает
  бан-лист. Токен используется один раз и не хранится.
- Свои аккаунты сервера, когда настроен лаунчер, закрыты: сервер с двумя
  дверями не имеет дверей. Открыть их можно флагом, но намеренно.
- Если игру запустили без лаунчера, экран всё равно спрашивает имя и
  пароль: это путь для своего сервера и для разработки.

Проверено живьём: клиент вошёл по токену лаунчера, сервер спросил
лаунчер, лаунчер сказал "свой", игрок получил мир, 210 чанков пришли и
лежат на диске, рельеф нарисован.

Что чинилось по дороге:

- Minecraft.setIngameFocus() возвращал на главное меню, когда окно
  получало фокус. Игрок как раз кликает в окно, чтобы напечатать пароль, и
  экран входа исчезал в этот самый момент.
- Ответ лаунчера разбирается, а не ищется подстрокой: {"valid": true} и
  {"valid":true} это один и тот же ответ, и сервер, который понимает
  только второй, ломает работающий лаунчер.
2026-10-19 20:10:20 +03:00

178 lines
8.6 KiB
PowerShell

# Builds the forked client from the verified vanilla jar.
#
# vanilla client -> MCP names -> source -> patches -> classes
#
# ASCII only: PowerShell 5.1 reads a .ps1 without a byte order mark using the
# system ANSI code page, which mangles UTF-8 and breaks the parser.
#
# Everything here is repeatable. Run it any time; nothing is edited by hand
# except game-fork\patches\decompiler-fixes.txt, which is re-applied at the end.
$ErrorActionPreference = 'Stop'
$Fork = Split-Path -Parent $PSScriptRoot
if (-not $Fork) { $Fork = $PSCommandPath | Split-Path -Parent | Split-Path -Parent }
$Root = Split-Path -Parent $Fork
$Client = Join-Path $Root 'rsx-client'
$VanillaJar = 'G:\rsx-map\vanilla\client-1.12.2.jar'
$Astral = Join-Path $env:APPDATA 'AstralRinthApp\meta'
$Fernflower = 'G:\Paper-Dev\work\BuildData\bin\fernflower.jar'
$SpecialSource = 'G:\Paper-Dev\work\BuildData\bin\SpecialSource.jar'
$McpCs = 'G:\rsx-map\vanilla\mcp-stable'
$Maps = Join-Path $Fork 'mappings'
$Classes = Join-Path $Fork 'classes'
$Src = Join-Path $Fork 'src'
$Out = Join-Path $Fork 'build\classes'
$Temp = Join-Path $env:TEMP 'opencode'
function Say($text) { Write-Host " $text" -ForegroundColor Cyan }
function Need($path, $what) { if (-not (Test-Path -LiteralPath $path)) { throw "missing $what at $path" } }
New-Item -ItemType Directory -Force -Path $Maps, $Temp | Out-Null
Need $VanillaJar 'the verified vanilla client jar'
Need $Fernflower 'fernflower'
Need $SpecialSource 'SpecialSource'
Need $McpCs 'the MCP name CSVs'
# 1. vanilla SRG
$srgZip = Join-Path $Maps 'mcp-1.12.2-srg.zip'
$srgDir = Join-Path $Maps 'mcp-srg-vanilla'
if (-not (Test-Path (Join-Path $srgDir 'joined.srg'))) {
Say 'downloading the vanilla 1.12.2 SRG'
Invoke-WebRequest -Uri 'https://maven.minecraftforge.net/de/oceanlabs/mcp/mcp/1.12.2/mcp-1.12.2-srg.zip' `
-OutFile $srgZip -TimeoutSec 180 -UseBasicParsing
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($srgZip, $srgDir)
}
# 2. merge SRG with the MCP CSVs into a single obf -> MCP SRG
$srg = Join-Path $Maps 'obf-to-mcp.srg'
Say 'merging SRG with the MCP CSVs'
& python (Join-Path $Client 'tools\merge_srg_mcp.py') `
(Join-Path $srgDir 'joined.srg') $McpCs $srg
if ($LASTEXITCODE -ne 0) { throw 'srg merge failed' }
# 3. remap the vanilla client
$named = Join-Path $Maps 'client-mcp-named.jar'
Say 'remapping the vanilla client to MCP names'
if (Test-Path $named) { Remove-Item $named -Force }
& java -Xmx4g -jar $SpecialSource -m $srg -i $VanillaJar -o $named
if (-not (Test-Path $named)) { throw 'remap produced nothing' }
Say " client-mcp-named.jar ($((Get-Item $named).Length) bytes)"
# 4. unpack for the decompiler
Remove-Item $Classes -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $Classes | Out-Null
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($named, $Classes)
Say " unpacked $((Get-ChildItem $Classes -Recurse -Filter '*.class' -File).Count) classes"
# 5. decompile
# -dgs=1 decode generic signatures
# -rbr=1 drop bridge methods (otherwise every generic method appears twice
# and the compiler rejects the erasure clash)
# -rsy=1 drop synthetic members
Remove-Item $Src -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $Src | Out-Null
Say 'decompiling (this is the slow part)'
$log = Join-Path $Temp 'fernflower.log'
& cmd /c "java -Xmx4g -jar `"$Fernflower`" -dgs=1 -rbr=1 -rsy=1 `"$Classes`" `"$Src`" > `"$log`" 2>&1"
Say " $((Get-ChildItem $Src -Recurse -Filter '*.java' -File).Count) source files"
# 5b. copy in our own game-side code
#
# Everything above this line is regenerated from the vanilla jar and is wiped on
# every run. Our own classes under net/rustnotmc are not generated and must live
# outside src, or a rebuild deletes them and leaves the patches that call them
# pointing at nothing. They go in before the patches so a hand fix may edit them
# too.
$Overlay = Join-Path $Fork 'src-overlay'
if (Test-Path $Overlay) {
$copied = 0
foreach ($f in (Get-ChildItem $Overlay -Recurse -Filter '*.java' -File)) {
$rel = $f.FullName.Substring($Overlay.Length).TrimStart('\')
$target = Join-Path $Src $rel
$dir = Split-Path -Parent $target
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
Copy-Item -LiteralPath $f.FullName -Destination $target -Force
$copied++
}
Say " overlay: $copied source file(s) from src-overlay"
}
# 6. re-apply the hand fixes
Say 'applying hand fixes'
& python (Join-Path $Client 'tools\apply_patches.py') $Src (Join-Path $Fork 'patches\decompiler-fixes.txt')
if ($LASTEXITCODE -ne 0) { Write-Host ' WARNING: some patches did not apply' -ForegroundColor Yellow }
# 6b. the systematic rewrites, which would be hundreds of patch entries one by one
Say 'applying systematic rules'
& python (Join-Path $Client 'tools\fix_decompiler_rules.py') $Src
# 7. compile, let the automated fixers converge, compile again.
#
# The fixers need javac's diagnostics as their input, and fixing one error can
# reveal the next, so this runs to a fixed point rather than once. It stops as
# soon as a round fails to reduce the count, which is the signal that the
# remaining errors need a human.
$argf = Join-Path $Temp 'fork-build.txt'
$errf = Join-Path $Temp 'fork-build-errors.txt'
$good = New-Object System.Collections.Generic.List[string]
foreach ($j in (Get-ChildItem (Join-Path $Astral 'libraries') -Recurse -Filter '*.jar' -File)) {
if ($j.Length -le 100) { continue }
$ok = $false
try { $z = [System.IO.Compression.ZipFile]::OpenRead($j.FullName); $z.Dispose(); $ok = $true } catch { $ok = $false }
if ($ok) { $good.Add(($j.FullName -replace '\\', '/')) }
}
# The fork calls into the mod loader, so the loader has to be visible to javac
# exactly as it is at runtime. The protocol and the account code are compiled
# against too: the client speaks the server's protocol, not the vanilla one.
$Loader = Join-Path $Client 'dist\rsx-loader.jar'
$Protocol = Join-Path $Root 'rsx-protocol\dist\rsx-protocol.jar'
$Auth = Join-Path $Root 'rsx-auth\dist\rsx-auth.jar'
$cp = @($good + $(if (Test-Path $Loader) { $Loader -replace '\\', '/' } else { $null }) `
+ $(if (Test-Path $Protocol) { $Protocol -replace '\\', '/' } else { $null }) `
+ $(if (Test-Path $Auth) { $Auth -replace '\\', '/' } else { $null })) -join ';'
$sb = New-Object System.Text.StringBuilder
foreach ($a in @('-nowarn', '-proc:none', '-encoding', 'UTF-8', '--release', '17', '-d', ($Out -replace '\\', '/'))) { [void]$sb.AppendLine($a) }
[void]$sb.AppendLine('-cp'); [void]$sb.AppendLine('"' + $cp + '"')
Get-ChildItem $Src -Recurse -Filter '*.java' | ForEach-Object { [void]$sb.AppendLine(($_.FullName -replace '\\', '/')) }
Set-Content -LiteralPath $argf -Value $sb.ToString() -Encoding ASCII
$javac = (Get-Command javac).Source
function Invoke-Compile {
Remove-Item $Out -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $Out | Out-Null
& cmd /c "`"$javac`" @`"$argf`" -Xmaxerrs 10000 > `"$errf`" 2>&1"
return (Get-Content -LiteralPath $errf | Select-String -Pattern ': error:').Count
}
$count = Invoke-Compile
Say "compiled, $count errors"
$previous = [int]::MaxValue
for ($round = 1; $round -le 8 -and $count -gt 0; $round++) {
if ($count -ge $previous) { Say "no further progress, stopping"; break }
$previous = $count
Say "fix round $round"
& python (Join-Path $Client 'tools\fix_stale_locals.py') $errf --apply | Out-Null
& python (Join-Path $Client 'tools\fix_generic_casts.py') $errf --apply | Out-Null
& python (Join-Path $Client 'tools\fix_decompiler_rules.py') $Src | Out-Null
$count = Invoke-Compile
Say " -> $count errors"
}
$errors = Get-Content -LiteralPath $errf | Select-String -Pattern ': error:'
$files = $errors | ForEach-Object { if ($_.Line -match '^([A-Z]:.*?\.java):\d+:') { $matches[1] } } | Sort-Object -Unique
Write-Host ''
if ($errors.Count -eq 0) {
Write-Host ' FORK COMPILES CLEAN' -ForegroundColor Green
Write-Host " $((Get-ChildItem $Out -Recurse -Filter '*.class' -File).Count) classes"
} else {
Write-Host " $((Get-ChildItem $Out -Recurse -Filter '*.class' -File).Count) classes, $($errors.Count) errors in $($files.Count) files" -ForegroundColor Yellow
$errors | ForEach-Object { if ($_.Line -match '^([A-Z]:.*?\.java):\d+:') { $matches[1] } } |
Group-Object | Sort-Object Count -Descending | Select-Object -First 15 |
ForEach-Object { Write-Host (" {0,4} {1}" -f $_.Count, ($_.Name.Replace(($Src + '\'), ''))) -ForegroundColor Gray }
}