示例#1
0
    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);
            }
        }
    }
示例#2
0
        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}");
                }
            }
        }
示例#3
0
    /**
     * 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;
    }
示例#4
0
        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}");
                }
            }
        }
示例#5
0
        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);
            }
        }
示例#6
0
        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);
            }
        }
示例#7
0
        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));
        }
示例#8
0
        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);
            }
        }
示例#9
0
    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);
    }
示例#10
0
        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);
        }
示例#11
0
 /**
  * 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");
 }
示例#12
0
    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);
    }
示例#13
0
        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);
        }