Files
a301_launcher/main.go
tolelom b026520b35
Some checks failed
CI/CD / test (push) Has been cancelled
CI/CD / release (push) Has been cancelled
fix: 런처 안정성 개선 (4건)
- ticket JSON 직렬화 json.Marshal 사용 (특수문자 안전)
- 4xx 에러 메시지 "서버 오류"→"요청 실패" 수정
- 자동 업데이트 실패 시 stderr 로깅 추가
- 서버 URL을 ldflags로 오버라이드 가능하도록 var 전환

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

156 lines
4.6 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
cmd.Env = append(os.Environ(), "A301_TOKEN="+token)
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
cmd.Env = append(os.Environ(), "A301_TOKEN="+token)
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)
}
}