From e03a6a4b7e0994ca431a49f07e27a51dec8648b6 Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Sun, 16 Nov 2025 16:06:39 +0000 Subject: [PATCH 1/2] profile importing/exporting --- .../gui/tabs/builtin/ProfilesTab.java | 171 +++++++++++++++++- .../systems/profiles/Profile.java | 5 +- .../systems/profiles/Profiles.java | 18 ++ 3 files changed, 190 insertions(+), 4 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java b/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java index ba193f8124..6944d6d6f5 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.gui.tabs.builtin; +import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.gui.GuiTheme; import meteordevelopment.meteorclient.gui.WindowScreen; import meteordevelopment.meteorclient.gui.renderer.GuiRenderer; @@ -12,22 +13,51 @@ import meteordevelopment.meteorclient.gui.tabs.TabScreen; import meteordevelopment.meteorclient.gui.tabs.WindowTabScreen; import meteordevelopment.meteorclient.gui.widgets.containers.WContainer; +import meteordevelopment.meteorclient.gui.widgets.containers.WHorizontalList; import meteordevelopment.meteorclient.gui.widgets.containers.WTable; import meteordevelopment.meteorclient.gui.widgets.pressable.WButton; +import meteordevelopment.meteorclient.gui.widgets.pressable.WCheckbox; import meteordevelopment.meteorclient.gui.widgets.pressable.WConfirmedButton; import meteordevelopment.meteorclient.gui.widgets.pressable.WConfirmedMinus; +import meteordevelopment.meteorclient.settings.Setting; import meteordevelopment.meteorclient.systems.profiles.Profile; import meteordevelopment.meteorclient.systems.profiles.Profiles; import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.misc.NbtUtils; +import meteordevelopment.meteorclient.utils.render.prompts.OkPrompt; import net.minecraft.client.gui.screen.Screen; - +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtIo; +import org.lwjgl.BufferUtils; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.tinyfd.TinyFileDialogs; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static meteordevelopment.meteorclient.MeteorClient.mc; public class ProfilesTab extends Tab { + private static final PointerBuffer filters; + + static { + filters = BufferUtils.createPointerBuffer(1); + + ByteBuffer pngFilter = MemoryUtil.memASCII("*.nbt"); + + filters.put(pngFilter); + filters.rewind(); + } + public ProfilesTab() { super("Profiles"); } @@ -54,9 +84,31 @@ public void initWidgets() { add(theme.horizontalSeparator()).expandX(); + WHorizontalList l = add(theme.horizontalList()).expandX().widget(); + // Create - WButton create = add(theme.button("Create")).expandX().widget(); + WButton create = l.add(theme.button("Create")).expandX().widget(); + create.tooltip = "Create new profile"; create.action = () -> mc.setScreen(new EditProfileScreen(theme, null, this::reload)); + + // Import + WButton importBtn = l.add(theme.button("Import")).expandX().widget(); + importBtn.tooltip = "Import profile"; + importBtn.action = () -> { + try { + Profile imported = importProfile(); + if (imported != null) MeteorClient.LOG.info("Successfully imported profile '{}'.", imported.name.get()); + reload(); + } catch (IOException e) { + MeteorClient.LOG.error("Error importing profile", e); + OkPrompt.create() + .title("Failure importing profile") + .message("There was an error importing the profile.") + .message("Error: %d", e.getMessage()) + .dontShowAgainCheckboxVisible(false) + .show(); + } + }; } private void initTable(WTable table) { @@ -73,6 +125,9 @@ private void initTable(WTable table) { WButton load = table.add(theme.button("Load")).widget(); load.action = profile::load; + WButton export = table.add(theme.button("Export")).widget(); + export.action = () -> mc.setScreen(new ExportProfileScreen(theme, profile)); + WButton edit = table.add(theme.button(GuiRenderer.EDIT)).widget(); edit.action = () -> mc.setScreen(new EditProfileScreen(theme, profile, this::reload)); @@ -86,6 +141,44 @@ private void initTable(WTable table) { } } + private Profile importProfile() throws IOException { + String file = TinyFileDialogs.tinyfd_openFileDialog("Select profile to import", null, filters, null, false); + if (file == null) return null; + File profileFile = new File(file); + + NbtCompound nbt = NbtIo.read(profileFile.toPath()); + + Profile p = new Profile(); + p.name.set(nbt.getString("name", profileFile.getName())); + //noinspection ResultOfMethodCallIgnored + p.getFile().mkdirs(); + + nbt.remove("name"); + for (Map.Entry entry : nbt.entrySet()) { + String filename = entry.getKey(); + // super scuffed + if (filename.endsWith(".nbt")) p.waypoints.set(true); + else { + Setting s = p.settings.get(filename, Boolean.class); + if (s == null) { + MeteorClient.LOG.error("Malformed imported profile setting {}", filename); + continue; + } + + s.set(true); + filename += ".nbt"; + } + + File f = new File(p.getFile(), filename); + NbtIo.write(entry.getValue(), new DataOutputStream(new FileOutputStream(f))); + } + + Profiles.get().getAll().add(p); + Profiles.get().save(); + + return p; + } + @Override public boolean toClipboard() { return NbtUtils.toClipboard(Profiles.get()); @@ -154,4 +247,78 @@ protected void onClosed() { if (action != null) action.run(); } } + + private static class ExportProfileScreen extends WindowScreen { + private final Profile profile; + + public ExportProfileScreen(GuiTheme theme, Profile profile) { + super(theme, "Export Profile"); + this.profile = profile; + } + + @Override + public void initWidgets() { + add(theme.label("Select which profile settings to export.")); + + WContainer settingsContainer = add(theme.verticalList()).expandX().minWidth(400).widget(); + + settingsContainer.add(theme.horizontalSeparator()).expandX().widget(); + + WCheckbox hud = addBool(settingsContainer, profile.settings.get("hud", Boolean.class)); + WCheckbox macros = addBool(settingsContainer, profile.settings.get("macros", Boolean.class)); + WCheckbox modules = addBool(settingsContainer, profile.settings.get("modules", Boolean.class)); + WCheckbox waypoints = addBool(settingsContainer, profile.settings.get("waypoints", Boolean.class)); + + add(theme.horizontalSeparator()).expandX().widget(); + + WButton export = add(theme.button("Export profile")).expandX().widget(); + export.action = () -> { + exportProfile(profile, hud.checked, macros.checked, modules.checked, waypoints.checked); + close(); + }; + } + + private WCheckbox addBool(WContainer container, Setting setting) { + WHorizontalList boolList = container.add(theme.horizontalList()).expandX().widget(); + boolList.add(theme.label(setting.title)).widget().tooltip = setting.description; + + WCheckbox c = theme.checkbox(setting.get()); + boolList.add(c).expandCellX().right(); + + return c; + } + + private void exportProfile(Profile profile, boolean hud, boolean macros, boolean modules, boolean waypoints) { + String path = TinyFileDialogs.tinyfd_saveFileDialog("Save profile", profile.name.get(), filters, null); + if (path == null) return; + Path p = Path.of(path.endsWith(".nbt") ? path : path + ".nbt"); + + NbtCompound nbt = new NbtCompound(); + nbt.putString("name", profile.name.get()); + + try { + for (File f : profile.getFile().listFiles()) { + // very scuffed + if (f.getName().equals("hud.nbt") && hud || + f.getName().equals("macros.nbt") && macros || + f.getName().equals("modules.nbt") && modules + ) { + nbt.put(f.getName().replace(".nbt", ""), NbtIo.read(f.toPath())); + } + else if (f.getName().endsWith(".nbt") && waypoints) + nbt.put(f.getName(), NbtIo.read(f.toPath())); + } + + NbtIo.write(nbt, p); + } catch (IOException e) { + MeteorClient.LOG.error("Error serialising profile {} to a file", profile.name.get(), e); + OkPrompt.create() + .title("Failure exporting profile") + .message("There was an error serialising or exporting the profile %d.", profile.name.get()) + .message("Error: %d", e.getMessage()) + .dontShowAgainCheckboxVisible(false) + .show(); + } + } + } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profile.java b/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profile.java index 64ee198944..8ce3aeb02f 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profile.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profile.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.systems.profiles; +import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.hud.Hud; import meteordevelopment.meteorclient.systems.macros.Macros; @@ -96,11 +97,11 @@ public void delete() { try { FileUtils.deleteDirectory(getFile()); } catch (IOException e) { - e.printStackTrace(); + MeteorClient.LOG.error("Error deleting profile {}", name.get(), e); } } - private File getFile() { + public File getFile() { return new File(Profiles.FOLDER, name.get()); } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profiles.java b/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profiles.java index c00680d4c0..0f37c0f49f 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profiles.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/profiles/Profiles.java @@ -91,6 +91,24 @@ public NbtCompound toTag() { @Override public Profiles fromTag(NbtCompound tag) { profiles = NbtUtils.listFromTag(tag.getListOrEmpty("profiles"), Profile::new); + + for (File file : FOLDER.listFiles()) { + if (file.isDirectory() && get(file.getName()) == null) { + Profile p = new Profile(); + p.name.set(file.getName()); + + boolean add = false; + for (File f : file.listFiles()) { + if (f.getName().equals("hud.nbt")) p.hud.set(add = true); + else if (f.getName().equals("macros.nbt")) p.macros.set(add = true); + else if (f.getName().equals("modules.nbt")) p.modules.set(add = true); + else if (f.getName().endsWith(".nbt")) p.waypoints.set(add = true); + } + + if (add) add(p); + } + } + return this; } } From f373524e99780b767a92e37a81525a926d6d7bf1 Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Thu, 20 Nov 2025 17:51:23 +0000 Subject: [PATCH 2/2] a bit of cleanup --- .../gui/tabs/builtin/ProfilesTab.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java b/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java index 6944d6d6f5..680e21d48c 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java @@ -156,17 +156,14 @@ private Profile importProfile() throws IOException { nbt.remove("name"); for (Map.Entry entry : nbt.entrySet()) { String filename = entry.getKey(); - // super scuffed - if (filename.endsWith(".nbt")) p.waypoints.set(true); - else { - Setting s = p.settings.get(filename, Boolean.class); - if (s == null) { - MeteorClient.LOG.error("Malformed imported profile setting {}", filename); - continue; - } - s.set(true); - filename += ".nbt"; + switch (filename) { + case "hud.nbt" -> p.hud.set(true); + case "macros.nbt" -> p.macros.set(true); + case "modules.nbt" -> p.modules.set(true); + default -> { + if (filename.endsWith(".nbt")) p.waypoints.set(true); + } } File f = new File(p.getFile(), filename); @@ -298,12 +295,11 @@ private void exportProfile(Profile profile, boolean hud, boolean macros, boolean try { for (File f : profile.getFile().listFiles()) { - // very scuffed if (f.getName().equals("hud.nbt") && hud || f.getName().equals("macros.nbt") && macros || f.getName().equals("modules.nbt") && modules ) { - nbt.put(f.getName().replace(".nbt", ""), NbtIo.read(f.toPath())); + nbt.put(f.getName(), NbtIo.read(f.toPath())); } else if (f.getName().endsWith(".nbt") && waypoints) nbt.put(f.getName(), NbtIo.read(f.toPath()));