Skip to content

Commit 8a26fab

Browse files
committed
feat: official api
fix that wrong file thing too
1 parent a48e90f commit 8a26fab

16 files changed

Lines changed: 476 additions & 88 deletions

File tree

commons/src/main/java/net/swofty/commons/config/Settings.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ public static class LimboSettings {
5757
public static class ResourcePackSettings {
5858
@Comment("Base URL of the pack server (e.g. http://0.0.0.0:7270)")
5959
private String serverUrl = "http://127.0.0.1:7270";
60+
61+
@Comment("Whether to use Hypixel's official resource pack API instead of building a local pack")
62+
private boolean useHypixelApi = false;
63+
64+
@Comment("URL of Hypixel's resource pack metadata API")
65+
private String hypixelApiUrl = "https://api.hypixel.net/v2/resources/packs";
6066
}
6167

6268
@Getter

configuration/config.docker.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ limbo:
1414
resource-packs:
1515
skyblockpack:
1616
server-url: http://127.0.0.1:7270
17+
use-hypixel-api: false
18+
hypixel-api-url: https://api.hypixel.net/v2/resources/packs
1719
ravengard:
1820
server-url: http://127.0.0.1:7270

configuration/config.example.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,10 @@ integrations:
2424
limbo:
2525
host-name: 127.0.0.1
2626
port: 65535
27-
# Resource pack settings keyed by pack name (e.g. testingpack, bedwarspack)
28-
resource-packs: { }
27+
# Resource pack settings keyed by pack name (e.g. testingpack, skyblockpack)
28+
resource-packs:
29+
skyblockpack:
30+
server-url: http://127.0.0.1:7270
31+
# Use Hypixel's official pack and select its format for each player's version
32+
use-hypixel-api: false
33+
hypixel-api-url: https://api.hypixel.net/v2/resources/packs
Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,77 @@
11
package net.swofty.packer;
22

3-
import net.kyori.adventure.text.Component;
43
import team.unnamed.creative.BuiltResourcePack;
5-
import team.unnamed.creative.ResourcePack;
6-
import team.unnamed.creative.metadata.pack.FormatVersion;
7-
import team.unnamed.creative.metadata.pack.PackFormat;
8-
import team.unnamed.creative.metadata.pack.PackMeta;
9-
import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackReader;
10-
import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackWriter;
4+
import team.unnamed.creative.base.Writable;
115

12-
import java.io.File;
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.io.UncheckedIOException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.security.MessageDigest;
12+
import java.security.NoSuchAlgorithmException;
13+
import java.util.HexFormat;
14+
import java.util.stream.Stream;
15+
import java.util.zip.ZipEntry;
16+
import java.util.zip.ZipOutputStream;
1317

1418
public class HypixelPackBuilder {
15-
private static final FormatVersion FORMAT_VERSION = FormatVersion.of(FormatVersion.FORMAT_26_1);
16-
1719
private final PackDefinition definition;
1820

1921
public HypixelPackBuilder(PackDefinition definition) {
2022
this.definition = definition;
2123
}
2224

2325
public BuiltResourcePack build() {
24-
File packDirectory = new File(definition.getPackDirectory()).getAbsoluteFile();
25-
if (!packDirectory.isDirectory()) {
26-
throw new IllegalStateException("Pack directory does not exist: " + packDirectory.getPath());
26+
Path packDirectory = Path.of(definition.getPackDirectory()).toAbsolutePath();
27+
28+
if (!Files.isDirectory(packDirectory)) {
29+
throw new IllegalStateException(
30+
"Pack directory does not exist: " + packDirectory
31+
);
2732
}
2833

29-
ResourcePack pack = MinecraftResourcePackReader.minecraft()
30-
.readFromDirectory(packDirectory);
31-
pack.packMeta(PackMeta.of(PackFormat.format(FORMAT_VERSION, FORMAT_VERSION), Component.text("Hypixel")));
34+
try {
35+
byte[] bytes = zipDirectory(packDirectory);
36+
37+
MessageDigest digest = MessageDigest.getInstance("SHA-1");
38+
String hash = HexFormat.of().formatHex(digest.digest(bytes));
39+
40+
return BuiltResourcePack.of(
41+
Writable.bytes(bytes),
42+
hash
43+
);
44+
} catch (IOException e) {
45+
throw new UncheckedIOException("Failed to build resource pack", e);
46+
} catch (NoSuchAlgorithmException e) {
47+
throw new IllegalStateException("SHA-1 is unavailable", e);
48+
}
49+
}
50+
51+
private byte[] zipDirectory(Path directory) throws IOException {
52+
ByteArrayOutputStream output = new ByteArrayOutputStream();
53+
54+
try (ZipOutputStream zip = new ZipOutputStream(output);
55+
Stream<Path> paths = Files.walk(directory)) {
56+
57+
paths.filter(Files::isRegularFile).forEach(path -> {
58+
String entryName = directory.relativize(path)
59+
.toString()
60+
.replace('\\', '/');
61+
62+
try {
63+
zip.putNextEntry(new ZipEntry(entryName));
64+
Files.copy(path, zip);
65+
zip.closeEntry();
66+
} catch (IOException e) {
67+
throw new UncheckedIOException(e);
68+
}
69+
});
70+
} catch (UncheckedIOException e) {
71+
throw e.getCause();
72+
}
3273

33-
return MinecraftResourcePackWriter.minecraft().build(pack);
74+
return output.toByteArray();
3475
}
3576

3677
}
Lines changed: 84 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,101 @@
11
package net.swofty.packer;
22

3+
import net.swofty.packer.packs.ravengard.RavengardPackDefinition;
4+
import net.swofty.packer.packs.skyblock.SkyblockPackDefinition;
35
import team.unnamed.creative.BuiltResourcePack;
4-
import team.unnamed.creative.base.Writable;
5-
import team.unnamed.creative.metadata.pack.FormatVersion;
6+
import team.unnamed.creative.server.ResourcePackServer;
7+
import team.unnamed.creative.server.handler.ResourcePackRequestHandler;
68

7-
import java.io.ByteArrayOutputStream;
89
import java.io.IOException;
9-
import java.io.UncheckedIOException;
10-
import java.nio.file.Files;
11-
import java.nio.file.Path;
12-
import java.security.MessageDigest;
13-
import java.security.NoSuchAlgorithmException;
14-
import java.util.HexFormat;
15-
import java.util.stream.Stream;
16-
import java.util.zip.ZipEntry;
17-
import java.util.zip.ZipOutputStream;
18-
19-
public class HypixelPackBuilder {
20-
private static final FormatVersion FORMAT_VERSION = FormatVersion.of(FormatVersion.FORMAT_26_1);
21-
22-
private final PackDefinition definition;
23-
24-
public HypixelPackBuilder(PackDefinition definition) {
25-
this.definition = definition;
26-
}
27-
28-
public BuiltResourcePack build() {
29-
Path packDirectory = Path.of(definition.getPackDirectory()).toAbsolutePath();
10+
import java.io.OutputStream;
11+
import java.nio.charset.StandardCharsets;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.concurrent.Executors;
15+
import java.util.function.Function;
16+
import java.util.stream.Collectors;
17+
18+
public class HypixelPackServer {
19+
private static final String DEFAULT_HOST = "0.0.0.0";
20+
private static final int DEFAULT_PORT = 7270;
21+
22+
static void main(String[] args) throws IOException {
23+
String host = DEFAULT_HOST;
24+
int port = DEFAULT_PORT;
25+
26+
for (int i = 0; i < args.length - 1; i++) {
27+
switch (args[i]) {
28+
case "-h", "--host" -> host = args[++i];
29+
case "-p", "--port" -> port = Integer.parseInt(args[++i]);
30+
}
31+
}
3032

31-
if (!Files.isDirectory(packDirectory)) {
32-
throw new IllegalStateException(
33-
"Pack directory does not exist: " + packDirectory
34-
);
33+
Map<String, BuiltResourcePack> packs = List.of(
34+
RavengardPackDefinition.INSTANCE,
35+
SkyblockPackDefinition.INSTANCE
36+
).stream()
37+
.map(HypixelPackServer::buildPack)
38+
.collect(Collectors.toMap(
39+
pack -> pack.hash() + ".zip",
40+
Function.identity(),
41+
(first, second) -> first
42+
));
43+
44+
ResourcePackServer server = ResourcePackServer.server()
45+
.address(host, port)
46+
.handler(createRequestHandler(packs))
47+
.executor(Executors.newFixedThreadPool(4))
48+
.build();
49+
server.start();
50+
51+
System.out.println("Resource pack server started on " + host + ":" + port);
52+
for (String fileName : packs.keySet()) {
53+
System.out.println("Pack URL: http://" + host + ":" + port + "/" + fileName);
3554
}
55+
System.out.println("Press Ctrl+C to stop.");
56+
57+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
58+
System.out.println("Shutting down pack server...");
59+
server.stop(5);
60+
}));
3661

3762
try {
38-
byte[] bytes = zipDirectory(packDirectory);
39-
40-
MessageDigest digest = MessageDigest.getInstance("SHA-1");
41-
String hash = HexFormat.of().formatHex(digest.digest(bytes));
42-
43-
return BuiltResourcePack.of(
44-
Writable.bytes(bytes),
45-
hash
46-
);
47-
} catch (IOException e) {
48-
throw new UncheckedIOException("Failed to build resource pack", e);
49-
} catch (NoSuchAlgorithmException e) {
50-
throw new IllegalStateException("SHA-1 is unavailable", e);
51-
}
63+
Thread.currentThread().join();
64+
} catch (InterruptedException ignored) {}
5265
}
5366

54-
private byte[] zipDirectory(Path directory) throws IOException {
55-
ByteArrayOutputStream output = new ByteArrayOutputStream();
67+
private static BuiltResourcePack buildPack(PackDefinition definition) {
68+
System.out.println("Building resource pack '" + definition.getPackName() + "'...");
69+
System.out.println("Pack directory: " + definition.getPackDirectory());
5670

57-
try (ZipOutputStream zip = new ZipOutputStream(output);
58-
Stream<Path> paths = Files.walk(directory)) {
71+
BuiltResourcePack built = new HypixelPackBuilder(definition).build();
72+
System.out.println("Resource pack built. Hash: " + built.hash());
73+
return built;
74+
}
5975

60-
paths.filter(Files::isRegularFile).forEach(path -> {
61-
String entryName = directory.relativize(path)
62-
.toString()
63-
.replace('\\', '/');
76+
private static ResourcePackRequestHandler createRequestHandler(Map<String, BuiltResourcePack> packs) {
77+
return (request, exchange) -> {
78+
String path = exchange.getRequestURI().getPath();
79+
String fileName = path.substring(path.lastIndexOf('/') + 1);
6480

65-
try {
66-
zip.putNextEntry(new ZipEntry(entryName));
67-
Files.copy(path, zip);
68-
zip.closeEntry();
69-
} catch (IOException e) {
70-
throw new UncheckedIOException(e);
71-
}
72-
});
73-
} catch (UncheckedIOException e) {
74-
throw e.getCause();
75-
}
81+
BuiltResourcePack pack = packs.get(fileName);
7682

77-
return output.toByteArray();
83+
if (pack == null) {
84+
byte[] response = "Resource pack not found\n".getBytes(StandardCharsets.UTF_8);
85+
exchange.getResponseHeaders().set("Content-Type", "text/plain");
86+
exchange.sendResponseHeaders(404, response.length);
87+
try (OutputStream output = exchange.getResponseBody()) {
88+
output.write(response);
89+
}
90+
return;
91+
}
92+
93+
byte[] response = pack.data().toByteArray();
94+
exchange.getResponseHeaders().set("Content-Type", "application/zip");
95+
exchange.sendResponseHeaders(200, response.length);
96+
try (OutputStream output = exchange.getResponseBody()) {
97+
output.write(response);
98+
}
99+
};
78100
}
79-
80101
}

proxy.api/src/main/java/net/swofty/proxyapi/ProxyPlayer.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,30 @@ public CompletableFuture<Boolean> isOnline() {
7373
return future;
7474
}
7575

76+
public CompletableFuture<Integer> getVersion() {
77+
CompletableFuture<Integer> future = new CompletableFuture<>();
78+
RedisClient.requestProxy(PLAYER_HANDLER,
79+
new PlayerHandlerProtocol.Request(uuid.toString(), PlayerHandlerProtocol.Action.VERSION, Map.of()))
80+
.thenAccept(response -> {
81+
if (!response.success()) {
82+
future.completeExceptionally(new IllegalStateException(response.error()));
83+
return;
84+
}
85+
86+
Object version = response.data().get("version");
87+
if (version instanceof Number number) {
88+
future.complete(number.intValue());
89+
} else {
90+
future.completeExceptionally(new IllegalStateException("Proxy returned no player version"));
91+
}
92+
})
93+
.exceptionally(error -> {
94+
future.completeExceptionally(error);
95+
return null;
96+
});
97+
return future;
98+
}
99+
76100
public void runEvent(ProxyUnderstandableEvent event) {
77101
RedisClient.requestProxy(PLAYER_HANDLER,
78102
new PlayerHandlerProtocol.Request(uuid.toString(), PlayerHandlerProtocol.Action.EVENT,

setup/internal/installer/files.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ resource-pack:
168168
resource-packs:
169169
skyblockpack:
170170
server-url: http://127.0.0.1:7270
171+
use-hypixel-api: false
172+
hypixel-api-url: https://api.hypixel.net/v2/resources/packs
171173
ravengard:
172174
server-url: http://127.0.0.1:7270
173175
`

type.generic/src/main/java/net/swofty/type/generic/resourcepack/HypixelResourcePack.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
import net.swofty.type.generic.user.HypixelPlayer;
66

77
public interface HypixelResourcePack {
8+
record PackInfo(String url, String hash) {
9+
}
10+
811
String getPackName();
912
String getPackUrl();
1013
String getPackHash();
1114

15+
default PackInfo getPackFor(HypixelPlayer player) {
16+
return new PackInfo(getPackUrl(), getPackHash());
17+
}
18+
1219
boolean isRequired();
1320

1421
void initialize();

type.generic/src/main/java/net/swofty/type/generic/resourcepack/ResourcePackManager.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import net.kyori.adventure.resource.ResourcePackRequest;
66
import net.kyori.adventure.text.Component;
77
import net.minestom.server.entity.Player;
8+
import net.swofty.type.generic.user.HypixelPlayer;
89
import org.tinylog.Logger;
910

1011
import java.net.URI;
@@ -37,8 +38,17 @@ public void sendPack(Player player) {
3738
* already-received chunk sections permanently unrendered on the client.
3839
*/
3940
public void sendPackBlocking(Player player, int timeoutSeconds) {
40-
String packUrl = activePack.getPackUrl();
41-
String packHash = activePack.getPackHash();
41+
HypixelResourcePack.PackInfo pack = player instanceof HypixelPlayer hypixelPlayer
42+
? activePack.getPackFor(hypixelPlayer)
43+
: new HypixelResourcePack.PackInfo(activePack.getPackUrl(), activePack.getPackHash());
44+
45+
if (pack == null) {
46+
Logger.warn("Resource pack could not be resolved for " + player.getUsername() + ", skipping pack send");
47+
return;
48+
}
49+
50+
String packUrl = pack.url();
51+
String packHash = pack.hash();
4252

4353
if (packUrl == null || packUrl.isEmpty() || packHash == null || packHash.isEmpty()) {
4454
Logger.warn("Resource pack URL or hash not configured, skipping pack send for " + player.getUsername());

0 commit comments

Comments
 (0)