Files
a301_launcher/main.go
tolelom 742712aa49
Some checks failed
CI/CD / test (push) Has been cancelled
CI/CD / release (push) Has been cancelled
fix: 게임에 토큰이 전달되지 않아 로그인 화면이 뜨는 버그 수정
환경변수(A301_TOKEN)로 토큰을 전달했지만 게임은 커맨드라인 인자(-token)를
읽도록 되어있어 토큰이 전달되지 않던 문제.
exec.Command에 -token 인자를 추가하여 게임의 AuthState가 토큰을 받도록 수정.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:21:23 +09:00

154 lines
4.5 KiB
Go

package main
import (
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
)
// version is set at build time via -ldflags "-X main.version=x.y.z"
var version = "dev"
func handleURI(rawURI string) error {
parsed, err := url.Parse(rawURI)
if err != nil {
return fmt.Errorf("URI 파싱 실패: %w", err)
}
if parsed.Scheme != protocolName {
return fmt.Errorf("지원하지 않는 URI 스킴: %s", parsed.Scheme)
}
// 웹 클라이언트가 발급한 일회용 티켓을 서버에서 JWT로 교환
ticket := parsed.Query().Get("token")
if ticket == "" {
return fmt.Errorf("토큰이 없습니다")
}
token, err := redeemTicket(ticket)
if err != nil {
return fmt.Errorf("런처 인증에 실패했습니다: %w", err)
}
// JWT는 점(.)으로 구분된 3파트 형식이어야 함
if parts := strings.Split(token, "."); len(parts) != 3 {
return fmt.Errorf("서버에서 유효하지 않은 토큰을 받았습니다")
}
gameDir, err := installDir()
if err != nil {
return err
}
if err := os.MkdirAll(gameDir, 0755); err != nil {
return fmt.Errorf("게임 디렉토리 생성 실패: %w", err)
}
gamePath := filepath.Join(gameDir, gameExeName)
// 프로토콜 등록이 현재 런처를 가리키도록 갱신 (사일런트)
_ = install()
// 이전 업데이트에서 남은 .old/.new 파일 정리
cleanupOldFiles(gameDir)
serverInfo, err := fetchServerInfo()
if err != nil {
// 오프라인 모드: 게임이 이미 설치되어 있으면 직접 실행
if _, statErr := os.Stat(gamePath); statErr == nil {
ret := msgBox("One of the plans", "서버에 연결할 수 없습니다.\n설치된 게임을 실행하시겠습니까?\n(업데이트 확인 불가)", mbYesNo|mbQ)
if ret == idYes {
cmd := exec.Command(gamePath, "-token", token)
cmd.Dir = gameDir
if err := cmd.Start(); err != nil {
return fmt.Errorf("게임 실행 실패: %w", err)
}
return nil
}
return fmt.Errorf("사용자가 취소했습니다")
}
return fmt.Errorf("버전 확인 실패: %w", err)
}
// 런처 자동 업데이트 체크
if updated, updateErr := ensureLauncher(serverInfo); updateErr != nil {
fmt.Fprintf(os.Stderr, "런처 업데이트 실패: %v\n", updateErr)
} else if updated {
cmd := exec.Command(os.Args[0], os.Args[1:]...)
if err := cmd.Start(); err != nil {
return fmt.Errorf("새 런처 시작 실패: %w", err)
}
os.Exit(0)
}
if err := ensureGame(gameDir, gamePath, serverInfo); err != nil {
return err
}
cmd := exec.Command(gamePath, "-token", token)
cmd.Dir = gameDir
if err := cmd.Start(); err != nil {
return fmt.Errorf("게임 실행 실패: %w", err)
}
return nil
}
func main() {
// DLL Hijacking 방어: 시스템 디렉토리에서만 DLL 로드
const loadLibrarySearchSystem32 = 0x00000800
kernel32.NewProc("SetDefaultDllDirectories").Call(loadLibrarySearchSystem32)
enableDPIAwareness()
if !acquireSingleInstance() {
activateExistingWindow()
return
}
if len(os.Args) < 2 {
if err := install(); err != nil {
msgBox("One of the plans 런처 - 오류", fmt.Sprintf("설치 실패:\n%v", err), mbOK|mbError)
os.Exit(1)
}
msgBox("One of the plans", "설치가 완료되었습니다.\n웹에서 게임 시작 버튼을 클릭하세요.", mbOK|mbInfo)
return
}
arg := os.Args[1]
switch {
case arg == "install":
if err := install(); err != nil {
msgBox("One of the plans 런처 - 오류", fmt.Sprintf("등록 실패:\n%v", err), mbOK|mbError)
os.Exit(1)
}
msgBox("One of the plans 런처", "a301:// 프로토콜이 등록되었습니다.", mbOK|mbInfo)
case arg == "uninstall":
ret := msgBox("One of the plans 런처", "게임 데이터도 함께 삭제하시겠습니까?", mbYesNo|mbQ)
deleteData := ret == idYes
if err := uninstall(); err != nil {
msgBox("One of the plans 런처 - 오류", fmt.Sprintf("제거 실패:\n%v", err), mbOK|mbError)
os.Exit(1)
}
if deleteData {
if dir, err := installDir(); err == nil {
os.RemoveAll(dir)
}
}
msgBox("One of the plans 런처", "a301:// 프로토콜이 제거되었습니다.", mbOK|mbInfo)
case arg == "--version" || arg == "version":
msgBox("One of the plans 런처", fmt.Sprintf("버전: %s", version), mbOK|mbInfo)
case strings.HasPrefix(arg, protocolName+"://"):
if err := handleURI(arg); err != nil {
msgBox("One of the plans 런처 - 오류", fmt.Sprintf("실행 실패:\n%v", err), mbOK|mbError)
os.Exit(1)
}
default:
msgBox("One of the plans 런처 - 오류", fmt.Sprintf("알 수 없는 명령: %s", arg), mbOK|mbError)
os.Exit(1)
}
}