private void GenerateLevel() { Level levelPrefab = null; try { settings.Load(); levelPrefab = CreateLevelPrefab(); JsonLevel jsonLevel = LoadJsonLevel($"level-{levelNumber}.json"); GenerateLevelLayout(levelPrefab, jsonLevel); SaveLevelPrefab(levelPrefab, jsonLevel); } catch (Exception e) { Debug.LogError(e); } finally { if (levelPrefab != null) { DestroyImmediate(levelPrefab.gameObject); } } }
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}"); } } }
/** * Builds a Level from a JsonLevel. */ private void GenerateLevelLayout(Level levelPrefab, JsonLevel jsonLevel) { AudioClip music = AssetDatabase.LoadAssetAtPath <AudioClip>($"Assets/{settings.musicFolder}/{jsonLevel.music}"); Texture2D tilemap = AssetDatabase.LoadAssetAtPath <Texture2D>($"Assets/{settings.tilemapFolder}/{jsonLevel.tilemap}"); CreateLevelLayout(levelPrefab, tilemap); jsonLevel.ApplyLevelSettings(levelPrefab); levelPrefab.music = music; }
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}"); } } }
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 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); } }
public IHttpActionResult parce() { string file = Request.Content.ReadAsStringAsync().Result; JsonLevel depth = new JsonLevel(); JObject ans = null; try { ans = JObject.Parse(file); } catch (JsonException) { var json = new { error = "Invalid JSON" }; return(Json(json)); } int count = depth.checkLevel(ans.ToString()); var data = new { levels = count }; return(Json(data)); }
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 static GeneratedLevel Generate(ref Constants constants, Level level, ref GameObject playerObject, Transform fixedParent, LevelStateFrame initialFrame, List <ObjectWithPosition> objectsWithPosition) { // Make the ceiling (fixed to camera) Vector3 ceilingPiecePos = constants.TopLeft; Vector3 floorPiecePos = constants.TopLeft; ceilingPiecePos.z = -1f; // In front of stuff floorPiecePos.z = -1f; floorPiecePos.y -= (constants.Height - 1) * constants.CelHeight; for (int i = 0; i < constants.Width; i++) { GameObject.Instantiate(level.LevelConstants.DeathCeiling, ceilingPiecePos, Quaternion.identity, fixedParent); GameObject.Instantiate(level.LevelConstants.DeathFloor, floorPiecePos, Quaternion.identity, fixedParent); ceilingPiecePos.x += constants.CelWidth; floorPiecePos.x += constants.CelWidth; } GeneratedLevel generatedLevel = new GeneratedLevel(); generatedLevel.Level = level; JsonLevel jsonLevel = JsonLevel.CreateFromJSON(level.Data.text); int[] backgroundData = jsonLevel.GetLayerData(Constants.BackgroundLayer); int[] objectsLayer = jsonLevel.GetLayerData(Constants.ObjectsLayer); int[] waterLayer = jsonLevel.GetLayerData(Constants.WaterLayer); string data = level.Data.text; Vector2 celPosition = constants.TopLeft; generatedLevel.Height = jsonLevel.height; generatedLevel.Width = jsonLevel.width; generatedLevel.Pieces = backgroundData; generatedLevel.Water = waterLayer; if (waterLayer == null) { generatedLevel.Water = new int[generatedLevel.Pieces.Length]; } int row = 0; int column = 0; celPosition.x = constants.TopLeft.x; int celCount = backgroundData.Length; for (int i = 0; i < celCount; i++) { int tile = backgroundData[i]; switch (tile) { case Constants.WallPiece: GameObject.Instantiate(level.LevelConstants.Wall, celPosition, Quaternion.identity, constants.Parent); break; case Constants.SupportPiece: GameObject.Instantiate(level.LevelConstants.WallSupport, celPosition, Quaternion.identity, constants.Parent); break; case Constants.Exit: GameObject.Instantiate(level.LevelConstants.Exit, celPosition, Quaternion.identity, constants.Parent); break; } int maybeObject = objectsLayer[i]; GameObject createdObject = null; switch (maybeObject) { case Constants.PlayerPiece: { if (!playerObject) { playerObject = GameObject.Instantiate(level.LevelConstants.Player, celPosition, Quaternion.identity, constants.Parent); } else { playerObject.transform.position = celPosition; } createdObject = playerObject; } break; case Constants.DraggableSupport: { createdObject = GameObject.Instantiate(level.LevelConstants.DraggableSupport, celPosition, Quaternion.identity, constants.Parent); } break; case Constants.Crate: { createdObject = GameObject.Instantiate(level.LevelConstants.Crate, celPosition, Quaternion.identity, constants.Parent); } break; } if (generatedLevel.Water[i] != 0) { GameObject.Instantiate(level.LevelConstants.Water, celPosition, Quaternion.identity, constants.Parent); } if (createdObject) { ObjectWithPosition pc = createdObject.GetComponent <ObjectWithPosition>(); objectsWithPosition.Add(pc); pc.X = column; pc.Y = row; } column++; if (column >= generatedLevel.Width) { column = 0; celPosition.x = constants.TopLeft.x; row++; celPosition.y -= constants.CelHeight; } else { celPosition.x += constants.CelWidth; } } // Sort objectsWithPosition.Sort( (a, b) => a.EvaluationOrder.CompareTo(b.EvaluationOrder) ); initialFrame.Objects = new ObjectLevelState[objectsWithPosition.Count]; for (int i = 0; i < objectsWithPosition.Count; i++) { initialFrame.Objects[i].Object = objectsWithPosition[i]; } return(generatedLevel); }
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); }
/** * Saves a GameObject prefab with an attached Level component. * It will be saved in the levelPrefabsFolder path found in the settings. */ private void SaveLevelPrefab(Level levelPrefab, JsonLevel jsonLevel) { PrefabUtility.SaveAsPrefabAsset(levelPrefab.gameObject, $"Assets/{settings.levelPrefabsFolder}/{jsonLevel.name}.prefab"); }
public LevelToLoad ReadLevelJSON() { try { string filePath = Path.Combine(Application.persistentDataPath, "Levels/" + levelName); //string filePath = Path.Combine(Application.dataPath, "Levels/level.json"); Debug.Log(filePath); if (File.Exists(filePath)) { string dataAsJson = File.ReadAllText(filePath); JsonLevel loadedData = JsonUtility.FromJson <JsonLevel>(dataAsJson); //Debug.Log(dataAsJson); List <Element> Elements = new List <Element>(); List <Group> Groups = new List <Group>(); List <Interactable> Interactables = new List <Interactable>(); //Debug.Log(JsonUtility.ToJson(loadedData)); foreach (var element in loadedData.elements) { Element elm = new Element(idToGameObject(element.id)); elm.Position = new Vector3(element.position.x - 20.5f, element.position.y, element.position.z - 20.5f); elm.Rotation = new Quaternion(element.rotation.x, element.rotation.y, element.rotation.z, element.rotation.w); //Debug.Log(elm.Rotation); Elements.Add(elm); } foreach (var group in loadedData.groups) { Group grp = new Group(); grp.component = new Component(); grp.component.channel = group.component.channel; grp.component.id = group.component.id; grp.component.position = new Vector3(group.component.position.x - 20.5f, group.component.position.y, group.component.position.z - 20.5f); grp.component.speed = group.component.speed; grp.pA = new Vector3((group.pA.x * 2) - 20.5f, group.pA.y * 2, (group.pA.z * 2) - 20.5f); grp.pB = new Vector3((group.pB.x * 2) - 20.5f, group.pB.y * 2, (group.pB.z * 2) - 20.5f); //Debug.Log(elm.Rotation); Groups.Add(grp); } foreach (var interactable in loadedData.interactables) { Interactable inte = new Interactable(); inte.position = new Vector3(interactable.pos.x - 20.5f, interactable.pos.y, interactable.pos.z - 20.5f); inte.channel = interactable.channel; //Debug.Log(elm.Rotation); Interactables.Add(inte); } LevelToLoad lvl = new LevelToLoad(); lvl.groups = Groups; lvl.elements = Elements; lvl.interactables = Interactables; return(lvl); } else { Debug.Log("Can't find file !"); } } catch (Exception e) { Debug.LogException(e, this); Debug.Log("Can't find designed level"); return(null); } return(null); }
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); }