Directly using VirtualBox from Java #5

Merged
NorbiPeti merged 60 commits from directvb into master 2019-04-18 23:29:21 +00:00
6 changed files with 271 additions and 192 deletions
Showing only changes of commit 2724e123fa - Show all commits

View file

@ -5,6 +5,7 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import sznp.virtualcomputer.events.Computer;
public class Commands implements CommandExecutor { public class Commands implements CommandExecutor {
@ -22,14 +23,14 @@ public class Commands implements CommandExecutor {
sender.sendMessage("§cUsage: /" + label + " start [index]"); sender.sendMessage("§cUsage: /" + label + " start [index]");
return true; return true;
} }
PluginMain.Instance.Start(sender, c); Computer.getInstance().Start(sender, c);
break; break;
case "stop": case "stop":
case "poweroff": case "poweroff":
case "off": case "off":
case "shutdown": case "shutdown":
case "kill": case "kill":
PluginMain.Instance.Stop(sender); Computer.getInstance().Stop(sender);
break; break;
case "powerbutton": case "powerbutton":
case "pwrbtn": case "pwrbtn":
@ -39,15 +40,15 @@ public class Commands implements CommandExecutor {
sender.sendMessage("§cUsage: /" + label + " powerbutton [index]"); sender.sendMessage("§cUsage: /" + label + " powerbutton [index]");
return true; return true;
} }
PluginMain.Instance.PowerButton(sender, c); Computer.getInstance().PowerButton(sender, c);
break; break;
case "reset": case "reset":
case "restart": case "restart":
PluginMain.Instance.Reset(sender); Computer.getInstance().Reset(sender);
break; break;
case "fix": case "fix":
case "fixscreen": case "fixscreen":
PluginMain.Instance.FixScreen(sender); Computer.getInstance().FixScreen(sender);
break; break;
case "key": case "key":
case "press": case "press":
@ -58,9 +59,9 @@ public class Commands implements CommandExecutor {
return true; return true;
} }
if (args.length < 3) if (args.length < 3)
PluginMain.Instance.PressKey(sender, args[1], ""); Computer.getInstance().PressKey(sender, args[1], "");
else else
PluginMain.Instance.PressKey(sender, args[1], args[2]); Computer.getInstance().PressKey(sender, args[1], args[2]);
break; break;
case "mouse": case "mouse":
boolean showusage = true; boolean showusage = true;
@ -68,15 +69,15 @@ public class Commands implements CommandExecutor {
// Command overloading, because I can :P // Command overloading, because I can :P
if (args.length > 4) // 4<x<6 if (args.length > 4) // 4<x<6
{ {
PluginMain.Instance.UpdateMouse(sender, Integer.parseInt(args[1]), Integer.parseInt(args[2]), Computer.getInstance().UpdateMouse(sender, Integer.parseInt(args[1]), Integer.parseInt(args[2]),
Integer.parseInt(args[3]), Integer.parseInt(args[4]), "", false); Integer.parseInt(args[3]), Integer.parseInt(args[4]), "", false);
showusage = false; showusage = false;
} else { } else {
if (args.length == 3) { if (args.length == 3) {
PluginMain.Instance.UpdateMouse(sender, 0, 0, 0, 0, args[1], args[2].equals("down")); Computer.getInstance().UpdateMouse(sender, 0, 0, 0, 0, args[1], args[2].equals("down"));
showusage = false; showusage = false;
} else if (args.length == 2) { } else if (args.length == 2) {
PluginMain.Instance.UpdateMouse(sender, 0, 0, 0, 0, args[1]); Computer.getInstance().UpdateMouse(sender, 0, 0, 0, 0, args[1]);
showusage = false; showusage = false;
} }
} }

View file

@ -9,6 +9,7 @@ import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import org.virtualbox_6_0.*; import org.virtualbox_6_0.*;
import sznp.virtualcomputer.events.VBoxEventHandler;
import sznp.virtualcomputer.renderer.BukkitRenderer; import sznp.virtualcomputer.renderer.BukkitRenderer;
import sznp.virtualcomputer.renderer.GPURenderer; import sznp.virtualcomputer.renderer.GPURenderer;
import sznp.virtualcomputer.renderer.IRenderer; import sznp.virtualcomputer.renderer.IRenderer;
@ -22,6 +23,8 @@ import java.lang.reflect.Field;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Predicate; import java.util.function.Predicate;
public class PluginMain extends JavaPlugin { public class PluginMain extends JavaPlugin {
@ -75,12 +78,13 @@ public class PluginMain extends JavaPlugin {
VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom"); VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom");
vbl.RTR3InitExe(0, "", 0); vbl.RTR3InitExe(0, "", 0);
vbox = manager.getVBox(); vbox = manager.getVBox();
vbox.getEventSource().registerListener(new IEventListener(new VBoxEventHandler()), Arrays.asList(VBoxEventType.OnMachineStateChanged), true);
session = manager.getSessionObject(); // TODO: Events session = manager.getSessionObject(); // TODO: Events
ccs.sendMessage("§bLoading Screen..."); ccs.sendMessage("§bLoading Screen...");
try { try {
//throw new NoClassDefFoundError("Test error pls ignore"); //throw new NoClassDefFoundError("Test error pls ignore");
for (short i = 0; i < 5; i++) for (short i = 0; i < MCX; i++)
for (short j = 0; j < 4; j++) for (short j = 0; j < MCY; j++)
renderers.add(new GPURenderer((short) (j * 5 + i), Bukkit.getWorlds().get(0), i, j)); renderers.add(new GPURenderer((short) (j * 5 + i), Bukkit.getWorlds().get(0), i, j));
//pxc = LibraryLoader.create(PXCLib.class).search(getDataFolder().getAbsolutePath()).load("pxc"); //pxc = LibraryLoader.create(PXCLib.class).search(getDataFolder().getAbsolutePath()).load("pxc");
direct=true; direct=true;
@ -123,186 +127,6 @@ public class PluginMain extends JavaPlugin {
saveConfig(); saveConfig();
} }
public void Start(CommandSender sender, int index) {// TODO: Add touchscreen support (#2)
if (session.getState() == SessionState.Locked) {
sender.sendMessage("§cThe machine is already running!");
return;
}
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
if (vbox.getMachines().size() <= index) {
sendMessage(sender, "§cMachine not found!");
return;
}
try {
sendMessage(sender, "§eStarting computer...");
machine = vbox.getMachines().get(index);
session.setName("minecraft");
// machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore
machine.lockMachine(session, LockType.VM); // We want the machine inside *our* process <-- Need the VM type to have console access
final Runnable tr = new Runnable() {
public void run() {
if (session.getState() != SessionState.Locked) { // https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#ac82c179a797c0d7c249d1b98a8e3aa8f
Bukkit.getScheduler().runTaskLaterAsynchronously(PluginMain.this, this, 5);
return; // "This state also occurs as a short transient state during an IMachine::lockMachine call."
}
machine = session.getMachine(); // This is the Machine object we can work with
final IConsole console = session.getConsole();
console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
console.getDisplay().attachFramebuffer(0L,
new IFramebuffer(new MCFrameBuffer(console.getDisplay(), true)));
startScreenTask(console, sender);
}
};
Bukkit.getScheduler().runTaskLaterAsynchronously(this, tr, 5);
} catch (VBoxException e) {
if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited"
sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible");
return; //TODO: If we have VirtualBox open, it won't close the server's port
//TODO: Can't detect if machine fails to start because of hardening issues
//TODO: This error also occurs if the machine has failed to start at least once (always reassign the machine?)
//machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way
//session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new MCFrameBuffer(session.getConsole().getDisplay(), false)));
//sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method.");
} else {
sendMessage(sender, "§cFailed to start computer: " + e.getMessage());
return;
}
}
sendMessage(sender, "§eComputer started.");
});
}
/**
* Right now only checks if the computer has turned off.
*/
private void startScreenTask(IConsole console, CommandSender sender) {
if (screenupdatetask == null)
screenupdatetask = Bukkit.getScheduler().runTaskTimerAsynchronously(PluginMain.this, () -> {
/*if (session.getState().equals(SessionState.Locked) // Don't run until the machine is running
&& console.getState().equals(MachineState.Running))
console.getDisplay().invalidateAndUpdateScreen(0L);*/
if (session.getState().equals(SessionState.Unlocked) // Stop if the machine stopped fully
|| console.getState().equals(MachineState.PoweredOff)
|| console.getState().equals(MachineState.Saved)) {
if (session.getState().equals(SessionState.Locked)) {
session.unlockMachine();
sendMessage(sender, "Computer powered off, released it.");
}
screenupdatetask.cancel();
screenupdatetask = null;
}
}, 100, 100); // Do a full update every 5 seconds
}
private void sendMessage(CommandSender sender, String message) {
sender.sendMessage(message);
getLogger().warning(sender.getName() + ": " + ChatColor.stripColor(message));
}
public void Stop(CommandSender sender) {
if (checkMachineNotRunning(sender)) {
if (session.getState().equals(SessionState.Locked)) {
session.unlockMachine();
sendMessage(sender, "§eComputer powered off, released it.");
}
return;
}
sendMessage(sender, "§eStopping computer...");
session.getConsole().powerDown().waitForCompletion(2000);
session.unlockMachine();
sendMessage(sender, "§eComputer stopped.");
}
public void PowerButton(CommandSender sender, int index) {
sendMessage(sender, "§ePressing powerbutton...");
getServer().getScheduler().runTaskAsynchronously(this, new Runnable() {
@Override
public void run() {
if (session.getState() != SessionState.Locked || session.getMachine() == null) {
Start(sender, index);
} else {
session.getConsole().powerButton();
sendMessage(sender, "§ePowerbutton pressed.");
}
}
});
}
public void Reset(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
sendMessage(sender, "§eResetting computer...");
session.getConsole().reset();
sendMessage(sender, "§eComputer reset.");
}
public void FixScreen(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
sendMessage(sender, "§eFixing screen...");
session.getConsole().getDisplay().setSeamlessMode(false);
session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L);
sendMessage(sender, "§eScreen fixed.");
}
public boolean checkMachineNotRunning(@Nullable CommandSender sender) {
if (session.getState() != SessionState.Locked || machine.getState() != MachineState.Running) {
if (sender != null)
sender.sendMessage("§cMachine isn't running.");
return true;
}
return false;
}
public void PressKey(CommandSender sender, String key, String stateorduration) {
if (checkMachineNotRunning(sender))
return;
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;
try {
code = Scancode.valueOf("sc_" + key.toLowerCase()).Code;
} catch (IllegalArgumentException e) {
sender.sendMessage("§cUnknown key: " + key);
return;
}
// 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 (checkMachineNotRunning(sender))
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) {
if (checkMachineNotRunning(sender))
return;
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 * Adds the specified path to the java library path
* *

View file

@ -0,0 +1,195 @@
package sznp.virtualcomputer.events;
import com.google.common.collect.Lists;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.virtualbox_6_0.*;
import sznp.virtualcomputer.PluginMain;
import sznp.virtualcomputer.renderer.MCFrameBuffer;
import sznp.virtualcomputer.util.Scancode;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
public final class Computer {
@Getter
private static Computer instance;
private final PluginMain plugin;
private ISession session;
private IVirtualBox vbox;
private IMachine machine;
@java.beans.ConstructorProperties({"plugin"})
public Computer(PluginMain plugin) {
this.plugin = plugin;
if(instance!=null) throw new IllegalStateException("A computer already exists!");
instance=this; //TODO: Move some init stuff here
}
public void Start(CommandSender sender, int index) {// TODO: Add touchscreen support (#2)
if (session.getState() == SessionState.Locked) {
sender.sendMessage("§cThe machine is already running!");
return;
}
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
if (vbox.getMachines().size() <= index) {
sendMessage(sender, "§cMachine not found!");
return;
}
try {
sendMessage(sender, "§eStarting computer...");
machine = vbox.getMachines().get(index);
session.setName("minecraft");
// machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore
machine.lockMachine(session, LockType.VM); // We want the machine inside *our* process <-- Need the VM type to have console access
final Runnable tr = new Runnable() {
public void run() {
if (session.getState() != SessionState.Locked) { // https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#ac82c179a797c0d7c249d1b98a8e3aa8f
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, this, 5);
return; // "This state also occurs as a short transient state during an IMachine::lockMachine call."
}
machine = session.getMachine(); // This is the Machine object we can work with
final IConsole console = session.getConsole();
console.getEventSource().registerListener(new IEventListener(new MachineEventHandler(Computer.this)), Collections.singletonList(VBoxEventType.MachineEvent), true);
console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
console.getDisplay().attachFramebuffer(0L,
new IFramebuffer(new MCFrameBuffer(console.getDisplay(), true)));
}
};
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, tr, 5);
} catch (VBoxException e) {
if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited"
sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible");
return; //TODO: If we have VirtualBox open, it won't close the server's port
//TODO: Can't detect if machine fails to start because of hardening issues
//TODO: This error also occurs if the machine has failed to start at least once (always reassign the machine?)
//machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way
//session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new MCFrameBuffer(session.getConsole().getDisplay(), false)));
//sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method.");
} else {
sendMessage(sender, "§cFailed to start computer: " + e.getMessage());
return;
}
}
sendMessage(sender, "§eComputer started.");
});
}
private void sendMessage(@Nullable CommandSender sender, String message) {
if(sender!=null)
sender.sendMessage(message);
plugin.getLogger().warning((sender==null?"":sender.getName() + ": ") + ChatColor.stripColor(message));
}
public void Stop(CommandSender sender) {
if (checkMachineNotRunning(sender)) {
if (session.getState().equals(SessionState.Locked)) {
session.unlockMachine();
sendMessage(sender, "§eComputer powered off, released it.");
}
return;
}
sendMessage(sender, "§eStopping computer...");
session.getConsole().powerDown().waitForCompletion(2000);
session.unlockMachine();
sendMessage(sender, "§eComputer stopped.");
}
public void PowerButton(CommandSender sender, int index) {
sendMessage(sender, "§ePressing powerbutton...");
Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
if (session.getState() != SessionState.Locked || session.getMachine() == null) {
Start(sender, index);
} else {
session.getConsole().powerButton();
sendMessage(sender, "§ePowerbutton pressed.");
}
}
});
}
public void Reset(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
sendMessage(sender, "§eResetting computer...");
session.getConsole().reset();
sendMessage(sender, "§eComputer reset.");
}
public void FixScreen(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
sendMessage(sender, "§eFixing screen...");
session.getConsole().getDisplay().setSeamlessMode(false);
session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L);
sendMessage(sender, "§eScreen fixed.");
}
public boolean checkMachineNotRunning(@Nullable CommandSender sender) {
if (session.getState() != SessionState.Locked || machine.getState() != MachineState.Running) {
if (sender != null)
sender.sendMessage("§cMachine isn't running.");
return true;
}
return false;
}
public void PressKey(CommandSender sender, String key, String stateorduration) {
if (checkMachineNotRunning(sender))
return;
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;
try {
code = Scancode.valueOf("sc_" + key.toLowerCase()).Code;
} catch (IllegalArgumentException e) {
sender.sendMessage("§cUnknown key: " + key);
return;
}
// 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(plugin, sendrelease, durationorstate);
}
}
public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs, boolean down) {
if (checkMachineNotRunning(sender))
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) {
if (checkMachineNotRunning(sender))
return;
UpdateMouse(sender, x, y, z, w, mbs, true);
UpdateMouse(sender, x, y, z, w, mbs, false);
}
public void stopRendering() {
//TODO
}
}

