01  ║  LAUNCHER

런처

Tauri v2로 만들었어요. 안쪽은 Rust core, 바깥은 React UI고, 둘은 bindings.ts 한 파일로만 만나요. 이 장에서는 그 파일부터 읽고, 설치와 계정 흐름을 따라가요.

계약: bindings.ts

launcher/src/core/bindings.ts

UI가 core에 시킬 수 있는 일이 타입과 함께 이 한 파일에 있어요. 설정 조회부터 게임 실행까지 37개 함수예요. UI는 이 목록만 보고 만들었고, core는 이 목록만 지키면 돼요.

// 계약의 일부 — 전문은 파일 참고
export interface Core {
  versions_manifest(): Promise<VersionManifest>;
  instance_install(id: string, version: string): Promise<void>;
  auth_begin_device_code(): Promise<DeviceCode>;
  launch_quickplay(req: QuickPlay): Promise<void>;
  news_feed(): Promise<NewsItem[]>;
  // …37개 전체
}

구현은 두 가지예요. mockCore.ts는 가짜 데이터로 UI만 돌리고, tauriCore.ts는 실제 Rust core로 넘겨요. UI는 어느 쪽 위인지 몰라요. UI를 먼저 만들 수 있었던 이유예요.

Rust core: 설치 파이프라인

launcher/src-tauri/src/ — meta · download · install · fabric · mods

설치는 다섯 단계로 나뉘어요. meta.rs가 piston-meta에서 버전 정의를 읽고, download.rs가 각 파일을 Mojang CDN에서 받은 뒤 공개된 SHA1과 맞춰 봐요. 다르면 그 파일은 쓰지 않아요. install.rs가 버전 JSON을 실행 계획으로 풀고(classpath, natives, JVM 인자), fabric.rs가 Fabric loader를 얹고, mods.rs가 카탈로그에 있는 모드를 넣어요.

// download.rs — SHA1이 맞을 때만 그 파일을 써요
match &item.sha1 {
    Some(want) => sha1_of(&item.path).await.as_deref() == Some(want.as_str()),
    // 해시가 없으면 받지 않아요
    None => false,
}

Sodium, Lithium, Fabric API는 설치할 때 각자 공식 배포처(Modrinth, Fabric maven)에서 받아요. 우리가 만든 jar가 아니면 손대지 않고, 목록은 저장소의 THIRD-PARTY-NOTICES.md에 있어요.

계정: 토큰이 UI에 닿지 않는다

launcher/src-tauri/src/auth.rs

로그인은 Microsoft OAuth(device code, 또는 시스템 브라우저 + PKCE)로 시작해서 Xbox Live → XSTS → Minecraft 서비스로 이어져요. 이 과정은 전부 core 안에서 끝나요. UI는 이름, uuid, 스킨 URL만 받고, 액세스·리프레시 토큰은 IPC를 넘어가지 않아요. 세션은 인스턴스 데이터 폴더에 저장되고, 만료 1분 전에 자동으로 갱신돼요.

Azure 애플리케이션 id는 포크마다 달라요. Microsoft가 Xbox Live 스코프를 앱에 묶어 둬서, 풀문 클라이언트는 자기 앱 id를 brand.json에 넣어요. 포크할 때는 자기 id를 등록하거나, 개발할 때는 PINION_MS_CLIENT_ID 환경 변수로 넣을 수 있어요.

실행: launch.rs

launcher/src-tauri/src/launch.rs

실행은 launch_quickplay 한 경로로 들어와요. core는 저장된 세션으로 인자를 만들고(토큰은 프로세스 인자로만 넘기고 파일로는 안 남겨요), 게임을 띄운 뒤 stdout을 game://log 이벤트로 UI에 보내요. 홈 화면 실행 오버레이가 그 로그를 보여 주고, 크래시가 나면 마지막 줄이 거기에 남아요. 별도 콘솔 화면은 없어요.