Skip to content

Commit 7b96105

Browse files
authored
Profile importing and exporting (#5858)
Add a system to export profiles to a .nbt file, and import them therefrom. Provides an easy way to share and receive whole configs.
1 parent 14ff7fb commit 7b96105

3 files changed

Lines changed: 186 additions & 4 deletions

File tree

src/main/java/meteordevelopment/meteorclient/gui/tabs/builtin/ProfilesTab.java

Lines changed: 165 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,59 @@
55

66
package meteordevelopment.meteorclient.gui.tabs.builtin;
77

8+
import meteordevelopment.meteorclient.MeteorClient;
89
import meteordevelopment.meteorclient.gui.GuiTheme;
910
import meteordevelopment.meteorclient.gui.WindowScreen;
1011
import meteordevelopment.meteorclient.gui.renderer.GuiRenderer;
1112
import meteordevelopment.meteorclient.gui.tabs.Tab;
1213
import meteordevelopment.meteorclient.gui.tabs.TabScreen;
1314
import meteordevelopment.meteorclient.gui.tabs.WindowTabScreen;
1415
import meteordevelopment.meteorclient.gui.widgets.containers.WContainer;
16+
import meteordevelopment.meteorclient.gui.widgets.containers.WHorizontalList;
1517
import meteordevelopment.meteorclient.gui.widgets.containers.WTable;
1618
import meteordevelopment.meteorclient.gui.widgets.pressable.WButton;
19+
import meteordevelopment.meteorclient.gui.widgets.pressable.WCheckbox;
1720
import meteordevelopment.meteorclient.gui.widgets.pressable.WConfirmedButton;
1821
import meteordevelopment.meteorclient.gui.widgets.pressable.WConfirmedMinus;
22+
import meteordevelopment.meteorclient.settings.Setting;
1923
import meteordevelopment.meteorclient.systems.profiles.Profile;
2024
import meteordevelopment.meteorclient.systems.profiles.Profiles;
2125
import meteordevelopment.meteorclient.utils.Utils;
2226
import meteordevelopment.meteorclient.utils.misc.NbtUtils;
27+
import meteordevelopment.meteorclient.utils.render.prompts.OkPrompt;
2328
import net.minecraft.client.gui.screen.Screen;
24-
29+
import net.minecraft.nbt.NbtCompound;
30+
import net.minecraft.nbt.NbtElement;
31+
import net.minecraft.nbt.NbtIo;
32+
import org.lwjgl.BufferUtils;
33+
import org.lwjgl.PointerBuffer;
34+
import org.lwjgl.system.MemoryUtil;
35+
import org.lwjgl.util.tinyfd.TinyFileDialogs;
36+
37+
import java.io.DataOutputStream;
38+
import java.io.File;
39+
import java.io.FileOutputStream;
40+
import java.io.IOException;
41+
import java.nio.ByteBuffer;
42+
import java.nio.file.Path;
2543
import java.util.ArrayList;
2644
import java.util.List;
45+
import java.util.Map;
2746

2847
import static meteordevelopment.meteorclient.MeteorClient.mc;
2948

3049
public class ProfilesTab extends Tab {
50+
private static final PointerBuffer filters;
51+
52+
static {
53+
filters = BufferUtils.createPointerBuffer(1);
54+
55+
ByteBuffer pngFilter = MemoryUtil.memASCII("*.nbt");
56+
57+
filters.put(pngFilter);
58+
filters.rewind();
59+
}
60+
3161
public ProfilesTab() {
3262
super("Profiles");
3363
}
@@ -54,9 +84,31 @@ public void initWidgets() {
5484

5585
add(theme.horizontalSeparator()).expandX();
5686

87+
WHorizontalList l = add(theme.horizontalList()).expandX().widget();
88+
5789
// Create
58-
WButton create = add(theme.button("Create")).expandX().widget();
90+
WButton create = l.add(theme.button("Create")).expandX().widget();
91+
create.tooltip = "Create new profile";
5992
create.action = () -> mc.setScreen(new EditProfileScreen(theme, null, this::reload));
93+
94+
// Import
95+
WButton importBtn = l.add(theme.button("Import")).expandX().widget();
96+
importBtn.tooltip = "Import profile";
97+
importBtn.action = () -> {
98+
try {
99+
Profile imported = importProfile();
100+
if (imported != null) MeteorClient.LOG.info("Successfully imported profile '{}'.", imported.name.get());
101+
reload();
102+
} catch (IOException e) {
103+
MeteorClient.LOG.error("Error importing profile", e);
104+
OkPrompt.create()
105+
.title("Failure importing profile")
106+
.message("There was an error importing the profile.")
107+
.message("Error: %d", e.getMessage())
108+
.dontShowAgainCheckboxVisible(false)
109+
.show();
110+
}
111+
};
60112
}
61113

62114
private void initTable(WTable table) {
@@ -73,6 +125,9 @@ private void initTable(WTable table) {
73125
WButton load = table.add(theme.button("Load")).widget();
74126
load.action = profile::load;
75127

128+
WButton export = table.add(theme.button("Export")).widget();
129+
export.action = () -> mc.setScreen(new ExportProfileScreen(theme, profile));
130+
76131
WButton edit = table.add(theme.button(GuiRenderer.EDIT)).widget();
77132
edit.action = () -> mc.setScreen(new EditProfileScreen(theme, profile, this::reload));
78133

@@ -86,6 +141,41 @@ private void initTable(WTable table) {
86141
}
87142
}
88143

144+
private Profile importProfile() throws IOException {
145+
String file = TinyFileDialogs.tinyfd_openFileDialog("Select profile to import", null, filters, null, false);
146+
if (file == null) return null;
147+
File profileFile = new File(file);
148+
149+
NbtCompound nbt = NbtIo.read(profileFile.toPath());
150+
151+
Profile p = new Profile();
152+
p.name.set(nbt.getString("name", profileFile.getName()));
153+
//noinspection ResultOfMethodCallIgnored
154+
p.getFile().mkdirs();
155+
156+
nbt.remove("name");
157+
for (Map.Entry<String, NbtElement> entry : nbt.entrySet()) {
158+
String filename = entry.getKey();
159+
160+
switch (filename) {
161+
case "hud.nbt" -> p.hud.set(true);
162+
case "macros.nbt" -> p.macros.set(true);
163+
case "modules.nbt" -> p.modules.set(true);
164+
default -> {
165+
if (filename.endsWith(".nbt")) p.waypoints.set(true);
166+
}
167+
}
168+
169+
File f = new File(p.getFile(), filename);
170+
NbtIo.write(entry.getValue(), new DataOutputStream(new FileOutputStream(f)));
171+
}
172+
173+
Profiles.get().getAll().add(p);
174+
Profiles.get().save();
175+
176+
return p;
177+
}
178+
89179
@Override
90180
public boolean toClipboard() {
91181
return NbtUtils.toClipboard(Profiles.get());
@@ -154,4 +244,77 @@ protected void onClosed() {
154244
if (action != null) action.run();
155245
}
156246
}
247+
248+
private static class ExportProfileScreen extends WindowScreen {
249+
private final Profile profile;
250+
251+
public ExportProfileScreen(GuiTheme theme, Profile profile) {
252+
super(theme, "Export Profile");
253+
this.profile = profile;
254+
}
255+
256+
@Override
257+
public void initWidgets() {
258+
add(theme.label("Select which profile settings to export."));
259+
260+
WContainer settingsContainer = add(theme.verticalList()).expandX().minWidth(400).widget();
261+
262+
settingsContainer.add(theme.horizontalSeparator()).expandX().widget();
263+
264+
WCheckbox hud = addBool(settingsContainer, profile.settings.get("hud", Boolean.class));
265+
WCheckbox macros = addBool(settingsContainer, profile.settings.get("macros", Boolean.class));
266+
WCheckbox modules = addBool(settingsContainer, profile.settings.get("modules", Boolean.class));
267+
WCheckbox waypoints = addBool(settingsContainer, profile.settings.get("waypoints", Boolean.class));
268+
269+
add(theme.horizontalSeparator()).expandX().widget();
270+
271+
WButton export = add(theme.button("Export profile")).expandX().widget();
272+
export.action = () -> {
273+
exportProfile(profile, hud.checked, macros.checked, modules.checked, waypoints.checked);
274+
close();
275+
};
276+
}
277+
278+
private WCheckbox addBool(WContainer container, Setting<Boolean> setting) {
279+
WHorizontalList boolList = container.add(theme.horizontalList()).expandX().widget();
280+
boolList.add(theme.label(setting.title)).widget().tooltip = setting.description;
281+
282+
WCheckbox c = theme.checkbox(setting.get());
283+
boolList.add(c).expandCellX().right();
284+
285+
return c;
286+
}
287+
288+
private void exportProfile(Profile profile, boolean hud, boolean macros, boolean modules, boolean waypoints) {
289+
String path = TinyFileDialogs.tinyfd_saveFileDialog("Save profile", profile.name.get(), filters, null);
290+
if (path == null) return;
291+
Path p = Path.of(path.endsWith(".nbt") ? path : path + ".nbt");
292+
293+
NbtCompound nbt = new NbtCompound();
294+
nbt.putString("name", profile.name.get());
295+
296+
try {
297+
for (File f : profile.getFile().listFiles()) {
298+
if (f.getName().equals("hud.nbt") && hud ||
299+
f.getName().equals("macros.nbt") && macros ||
300+
f.getName().equals("modules.nbt") && modules
301+
) {
302+
nbt.put(f.getName(), NbtIo.read(f.toPath()));
303+
}
304+
else if (f.getName().endsWith(".nbt") && waypoints)
305+
nbt.put(f.getName(), NbtIo.read(f.toPath()));
306+
}
307+
308+
NbtIo.write(nbt, p);
309+
} catch (IOException e) {
310+
MeteorClient.LOG.error("Error serialising profile {} to a file", profile.name.get(), e);
311+
OkPrompt.create()
312+
.title("Failure exporting profile")
313+
.message("There was an error serialising or exporting the profile %d.", profile.name.get())
314+
.message("Error: %d", e.getMessage())
315+
.dontShowAgainCheckboxVisible(false)
316+
.show();
317+
}
318+
}
319+
}
157320
}

src/main/java/meteordevelopment/meteorclient/systems/profiles/Profile.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package meteordevelopment.meteorclient.systems.profiles;
77

8+
import meteordevelopment.meteorclient.MeteorClient;
89
import meteordevelopment.meteorclient.settings.*;
910
import meteordevelopment.meteorclient.systems.hud.Hud;
1011
import meteordevelopment.meteorclient.systems.macros.Macros;
@@ -96,11 +97,11 @@ public void delete() {
9697
try {
9798
FileUtils.deleteDirectory(getFile());
9899
} catch (IOException e) {
99-
e.printStackTrace();
100+
MeteorClient.LOG.error("Error deleting profile {}", name.get(), e);
100101
}
101102
}
102103

103-
private File getFile() {
104+
public File getFile() {
104105
return new File(Profiles.FOLDER, name.get());
105106
}
106107

src/main/java/meteordevelopment/meteorclient/systems/profiles/Profiles.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,24 @@ public NbtCompound toTag() {
9191
@Override
9292
public Profiles fromTag(NbtCompound tag) {
9393
profiles = NbtUtils.listFromTag(tag.getListOrEmpty("profiles"), Profile::new);
94+
95+
for (File file : FOLDER.listFiles()) {
96+
if (file.isDirectory() && get(file.getName()) == null) {
97+
Profile p = new Profile();
98+
p.name.set(file.getName());
99+
100+
boolean add = false;
101+
for (File f : file.listFiles()) {
102+
if (f.getName().equals("hud.nbt")) p.hud.set(add = true);
103+
else if (f.getName().equals("macros.nbt")) p.macros.set(add = true);
104+
else if (f.getName().equals("modules.nbt")) p.modules.set(add = true);
105+
else if (f.getName().endsWith(".nbt")) p.waypoints.set(add = true);
106+
}
107+
108+
if (add) add(p);
109+
}
110+
}
111+
94112
return this;
95113
}
96114
}

0 commit comments

Comments
 (0)