View file

@ -0,0 +1,29 @@
package sznp.virtualcomputer.events;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.mozilla.interfaces.IEvent;
import org.mozilla.interfaces.IEventListener;
import org.virtualbox_6_0.IStateChangedEvent;
import org.virtualbox_6_0.MachineState;
import sznp.virtualcomputer.renderer.MCFrameBuffer;
import sznp.virtualcomputer.util.COMObjectBase;
@RequiredArgsConstructor
public class MachineEventHandler extends COMObjectBase implements IEventListener {
private final Computer computer;
@Override
public void handleEvent(IEvent iEvent) {
if(iEvent instanceof IStateChangedEvent) {
val event=(IStateChangedEvent) iEvent; //https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#a80b08f71210afe16038e904a656ed9eb
switch (event.getState()) {
case Stuck:
computer.Stop(null);
break;
case PoweredOff:
case Saved:
computer.stopRendering();
}
}
}
}

View file

@ -0,0 +1,19 @@
package sznp.virtualcomputer.events;
import lombok.val;
import org.mozilla.interfaces.IEvent;
import org.mozilla.interfaces.IEventListener;
import org.mozilla.interfaces.nsISupports;
import org.mozilla.xpcom.Mozilla;
import org.virtualbox_6_0.IMachineStateChangedEvent;
import org.virtualbox_6_0.VBoxEventType;
import sznp.virtualcomputer.util.COMObjectBase;
public class VBoxEventHandler extends COMObjectBase implements IEventListener {
@Override
public void handleEvent(IEvent iEvent) {
if(iEvent.getType()== VBoxEventType.OnMachineStateChanged.value()) {
val event=(IMachineStateChangedEvent) iEvent;
}
}
}

View file

@ -0,0 +1,11 @@
package sznp.virtualcomputer.util;
import org.mozilla.interfaces.nsISupports;
import org.mozilla.xpcom.Mozilla;
public abstract class COMObjectBase implements nsISupports {
public nsISupports queryInterface(String id) {
return Mozilla.queryInterface(this, id);
}
}