Directly using VirtualBox from Java #5

Merged
NorbiPeti merged 60 commits from directvb into master 2019-04-18 23:29:21 +00:00
7 changed files with 270 additions and 63 deletions
Showing only changes of commit 9ef0b00caa - Show all commits

View file

@ -70,7 +70,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.9.2-R0.1-SNAPSHOT</version>
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
@ -89,10 +89,6 @@
<artifactId>gson</artifactId>
<groupId>com.google.code.gson</groupId>
</exclusion>
<exclusion>
<artifactId>ebean</artifactId>
<groupId>org.avaje</groupId>
</exclusion>
<exclusion>
<artifactId>snakeyaml</artifactId>
<groupId>org.yaml</groupId>
@ -109,6 +105,12 @@
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -1,18 +1,38 @@
package sznp.virtualcomputer;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.concurrent.TimeUnit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.map.MapCanvas;
import org.bukkit.map.MapRenderer;
import org.bukkit.map.MapView;
public class BukkitRenderer extends MapRenderer implements IRenderer {
private byte[] allpixels;
private BufferedImage image;
private int startindex;
public BukkitRenderer(BufferedImage image) {
this.image = image;
/**
* Generic implementation, should work on most versions
*
* @param id
* The ID of the current map
* @param world
* The world to create new maps in
* @param allpixels
* The raw pixel data from the machine in BGRA format
* @param startindex
* The index to start from in allpixels
*/
public BukkitRenderer(short id, World world, byte[] allpixels, int startindex) {
MapView map = IRenderer.prepare(id, world);
map.addRenderer(this);
this.allpixels = allpixels;
this.startindex = startindex;
image = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);
}
private int progress = 0;
@ -22,6 +42,19 @@ public class BukkitRenderer extends MapRenderer implements IRenderer {
public void render(MapView view, MapCanvas canvas, Player player) {
long time = System.nanoTime();
final int[] a = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); // Directly update the bytes of the image
// (byte) bgra to rgb (int)
for (int i = startindex, j = 0; i < startindex + 128 * 128; i = i + 4, j++) {
int b, g, r;
b = allpixels[i] & 0xFF;
g = allpixels[i + 1] & 0xFF;
r = allpixels[i + 2] & 0xFF;
a[j] = (r << 16) | (g << 8) | b;
}
try {
canvas.drawImage(0, progress * updatepixels, image.getSubimage(0, progress * updatepixels, 128,
(progress * updatepixels + updatepixels >= 128 ? 128 - progress * updatepixels : updatepixels)));

View file

@ -62,7 +62,7 @@ public class Commands implements CommandExecutor
case "key":
if (args.length < 2)
{
sender.sendMessage("§cUsage: /computer key <key> [down/up|interval]");
sender.sendMessage("§cUsage: /computer key <key> [down/up|duration(ticks)]");
return true;
}
if (args.length < 3)
@ -187,8 +187,7 @@ public class Commands implements CommandExecutor
}
MouseLockerPlayerListener.LockedSpeed = Float
.parseFloat(args[2]);
sender.sendMessage("§aMouse speed set to "
+ MouseLockerPlayerListener.LockedSpeed);
sender.sendMessage("§aMouse speed set to " + MouseLockerPlayerListener.LockedSpeed);
}
break;
}

View file

