private static string FindFirstOfSplit(IAssetsFileProvider fp, string assetsFile) { int lastDot = assetsFile.LastIndexOf('.'); if (lastDot > 0) { string afterDot = assetsFile.Substring(lastDot, assetsFile.Length - lastDot); string noSplit; if (afterDot.ToLower().StartsWith(".split")) { noSplit = assetsFile.Substring(0, lastDot); if (fp.FileExists(noSplit)) { return(noSplit); } } else { noSplit = assetsFile; } var split0 = noSplit + ".split0"; if (fp.FileExists(split0)) { return(split0); } } if (fp.FileExists(assetsFile)) { return(assetsFile); } return(null); }
public AssetsFile(AssetsManager manager, IAssetsFileProvider fileProvider, string assetsRootPath, string assetsFileName, bool loadData = true) { Manager = manager; FileProvider = fileProvider; AssetsRootPath = assetsRootPath; AssetsFilename = assetsFileName; OpenBaseStream(); BaseStream.Seek(0, SeekOrigin.Begin); using (AssetsReader reader = new AssetsReader(BaseStream, false)) { Header = new AssetsFileHeader(reader); } if (Header.MetadataSize > Header.FileSize || Header.ObjectDataOffset < Header.MetadataSize || Header.Version != 17) { throw new NotSupportedException($"{AssetsFilename} doesn't appear to be a valid assets file, or {Header.Version} is unsupported!"); } if (loadData) { LoadData(); } }
/// <summary> /// Writes the MANIFEST.MF name and hash and the sigfile.SF hash for the sourceFile /// </summary> private void WriteEntryHashes(IAssetsFileProvider provider, string sourceFile, Stream manifestFileStream, Stream signatureFileStream) { using (Stream s = provider.GetReadStream(sourceFile)) { var hash = _sha.ComputeHash(s); using (MemoryStream msSection = new MemoryStream()) { string hashOfMFSection = null; using (StreamWriter swSection = GetSW(msSection)) { swSection.WriteLine($"Name: {sourceFile}"); swSection.WriteLine($"SHA1-Digest: {Convert.ToBase64String(hash)}"); swSection.WriteLine(""); } msSection.Seek(0, SeekOrigin.Begin); hashOfMFSection = Convert.ToBase64String(_sha.ComputeHash(msSection)); msSection.Seek(0, SeekOrigin.Begin); var actualString = UTF8Encoding.UTF8.GetString(msSection.ToArray()); using (var swSFFile = GetSW(signatureFileStream)) { swSFFile.WriteLine($"Name: {sourceFile}"); swSFFile.WriteLine($"SHA1-Digest: {hashOfMFSection}"); swSFFile.WriteLine(); } msSection.Seek(0, SeekOrigin.Begin); msSection.CopyTo(manifestFileStream); } } }
public static Stream ReadCombinedAssets(this IAssetsFileProvider fp, string assetsFilePath) { 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 { return(fp.GetReadStream(actualName)); } MemoryStream msFullFile = new MemoryStream(); foreach (string assetsFile in assetFiles) { byte[] fileBytes = fp.Read(assetsFile); msFullFile.Write(fileBytes, 0, fileBytes.Length); } return(msFullFile); }
protected override QaeConfig GetQaeConfig(IAssetsFileProvider prov) { return(new QaeConfig() { AssetsPath = "assets/bin/Data/", SongsPath = "", FileProvider = prov, SongFileProvider = new FolderFileProvider(".\\", false) }); }
public AssetsManager(IAssetsFileProvider fileProvider, string assetsRootPath, Dictionary <string, Type> classNameToTypes) { _fileProvider = fileProvider; _assetsRootPath = assetsRootPath; LazyLoad = true; ClassNameToTypes = classNameToTypes; ForceLoadAllFiles = false; }
public static bool Patch(IAssetsFileProvider apk, FilePatch patch) { string binaryFile = patch.Filename; if (!apk.FileExists(binaryFile)) { Console.WriteLine("Binary file to patch doesn't exist in the APK!"); return(false); } byte[] binaryBytes = apk.Read(binaryFile); if (binaryBytes.Length != patch.ExpectedFileSize) { Console.WriteLine("Binary file to patch is the wrong length!"); return(false); } List <Patch> toApply = new List <Patch>(); Console.WriteLine("Verifying patches binary..."); using (MemoryStream msBinary = new MemoryStream(binaryBytes)) { //verify each of the patches can be applied or already are applied foreach (Patch p in patch.Patches) { msBinary.Seek(p.Address, SeekOrigin.Begin); byte[] readVals = new byte[p.ExpectedData.Count]; msBinary.Read(readVals, 0, p.ExpectedData.Count); if (!readVals.SequenceEqual(p.ExpectedData)) { msBinary.Seek(p.Address, SeekOrigin.Begin); readVals = new byte[p.PatchData.Count]; msBinary.Read(readVals, 0, p.PatchData.Count); if (readVals.SequenceEqual(p.PatchData)) { Console.WriteLine($"Patch {p.Name} already appears to be applied."); continue; } else { Console.WriteLine($"Patch {p.Name} can't be applied to this binary, the code at the patch location doesn't match what was expected. Aborting any patching..."); //if one patch can't be applied, abort the whole thing return(false); } } } foreach (Patch p in toApply) { msBinary.Seek(p.Address, SeekOrigin.Begin); msBinary.Write(p.PatchData.ToArray(), 0, p.PatchData.Count); } msBinary.Seek(0, SeekOrigin.Begin); apk.Write(binaryFile, msBinary.ToArray(), true, true); } Console.WriteLine("Done patching binary!"); return(true); }
private void CloseStuff() { etMain.DataSource = null; etLeft.DataSource = null; etRight.DataSource = null; cbAssetsFile.Items.Clear(); if (_fileProvider != null) { _fileProvider.Dispose(); _fileProvider = null; } }
public static void WriteCombinedAssets(this IAssetsFileProvider fp, AssetsFile assetsFile, string assetsFilePath) { if (assetsFilePath.EndsWith("split0")) { throw new ArgumentException("Don't pass in filenames with split0, pass in the original."); } fp.DeleteFiles(assetsFilePath + ".split*"); using (var ms = new MemoryStream()) { assetsFile.Write(ms); ms.Seek(0, SeekOrigin.Begin); fp.Write(assetsFilePath, ms.ToArray(), true, true); } }
public static string CorrectAssetFilename(this IAssetsFileProvider fp, string assetsFile) { var correctName = FindFirstOfSplit(fp, assetsFile); if (correctName != null) { return(correctName); } //some of the files in ExternalFiles have library/ on them, but they're actually in Resources/ if (assetsFile.Contains("library/")) { string whyUnity = assetsFile.Replace("library/", "Resources/"); correctName = FindFirstOfSplit(fp, whyUnity); if (correctName != null) { return(correctName); } //whyUnity = assetsFile.Replace("library/", ""); //correctName = FindFirstOfSplit(fp, whyUnity); //if (correctName != null) // return correctName; //whyUnity = assetsFile.Replace("/library/", ""); //correctName = FindFirstOfSplit(fp, whyUnity); //if (correctName != null) // return correctName; } //some of the files in ExternalFiles have library/ on them, but they're actually in the root path var splitPath = assetsFile.Split('/').ToList(); if (splitPath.Count() > 1) { splitPath.RemoveAt(splitPath.Count - 2); correctName = String.Join("/", splitPath); correctName = FindFirstOfSplit(fp, correctName); if (correctName != null) { return(correctName); } } throw new ArgumentException($"The assets file {assetsFile} doesn't exist in with any known name variations!"); }
public static string CorrectAssetFilename(this IAssetsFileProvider fp, string assetsFile) { var correctName = FindFirstOfSplit(fp, assetsFile); if (correctName != null) { return(correctName); } //some of the files in ExternalFiles have library/ on them, but they're actually in the root path var splitPath = assetsFile.Split('/').ToList(); if (splitPath.Count() > 1) { splitPath.RemoveAt(splitPath.Count - 2); correctName = String.Join("/", splitPath); correctName = FindFirstOfSplit(fp, correctName); if (correctName != null) { return(correctName); } } throw new ArgumentException("The file doesn't exist in the APK with any name!"); }
public static Stream ReadCombinedAssets(this IAssetsFileProvider 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. if (true) { return(new CombinedStream(assetFiles, fp)); } else { MemoryStream msFullFile = new MemoryStream(); foreach (string assetsFile in assetFiles) { byte[] fileBytes = fp.Read(assetsFile); msFullFile.Write(fileBytes, 0, fileBytes.Length); } return(msFullFile); } }
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 ApkAssetsFileProvider(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 APK!", 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; }
public static string ReadToString(this IAssetsFileProvider provider, string filename) { var data = provider.Read(filename); return(System.Text.Encoding.UTF8.GetString(data)); }
public CombinedStream(List <string> orderedSplitFiles, IAssetsFileProvider provider) { _fileProvider = provider; InitFromFiles(orderedSplitFiles); }
public void Sign(IAssetsFileProvider 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(); } } }
protected abstract QaeConfig GetQaeConfig(IAssetsFileProvider prov);
//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); } } }
public AssetsManager(IAssetsFileProvider fileProvider, Dictionary <string, Type> classNameToTypes, bool lazyLoad = false) { _fileProvider = fileProvider; LazyLoad = lazyLoad; ClassNameToTypes = classNameToTypes; }