/* * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod * Copyright (C) 2021 cyoung06 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package kr.syeyoung.dungeonsguide.utils.cursor; import com.sun.jna.Memory; import com.sun.jna.Pointer; import com.sun.jna.Structure; import kr.syeyoung.dungeonsguide.utils.RenderUtils; import net.minecraft.client.Minecraft; import net.minecraft.util.MathHelper; import net.minecraft.util.ResourceLocation; import org.apache.commons.io.IOUtils; import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.LWJGLUtil; import org.lwjgl.input.Cursor; import sun.misc.Unsafe; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class GLCursors { private static Unsafe unsafe; private static Class cursorElement; private static Constructor constructor; private static Field cursorField; private static Map enumCursorCursorMap = new HashMap<>(); static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); cursorElement = Class.forName("org.lwjgl.input.Cursor$CursorElement"); constructor = cursorElement.getDeclaredConstructor(Object.class, long.class, long.class); constructor.setAccessible(true); cursorField = Cursor.class.getDeclaredField("cursors"); cursorField.setAccessible(true); } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } } public static void setupCursors() { if (enumCursorCursorMap.size() != 0) return; int platform = LWJGLUtil.getPlatform(); for (EnumCursor value : EnumCursor.values()) { Cursor c = null; try { switch(platform) { case LWJGLUtil.PLATFORM_WINDOWS: if (value.getWindows() != -1) c = createCursorWindows(value.getWindows()); break; case LWJGLUtil.PLATFORM_LINUX: if (value.getLinux() != -1) c = createCursorLinux(value.getLinux()); break; case LWJGLUtil.PLATFORM_MACOSX: if (value.getMacos() != null) c = createCursorMac(value.getMacos()); break; } } catch (Throwable e) { System.out.println("Error occured while loading cursor: "+value); e.printStackTrace(); } try { if (c == null) { int hotspotX = 0, hotspotY = 0; BufferedImage bufferedImage = null; int minC = Cursor.getMinCursorSize(), maxC = Cursor.getMaxCursorSize(); try { ResourceLocation cursorinfo = new ResourceLocation("dungeonsguide:cursors/"+value.getAltFileName()); List cursorDataList = CursorReader.readFromInputStream(Minecraft.getMinecraft().getResourceManager().getResource(cursorinfo).getInputStream()); List cursorDataList2 = cursorDataList.stream() .filter(cdata -> cdata.getBufferedImage() != null) .filter(cdata -> minC <= cdata.getHeight() && cdata.getHeight() <= maxC && minC <= cdata.getWidth() && cdata.getWidth() <= maxC) .sorted(Comparator.comparingInt(CursorReader.CursorData::getWidth)).collect(Collectors.toList()); CursorReader.CursorData cursorData = cursorDataList2.size() == 0 ? cursorDataList.get(0) : cursorDataList2.get(0); System.out.println(cursorData); bufferedImage = cursorData.getBufferedImage(); hotspotX = cursorData.getXHotSpot(); hotspotY = cursorData.getYHotSpot(); } catch (Throwable t) {t.printStackTrace();} int width = bufferedImage == null ? 16 : bufferedImage.getWidth(); int height = bufferedImage == null ? 16 : bufferedImage.getHeight(); int effWidth = MathHelper.clamp_int(width, Cursor.getMinCursorSize(), Cursor.getMaxCursorSize()); int effHeight = MathHelper.clamp_int(height, Cursor.getMinCursorSize(), Cursor.getMaxCursorSize()); int length = effHeight * effWidth; IntBuffer intBuffer = BufferUtils.createIntBuffer(length); for (int i = 0; i < length; i++) { int x = i % effWidth; int y = i / effWidth; if (bufferedImage == null) { intBuffer.put(RenderUtils.getChromaColorAt(x,y,1.0f, 1.0f, 1.0f, 1.0f)); } else if (x >= width || y >= height) { intBuffer.put(0); } else { intBuffer.put(bufferedImage.getRGB(x, height - y - 1)); } } intBuffer.flip(); c = new Cursor(effWidth, effHeight, hotspotX, height - hotspotY - 1,1,intBuffer, null); } } catch (Throwable e) { System.out.println("Error occured while loading cursor from resource: "+value); e.printStackTrace(); } if (c != null) { try { Object arr = cursorField.get(c); Object cursor = Array.get(arr, 0); for (Field declaredField : cursor.getClass().getDeclaredFields()) { declaredField.setAccessible(true); Object obj = declaredField.get(cursor); System.out.println(declaredField.getName()+": "+obj+" - "+(obj instanceof ByteBuffer)); if (obj instanceof ByteBuffer) { ByteBuffer b = (ByteBuffer) declaredField.get(cursor); StringBuilder sb = new StringBuilder("Contents: "); for (int i = 0; i < b.limit(); i++) { sb.append(Integer.toHexString(b.get(i) & 0xFF)).append(" "); } System.out.println(sb.toString()); } } } catch (IllegalAccessException e) { e.printStackTrace(); } enumCursorCursorMap.put(value, c); } } } public static Cursor getCursor(EnumCursor enumCursor) { return enumCursorCursorMap.get(enumCursor); } private static Cursor createCursorWindows(int cursor) throws LWJGLException, InstantiationException, InvocationTargetException, IllegalAccessException { User32 user32 = User32.INSTANCE; Pointer hIcon = user32 .LoadCursorW(Pointer.NULL, cursor); long ptrVal = Pointer.nativeValue(hIcon); ByteBuffer handle = BufferUtils.createByteBuffer(Pointer.SIZE); // Why does it have to be direct? well it crashes without it. if (handle.order() == ByteOrder.LITTLE_ENDIAN) { for (int i = 0; i < Pointer.SIZE; i++) { byte value = (byte) ((ptrVal >> i * 8) & 0xFF); handle.put(value); } } else { for (int i = Pointer.SIZE; i >= 0; i++) { byte value = (byte) ((ptrVal >> i * 8) & 0xFF); handle.put(value); } } handle.position(0); return createCursor(handle); } private static Cursor createCursorLinux(int cursor) throws LWJGLException, InstantiationException, InvocationTargetException, IllegalAccessException { X11.Display display = X11.INSTANCE.XOpenDisplay(null); Pointer fontCursor = X11.INSTANCE.XCreateFontCursor(display, cursor); long iconPtr = Pointer.nativeValue(fontCursor); return createCursor(iconPtr); } private static Cursor createCursorMac(String cursor) throws LWJGLException, InstantiationException, InvocationTargetException, IllegalAccessException { // trust me, it's horrible. Foundation foundation = Foundation.INSTANCE; Pointer nsCursor = foundation.objc_getClass("NSCursor"); Pointer selector = foundation.sel_registerName(cursor); Pointer thePointer = foundation.objc_msgSend(nsCursor, selector); long iconPtr = Pointer.nativeValue(thePointer); return createCursor(iconPtr); } private static Cursor createCursor(Object handle) throws IllegalAccessException, InvocationTargetException, InstantiationException { // Yes. I had no way. Cursor ADANGEROUSOBJECT = (Cursor) unsafe.allocateInstance(Cursor.class); Object cursorElement = constructor.newInstance(handle, 0, LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_LINUX ? -1 : System.currentTimeMillis()); Object array = Array.newInstance(GLCursors.cursorElement, 1); Array.set(array, 0, cursorElement); cursorField.set(ADANGEROUSOBJECT, array); return ADANGEROUSOBJECT; } }