Files
bd2/go/internal/client/app/web/index.html
T

130 lines
48 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#e8decc">
<title>BD2 Client Studio</title>
<style>
:root{color-scheme:light;--paper:#eee7da;--ink:#382e26;--muted:#796c5d;--line:rgba(84,62,42,.18);--rust:#965437;--olive:#63704d;--bad:#9d4034;--ease:cubic-bezier(.22,1,.36,1);--serif:Georgia,'Songti SC','Noto Serif CJK SC',SimSun,serif;--sans:'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif}
*{box-sizing:border-box}body{margin:0;background:#e7dcc7;color:var(--ink);font:14px/1.65 var(--sans);overflow-x:hidden}button,input{font:inherit}button{cursor:pointer}button:disabled{cursor:wait;opacity:.5}button,input,a{-webkit-tap-highlight-color:transparent}button:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--rust);outline-offset:5px}[hidden]{display:none!important}::selection{background:#b9764c;color:#fff9ef}
.landscape{position:fixed;inset:0;z-index:-3;background:linear-gradient(130deg,#f1e9d9 0%,#e7d5b6 42%,#bc8665 100%);overflow:hidden}.landscape svg{position:absolute;width:100%;height:100%;transition:transform 2s var(--ease),opacity 1.6s;transform:scale(1.04);opacity:.87}.landscape:after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,rgba(244,237,222,.8),rgba(240,228,207,.2) 65%,transparent),linear-gradient(0deg,rgba(73,48,31,.14),transparent 48%)}body[data-scene=server] .landscape svg{transform:scale(1.14) translateX(-2%)}body[data-scene=desk] .landscape svg{transform:scale(1.2) translateY(-3%);opacity:.27}body[data-scene=desk] .landscape:after{background:rgba(240,234,221,.55)}
.grain{position:fixed;inset:0;pointer-events:none;z-index:20;opacity:.038;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Cpath fill='%23000' filter='url(%23grain)' d='M0 0h180v180H0z'/%3E%3C/svg%3E")}.sunlight{position:fixed;inset:-40%;pointer-events:none;z-index:-2;background:radial-gradient(ellipse at 70% 23%,#fff7d777,transparent 38%);animation:drift 18s ease-in-out infinite alternate}
.topbar{height:94px;max-width:1440px;margin:auto;padding:0 58px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);gap:24px}.brand{display:flex;gap:13px;align-items:center;flex-shrink:0}.emblem{width:36px;height:36px;border:1px solid #7d624a;transform:rotate(45deg);display:grid;place-items:center}.emblem span{font:20px var(--serif);transform:rotate(-45deg)}.brand-name{font-size:13px;letter-spacing:.19em;line-height:1.4}.brand-name small{display:block;font-size:9px;letter-spacing:.28em;color:var(--muted);margin-top:4px}.top-note{font-size:11px;color:var(--muted);letter-spacing:.07em}.text-btn{background:none;border:0;color:var(--muted);padding:8px 0;border-bottom:1px solid transparent}.text-btn:hover{color:var(--rust);border-bottom-color:var(--rust)}
main{max-width:1324px;margin:auto;padding:0 58px}.scene{position:relative}.intro-scene{min-height:calc(100svh - 178px);display:grid;grid-template-columns:1.1fr .9fr;align-items:center;gap:8%;padding:66px 0 82px}.eyebrow{font-size:10px;letter-spacing:.22em;text-transform:uppercase;display:flex;align-items:center;gap:12px;color:#81634c;margin-bottom:24px}.eyebrow:before{content:'';height:1px;width:32px;background:currentColor}h1,h2,h3,p{margin:0}h1{font:400 clamp(42px,4.8vw,68px)/1.28 var(--serif);letter-spacing:.035em}.lead{max-width:380px;font-size:14px;color:var(--muted);line-height:1.95;margin-top:26px}.intro-label{display:flex;gap:14px;align-items:center;margin-top:44px;font-size:11px;color:#8a7059}.intro-label i{height:1px;width:30px;background:#987759}.form-card{position:relative;background:linear-gradient(135deg,rgba(250,246,237,.89),rgba(238,225,203,.7));border:1px solid rgba(255,255,255,.6);box-shadow:0 24px 70px -35px #65472c77,inset 0 0 0 1px rgba(116,89,55,.035);backdrop-filter:blur(24px);border-radius:4px;padding:34px 32px}.card-number{font:italic 46px/1 var(--serif);color:#b2997e;opacity:.6;margin-bottom:30px}.form-card h2{font:400 23px var(--serif);margin-bottom:9px}.description{font-size:12px;line-height:1.8;color:var(--muted);margin-bottom:26px}code{font:11px/1.8 Consolas,monospace;overflow-wrap:anywhere}label.field-label{display:block;font-size:11px;margin-bottom:9px;color:#6f5c49}.input-row{display:flex;gap:8px}input[type=text]{width:100%;min-width:0;border:1px solid #aa927755;border-radius:3px;padding:13px 12px;background:#fffcf57d;color:var(--ink);outline:none;transition:background .2s,border-color .2s;font-size:12px}input[type=text]:focus{border-color:var(--rust);background:#fffaf0}.btn{border:1px solid #81664a55;border-radius:3px;padding:11px 18px;display:inline-flex;align-items:center;justify-content:center;gap:12px;min-height:43px;background:#fffcf533;color:var(--ink);transition:background .25s,box-shadow .25s,transform .25s;white-space:nowrap;font-size:12px}.btn:hover:not(:disabled){background:#fff9eb99;transform:translateY(-2px)}.btn.primary{background:#4b3a2c;color:#fff5e4;border-color:#4b3a2c}.btn.primary:hover:not(:disabled){background:#654932;box-shadow:0 6px 17px #4b3a2c24}.btn.rust{background:#985e40;color:#fff6e8;border-color:#985e40}.btn.rust:hover:not(:disabled){background:#ad6c49}.arrow{font-size:20px;font-weight:300;line-height:1}.wide{width:100%;justify-content:space-between;margin-top:28px;padding:14px 18px}.small-note{font-size:11px;color:#877460;margin-top:12px;line-height:1.8}.inline-result{font-size:11px;min-height:22px;margin-top:10px;color:var(--muted)}.inline-result.error{color:var(--bad)}.form-tools{display:flex;align-items:center;justify-content:space-between;margin-top:4px;gap:8px;font-size:11px}.server-back{margin-top:14px;text-align:center}.server-back .text-btn{font-size:11px}.bottom-bar{max-width:1324px;margin:auto;padding:0 58px 28px;display:flex;justify-content:space-between;align-items:center;gap:24px}.steps{display:flex;list-style:none;padding:0;margin:0;gap:26px;font-size:11px;color:#8f7d69}.steps li{display:flex;gap:8px;align-items:center}.steps .num{font:italic 14px var(--serif)}.steps li.active{color:#503b2b}.steps li.active:after{content:'';width:19px;height:1px;background:#86573b}.bottom-note{font-size:10px;color:#897963;letter-spacing:.04em}
.scene.is-entering .reveal{animation:reveal 1.3s var(--ease) both;animation-delay:calc(var(--i,0)*110ms)}.scene.is-leaving{animation:leave .55s cubic-bezier(.4,0,.6,1) both;pointer-events:none}.transition-veil{position:fixed;inset:0;background:linear-gradient(115deg,#eee4d4ed,#dfc7a8e8);z-index:30;pointer-events:none;opacity:0;backdrop-filter:blur(8px)}.transition-veil.play{animation:veil 1.25s ease-in-out both}.opening-curtain{position:fixed;inset:0;background:#e8decc;pointer-events:none;z-index:40;animation:opening 1.65s ease forwards}
.desk-scene{padding:46px 0 34px}.desk-header{display:flex;justify-content:space-between;align-items:end;gap:24px;margin-bottom:30px}.desk-header .eyebrow{margin-bottom:13px}.desk-header h1{font-size:42px}.desk-header p{margin-top:12px;font-size:12px;color:var(--muted)}.connection-chip{display:flex;align-items:center;gap:9px;font-size:11px;color:#74634f;border:1px solid var(--line);padding:8px 13px;border-radius:30px}.dot{width:6px;height:6px;border-radius:50%;background:#a99b85;display:inline-block;flex-shrink:0}.dot.good{background:var(--olive);box-shadow:0 0 0 4px #68744912}.dot.bad{background:var(--bad)}.desk-grid{display:grid;grid-template-columns:minmax(0,1fr) 298px;gap:24px}.main-panel{background:#f9f5ec94;border:1px solid #fff9;border-radius:4px;box-shadow:0 12px 50px -40px #5c412c55}.panel-section{padding:25px 28px;border-bottom:1px solid var(--line)}.panel-section:last-child{border-bottom:0}.section-title{display:flex;align-items:center;gap:11px;margin-bottom:22px}.section-title .index{font:italic 14px var(--serif);color:#a08569}.section-title h2{font-size:15px;font-weight:500}.section-title .end{margin-left:auto;font-size:10px;color:var(--muted)}.setting-row{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:18px}.setting-name{font-size:10px;color:var(--muted);margin-bottom:4px}.setting-value{font-size:12px;overflow-wrap:anywhere}.setting-row>div{min-width:0}.setting-row .text-btn{font-size:11px;flex-shrink:0;color:var(--rust)}.route-options{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.route{position:relative;cursor:pointer}.route input{position:absolute;opacity:0;inset:0;width:100%;height:100%;margin:0}.route-content{display:block;position:relative;pointer-events:none;min-height:139px;padding:15px 14px;border:1px solid var(--line);border-radius:3px;transition:.25s;background:#f8f3e83d}.route-content .route-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.route-code{font:10px var(--serif);letter-spacing:.08em;color:#98826b}.radio-dot{width:11px;height:11px;border-radius:50%;border:1px solid #aa9680;display:block}.route-content strong{font-size:12px;font-weight:500;display:block;margin-bottom:5px}.route-content small{font-size:10px;line-height:1.75;display:block;color:var(--muted)}.route input:checked+.route-content{border-color:#9e7551;background:#b9986920;box-shadow:inset 0 0 0 1px #9e755118}.route input:checked+.route-content .radio-dot{background:#916c4a;border:3px solid #e8d9c3;box-shadow:0 0 0 1px #916c4a}.route input:focus-visible+.route-content{outline:2px solid var(--rust);outline-offset:4px}.route input:disabled+.route-content{opacity:.55}.route-actions{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:18px}.route-actions .text-btn{font-size:11px}.route-hint{font-size:10px;color:#927d64;margin-top:13px}.policy{font:11px/1.9 Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere;background:#dfd2bc50;border-left:2px solid #a3835e;padding:13px;margin-top:16px}.prepare-note{font-size:11px;color:var(--muted);margin:-8px 0 19px;line-height:1.8}.prepare-actions{display:flex;gap:10px;flex-wrap:wrap}.prepare-actions .btn{flex:1}.status-panel{align-self:start;padding:25px 23px;background:#dfd1ba66;border:1px solid #9875501c;border-radius:4px}.status-title{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.status-title h2{font:400 19px var(--serif)}.status-title span{font-size:9px;color:#927d65;letter-spacing:.1em}.check{display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--line);font-size:11px;gap:8px}.check span:last-child{color:#948471;font-size:10px}.check span.ok{color:var(--olive)}.check span.warn{color:var(--bad)}.status-foot{font-size:10px;color:#8d7a63;margin-top:14px;line-height:1.8}.log-wrap{border-top:1px solid var(--line);margin-top:24px;padding-top:19px}.log-heading{font-size:10px;color:#8d745b;display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.log{font-size:11px;color:#7e6c56;line-height:1.8;overflow-wrap:anywhere;min-height:65px}.log strong{display:block;color:var(--ink);font-size:12px;font-weight:500;margin-bottom:5px}.history{margin-top:14px;border-top:1px solid var(--line);padding-top:12px;font-size:10px;color:#806e59}.history summary{cursor:pointer}.history ol{list-style:none;padding:0;margin:10px 0 0;max-height:180px;overflow:auto}.history li{padding:7px 0;border-top:1px solid #a487651a;overflow-wrap:anywhere}.history time{display:block;color:#9a876f;font-size:9px}.history li.error{color:var(--bad)}.unsaved{font-size:10px;color:var(--rust);margin-top:12px}.desk-footer{display:flex;justify-content:space-between;gap:20px;margin-top:24px;color:#8a765e;font-size:10px}.exit-btn{font-size:11px}
.desk-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end}.desk-actions .btn{min-height:38px;padding:8px 16px}.local-resource{margin-top:14px;padding:14px;border:1px solid var(--line);background:#f8f3e83d;border-radius:3px}.local-resource[hidden]{display:none}.local-resource .small-note{margin:8px 0 0}
.toast{position:fixed;bottom:28px;left:50%;transform:translate(-50%,18px);max-width:min(590px,calc(100% - 32px));width:max-content;padding:13px 21px;background:#483a2e;color:#fff4e0;box-shadow:0 10px 40px #483a2e26;border:1px solid #c9ac82;border-radius:3px;font-size:12px;opacity:0;pointer-events:none;transition:.35s var(--ease);z-index:70}.toast.show{opacity:1;transform:translate(-50%,0)}.toast.error{background:#793e30;color:#fff0e3}dialog{width:min(490px,calc(100% - 32px));padding:30px;border:1px solid #fff9;border-radius:5px;background:#f0e7d7;color:var(--ink);box-shadow:0 30px 140px #37271955}dialog::backdrop{background:#40322266;backdrop-filter:blur(9px)}dialog[open]{animation:reveal .55s var(--ease)}.dialog-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:25px}.dialog-head h2{font:24px var(--serif)}.dialog-close{background:none;border:0;font-size:23px;color:var(--muted);padding:0 7px}.dialog-actions{display:flex;justify-content:flex-end;margin-top:20px}.quit-scene{min-height:65svh;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center}.quit-scene h1{font-size:44px}.quit-scene p{color:var(--muted);margin-top:20px;font-size:13px}
@keyframes reveal{from{opacity:0;filter:blur(10px);transform:translateY(24px) scale(.986)}to{opacity:1;filter:blur(0);transform:none}}@keyframes leave{to{opacity:0;filter:blur(8px);transform:translateY(-15px) scale(1.014)}}@keyframes veil{0%,100%{opacity:0}45%,58%{opacity:1}}@keyframes opening{0%,15%{opacity:1}100%{opacity:0}}@keyframes drift{to{transform:translate(-4%,3%) scale(1.05)}}
@media(min-width:1500px){.intro-scene{gap:13%}.form-card{padding:40px}.intro-scene h1{font-size:72px}}@media(max-width:1050px){.topbar{padding:0 30px}main,.bottom-bar{padding-left:30px;padding-right:30px}.intro-scene{gap:5%}.form-card{padding:27px 23px}.desk-grid{grid-template-columns:minmax(0,1fr) 250px;gap:16px}.panel-section{padding:23px 20px}.status-panel{padding:23px 18px}.route-content{padding:12px 10px}.top-note{display:none}}@media(max-width:760px){.topbar{height:77px;padding:0 22px}main,.bottom-bar{padding-left:22px;padding-right:22px}.intro-scene{grid-template-columns:1fr;gap:32px;padding:42px 0;min-height:auto}.intro-scene h1{font-size:43px}.eyebrow{margin-bottom:17px}.lead{margin-top:18px;max-width:100%}.intro-label{display:none}.form-card{padding:25px}.card-number{position:absolute;right:25px;top:25px;font-size:31px}.form-card h2{padding-right:45px}.bottom-bar{align-items:start;flex-direction:column;gap:15px}.steps{gap:21px}.desk-scene{padding-top:31px}.desk-header{align-items:start}.desk-header h1{font-size:34px}.connection-chip{font-size:10px;padding:6px 10px;margin-top:4px}.desk-grid{grid-template-columns:1fr}.status-panel{display:grid;grid-template-columns:1fr 1fr;gap:0 30px}.status-title{grid-column:1/-1}.log-wrap{margin-top:10px;border-top:0}.status-foot{margin-bottom:14px}.desk-footer{flex-direction:column;gap:7px}.bottom-note{font-size:9px}.landscape svg{width:180%;left:-60%}.desk-header p{max-width:240px}.route-content{min-height:145px}}@media(max-width:400px){main,.bottom-bar{padding-left:16px;padding-right:16px}.form-card{padding:23px 18px}.route-options{grid-template-columns:1fr}.route-content{min-height:0;padding:12px 14px}.route-content .route-top{float:right;margin:0 0 10px 20px;gap:12px}.route-content small{max-width:90%}.status-panel{display:block}.desk-header{flex-direction:column;gap:14px}.panel-section{padding:22px 16px}.btn{padding-left:13px;padding-right:13px}.steps{gap:15px}.route-actions{align-items:stretch}.prepare-actions{flex-direction:column}}
@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}.opening-curtain{display:none}}
</style>
</head>
<body data-scene="directory">
<div class="landscape" aria-hidden="true">
<svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="sky" x2="0" y2="1"><stop stop-color="#f3e6c9"/><stop offset=".7" stop-color="#dab88e"/><stop offset="1" stop-color="#b98560"/></linearGradient><linearGradient id="mesa" x2="0.2" y2="1"><stop stop-color="#b48360"/><stop offset="1" stop-color="#8f624b"/></linearGradient><linearGradient id="sand" x2=".6" y2="1"><stop stop-color="#b8946e"/><stop offset="1" stop-color="#674b39"/></linearGradient><filter id="soft"><feGaussianBlur stdDeviation="18"/></filter></defs><path fill="url(#sky)" d="M0 0h1600v1000H0z"/><circle cx="1150" cy="270" r="91" fill="#fff4ce" opacity=".4" filter="url(#soft)"/><circle cx="1150" cy="270" r="68" fill="#fff0c7" opacity=".8"/><path d="M0 640 240 590 370 610 520 558 670 580 820 510 950 560 1120 492 1280 560 1420 482 1600 530V1000H0Z" fill="#bea17f" opacity=".6"/><path d="m835 710 60-133 43-18 17-104 32-8 17-45 130-2 18 48 32 6 15 112 35 14 36 114 58 38 212 18V1000H790Z" fill="url(#mesa)" opacity=".65"/><path d="m1004 400 15 162 24 39-12 110 75 112 18-203-23-111 33-109z" fill="#d3a079" opacity=".38"/><path d="m1280 760 81-113 24-160 47-12 13-59 122 3 14 67 19 18v496h-320Z" fill="#9a6b4f" opacity=".6"/><path d="M0 746Q260 652 525 746T1030 775 1600 724V1000H0Z" fill="#c8aa80"/><path d="M0 869Q300 687 660 860T1300 814 1600 830V1000H0Z" fill="url(#sand)" opacity=".65"/><path d="M860 1000q185-97 375-124t365-13v137Z" fill="#826045" opacity=".7"/><path d="M870 998q146-128 235-165t69-90" fill="none" stroke="#e1c397" stroke-width="3" opacity=".24"/></svg>
</div>
<div class="sunlight" aria-hidden="true"></div><div class="grain" aria-hidden="true"></div><div class="opening-curtain" aria-hidden="true"></div><div id="veil" class="transition-veil" aria-hidden="true"></div>
<header class="topbar"><div class="brand"><div class="emblem" aria-hidden="true"><span>B</span></div><div class="brand-name">BD2 CLIENT STUDIO<small>CLIENT CONFIGURATION</small></div></div><div class="top-note">客户端设置工具 · 配置保存在本机</div><button class="text-btn exit-btn" id="quit">退出工具 ↗</button></header>
<main>
<section class="scene intro-scene is-entering" id="directoryScene" aria-labelledby="directoryTitle">
<div><div class="eyebrow reveal" style="--i:1">01 / GAME DIRECTORY</div><h1 class="reveal" style="--i:2" id="directoryTitle" tabindex="-1">先从游戏目录<br>开始。</h1><p class="lead reveal" style="--i:3">选择已安装的游戏文件夹。<br>我们会检查游戏程序和资源文件,再继续设置连接。</p><div class="intro-label reveal" style="--i:4"><i></i>BrownDust II · 客户端配置</div></div>
<div class="form-card reveal" style="--i:4"><div class="card-number" aria-hidden="true">01</div><h2>选择游戏目录</h2><p class="description">请选择包含 <code>BrownDust II.exe</code> 的文件夹。</p><div id="directoryHome"><div id="directoryFields"><label class="field-label" for="gameDir">游戏安装目录</label><div class="input-row"><input id="gameDir" type="text" value="" placeholder="例如 E:\Games\BrownDust II" autocomplete="off" spellcheck="false"><button class="btn" id="browse">浏览</button></div><div class="form-tools"><span id="directoryHint">可直接填写路径,也可以浏览目录。</span><button class="text-btn" id="inspect">检查目录</button></div><p class="inline-result" id="directoryResult" role="status"></p></div></div><button class="btn primary wide" id="directoryNext"><span>继续设置服务器</span><span class="arrow" aria-hidden="true">→</span></button><p class="small-note">此步骤只检查文件,不会修改游戏资源。</p></div>
</section>
<section class="scene intro-scene" id="serverScene" hidden aria-labelledby="serverTitle">
<div><div class="eyebrow reveal" style="--i:1">02 / SERVER CONNECTION</div><h1 class="reveal" style="--i:2" id="serverTitle" tabindex="-1">接下来,<br>设置服务器。</h1><p class="lead reveal" style="--i:3">填写要连接的服务端地址。<br>进入工作台后,你可以选择资源来源、应用补丁和安装插件。</p><div class="intro-label reveal" style="--i:4"><i></i>目录已选择 · 之后仍可修改</div></div>
<div class="form-card reveal" style="--i:4"><div class="card-number" aria-hidden="true">02</div><h2>服务器地址</h2><p class="description">填写完整地址,可以包含端口。</p><div id="serverHome"><div id="serverFields"><label class="field-label" for="origin">地址与端口</label><input id="origin" type="text" value="" placeholder="https://play.example.com" autocomplete="url" spellcheck="false" aria-describedby="originHint"><p class="small-note" id="originHint">例如 <code>http://192.168.1.8:8080</code><br>不要添加 <code>/game/</code> 或其他路径。真实连接地址由 LI 插件读取。</p><p class="inline-result" id="serverResult" role="status"></p></div></div><button class="btn primary wide" id="serverNext"><span>进入工作台</span><span class="arrow" aria-hidden="true">→</span></button><div class="server-back"><button class="text-btn" id="serverBack">← 返回目录设置</button></div></div>
</section>
<section class="scene desk-scene" id="deskScene" hidden aria-labelledby="deskTitle">
<header class="desk-header reveal" style="--i:0"><div><div class="eyebrow">03 / WORKSPACE</div><h1 id="deskTitle" tabindex="-1">客户端工作台</h1><p>管理连接设置,完成游戏启动前的准备。</p></div><div class="desk-actions"><div class="connection-chip"><span class="dot" id="pulse"></span><span id="overallStatus">等待检查</span></div><button class="btn primary" id="launch">启动游戏 <span aria-hidden="true">▶</span></button></div></header>
<div class="desk-grid"><div class="main-panel reveal" style="--i:1">
<section class="panel-section"><div class="section-title"><span class="index">01</span><h2>连接设置</h2><span class="end">随时可以修改</span></div><div class="setting-row"><div><div class="setting-name">游戏目录</div><div class="setting-value" id="directorySummary">尚未选择</div></div><button class="text-btn" id="editDirectory">修改目录 ↗</button></div><div class="setting-row"><div><div class="setting-name">服务器地址</div><div class="setting-value" id="serverSummary">尚未填写</div></div><button class="text-btn" id="editServer">修改地址 ↗</button></div></section>
<section class="panel-section"><div class="section-title"><span class="index">02</span><h2>资源来源</h2><span class="end">CDN</span></div><div class="route-options" role="radiogroup" aria-label="CDN 类型">
<label class="route"><input type="radio" name="cdn" value="official" checked><span class="route-content"><span class="route-top"><span class="route-code">OFFICIAL</span><span class="radio-dot"></span></span><strong>官方 CDN</strong><small>从官方节点下载资源,不占用服主的大文件流量。</small></span></label>
<label class="route"><input type="radio" name="cdn" value="local"><span class="route-content"><span class="route-top"><span class="route-code">LOCAL</span><span class="radio-dot"></span></span><strong>本机已下载资源</strong><small>直接读取电脑上的 ServerData 和 GameData,不再下载相同文件。</small></span></label>
<label class="route"><input type="radio" name="cdn" value="server"><span class="route-content"><span class="route-top"><span class="route-code">SERVER</span><span class="radio-dot"></span></span><strong>服主资源</strong><small>由服务器提供资源地址;服主可自建 CDN,也可以反代官方 CDN。</small></span></label>
</div><div class="local-resource" id="localResource" hidden><label class="field-label" for="localResourceDir">本机 CDN 根目录</label><div class="input-row"><input id="localResourceDir" type="text" value="" placeholder="例如 E:\bd2\dl" autocomplete="off" spellcheck="false"><button class="btn" id="browseResources">浏览</button></div><p class="small-note">所选目录内必须同时包含 <code>ServerData</code> 和 <code>GameData</code>。</p></div><p class="route-hint" id="routeHint">官方模式由客户端直连版本锁定的官方 CDN。</p><div class="route-actions"><button class="text-btn" id="checkResources">验证资源来源 ↗</button><button class="btn" id="save">保存连接设置</button></div><p class="unsaved" id="dirtyNotice">当前设置尚未保存。</p><div class="policy" id="policy" role="status" hidden></div></section>
<section class="panel-section"><div class="section-title"><span class="index">03</span><h2>补丁与插件</h2></div><p class="prepare-note">操作前请先关闭游戏。补丁只修改内置 Intro 地址,并保留 <code>resources.assets.bak</code> 备份。安装插件前,请先手动安装 BepInEx。</p><div class="prepare-actions"><button class="btn rust" id="patch">应用客户端补丁 <span aria-hidden="true">↗</span></button><button class="btn primary" id="install">安装 / 更新插件 <span aria-hidden="true">↗</span></button></div></section>
</div><aside class="status-panel reveal" style="--i:3" aria-label="客户端状态"><div class="status-title"><h2>当前状态</h2><span>LOCAL STATUS</span></div><div><div class="check"><span>游戏程序</span><span id="sExe">等待检查</span></div><div class="check"><span>资源文件</span><span id="sAssets">等待检查</span></div><div class="check"><span>BepInEx</span><span id="sBep">等待检查</span></div><div class="check"><span>连接配置</span><span id="sConfig">尚未写入</span></div><p class="status-foot">状态来自本机文件检查,不代表服务器在线。<br>诊断日志:<code id="persistentLogPath"></code></p><button class="text-btn" style="font-size:11px" id="refresh">重新检查 ↻</button></div><div class="log-wrap"><div class="log-heading"><span>最近操作</span><time id="logTime">—</time></div><div class="log" id="log" role="status"><strong>等待操作</strong><span>选择游戏目录后即可开始检查。</span></div><details class="history"><summary>查看操作记录</summary><ol id="historyList"></ol></details></div></aside></div>
<footer class="desk-footer reveal" style="--i:4"><span>BD2 CLIENT STUDIO <span aria-hidden="true">/</span> 配置仅保存在你的电脑上</span><span>不会保存账号凭据或服主密钥</span></footer>
</section>
<section class="scene quit-scene" id="quitScene" hidden aria-labelledby="quitTitle"><div class="eyebrow reveal">CLIENT STUDIO</div><h1 class="reveal" id="quitTitle" tabindex="-1">工具已退出</h1><p class="reveal">现在可以关闭这个页面。</p></section>
</main>
<footer class="bottom-bar" id="journey"><ol class="steps" aria-label="设置进度"><li data-step="directory" class="active" aria-current="step"><span class="num">01</span>选择目录</li><li data-step="server"><span class="num">02</span>设置服务器</li><li data-step="desk"><span class="num">03</span>客户端工作台</li></ol><span class="bottom-note">BROWN DUST II · CLIENT UTILITY</span></footer>
<dialog id="editor" aria-labelledby="editorTitle"><div class="dialog-head"><h2 id="editorTitle">修改设置</h2><button class="dialog-close" id="closeEditor" aria-label="关闭设置">×</button></div><div id="editorContent"></div><p class="small-note">修改后请在工作台保存连接设置。</p><div class="dialog-actions"><button class="btn primary" id="doneEditor">完成修改</button></div></dialog>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<noscript><p style="padding:20px">请启用 JavaScript 以使用客户端设置工具。</p></noscript>
<script>
(()=>{
'use strict';
let HOST_PLATFORM='',INITIAL_CDN='official',AUTO_OPEN=false;
const $=id=>document.getElementById(id);
const reduced=window.matchMedia('(prefers-reduced-motion: reduce)');
const CHINA_TIME_ZONES=new Set(['Asia/Shanghai','Asia/Chongqing','Asia/Harbin','Asia/Urumqi','Asia/Hong_Kong','Asia/Macau','Asia/Taipei']);
let detectedTimeZone='';try{detectedTimeZone=Intl.DateTimeFormat().resolvedOptions().timeZone||''}catch{}
const zhCN=CHINA_TIME_ZONES.has(detectedTimeZone);
const localized=(zh,en)=>zhCN?zh:en;
if(!zhCN){
document.documentElement.lang='en';
document.title='BD2 Client Studio';
const translations=new Map(Object.entries({
'客户端设置工具 · 配置保存在本机':'Client setup utility · Settings stay on this computer','退出工具 ↗':'Exit tool ↗','先从游戏目录':'Start with the game directory','开始。':'first.','选择已安装的游戏文件夹。':'Select the installed game folder.','我们会检查游戏程序和资源文件,再继续设置连接。':'We will inspect the game executable and assets before configuring the connection.','客户端配置':'Client configuration','选择游戏目录':'Select game directory','请选择包含':'Select the folder containing','的文件夹。':'.','游戏安装目录':'Game installation directory','浏览':'Browse','可直接填写路径,也可以浏览目录。':'Enter a path or browse for a directory.','检查目录':'Inspect directory','继续设置服务器':'Continue to server setup','此步骤只检查文件,不会修改游戏资源。':'This step only inspects files and does not modify game assets.','接下来,':'Next,','设置服务器。':'configure the server.','填写要连接的服务端地址。':'Enter the server you want to connect to.','进入工作台后,你可以选择资源来源、应用补丁和安装插件。':'In the workspace you can select a resource source, apply the patch, and install plugins.','目录已选择 · 之后仍可修改':'Directory selected · You can change it later','服务器地址':'Server address','填写完整地址,可以包含端口。':'Enter the complete origin, including a port when needed.','地址与端口':'Address and port','例如':'Example','不要添加':'Do not add','或其他路径。真实连接地址由 LI 插件读取。':'or another path. The LI plugin reads the actual connection address.','进入工作台':'Open workspace','返回目录设置':'Back to directory setup','客户端工作台':'Client workspace','管理连接设置,完成游戏启动前的准备。':'Manage connection settings and prepare the game client.','启动游戏':'Launch game','等待检查':'Waiting','连接设置':'Connection settings','随时可以修改':'Editable at any time','游戏目录':'Game directory','尚未选择':'Not selected','修改目录 ↗':'Change directory ↗','尚未填写':'Not entered','修改地址 ↗':'Change address ↗','资源来源':'Resource source','官方 CDN':'Official CDN','从官方节点下载资源,不占用服主的大文件流量。':'Download from official nodes without using the server owner’s bandwidth.','本机已下载资源':'Downloaded local resources','直接读取电脑上的 ServerData 和 GameData,不再下载相同文件。':'Read ServerData and GameData already stored on this computer.','服主资源':'Server-provided resources','由服务器提供资源地址;服主可自建 CDN,也可以反代官方 CDN。':'The server supplies resource URLs backed by either a private CDN or an official-CDN proxy.','本机 CDN 根目录':'Local CDN root directory','所选目录内必须同时包含':'The selected directory must contain both',' 和 ':' and ','。':'.','官方模式由客户端直连版本锁定的官方 CDN。':'Official mode connects directly to the release-locked official CDN.','验证资源来源 ↗':'Verify resource source ↗','保存连接设置':'Save connection settings','当前设置尚未保存。':'Current settings have not been saved.','补丁与插件':'Patch and plugins','操作前请先关闭游戏。补丁只修改内置 Intro 地址,并保留':'Close the game first. The patch only changes the built-in Intro address and retains a','备份。安装插件前,请先手动安装 BepInEx。':'backup. Install BepInEx manually before installing the plugins.','应用客户端补丁':'Apply client patch','安装 / 更新插件':'Install / update plugins','当前状态':'Current status','游戏程序':'Game executable','资源文件':'Asset file','需手动安装':'Manual install required','连接配置':'Connection configuration','尚未写入':'Not written','状态来自本机文件检查,不代表服务器在线。':'Status is based on local files and does not indicate server availability.','诊断日志:':'Diagnostic log:','重新检查 ↻':'Inspect again ↻','最近操作':'Recent operation','等待操作':'Waiting for an operation','选择游戏目录后即可开始检查。':'Select the game directory to begin.','查看操作记录':'View operation history','配置仅保存在你的电脑上':'Settings are stored only on your computer','不会保存账号凭据或服主密钥':'Account credentials and server-owner secrets are never stored','工具已退出':'Tool exited','现在可以关闭这个页面。':'You can close this page now.','选择目录':'Select directory','设置服务器':'Configure server','修改设置':'Edit settings','修改后请在工作台保存连接设置。':'Save changes from the workspace when finished.','完成修改':'Finish editing','请启用 JavaScript 以使用客户端设置工具。':'Enable JavaScript to use the client setup tool.','CDN 类型':'CDN source','客户端状态':'Client status','设置进度':'Setup progress','关闭设置':'Close settings'
}));
const walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let node;
while(node=walker.nextNode()){if(node.parentElement&&['SCRIPT','STYLE'].includes(node.parentElement.tagName))continue;let value=node.nodeValue;for(const [source,target] of translations)value=value.replaceAll(source,target);node.nodeValue=value}
document.querySelectorAll('[aria-label]').forEach(element=>{let value=element.getAttribute('aria-label');for(const [source,target] of translations)value=value.replaceAll(source,target);element.setAttribute('aria-label',value)});
$('gameDir').placeholder='Example: E:\\Games\\Brown Dust II';$('origin').placeholder='https://play.example.com';$('localResourceDir').placeholder='Example: E:\\bd2\\dl';
}
let scene='directory',busy=false,transitioning=false,toastTimer,editorKind=null,savedSignature=null;
const status={executable:null,resources:null,bepinex:null,config:null};
function payload(){const mode=document.querySelector('input[name="cdn"]:checked').value;return{game_directory:$('gameDir').value.trim(),server_origin:$('origin').value.trim(),cdn_mode:mode,local_resource_directory:mode==='local'?$('localResourceDir').value.trim():'',ui_language:zhCN?'zh-CN':'en'}}
function signature(){return JSON.stringify(payload())}
const backendMessages={'Selection cancelled':'已取消选择','Game client found':'已找到游戏客户端','Local resource directory found':'已找到本机资源目录','Game directory is valid':'游戏目录有效','The client will use the release-locked official CDN':'将由客户端直接连接版本锁定的官方 CDN','Local resources verified':'本机资源已验证','Server resource policy verified':'服务器资源策略已确认','Connection settings saved':'连接设置已保存','Client entry point patched; the original backup was retained':'客户端入口补丁完成;原文件备份已保留','Client patch is already complete; no asset file was rewritten':'客户端补丁已经完成,未再次改写资源文件','BD2 client plugins installed or updated':'BD2 客户端插件已安装或更新','BD2 client plugins are already up to date; no DLL was rewritten':'BD2 客户端插件已经是最新版本,未再次改写 DLL','Brown Dust II started':'Brown Dust II 已启动','Brown Dust II is already running':'Brown Dust II 已经在运行','Client tool exited':'客户端工具已退出'};
const localizeMessage=message=>zhCN?(backendMessages[message]||message):message;
const nativeMethods={browse:'Browse',browseResources:'BrowseResources',inspect:'Inspect',resources:'Resources',save:'Save',patch:'Patch',install:'Install',launch:'Launch'};
function bridge(){const bindings=window.go&&window.go.app&&window.go.app.Studio;if(!bindings)throw new Error(localized('客户端原生接口尚未就绪','The native client interface is unavailable'));return bindings}
async function api(operation,body={}){let data;try{data=await bridge()[nativeMethods[operation]](body)}catch(error){throw new Error(localizeMessage(error.message||String(error)))}if(!data||!data.ok)throw new Error(localizeMessage(data&&data.message)||localized('操作失败','Operation failed'));if(data.message)data.message=localizeMessage(data.message);return data}
function show(message,error=false){message=message||(error?localized('操作未完成','Operation failed'):localized('操作已完成','Operation completed'));clearTimeout(toastTimer);const t=$('toast');t.textContent=message;t.className='toast show'+(error?' error':'');toastTimer=setTimeout(()=>t.className='toast',5200);$('log').replaceChildren();const title=document.createElement('strong');title.textContent=error?localized('操作未完成','Operation failed'):localized('操作已完成','Operation completed');const desc=document.createElement('span');desc.textContent=message;$('log').append(title,desc);const now=new Date(),time=now.toLocaleTimeString(zhCN?'zh-CN':'en-US',{hour:'2-digit',minute:'2-digit',second:'2-digit'});$('logTime').textContent=time;const row=document.createElement('li');if(error)row.className='error';const stamp=document.createElement('time');stamp.dateTime=now.toISOString();stamp.textContent=time;row.append(stamp,document.createTextNode(message));$('historyList').prepend(row);while($('historyList').children.length>20)$('historyList').lastElementChild.remove()}
function mark(id,value,yes=localized('已就绪','Ready'),no=localized('未检测到','Not found')){const e=$(id);e.textContent=value===null?localized('等待检查','Waiting'):value?yes:no;e.className=value===null?'':value?'ok':'warn'}
function renderStatus(){mark('sExe',status.executable);mark('sAssets',status.resources);mark('sBep',status.bepinex,localized('已安装','Installed'),localized('需手动安装','Manual install required'));mark('sConfig',status.config,localized('已写入','Written'),localized('尚未写入','Not written'));const checked=status.executable!==null&&status.resources!==null;const ready=status.executable&&status.resources;$('pulse').className='dot'+(checked?(ready?' good':' bad'):'');$('overallStatus').textContent=checked?(ready?localized('游戏文件已就绪','Game files ready'):localized('游戏文件待检查','Game files need attention')):localized('等待检查','Waiting');if(savedSignature&&signature()!==savedSignature){$('sConfig').textContent=localized('有未保存修改','Unsaved changes');$('sConfig').className='warn'}}
function sync(){const p=payload();$('directorySummary').textContent=p.game_directory||localized('尚未选择','Not selected');$('serverSummary').textContent=p.server_origin||localized('尚未填写','Not entered');$('localResource').hidden=p.cdn_mode!=='local';$('routeHint').textContent=p.cdn_mode==='official'?localized('官方模式由客户端直连版本锁定的官方 CDN。','Official mode connects directly to the release-locked official CDN.'):p.cdn_mode==='local'?localized('本机模式不会请求服务器资源接口;资源版本仍必须与当前客户端一致。','Local mode does not call the server resource API; resources must still match this client release.'):localized('服主自建与反代在客户端中是同一种来源,地址统一由服务器返回。','Private hosting and reverse proxying are one client mode; the server returns the resource URLs.');$('dirtyNotice').hidden=signature()===savedSignature;renderStatus()}
function applyStatus(s){if(!s)return;if(s.game_directory)$('gameDir').value=s.game_directory;for(const key of Object.keys(status))status[key]=Boolean(s[key]);sync();$('directoryResult').textContent=status.executable&&status.resources?localized('游戏程序和资源文件已检测到。','Game executable and asset file found.'):localized('检查已完成,请在工作台查看缺失项。','Inspection completed; review missing items in the workspace.');$('directoryResult').className='inline-result'+(status.executable&&status.resources?'':' error')}
function invalidate(directory=false){if(directory){for(const key of Object.keys(status))status[key]=null;$('directoryResult').textContent=localized('目录已修改,请重新检查。','The directory changed; inspect it again.')}$('policy').hidden=true;sync()}
function requireDirectory(){if(!payload().game_directory){$('gameDir').focus();throw new Error(localized('请先选择游戏安装目录','Select the game installation directory first'))}}
function requireOrigin(){const value=$('origin').value.trim();let u;try{u=new URL(value)}catch{throw new Error(localized('请输入完整服务器地址,例如 http://127.0.0.1:8080','Enter a complete server origin, for example http://127.0.0.1:8080'))}if(!['http:','https:'].includes(u.protocol)||u.username||u.password||u.search||u.hash||(u.pathname!=='/'&&u.pathname!==''))throw new Error(localized('服务器地址仅填写 http(s) 地址与端口,不要包含路径、查询参数或账号信息','Enter only an HTTP(S) origin and port, without a path, query, or credentials'));const loopback=['localhost','127.0.0.1','[::1]'].includes(u.hostname.toLowerCase());if(u.protocol!=='https:'&&!loopback)throw new Error(localized('公网或局域网服务器必须使用 HTTPS;只有本机 loopback 可以使用 HTTP','Public and LAN servers must use HTTPS; only loopback may use HTTP'));$('origin').value=value.replace(/\/$/,'');$('serverResult').textContent='';sync()}
function requireResources(){const p=payload();if(p.cdn_mode==='local'&&!p.local_resource_directory){$('localResourceDir').focus();throw new Error(localized('请选择包含 ServerData 和 GameData 的本机 CDN 目录','Select a local CDN directory containing ServerData and GameData'))}}
function setBusy(value){busy=value;document.querySelectorAll('button,input').forEach(e=>{e.disabled=value});document.body.setAttribute('aria-busy',String(value))}
async function action(id,fn){if(busy||transitioning)return;const b=$(id),old=b.innerHTML;setBusy(true);b.textContent=localized('请稍候…','Please wait…');try{await fn()}catch(e){const message=e.message||String(e);show(message,true);if(scene==='directory'||editorKind==='directory'){$('directoryResult').textContent=message;$('directoryResult').className='inline-result error'}if(scene==='server'||editorKind==='server'){$('serverResult').textContent=message;$('serverResult').className='inline-result error'}}finally{b.innerHTML=old;setBusy(false)}}
const delay=ms=>new Promise(resolve=>setTimeout(resolve,ms));
async function navigateScene(next){if(transitioning||scene===next)return;transitioning=true;const old=$(scene+'Scene'),target=$(next+'Scene');if(!reduced.matches){old.classList.add('is-leaving');$('veil').classList.add('play');await delay(570)}old.hidden=true;old.classList.remove('is-leaving','is-entering');scene=next;document.body.dataset.scene=next;target.hidden=false;target.classList.add('is-entering');$('journey').hidden=next==='desk'||next==='quit';document.querySelectorAll('[data-step]').forEach(e=>{const active=e.dataset.step===next;e.classList.toggle('active',active);if(active)e.setAttribute('aria-current','step');else e.removeAttribute('aria-current')});window.scrollTo({top:0,behavior:'instant'});$(next==='directory'?'directoryTitle':next==='server'?'serverTitle':next==='desk'?'deskTitle':'quitTitle').focus({preventScroll:true});if(!reduced.matches)await delay(680);$('veil').classList.remove('play');transitioning=false}
async function inspect(){requireDirectory();const r=await api('inspect',payload());applyStatus(r.data);show(r.message);return r}
function openEditor(kind){if(busy||transitioning)return;editorKind=kind;$('editorTitle').textContent=kind==='directory'?localized('修改游戏目录','Change game directory'):localized('修改服务器地址','Change server address');$('editorContent').append($(kind+'Fields'));$('editor').showModal();$(kind==='directory'?'gameDir':'origin').focus()}
function closeEditor(){if(busy)return;$('editor').close()}
$('editor').addEventListener('close',()=>{if(editorKind)$(editorKind+'Home').append($(editorKind+'Fields'));editorKind=null;sync()});
$('editor').addEventListener('cancel',e=>{if(busy)e.preventDefault()});
$('closeEditor').onclick=closeEditor;
$('doneEditor').onclick=()=>{try{if(editorKind==='server')requireOrigin();else requireDirectory();closeEditor()}catch(e){show(e.message,true)}};
$('editDirectory').onclick=()=>openEditor('directory');$('editServer').onclick=()=>openEditor('server');
$('gameDir').addEventListener('input',()=>invalidate(true));$('origin').addEventListener('input',()=>{$('serverResult').textContent='';invalidate()});$('localResourceDir').addEventListener('input',()=>invalidate());document.querySelectorAll('input[name="cdn"]').forEach(e=>e.addEventListener('change',()=>invalidate()));
$('browse').onclick=()=>action('browse',async()=>{const previous=payload().game_directory;const r=await api('browse',payload());if(r.data){applyStatus(r.data);if(payload().game_directory!==previous)$('policy').hidden=true}show(r.message)});
$('browseResources').onclick=()=>action('browseResources',async()=>{const r=await api('browseResources',payload());if(r.data){$('localResourceDir').value=r.data.local_directory||'';invalidate()}show(r.message)});
$('inspect').onclick=()=>action('inspect',inspect);$('refresh').onclick=()=>action('refresh',inspect);
$('directoryNext').onclick=()=>action('directoryNext',async()=>{await inspect();sync();await navigateScene('server')});
$('serverBack').onclick=()=>{if(!busy)navigateScene('directory')};
$('serverNext').onclick=()=>action('serverNext',async()=>{requireOrigin();sync();await navigateScene('desk')});
$('checkResources').onclick=()=>action('checkResources',async()=>{requireDirectory();requireOrigin();requireResources();const r=await api('resources',payload()),p=r.data||{},missing=localized('未返回','Missing');$('policy').hidden=false;$('policy').textContent=p.mode==='official'?localized('OFFICIAL · 客户端使用版本锁定的官方地址','OFFICIAL · The client uses release-locked official URLs'):p.mode==='local'?`LOCAL · ${p.local_directory||payload().local_resource_directory}\nServerData · ${p.server_data_url||missing}\nGameData · ${p.game_data_url||missing}`:`SERVER · ${localized('由服务器提供资源地址','Resource URLs supplied by the server')}\nServerData · ${p.server_data_url||missing}\nGameData · ${p.game_data_url||missing}\nBundle · ${p.bundle_version||missing}\nGameData · ${p.game_data_version||missing}`;show(r.message)});
function saved(){savedSignature=signature();status.config=true;sync()}
$('save').onclick=()=>action('save',async()=>{requireDirectory();requireOrigin();requireResources();const r=await api('save',payload());saved();show(r.message)});
$('patch').onclick=()=>action('patch',async()=>{requireDirectory();requireOrigin();requireResources();const r=await api('patch',payload());saved();show(r.message)});
$('install').onclick=()=>action('install',async()=>{requireDirectory();requireOrigin();requireResources();const r=await api('install',payload());status.bepinex=true;saved();show(r.message)});
$('launch').onclick=()=>action('launch',async()=>{requireDirectory();requireOrigin();requireResources();const r=await api('launch',payload());saved();show(r.message)});
$('quit').onclick=()=>{if(!busy){clearTimeout(toastTimer);bridge().Quit().catch(error=>show(error.message||String(error),true))}};
$('gameDir').addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();(editorKind?$('inspect'):$('directoryNext')).click()}});$('origin').addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();(editorKind?$('doneEditor'):$('serverNext')).click()}});
async function initialize(){setBusy(true);try{const initial=await bridge().Initialize();HOST_PLATFORM=initial.platform;INITIAL_CDN=initial.cdn_mode;AUTO_OPEN=initial.auto_open;$('gameDir').value=initial.game_directory||'';$('origin').value=initial.server_origin||'http://127.0.0.1:8080';$('localResourceDir').value=initial.local_resource_directory||'';$('persistentLogPath').textContent=initial.log_path||'';const initialRadio=document.querySelector(`input[name="cdn"][value="${CSS.escape(INITIAL_CDN)}"]`);if(initialRadio)initialRadio.checked=true;if(HOST_PLATFORM==='darwin'){$('gameDir').placeholder=localized('例如 /Applications/BrownDust II.app','Example: /Applications/BrownDust II.app');$('directoryScene').querySelector('.description').textContent=localized('请选择 BrownDust II.app 或它所在的文件夹。','Select BrownDust II.app or its parent folder.')}sync();if($('gameDir').value.trim()){await inspect();if(AUTO_OPEN){saved();await navigateScene('desk')}}}catch(error){show(error.message||String(error),true)}finally{setBusy(false)}}
initialize();
})();
</script>
</body>
</html>