public void UpdateConfig(BeatSaberQuestomConfig config) { //todo: basic validation of the config if (_readOnly) { throw new InvalidOperationException("Cannot update in read only mode."); } using (var apkFileProvider = new ApkAssetsFileProvider(_apkFilename, ApkAssetsFileProvider.FileCacheMode.Memory, false)) { var manager = new AssetsManager(apkFileProvider, BSConst.GetAssetTypeMap(), false); manager.GetAssetsFile("globalgamemanagers"); //get existing playlists and their songs //compare with new ones //generate a diff //etc. UpdateColorConfig(manager, config.Colors); UpdateTextConfig(manager, config.TextChanges); if (!UpdateSaberConfig(manager, config.Saber)) { Log.LogErr("Saber failed to update. Aborting all changes."); } if (config.Playlists != null) { UpdateMusicConfig(manager, config, apkFileProvider); } else { Log.LogMsg("Playlists is null, song configuration will not be changed."); } Log.LogMsg("Serializing all assets..."); manager.WriteAllOpenAssets(); apkFileProvider.Save(); } }
//private bool SaberExists(AssetsManager manager, string saberID) //{ // var file11 = manager.GetAssetsFile(BSConst.KnownFiles.File11); // return file11.FindAsset<GameObject>(x => x.Object.Name == $"{saberID}Saber") != null; //} //private string GetCurrentSaberID(AssetsManager manager) //{ // var saberChild = GetSaberObjectParentTransform(manager)?.GameObject?.Object; // if (saberChild == null) // throw new Exception("Couldn't find child saber game object of transform."); // return saberChild.Name.Substring(0, saberChild.Name.Length - 5); //} //private Transform GetSaberObjectParentTransform(AssetsManager manager) //{ // var file11 = manager.GetAssetsFile(BSConst.KnownFiles.File11); // var basicSaberModel = file11.FindAsset<GameObject>(x => x.Object.Name == "BasicSaberModel"); // if (basicSaberModel == null) // throw new Exception("Couldn't find BasicSaberModel!"); // var transform = basicSaberModel.Object.Components.FirstOrDefault(x => x.Object is Transform)?.Object as Transform; // if (transform == null) // throw new Exception("Couldn't find Transform on BasicSaberModel!"); // var saberParent = (transform.Children.FirstOrDefault(x => x.Object is Transform // && ((x.Object as Transform).GameObject?.Object?.Name?.EndsWith("Saber") ?? false)).Object as Transform); // if (saberParent == null) // throw new Exception("Could not find child transform of BasicSaberModel!"); // return saberParent; //} //private void SwapToSaberID(AssetsManager manager, string saberID) //{ // var file11 = manager.GetAssetsFile(BSConst.KnownFiles.File11); // var newSaber = file11.FindAsset<GameObject>(x => x.Object.Name == $"{saberID}Saber")?.Object; // if (newSaber == null) // throw new Exception($"Saber with ID {saberID} does not exist!"); // var newSaberTransform = newSaber.Components.FirstOrDefault(x => x.Object is Transform).Object as Transform; // if (newSaberTransform == null) // throw new Exception($"Saber with ID {saberID} is missing its parent transform!"); // var basicSaberModel = file11.FindAsset<GameObject>(x => x.Object.Name == "BasicSaberModel"); // if (basicSaberModel == null) // throw new Exception("Couldn't find BasicSaberModel!"); // var transform = basicSaberModel.Object.Components.FirstOrDefault(x => x.Object is Transform)?.Object as Transform; // if (transform == null) // throw new Exception("Couldn't find Transform on BasicSaberModel!"); // var saberChild = transform.Children.FirstOrDefault(x => x.Object.GameObject?.Object?.Name?.EndsWith("Saber")??false); // if (saberChild == null) // throw new Exception("Couldn't find a game object on the BasicSaberModel Transform that ended with -Saber!"); // int saberIndex = transform.Children.IndexOf(saberChild); // saberChild.Object.Father = null; // transform.Children[saberIndex] = newSaberTransform.PtrFrom(transform) as ISmartPtr<Transform>; // newSaberTransform.Father = transform.PtrFrom(newSaberTransform); //} #endregion private BeatSaberQuestomConfig GetConfig() { lock (this) { BeatSaberQuestomConfig config = new BeatSaberQuestomConfig(); var mainPack = GetMainLevelPack(); CustomLevelLoader loader = new CustomLevelLoader(GetSongsAssetsFile(), _config); foreach (var packPtr in mainPack.BeatmapLevelPacks) { var pack = packPtr.Target.Object; if (HideOriginalPlaylists && BSConst.KnownLevelPackIDs.Contains(pack.PackID)) { continue; } var packModel = new BeatSaberPlaylist() { PlaylistName = pack.PackName, PlaylistID = pack.PackID, LevelPackObject = pack }; var collection = pack.BeatmapLevelCollection.Object; foreach (var songPtr in collection.BeatmapLevels) { var songObj = songPtr.Object; var songModel = new BeatSaberSong() { LevelAuthorName = songObj.LevelAuthorName, SongID = songObj.LevelID, SongAuthorName = songObj.SongAuthorName, SongName = songObj.SongName, SongSubName = songObj.SongSubName, LevelData = songObj }; songModel.CoverArtFilename = loader.GetCoverImageFilename(songObj); packModel.SongList.Add(songModel); } config.Playlists.Add(packModel); } return(config); } }
public void UpdateConfig(BeatSaberQuestomConfig config) { lock (this) { //todo: basic validation of the config PreloadFiles(); // //get existing playlists and their songs //compare with new ones //generate a diff //etc. //TODO; fix //UpdateColorConfig(config.Colors); //TODO: something broke UpdateTextConfig(config.TextChanges); //if (!UpdateSaberConfig(manager, config.Saber)) //{ // Log.LogErr("Saber failed to update. Aborting all changes."); //} if (config.Playlists != null) { UpdateMusicConfig(config); } else { Log.LogMsg("Playlists is null, song configuration will not be changed."); } Log.LogMsg("Serializing all assets..."); _manager.WriteAllOpenAssets(); FileProvider.Save(); } }
//private void UpdateSaberConfig(AssetsManager manager) //{ // var currentSaber = GetCurrentSaberID(manager); // if (saberInfo == null || saberInfo.ID == null) // { // Log.LogMsg("No SaberID provided, saber will not be changed."); // return; // } // if (saberInfo.ID.ToLower() == currentSaber.ToLower()) // { // Log.LogMsg("Current saber ID is already set, no change needed."); // return; // } //} private void UpdateMusicConfig(AssetsManager manager, BeatSaberQuestomConfig config, IAssetsFileProvider apkFileProvider) { //get the old config before we start on this var originalConfig = GetConfig(manager, false); var songsAssetFile = manager.GetAssetsFile(BSConst.KnownFiles.SongsAssetsFilename); foreach (var playlist in config.Playlists) { UpdatePlaylistConfig(manager, playlist); } //open the assets with the main levels collection, find the file index of sharedassets17.assets, and add the playlists to it var mainLevelsFile = manager.GetAssetsFile(BSConst.KnownFiles.MainCollectionAssetsFilename); var file17Index = mainLevelsFile.GetFileIDForFilename(BSConst.KnownFiles.SongsAssetsFilename); var mainLevelPack = GetMainLevelPack(manager); var packsToUnlink = mainLevelPack.BeatmapLevelPacks.Where(x => !HideOriginalPlaylists || !BSConst.KnownLevelPackIDs.Contains(x.Object.PackID)).ToList(); var packsToRemove = mainLevelPack.BeatmapLevelPacks.Where(x => !BSConst.KnownLevelPackIDs.Contains(x.Object.PackID) && !config.Playlists.Any(y => y.PlaylistID == x.Object.PackID)).Select(x => x.Object).ToList(); foreach (var unlink in packsToUnlink) { mainLevelPack.BeatmapLevelPacks.Remove(unlink); unlink.Dispose(); } var oldSongs = originalConfig.Playlists.SelectMany(x => x.SongList).Select(x => x.LevelData).Distinct(); var newSongs = config.Playlists.SelectMany(x => x.SongList).Select(x => x.LevelData).Distinct(); //don't allow removal of the actual tracks or level packs that are built in, although you can unlink them from the main list var removeSongs = oldSongs.Where(x => !newSongs.Contains(x) && !BSConst.KnownLevelIDs.Contains(x.LevelID)).Distinct().ToList(); var addedSongs = newSongs.Where(x => !oldSongs.Contains(x)); var removedPlaylistCount = originalConfig.Playlists.Where(x => !config.Playlists.Any(y => y.PlaylistID == x.PlaylistID)).Count(); var newPlaylistCount = config.Playlists.Where(x => !originalConfig.Playlists.Any(y => y.PlaylistID == x.PlaylistID)).Count(); // // //TODO: clean up cover art, it's leaking! // // List <string> audioFilesToDelete = new List <string>(); removeSongs.ForEach(x => RemoveLevelAssets(manager, x, audioFilesToDelete)); packsToRemove.ForEach(x => RemoveLevelPackAssets(manager, x)); //relink all the level packs in order var addPacks = config.Playlists.Select(x => x.LevelPackObject.PtrFrom(mainLevelPack)); mainLevelPack.BeatmapLevelPacks.AddRange(addPacks); //do a first loop to guess at the file size Int64 originalApkSize = new FileInfo(_apkFilename).Length; Int64 sizeGuess = originalApkSize; foreach (var pl in config.Playlists) { foreach (var sng in pl.SongList) { if (sng.SourceOgg != null) { var clip = sng.LevelData.AudioClip.Object; sizeGuess += new FileInfo(sng.SourceOgg).Length; } } } foreach (var toDelete in audioFilesToDelete) { sizeGuess -= apkFileProvider.GetFileSize(BSConst.KnownFiles.AssetsRootPath + toDelete); } Log.LogMsg(""); Log.LogMsg("Playlists:"); Log.LogMsg($" Added: {newPlaylistCount}"); Log.LogMsg($" Removed: {removedPlaylistCount}"); Log.LogMsg(""); Log.LogMsg("Songs:"); Log.LogMsg($" Added: {addedSongs.Count()}"); Log.LogMsg($" Removed: {removeSongs.Count()}"); Log.LogMsg(""); Log.LogMsg($"Original APK size: {originalApkSize:n0}"); Log.LogMsg($"Guesstimated new size: {sizeGuess:n0}"); Log.LogMsg(""); if (sizeGuess > Int32.MaxValue) { Log.LogErr("***************ERROR*****************"); Log.LogErr($"Guesstimating a file size around {sizeGuess / (Int64)1000000}MB , this will crash immediately upon launch."); Log.LogErr($"The file size MUST be less than {Int32.MaxValue / (int)1000000}MB"); Log.LogErr("***************ERROR*****************"); throw new OverflowException("File might exceed 2.1GB, aborting."); } ////////START WRITING DATA //todo: save here? foreach (var pl in config.Playlists) { foreach (var sng in pl.SongList) { if (sng.SourceOgg != null) { var clip = sng.LevelData.AudioClip.Object; apkFileProvider.WriteFile(sng.SourceOgg, BSConst.KnownFiles.AssetsRootPath + clip.Resource.Source, true, false); //saftey check to make sure we aren't removing a file we just put here if (audioFilesToDelete.Contains(clip.Resource.Source)) { Log.LogErr($"Level id '{sng.LevelData.LevelID}' wrote file '{clip.Resource.Source}' that was on the delete list..."); audioFilesToDelete.Remove(clip.Resource.Source); } } //todo: save on some interval to save ram? } } if (audioFilesToDelete.Count > 0) { Log.LogMsg($"Deleting {audioFilesToDelete.ToString()} audio files"); foreach (var toDelete in audioFilesToDelete) { //Log.LogMsg($"Deleting audio file {toDelete}"); apkFileProvider.Delete(BSConst.KnownFiles.AssetsRootPath + toDelete); } } }
private BeatSaberQuestomConfig GetConfig(AssetsManager manager, bool suppressImages) { BeatSaberQuestomConfig config = new BeatSaberQuestomConfig(); var file19 = manager.GetAssetsFile(BSConst.KnownFiles.MainCollectionAssetsFilename); var file17 = manager.GetAssetsFile(BSConst.KnownFiles.SongsAssetsFilename); var mainPack = GetMainLevelPack(manager); foreach (var packPtr in mainPack.BeatmapLevelPacks) { var pack = packPtr.Target.Object; if (HideOriginalPlaylists && BSConst.KnownLevelPackIDs.Contains(pack.PackID)) { continue; } var packModel = new BeatSaberPlaylist() { PlaylistName = pack.PackName, PlaylistID = pack.PackID, LevelPackObject = pack }; var collection = pack.BeatmapLevelCollection.Object; //get cover art for playlist if (!suppressImages) { try { var coverSprite = pack.CoverImage.Object; var coverTex = coverSprite.Texture.Object; packModel.CoverArt = coverTex.ToBitmap(); packModel.CoverArtBase64PNG = packModel.CoverArt.ToBase64PNG(); } catch (Exception ex) { Log.LogErr($"Unable to convert texture for playlist ID '{pack.PackID}' cover art", ex); } } foreach (var songPtr in collection.BeatmapLevels) { var songObj = songPtr.Object; var songModel = new BeatSaberSong() { LevelAuthorName = songObj.LevelAuthorName, SongID = songObj.LevelID, SongAuthorName = songObj.SongAuthorName, SongName = songObj.SongName, SongSubName = songObj.SongSubName, LevelData = songObj }; if (!suppressImages) { try { var songCover = songObj.CoverImageTexture2D.Object; try { songModel.CoverArt = songCover.ToBitmap(); songModel.CoverArtBase64PNG = songModel.CoverArt.ToBase64PNG(); } catch (Exception ex) { Log.LogErr($"Unable to convert texture for song ID '{songModel.SongID}' cover", ex); } } catch (Exception ex) { Log.LogErr($"Exception loading/converting the cover image for song id '{songObj.LevelID}'", ex); } } packModel.SongList.Add(songModel); } config.Playlists.Add(packModel); } return(config); }