private DownloadType IdentifyFileType(IFileProvider provider) { if (provider.FileExists("beatonmod.json")) { return(DownloadType.ModFile); } else if (provider.FileExists("info.dat")) { return(DownloadType.SongFile); } else if (provider.FileExists("info.json")) { return(DownloadType.OldSongFile); } else { var files = provider.FindFiles("*"); //check if all of the files are .json files, and guess that maybe it's a playlist or two if (!files.Any(x => !x.ToLower().EndsWith(".json"))) { //going to guess maybe this is a playlist, becuase there aren't any other options right now return(DownloadType.Playlist); } } return(DownloadType.Unknown); }
/// <summary> /// Extracts a mod from a provider and returns the path RELATIVE TO THE BEATONDATAROOT /// </summary> private void ExtractAndInstallMod(IFileProvider provider) { try { var def = _getEngine().ModManager.LoadDefinitionFromProvider(provider); if (def.Platform != "Quest") { Log.LogErr($"Attempted to load a mod for a different platform, '{def.Platform ?? "(null)"}', only 'Quest' is supported"); _showToast("Incompatible Mod", "This mod is not supported on the Quest.", ToastType.Error, 5); return; } var modOutputPath = _qaeConfig.ModsSourcePath.CombineFwdSlash(def.ID); if (_qaeConfig.RootFileProvider.DirectoryExists(modOutputPath)) { Log.LogMsg($"Installing mod ID {def.ID} but it seems to exist. Deleting the existing mod folder."); _qaeConfig.RootFileProvider.RmRfDir(modOutputPath); } _qaeConfig.RootFileProvider.MkDir(modOutputPath); var allFiles = provider.FindFiles("*"); foreach (var file in allFiles) { try { var targetFile = modOutputPath.CombineFwdSlash(file); var dir = targetFile.GetDirectoryFwdSlash(); if (!_qaeConfig.RootFileProvider.DirectoryExists(dir)) { _qaeConfig.RootFileProvider.MkDir(dir); } using (Stream fs = _qaeConfig.RootFileProvider.GetWriteStream(targetFile)) { using (Stream rs = provider.GetReadStream(file, true)) { rs.CopyTo(fs); } //TODO: this won't work with zip file provider on the root because the stream has to stay open until Save() } } catch (Exception ex) { Log.LogErr($"Exception extracting {file} from provider {provider.SourceName} on mod ID {def.ID}", ex); throw new ImportException($"Exception extracting {file} from provider {provider.SourceName}", $"Unable to extract files from mod file {provider.SourceName}!", ex); } } _getEngine().ModManager.ResetCache(); QueueModInstall(def); } catch (Exception ex) { Log.LogErr($"Exception trying to load mod from provider {provider.SourceName}", ex); throw new ImportException($"Exception trying to load mod from provider {provider.SourceName}", $"Unable to load mod from {provider.SourceName}, it does not appear to be a valid mod file.", ex); } }
//if file ends in .split0 yes //if file ends in .assets yes //if file has no extension yes public List <string> FindAndLoadAllAssets() { List <string> loadedFiles = new List <string>(); var foundFiles = _fileProvider.FindFiles(_assetsRootPath + "*"); List <string> tryFiles = new List <string>(); foreach (var foundFile in foundFiles) { var filename = foundFile.Substring(_assetsRootPath.Length); if (filename.Any(x => x == '/')) { if (!filename.Substring(filename.LastIndexOf("/")).Contains(".")) { tryFiles.Add(filename); continue; } } else if (!filename.Contains(".")) { tryFiles.Add(filename); continue; } if (filename.ToLower().EndsWith(".split0")) { filename = filename.Substring(0, filename.Length - ".split0".Length); tryFiles.Add(filename); continue; } if (filename.ToLower().EndsWith(".assets")) { tryFiles.Add(filename); continue; } } foreach (var tryFile in tryFiles) { if (_openAssetsFiles.ContainsKey(tryFile.ToLower())) { loadedFiles.Add(tryFile); continue; } AssetsFile file; if (TryGetAssetsFile(tryFile, out file)) { loadedFiles.Add(tryFile); } } return(loadedFiles); }
public static Stream ReadCombinedAssets(this IFileProvider fp, string assetsFilePath, out bool wasCombined) { string actualName = fp.CorrectAssetFilename(assetsFilePath); List <string> assetFiles = new List <string>(); if (actualName.ToLower().EndsWith("split0")) { assetFiles.AddRange(fp.FindFiles(actualName.Replace(".split0", ".split*")) .OrderBy(x => Convert.ToInt32(x.Split(new string[] { ".split" }, StringSplitOptions.None).Last()))); } else { wasCombined = false; return(fp.GetReadStream(actualName)); } wasCombined = true; //TODO: property or something on the file provider interface letting this code know if it should use the combined stream // I think combined stream may perform horribly on zip files or cause other issues. return(new CombinedStream(assetFiles, fp)); }
private void ImportPlaylistFilesFromProvider(IFileProvider provider) { int success = 0; int fail = 0; foreach (var file in provider.FindFiles("*.json")) { try { Log.LogMsg($"Importing playlist file '{file}' from zip '{provider.SourceName}'"); ImportFile(file.GetFilenameFwdSlash(), "application/json", provider.Read(file)); success++; } catch (Exception ex) { Log.LogErr($"Exception trying to add playlist file '{file}' from zip '{provider.SourceName}'", ex); fail++; continue; } } //todo: show a toast here? }
public void Sign(IFileProvider fileProvider) { MemoryStream msManifestFile = new MemoryStream(); MemoryStream msSigFile = new MemoryStream(); byte[] keyBlock; MemoryStream msSignatureFileBody = new MemoryStream(); try { //create the MF file header using (StreamWriter swManifest = GetSW(msManifestFile)) { swManifest.WriteLine("Manifest-Version: 1.0"); swManifest.WriteLine("Created-By: emulamer"); swManifest.WriteLine(); } //so that we can do it in one pass, write the MF and SF line items at the same time to their respective streams foreach (var infFile in fileProvider.FindFiles("*").Where(x => !x.StartsWith("META-INF"))) { WriteEntryHashes(fileProvider, infFile, msManifestFile, msSignatureFileBody); } //compute the hash on the entirety of the manifest file for the SF file msManifestFile.Seek(0, SeekOrigin.Begin); var manifestFileHash = _sha.ComputeHash(msManifestFile); //write the SF to memory then copy it out to the actual file- contents will be needed later to use for signing, don't want to hit the zip stream twice byte[] sigFileBytes = null; using (StreamWriter swSignatureFile = GetSW(msSigFile)) { swSignatureFile.WriteLine("Signature-Version: 1.0"); swSignatureFile.WriteLine($"SHA1-Digest-Manifest: {Convert.ToBase64String(manifestFileHash)}"); swSignatureFile.WriteLine("Created-By: emulamer"); swSignatureFile.WriteLine(); } msSignatureFileBody.Seek(0, SeekOrigin.Begin); msSignatureFileBody.CopyTo(msSigFile); msSigFile.Seek(0, SeekOrigin.Begin); sigFileBytes = msSigFile.ToArray(); //get the key block (all the hassle distilled into one line), then write it out to the RSA file keyBlock = SignIt(sigFileBytes); //delete all the META-INF stuff that exists already fileProvider.DeleteFiles("META-INF*"); //write the 3 files msManifestFile.Seek(0, SeekOrigin.Begin); fileProvider.Write("META-INF/MANIFEST.MF", msManifestFile.ToArray(), true, true); fileProvider.Write("META-INF/BS.SF", sigFileBytes, true, true); fileProvider.Write("META-INF/BS.RSA", keyBlock, true, true); fileProvider.Save(); } finally { if (msManifestFile != null) { msManifestFile.Dispose(); } if (msSignatureFileBody != null) { msSignatureFileBody.Dispose(); } if (msManifestFile != null) { msManifestFile.Dispose(); } if (msSigFile != null) { msSigFile.Dispose(); } } }
/// <summary> /// Extracts a song from a provider and returns the path RELATIVE TO THE BEATONDATAROOT /// </summary> private string ExtractSongGetPath(IFileProvider provider) { try { //use the incoming file or folder name (without extension) as the song ID. Since the filesystem has to have unique names, this should help keep songIDs unique also var targetSongID = Path.GetFileNameWithoutExtension(provider.SourceName); if (targetSongID.Contains(".")) { targetSongID = targetSongID.Substring(0, targetSongID.LastIndexOf(".")); } var targetOutputDir = _qaeConfig.SongsPath.CombineFwdSlash(targetSongID); if (!_qaeConfig.RootFileProvider.DirectoryExists(targetOutputDir)) { _qaeConfig.RootFileProvider.MkDir(targetOutputDir); } if (_qaeConfig.RootFileProvider.FileExists(targetOutputDir.CombineFwdSlash("info.dat"))) { Log.LogMsg($"ImportManager skipping extract because {targetOutputDir} already exists and has an info.dat."); return(targetOutputDir); } var allFiles = provider.FindFiles("*"); var firstInfoDat = allFiles.FirstOrDefault(x => x.ToLower() == "info.dat"); if (firstInfoDat == null) { throw new ImportException($"Unable to find info.dat in provider {provider.SourceName}", $"Zip file {provider.SourceName} doesn't seem to be a song (no info.dat)."); } //get the path of where the info.dat is and assume that the rest of the files will be in the same directory as it. // This is to handle zips that have duplicate info or nested folders var infoDatPath = firstInfoDat.GetDirectoryFwdSlash(); foreach (var file in allFiles) { try { if (Path.GetExtension(file).ToLower() == "zip") { Log.LogMsg($"Skipped {file} because it looks like a nested zip file."); continue; } //if the file isn't in the same path as the located info.dat, skip it if (file.GetDirectoryFwdSlash() != infoDatPath) { Log.LogMsg($"Skipped zip file {file} because it wasn't in the path with info.dat at {infoDatPath}"); continue; } var targetFile = targetOutputDir.CombineFwdSlash(file.GetFilenameFwdSlash()); using (Stream fs = _qaeConfig.RootFileProvider.GetWriteStream(targetFile)) { using (Stream rs = provider.GetReadStream(file, true)) { rs.CopyTo(fs); } //TODO: this won't work with zip file provider on the root because the stream has to stay open until Save() } } catch (Exception ex) { Log.LogErr($"Exception extracting song file {file} from provider {provider.SourceName}", ex); throw new ImportException($"Exception extracting song file {file} from provider {provider.SourceName}", $"Unable to import song from {provider.SourceName}! Extracting files failed!", ex); } } return(targetOutputDir); } catch (Exception ex) { Log.LogErr($"Exception importing song from provider {provider.SourceName}", ex); throw new ImportException($"Exception importing song from provider {provider.SourceName}", $"Error importing song from {provider.SourceName}", ex); } }
/// <summary> /// Extracts a mod from a provider and returns the path RELATIVE TO THE BEATONDATAROOT /// </summary> private void ExtractAndInstallMod(IFileProvider provider) { lock (_modInstallLock) { try { var def = _getEngine().ModManager.LoadDefinitionFromProvider(provider); if (def.Platform != "Quest") { Log.LogErr($"Attempted to load a mod for a different platform, '{def.Platform ?? "(null)"}', only 'Quest' is supported"); _showToast("Incompatible Mod", "This mod is not supported on the Quest.", ToastType.Error, 5); return; } var modOutputPath = _qaeConfig.ModsSourcePath.CombineFwdSlash(def.ID); if (_qaeConfig.RootFileProvider.DirectoryExists(modOutputPath)) { Log.LogMsg($"Installing mod ID {def.ID} but it seems to exist. Deleting the existing mod folder."); _qaeConfig.RootFileProvider.RmRfDir(modOutputPath); } _qaeConfig.RootFileProvider.MkDir(modOutputPath); var allFiles = provider.FindFiles("*"); foreach (var file in allFiles) { try { var targetFile = modOutputPath.CombineFwdSlash(file); var dir = targetFile.GetDirectoryFwdSlash(); if (!_qaeConfig.RootFileProvider.DirectoryExists(dir)) { _qaeConfig.RootFileProvider.MkDir(dir); } using (Stream fs = _qaeConfig.RootFileProvider.GetWriteStream(targetFile)) { using (Stream rs = provider.GetReadStream(file, true)) { rs.CopyTo(fs); } //TODO: this won't work with zip file provider on the root because the stream has to stay open until Save() } } catch (Exception ex) { Log.LogErr($"Exception extracting {file} from provider {provider.SourceName} on mod ID {def.ID}", ex); throw new ImportException($"Exception extracting {file} from provider {provider.SourceName}", $"Unable to extract files from mod file {provider.SourceName}!", ex); } } var eng = _getEngine(); eng.ModManager.ModAdded(def); _getEngine().ModManager.ResetCache(); var bsVer = _getConfig().BeatSaberVersion; if (_getConfig().BeatSaberVersion != def.TargetBeatSaberVersion) { Log.LogErr($"Mod ID {def.ID} was imported but will not be automatically activated because it's target version {def.TargetBeatSaberVersion} doesn't match beat saber's version {bsVer}"); _getConfig().Config = _getEngine().GetCurrentConfig(); _triggerConfigChanged(); _showToast("Mod Not Enabled", "Mod was not enabled because it may not be compatible.", ToastType.Warning, 5); } else { QueueModInstall(def); } } catch (Exception ex) { Log.LogErr($"Exception trying to load mod from provider {provider.SourceName}", ex); throw new ImportException($"Exception trying to load mod from provider {provider.SourceName}", $"Unable to load mod from {provider.SourceName}, it may be an invalid file or another mod of the same type failed to uninstall.", ex); } } }
/// <summary> /// Extracts a song from a provider and returns the path RELATIVE TO THE BEATONDATAROOT /// </summary> private string ExtractSongGetPath(IFileProvider provider, bool overwriteIfExists) { try { //use the incoming file or folder name (without extension) as the song ID. Since the filesystem has to have unique names, this should help keep songIDs unique also var targetSongID = Path.GetFileNameWithoutExtension(provider.SourceName); if (targetSongID.Contains(".")) { targetSongID = targetSongID.Substring(0, targetSongID.LastIndexOf(".")); } //checking this first to maintain compability with song IDs that are just <songid> and are not <songid>_<name> - <author> var targetOutputDir = _qaeConfig.SongsPath.CombineFwdSlash(targetSongID); if (!overwriteIfExists && _qaeConfig.RootFileProvider.FileExists(targetOutputDir.CombineFwdSlash("info.dat"))) { Log.LogMsg($"ImportManager skipping extract because {targetOutputDir} already exists and has an info.dat."); return(targetOutputDir); } var allFiles = provider.FindFiles("*"); var firstInfoDat = allFiles.FirstOrDefault(x => x.ToLower() == "info.dat"); if (firstInfoDat == null) { throw new ImportException($"Unable to find info.dat in provider {provider.SourceName}", $"Zip file {provider.SourceName} doesn't seem to be a song (no info.dat)."); } //deserialize the info to get song name and stuff try { var infoStr = provider.ReadToString(firstInfoDat); var bml = JsonConvert.DeserializeObject <BeatmapLevelDataObject>(infoStr); if (bml == null) { throw new Exception("Info.dat returned null from deserializer."); } if (bml.SongName == null) { throw new Exception("Info.dat deserialized with a null SongName."); } var songName = $"{bml.SongName} - {bml.SongAuthorName}"; songName = Regex.Replace(songName, "[^a-zA-Z0-9 -]", ""); targetSongID = $"{targetSongID}_{songName}"; } catch (Exception ex) { Log.LogErr($"Deserializing song info for file {provider.SourceName} failed", ex); throw new ImportException($"Deserializing song info for file {provider.SourceName} failed", $"Info.dat file in zip {provider.SourceName} doesn't appear to be a valid song.", ex); } targetOutputDir = _qaeConfig.SongsPath.CombineFwdSlash(targetSongID); if (!overwriteIfExists && _qaeConfig.RootFileProvider.FileExists(targetOutputDir.CombineFwdSlash("info.dat"))) { Log.LogMsg($"ImportManager skipping extract because {targetOutputDir} already exists and has an info.dat."); return(targetOutputDir); } if (!_qaeConfig.RootFileProvider.DirectoryExists(targetOutputDir)) { _qaeConfig.RootFileProvider.MkDir(targetOutputDir); } //get the path of where the info.dat is and assume that the rest of the files will be in the same directory as it. // This is to handle zips that have duplicate info or nested folders var infoDatPath = firstInfoDat.GetDirectoryFwdSlash(); foreach (var file in allFiles) { try { if (Path.GetExtension(file).ToLower() == "zip") { Log.LogMsg($"Skipped {file} because it looks like a nested zip file."); continue; } //if the file isn't in the same path as the located info.dat, skip it if (file.GetDirectoryFwdSlash() != infoDatPath) { Log.LogMsg($"Skipped zip file {file} because it wasn't in the path with info.dat at {infoDatPath}"); continue; } var targetFile = targetOutputDir.CombineFwdSlash(file.GetFilenameFwdSlash()); using (Stream fs = _qaeConfig.RootFileProvider.GetWriteStream(targetFile)) { using (Stream rs = provider.GetReadStream(file, true)) { rs.CopyTo(fs); } //TODO: this won't work with zip file provider on the root because the stream has to stay open until Save() } } catch (Exception ex) { Log.LogErr($"Exception extracting song file {file} from provider {provider.SourceName}", ex); throw new ImportException($"Exception extracting song file {file} from provider {provider.SourceName}", $"Unable to import song from {provider.SourceName}! Extracting files failed!", ex); } } return(targetOutputDir); } catch (Exception ex) { Log.LogErr($"Exception importing song from provider {provider.SourceName}", ex); throw new ImportException($"Exception importing song from provider {provider.SourceName}", $"Error importing song from {provider.SourceName}", ex); } }
private void BtnLoad_Click(object sender, EventArgs e) { ContextMenu cm = new ContextMenu(new MenuItem[] { new MenuItem("APK", (s, e2) => { OpenFileDialog ofd = new OpenFileDialog() { CheckFileExists = true, Title = "Open Bundle File", Multiselect = false }; if (ofd.ShowDialog() == DialogResult.Cancel) { return; } CloseStuff(); try { _fileProvider = new ZipFileProvider(ofd.FileName, FileCacheMode.Memory, false); _manager = new AssetsManager(_fileProvider, BSConst.KnownFiles.AssetsRootPath, BSConst.GetAssetTypeMap()); if (_fileProvider.FindFiles("globalgamemanagers").Count > 0) { _manager.GetAssetsFile("globalgamemanagers.assets"); } if (_fileProvider.FindFiles("globalgamemanagers.assets*").Count > 0) { _manager.GetAssetsFile("globalgamemanagers.assets"); } _manager.FindAndLoadAllAssets(); FillAssetsFiles(); this.Text = "Assets Explorer - " + Path.GetFileName(ofd.FileName); } catch (Exception ex) { Log.LogErr("Couldn't load APK!", ex); MessageBox.Show("Failed to load!"); if (_fileProvider != null) { _fileProvider.Dispose(); _fileProvider = null; } return; } }), new MenuItem("Folder", (s, e2) => { FolderBrowserDialog fbd = new FolderBrowserDialog() { ShowNewFolderButton = false, Description = "Select Assets Root Folder" }; if (fbd.ShowDialog() == DialogResult.Cancel) { return; } CloseStuff(); try { _fileProvider = new FolderFileProvider(fbd.SelectedPath, false); _manager = new AssetsManager(_fileProvider, "", BSConst.GetAssetTypeMap()); if (_fileProvider.FindFiles("globalgamemanagers").Count > 0) { _manager.GetAssetsFile("globalgamemanagers.assets"); } if (_fileProvider.FindFiles("globalgamemanagers.assets*").Count > 0) { _manager.GetAssetsFile("globalgamemanagers.assets"); } _manager.FindAndLoadAllAssets(); FillAssetsFiles(); this.Text = "Assets Explorer - " + Path.GetFileName(fbd.SelectedPath); } catch (Exception ex) { Log.LogErr("Couldn't load folder!", ex); MessageBox.Show("Failed to load!"); if (_fileProvider != null) { _fileProvider.Dispose(); _fileProvider = null; } return; } }), new MenuItem("Bundle", (s, e2) => { OpenFileDialog ofd = new OpenFileDialog() { CheckFileExists = true, Title = "Open Bundle File", Multiselect = false }; if (ofd.ShowDialog() == DialogResult.Cancel) { return; } CloseStuff(); try { _fileProvider = new BundleFileProvider(ofd.FileName, true); _manager = new AssetsManager(_fileProvider, "", BSConst.GetAssetTypeMap()); _manager.FindAndLoadAllAssets(); FillAssetsFiles(); this.Text = "Assets Explorer - " + Path.GetFileName(ofd.FileName); } catch (Exception ex) { Log.LogErr("Couldn't load bundle!", ex); MessageBox.Show("Failed to load!"); if (_fileProvider != null) { _fileProvider.Dispose(); _fileProvider = null; } return; } }) }); cm.Show(btnLoad, new Point(0, btnLoad.Height)); return; }