@ -33,13 +33,9 @@ public class DirectRenderer implements IRenderer {
* @throws Exception
* Usually happens on incompatibility
*/
@SuppressWarnings("deprecation")
public DirectRenderer(short id, World world, byte[] allpixels, int startindex)
throws Exception, Exception, Exception, Exception {
map = Bukkit.getMap(id);
if (map == null)
map = Bukkit.createMap(world);
map.getRenderers().clear();
map = IRenderer.prepare(id, world);
final Field field = map.getClass().getDeclaredField("renderCache");
field.setAccessible(true);
@SuppressWarnings("unchecked")

View file

@ -1,4 +1,16 @@
package sznp.virtualcomputer;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.map.MapView;
public interface IRenderer {
static MapView prepare(short id, World world) {
@SuppressWarnings("deprecation")
MapView map = Bukkit.getMap(id);
if (map == null)
map = Bukkit.createMap(world);
map.getRenderers().clear();
return map;
}
}

View file

@ -1,8 +1,8 @@
package sznp.virtualcomputer;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.HashMap;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import net.countercraft.movecraft.craft.Craft;
import net.countercraft.movecraft.craft.CraftManager;
@ -12,21 +12,18 @@ import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.virtualbox_5_1.IFramebuffer;
import org.virtualbox_5_1.ISession;
import org.virtualbox_5_1.IVirtualBox;
import org.virtualbox_5_1.VirtualBoxManager;
import org.virtualbox_5_1.*;
import com.mcplugindev.slipswhitley.sketchmap.map.RelativeLocation;
import com.mcplugindev.slipswhitley.sketchmap.map.SketchMap;
import com.google.common.collect.Lists;
public class PluginMain extends JavaPlugin {
private IVirtualBox vbox;
private ISession session;
private SketchMap smap;
private ArrayList<IRenderer> renderers = new ArrayList<>();
private IMachine machine;
public static PluginMain Instance;
public static byte[] allpixels = new byte[640 * 480];
// Fired when plugin is first enabled
@Override
@ -36,21 +33,27 @@ public class PluginMain extends JavaPlugin {
ConsoleCommandSender ccs = getServer().getConsoleSender();
this.getCommand("computer").setExecutor(new Commands());
ccs.sendMessage("§bInitializing VirtualBox...");
final String vbpath = System.getProperty("os.name").toLowerCase().contains("mac")
? "/Applications/VirtualBox.app/Contents/MacOS" : "/opt/virtualbox";
if (System.getProperty("vbox.home") == null || System.getProperty("vbox.home").isEmpty())
System.setProperty("vbox.home", "/opt/virtualbox");
System.setProperty("vbox.home", vbpath);
if (System.getProperty("sun.boot.library.path") == null
|| System.getProperty("sun.boot.library.path").isEmpty())
System.setProperty("sun.boot.library.path", vbpath);
addLibraryPath(vbpath);
final VirtualBoxManager manager = VirtualBoxManager.createInstance(getDataFolder().getAbsolutePath());
vbox = manager.getVBox();
session = manager.getSessionObject();
ccs.sendMessage("§bStarting VM for testing...");
vbox.getMachines().get(0).launchVMProcess(session, "headless", "").waitForCompletion(2000);
session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new MCFrameBuffer()));
ccs.sendMessage("§bLoading SketchMap...");
img = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
HashMap<Short, RelativeLocation> map = new HashMap<>();
for (int i = 0; i < 5; i++)
for (int j = 0; j < 4; j++)
map.put((short) (i * 4 + j), new RelativeLocation(i, j));
smap = new SketchMap(img, "Screen", 5, 4, false, map);
ccs.sendMessage("§bLoading Screen...");
try {
for (short i = 0; i < 20; i++)
renderers.add(new DirectRenderer(i, Bukkit.getWorlds().get(0), allpixels, i * 128 * 128 * 4)); // TODO: The pixels are selected in a horribly wrong way probably
ccs.sendMessage("§bUsing Direct Renderer");
} catch (NoClassDefFoundError e) {
for (short i = 0; i < 20; i++)
renderers.add(new BukkitRenderer(i, Bukkit.getWorlds().get(0), allpixels, i * 128 * 128 * 4));
ccs.sendMessage("§6Compability error, using slower renderer");
}
ccs.sendMessage("§bLoaded!");
getServer().getPluginManager().registerEvents(new MouseLockerPlayerListener(), this);
DoStart();
@ -67,12 +70,12 @@ public class PluginMain extends JavaPlugin {
saveConfig();
}
private volatile BufferedImage img;
private volatile BukkitTask task = null;
public void Start(CommandSender sender) {
sender.sendMessage("§eStarting computer...");
// computer.Start();
if (machine == null)
machine = vbox.getMachines().get(0);
machine.launchVMProcess(session, "headless", "").waitForCompletion(10000);
session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new MCFrameBuffer()));
sender.sendMessage("§eComputer started.");
DoStart();
}
@ -80,14 +83,6 @@ public class PluginMain extends JavaPlugin {
public static int MouseSpeed = 1;
private void DoStart() {
if (task == null)
task = this.getServer().getScheduler().runTaskTimerAsynchronously(this, new Runnable() {
public void run() {
final int[] a = ((DataBufferInt) smap.image.getRaster().getDataBuffer()).getData();
// final int[] data = computer.GetScreenPixelColors();
// System.arraycopy(data, 0, a, 0, data.length);
}
}, 1, 10);
if (getServer().getPluginManager().isPluginEnabled("Movecraft")) {
this.getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
public void run() {
@ -113,50 +108,106 @@ public class PluginMain extends JavaPlugin {
public void Stop(CommandSender sender) {
sender.sendMessage("§eStopping computer...");
// computer.PowerOff();
session.getConsole().powerDown().waitForCompletion(2000);
sender.sendMessage("§eComputer stopped.");
}
public void PowerButton(CommandSender sender) {
sender.sendMessage("§eStarting/stoppping computer...");
sender.sendMessage("§ePressing powerbutton...");
final CommandSender s = sender;
getServer().getScheduler().runTaskAsynchronously(this, new Runnable() {
@Override
public void run() {
/*
* if (computer.PowerButton()) { DoStart(); s.sendMessage("§eComputer started."); } else s.sendMessage("§ePowerbutton pressed.");
*/
if (session.getState() != SessionState.Locked || session.getMachine() == null) {
Start(sender);
} else {
session.getConsole().powerButton();
s.sendMessage("§ePowerbutton pressed.");
}
}
});
}
public void Reset(CommandSender sender) {
sender.sendMessage("§eResetting computer...");
// computer.Reset();
if (session.getState() == SessionState.Locked)
session.getConsole().powerDown().waitForCompletion(10000);
sender.sendMessage("§eComputer reset.");
}
public void FixScreen(CommandSender sender) {
sender.sendMessage("§eFixing screen...");
// computer.FixScreen();
session.getConsole().getDisplay().setSeamlessMode(false);
session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L);
sender.sendMessage("§eScreen fixed.");
}
public void PressKey(CommandSender sender, String key, String stateorduration) {
/*
* if (stateorduration.length() == 0) computer.PressKey(key, (short) 0); else if (stateorduration.equalsIgnoreCase("down")) computer.PressKey(key, (short) -1); else if
* (stateorduration.equalsIgnoreCase("up")) computer.PressKey(key, (short) -2); else computer.PressKey(key, Short.parseShort(stateorduration));
*/
if (session.getState() == SessionState.Locked) {
int durationorstate;
if (stateorduration.length() == 0)
durationorstate = 0;
else if (stateorduration.equalsIgnoreCase("down"))
durationorstate = -1;
else if (stateorduration.equalsIgnoreCase("up"))
durationorstate = -2;
else
durationorstate = Short.parseShort(stateorduration);
int code = 0;
// Release key scan code concept taken from VirtualBox source code (KeyboardImpl.cpp:putCAD())
// +128
if (durationorstate != 2)
session.getConsole().getKeyboard().putScancode(code);
Runnable sendrelease = () -> session.getConsole().getKeyboard()
.putScancodes(Lists.newArrayList(code + 128, Scancode.sc_controlLeft.Code + 128,
Scancode.sc_shiftLeft.Code + 128, Scancode.sc_altLeft.Code + 128));
if (durationorstate == 0 || durationorstate == -2)
sendrelease.run();
if (durationorstate > 0) {
Bukkit.getScheduler().runTaskLaterAsynchronously(this, sendrelease, durationorstate);
}
}
}
public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs, boolean down) {
/*
* if (down) computer.UpdateMouse(x, y, z, w, mbs); else computer.UpdateMouse(x, y, z, w, "");
*/
if (session.getState() != SessionState.Locked)
return;
int state = 0;
if (mbs.length() > 0 && down)
state = Arrays.stream(MouseButtonState.values()).filter(mousebs -> mousebs.name().equalsIgnoreCase(mbs))
.findAny().orElseThrow(() -> new RuntimeException("Unknown mouse button")).value();
session.getConsole().getMouse().putMouseEvent(x, y, z, w, state);
}
public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs) {
UpdateMouse(sender, x, y, z, w, mbs, true);
UpdateMouse(sender, x, y, z, w, mbs, false);
}
/**
* Adds the specified path to the java library path
*
* @param pathToAdd
* the path to add
* @throws Exception
*/
public static void addLibraryPath(String pathToAdd) throws Exception {
final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
usrPathsField.setAccessible(true);
// get array of paths
final String[] paths = (String[]) usrPathsField.get(null);
// check if the path to add is already present
for (String path : paths) {
if (path.equals(pathToAdd)) {
return;
}
}
// add the new path
final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
newPaths[newPaths.length - 1] = pathToAdd;
usrPathsField.set(null, newPaths);
}
}

View file

@ -0,0 +1,114 @@
package sznp.virtualcomputer;
/*
The scancode values come from:
- http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc (March 16), 2000).
- http://www.computer-engineering.org/ps2keyboard/scancodes1.html
- using MapVirtualKeyEx( VK_*), MAPVK_VK_TO_VSC_EX), 0 ) with the english us keyboard layout
- reading win32 WM_INPUT keyboard messages.
*/
enum Scancode { // https://handmade.network/forums/t/2011-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names
sc_escape(0x01), sc_1(0x02), sc_2(0x03), sc_3(0x04), sc_4(0x05), sc_5(0x06), sc_6(0x07), sc_7(0x08), sc_8(
0x09), sc_9(0x0A), sc_0(0x0B), sc_minus(0x0C), sc_equals(0x0D), sc_backspace(0x0E), sc_tab(0x0F), sc_q(
0x10), sc_w(0x11), sc_e(0x12), sc_r(0x13), sc_t(0x14), sc_y(0x15), sc_u(0x16), sc_i(0x17), sc_o(
0x18), sc_p(0x19), sc_bracketLeft(0x1A), sc_bracketRight(0x1B), sc_enter(
0x1C), sc_controlLeft(0x1D), sc_a(0x1E), sc_s(0x1F), sc_d(
0x20), sc_f(0x21), sc_g(0x22), sc_h(0x23), sc_j(0x24), sc_k(0x25), sc_l(
0x26), sc_semicolon(0x27), sc_apostrophe(0x28), sc_grave(
0x29), sc_shiftLeft(0x2A), sc_backslash(0x2B), sc_z(
0x2C), sc_x(0x2D), sc_c(0x2E), sc_v(0x2F), sc_b(
0x30), sc_n(0x31), sc_m(0x32), sc_comma(
0x33), sc_preiod(0x34), sc_slash(
0x35), sc_shiftRight(
0x36), sc_numpad_multiply(
0x37), sc_altLeft(
0x38), sc_space(
0x39), sc_capsLock(
0x3A), sc_f1(
0x3B), sc_f2(
0x3C), sc_f3(
0x3D), sc_f4(
0x3E), sc_f5(
0x3F), sc_f6(
0x40), sc_f7(
0x41), sc_f8(
0x42), sc_f9(
0x43), sc_f10(
0x44), sc_numLock(
0x45), sc_scrollLock(
0x46), sc_numpad_7(
0x47), sc_numpad_8(
0x48), sc_numpad_9(
0x49), sc_numpad_minus(
0x4A), sc_numpad_4(
0x4B), sc_numpad_5(
0x4C), sc_numpad_6(
0x4D), sc_numpad_plus(
0x4E), sc_numpad_1(
0x4F), sc_numpad_2(
0x50), sc_numpad_3(
0x51), sc_numpad_0(
0x52), sc_numpad_period(
0x53), sc_alt_printScreen(
0x54), /*
* Alt
* +
* print
* screen.
* MapVirtualKeyEx(
* VK_SNAPSHOT
* )
* ,
* MAPVK_VK_TO_VSC_EX
* )
* ,
* 0
* )
* returns
* scancode
* 0x54.
*/
sc_bracketAngle(0x56), /* Key between the left shift and Z. */
sc_f11(0x57), sc_f12(0x58), sc_oem_1(0x5a), /* VK_OEM_WSCTRL */
sc_oem_2(0x5b), /* VK_OEM_FINISH */
sc_oem_3(0x5c), /* VK_OEM_JUMP */
sc_eraseEOF(0x5d), sc_oem_4(0x5e), /* VK_OEM_BACKTAB */
sc_oem_5(0x5f), /* VK_OEM_AUTO */
sc_zoom(0x62), sc_help(0x63), sc_f13(0x64), sc_f14(0x65), sc_f15(0x66), sc_f16(0x67), sc_f17(0x68), sc_f18(
0x69), sc_f19(
0x6a), sc_f20(0x6b), sc_f21(0x6c), sc_f22(0x6d), sc_f23(0x6e), sc_oem_6(0x6f), /* VK_OEM_PA3 */
sc_katakana(0x70), sc_oem_7(0x71), /* VK_OEM_RESET */
sc_f24(0x76), sc_sbcschar(0x77), sc_convert(0x79), sc_nonconvert(0x7B), /* VK_OEM_PA1 */
sc_media_previous(0xE010), sc_media_next(0xE019), sc_numpad_enter(0xE01C), sc_controlRight(0xE01D), sc_volume_mute(
0xE020), sc_launch_app2(0xE021), sc_media_play(0xE022), sc_media_stop(
0xE024), sc_volume_down(0xE02E), sc_volume_up(
0xE030), sc_browser_home(0xE032), sc_numpad_divide(0xE035), sc_printScreen(0xE037),
/*
* sc_printScreen: - make: 0xE02A 0xE037 - break: 0xE0B7 0xE0AA - MapVirtualKeyEx( VK_SNAPSHOT), MAPVK_VK_TO_VSC_EX), 0 ) returns scancode 0x54; - There is no VK_KEYDOWN with VK_SNAPSHOT.
*/
sc_altRight(0xE038), sc_cancel(0xE046), /* CTRL + Pause */
sc_home(0xE047), sc_arrowUp(0xE048), sc_pageUp(0xE049), sc_arrowLeft(0xE04B), sc_arrowRight(0xE04D), sc_end(
0xE04F), sc_arrowDown(0xE050), sc_pageDown(0xE051), sc_insert(0xE052), sc_delete(0xE053), sc_metaLeft(
0xE05B), sc_metaRight(0xE05C), sc_application(0xE05D), sc_power(0xE05E), sc_sleep(0xE05F), sc_wake(
0xE063), sc_browser_search(0xE065), sc_browser_favorites(0xE066), sc_browser_refresh(
0xE067), sc_browser_stop(0xE068), sc_browser_forward(0xE069), sc_browser_back(
0xE06A), sc_launch_app1(
0xE06B), sc_launch_email(0xE06C), sc_launch_media(0xE06D),
sc_pause(0xE11D45);
/*
* sc_pause: - make: 0xE11D 45 0xE19D C5 - make in raw input: 0xE11D 0x45 - break: none - No repeat when you hold the key down - There are no break so I don't know how the key down/up is expected
* to work. Raw input sends "keydown" and "keyup" messages), and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when
* GetMessage or PeekMessage will return messages), you may get both a keydown and keyup message "at the same time". If you use VK messages most of the time you only get keydown messages), but
* some times you get keyup messages too. - when pressed at the same time as one or both control keys, generates a 0xE046 (sc_cancel) and the string for that scancode is "break".
*/
public int Code;
Scancode(int code) {
Code = code;
}
};