没有启动器怎么下载并启动原版MC?附带下载渠道
2026-10-21mc,我的世界,教程,pcl2,pcl,html,启动器,powershell,脚本
## 使用Windows自带的powershell脚本下载并启动原版Minecraft
### 一、前置准备
- version.json(必须)
- powershell 5.1(2.0-5.1、7)
- ps脚本执行代码(文章末尾)
- 与 mc 版本对应的 Java(如 1.20.6 需要 Java 21)
- 脑子(必须)
### 二、说明
- 仅提供最基础的启动功能,如果需要高级功能,请移步mc wiki查阅启动项。
- 请将ps1脚本与json放至同一目录下。
- 请使用与 mc 版本对应的 Java,在 cmd 输入 `java -version` 查看。脚本运行时会打印该版本要求的 `java: 21+`
- 不设 `MC_JAVA` 时优先用环境里的 Java(`JAVA_HOME`,其次 `PATH` 里的 `javaw.exe`);设了 `MC_JAVA` 则优先生成到 `run.bat` 里
- 启动失败大概率是Java版本不一致
- 由于json结构问题,使用脚本来批量下载是比较合理的。
- 提供基础的并发下载
- 自动生成`run.bat`用于启动。
- client.jar 不走镜像。它来自 piston-data.mojang.com国内下载慢的话需要额外改 UR 加上这条,或预先手动放好对应版本的client.jar
- 无法自动使用模组加载器
- 注意尽量使用**全英文路径**。
- 默认使用官方源和环境Java。要换下载源或指定 Java,可以改脚本前两行的 `else { "" }`,也可以直接用环境变量(推荐,不用改脚本):
```powershell
# 换下载源(留空则为官方源)
$env:MC_MIRROR = "https://bmclapi2.bangbang93.com"
# 指定 Java:目录、bin 目录、或 javaw.exe 完整路径都行
$env:MC_JAVA = "C:\Program Files\Java\jdk-21"
# 其他:并发线程数 1-64;设置 MC_NO_PROGRESS 可关闭进度条
$env:MC_THREADS = 16
# 然后在同一窗口里运行脚本
.\jiaoben.ps1
```
对应的脚本内默认值(改这里则不依赖环境变量):
```powershell
# 留空则为官方源
$MIRROR = if ($env:MC_MIRROR) { $env:MC_MIRROR } else { "https://bmclapi2.bangbang93.com" }
# 留空则自动探测:先 JAVA_HOME,再 PATH 里的 javaw.exe
$JAVA_HINT = if ($env:MC_JAVA) { $env:MC_JAVA } else { "" }
```
### 三、如何获取json和脚本
进入[Minecraft 原版版本 JSON 获取](https://giogio.top/mc-download)

点进去会出现一堆代码,直接**右键→另存为**或者快捷键`win`+`s`下载,并命名为 `version.json`(文件名必须是 `version.json`,脚本按这个固定名字查找)
将文章最后的代码全部复制,并在文件夹下创建后缀名为`.ps1`的文件(如 `jiaoben.ps1`),**使用记事本打开**粘贴进去保存。
> 建议用记事本另存为时选择 **UTF-8** 编码;如果保存成 ANSI,中文注释可能乱码(脚本仍能运行,乱码只影响注释)。
完成后文件夹下会有
```plaintext
mc1.20.6/
├─ version.json
└─ jiaoben.ps1
```
在该文件夹里打开 **PowerShell**(在地址栏输入 `powershell` 回车,或按住 `Shift` 右键→“在此处打开 PowerShell 窗口”),执行:
```powershell
# 默认下载执行策略下不能直接运行脚本,先放开当前会话(只影响这个窗口)
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\jiaoben.ps1
```

等待下载完成后,双击 **run.bat**(脚本会在同目录生成 `run.bat` 和 `run.bat.utf8.bat`,后者是 UTF-8 版,`run.bat` 打不开时改双击它)。
---
### 脚本代码:
```bash
# jiaoben.ps1
# mc(原版)自动依赖下载+run.bat。
# 兼容 Win7 自带的 PowerShell 2.0 一直到 Win11 的 5.1,以及 PS 7。
# PS 2.0 会把中文注释按 ANSI 误读。出现问题请删除全文注释
# 环境变量覆盖:MC_MIRROR、MC_THREADS、MC_NO_PROGRESS、MC_JAVA
# MC_JAVA 可以是 java 目录(...\jdk17)、bin 目录或 javaw.exe 的完整路径;写进 run.bat 后优先使用。
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$MIRROR = if ($env:MC_MIRROR) { $env:MC_MIRROR } else { "" }
$JAVA_HINT = if ($env:MC_JAVA) { $env:MC_JAVA } else { "" }
$THREADS = 8; if ($env:MC_THREADS) { try { $THREADS = [int]$env:MC_THREADS } catch { } }
if ($THREADS -lt 1) { $THREADS = 1 } elseif ($THREADS -gt 64) { $THREADS = 64 }
$MAX_RETRY = 3
$SHOW_PROGRESS = -not $env:MC_NO_PROGRESS
$UseCR = -not [Console]::IsOutputRedirected # \r 原地刷新只在真实控制台下有意义
$UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) MCDownloader/2.0"
$Dir = Split-Path -Parent $MyInvocation.MyCommand.Definition
if (-not $Dir) { $Dir = (Get-Location).Path }
# 32 位系统 => 只能跑 32 位 JVM => 只能用 -x86 的 natives 包
$script:Is32 = $false
if ($env:PROCESSOR_ARCHITECTURE -eq 'x86' -and -not $env:PROCESSOR_ARCHITEW6432) { $script:Is32 = $true }
function J { param([string]$Base, [string]$Child) if ($Base) { $Base.TrimEnd('\') + '\' + ($Child -replace '/', '\').TrimStart('\') } else { $Child } }
function MK { param([string]$P) if ($P -and -not (Test-Path -LiteralPath $P)) { New-Item -ItemType Directory -Force -Path $P | Out-Null } }
# ------------------------------------------------------------------ JSON 解析 --
function JV {
# 递归下降读取任意值;状态保存在 $script:T / $script:P
while ($script:P -lt $script:T.Length -and " `t`r`n".IndexOf($script:T[$script:P]) -ge 0) { $script:P++ }
$c = $script:T[$script:P]
if ($c -eq '{') {
$script:P++; $h = @{}
if ($script:T[$script:P] -eq '}') { $script:P++; return $h }
while ($true) {
$k = JS
while ($script:T[$script:P] -ne ':') { $script:P++ }
$script:P++
$h[$k] = JV
while (" `t`r`n".IndexOf($script:T[$script:P]) -ge 0) { $script:P++ }
if ($script:T[$script:P] -eq '}') { $script:P++; return $h }
$script:P++
}
}
if ($c -eq '[') {
$script:P++; $l = New-Object System.Collections.ArrayList
if ($script:T[$script:P] -eq ']') { $script:P++; return , $l.ToArray() }
while ($true) {
[void]$l.Add((JV))
while (" `t`r`n".IndexOf($script:T[$script:P]) -ge 0) { $script:P++ }
if ($script:T[$script:P] -eq ']') { $script:P++; return , $l.ToArray() }
$script:P++
}
}
if ($c -eq '"') { return (JS) }
if ($c -eq 't') { $script:P += 4; return $true }
if ($c -eq 'f') { $script:P += 5; return $false }
if ($c -eq 'n') { $script:P += 4; return $null }
$s = $script:P
while ($script:P -lt $script:T.Length -and '+-.eE0123456789'.IndexOf($script:T[$script:P]) -ge 0) { $script:P++ }
[double]::Parse($script:T.Substring($s, $script:P - $s), [Globalization.CultureInfo]::InvariantCulture)
}
function JS {
# 读取当前位置的 JSON 字符串
while (" `t`r`n".IndexOf($script:T[$script:P]) -ge 0) { $script:P++ }
$script:P++
$s = $script:P; $esc = $false
while ($true) {
$c = $script:T[$script:P]
if ($c -eq '\') { $esc = $true; $script:P += 2; continue }
if ($c -eq '"') { break }
$script:P++
}
$r = $script:T.Substring($s, $script:P - $s); $script:P++
if (-not $esc) { return $r }
$sb = New-Object Text.StringBuilder; $i = 0
while ($i -lt $r.Length) {
$c = $r[$i]
if ($c -ne '\' -or $i + 1 -ge $r.Length) { [void]$sb.Append($c); $i++; continue }
$e = $r[$i + 1]
if ($e -eq 'u' -and $i + 5 -lt $r.Length) {
$n = [Convert]::ToInt32($r.Substring($i + 2, 4), 16)
if ($n -ge 0xD800 -and $n -le 0xDBFF -and $i + 11 -lt $r.Length -and $r[$i + 6] -eq '\') {
$lo = [Convert]::ToInt32($r.Substring($i + 8, 4), 16)
[void]$sb.Append([char]::ConvertFromUtf32(0x10000 + ($n - 0xD800) * 0x400 + ($lo - 0xDC00)))
$i += 12; continue
}
[void]$sb.Append([char]$n); $i += 6; continue
}
$m = @{ '"' = '"'; '\' = '\'; '/' = '/'; b = [char]8; f = [char]12; n = [char]10; r = [char]13; t = [char]9 }
if ($m.ContainsKey([string]$e)) { [void]$sb.Append($m[[string]$e]); $i += 2 } else { [void]$sb.Append($c); $i++ }
}
$sb.ToString()
}
function Json { param([string]$Text) $script:T = $Text.TrimStart([char]65279); $script:P = 0; JV }
function V { param($O, [string]$K) if ($O -is [hashtable] -and $O.ContainsKey($K)) { $O[$K] } else { $null } }
function ReadText {
param([string]$P)
$b = [IO.File]::ReadAllBytes($P)
try { return (New-Object Text.UTF8Encoding($false, $true)).GetString($b) } catch { return [Text.Encoding]::Default.GetString($b) }
}
# -------------------------------------------------------------------- 网络层 --
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { }
function UR { param([string]$U) # 套用镜像地址
if (-not $MIRROR) { return $U }
$m = $MIRROR.TrimEnd('/')
if ($U.StartsWith("https://libraries.minecraft.net")) { return $U.Replace("https://libraries.minecraft.net", "$m/libraries") }
if ($U.StartsWith("https://resources.download.minecraft.net")) { return $U.Replace("https://resources.download.minecraft.net", "$m/resources") }
$U
}
function Grab { param([string]$U, [string]$P) # 单文件下载,不打印过程
if ((Test-Path -LiteralPath $P) -and (Get-Item -LiteralPath $P).Length -gt 0) { return $true }
MK (Split-Path -Parent $P)
$t = "$P.part"
for ($a = 1; $a -le $MAX_RETRY; $a++) {
$w = New-Object Net.WebClient
try {
$w.Headers.Add("User-Agent", $UA); $w.DownloadFile((UR $U), $t)
if ((Get-Item -LiteralPath $t).Length -gt 0) { Move-Item -LiteralPath $t -Destination $P -Force; return $true }
} catch {
if (Test-Path -LiteralPath $t) { Remove-Item -LiteralPath $t -Force -ErrorAction SilentlyContinue }
if ($a -lt $MAX_RETRY) { Start-Sleep -Seconds (2 * $a) }
} finally { $w.Dispose() }
}
Write-Host " [FAIL] $U"
$false
}
# ---------------------------------------------------------------- 并发下载 --
# 在 worker runspace 内执行。结果写入线程安全队列,
# 因为 BeginInvoke 不会捕获 AddScript 的管道输出。
$Worker = @'
param($Jobs, $Retry, $UA, $Sink)
foreach ($j in $Jobs) {
$url, $dst = $j
$ok = $false
if (Test-Path -LiteralPath $dst) { $ok = (Get-Item -LiteralPath $dst).Length -gt 0 }
if (-not $ok) {
$tmp = "$dst.part"
$d = Split-Path -Parent $dst
if (-not (Test-Path -LiteralPath $d)) { New-Item -ItemType Directory -Force -Path $d | Out-Null }
for ($a = 1; $a -le $Retry -and -not $ok; $a++) {
$w = New-Object Net.WebClient
try {
$w.Headers.Add("User-Agent", $UA)
$w.DownloadFile($url, $tmp)
if ((Get-Item -LiteralPath $tmp).Length -gt 0) { Move-Item -LiteralPath $tmp -Destination $dst -Force; $ok = $true }
} catch {
if (Test-Path -LiteralPath $tmp) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue }
if ($a -lt $Retry) { Start-Sleep -Seconds (2 * $a) }
} finally { $w.Dispose() }
}
}
$Sink.Enqueue($ok)
}
'@
function Human { param([double]$S) if ($S -lt 0 -or $S -gt 359999) { "--:--" } elseif ($S -ge 3600) { '{0}:{1:00}:{2:00}' -f [int]($S / 3600), [int]($S % 3600 / 60), [int]($S % 60) } else { '{0:00}:{1:00}' -f [int]($S / 60), [int]($S % 60) } }
# $Items:元素为 @(url, 保存路径) 的数组
function Batch { param($Items, [string]$Label)
$all = @($Items); $total = $all.Count
if ($total -eq 0) { return 0 }
$jobs = @()
foreach ($it in $all) { if (-not ((Test-Path -LiteralPath $it[1]) -and (Get-Item -LiteralPath $it[1]).Length -gt 0)) { $jobs += , @((UR $it[0]), $it[1]) } }
$skip = $total - $jobs.Count
Write-Host ""
Write-Host ("{0}: {1} file(s), {2} present, {3} to download, {4} thread(s)" -f $Label, $total, $skip, $jobs.Count, $THREADS)
if ($jobs.Count -eq 0) { return $total }
# 按线程数切成连续的分块,每块交给一个 worker
$n = [Math]::Min($THREADS, $jobs.Count)
$size = [Math]::Ceiling($jobs.Count / $n)
$runs = @()
for ($i = 0; $i -lt $jobs.Count; $i += $size) { $runs += , @($jobs[$i..([Math]::Min($i + $size - 1, $jobs.Count - 1))]) }
$sink = New-Object 'System.Collections.Concurrent.ConcurrentQueue[bool]'
$pool = $null
if ($runs.Count -gt 1) {
try { $pool = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool($runs.Count, $runs.Count); $pool.Open() } catch { $pool = $null }
}
if (-not $pool) { # 无法并发时退化为顺序下载
& ([ScriptBlock]::Create($Worker)) $runs[0] $MAX_RETRY $UA $sink
$ok = 0; $v = $false
while ($sink.TryDequeue([ref]$v)) { if ($v) { $ok++ } }
Write-Host ("{0}: done, ok {1}, failed {2}" -f $Label, ($ok + $skip), ($jobs.Count - $ok))
return $ok + $skip
}
$hs = @()
foreach ($r in $runs) {
$ps = [PowerShell]::Create(); $ps.RunspacePool = $pool
[void]$ps.AddScript($Worker).AddArgument($r).AddArgument($MAX_RETRY).AddArgument($UA).AddArgument($sink)
$hs += , @($ps, $ps.BeginInvoke())
}
$ok = 0; $done = 0; $spin = 0; $marks = '|', '/', '-', '\'
$last = Get-Date; $prev = 0; $rate = 0.0
try {
do {
Start-Sleep -Milliseconds 300
$v = $false
while ($sink.TryDequeue([ref]$v)) { $done++; if ($v) { $ok++ } }
if (-not $SHOW_PROGRESS) { continue }
$now = Get-Date; $span = ($now - $last).TotalSeconds
if ($span -ge 1) { $rate = ($done - $prev) / $span; $prev = $done; $last = $now }
$p = $skip + $done
$eta = if ($rate -gt 0.2) { Human (($total - $p) / $rate) } else { "--:--" }
$line = "{0}: {1,3}% [{2}/{3}] fail {4} {5} {6:F1}/s eta {7}" -f $Label, [int]($p * 100 / $total), $p, $total, ($done - $ok), $marks[($spin++) % 4], $rate, $eta
if ($UseCR) { Write-Host ("`r" + $line.PadRight(100)) -NoNewline } else { Write-Host $line }
} while (@($hs | Where-Object { $_[0].InvocationStateInfo.State -eq 'Running' }).Count -gt 0)
foreach ($h in $hs) {
$h[0].EndInvoke($h[1]) | Out-Null
$h[0].Streams.Error | ForEach-Object { Write-Host (" [warn] " + $_.ToString().Split("`n")[0]) }
$h[0].Dispose()
}
$v = $false
while ($sink.TryDequeue([ref]$v)) { $done++; if ($v) { $ok++ } }
} finally { if ($pool) { $pool.Close(); $pool.Dispose() } }
if ($SHOW_PROGRESS -and $UseCR) { Write-Host "" }
Write-Host ("{0}: done, ok {1}, failed {2}" -f $Label, ($ok + $skip), ($jobs.Count - $ok))
$ok + $skip
}
# ---------------------------------------------------------------------- 解压 --
# 先试 .NET ZipFile(.NET 4.5+),失败再退回 Shell.Application(Win2000 起都有)
function Unzip { param([string]$Zip, [string]$To)
MK $To
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
$z = [IO.Compression.ZipFile]::OpenRead($Zip)
try {
foreach ($e in $z.Entries) {
if ($e.Length -le 0) { continue }
$p = J $To $e.FullName
MK (Split-Path -Parent $p)
[IO.Compression.ZipFileExtensions]::ExtractToFile($e, $p, $true)
}
} finally { $z.Dispose() }
return $true
} catch { }
try {
$sh = New-Object -ComObject Shell.Application
$src = $sh.NameSpace($Zip); $dst = $sh.NameSpace($To)
if (-not $src -or -not $dst) { throw "cannot open" }
$items = $src.Items(); $want = @($items).Count
$dst.CopyHere($items, 0x14)
for ($i = 0; $i -lt 600; $i++) {
if (@(Get-ChildItem -LiteralPath $To -Recurse -File -ErrorAction SilentlyContinue).Count -ge $want - 2) { break }
Start-Sleep -Milliseconds 200
}
return $true
} catch { return $false }
}
# --------------------------------------------------------------- 库匹配规则 --
# Mojang rules:默认拒绝,最后一条命中的规则生效;只认 features 为 false 的情况。
function Allow { param($Rules)
if ($Rules -eq $null) { return $true }
if ($Rules -isnot [array]) { $Rules = @($Rules) }
$a = $false
foreach ($r in $Rules) {
if ($r -isnot [hashtable]) { continue }
$act = V $r "action"; if (-not $act) { continue }
$m = $true
$os = V $r "os"
if ($os) {
$n = V $os "name"; if ($n -and $n -ne "windows") { $m = $false }
$ar = V $os "arch"; if ($m -and $ar -and $ar -eq "arm64") { $m = $false }
}
$f = V $r "features"
if ($m -and $f) { foreach ($k in $f.Keys) { if ($f[$k] -eq $true) { $m = $false; break } } }
if ($m) { $a = ($act -eq "allow") }
}
$a
}
# ====================================================================== 主流程 ===
Write-Host "=============================================="
Write-Host " Minecraft vanilla setup"
Write-Host " dir : $Dir"
Write-Host " mirror : $(if ($MIRROR) { $MIRROR } else { 'mojang (official)' })"
Write-Host " java : $(if ($JAVA_HINT) { $JAVA_HINT } else { 'auto detect' })"
Write-Host "=============================================="
$verPath = J $Dir "version.json"
if (-not (Test-Path -LiteralPath $verPath)) { Write-Host "[ERROR] version.json not found"; exit 1 }
$LIB = J $Dir "libraries"; $NatDir = J $Dir "natives"; $AST = J $Dir "assets"
MK $LIB; MK $NatDir; MK (J $AST "indexes"); MK (J $AST "objects")
$ver = Json (ReadText $verPath)
$id = V $ver "id"; $main = V $ver "mainClass"
$ain = V $ver "assetIndex"; $aid = V $ain "id"; $aurl = V $ain "url"
$java = V (V $ver "javaVersion") "majorVersion"
Write-Host ""
Write-Host "version : $id assets: $aid java: $java+"
Write-Host ""
# 1) 客户端 jar
$c = V (V $ver "downloads") "client"
if ($c) { Write-Host "downloading client.jar ..."; [void](Grab (V $c "url") (J $Dir "client.jar")) }
# 2) 库文件(并发下载)+ 解压 natives
$libs = @(V $ver "libraries"); $cp = @(); $items = @(); $natives = @()
foreach ($l in $libs) {
if ($l -isnot [hashtable]) { continue }
if (-not (Allow (V $l "rules"))) { continue }
$nm = V $l "name"
if ($nm -match ':natives-(linux|macos)') { continue }
if ($nm -match ':natives-windows-arm64') { continue } # arm64 上跑 x64 版本即可
$dl = V $l "downloads"; if (-not $dl) { continue }
$art = V $dl "artifact"; $isNat = [bool]($nm -match ':natives-windows')
$no = V $l "natives" # 旧版 JSON 的写法
if ($no) {
$k = V $no "windows"
$cls = V $dl "classifiers"
if ($k -and $cls -and (V $cls $k)) { $art = V $cls $k; $isNat = $true }
}
if (-not $art -or -not (V $art "path") -or -not (V $art "url")) { continue }
$save = J $LIB (V $art "path")
if ($isNat) { $natives += $save } else { $cp += $save }
$items += , @((V $art "url"), $save)
}
$libOk = Batch $items "libraries"
foreach ($s in $natives) {
if (-not (Test-Path -LiteralPath $s)) { continue }
# 64 位 JVM 跳过 32 位 natives(反之亦然)。混用会让错误位数的 lwjgl.dll
# 覆盖进 natives\,导致启动时报 Failed to locate library。
$isX86 = (Split-Path -Leaf $s) -match '-x86\.jar$'
if ($isX86 -and -not $script:Is32) { continue }
if (-not $isX86 -and $script:Is32) { continue }
Write-Host (" [unzip] " + (Split-Path -Leaf $s))
if (-not (Unzip $s $NatDir)) { Write-Host " [WARN] unzip failed: $s" }
}
# 把 windows/x64/<包名>/*.dll 打平到 natives/*.dll,这样 -Djava.library.path=natives 才找得到
$nroot = (Get-Item -LiteralPath $NatDir).FullName
Get-ChildItem -LiteralPath $NatDir -Recurse -Filter *.dll -ErrorAction SilentlyContinue | ForEach-Object {
if ($_.DirectoryName -ne $nroot) { $t = J $NatDir $_.Name; if (-not (Test-Path -LiteralPath $t)) { Copy-Item -LiteralPath $_.FullName -Destination $t -Force } }
}
$dlls = @(Get-ChildItem -LiteralPath $NatDir -Filter *.dll -ErrorAction SilentlyContinue).Count
Write-Host ""
Write-Host "libraries: $libOk/$($items.Count) ok natives: $dlls dll(s)"
if ($dlls -eq 0) { Write-Host " [WARN] no dll extracted" }
# 3) log4j 配置
$logArg = @()
$lc = V (V $ver "logging") "client"
if ($lc) {
$lf = V $lc "file"; $lid = (V $lf "id"); if (-not $lid) { $lid = "client.xml" }
$rel = J (J "assets" "log_configs") $lid
if ((V $lf "url") -and -not (Test-Path -LiteralPath (J $Dir $rel))) { [void](Grab (V $lf "url") (J $Dir $rel)) }
if (Test-Path -LiteralPath (J $Dir $rel)) { $logArg = @("-Dlog4j.configurationFile=" + ($rel -replace '/', '\')) }
}
# 4) 资源索引 + 资源文件(并发下载)
$irel = J (J "assets" "indexes") "$aid.json"; $ifull = J $Dir $irel
if ($aurl -and -not (Test-Path -LiteralPath $ifull)) { [void](Grab $aurl $ifull) }
if (Test-Path -LiteralPath $ifull) {
$obj = V (Json (ReadText $ifull)) "objects"
if ($obj) {
$ai = @()
foreach ($k in $obj.Keys) {
$h = V $obj[$k] "hash"; if (-not $h) { continue }
$ai += , @("https://resources.download.minecraft.net/$($h.Substring(0,2))/$h", (J (J (J $AST "objects") $h.Substring(0,2)) $h))
}
[void](Batch $ai "assets")
}
}
# 5) 生成 run.bat
Write-Host ""
Write-Host "generating run.bat ..."
$cpAll = ($cp -join ';') + ';' + (J $Dir "client.jar")
$jvm = "-Xms2G -Xmx4G -Djava.library.path=natives -Dminecraft.launcher.brand=custom " +
"-Dminecraft.launcher.version=1.0 -Dminecraft.applet.TargetDirectory=. -Duser.home=. " + ($logArg -join ' ')
$game = "$main --username Player --version `"$id`" --gameDir . --assetsDir assets " +
"--assetIndex $aid --uuid 00000000000000000000000000000000 --accessToken 0 " +
"--versionType release --width 960 --height 540"
# 把 MC_JAVA 规范成 javaw.exe 的路径(允许传 java 目录、bin 目录或 exe 本身)
$javaExe = ""
if ($JAVA_HINT) {
$jp = $JAVA_HINT.Trim().Trim('"')
switch -Wildcard ($jp.ToLowerInvariant()) {
'*.exe' { $javaExe = $jp }
default {
if (Test-Path -LiteralPath (J $jp "bin\javaw.exe")) { $javaExe = J $jp "bin\javaw.exe" }
elseif (Test-Path -LiteralPath (J $jp "javaw.exe")) { $javaExe = J $jp "javaw.exe" }
else { Write-Host ""; Write-Host "[WARN] MC_JAVA not found: $JAVA_HINT (run.bat will auto detect)" }
}
}
}
$bat = @(
"@echo off",
"setlocal",
"cd /d `"%~dp0`"",
"",
"rem ---- 查找 Java $java+ 运行时 ----",
"set `"JAVA_EXE=`""
)
# 用户指定的路径排在自动探测之前
if ($javaExe) { $bat += "if exist `"$javaExe`" set `"JAVA_EXE=$javaExe`"" }
$bat += @(
"if not defined JAVA_EXE if exist `"%~dp0jre\bin\javaw.exe`" set `"JAVA_EXE=%~dp0jre\bin\javaw.exe`"",
"rem JAVA_EXE 未指定(或指定路径不存在)时,优先用环境变量里的 Java",
"if not defined JAVA_EXE if defined JAVA_HOME if exist `"%JAVA_HOME%\bin\javaw.exe`" set `"JAVA_EXE=%JAVA_HOME%\bin\javaw.exe`"",
"if not defined JAVA_EXE for /d %%D in (`"C:\Program Files\Java\jdk-$java*`") do if exist `"%%D\bin\javaw.exe`" set `"JAVA_EXE=%%D\bin\javaw.exe`"",
"if not defined JAVA_EXE for /d %%D in (`"C:\Program Files\Eclipse Adoptium\jdk-$java*`") do if exist `"%%D\bin\javaw.exe`" set `"JAVA_EXE=%%D\bin\javaw.exe`"",
"if not defined JAVA_EXE for /d %%D in (`"C:\Program Files\Microsoft\jdk-$java*`") do if exist `"%%D\bin\javaw.exe`" set `"JAVA_EXE=%%D\bin\javaw.exe`"",
"if not defined JAVA_EXE set `"JAVA_EXE=javaw.exe`"",
"",
"`"%JAVA_EXE%`" $jvm -cp `"$cpAll`" $game",
"if errorlevel 1 goto fail",
"goto end",
"",
":fail",
"echo.",
"echo Launch failed - this version needs Java $java.",
"echo JAVA_EXE=%JAVA_EXE%",
"pause",
"",
":end",
"endlocal"
)
$batText = ($bat -join "`r`n") + "`r`n"
[IO.File]::WriteAllText((J $Dir "run.bat"), $batText, [Text.Encoding]::Default)
[IO.File]::WriteAllText((J $Dir "run.bat.utf8.bat"), $batText, (New-Object Text.UTF8Encoding($false)))
Write-Host ""
Write-Host "done - run run.bat (needs Java $java+)"
if ($javaExe) { Write-Host "java : $javaExe" }
```