public void TestBasicRoundTrip() { using (Apk apk = new Apk(baseAPKPath)) { byte[] data = apk.ReadEntireEntry("assets/bin/Data/3eb70b9e20363dd488f8c4841d7db87f"); TestRoundTrips(data, "basic"); } }
static void Install( Apk apk, SerializedAssets assets, HashSet <string> toInstall, InvocationResult res, Dictionary <string, string> levels ) { foreach (string levelID in toInstall) { string levelFolder = levels[levelID]; try { JsonLevel level = JsonLevel.LoadFromFolder(levelFolder); // We use transactions here so if these throw // an exception, which happens when levels are // invalid, then it doesn't modify the APK in // any way that might screw things up later. var assetsTxn = new SerializedAssets.Transaction(assets); var apkTxn = new Apk.Transaction(); AssetPtr levelPtr = level.AddToAssets(assetsTxn, apkTxn, levelID); // Danger should be over, nothing here should fail assetsTxn.ApplyTo(assets); apkTxn.ApplyTo(apk); res.installedLevels.Add(levelID); } catch (FileNotFoundException e) { res.installSkipped.Add(levelID, $"Missing file referenced by level: {e.FileName}"); } catch (JsonReaderException e) { res.installSkipped.Add(levelID, $"Invalid level JSON: {e.Message}"); } } }
public RenamerWindow(Apk apk) { InitializeComponent(); this.apk = apk; this.config = new Config(); }
public void TestLoadBeatmap() { using (Apk apk = new Apk(baseAPKPath)) { byte[] data = apk.ReadEntireEntry(Apk.MainAssetsFile); SerializedAssets assets = SerializedAssets.FromBytes(data); SerializedAssets.AssetObject obj = assets.objects[62]; MonoBehaviorAssetData monob = (MonoBehaviorAssetData)obj.data; BeatmapDataBehaviorData beatmap = (BeatmapDataBehaviorData)monob.data; using (Stream fileStream = new FileStream(repoPath("testoutput/beatmap_deflated.bin"), FileMode.Create)) { using (MemoryStream memoryStream = new MemoryStream(beatmap.projectedData)) { using (DeflateStream ds = new DeflateStream(memoryStream, CompressionMode.Decompress)) { ds.CopyTo(fileStream); } } } BeatmapSaveData saveData = BeatmapSaveData.DeserializeFromBinary(beatmap.projectedData); Assert.NotEmpty(saveData._notes); byte[] outData = saveData.SerializeToBinary(false); File.WriteAllBytes(repoPath("testoutput/beatmap_roundtrip.bin"), outData); BeatmapSaveData saveData2 = BeatmapSaveData.DeserializeFromBinary(outData, false); Assert.NotEmpty(saveData._notes); byte[] outData2 = saveData.SerializeToBinary(false); File.WriteAllBytes(repoPath("testoutput/beatmap_roundtrip2.bin"), outData); } }
private async void TxtPath_TextChanged(object sender, TextChangedEventArgs e) { if (!(sender is TextBox tb)) { return; } txtLabel.Text = ". . . ."; txtLabel.ToolTip = null; txtPackage.Text = ". . . ."; txtPackage.ToolTip = null; txtVersion.Text = ". . . ."; txtVersion.ToolTip = null; txtAbi.Text = ". . . ."; txtAbi.ToolTip = null; txtSdk.Text = ". . . ."; imgIcon.Source = App.GetPlaystoreImageFromResources(); gbAction.IsEnabled = false; if (string.IsNullOrWhiteSpace(tb.Text)) { return; } if (loadedApk == null || loadedApk.FilePath != tb.Text) { ShowLoading(); var aapt = await Aapt.DumbBadging(tb.Text); if (aapt.Success) { loadedApk = aapt.Apk; txtLabel.Text = loadedApk.Label; txtLabel.ToolTip = txtLabel.Text; txtPackage.Text = loadedApk.PackageName; txtPackage.ToolTip = txtPackage.Text; txtVersion.Text = string.Format("{0} ( {1} )", loadedApk.VersionName, loadedApk.VersionCode); txtVersion.ToolTip = txtVersion.Text; txtAbi.Text = loadedApk.AbiList; txtAbi.ToolTip = txtAbi.Text; txtSdk.Text = loadedApk.SdkVersion.ToString(); imgIcon.Source = loadedApk.Icon; gbAction.IsEnabled = true; } // file corrupt or error else { txtLabel.Text = "file corrupt?"; txtPackage.Text = "not an apk file? unusual file path?"; } ShowLoading(false); } }
private async Task Run() { GoogleCredential creds; using (var stream = new FileStream(_credsFilePath, FileMode.Open)) { creds = GoogleCredential.FromStream(stream).CreateScoped( AndroidPublisherService.Scope.Androidpublisher); } var service = new AndroidPublisherService(new BaseClientService.Initializer { HttpClientInitializer = creds }); var editRequest = service.Edits.Insert(null, Package); var edit = await editRequest.ExecuteAsync(); Console.WriteLine("Created edit with id {0}.", edit.Id); Apk apk = null; using (var stream = new FileStream(_apkFilePath, FileMode.Open)) { var uploadMedia = service.Edits.Apks.Upload(Package, edit.Id, stream, "application/vnd.android.package-archive"); var progress = await uploadMedia.UploadAsync(); if (progress.Status == Google.Apis.Upload.UploadStatus.Completed) { apk = uploadMedia.ResponseBody; } else { throw new Exception("Upload failed."); } } Console.WriteLine("Version code {0} has been uploaded.", apk.VersionCode); var trackRequest = service.Edits.Tracks.Update(new Track { VersionCodes = new List <int?> { apk.VersionCode } }, Package, edit.Id, _track); var updatedTrack = await trackRequest.ExecuteAsync(); Console.WriteLine("Track {0} has been updated.", updatedTrack.TrackValue); var commitRequest = service.Edits.Commit(Package, edit.Id); var commitEdit = await commitRequest.ExecuteAsync(); Console.WriteLine("App edit with id {0} has been comitted.", commitEdit.Id); }
void UploadMedia_ResponseReceived(Apk obj) { _versionCode = obj.VersionCode.Value; Logger.WrightLog("Upload: version code: " + _versionCode); if (uploadType == UploadType.library) { Logger.WrightLog("Upload finished. Find it in Console artifact library"); return; } UpdateTrack(uploadType.ToString().ToLower()); }
private void BuildCommand_Execute() { if (Apk == null) { return; } if (!Apk.HasJava()) { MessBox.ShowDial(StringResources.JavaNotFoundError, StringResources.ErrorLower); return; } ClearVisLog(); bool success = false; var errors = new List <Error>(); LoadingWindow.ShowWindow( beforeStarting: () => IsBusy = true, threadActions: source => success = Apk.Compile(out errors), finishActions: () => { IsBusy = false; VisLog(LogLine); VisLog(success ? StringResources.Finished : StringResources.ErrorWhileCompiling); VisLog(LogLine); if (GlobalVariables.AppSettings.ShowNotifications) { NotificationService.Instance.ShowMessage(StringResources.CompilationFinished); } if (!success && errors.Any(error => error.Type != Error.ErrorType.None)) { if (MessBox.ShowDial("Обнаружены ошибки. Попробовать исправить?", "", MessBox.MessageButtons.Yes, MessBox.MessageButtons.No) == MessBox.MessageButtons.Yes) { Apktools.FixErrors(errors); BuildCommand_Execute(); } } else { VisLog(StringResources.FileIsSituatedIn + " " + Apk.NewApk); } RaiseCommandsCanExecute(); }, cancelVisibility: Visibility.Collapsed, ownerWindow: _window ); }
public void TestTextReplaceRoundTrip() { using (Apk apk = new Apk(baseAPKPath)) { byte[] data = apk.ReadEntireEntry(apk.TextFile()); SerializedAssets textAssets = SerializedAssets.FromBytes(data, apk.version); var aotext = textAssets.GetAssetAt(1); TextAssetData ta = aotext.data as TextAssetData; string oldScript = ta.script; var segments = ta.ReadLocaleText(); ta.WriteLocaleText(segments); Assert.Equal(oldScript, ta.script); } }
public async Task <bool> LaunchActivity(string device, Apk apk) { try { var result = await RunAsync("-s {0} shell am start -n \"{1}/{2}\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", device, apk.PackageName, apk.LaunchableActivity); return(result.ToLower().Contains("error")); } catch (Exception e) { Debug.Print("Adb.LunchActivity: {0}", e.Message); return(false); } }
static void UpdateColors(Apk apk, CustomColors colors, InvocationResult res) { SerializedAssets colorAssets = SerializedAssets.FromBytes( apk.ReadEntireEntry(apk.ColorsFile()), apk.version); // There should only be one color manager var colorManager = colorAssets.FindScript <ColorManager>(cm => true); colorManager.UpdateColor(colorAssets, colors.colorA, ColorManager.ColorSide.A); colorManager.UpdateColor(colorAssets, colors.colorB, ColorManager.ColorSide.B); apk.ReplaceAssetsFile(apk.ColorsFile(), colorAssets.ToBytes()); res.newColors = new CustomColors() { colorA = colorManager.colorA.FollowToScript <SimpleColor>(colorAssets), colorB = colorManager.colorB.FollowToScript <SimpleColor>(colorAssets), }; }
private void SignCommand_Execute() { if (Apk?.NewApk == null) { return; } if (!Apk.HasJava()) { MessBox.ShowDial(StringResources.JavaNotFoundError, StringResources.ErrorLower); return; } bool success = false; LoadingWindow.ShowWindow( beforeStarting: () => IsBusy = true, threadActions: source => success = Apk.Sign(), finishActions: () => { IsBusy = false; VisLog(LogLine); VisLog(success ? StringResources.Finished : StringResources.ErrorWhileSigning); if (success) { string message = $"{StringResources.FileIsSituatedIn} {Apk.SignedApk}"; VisLog(message); string dir = Path.GetDirectoryName(Apk.SignedApk); if (dir != null && MessBox.ShowDial(message, StringResources.Finished, MessBox.MessageButtons.OK, StringResources.Open) == StringResources.Open) { Process.Start(dir); } } }, cancelVisibility: Visibility.Collapsed, ownerWindow: _window ); VisLog(string.Format("{0}{1}Signing...{1}{0}", LogLine, Environment.NewLine)); }
static void EnsureInstalled( Apk apk, SerializedAssets assets, HashSet <string> existingLevels, InvocationResult res, Dictionary <string, string> ensureInstalled ) { LevelCollectionBehaviorData extrasCollection = assets.FindExtrasLevelCollection(); foreach (KeyValuePair <string, string> entry in ensureInstalled) { string levelID = entry.Key; string levelFolder = entry.Value; try { JsonLevel level = JsonLevel.LoadFromFolder(levelFolder); if (existingLevels.Contains(levelID)) { res.installSkipped.Add(levelID, "Present"); } else { // We use transactions here so if these throw // an exception, which happens when levels are // invalid, then it doesn't modify the APK in // any way that might screw things up later. var assetsTxn = new SerializedAssets.Transaction(assets); var apkTxn = new Apk.Transaction(); AssetPtr levelPtr = level.AddToAssets(assetsTxn, apkTxn, levelID); // Danger should be over, nothing here should fail assetsTxn.ApplyTo(assets); extrasCollection.levels.Add(levelPtr); apkTxn.ApplyTo(apk); existingLevels.Add(levelID); res.installedLevels.Add(levelID); } } catch (FileNotFoundException e) { res.installSkipped.Add(levelID, $"Missing file referenced by level: {e.FileName}"); } catch (JsonReaderException e) { res.installSkipped.Add(levelID, $"Invalid level JSON: {e.Message}"); } } }
static InvocationResult RunInvocation(Invocation inv) { InvocationResult res = new InvocationResult(); try { using (Apk apk = new Apk(inv.apkPath)) { if (inv.patchSignatureCheck) { apk.PatchSignatureCheck(); res.didSignatureCheckPatch = true; } SerializedAssets mainAssets = SerializedAssets.FromBytes( apk.ReadEntireEntry(apk.MainAssetsFile()), apk.version ); SyncLevels(apk, mainAssets, inv, res); apk.ReplaceAssetsFile(apk.MainAssetsFile(), mainAssets.ToBytes()); if (inv.colors != null) { UpdateColors(apk, inv.colors, res); } if (inv.replaceText != null) { UpdateText(apk, inv.replaceText, res); } apk.Save(); } if (inv.sign) { Signer.Sign(inv.apkPath); res.didSign = true; } } catch (Exception e) { res.error = e.ToString(); } return(res); }
public void TestBigFile() { using (Apk apk = new Apk(baseAPKPath)) { byte[] data = apk.ReadEntireEntry(Apk.MainAssetsFile); var assets = TestRoundTrips(data, "big"); var existing = assets.ExistingLevelIDs(); Assert.NotEmpty(existing); Assert.False(existing.Contains("BUBBLETEA"), "Run tests on a non-patched APK"); JsonLevel level = JsonLevel.LoadFromFolder(repoPath("testdata/bubble_tea_song/")); // pass null as the apk so it doesn't get modified AssetPtr levelPtr = level.AddToAssets(assets, null); LevelCollectionBehaviorData extrasCollection = assets.FindExtrasLevelCollection(); extrasCollection.levels.Add(levelPtr); byte[] outData = assets.ToBytes(); File.WriteAllBytes($"../../../../testoutput/bubble_tea_mod.asset", outData); } }
static InvocationResult RunInvocation(Invocation inv) { InvocationResult res = new InvocationResult(); try { using (Apk apk = new Apk(inv.apkPath)) { if (inv.patchSignatureCheck) { apk.PatchSignatureCheck(); res.didSignatureCheckPatch = true; } byte[] data = apk.ReadEntireEntry(apk.MainAssetsFile()); SerializedAssets assets = SerializedAssets.FromBytes(data, apk.version); HashSet <string> existingLevels = assets.ExistingLevelIDs(); if (inv.ensureInstalled.Count > 0) { Program.EnsureInstalled(apk, assets, existingLevels, res, inv.ensureInstalled); byte[] outData = assets.ToBytes(); apk.ReplaceAssetsFile(apk.MainAssetsFile(), outData); } res.presentLevels = existingLevels.ToList(); apk.Save(); } if (inv.sign) { Signer.Sign(inv.apkPath); res.didSign = true; } } catch (Exception e) { res.error = e.ToString(); } return(res); }
static void Main(string[] args) { if (args.Length < 1) { Console.WriteLine("arguments: pathToAPKFileToModify levelFolders..."); return; } string apkPath = args[0]; using (Apk apk = new Apk(apkPath)) { apk.PatchSignatureCheck(); byte[] data = apk.ReadEntireEntry(Apk.MainAssetsFile); SerializedAssets assets = SerializedAssets.FromBytes(data); HashSet <string> existingLevels = assets.ExistingLevelIDs(); LevelCollectionBehaviorData extrasCollection = assets.FindExtrasLevelCollection(); for (int i = 1; i < args.Length; i++) { Utils.FindLevels(args[i], levelFolder => { JsonLevel level = JsonLevel.LoadFromFolder(levelFolder); string levelID = level.LevelID(); if (existingLevels.Contains(levelID)) { Console.WriteLine($"Present: {level._songName}"); } else { Console.WriteLine($"Adding: {level._songName}"); AssetPtr levelPtr = level.AddToAssets(assets, apk); extrasCollection.levels.Add(levelPtr); existingLevels.Add(levelID); } }); } byte[] outData = assets.ToBytes(); apk.ReplaceAssetsFile(Apk.MainAssetsFile, outData); } }
private void UploadObbFiles(AndroidPublisherService service, AppEdit edit, Apk apk, IAndroidDistributionSettings configs, string[] obbs) { foreach (var obbPath in obbs) { var upload = service.Edits.Expansionfiles.Upload( configs.PackageName, edit.Id, apk.VersionCode.Value, EditsResource.ExpansionfilesResource.UploadMediaUpload.ExpansionFileTypeEnum.Main, new FileStream(obbPath, FileMode.Open), "application/octet-stream" ); Debug.Log($"Starting Uploading Obb:{obbPath}"); upload.ResponseReceived += response => { if (response == null) { throw new Exception("Failed Upload " + obbPath); } else { Debug.Log("Success Upload " + obbPath); } }; var result = upload.Upload(); if (result.Exception != null) { throw new Exception("Error: " + result.Exception.Message); } if (result.Status != UploadStatus.Completed) { throw new Exception("Obb not uploaded"); } Debug.Log($"Finish Uploading Obb:{obbPath}"); } }
static void UpdateText(Apk apk, Dictionary <string, string> replaceText, InvocationResult res) { SerializedAssets textAssets = SerializedAssets.FromBytes(apk.ReadEntireEntry(apk.TextFile()), apk.version); var aotext = textAssets.GetAssetAt(1); TextAssetData ta = aotext.data as TextAssetData; var segments = ta.ReadLocaleText(); TextAssetData.ApplyWatermark(segments); foreach (var entry in replaceText) { Dictionary <string, string> value; if (!segments.TryGetValue(entry.Key, out value)) { continue; } value["ENGLISH"] = entry.Value; } ta.WriteLocaleText(segments); apk.ReplaceAssetsFile(apk.TextFile(), textAssets.ToBytes()); res.didReplaceText = true; }
public void TestBigFile() { using (Apk apk = new Apk(baseAPKPath)) { byte[] data = apk.ReadEntireEntry(Apk.MainAssetsFile); var assets = TestRoundTrips(data, "big"); var existing = assets.ExistingLevelIDs(); Assert.NotEmpty(existing); Assert.False(existing.Contains("BUBBLETEA"), "Run tests on a non-patched APK"); JsonLevel level = JsonLevel.LoadFromFolder(repoPath("testdata/bubble_tea_song/")); var assetsTxn = new SerializedAssets.Transaction(assets); var apkTxn = new Apk.Transaction(); AssetPtr levelPtr = level.AddToAssets(assetsTxn, apkTxn, level.GenerateBasicLevelID()); assetsTxn.ApplyTo(assets); // don't apply apkTxn so our tests don't modify the APK LevelCollectionBehaviorData extrasCollection = assets.FindExtrasLevelCollection(); extrasCollection.levels.Add(levelPtr); byte[] outData = assets.ToBytes(); File.WriteAllBytes($"../../../../testoutput/bubble_tea_mod.asset", outData); } }
public AdbWindow(Apk apk) { InitializeComponent(); // make all flyout width same as root foreach (Flyout flayout in Flyouts.Items) { flayout.Width = this.Width; } // set switch in settings cfg = new Config(); swInstall.IsChecked = cfg.AutoInstall(); swUninstall.IsChecked = cfg.AutoUninstall(); swClose.IsChecked = cfg.AutoClose(); // define adb adb = new Adb(); adb.OnProcess += (value) => ShowLoading(value); adb.OutputDataReceived += (msg) => CommandOutput_Insert(msg, false); adb.ErrorDataReceived += (msg) => CommandOutput_Insert(msg, true); this.apk = apk; }
private void OnUploadResponce(Apk apk, AndroidPublisherService androidPublisherService, IAndroidDistributionSettings configs, AppEdit edit) { if (apk == null) { return; } var track = androidPublisherService.LoadTrackBranch(configs, edit); track.UpdateTrackInformation(apk.VersionCode, configs); //Verify if exist any obb var needUploadExtensionsFiles = CheckIfNeedProcessObb(configs, out string[] obbs); if (needUploadExtensionsFiles) { UploadObbFiles(androidPublisherService, edit, apk, configs, obbs); } var updatedTrack = androidPublisherService.Edits.Tracks .Update(track, configs.PackageName, edit.Id, track.TrackValue).Execute(); Debug.Log("Track " + updatedTrack.TrackValue + " has been updated."); }
// ReSharper disable once UnusedMethodReturnValue.Local private bool UploadApk(string apkFileName, string expansionFileName, string track, List <UpdateInfo> apkChanges) { if (_serviceThread != null) { return(false); } if (!File.Exists(apkFileName)) { UpdateStatus("Apk file not existing"); return(false); } if (!string.IsNullOrEmpty(expansionFileName) && !File.Exists(expansionFileName)) { UpdateStatus("Expansion file not existing"); return(false); } UpdateStatus(string.Empty); _cts = new CancellationTokenSource(); _serviceThread = new Thread(async() => { UpdateStatus(string.Empty); StringBuilder sb = new StringBuilder(); try { if (apkChanges != null) { sb.Append("Changes info for languages: "); foreach (UpdateInfo updateInfo in apkChanges) { sb.Append($"{updateInfo.Language} "); } sb.AppendLine(); UpdateStatus(sb.ToString()); } UserCredential credential = await GetCredatials(); using (AndroidPublisherService service = new AndroidPublisherService(GetInitializer(credential))) { EditsResource edits = service.Edits; EditsResource.InsertRequest editRequest = edits.Insert(null, PackageName); AppEdit appEdit = await editRequest.ExecuteAsync(_cts.Token); bool reuseExpansion = false; ExpansionInfo expansionInfo = await GetNewestExpansionFile(edits, appEdit); if (expansionInfo != null) { sb.AppendLine($"Latest expansion: apk version={expansionInfo.ApkVersion}, expansion version={expansionInfo.ExpansionVersion}, size={expansionInfo.FileSize}"); if (!string.IsNullOrEmpty(expansionFileName)) { FileInfo fileInfo = new FileInfo(expansionFileName); if (fileInfo.Exists && fileInfo.Length == expansionInfo.FileSize) { sb.AppendLine("Size unchanged, reusing old expansion file"); reuseExpansion = true; } } } Apk apkUploaded = null; using (FileStream apkStream = new FileStream(apkFileName, FileMode.Open, FileAccess.Read)) { long fileLength = (apkStream.Length > 0) ? apkStream.Length : 1; EditsResource.ApksResource.UploadMediaUpload upload = edits.Apks.Upload(PackageName, appEdit.Id, apkStream, "application/vnd.android.package-archive"); upload.ChunkSize = ResumableUpload.MinimumChunkSize; upload.ProgressChanged += progress => { UpdateStatus(sb.ToString() + $"Apk progress: {100 * progress.BytesSent / fileLength}%"); }; upload.ResponseReceived += apk => { apkUploaded = apk; }; IUploadProgress uploadProgress = await upload.UploadAsync(_cts.Token); sb.AppendLine($"Upload status: {uploadProgress.Status.ToString()}"); UpdateStatus(sb.ToString()); if (uploadProgress.Exception != null) { throw uploadProgress.Exception; } } int?versionCode = apkUploaded?.VersionCode; if (!versionCode.HasValue) { throw new Exception("No apk version code"); } sb.AppendLine($"Version code uploaded: {versionCode.Value}"); UpdateStatus(sb.ToString()); if (!string.IsNullOrEmpty(expansionFileName) && !reuseExpansion) { using (FileStream expansionStream = new FileStream(expansionFileName, FileMode.Open, FileAccess.Read)) { long fileLength = (expansionStream.Length > 0) ? expansionStream.Length : 1; EditsResource.ExpansionfilesResource.UploadMediaUpload upload = edits.Expansionfiles.Upload(PackageName, appEdit.Id, versionCode.Value, EditsResource.ExpansionfilesResource.UploadMediaUpload.ExpansionFileTypeEnum.Main, expansionStream, "application/octet-stream"); upload.ChunkSize = ResumableUpload.MinimumChunkSize; upload.ProgressChanged += progress => { UpdateStatus(sb.ToString() + $"Expansion progress: {100 * progress.BytesSent / fileLength}%"); }; IUploadProgress uploadProgress = await upload.UploadAsync(_cts.Token); sb.AppendLine($"Upload status: {uploadProgress.Status.ToString()}"); UpdateStatus(sb.ToString()); if (uploadProgress.Exception != null) { throw uploadProgress.Exception; } } } else { if (expansionInfo != null) { ExpansionFile expansionRef = new ExpansionFile { ReferencesVersion = expansionInfo.ExpansionVersion }; await edits.Expansionfiles.Update(expansionRef, PackageName, appEdit.Id, versionCode.Value, EditsResource.ExpansionfilesResource.UpdateRequest.ExpansionFileTypeEnum.Main).ExecuteAsync(_cts.Token); sb.AppendLine($"Expansion version {expansionInfo.ExpansionVersion} assigned"); } else { sb.AppendLine("No existing expansion found!"); } } Track updateTrack = new Track { VersionCodes = new List <int?> { versionCode.Value } }; EditsResource.TracksResource.UpdateRequest updateRequest = edits.Tracks.Update(updateTrack, PackageName, appEdit.Id, track); Track updatedTrack = await updateRequest.ExecuteAsync(_cts.Token); sb.AppendLine($"Track updated: {updatedTrack.TrackValue}"); UpdateStatus(sb.ToString()); if (apkChanges != null) { foreach (UpdateInfo updateInfo in apkChanges) { ApkListing apkListing = new ApkListing { RecentChanges = updateInfo.Changes }; await edits.Apklistings.Update(apkListing, PackageName, appEdit.Id, versionCode.Value, updateInfo.Language).ExecuteAsync(_cts.Token); sb.AppendLine($"Changes for language {updateInfo.Language} updated"); UpdateStatus(sb.ToString()); } } EditsResource.CommitRequest commitRequest = edits.Commit(PackageName, appEdit.Id); AppEdit appEditCommit = await commitRequest.ExecuteAsync(_cts.Token); sb.AppendLine($"App edit committed: {appEditCommit.Id}"); UpdateStatus(sb.ToString()); } } catch (Exception e) { sb.AppendLine($"Exception: {e.Message}"); } finally { _serviceThread = null; _cts.Dispose(); UpdateStatus(sb.ToString()); } }); _serviceThread.Start(); return(true); }
static void Main(string[] args) { if (args.Length < 1) { Console.WriteLine("arguments: pathToAPKFileToModify levelFolders..."); return; } string apkPath = args[0]; using (Apk apk = new Apk(apkPath)) { apk.PatchSignatureCheck(); byte[] data = apk.ReadEntireEntry(apk.MainAssetsFile()); SerializedAssets assets = SerializedAssets.FromBytes(data, apk.version); HashSet <string> existingLevels = assets.ExistingLevelIDs(); LevelCollectionBehaviorData extrasCollection = assets.FindExtrasLevelCollection(); for (int i = 1; i < args.Length; i++) { Utils.FindLevels(args[i], levelFolder => { try { JsonLevel level = JsonLevel.LoadFromFolder(levelFolder); string levelID = level.GenerateBasicLevelID(); if (existingLevels.Contains(levelID)) { Console.WriteLine($"Present: {level._songName}"); } else { Console.WriteLine($"Adding: {level._songName}"); // We use transactions here so if these throw // an exception, which happens when levels are // invalid, then it doesn't modify the APK in // any way that might screw things up later. var assetsTxn = new SerializedAssets.Transaction(assets); var apkTxn = new Apk.Transaction(); AssetPtr levelPtr = level.AddToAssets(assetsTxn, apkTxn, levelID); // Danger should be over, nothing here should fail assetsTxn.ApplyTo(assets); extrasCollection.levels.Add(levelPtr); existingLevels.Add(levelID); apkTxn.ApplyTo(apk); } } catch (FileNotFoundException e) { Console.WriteLine("[SKIPPING] Missing file referenced by level: {0}", e.FileName); } catch (JsonReaderException e) { Console.WriteLine("[SKIPPING] Invalid level JSON: {0}", e.Message); } }); } byte[] outData = assets.ToBytes(); apk.ReplaceAssetsFile(apk.MainAssetsFile(), outData); apk.Save(); } Console.WriteLine("Signing APK..."); Signer.Sign(apkPath); }
static void SyncLevels( Apk apk, SerializedAssets mainAssets, Invocation inv, InvocationResult res ) { if (inv.levels == null || inv.packs == null) { throw new ApplicationException("Either the 'levels' or 'packs' key is missing. Note the 'levels' key changed names from 'ensureInstalled' in the new version."); } Dictionary <string, ulong> existingLevels = mainAssets.FindLevels(); ulong maxBasePathID = mainAssets.MainAssetsMaxBaseGamePath(); // === Load root level pack SerializedAssets rootPackAssets = SerializedAssets.FromBytes(apk.ReadEntireEntry(apk.RootPackFile()), apk.version); string mainFileName = apk.MainAssetsFileName(); int mainFileI = rootPackAssets.externals.FindIndex(e => e.pathName == mainFileName) + 1; BeatmapLevelPackCollection rootLevelPack = rootPackAssets.FindMainLevelPackCollection(); // Might be null if we're on a version older than v1.1.0 AlwaysOwnedBehaviorData alwaysOwned = mainAssets.FindScript <AlwaysOwnedBehaviorData>(x => true); // === Remove existing custom packs rootLevelPack.beatmapLevelPacks.RemoveAll(ptr => ptr.fileID == mainFileI && ptr.pathID > maxBasePathID); if (alwaysOwned != null) { alwaysOwned.levelPacks.RemoveAll(ptr => ptr.fileID == 0 && ptr.pathID > maxBasePathID); } LevelPackBehaviorData.RemoveCustomPacksFromEnd(mainAssets); // === Remove old-school custom levels from Extras pack var extrasCollection = mainAssets.FindExtrasLevelCollection(); extrasCollection.levels.RemoveAll(ptr => ptr.pathID > maxBasePathID); // === Remove existing levels var toRemove = new HashSet <string>(); foreach (var entry in existingLevels) { if (inv.levels.ContainsKey(entry.Key)) { continue; // requested } if (entry.Value <= maxBasePathID) { continue; // base game level } toRemove.Add(entry.Key); } foreach (string levelID in toRemove) { var ao = mainAssets.GetAssetObjectFromScript <LevelBehaviorData>(p => p.levelID == levelID); var apkTxn = new Apk.Transaction(); Utils.RemoveLevel(mainAssets, ao, apkTxn); apkTxn.ApplyTo(apk); } res.removedLevels = toRemove.ToList(); // === Install new levels var toInstall = new HashSet <string>(); foreach (var entry in inv.levels) { if (existingLevels.ContainsKey(entry.Key)) { continue; // already installed } toInstall.Add(entry.Key); } Program.Install(apk, mainAssets, toInstall, res, inv.levels); // === Create new custom packs Dictionary <string, ulong> availableLevels = mainAssets.FindLevels(); foreach (LevelPack pack in inv.packs) { if (pack.name == null || pack.id == null || pack.levelIDs == null) { throw new ApplicationException("Packs require name, id and levelIDs list"); } var txn = new SerializedAssets.Transaction(mainAssets); CustomPackInfo info = LevelPackBehaviorData.CreateCustomPack( txn, pack.id, pack.name, pack.coverImagePath ); txn.ApplyTo(mainAssets); var customCollection = info.collection.FollowToScript <LevelCollectionBehaviorData>(mainAssets); foreach (string levelID in pack.levelIDs) { ulong levelPathID; if (!availableLevels.TryGetValue(levelID, out levelPathID)) { res.missingFromPacks.Add(levelID); continue; } customCollection.levels.Add(new AssetPtr(0, levelPathID)); } rootLevelPack.beatmapLevelPacks.Add(new AssetPtr(mainFileI, info.pack.pathID)); if (alwaysOwned != null) { alwaysOwned.levelPacks.Add(info.pack); } } res.presentLevels = availableLevels.Keys.ToList(); apk.ReplaceAssetsFile(apk.RootPackFile(), rootPackAssets.ToBytes()); }
static void Main(string[] args) { if (args.Length < 1) { Console.WriteLine("arguments: pathToAPKFileToModify [-r removeSongs] levelFolders..."); return; } bool removeSongs = false; if (args.Contains("-r") || args.Contains("removeSongs")) { removeSongs = true; } bool replaceExtras = false; if (args.Contains("-e")) { replaceExtras = true; } string apkPath = args[0]; using (Apk apk = new Apk(apkPath)) { apk.PatchSignatureCheck(); byte[] data = apk.ReadEntireEntry(Apk.MainAssetsFile); SerializedAssets assets = SerializedAssets.FromBytes(data); string colorPath = "assets/bin/Data/sharedassets1.assets"; SerializedAssets colorAssets = SerializedAssets.FromBytes(apk.ReadEntireEntry(colorPath)); //string textAssetsPath = "assets/bin/Data/c4dc0d059266d8d47862f46460cf8f31"; string textAssetsPath = "assets/bin/Data/231368cb9c1d5dd43988f2a85226e7d7"; SerializedAssets textAssets = SerializedAssets.FromBytes(apk.ReadEntireEntry(textAssetsPath)); HashSet <string> existingLevels = assets.ExistingLevelIDs(); LevelCollectionBehaviorData customCollection = assets.FindCustomLevelCollection(); LevelPackBehaviorData customPack = assets.FindCustomLevelPack(); ulong customPackPathID = assets.GetAssetObjectFromScript <LevelPackBehaviorData>(mob => mob.name == "CustomLevelPack", b => true).pathID; for (int i = 1; i < args.Length; i++) { if (args[i] == "-r" || args[i] == "removeSongs" || args[i] == "-e") { continue; } if (args[i] == "-t") { if (i + 2 >= args.Length) { // There is not enough data after the text // Reset it. //continue; } var ao = textAssets.GetAssetAt(1); TextAssetAssetData ta = ao.data as TextAssetAssetData; string key = args[i + 1].ToUpper(); var segments = Utils.ReadLocaleText(ta.script, new List <char>() { ',', ',', '\n' }); //segments.ToList().ForEach(a => Console.Write(a.Trim() + ",")); List <string> value; if (!segments.TryGetValue(key.Trim(), out value)) { Console.WriteLine($"[ERROR] Could not find key: {key} in text!"); } Console.WriteLine($"Found key at index: {key.Trim()} with value: {value[value.Count - 1]}"); segments[key.Trim()][value.Count - 1] = args[i + 2]; Console.WriteLine($"New value: {args[i + 2]}"); Utils.ApplyWatermark(segments); ta.script = Utils.WriteLocaleText(segments, new List <char>() { ',', ',', '\n' }); i += 2; apk.ReplaceAssetsFile(textAssetsPath, textAssets.ToBytes()); //Console.WriteLine((a.data as TextAsset).script); continue; } if (args[i] == "-c1" || args[i] == "-c2") { if (i + 1 >= args.Length) { // There is nothing after the color // Reset it. Utils.ResetColors(colorAssets); apk.ReplaceAssetsFile(colorPath, colorAssets.ToBytes()); continue; } if (!args[i + 1].StartsWith("(")) { // Reset it. Utils.ResetColors(colorAssets); apk.ReplaceAssetsFile(colorPath, colorAssets.ToBytes()); continue; } if (i + 4 >= args.Length) { Console.WriteLine($"[ERROR] Cannot parse color, not enough colors! Please copy-paste a series of floats"); i += 4; continue; } SimpleColor c = new SimpleColor { r = Convert.ToSingle(args[i + 1].Split(',')[0].Replace('(', '0')), g = Convert.ToSingle(args[i + 2].Split(',')[0].Replace('(', '0')), b = Convert.ToSingle(args[i + 3].Split(',')[0].Replace(')', '0')), a = Convert.ToSingle(args[i + 4].Split(',')[0].Replace(')', '.')) }; ColorManager dat = Utils.CreateColor(colorAssets, c); var ptr = colorAssets.AppendAsset(new MonoBehaviorAssetData() { data = c, name = "CustomColor" + args[i][args[i].Length - 1], script = new AssetPtr(1, SimpleColor.PathID), }); Console.WriteLine($"Created new CustomColor for colorA at PathID: {ptr.pathID}"); if (args[i] == "-c1") { dat.colorA = ptr; } else { dat.colorB = ptr; } apk.ReplaceAssetsFile(colorPath, colorAssets.ToBytes()); i += 4; continue; } if (args[i] == "-g") { string path = "assets/bin/Data/level11"; SerializedAssets a = SerializedAssets.FromBytes(apk.ReadEntireEntry(path)); var gameobject = a.FindGameObject("LeftSaber"); var script = gameobject.components[4].FollowToScript <Saber>(a); Console.WriteLine($"GameObject: {gameobject}"); foreach (AssetPtr p in gameobject.components) { Console.WriteLine($"Component: {p.pathID} followed: {p.Follow(a)}"); } Console.WriteLine($"Left saber script: {script}"); // Find all objects that have the GameObject: LeftSaber (pathID = 20, fileID = 0 (142)) continue; } if (args[i] == "-s") { string cusomCoverFile = args[i + 1]; try { Texture2DAssetData dat = assets.GetAssetAt(14).data as Texture2DAssetData; //assets.SetAssetAt(14, dat); var ptr = assets.AppendAsset(Texture2DAssetData.CoverFromImageFile(args[i + 1], "CustomSongs", true)); Console.WriteLine($"Added Texture at PathID: {ptr.pathID} with new Texture2D from file: {args[i + 1]}"); var sPtr = assets.AppendAsset(Utils.CreateSprite(assets, ptr)); Console.WriteLine($"Added Sprite at PathID: {sPtr.pathID}!"); customPack.coverImage = sPtr; } catch (FileNotFoundException) { Console.WriteLine($"[ERROR] Custom cover file does not exist: {args[i+1]}"); } i++; continue; } Utils.FindLevels(args[i], levelFolder => { try { JsonLevel level = JsonLevel.LoadFromFolder(levelFolder); string levelID = level.GenerateBasicLevelID(); var apkTxn = new Apk.Transaction(); if (existingLevels.Contains(levelID)) { if (removeSongs) { // Currently does not handle transactions Console.WriteLine($"Removing: {level._songName}"); existingLevels.Remove(levelID); var l = assets.GetLevelMatching(levelID); var ao = assets.GetAssetObjectFromScript <LevelBehaviorData>(p => p.levelID == l.levelID); ulong lastLegitPathID = 201; // Currently, this removes all songs matching the song // the very first time it runs customCollection.levels.RemoveAll(ptr => ptr.pathID > lastLegitPathID && ao.pathID == ptr.pathID); foreach (string s in l.OwnedFiles(assets)) { if (apk != null) { apk.RemoveFileAt($"assets/bin/Data/{s}"); } } Utils.RemoveLevel(assets, l); apkTxn.ApplyTo(apk); } else { Console.WriteLine($"Present: {level._songName}"); } } else { Console.WriteLine($"Adding: {level._songName}"); // We use transactions here so if these throw // an exception, which happens when levels are // invalid, then it doesn't modify the APK in // any way that might screw things up later. var assetsTxn = new SerializedAssets.Transaction(assets); AssetPtr levelPtr = level.AddToAssets(assetsTxn, apkTxn, levelID); // Danger should be over, nothing here should fail assetsTxn.ApplyTo(assets); customCollection.levels.Add(levelPtr); existingLevels.Add(levelID); apkTxn.ApplyTo(apk); } } catch (FileNotFoundException e) { Console.WriteLine("[SKIPPING] Missing file referenced by level: {0}", e.FileName); } catch (JsonReaderException e) { Console.WriteLine("[SKIPPING] Invalid level JSON: {0}", e.Message); } }); } byte[] outData = assets.ToBytes(); apk.ReplaceAssetsFile(Apk.MainAssetsFile, outData); string mainPackFile = "assets/bin/Data/sharedassets19.assets"; SerializedAssets mainPackAssets = SerializedAssets.FromBytes(apk.ReadEntireEntry(mainPackFile)); // Modify image to be CustomLevelPack image? //customPack.coverImage = new AssetPtr(assets.externals.FindIndex(e => e.pathName == "sharedassets19.assets")) // Adds custom pack to the set of all packs int fileI = mainPackAssets.externals.FindIndex(e => e.pathName == "sharedassets17.assets") + 1; Console.WriteLine($"Found sharedassets17.assets at FileID: {fileI}"); var mainLevelPack = mainPackAssets.FindMainLevelPackCollection(); var pointerPacks = mainLevelPack.beatmapLevelPacks[mainLevelPack.beatmapLevelPacks.Count - 1]; Console.WriteLine($"Original last pack FileID: {pointerPacks.fileID} PathID: {pointerPacks.pathID}"); if (!mainLevelPack.beatmapLevelPacks.Any(ptr => ptr.fileID == fileI && ptr.pathID == customPackPathID)) { Console.WriteLine($"Added CustomLevelPack to {mainPackFile}"); if (replaceExtras) { Console.WriteLine("Replacing ExtrasPack!"); mainLevelPack.beatmapLevelPacks[2] = new AssetPtr(fileI, customPackPathID); } else { Console.WriteLine("Adding as new Pack!"); mainLevelPack.beatmapLevelPacks.Add(new AssetPtr(fileI, customPackPathID)); } } pointerPacks = mainLevelPack.beatmapLevelPacks[mainLevelPack.beatmapLevelPacks.Count - 1]; Console.WriteLine($"New last pack FileID: {pointerPacks.fileID} PathID: {pointerPacks.pathID}"); apk.ReplaceAssetsFile(mainPackFile, mainPackAssets.ToBytes()); Console.WriteLine("Complete!"); } Console.WriteLine("Signing APK..."); Signer.Sign(apkPath); }