private async Task <ContentEntryBase> CheckDirectoryNode(DirectoryNode directory, CancellationToken cancellation) { if (directory.Parent?.NameLowerCase == "python" && directory.Parent.Parent?.NameLowerCase == "apps" || directory.HasSubFile(directory.Name + ".py")) { var id = directory.Name; if (id == null) { // It’s unlikely there will be a car or a track in apps/python directory return(null); } // App? var root = directory.Parent?.Parent?.Parent; var gui = root?.GetSubDirectory("content")?.GetSubDirectory("gui")?.GetSubDirectory("icons"); // Collecting values… var missing = false; var uiAppFound = false; string version = null, name = null; // Maybe, it’s done nicely? var uiApp = directory.GetSubDirectory("ui")?.GetSubFile("ui_app.json"); if (uiApp != null) { uiAppFound = true; var data = await uiApp.Info.ReadAsync(); if (data == null) { missing = true; } else { var parsed = JsonExtension.Parse(data.ToUtf8String()); name = parsed.GetStringValueOnly("name"); version = parsed.GetStringValueOnly("version"); } } // Let’s try to guess version if (version == null && !uiAppFound) { foreach (var c in PythonAppObject.VersionSources.Select(directory.GetSubFile).NonNull()) { var r = await c.Info.ReadAsync(); if (r == null) { missing = true; } else { version = PythonAppObject.GetVersion(r.ToUtf8String()); if (version != null) { break; } } } } // And icon byte[] icon; List <FileNode> icons; if (gui != null) { icons = gui.Files.Where(x => x.NameLowerCase.EndsWith("_on.png") || x.NameLowerCase.EndsWith("_off.png")).ToList(); var mainIcon = icons.GetByIdOrDefault(directory.NameLowerCase + "_off.png") ?? icons.OrderByDescending(x => x.NameLowerCase.Length).FirstOrDefault(); icon = await(mainIcon?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); if (mainIcon != null && icon == null) { missing = true; } cancellation.ThrowIfCancellationRequested(); } else { icon = null; icons = null; } if (missing) { throw new MissingContentException(); } return(new PythonAppContentEntry(directory.Key ?? "", id, name ?? id, version, icon, icons?.Select(x => x.Key))); } var ui = directory.GetSubDirectory("ui"); if (ui != null) { // Is it a car? var uiCar = ui.GetSubFile("ui_car.json"); if (uiCar != null) { var icon = await(ui.GetSubFile("badge.png")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await uiCar.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); var carId = directory.Name ?? directory.GetSubDirectory("sfx")?.Files.Select(x => x.NameLowerCase) .FirstOrDefault(x => x.EndsWith(@".bank") && x.Count('.') == 1 && x != @"common.bank")?.ApartFromLast(@".bank"); if (carId != null) { return(new CarContentEntry(directory.Key ?? "", carId, parsed.GetStringValueOnly("parent") != null, parsed.GetStringValueOnly("name"), parsed.GetStringValueOnly("version"), icon)); } } // A track? var foundTrack = await CheckDirectoryNodeForTrack(directory, cancellation); if (foundTrack != null) { return(foundTrack); } // Or maybe a showroom? var uiShowroom = ui.GetSubFile(@"ui_showroom.json"); if (uiShowroom != null) { var icon = await(directory.GetSubFile(@"preview.jpg")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await uiShowroom.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); var showroomId = directory.Name ?? directory.Files.Where(x => x.NameLowerCase.EndsWith(@".kn5")).OrderByDescending(x => x.Info.Size) .FirstOrDefault()?.NameLowerCase.ApartFromLast(@".kn5"); if (showroomId != null) { return(new ShowroomContentEntry(directory.Key ?? "", showroomId, parsed.GetStringValueOnly("name"), parsed.GetStringValueOnly("version"), icon)); } } } else { // Another case for showrooms if (directory.Name != null && directory.HasSubFile(directory.Name + @".kn5") && directory.HasSubFile(@"colorCurves.ini") && directory.HasSubFile(@"ppeffects.ini")) { var icon = directory.HasSubFile(@"preview.jpg") ? await(directory.GetSubFile(@"preview.jpg")?.Info.ReadAsync() ?? throw new MissingContentException()) : null; cancellation.ThrowIfCancellationRequested(); return(new ShowroomContentEntry(directory.Key ?? "", directory.Name ?? throw new ArgumentException(), iconData: icon)); } } var uiTrackSkin = directory.GetSubFile("ui_track_skin.json"); if (uiTrackSkin != null && TracksManager.Instance != null) { var icon = await(directory.GetSubFile("preview.png")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await uiTrackSkin.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); var skinId = parsed.GetStringValueOnly("id") ?? directory.Name; var trackId = parsed.GetStringValueOnly("track"); var name = parsed.GetStringValueOnly("name"); if (skinId != null && trackId != null) { return(new TrackSkinContentEntry(directory.Key ?? "", skinId, trackId, name, parsed.GetStringValueOnly("version"), icon)); } } if (directory.HasSubFile("settings.ini")) { var kn5 = directory.Files.Where(x => x.NameLowerCase.EndsWith(@".kn5")).ToList(); var id = directory.Name; if (id != null) { if (kn5.Any(x => x.NameLowerCase.ApartFromLast(@".kn5") == directory.NameLowerCase)) { var icon = await(directory.GetSubFile("preview.jpg")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); return(new ShowroomContentEntry(directory.Key ?? "", id, AcStringValues.NameFromId(id), null, icon)); } } } var weatherIni = directory.GetSubFile("weather.ini"); if (weatherIni != null) { var icon = await(directory.GetSubFile("preview.jpg")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await weatherIni.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = IniFile.Parse(data.ToUtf8String()); var name = parsed["LAUNCHER"].GetNonEmpty("NAME"); if (name != null) { var id = directory.Name ?? name; return(new WeatherContentEntry(directory.Key ?? "", id, name, icon)); } } var uiCarSkin = directory.GetSubFile("ui_skin.json"); if ((uiCarSkin != null || directory.HasSubFile("preview.jpg") && directory.HasSubFile("livery.png")) && CarsManager.Instance != null /* for crawlers only */) { var icon = await(directory.GetSubFile("livery.png")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); string carId; var skinFor = await(directory.GetSubFile("cm_skin_for.json")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); if (skinFor != null) { carId = JsonExtension.Parse(skinFor.ToUtf8String())[@"id"]?.ToString(); } else { carId = _installationParams.CarId; if (carId == null && directory.Parent?.NameLowerCase == "skins") { carId = directory.Parent.Parent?.Name; } if (carId == null) { carId = AcContext.Instance.CurrentCar?.Id; } } if (carId == null) { throw new Exception("Can’t figure out car’s ID"); } var skinId = directory.Name; if (skinId != null) { string name; if (uiCarSkin != null) { var data = await uiCarSkin.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); name = parsed.GetStringValueOnly("name"); } else { name = AcStringValues.NameFromId(skinId); } return(new CarSkinContentEntry(directory.Key ?? "", skinId, carId, name, icon)); } } // New textures if (directory.NameLowerCase == "damage" && directory.HasSubFile("flatspot_fl.png")) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? @"damage")); } if (directory.Parent?.NameLowerCase == "crew_brand" && directory.HasSubFile("Brands_Crew.dds") && directory.HasSubFile("Brands_Crew.jpg") && directory.HasSubFile("Brands_Crew_NM.dds")) { return(new CrewBrandEntry(directory.Key ?? "", directory.Name ?? @"unknown")); } if (directory.Parent?.NameLowerCase == "crew_helmet" && directory.HasSubFile("Crew_HELMET_Color.dds")) { return(new CrewHelmetEntry(directory.Key ?? "", directory.Name ?? @"unknown")); } // TODO: More driver and crew textures if (directory.NameLowerCase == "clouds" && directory.Files.Any( x => (x.NameLowerCase.StartsWith(@"cloud") || directory.Parent?.NameLowerCase == "texture") && x.NameLowerCase.EndsWith(@".dds"))) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? @"clouds")); } if (directory.NameLowerCase == "clouds_shared" && directory.Files.Any( x => (x.NameLowerCase.StartsWith(@"cloud") || directory.Parent?.NameLowerCase == "texture") && x.NameLowerCase.EndsWith(@".dds"))) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? @"clouds_shared")); } if (directory.NameLowerCase == "people" && (directory.HasSubFile("crowd_sit.dds") || directory.HasSubFile("people_sit.dds"))) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? @"people")); } if (directory.HasSubFile(PatchHelper.MainFileName) && directory.HasSubDirectory("extension")) { var dwrite = directory.GetSubFile(PatchHelper.MainFileName); var extension = directory.GetSubDirectory("extension"); var manifest = directory.GetSubDirectory("extension")?.GetSubDirectory("config")?.GetSubFile("data_manifest.ini"); string version; if (manifest != null) { var data = await manifest.Info.ReadAsync() ?? throw new MissingContentException(); version = IniFile.Parse(data.ToUtf8String())["VERSION"].GetNonEmpty("SHADERS_PATCH"); } else { var description = directory.GetSubFile("description.jsgme"); if (description != null) { var data = await description.Info.ReadAsync() ?? throw new MissingContentException(); version = Regex.Match(data.ToUtf8String(), @"(?<=v)\d.*").Value?.TrimEnd('.').Or(null); } else { var parent = directory; while (parent.Parent?.Name != null) { parent = parent.Parent; } version = parent.Name != null?Regex.Match(parent.Name, @"(?<=v)\d.*").Value?.TrimEnd('.').Or(null) : null; } } return(new ShadersPatchEntry(directory.Key ?? "", new[] { dwrite.Key, extension.Key }, version)); } if (directory.NameLowerCase == "__gbwsuite") { return(new CustomFolderEntry(directory.Key ?? "", new[] { directory.Key }, "GBW scripts", "__gbwSuite")); } if (directory.HasSubFile("weather.lua") && directory.Parent.NameLowerCase == "weather") { return(new CustomFolderEntry(directory.Key ?? "", new[] { directory.Key }, $"Weather FX script “{AcStringValues.NameFromId(directory.Name)}”", Path.Combine(AcRootDirectory.Instance.RequireValue, "extension", "weather", directory.Name), 1e5)); } if (directory.HasSubFile("controller.lua") && directory.Parent.NameLowerCase == "weather-controllers") { return(new CustomFolderEntry(directory.Key ?? "", new[] { directory.Key }, $"Weather FX controller “{AcStringValues.NameFromId(directory.Name)}”", Path.Combine(AcRootDirectory.Instance.RequireValue, "extension", "weather-controllers", directory.Name), 1e5)); } // Mod if (directory.Parent?.NameLowerCase == "mods" && (directory.HasAnySubDirectory("content", "apps", "system", "launcher", "extension") || directory.HasSubFile(PatchHelper.MainFileName))) { var name = directory.Name; if (name != null && directory.GetSubDirectory("content")?.GetSubDirectory("tracks")?.Directories.Any( x => x.GetSubDirectory("skins")?.GetSubDirectory("default")?.GetSubFile("ui_track_skin.json") != null) != true) { var description = directory.Files.FirstOrDefault(x => x.NameLowerCase.EndsWith(@".jsgme")); if (description == null && directory.HasSubDirectory("documentation")) { description = directory.GetSubDirectory("documentation")?.Files.FirstOrDefault(x => x.NameLowerCase.EndsWith(@".jsgme")); } if (description != null) { var data = await description.Info.ReadAsync() ?? throw new MissingContentException(); return(new GenericModConfigEntry(directory.Key ?? "", name, data.ToUtf8String())); } return(new GenericModConfigEntry(directory.Key ?? "", name)); } } return(null); }
private async Task <ContentEntryBase> CheckDirectoryNode(DirectoryNode directory, CancellationToken cancellation) { if (directory.Parent?.NameLowerCase == "python" && directory.Parent.Parent?.NameLowerCase == "apps" || directory.HasSubFile(directory.Name + ".py")) { var id = directory.Name; if (id == null) { // It’s unlikely there will be a car or a track in apps/python directory return(null); } // App? var root = directory.Parent?.Parent?.Parent; var gui = root?.GetSubDirectory("content")?.GetSubDirectory("gui")?.GetSubDirectory("icons"); // Let’s try to guess version var missing = false; string version = null; foreach (var c in PythonAppObject.VersionSources.Select(directory.GetSubFile).NonNull()) { var r = await c.Info.ReadAsync(); if (r == null) { missing = true; } else { version = PythonAppObject.GetVersion(r.ToUtf8String()); if (version != null) { break; } } } if (missing) { throw new MissingContentException(); } // And icon byte[] icon; List <FileNode> icons; if (gui != null) { icons = gui.Files.Where(x => x.NameLowerCase.EndsWith("_on.png") || x.NameLowerCase.EndsWith("_off.png")).ToList(); var mainIcon = icons.GetByIdOrDefault(directory.NameLowerCase + "_on.png") ?? icons.OrderByDescending(x => x.NameLowerCase.Length).FirstOrDefault(); icon = await(mainIcon?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); } else { icon = null; icons = null; } return(new PythonAppContentEntry(directory.Key ?? "", id, id, version, icon, icons?.Select(x => x.Key))); } var ui = directory.GetSubDirectory("ui"); if (ui != null) { // Is it a car? var uiCar = ui.GetSubFile("ui_car.json"); if (uiCar != null) { var icon = await(ui.GetSubFile("badge.png")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await uiCar.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); var carId = directory.Name ?? directory.GetSubDirectory("sfx")?.Files.Select(x => x.NameLowerCase) .FirstOrDefault(x => x.EndsWith(".bank") && x.Count('.') == 1 && x != "common.bank")?.ApartFromLast(".bank"); if (carId != null) { return(new CarContentEntry(directory.Key ?? "", carId, parsed.GetStringValueOnly("name"), parsed.GetStringValueOnly("version"), icon)); } } // A track? var foundTrack = await CheckDirectoryNodeForTrack(directory, cancellation); if (foundTrack != null) { return(foundTrack); } // Or maybe a showroom? var uiShowroom = ui.GetSubFile("ui_showroom.json"); if (uiShowroom != null) { var icon = await(directory.GetSubFile("preview.jpg")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await uiShowroom.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); var showroomId = directory.Name ?? directory.Files.Where(x => x.NameLowerCase.EndsWith(".kn5")).OrderByDescending(x => x.Info.Size) .FirstOrDefault()?.NameLowerCase.ApartFromLast(".kn5"); if (showroomId != null) { return(new ShowroomContentEntry(directory.Key ?? "", showroomId, parsed.GetStringValueOnly("name"), parsed.GetStringValueOnly("version"), icon)); } } } var uiCarSkin = directory.GetSubFile("ui_skin.json"); if ((uiCarSkin != null || directory.HasSubFile("preview.jpg") || directory.HasSubFile("livery.png")) && CarsManager.Instance != null /* for crawlers only */) { var icon = await(directory.GetSubFile("livery.png")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); string carId; var skinFor = await(directory.GetSubFile("cm_skin_for.json")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); if (skinFor != null) { carId = JsonExtension.Parse(skinFor.ToUtf8String())["id"]?.ToString(); } else { carId = _installationParams.CarId; if (carId == null && directory.Parent?.NameLowerCase == "skins") { carId = directory.Parent.Parent?.Name; } if (carId == null) { carId = AcContext.Instance.CurrentCar?.Id; } } if (carId == null) { throw new Exception("Can’t figure out car’s ID"); } var skinId = directory.Name; if (skinId != null) { string name; if (uiCarSkin != null) { var data = await uiCarSkin.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); name = parsed.GetStringValueOnly("name"); } else { name = AcStringValues.NameFromId(skinId); } return(new CarSkinContentEntry(directory.Key ?? "", skinId, carId, name, icon)); } } var uiTrackSkin = directory.GetSubFile("ui_track_skin.json"); if (uiTrackSkin != null && TracksManager.Instance != null) { var icon = await(directory.GetSubFile("preview.png")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await uiTrackSkin.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = JsonExtension.Parse(data.ToUtf8String()); var skinId = parsed.GetStringValueOnly("id") ?? directory.Name; var trackId = parsed.GetStringValueOnly("track"); var name = parsed.GetStringValueOnly("name"); if (skinId != null && trackId != null) { return(new TrackSkinContentEntry(directory.Key ?? "", skinId, trackId, name, parsed.GetStringValueOnly("version"), icon)); } } if (directory.HasSubFile("settings.ini")) { var kn5 = directory.Files.Where(x => x.NameLowerCase.EndsWith(".kn5")).ToList(); var id = directory.Name; if (id != null) { if (kn5.Any(x => x.NameLowerCase.ApartFromLast(".kn5") == directory.NameLowerCase)) { var icon = await(directory.GetSubFile("preview.jpg")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); return(new ShowroomContentEntry(directory.Key ?? "", id, AcStringValues.NameFromId(id), null, icon)); } } } var weatherIni = directory.GetSubFile("weather.ini"); if (weatherIni != null) { var icon = await(directory.GetSubFile("preview.jpg")?.Info.ReadAsync() ?? Task.FromResult((byte[])null)); cancellation.ThrowIfCancellationRequested(); var data = await weatherIni.Info.ReadAsync() ?? throw new MissingContentException(); var parsed = IniFile.Parse(data.ToUtf8String()); var name = parsed["LAUNCHER"].GetNonEmpty("NAME"); if (name != null) { var id = directory.Name ?? name; return(new WeatherContentEntry(directory.Key ?? "", id, name, icon)); } } // New textures if (directory.NameLowerCase == "damage" && directory.HasSubFile("flatspot_fl.png")) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? "damage")); } if (directory.NameLowerCase == "clouds" && directory.Files.Any(x => x.NameLowerCase.StartsWith("cloud") && x.NameLowerCase.EndsWith(".dds"))) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? "clouds")); } if (directory.NameLowerCase == "people" && (directory.HasSubFile("crowd_sit.dds") || directory.HasSubFile("people_sit.dds"))) { return(new TexturesConfigEntry(directory.Key ?? "", directory.Name ?? "people")); } // Mod if (directory.Parent?.NameLowerCase == "mods" && directory.HasAnySubDirectory("content", "apps", "system", "launcher")) { var name = directory.Name; if (name != null) { var description = directory.Files.FirstOrDefault(x => x.NameLowerCase.EndsWith(".jsgme")); if (description == null && directory.HasSubDirectory("documentation")) { description = directory.GetSubDirectory("documentation")?.Files.FirstOrDefault(x => x.NameLowerCase.EndsWith(".jsgme")); } if (description != null) { var data = await description.Info.ReadAsync() ?? throw new MissingContentException(); return(new GenericModConfigEntry(directory.Key ?? "", name, data.ToUtf8String())); } return(new GenericModConfigEntry(directory.Key ?? "", name)); } } return(null); }