feat: 게임 실행 전 서버 해시 검증으로 버전 강제 차단
- 서버 /api/download/info에서 최신 fileHash 조회 - 로컬 A301.exe SHA256과 비교 - 불일치 시 게임 실행 차단 + 다운로드 페이지 열기 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
74
main.go
74
main.go
@@ -1,7 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -16,11 +21,14 @@ import (
|
||||
const (
|
||||
protocolName = "a301"
|
||||
gameExeName = "A301.exe"
|
||||
serverInfoURL = "https://a301.tolelom.xyz/api/download/info"
|
||||
webDownloadURL = "https://a301.tolelom.xyz"
|
||||
)
|
||||
|
||||
var (
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
messageBoxW = user32.NewProc("MessageBoxW")
|
||||
shellExecuteW = windows.NewLazySystemDLL("shell32.dll").NewProc("ShellExecuteW")
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,9 +52,14 @@ func msgBox(title, text string, flags uintptr) int {
|
||||
return int(ret)
|
||||
}
|
||||
|
||||
func openBrowser(rawURL string) {
|
||||
u, _ := windows.UTF16PtrFromString(rawURL)
|
||||
op, _ := windows.UTF16PtrFromString("open")
|
||||
shellExecuteW.Call(0, uintptr(unsafe.Pointer(op)), uintptr(unsafe.Pointer(u)), 0, 0, 1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
// 더블클릭 실행 시 install 안내 후 자동 설치
|
||||
ret := msgBox(
|
||||
"A301 런처",
|
||||
"게임 실행을 위해 프로토콜을 등록합니다.\n계속하시겠습니까?",
|
||||
@@ -153,6 +166,39 @@ func uninstall() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type downloadInfo struct {
|
||||
FileHash string `json:"fileHash"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func fetchServerInfo() (*downloadInfo, error) {
|
||||
resp, err := http.Get(serverInfoURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("서버 연결 실패: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var info downloadInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
||||
return nil, fmt.Errorf("서버 응답 파싱 실패: %w", err)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func hashFile(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func handleURI(rawURI string) error {
|
||||
parsed, err := url.Parse(rawURI)
|
||||
if err != nil {
|
||||
@@ -175,6 +221,32 @@ func handleURI(rawURI string) error {
|
||||
return fmt.Errorf("게임 파일을 찾을 수 없습니다:\n%s", gamePath)
|
||||
}
|
||||
|
||||
// 서버에서 최신 해시 조회
|
||||
serverInfo, err := fetchServerInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("버전 확인 실패:\n%w", err)
|
||||
}
|
||||
|
||||
// 서버에 해시가 등록된 경우에만 검증
|
||||
if serverInfo.FileHash != "" {
|
||||
localHash, err := hashFile(gamePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("게임 파일 검증 실패:\n%w", err)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(localHash, serverInfo.FileHash) {
|
||||
ret := msgBox(
|
||||
"A301 - 업데이트 필요",
|
||||
"새로운 버전의 게임이 있습니다.\n최신 버전을 다운로드해주세요.\n\n확인을 누르면 다운로드 페이지로 이동합니다.",
|
||||
mbOK|mbIconInfo,
|
||||
)
|
||||
if ret > 0 {
|
||||
openBrowser(webDownloadURL)
|
||||
}
|
||||
return fmt.Errorf("버전이 최신이 아닙니다")
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(gamePath, "-token", token)
|
||||
cmd.Dir = gameDir
|
||||
if err := cmd.Start(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user