Панель видит моды Build 42 и названия больших сборок
В Build 42 mod.info лежит не в корне мода, а в папке версии: common, 42, 42.16. Сканер смотрел только в корень, и такие моды выглядели нескачанными с неизвестным Mod ID — включить их через панель было нельзя. На реальной мастерской находилось 237 модов из 399. Названия из мастерской фронтенд запрашивал одним списком, а эндпоинт не принимает больше двухсот идентификаторов за раз. У сборки на 284 пакета запрос падал целиком, ошибка молча проглатывалась, и вместо названий оставались одни номера. Теперь запрос идёт партиями, а полная неудача больше не остаётся незамеченной. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,7 +91,11 @@ func readModsDir(dir, workshopID string) []InstalledMod {
|
||||
continue
|
||||
}
|
||||
modDir := filepath.Join(dir, entry.Name())
|
||||
mod, ok := readModInfo(filepath.Join(modDir, "mod.info"))
|
||||
info, found := modInfoPath(modDir)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
mod, ok := readModInfo(info)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -105,6 +109,55 @@ func readModsDir(dir, workshopID string) []InstalledMod {
|
||||
return out
|
||||
}
|
||||
|
||||
// modInfoPath ищет mod.info внутри папки мода.
|
||||
//
|
||||
// В Build 41 он лежал в корне, а в Build 42 мод раскладывают по папкам версий:
|
||||
// common — общая часть, 42 и 42.16 — под конкретные сборки игры. Если искать
|
||||
// только в корне, мод из мастерской выглядит нескачанным, его Mod ID остаётся
|
||||
// неизвестным и включить его через панель нельзя.
|
||||
func modInfoPath(modDir string) (string, bool) {
|
||||
if root := filepath.Join(modDir, "mod.info"); fileExists(root) {
|
||||
return root, true
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(modDir)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
var versions []string
|
||||
common := ""
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !fileExists(filepath.Join(modDir, entry.Name(), "mod.info")) {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(entry.Name(), "common") {
|
||||
common = entry.Name()
|
||||
continue
|
||||
}
|
||||
versions = append(versions, entry.Name())
|
||||
}
|
||||
|
||||
// Сначала самая свежая версия: «42.16» важнее «42», а «common» —
|
||||
// последняя, это общая часть на случай, когда версии своего mod.info не
|
||||
// завели. Обратный порядок строк для номеров версий работает и здесь.
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(versions)))
|
||||
if common != "" {
|
||||
versions = append(versions, common)
|
||||
}
|
||||
if len(versions) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return filepath.Join(modDir, versions[0], "mod.info"), true
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
// readModInfo разбирает mod.info — файл вида "ключ=значение" с необязательными
|
||||
// комментариями. Нас интересуют id, name и description.
|
||||
func readModInfo(path string) (InstalledMod, bool) {
|
||||
|
||||
@@ -75,3 +75,63 @@ func writeModInfo(t *testing.T, dir, content string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// В Build 42 mod.info лежит не в корне мода, а в папке версии. Мод из такой
|
||||
// раскладки должен находиться, иначе панель считает его нескачанным.
|
||||
func TestScanInstalledModsBuild42Layout(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
content := filepath.Join(root, "server", "steamapps", "workshop", "content", pzAppID)
|
||||
|
||||
writeModInfo(t, filepath.Join(content, "111", "mods", "Wipes", "common"),
|
||||
"name=Cleaning Wipes\nid=dustinguished_cleaning_wipes\n")
|
||||
// Мод с несколькими версиями: берём самую свежую, а не common.
|
||||
mod := filepath.Join(content, "222", "mods", "NewMusicG90s")
|
||||
writeModInfo(t, filepath.Join(mod, "common"), "name=Mixtape (common)\nid=NewMusicG90s\n")
|
||||
writeModInfo(t, filepath.Join(mod, "42"), "name=Mixtape (42)\nid=NewMusicG90s\n")
|
||||
writeModInfo(t, filepath.Join(mod, "42.16"), "name=Mixtape (42.16)\nid=NewMusicG90s\n")
|
||||
|
||||
mods := ScanInstalledMods([]string{filepath.Join(root, "server")}, "")
|
||||
|
||||
byID := make(map[string]InstalledMod, len(mods))
|
||||
for _, m := range mods {
|
||||
byID[m.ModID] = m
|
||||
}
|
||||
if len(byID) != 2 {
|
||||
t.Fatalf("найдено %d модов, ожидалось 2: %v", len(byID), byID)
|
||||
}
|
||||
if got := byID["dustinguished_cleaning_wipes"]; got.Name != "Cleaning Wipes" || got.WorkshopID != "111" {
|
||||
t.Errorf("мод из common разобран неверно: %+v", got)
|
||||
}
|
||||
if got := byID["NewMusicG90s"]; got.Name != "Mixtape (42.16)" {
|
||||
t.Errorf("взята не самая свежая версия: %+v", got)
|
||||
}
|
||||
// Dir указывает на папку мода, а не на папку версии: так его удобнее найти.
|
||||
if got := byID["NewMusicG90s"]; filepath.Base(got.Dir) != "NewMusicG90s" {
|
||||
t.Errorf("Dir указывает не на папку мода: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Корневой mod.info главнее версий: так раскладывают моды Build 41.
|
||||
func TestModInfoPathPrefersRoot(t *testing.T) {
|
||||
mod := t.TempDir()
|
||||
writeModInfo(t, mod, "name=Root\nid=Root\n")
|
||||
writeModInfo(t, filepath.Join(mod, "42"), "name=Version\nid=Version\n")
|
||||
|
||||
path, ok := modInfoPath(mod)
|
||||
if !ok {
|
||||
t.Fatal("mod.info не найден")
|
||||
}
|
||||
if filepath.Dir(path) != mod {
|
||||
t.Errorf("взят mod.info из папки версии: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModInfoPathWithoutModInfo(t *testing.T) {
|
||||
mod := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(mod, "media"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := modInfoPath(mod); ok {
|
||||
t.Error("mod.info найден там, где его нет")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user