feat(cafegame): 自 go-hua 迁入网咖游戏识别与运行记录框架
- 游戏来源常量/按目录特征识别(云更新/网维大师/易乐游)/RunAppRecord 上报框架 - 网吧业务专用包, 附测试、examples/cafegame 例程与包 README
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
//go:build windows
|
||||
|
||||
package cafegame
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsGameDirByYGX 云更新 id从40000开始
|
||||
func IsGameDirByYGX(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
gamePatch := filepath.Join(dir, "gamepatch")
|
||||
ok, err := pathExists(gamePatch)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
gconfig := filepath.Join(gamePatch, "gconfig.ini")
|
||||
ok, err = pathExists(gconfig)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdByNumberIco(dir)
|
||||
if id < 40000 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceYGX
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByStory 蘑菇 id从8000开始
|
||||
func IsGameDirByStory(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
gamePatch := filepath.Join(dir, "gamepatch")
|
||||
ok, err := pathExists(gamePatch)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
gconfig := filepath.Join(gamePatch, "gconfig.ini")
|
||||
ok, err = pathExists(gconfig)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdByNumberIco(dir)
|
||||
if id < 10000 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceStory
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByWWDS 网维大师 ID 从 10001 开始
|
||||
func IsGameDirByWWDS(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
SunwardPkg := filepath.Join(dir, "SunwardPkg.idx")
|
||||
ok, err := pathExists(SunwardPkg)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
SunwardPkgCks := filepath.Join(dir, "SunwardPkg.cks")
|
||||
ok, err = pathExists(SunwardPkgCks)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
fmt.Printf("没有cks文件 dir:%s\n", dir)
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
// 不能有这文件 这是盖伦文件
|
||||
GameIndex := filepath.Join(dir, "GameIndex.txt")
|
||||
ok, err = pathExists(GameIndex)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
// 不能有这文件,这是易乐游文件
|
||||
GameInfodb := filepath.Join(dir, "GameInfodb.gz")
|
||||
ok, err = pathExists(GameInfodb)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
// 不能有这文件 这是好司机文件
|
||||
hsjGame := filepath.Join(dir, "hsjGame.exe")
|
||||
ok, err = pathExists(hsjGame)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdBySunwardPkg(dir)
|
||||
if id < 10000 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceWWDS
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByGoodGame 盖伦 id应该是从 1001开始 steam是第一个游戏 1011
|
||||
func IsGameDirByGoodGame(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
GameIndex := filepath.Join(dir, "GameIndex.txt")
|
||||
ok, err := pathExists(GameIndex)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
GameCksum := filepath.Join(dir, "GameCksum.txt")
|
||||
ok, err = pathExists(GameCksum)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdBySunwardPkg(dir)
|
||||
if id < 1000 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceGoodGame
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByYLY 易乐游
|
||||
func IsGameDirByYLY(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
GameInfodb := filepath.Join(dir, "GameInfodb.gz")
|
||||
ok, err := pathExists(GameInfodb)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
Gamever := filepath.Join(dir, "Gamever.ver")
|
||||
ok, err = pathExists(Gamever)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
// TODO: 没有游戏ID,只有GUID
|
||||
guid, _ := GetGuidByYLY(dir)
|
||||
if guid == "{00000000-0000-0000-0000-000000000000}" {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = 0
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceYLY
|
||||
info.AppGuid = guid
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByHaoSiJi 好司机
|
||||
func IsGameDirByHaoSiJi(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
hsjGame := filepath.Join(dir, "hsjGame.exe")
|
||||
ok, err := pathExists(hsjGame)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
hsjLoader := filepath.Join(dir, "hsjLoader.exe")
|
||||
ok, err = pathExists(hsjLoader)
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
|
||||
SunwardPkg := filepath.Join(dir, "SunwardPkg.idx")
|
||||
ok, err = pathExists(SunwardPkg)
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
|
||||
id, _ := getIdBySunwardPkg(dir)
|
||||
if id == 0 {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
guid, _ := GetGuidByYLY(dir)
|
||||
if guid == "{00000000-0000-0000-0000-000000000000}" {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceHaoSiJi
|
||||
info.AppGuid = guid
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByMTGX 马蹄更新 id从10000开始
|
||||
func IsGameDirByMTGX(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
patchdata := filepath.Join(dir, "patchdata")
|
||||
ok, err := pathExists(patchdata)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
config := filepath.Join(patchdata, "config.ini")
|
||||
ok, err = pathExists(config)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
ndstorrent := filepath.Join(dir, "ndstorrent.idx")
|
||||
ok, err = pathExists(ndstorrent)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdByNumberIco(dir)
|
||||
if id < 10000 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceMTGX
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByFast 火箭 id从8000开始
|
||||
func IsGameDirByFast(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
patchdata := filepath.Join(dir, "patchdata")
|
||||
ok, err := pathExists(patchdata)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
rkttorrent := filepath.Join(dir, "rkttorrent.idx")
|
||||
ok, err = pathExists(rkttorrent)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
gconfig := filepath.Join(patchdata, "config.ini")
|
||||
ok, err = pathExists(gconfig)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdByNumberIco(dir)
|
||||
if id < 8000 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceFast
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// IsGameDirByWfGame WF游戏
|
||||
func IsGameDirByWfGame(dir string) (bool, AppData, error) {
|
||||
info := AppData{}
|
||||
ndsgameres := filepath.Join(dir, "ndsgameres.index")
|
||||
ok, err := pathExists(ndsgameres)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
dd_launcher := filepath.Join(dir, "dd_launcher.json")
|
||||
ok, err = pathExists(dd_launcher)
|
||||
if err != nil {
|
||||
return false, info, err
|
||||
}
|
||||
if !ok {
|
||||
return false, info, nil
|
||||
}
|
||||
|
||||
id, _ := getIdByDdLauncher(dir)
|
||||
if id == 0 {
|
||||
return false, info, nil
|
||||
}
|
||||
info.AppId = id
|
||||
info.AppName = filepath.Base(dir)
|
||||
info.AppDir = dir
|
||||
info.AppSource = SourceWfGame
|
||||
|
||||
return true, info, nil
|
||||
}
|
||||
|
||||
// 网维大师 盖伦 好司机
|
||||
func getIdBySunwardPkg(dir string) (int, error) {
|
||||
path := filepath.Join(dir, "SunwardPkg.idx")
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read a 4-byte integer
|
||||
var num int32
|
||||
err = binary.Read(file, binary.BigEndian, &num)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading integer:", err)
|
||||
return 0, err
|
||||
}
|
||||
// fmt.Println("Read integer:", num)
|
||||
return int(num), nil
|
||||
}
|
||||
|
||||
// 云更新 蘑菇 火箭使用
|
||||
func getIdByNumberIco(dir string) (int, error) {
|
||||
// 获该目录下所有文件
|
||||
fileList, _ := getFileList(dir)
|
||||
for _, s := range fileList {
|
||||
ext := filepath.Ext(s)
|
||||
// 判断是否ico图标
|
||||
if strings.ToLower(ext) == ".ico" {
|
||||
// fmt.Printf("ico: %s\n", s)
|
||||
// 通过ico图标的数字确定游戏ID
|
||||
fileName := strings.TrimSuffix(filepath.Base(s), filepath.Ext(s))
|
||||
if IsAllNumberHandle(fileName) {
|
||||
gameId, _ := strconv.Atoi(fileName)
|
||||
if gameId >= 8000 {
|
||||
return gameId, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// DdLauncherJson DDL Launcher 平台游戏信息(JSON 配置)
|
||||
type DdLauncherJson struct {
|
||||
Gameid int `json:"gameid"`
|
||||
}
|
||||
|
||||
// getIdByDdLauncher 从 DDL Launcher 配置文件中读取游戏 ID
|
||||
func getIdByDdLauncher(dir string) (int, error) {
|
||||
path := filepath.Join(dir, "dd_launcher.json")
|
||||
|
||||
ok, err := pathExists(path)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ok {
|
||||
return 0, errors.New("文件不存在")
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data := DdLauncherJson{}
|
||||
err = json.Unmarshal(buf, &data)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if data.Gameid == 0 {
|
||||
return 0, errors.New("游戏ID为空")
|
||||
}
|
||||
|
||||
return data.Gameid, nil
|
||||
}
|
||||
|
||||
// GetGuidByYLY 从易乐游目录配置中读取游戏 GUID
|
||||
func GetGuidByYLY(dir string) (string, error) {
|
||||
path := filepath.Join(dir, "$ResMetadata$", "ResAppData.dat")
|
||||
|
||||
ok, err := pathExists(path)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ok {
|
||||
return "", errors.New("文件不存在")
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
// 设置读取起始位置为第25个字节
|
||||
_, err = file.Seek(24, 0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
}
|
||||
// 创建一个字节切片来存储GUID
|
||||
buf := make([]byte, 16)
|
||||
// 读取GUID到字节切片中
|
||||
_, err = file.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", err
|
||||
}
|
||||
guidInt32 := binary.LittleEndian.Uint32(buf[:4])
|
||||
guidInt16_1 := binary.LittleEndian.Uint16(buf[4:6])
|
||||
guidInt16_2 := binary.LittleEndian.Uint16(buf[6:8])
|
||||
guidBytes_1 := buf[8:10]
|
||||
guidBytes_2 := buf[10:]
|
||||
guid := fmt.Sprintf("{%08x-%04x-%04x-%s-%s}", guidInt32, guidInt16_1, guidInt16_2, hex.EncodeToString(guidBytes_1), hex.EncodeToString(guidBytes_2))
|
||||
guid = strings.ToTitle(guid)
|
||||
return guid, err
|
||||
}
|
||||
|
||||
// GetGameInfoByDir 获取游戏信息 从目录获取
|
||||
func GetGameInfoByDir(root string) (AppData, error) {
|
||||
// fmt.Println(root)
|
||||
data := AppData{}
|
||||
|
||||
// 云更新
|
||||
ok, data, err := IsGameDirByYGX(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 网维大师
|
||||
ok, data, err = IsGameDirByWWDS(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 易乐游
|
||||
ok, data, err = IsGameDirByYLY(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 马蹄更新
|
||||
ok, data, err = IsGameDirByMTGX(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 蘑菇
|
||||
ok, data, err = IsGameDirByStory(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 盖伦
|
||||
ok, data, err = IsGameDirByGoodGame(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 好司机
|
||||
ok, data, err = IsGameDirByHaoSiJi(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 火箭
|
||||
ok, data, err = IsGameDirByFast(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
// WF游戏
|
||||
ok, data, err = IsGameDirByWfGame(root)
|
||||
if ok {
|
||||
return data, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
||||
}
|
||||
|
||||
// GetGameInfoByPath 获取游戏信息 从路径获取
|
||||
func GetGameInfoByPath(exePath string) (AppData, error) {
|
||||
|
||||
path := filepath.ToSlash(exePath)
|
||||
pathArr := strings.Split(path, "/")
|
||||
// 3个/ 比如 D:/网络游戏/英雄联盟/xxx.exe
|
||||
|
||||
// 长度3 是 D:/英雄联盟/xxx.exe
|
||||
if len(pathArr) < 3 {
|
||||
return AppData{}, errors.New("不支持1级目录")
|
||||
}
|
||||
|
||||
dir := ""
|
||||
// 连接二级目录
|
||||
for i, s := range pathArr {
|
||||
|
||||
// 最后是文件名,不检测
|
||||
if i >= len(pathArr)-1 {
|
||||
break
|
||||
}
|
||||
// 最多支持3级目录 如 D:/网络游戏/腾讯游戏/英雄联盟/xxx.exe
|
||||
if i > 4 {
|
||||
break
|
||||
}
|
||||
if i == 0 {
|
||||
dir = s
|
||||
} else {
|
||||
dir = filepath.Join(dir, string(filepath.Separator), s)
|
||||
}
|
||||
|
||||
// 获取游戏信息
|
||||
info, err := GetGameInfoByDir(dir)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if info.AppId > 0 || info.AppGuid != "" {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return AppData{}, nil
|
||||
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
func getFileList(dir string) ([]string, error) {
|
||||
list := make([]string, 0)
|
||||
//fmt.Println(dir)
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
files, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && file.Name() != "$RECYCLE.BIN" && file.Name() != "System Volume Information" {
|
||||
list = append(list, filepath.Join(dir, file.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// IsAllNumberHandle /** 是否全部是数字 */
|
||||
func IsAllNumberHandle(str string) bool {
|
||||
// 空串不认为是数字
|
||||
if str == "" {
|
||||
return false
|
||||
}
|
||||
anyReplaceList := []int32{'.', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
|
||||
for strIndex, strInt := range str {
|
||||
if strIndex == 0 {
|
||||
if strInt == '-' {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if IsHaveReplaceList(strInt, anyReplaceList) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsHaveReplaceList 是否在过滤列表中
|
||||
func IsHaveReplaceList(strInt int32, replaceList []int32) bool {
|
||||
for _, chStrInt := range replaceList {
|
||||
if chStrInt == strInt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pathExists 判断一个文件或文件夹是否存在
|
||||
// 输入文件路径,根据返回的bool值来判断文件或文件夹是否存在
|
||||
func pathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
Reference in New Issue
Block a user