- main.go를 main()만 남기고 함수 분리 (game.go, protocol.go, ui.go) - 재시도 로직을 retryWithBackoff 공통 함수로 통합 - redeemTicketFrom 별도 HTTP 클라이언트 → apiClient 사용으로 통일 - doDownload에서 resumeOffset 이중 계산 제거 - extractZip에서 stripTopDir/extractFile 함수 분리 - downloadWithProgress에서 createProgressWindow 함수 분리 - DLL 선언을 DLL별로 그룹화, 상수를 역할별로 분리 - 전체 주석 한국어 통일 및 섹션 구분 추가 버그 수정: - ensureLauncher가 설치 경로 대신 실행 중인 경로를 해시하던 문제 수정 - uninstall 시 실행 중인 exe 삭제 실패 → 백그라운드 cmd로 대체 - moveContents에서 os.Remove 에러를 무시하던 문제 수정 - install/uninstall 메시지 통일, exitWithError 헬퍼 추가 - .gitignore에 *.exe 통일, ANALYSIS.md 삭제 - 빌드 명령에 git 태그 기반 버전 주입 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// handleURI 웹에서 a301://... 링크를 통해 호출되는 핵심 게임 실행 흐름.
|
|
func handleURI(rawURI string) error {
|
|
// 1. URI에서 티켓 추출 → JWT 교환
|
|
token, err := authenticateFromURI(rawURI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 2. 게임 디렉토리 준비 + 프로토콜 갱신 + 잔여 파일 정리
|
|
gameDir, gamePath, err := prepareGameDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 3. 서버 정보 조회 (실패 시 오프라인 실행 시도)
|
|
serverInfo, err := fetchServerInfo()
|
|
if err != nil {
|
|
return tryOfflineLaunch(gamePath, gameDir, token, err)
|
|
}
|
|
|
|
// 4. 런처 자동 업데이트 (실패해도 게임 실행은 계속)
|
|
if restartNeeded := tryLauncherUpdate(serverInfo); restartNeeded {
|
|
return nil
|
|
}
|
|
|
|
// 5. 게임 다운로드/검증
|
|
if err := ensureGame(gameDir, gamePath, serverInfo); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 6. 게임 실행
|
|
return launchGame(gamePath, gameDir, token)
|
|
}
|
|
|
|
// authenticateFromURI URI를 파싱하고 일회용 티켓을 JWT로 교환한다.
|
|
func authenticateFromURI(rawURI string) (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)
|
|
}
|
|
|
|
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("서버에서 유효하지 않은 토큰을 받았습니다")
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
// prepareGameDir 게임 디렉토리를 생성하고 프로토콜 등록을 갱신한다.
|
|
func prepareGameDir() (gameDir, gamePath string, err error) {
|
|
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)
|
|
|
|
return gameDir, gamePath, nil
|
|
}
|
|
|
|
// tryOfflineLaunch 서버 연결 실패 시 설치된 게임을 직접 실행한다.
|
|
func tryOfflineLaunch(gamePath, gameDir, token string, serverErr error) error {
|
|
if _, err := os.Stat(gamePath); err != nil {
|
|
return fmt.Errorf("버전 확인 실패: %w", serverErr)
|
|
}
|
|
|
|
ret := msgBox("One of the plans",
|
|
"서버에 연결할 수 없습니다.\n설치된 게임을 실행하시겠습니까?\n(업데이트 확인 불가)",
|
|
mbYesNo|mbQ)
|
|
if ret != idYes {
|
|
return fmt.Errorf("사용자가 취소했습니다")
|
|
}
|
|
|
|
return launchGame(gamePath, gameDir, token)
|
|
}
|
|
|
|
// tryLauncherUpdate 런처 업데이트를 확인하고 필요 시 새 런처로 재시작한다.
|
|
// 재시작이 필요하면 true를 반환한다.
|
|
func tryLauncherUpdate(serverInfo *downloadInfo) bool {
|
|
updated, err := ensureLauncher(serverInfo)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if !updated {
|
|
return false
|
|
}
|
|
|
|
launcherDir, err := installDir()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
cmd := exec.Command(filepath.Join(launcherDir, "launcher.exe"), os.Args[1:]...)
|
|
if err := cmd.Start(); err != nil {
|
|
return false
|
|
}
|
|
os.Exit(0)
|
|
return true // unreachable, os.Exit 위에서 종료
|
|
}
|
|
|
|
// launchGame 게임 프로세스를 시작한다.
|
|
func launchGame(gamePath, gameDir, token string) error {
|
|
cmd := exec.Command(gamePath, "-token", token)
|
|
cmd.Dir = gameDir
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("게임 실행 실패: %w", err)
|
|
}
|
|
return nil
|
|
}
|