- 游戏来源常量/按目录特征识别(云更新/网维大师/易乐游)/RunAppRecord 上报框架 - 网吧业务专用包, 附测试、examples/cafegame 例程与包 README
679 lines
14 KiB
Go
679 lines
14 KiB
Go
//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
|
|
}
|