// This information is in the file config/shortcuts.vdf, in binary format.
// It contains the non-Steam games with names, target (exe location) and
// tags/categories. To create a grid image we must compute the Steam ID, which
// is just crc32(target + label) + "02000000", using IEEE standard polynomials.
func addNonSteamGames(user User, games map[string]*Game) {
shortcutsVdf := filepath.Join(user.Dir, "config", "shortcuts.vdf")
if _, err :=
os.Stat(shortcutsVdf); err != nil {
return
}
shortcutBytes, err := ioutil.ReadFile(shortcutsVdf)
if err != nil {
return
}
// The actual binary format is known, but using regexes is way easier than
// parsing the entire file. If I run into any problems I'll replace this.
gamePattern := regexp.MustCompile("(?i)appname\x00(.+?)\x00\x01exe\x00(.+?)\x00\x01.+?\x00tags\x00(.*?)\x08\x08")
tagsPattern := regexp.MustCompile("\\d\x00(.+?)\x00")
for _, gameGroups := range gamePattern.FindAllSubmatch(shortcutBytes, -1) {
gameName := gameGroups[1]
target := gameGroups[2]
uniqueName := bytes.Join([][]byte{target, gameName}, []byte(""))
// Does IEEE CRC32 of target concatenated with gameName, then convert
// to 64bit Steam ID. No idea why Steam chose this operation.
top := uint64(crc32.ChecksumIEEE(uniqueName)) | 0x80000000
gameId := strconv.FormatUint(top<<32|0x02000000, 10)
game := Game{gameId, string(gameName), []string{}, "", nil}
games[gameId] = &game
tagsText := gameGroups[3]
for _, tagGroups := range tagsPattern.FindAllSubmatch(tagsText, -1) {
tag := tagGroups[1]
game.Tags = append(game.Tags, string(tag))
}
}
}