diff --git a/src/main/java/land/chipmunk/chayapak/chomens_bot/commands/MusicCommand.java b/src/main/java/land/chipmunk/chayapak/chomens_bot/commands/MusicCommand.java index 7f0e053..3bba3a8 100644 --- a/src/main/java/land/chipmunk/chayapak/chomens_bot/commands/MusicCommand.java +++ b/src/main/java/land/chipmunk/chayapak/chomens_bot/commands/MusicCommand.java @@ -79,6 +79,7 @@ public class MusicCommand extends Command { return switch (action) { case "play", "playurl", "playnbs", "playnbsurl" -> play(context); case "playfromitem", "playitem" -> playFromItem(context); + case "playsongplayer" -> playSongPlayer(context); case "stop" -> stop(context); case "loop" -> loop(context); case "list" -> list(context); @@ -256,6 +257,67 @@ public class MusicCommand extends Command { return null; } + public Component playSongPlayer (CommandContext context) throws CommandException { + // dupe codes ?? + + final Bot bot = context.bot; + + final CompletableFuture future = bot.core.runTracked( + "minecraft:data get entity " + + UUIDUtilities.selector(context.sender.profile.getId()) + + " SelectedItem.tag.SongItemData.SongData" + ); + + if (future == null) { + throw new CommandException(Component.text("There was an error while getting your data")); + } + + future.thenApply(tags -> { + if (!tags.contains("LastOutput") || !(tags.get("LastOutput") instanceof StringTag)) return tags; + + final StringTag lastOutput = tags.get("LastOutput"); + + final Component output = GsonComponentSerializer.gson().deserialize(lastOutput.getValue()); + + final List children = output.children(); + + if ( + !children.isEmpty() && + !children.get(0).children().isEmpty() && + ((TranslatableComponent) children.get(0).children().get(0)) + .key() + .equals("arguments.nbtpath.nothing_found") + ) { + context.sendOutput(Component.text("Player has no SongItemData -> SongData NBT tag in the selected item").color(NamedTextColor.RED)); + return tags; + } + + final String value = ComponentUtilities.stringify(((TranslatableComponent) children.get(0)).args().get(1)); + + if (!value.startsWith("\"") && !value.endsWith("\"") && !value.startsWith("'") && !value.endsWith("'")) { + context.sendOutput(Component.text("NBT is not a string").color(NamedTextColor.RED)); + return tags; + } + + try { + bot.music.loadSong( + Base64.getDecoder().decode( + value + .substring(1) + .substring(0, value.length() - 2) + ), + context.sender + ); + } catch (IllegalArgumentException e) { + context.sendOutput(Component.text("Invalid song data in the selected item").color(NamedTextColor.RED)); + } + + return tags; + }); + + return null; + } + public Component stop (CommandContext context) { final Bot bot = context.bot; bot.music.stopPlaying(); diff --git a/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongLoaderThread.java b/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongLoaderThread.java index e82cd52..351d2ef 100644 --- a/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongLoaderThread.java +++ b/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongLoaderThread.java @@ -25,6 +25,7 @@ public class SongLoaderThread extends Thread { converters.add(new MidiConverter()); converters.add(new NBSConverter()); converters.add(new TextFileConverter()); + converters.add(new SongPlayerConverter()); } public final String fileName; diff --git a/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongPlayerConverter.java b/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongPlayerConverter.java new file mode 100644 index 0000000..8d7a815 --- /dev/null +++ b/src/main/java/land/chipmunk/chayapak/chomens_bot/song/SongPlayerConverter.java @@ -0,0 +1,126 @@ +package land.chipmunk.chayapak.chomens_bot.song; + +import land.chipmunk.chayapak.chomens_bot.Bot; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.zip.GZIPInputStream; + +// Author: hhhzzzsss (i ported it from songplayer) +public class SongPlayerConverter implements Converter { + public static final byte[] FILE_TYPE_SIGNATURE = {-53, 123, -51, -124, -122, -46, -35, 38}; + public static final long MAX_UNCOMPRESSED_SIZE = 50 * 1024 * 1024; + + @Override + public Song getSongFromBytes(byte[] bytes, String fileName, Bot bot) throws Exception { + InputStream is = new LimitedSizeInputStream(new GZIPInputStream(new ByteArrayInputStream(bytes)), MAX_UNCOMPRESSED_SIZE); + bytes = is.readAllBytes(); + is.close(); + + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (byte b : FILE_TYPE_SIGNATURE) { + if (b != buffer.get()) { + throw new IOException("Invalid file type signature"); + } + } + + byte version = buffer.get(); + // Currently on format version 1 + if (version != 1) { + throw new IOException("Unsupported format version!"); + } + + long songLength = buffer.getLong(); + String songName = getString(buffer, bytes.length); + int loop = buffer.get() & 0xFF; + int loopCount = buffer.get() & 0xFF; + long loopPosition = buffer.getLong(); + + Song song = new Song(fileName, bot, !songName.trim().isEmpty() ? songName : null, null, null, null, false); + song.length = songLength; +// song.looping = loop > 0; +// song.loopCount = loopCount; + song.loopPosition = loopPosition == 0 ? 200 : loopPosition; + + long time = 0; + while (true) { + int noteId = buffer.getShort(); + if (noteId >= 0 && noteId < 400) { + time += getVarLong(buffer); + song.add(new Note(Instrument.fromId(noteId / 25), noteId % 25, noteId % 25, 1, time, -1, 100)); + } + else if ((noteId & 0xFFFF) == 0xFFFF) { + break; + } + else { + throw new IOException("Song contains invalid note id of " + noteId); + } + } + + return song; + } + + private static String getString(ByteBuffer buffer, int maxSize) throws IOException { + int length = buffer.getInt(); + if (length > maxSize) { + throw new IOException("String is too large"); + } + byte[] arr = new byte[length]; + buffer.get(arr, 0, length); + return new String(arr, StandardCharsets.UTF_8); + } + + private static long getVarLong(ByteBuffer buffer) { + long val = 0; + long mult = 1; + int flag = 1; + while (flag != 0) { + int b = buffer.get() & 0xFF; + val += (b & 0x7F) * mult; + mult <<= 7; + flag = b >>> 7; + } + return val; + } + + private static class LimitedSizeInputStream extends InputStream { + private final InputStream original; + private final long maxSize; + private long total; + + public LimitedSizeInputStream(InputStream original, long maxSize) { + this.original = original; + this.maxSize = maxSize; + } + + @Override + public int read() throws IOException { + int i = original.read(); + if (i>=0) incrementCounter(1); + return i; + } + + @Override + public int read(byte b[]) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + int i = original.read(b, off, len); + if (i>=0) incrementCounter(i); + return i; + } + + private void incrementCounter(int size) throws IOException { + total += size; + if (total>maxSize) throw new IOException("Input stream exceeded maximum size of " + maxSize + " bytes"); + } + } +}