환경변수(A301_TOKEN)로 토큰을 전달했지만 게임은 커맨드라인 인자(-token)를 읽도록 되어있어 토큰이 전달되지 않던 문제. exec.Command에 -token 인자를 추가하여 게임의 AuthState가 토큰을 받도록 수정. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
154 lines
4.5 KiB
Go
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)
|
|
}
|
|
}
|