Restoring item frames with Ledger & co

Some of the people I play with have taken to use Vanilla+ features to further enhance the experience by hiding item frames and marking them as “fixed” (meaning you cannot remove or rotate the items). We used to use the Armor Statues -datapack from VanillaTweaks, but decided to go with the mod Armor Poser this time around. This left the players without any means to manipulate the item frames.

Enter Toggle Item Frames and Wax Item Frames.

I checked out the descriptions and the issues listed on github. There were no red flags for implementing this, aside from a side note in the description:

As a side note:
As this mod is just an interface for vanilla features, if you want your invisible item frames to go back to normal after the mod is uninstalled, you’ll have to use this command:

I assumed it meant they were safe to install. I assumed wrong.

As soon as the first player joined after I had installed the mods, they noticed the item frames on their chests were missing. All of them.

That’s when I activated Ledger and started looking for references to placed item frames, so that I could restore them from the ledger.

It turns out, Ledger does not log placing of item frames, because they are entities. (If you want to create them with a vanilla command, you use /summon – not /setblock.) Ledger does log when you change them or remove them, but not when you place them.

The Solution

I started by downloading the Ledger sqlite database from the world/ -directory.

I then opened the database in an SQL administration tool so I could do more advanced searches and ended up with a nested SELECT-query to find all coordinates of item frame interaction, but only listing the latest ones and grab the NBT-data from those latest states.

# minecraft:item_frame 1440
# minecraft:glow_item_frame 1132

SELECT
    e1.x,
    e1.y,
    e1.z,
    e1.id,
    e1.block_state,
    e1.object_id,
    e1.old_object_id,
    e1.player_id
FROM
    actions e1
WHERE
    e1.id = (
        SELECT
            MAX(e2.id)
        FROM
            actions e2
        WHERE
            e2.x = e1.x AND
            e2.y = e1.y AND
            e2.z = e1.z AND
            e1.block_state is not null
    ) and (object_id = 1440 OR
        old_object_id = 1440);

The server has only been up for about a month and most people use signs for marking chests, so I only ended up with about 70 or so sets of coordinates. With a total player base of about 20, I figured that seemed about right.

I then exported the results to a spreadsheet, where I could generate /summon -commands to re-summon the item frames.

I had to do this twice – once for item frames and once for glowing item frames.

I stored the base of the Minecraft /summon -command in a separate sheet (Sheet3.A2) for reuse and concocted this monstrosity:

=Sheet3.$A$2&" "&A2&" "&B2&" "&C2&" "&E2

Once I had a command for each row, it was time to execute it.

Executing Scripts in Pterodactyl

The console in Pterodactyl does not allow for pasting of multiple rows. I’m sure this is because of a security concern, but sometimes you just want to execute a bunch of code, legitimately.

As a workaround I looked into attaching to the console through docker, but that turned out to be a dead-end. That’s when I remembered the scheduling tool.

Scheduling allows an admin to set up regular events, like taking backups or restarting the server. But it also allows for execution of multiple commands.

So I created an inactive schedule specifically for executing multiple commands and then created a Command -task. I copied all of the /summon -commands into this task and logged in. It worked like a charm; all of the commands are executed almost simultaneously as soon as I clicked Run Now.

Cleanup discoveries

I did also discover that not all item frames were affected. Specifically, only item frames that were loaded when the rogue mods were running were affected.

I also discovered that not all item frames were restored. I think the SQL-query could do with some tweaking, but it did restore the vast majority so it will have to do for now.