Compare commits

...

26 commits

Author SHA1 Message Date
f4871574d3
Add check for when the session state doesn't actually change 2021-07-15 22:37:08 +02:00
2d08ef55fa
Pass the embedded option to the Computer 2021-03-10 02:04:18 +01:00
e23e062be9
Some fixes and attempts to fix the screen 2021-02-20 18:16:54 +01:00
4a89e547a4
Improve encapsulation and support of the rendering modes 2021-02-20 16:51:43 +01:00
61f3192760
Imitial support for running in a separate process
(Again)
2021-02-20 03:36:03 +01:00
fd9803c6f1
Some fixes
It's really slow now and I don't know why
2020-11-28 01:17:22 +01:00
2c6eb3a610
Add support for disabling/enabling the plugin
And autostart can be disabled too
2020-11-28 00:14:26 +01:00
6058842b61
Fixes, update Aparapi, set layout command 2020-10-14 22:47:29 +02:00
86b8e363e2
Info, status cmd, spawn cmd, config, render crash check
The spawn command is 1.13+
2020-10-14 01:15:00 +02:00
a32f80fb79
Converted and improved the commands 2020-10-13 02:57:35 +02:00
559b2989c6
Started integrating with Chroma-Core 2020-09-03 02:01:25 +02:00
26d8cc1ff0 Added source for VBoxJXPCOM 2020-08-12 00:15:49 +02:00
580d48d7d0 Fixes for Windows and MSCOM implementation
Still can't pass Java objects to COM
2020-08-11 19:00:00 +02:00
586ab05033
Screen fixes, improved update method
Fixed updates stopping, but not the cursor
NotifyUpdate handling is in a separate thread to avoid slowing the machine and it will only perform one update at a time that reflects the current state
2020-08-03 22:49:25 +02:00
32cea5c384
Attempts to fix screen on Ubuntu after /c fix
It stops sending updates after running the fix command
2020-08-03 02:43:31 +02:00
3ce2da7f7f
Add tab complete support 2020-08-01 23:15:10 +02:00
de13d9876d
Some fixes 2020-08-01 20:26:18 +02:00
cd0e1d01b6
Update to VirtualBox 6.1
It's about time
2020-08-01 00:14:30 +02:00
c98fcdb1c0
Some docs and older pom stuff 2019-07-03 22:13:16 +02:00
6de24a2742
Some fixes
It compiles for sure
2019-05-18 02:29:10 +02:00
751cc5eb92 Some fixes, some errors 2019-05-08 17:49:11 +02:00
9ba0c3e820
Moving XPCOM stuff 2019-04-22 22:21:48 +02:00
515fb1f4d1
Server version independence, deps
Hopefully made the renderer version-independent
2019-04-22 01:04:01 +02:00
b0ad790f72
Using separate modules 2019-04-22 00:39:49 +02:00
b7d0b49ac6
Revert "Attempt to use MSCOM"
This reverts commit ea6c2485
2019-04-21 23:27:58 +02:00
ea6c248534
Attempt to use MSCOM
It starts to get messy
2019-04-21 23:26:40 +02:00
73 changed files with 2007 additions and 1102 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
libpxc.so
dependency-reduced-pom.xml
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

View file

@ -1,10 +1,15 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer</artifactId>
<version>2.0-SNAPSHOT</version>
<artifactId>VirtualComputer-Core</artifactId>
<version>2.1-SNAPSHOT</version>
<parent>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer</artifactId>
<version>2.1-SNAPSHOT</version>
</parent>
<build>
<finalName>VirtualComputer</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
@ -20,7 +25,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
@ -32,68 +37,34 @@
</execution>
</executions>
</plugin>
</plugins>
</plugins>
</build>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>repo</id>
<url>file://${basedir}/repo</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<dependency> <!-- This has the least additional (platform-dependent) code -->
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox-MSCOM</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.jnr/jnr-ffi -->
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-ffi</artifactId>
<version>2.1.7</version>
<artifactId>VirtualBox-MSCOM</artifactId>
<version>6.1</version>
</dependency>
<dependency>
<groupId>com.aparapi</groupId>
<artifactId>aparapi</artifactId>
<version>1.10.0</version>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer-XPCOM</artifactId>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency> <!-- javax.annotations.Nullable -->
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.0</version>
<dependency>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer-MSCOM</artifactId>
<version>2.1-SNAPSHOT</version>
</dependency>
<!-- <dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.8</version>
</dependency> -->
</dependencies>
<profiles>
<profile>
@ -112,8 +83,26 @@
<artifactSet>
<excludes>
<exclude>org.virtualbox:VirtualBox-MSCOM</exclude>
<exclude>io.github.NorbiPeti:VirtualComputer-MSCOM</exclude>
</excludes>
</artifactSet>
<!-- <minimizeJar>true</minimizeJar> -->
<filters>
<filter>
<artifact>io.github.NorbiPeti:VirtualComputer-XPCOM</artifact>
<includes>
<include>**
</include> <!-- Don't delete org.mozilla.interfaces.internal when minimizing -->
</includes>
</filter>
<filter>
<artifact>com.github.jnr:jnr-ffi</artifact>
<includes>
<include>**
</include> <!-- Don't delete org.mozilla.interfaces.internal when minimizing -->
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
@ -137,6 +126,7 @@
<artifactSet>
<excludes>
<exclude>org.virtualbox:VirtualBox</exclude>
<exclude>io.github.NorbiPeti:VirtualComputer-XPCOM</exclude>
</excludes>
</artifactSet>
</configuration>

View file

@ -0,0 +1,393 @@
package sznp.virtualcomputer;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.val;
import lombok.var;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.virtualbox_6_1.*;
import sznp.virtualcomputer.events.MachineEventHandler;
import sznp.virtualcomputer.events.VBoxEventHandler;
import sznp.virtualcomputer.renderer.GPURenderer;
import sznp.virtualcomputer.renderer.MCFrameBuffer;
import sznp.virtualcomputer.util.COMUtils;
import sznp.virtualcomputer.util.Scancode;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
public final class Computer {
@Getter
private static Computer instance;
private final PluginMain plugin;
private ISession session;
private final IVirtualBox vbox;
private IMachine machine;
private MachineEventHandler handler;
private IEventListener listener;
private final VirtualBoxManager manager;
private MCFrameBuffer framebuffer;
private final boolean direct;
private final boolean embedded;
public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox, boolean direct, boolean embedded) {
this.plugin = plugin;
this.manager = manager;
session = manager.getSessionObject();
this.vbox = vbox;
this.direct = direct;
this.embedded = embedded;
if (instance != null) throw new IllegalStateException("A computer already exists!");
instance = this;
}
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 (index < 0 || vbox.getMachines().size() <= index) {
sendMessage(sender, "§cMachine not found!");
return;
}
try {
sendMessage(sender, "§eStarting computer...");
machine = vbox.getMachines().get(index);
if (!machine.getAccessible()) {
sendMessage(sender, "§cMachine is not accessible! " + machine.getAccessError().getText());
return;
}
session.setName("minecraft");
VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null
synchronized (this) {
if (embedded)
machine.lockMachine(session, LockType.VM); //Run in our process <-- Need the VM type to have console access
else {
val progress = machine.launchVMProcess(session, "headless", Collections.emptyList()); //Run in a separate process
onStartSetProgress(progress, sender);
}
}
} catch (VBoxException e) {
if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited"
sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible");
sendMessage(sender, "§6Make sure that the server is running as root (sudo)");
//TODO: If we have VirtualBox open, it won't close the server's port
//TODO: "The object in question already exists." on second start
} else {
sendMessage(sender, "§cFailed to start computer: " + e.getMessage());
}
}
});
}
public void List(CommandSender sender) {
sender.sendMessage("§bAvailable machines:");
val machines = vbox.getMachines();
for (int i = 0; i < machines.size(); i++) {
val m = machines.get(i);
if (m.getAccessible())
sender.sendMessage("[" + i + "] " + m.getName() + " - " + m.getState());
else
sender.sendMessage("[" + i + "] <Inaccessible, check VirtualBox>");
}
}
/**
* Gets called when the machine is locked after {@link #Start(CommandSender, int)}
*
* @param sender The sender which started the machine
*/
public void onLock(CommandSender sender) {
System.out.println("A");
if (session.getState() != SessionState.Locked) {
sendMessage(sender, "§cFailed to start computer! Failed to lock session");
return;
}
machine = session.getMachine(); // This is the Machine object we can work with
final IConsole console = session.getConsole();
if (embedded) { //Otherwise it's set while starting the VM
IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
onStartSetProgress(progress, sender);
}
System.out.println("B");
System.out.println("State: " + console.getState());
listener = handler.registerTo(console.getEventSource());
System.out.println("State: " + console.getState());
val fb = new MCFrameBuffer(console.getDisplay(), plugin, embedded, direct);
System.out.println("C");
if (embedded)
fb.startEmbedded();
String fbid = console.getDisplay().attachFramebuffer(0L,
COMUtils.gimmeAFramebuffer(fb));
System.out.println("State: " + console.getState());
System.out.println("D"); //TODO: No UpdateImage
fb.setId(fbid);
framebuffer = fb;
}
private void onStartSetProgress(IProgress progress, CommandSender sender) {
if (handler != null)
handler.disable();
handler = new MachineEventHandler(Computer.this, sender);
handler.setProgress(progress);
handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way?
}
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)) {
onMachineStop(sender); //Needed for session reset
sendMessage(sender, "§eComputer was already off, released it.");
}
return;
}
sendMessage(sender, "§eStopping computer...");
synchronized (this) {
session.getConsole().powerDown();
}
}
public void PowerButton(CommandSender sender, int index) {
sendMessage(sender, "§ePressing powerbutton...");
Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
if (session.getState() != SessionState.Locked || session.getMachine() == null) {
Start(sender, index);
} else {
synchronized (this) {
session.getConsole().powerButton();
}
sendMessage(sender, "§ePowerbutton pressed.");
}
});
}
public void Reset(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
sendMessage(sender, "§eResetting computer...");
synchronized (this) {
session.getConsole().reset();
}
sendMessage(sender, "§eComputer reset.");
}
public void SaveState(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
sendMessage(sender, "§eSaving computer state...");
synchronized (this) {
session.getMachine().saveState();
}
}
public void FixScreen(CommandSender sender) {
if (checkMachineNotRunning(sender))
return;
if (framebuffer == null) {
sender.sendMessage("§cFramebuffer is null...");
return;
}
sendMessage(sender, "§eFixing screen...");
try {
synchronized (this) {
session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L, false);
} //Last param: notify - send PnP notification - stops updates but not changes for some reason
Bukkit.getScheduler().runTaskLaterAsynchronously(PluginMain.Instance, () -> {
synchronized (this) {
session.getConsole().getMouse().putMouseEventAbsolute(-1, -1, 0, 0, 0);
session.getConsole().getMouse().putMouseEvent(0, 0, 0, 0, 0); //Switch to relative mode
sendMessage(sender, "§eScreen fixed.");
}
}, 20);
} catch (Exception e) {
e.printStackTrace();
}
}
private 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 = Scancode.getCode("sc_" + key.toLowerCase());
if (code == -1) {
sender.sendMessage("§cUnknown key: " + key);
return;
}
// Release key scan code concept taken from VirtualBox source code (KeyboardImpl.cpp:putCAD())
// +128
synchronized (this) {
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 * 20);
}
}
}
public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs, boolean down) throws Exception {
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 Exception("Unknown mouse button")).value();
synchronized (this) {
session.getConsole().getMouse().putMouseEvent(x, y, z, w, state);
}
}
public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs) throws Exception {
if (checkMachineNotRunning(sender))
return;
UpdateMouse(sender, x, y, z, w, mbs, true);
UpdateMouse(sender, x, y, z, w, mbs, false);
}
public void Status(CommandSender sender) {
switch (session.getState()) {
case Spawning:
sender.sendMessage("§bThe computer session is currently starting.");
break;
case Unlocking:
sender.sendMessage("§bThe computer session is currently stopping.");
break;
case Unlocked:
sender.sendMessage("§bThe computer is currently powered off. Use /c start to start it.");
break;
case Null:
sender.sendMessage("§bUnknown state! Try /c stop if the machine isn't running.");
break;
case Locked:
sender.sendMessage("§bThe computer session is active.");
break;
}
if (machine == null)
return;
switch (machine.getState()) {
case Aborted:
sender.sendMessage("§bThe computer is powered off. It was unexpectedly shut down last time.");
return;
case Paused:
sender.sendMessage("§bThe machine is currently paused.");
return;
case PoweredOff:
sender.sendMessage("§bThe computer is currently powered off.");
return;
case Restoring:
sender.sendMessage("§bThe computer is restoring a saved state. This can take a while...");
return;
case Running:
sender.sendMessage("§bThe computer is currently running.");
return;
case Saving:
sender.sendMessage("§bThe computer is saving the current state. This can take a while...");
return;
case Saved:
sender.sendMessage("§bThe computer is powered off. It has a saved state it will load on start.");
return;
case Starting:
sender.sendMessage("§bThe computer is currently starting...");
return;
case Stopping:
sender.sendMessage("§bThe computer is currently stopping...");
return;
case SettingUp:
sender.sendMessage("§bThe computer is setting up...");
break;
case Stuck:
sender.sendMessage("§bThe computer is stuck. Use /c stop.");
break;
}
if (session.getState() == SessionState.Locked) {
if (machine.getState() == MachineState.Running) {
var con = session.getConsole();
Holder<Long> w = new Holder<>(), h = new Holder<>(), bpp = new Holder<>();
Holder<Integer> xo = new Holder<>(), yo = new Holder<>();
var gms = new Holder<GuestMonitorStatus>();
con.getDisplay().getScreenResolution(0L, w, h, bpp, xo, yo, gms);
sender.sendMessage("§bScreen info: " + w.value + "x" + h.value + " (" + bpp.value + ") at " + xo.value + " " + yo.value + " - " + gms.value);
sender.sendMessage("§bKeyboard LEDs: " + con.getKeyboard().getKeyboardLEDs().stream().map(Enum::toString).collect(Collectors.joining(", ")));
} else if (machine.getState().value() < MachineState.FirstOnline.value()
|| machine.getState().value() > MachineState.LastOnline.value())
sender.sendMessage("§bUse /c stop to fix the computer.");
}
}
public void onMachineStart(CommandSender sender) {
sendMessage(sender, "§eComputer started.");
}
public void onMachineStop(CommandSender sender) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
if (session.getState() == SessionState.Locked) {
synchronized (this) {
session.unlockMachine(); //Needs to be outside of the event handler
}
handler = null;
machine = null;
session = manager.getSessionObject();
sendMessage(sender, "§eComputer powered off."); //This block runs later
}
});
GPURenderer.update(new byte[1], 0, 0, 0, 0, 640, 480); //Black screen
stopEvents();
MouseLockerPlayerListener.computerStop();
if (framebuffer != null)
framebuffer.stop();
}
private void stopEvents() {
/*if(session!=null && listener!=null)
session.getConsole().getEventSource().unregisterListener(listener);*/
if (listener != null)
handler.disable();
listener = null;
}
public void pluginDisable(CommandSender ccs) {
stopEvents();
if (framebuffer != null)
framebuffer.stop();
if (session.getState() == SessionState.Locked) {
if (session.getMachine().getState().equals(MachineState.Running)) {
ccs.sendMessage("§aSaving machine state...");
session.getMachine().saveState().waitForCompletion(10000);
}
session.unlockMachine();
}
instance = null; //Allow setting it again
}
}

View file

@ -0,0 +1,248 @@
package sznp.virtualcomputer;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.CustomTabComplete;
import buttondevteam.lib.chat.ICommand2MC;
import lombok.var;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.MapMeta;
import org.virtualbox_6_1.VBoxException;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Objects;
@CommandClass(helpText = {
"Computer plugin",
"§bTo see the computer's status do /c status",
"§bTo see the available machines do /c list",
"§bTo start one of them do /c start <index>",
"§bYou can only have one machine running at a time."
}, permGroup = "computer")
public class ComputerCommand extends ICommand2MC {
@Command2.Subcommand
public void status(CommandSender sender) {
if (checkDisabled(sender)) return;
Computer.getInstance().Status(sender);
}
@Command2.Subcommand(aliases = {"poweron", "on"}, helpText = {
"Start",
"Starts the given virtual machine or the first one by default.",
"Use /c list to see the index of the machines."
})
public void start(CommandSender sender, @Command2.OptionalArg int index) {
if (checkDisabled(sender)) return;
Computer.getInstance().Start(sender, index);
}
@Command2.Subcommand
public void list(CommandSender sender) {
if (checkDisabled(sender)) return;
Computer.getInstance().List(sender);
}
@Command2.Subcommand(aliases = {"poweroff", "off", "kill"})
public void stop(CommandSender sender) {
if (checkDisabled(sender)) return;
Computer.getInstance().Stop(sender);
}
@Command2.Subcommand(aliases = {"powerbtn", "pwrbtn"})
public void powerbutton(CommandSender sender, @Command2.OptionalArg int index) {
if (checkDisabled(sender)) return;
Computer.getInstance().PowerButton(sender, index);
}
@Command2.Subcommand(aliases = "restart")
public void reset(CommandSender sender) {
if (checkDisabled(sender)) return;
Computer.getInstance().Reset(sender);
}
@Command2.Subcommand(aliases = "save state")
public void save(CommandSender sender) {
if (checkDisabled(sender)) return;
Computer.getInstance().SaveState(sender);
}
@Command2.Subcommand(aliases = "fix screen")
public void fix(CommandSender sender) {
if (checkDisabled(sender)) return;
Computer.getInstance().FixScreen(sender);
}
@Command2.Subcommand(aliases = {"press", "presskey", "keypress"}, helpText = {
"Press key",
"Presses the specified key. Valid values for the last param are 'down', 'up' and amount of ticks to hold."
})
public void key(CommandSender sender, String key, @Command2.OptionalArg String stateorduration) {
if (checkDisabled(sender)) return;
if (stateorduration == null) stateorduration = "";
Computer.getInstance().PressKey(sender, key, stateorduration);
}
@Command2.Subcommand(helpText = {
"Mouse event",
"Move the mouse by the specified offset or press the given button."
})
public void mouse(CommandSender sender, String keyOrX, @Command2.OptionalArg int y, @Command2.OptionalArg int z, @Command2.OptionalArg int w) {
if (checkDisabled(sender)) return;
try {
if (y != 0 || z != 0 || w != 0) {
int x = Integer.parseInt(keyOrX);
Computer.getInstance().UpdateMouse(sender, x, y, z, w, "");
} else
Computer.getInstance().UpdateMouse(sender, 0, 0, 0, 0, keyOrX);
} catch (VBoxException e) {
throw e;
} catch (Exception ignored) {
}
}
@Command2.Subcommand(helpText = {
"Show keyboard",
"Displays a keyboard in chat that you can click on.",
"Each keypress is actually a command so they will be logged by the server."
})
public void show_keyboard(CommandSender sender) {
sender.sendMessage("§6Avoid entering sensitive info here unless you own the server");
var cc = new TextComponent();
for (int i = 0; i < 10; i++)
cc.addExtra(keyComp(i + "", null));
var row = new String[]{"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"};
for (String s : row) cc.addExtra(keyComp(s, null));
cc.addExtra(keyComp("-", "minus"));
cc.addExtra(keyComp("=", "equals"));
cc.addExtra(keyComp("Backspace", "backspace"));
cc.addExtra(keyComp("Tab", "tab"));
sender.spigot().sendMessage(cc);
cc = new TextComponent();
row = new String[]{"A", "S", "D", "F", "G", "H", "J", "K", "L"};
for (String s : row) cc.addExtra(keyComp(s, null)); //TODO: File format for layouts
cc.addExtra(keyComp("{", "bracketLeft"));
cc.addExtra(keyComp("}", "bracketRight"));
sender.spigot().sendMessage(cc);
}
private TextComponent keyComp(String name, @Nullable String code) {
if (code == null) code = name;
var tc = new TextComponent("[" + name + "] ");
tc.setColor(ChatColor.AQUA);
tc.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/c key " + code));
return tc;
}
@Command2.Subcommand(helpText = {
"Lock mouse",
"Locks the mouse to where you're looking. If you move your mouse, the computer's mouse will be moved."
})
public void lock_mouse(Player player) {
if (checkDisabled(player)) return;
MouseLockerPlayerListener.toggleLock(player);
}
@Command2.Subcommand(helpText = {
"Set mouse speed",
"Sets the mouse speed when locked. The default is 5."
})
public void lock_speed(CommandSender sender, float speed) {
if (checkDisabled(sender)) return;
MouseLockerPlayerListener.LockedSpeed = speed;
sender.sendMessage("§aMouse speed set to " + MouseLockerPlayerListener.LockedSpeed);
}
@Command2.Subcommand(helpText = {
"Spawn screen",
"Spawns a computer screen near you. All of them show the same thing."
})
public void spawn(Player player) {
if (checkDisabled(player)) return;
var loc = player.getLocation();
var world = Objects.requireNonNull(loc.getWorld());
short id = PluginMain.Instance.startID.get();
for (int j = PluginMain.MCY - 1; j >= 0; j--) {
for (int i = 0; i < PluginMain.MCX; i++) {
var block = world.getBlockAt(loc.getBlockX() + i, loc.getBlockY() + j, loc.getBlockZ());
block.setType(Material.BLACK_WOOL);
var frameLoc = block.getLocation().add(0, 0, 1);
var map = new ItemStack(Material.FILLED_MAP, 1);
var meta = ((MapMeta) map.getItemMeta());
if (meta == null) throw new NullPointerException("Map meta is null for " + frameLoc);
meta.setMapId(id++);
map.setItemMeta(meta);
world.spawn(frameLoc, ItemFrame.class).setItem(map);
}
}
}
@Command2.Subcommand(helpText = {
"Set layout",
"This command sets the keyboard layout used for /c show keyboard.",
"Valid options are files in the layouts folder in the plugin's directory."
})
public void set_layout(CommandSender sender, String layout) {
var lf = PluginMain.Instance.layoutFolder;
if (!lf.mkdirs()) {
sender.sendMessage("§cFailed to create layouts folder!");
return;
}
var l = new File(lf, layout + ".yml");
if (!l.exists()) {
sender.sendMessage("§cThe file " + l + " does not exist.");
return;
}
var yc = YamlConfiguration.loadConfiguration(l);
var list = yc.getList("keyboard");
if (list == null) {
sender.sendMessage("§cThe 'keyboard' key is missing.");
return;
}
for (var item : list) {
System.out.println("item: " + item);
}
}
@Command2.Subcommand(helpText = {
"Plugin enable/disable",
"Use this command to enable or disable the plugin.",
"This can be useful to save resources if it is unused."
})
public boolean plugin(CommandSender sender, @CustomTabComplete({"enable", "disable"}) String enableDisable) {
switch (enableDisable) {
case "enable":
sender.sendMessage("§bEnabling plugin...");
PluginMain.Instance.reloadConfig();
PluginMain.Instance.pluginEnableInternal();
sender.sendMessage("§bPlugin enabled! More info on console.");
break;
case "disable":
sender.sendMessage("§aDisabling plugin...");
PluginMain.Instance.pluginDisableInternal();
sender.sendMessage("§aPlugin disabled! More info on console.");
break;
default:
return false;
}
return true;
}
private boolean checkDisabled(CommandSender sender) {
boolean disabled = !PluginMain.isPluginEnabled();
if (disabled)
sender.sendMessage("The plugin is currently disabled. Do /c plugin enable to enable it.");
return disabled;
}
}

View file

@ -0,0 +1,193 @@
package sznp.virtualcomputer;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.ConfigData;
import jnr.ffi.LibraryLoader;
import lombok.Getter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.scheduler.BukkitTask;
import org.virtualbox_6_1.IVirtualBox;
import org.virtualbox_6_1.VirtualBoxManager;
import sznp.virtualcomputer.events.VBoxEventHandler;
import sznp.virtualcomputer.renderer.BukkitRenderer;
import sznp.virtualcomputer.renderer.GPURenderer;
import sznp.virtualcomputer.util.Utils;
import sznp.virtualcomputer.util.VBoxLib;
import java.io.File;
import java.util.Arrays;
import java.util.function.Predicate;
public class PluginMain extends ButtonPlugin {
public static final int MCX = 5;
public static final int MCY = 4;
private BukkitTask mousetask;
private VBoxEventHandler listener;
private VirtualBoxManager manager;
public static PluginMain Instance;
@Getter
private static boolean pluginEnabled; //The Bukkit plugin has to be enabled for the enable command to work
/**
* The first map ID to use for the screen.
* The maps with IDs in the range startID -> startID+19 will be temporarily replaced with the screen.
*/
public final ConfigData<Short> startID = getIConfig().getData("startID", (short) 0);
/**
* If true, uses the GPU to accelerate screen rendering. May require root on Linux.
*/
private final ConfigData<Boolean> useGPU = getIConfig().getData("useGPU", true);
/**
* The virtual machine will be hosted inside the server process if this option is enabled.
* This can improve performance but may cause stability and other issues.
*/
public final ConfigData<Boolean> runEmbedded = getIConfig().getData("runEmbedded", true);
/**
* Determines the keyboard layout to use for /c show keyboard. Layouts can be defined in VirtualComputer/layouts/.
*/
private final ConfigData<String> kbLayout = getIConfig().getData("kbLayout", "en");
public File layoutFolder = new File(getDataFolder(), "layouts");
/**
* When set to false, the plugin will not initialize on server startup and its only valid command will be /c plugin enable.
* This can be useful to save resources as the plugin keeps the VirtualBox interface running while enabled.
*/
private final ConfigData<Boolean> autoEnable = getIConfig().getData("autoEnable", true);
/**
* The amount of rows to update if the slower, Bukkit renderer is being used.
*/
private final ConfigData<Integer> updateRows = getIConfig().getData("updateRows", 15, o -> (int) o, i -> BukkitRenderer.updatepixels = i);
/**
* Experimental. Whether all of the image data should be sent even on small changes. Defaults to true.
*/
private final ConfigData<Boolean> sendAll = getIConfig().getData("sendAll", true);
@Override
public void pluginEnable() {
registerCommand(new ComputerCommand());
Instance = this;
if (autoEnable.get())
pluginEnableInternal();
else
getLogger().info("Auto-enable is disabled. Enable with /c plugin enable.");
}
void pluginEnableInternal() {
if (pluginEnabled)
return;
pluginEnabled = true;
try {
ConsoleCommandSender ccs = getServer().getConsoleSender();
ccs.sendMessage("§bInitializing VirtualBox...");
String osname = System.getProperty("os.name").toLowerCase();
final boolean windows;
String vbpath;
{
boolean win = false;
vbpath = osname.contains("mac")
? "/Applications/VirtualBox.app/Contents/MacOS"
: (win = osname.contains("windows"))
? "C:\\Program Files\\Oracle\\VirtualBox"
: "/opt/virtualbox";
windows = win;
}
//noinspection ConstantConditions
Predicate<File> notGoodDir = ff -> !ff.isDirectory() || (!windows && Arrays.stream(ff.list()).noneMatch(s -> s.contains("xpcom")));
if (notGoodDir.test(new File(vbpath)))
vbpath = "/usr/lib/virtualbox";
if (notGoodDir.test(new File(vbpath)))
error("Could not find VirtualBox! Download from https://www.virtualbox.org/wiki/Downloads", null);
if (System.getProperty("vbox.home") == null || System.getProperty("vbox.home").isEmpty())
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);
if (System.getProperty("java.library.path") == null || System.getProperty("java.library.path").isEmpty())
System.setProperty("java.library.path", vbpath);
Utils.addLibraryPath(vbpath); //TODO: Jacob DLL must be in the server folder
final VirtualBoxManager manager = VirtualBoxManager.createInstance(getDataFolder().getAbsolutePath());
if (!windows && runEmbedded.get()) {
VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom");
vbl.RTR3InitExe(0, "", 0);
}
IVirtualBox vbox = manager.getVBox();
(listener = new VBoxEventHandler()).registerTo(vbox.getEventSource());
this.manager = manager;
ccs.sendMessage("§bLoading Screen...");
boolean direct;
try {
if (useGPU.get()) {
setupDirectRendering(ccs);
direct = true;
} else {
setupBukkitRendering(ccs);
direct = false;
}
} catch (NoClassDefFoundError | Exception e) {
e.printStackTrace();
setupBukkitRendering(ccs);
direct = false;
}
new Computer(this, manager, vbox, direct, runEmbedded.get()); //Saves itself
ccs.sendMessage("§bLoaded!");
val mlpl = new MouseLockerPlayerListener();
mousetask = getServer().getScheduler().runTaskTimer(this, mlpl, 0, 0);
getServer().getPluginManager().registerEvents(mlpl, this);
} catch (final Exception e) {
error(null, e);
}
}
private void setupDirectRendering(CommandSender ccs) throws Exception {
for (short i = 0; i < MCX; i++)
for (short j = 0; j < MCY; j++)
new GPURenderer((short) (startID.get() + j * MCX + i), Bukkit.getWorlds().get(0), sendAll.get(), i, j);
ccs.sendMessage("§bUsing Direct Renderer, all good");
}
private void setupBukkitRendering(CommandSender ccs) {
for (short i = 0; i < MCX * MCY; i++)
new BukkitRenderer((short) (startID.get() + i), Bukkit.getWorlds().get(0), i * 128 * 128 * 4, getLogger());
ccs.sendMessage("§6Using Bukkit renderer");
}
private void error(String message, Exception e) { //Message OR exception
getLogger().severe("A fatal error occured, disabling plugin!");
Bukkit.getPluginManager().disablePlugin(this);
if (message != null)
throw new RuntimeException(message);
else
throw new RuntimeException(e);
}
@Override
public void pluginDisable() {
pluginDisableInternal();
}
void pluginDisableInternal() {
if (!pluginEnabled)
return;
pluginEnabled = false;
ConsoleCommandSender ccs = getServer().getConsoleSender();
if (mousetask != null)
mousetask.cancel();
/*try {
source.unregisterListener(listener);
} catch (VBoxException e) { //"Listener was never registered"
e.printStackTrace(); - VBox claims the listener was never registered (can double register as well)
}*/
if (listener != null)
listener.disable(); //The save progress wait locks with the event
if (Computer.getInstance() != null)
Computer.getInstance().pluginDisable(ccs);
ccs.sendMessage("§aHuh.");
saveConfig();
manager.cleanup();
}
}

View file

@ -2,12 +2,11 @@ package sznp.virtualcomputer.events;
import lombok.val;
import org.bukkit.Bukkit;
import org.mozilla.interfaces.IEvent;
import org.mozilla.interfaces.IEventListener;
import org.virtualbox_6_0.IEventSource;
import org.virtualbox_6_0.VBoxEventType;
import sznp.virtualcomputer.util.COMObjectBase;
import sznp.virtualcomputer.util.Utils;
import org.virtualbox_6_1.IEvent;
import org.virtualbox_6_1.IEventSource;
import org.virtualbox_6_1.VBoxEventType;
import sznp.virtualcomputer.util.COMUtils;
import sznp.virtualcomputer.util.IEventHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -17,31 +16,29 @@ import java.util.Map;
/**
* A Bukkit-like event system which calls the appropriate methods on an event.
*/
public abstract class EventHandlerBase extends COMObjectBase implements IEventListener {
public abstract class EventHandlerBase implements IEventHandler {
/**
* The events to listen for. It will only look for these handlers.
*/
private final Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap;
private final Map<VBoxEventType, Class<? extends org.virtualbox_6_1.IEvent>> eventMap;
private boolean enabled = true;
protected EventHandlerBase(Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap) {
//this.eventMap = eventMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().value(), Map.Entry::getValue));
protected EventHandlerBase(Map<VBoxEventType, Class<? extends org.virtualbox_6_1.IEvent>> eventMap) {
this.eventMap = eventMap;
}
@Override
public final void handleEvent(IEvent iEvent) {
//val cl=eventMap.get((int)iEvent.getType()); - We can afford to search through the events for this handler
if (!enabled)
return;
val kv = eventMap.entrySet().stream().filter(e -> e.getKey().value() == iEvent.getType()).findAny();
val kv = eventMap.entrySet().stream().filter(e -> e.getKey().value() == iEvent.getType().value()).findAny();
if (!kv.isPresent()) return; //Event not supported
val cl = kv.get().getValue();
for (Method method : getClass().getMethods()) {
if (method.isAnnotationPresent(org.bukkit.event.EventHandler.class)
&& method.getParameterCount() == 1 && method.getParameterTypes()[0] == cl) {
try {
method.invoke(this, Utils.getEvent(iEvent, cl));
method.invoke(this, COMUtils.getEvent(iEvent, cl));
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
@ -53,8 +50,8 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi
}
}
public <T extends EventHandlerBase> org.virtualbox_6_0.IEventListener registerTo(IEventSource source) {
return Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet()));
public <T extends EventHandlerBase> org.virtualbox_6_1.IEventListener registerTo(IEventSource source) {
return COMUtils.registerListener(source, this, new ArrayList<>(eventMap.keySet()));
}
public void disable() {

View file

@ -6,9 +6,9 @@ import lombok.experimental.var;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler;
import org.virtualbox_6_0.IProgress;
import org.virtualbox_6_0.IStateChangedEvent;
import org.virtualbox_6_0.VBoxEventType;
import org.virtualbox_6_1.IProgress;
import org.virtualbox_6_1.IStateChangedEvent;
import org.virtualbox_6_1.VBoxEventType;
import sznp.virtualcomputer.Computer;
import sznp.virtualcomputer.PluginMain;
@ -38,17 +38,22 @@ public class MachineEventHandler extends EventHandlerBase {
case Saved:
if (starting) {
sender.sendMessage("§cFailed to start computer! See the console for more details.");
sender.sendMessage("§cMake sure that 2D and 3D acceleration is disabled.");
starting = false;
Bukkit.getScheduler().runTaskAsynchronously(PluginMain.Instance, () -> {
if (progress == null) return;
progress.waitForCompletion(-1);
if (progress != null && progress.getCompleted() && progress.getResultCode() != 0) {
if (progress.getCompleted() && progress.getResultCode() != 0) {
Logger l = PluginMain.Instance.getLogger();
l.warning("Result code: " + Integer.toHexString(progress.getResultCode()));
for (var info = progress.getErrorInfo(); info != null; info = info.getNext()) {
l.warning("----------------");
l.warning("VBox: " + info.getText());
l.warning("Component: " + info.getComponent());
if (info.getResultCode() == 0x80004005 && info.getResultDetail() == 0xFFFFF88B)
l.warning("The server cannot access the VirtualBox driver. Either run it as root or disable the 'runEmbedded' config option. Make sure to only run plugins you trust as root.");
else {
l.warning("VBox: " + info.getText());
l.warning("Component: " + info.getComponent());
l.warning("Error codes: " + Integer.toHexString(info.getResultCode()) + " " + Integer.toHexString(info.getResultDetail()));
}
}
}
});

View file

@ -0,0 +1,39 @@
package sznp.virtualcomputer.events;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler;
import org.virtualbox_6_1.ISessionStateChangedEvent;
import org.virtualbox_6_1.SessionState;
import org.virtualbox_6_1.VBoxEventType;
import sznp.virtualcomputer.Computer;
public class VBoxEventHandler extends EventHandlerBase {
public VBoxEventHandler() {
super(ImmutableMap.of(VBoxEventType.OnSessionStateChanged, ISessionStateChangedEvent.class));
instance = this;
}
@Getter
private static VBoxEventHandler instance;
private String machineID;
private CommandSender sender;
@EventHandler
public void onSessionStateChange(ISessionStateChangedEvent event) {
if (!event.getMachineId().equals(machineID)) return;
try {
if (event.getState() == SessionState.Locked) //Need to check here, because we can't access the console yet
Computer.getInstance().onLock(sender);
} catch (Exception e) {
sender.sendMessage("§cFailed to start computer! See the console for more details.");
throw e;
}
}
public void setup(String machineID, CommandSender sender) {
this.machineID = machineID;
this.sender = sender;
}
}

View file

@ -10,30 +10,33 @@ import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class BukkitRenderer extends MapRenderer implements IRenderer {
private ByteBuffer allpixels;
private BufferedImage image;
private int startindex;
private static ByteBuffer allpixels;
private final BufferedImage image;
private final int startindex;
private final Logger logger;
/**
* The raw pixel data from the machine in BGRA format
* Updates the screen.
*
* @param allpixels The raw pixel data from the machine in BGRA format
*/
public void setAllPixels(ByteBuffer allpixels) {
this.allpixels = allpixels;
public static void update(ByteBuffer allpixels, int x, int y, int width, int height) {
BukkitRenderer.allpixels = allpixels; //TODO: Only update what actually changes
}
/**
* 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 startindex
* The index to start from in allpixels
*
* @param id The ID of the current map
* @param world The world to create new maps in
* @param startindex The index
* @param logger The plugin's logger
*/
public BukkitRenderer(short id, World world, int startindex) {
public BukkitRenderer(short id, World world, int startindex, Logger logger) {
this.logger = logger;
MapView map = IRenderer.prepare(id, world);
map.addRenderer(this);
this.startindex = startindex;
@ -72,12 +75,12 @@ public class BukkitRenderer extends MapRenderer implements IRenderer {
long diff = System.nanoTime() - time;
if (TimeUnit.NANOSECONDS.toMillis(diff) > 40) {
System.out.println("Map rendering took " + TimeUnit.NANOSECONDS.toMillis(diff) + " ms");
logger.warning("Map rendering took " + TimeUnit.NANOSECONDS.toMillis(diff) + " ms");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Progess: " + progress);
System.out.println("UpdatePixels: " + updatepixels);
logger.warning("Progess: " + progress);
logger.warning("UpdatePixels: " + updatepixels);
}
}
}

View file

@ -1,6 +1,9 @@
package sznp.virtualcomputer.renderer;
import net.minecraft.server.v1_12_R1.WorldMap;
import com.aparapi.device.Device;
import com.aparapi.internal.kernel.KernelManager;
import lombok.val;
import lombok.var;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.map.MapCanvas;
@ -12,19 +15,26 @@ import sznp.virtualcomputer.util.Timing;
import java.awt.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.function.BiConsumer;
public class GPURenderer extends MapRenderer implements IRenderer {
private byte[] buffer;
private final GPURendererInternal kernel;
private WorldMap wmap;
private final boolean sendAll;
private int mapx, mapy;
//Store at central location after conversion
private static int[] colors_;
private int changedX = 0, changedY = 0, changedWidth = 640, changedHeight = 480;
private static ArrayList<GPURenderer> renderers = new ArrayList<>();
private BiConsumer<Integer, Integer> flagDirty; //This way it's version independent, as long as it's named the same
private static final ArrayList<GPURenderer> renderers = new ArrayList<>();
private static Method flagDirtyMethod;
private static boolean enabled = true;
private static boolean warned = false;
public GPURenderer(short id, World world, int mapx, int mapy) throws Exception {
public GPURenderer(short id, World world, boolean sendAll, int mapx, int mapy) throws Exception {
this.sendAll = sendAll;
MapView map = IRenderer.prepare(id, world);
if (map == null) {
kernel = null;
@ -44,15 +54,29 @@ public class GPURenderer extends MapRenderer implements IRenderer {
this.mapy = mapy;
Field field = map.getClass().getDeclaredField("worldMap");
field.setAccessible(true);
wmap = (WorldMap) field.get(map);
val wmap = field.get(map);
if (flagDirtyMethod == null)
flagDirtyMethod = wmap.getClass().getMethod("flagDirty", int.class, int.class);
flagDirty = (x, y) -> {
try {
flagDirtyMethod.invoke(wmap, x, y);
} catch (Exception e) {
e.printStackTrace();
}
};
kernel = new GPURendererInternal(mapx, mapy, colors_);
var dev = kernel.getTargetDevice();
if (mapx == mapy && mapx == 0)
PluginMain.Instance.getLogger().info("Using device: " + dev.getShortDescription());
renderers.add(this);
map.addRenderer(this);
enabled = true; //Enable at each plugin (re)enable
}
@Override
public void render(MapView map, MapCanvas canvas, Player player) {
if (!enabled) return;
Timing t = new Timing();
try {
if (kernel.isRendered()) return;
@ -61,7 +85,15 @@ public class GPURenderer extends MapRenderer implements IRenderer {
field.setAccessible(true);
buffer = (byte[]) field.get(canvas);
}
if (!PluginMain.sendAll) {
if (!warned) { //Only print once
warned = true;
if (kernel.getTargetDevice().getType() != Device.TYPE.GPU) {
PluginMain.Instance.getLogger().warning("Cannot use GPU! Target device: " + kernel.getTargetDevice().getShortDescription()
+ " - Best device: " + KernelManager.instance().bestDevice().getShortDescription());
PluginMain.Instance.getLogger().warning("Server performance may be affected"); //TODO: Index 0 out of range 0
}
}
if (!sendAll) {
synchronized (kernel) {
if (changedX >= (mapx + 1) * 128 || changedY >= (mapy + 1) * 128
|| changedX + changedWidth < mapx * 128 || changedY + changedHeight < mapy * 128) {
@ -79,28 +111,33 @@ public class GPURenderer extends MapRenderer implements IRenderer {
int yy = y + changedHeight >= 128 ? 127 : y + changedHeight;
//System.out.println("local: ("+x+", "+y+") "+w+"x"+h);
kernel.render(buffer);
wmap.flagDirty(x, y);
wmap.flagDirty(xx, yy); // Send the changes only
flagDirty.accept(x, y);
flagDirty.accept(xx, yy); // Send the changes only
changedX = Integer.MAX_VALUE; //Finished rendering
changedY = Integer.MAX_VALUE; //TODO: Render as soon as we receive new image
changedWidth = -1; //Finished rendering
changedHeight = -1;
} else {
kernel.render(buffer);
wmap.flagDirty(0, 0);
wmap.flagDirty(127, 127); // Send everything
flagDirty.accept(0, 0);
flagDirty.accept(127, 127); // Send everything
}
} catch (Exception e) {
e.printStackTrace();
}
if (t.elapsedMS() > 60)
System.out.println("Map rendering took " + t.elapsedMS() + "ms");
PluginMain.Instance.getLogger().warning("Map rendering took " + t.elapsedMS() + "ms");
if (t.elapsedMS() > 2000) {
PluginMain.Instance.getLogger().severe("Map rendering is taking too long! Disabling rendering to prevent the server from crashing.");
PluginMain.Instance.getLogger().severe("Make sure the server has root privileges or disable GPU rendering.");
enabled = false;
}
}
public static void update(byte[] pixels, int width, int height, int changedX, int changedY, int changedWidth, int changedHeight) {
for (GPURenderer r : renderers) {
synchronized (r.kernel) {
if (!PluginMain.sendAll) {
if (!r.sendAll) {
if (changedX < r.changedX)
r.changedX = changedX;
if (changedY < r.changedY)

View file

@ -0,0 +1,209 @@
package sznp.virtualcomputer.renderer;
import com.sun.jna.Pointer;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import org.virtualbox_6_1.Holder;
import org.virtualbox_6_1.IDisplay;
import org.virtualbox_6_1.IDisplaySourceBitmap;
import org.virtualbox_6_1.VBoxException;
import sznp.virtualcomputer.PluginMain;
import sznp.virtualcomputer.util.COMUtils;
import sznp.virtualcomputer.util.IMCFrameBuffer;
import sznp.virtualcomputer.util.Timing;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.logging.Logger;
public class MCFrameBuffer implements IMCFrameBuffer {
private final IDisplay display;
private final Holder<IDisplaySourceBitmap> holder = new Holder<>();
private final Logger logger;
private final PluginMain plugin;
/**
* Whether the VM is running inside the server
*/
private final boolean embedded;
/**
* Whether the GPU is being used to render
*/
private final boolean direct;
private BukkitTask tt;
/**
* Used when running embedded
*/
private Pointer pointer;
/**
* Used when not running embedded
*/
private byte[] screenImage;
/**
* Used when running in indirect mode, not embedded
*/
private ByteBuffer screenBuffer;
private int width;
private int height;
@Getter
@Setter
private String id;
private final AtomicBoolean shouldUpdate = new AtomicBoolean();
private final AtomicIntegerArray updateParameters = new AtomicIntegerArray(4);
private boolean running;
/**
* Creates a new framebuffer that receives images from the VM and sends the image data to Minecraft.
*
* @param display The VM display to use - TODO: Multiple monitors
* @param plugin The plugin
* @param direct Whether the GPU rendering is used
*/
public MCFrameBuffer(IDisplay display, PluginMain plugin, boolean embedded, boolean direct) {
this.display = display;
this.plugin = plugin;
this.logger = plugin.getLogger();
this.embedded = embedded; //Don't change even if the config got updated while running
this.direct = direct;
}
@Override
public void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height) {
if (tt != null)
tt.cancel();
tt = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
synchronized (this) { //If a change occurs twice, then wait for it
try {
if (!embedded) { //Running separately
this.width = (int) width;
this.height = (int) height;
if (screenImage == null || screenImage.length != width * height * 4) {
screenImage = new byte[(int) (width * height * 4)];
screenBuffer = ByteBuffer.wrap(screenImage);
}
updateScreen((int) xOrigin, (int) yOrigin, (int) width, (int) height);
return;
}
display.querySourceBitmap(0L, holder);
long[] ptr = new long[1], w = new long[1], h = new long[1], bpp = new long[1], bpl = new long[1], pf = new long[1];
COMUtils.queryBitmapInfo(holder.value, ptr, w, h, bpp, bpl, pf);
pointer = new Pointer(ptr[0]);
this.width = (int) w[0];
this.height = (int) h[0];
updateScreen(0, 0, (int) width, (int) height);
} catch (VBoxException e) {
if (e.getResultCode() == 0x80070005)
return; // Machine is being powered down
if (e.getResultCode() == 0x80004005) //The function "querySourceBitmap" returned an error condition: "Operation failed (NS_ERROR_FAILURE)"
System.out.println("I don't know why this happens, but stopping the computer helps.");
e.printStackTrace();
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
@Override
public void notifyUpdate(long x, long y, long width, long height) {
if (shouldUpdate.get())
return; //Don't wait for lock, ignore update since we're updating everything anyway - TODO: Not always
synchronized (this) {
shouldUpdate.set(true);
updateParameters.set(0, (int) x);
updateParameters.set(1, (int) y);
updateParameters.set(2, (int) width);
updateParameters.set(3, (int) height);
notifyAll();
}
}
@Override
public void notifyUpdateImage(long x, long y, long width, long height, byte[] image) {
System.out.println("Update image!");
if (this.width == 0 || this.height == 0) {
logger.warning("Received screen image before resolution change!");
return;
}
for (int i = 0; i < height; i++) //Copy lines of the screen in a fast way
System.arraycopy(image, (int) (i * width * 4), screenImage, (int) (x + y * this.width * 4), (int) width * 4);
updateScreen((int) x, (int) y, (int) width, (int) height);
}
public void startEmbedded() {
running = true;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
while (running) {
synchronized (this) {
while (!shouldUpdate.get())
wait(1000);
if (pointer == null) {
logger.warning("Embedded screen data pointer is null");
shouldUpdate.set(false);
continue;
}
if (!running) return;
updateScreen(updateParameters.get(0), updateParameters.get(1), updateParameters.get(2), updateParameters.get(3));
shouldUpdate.set(false);
}
}
} catch (InterruptedException ignored) {
}
});
}
private void updateScreenDirectInternal(byte[] pixels, int x, int y, int width, int height) {
if (pixels == null) {
logger.warning("Direct pixel data is null");
return;
}
Timing t = new Timing();
GPURenderer.update(pixels, this.width, this.height, x, y, width, height);
if (t.elapsedMS() > 60) //Typically 1ms max
logger.warning("Direct update took " + t.elapsedMS() + "ms");
}
private void updateScreenIndirectInternal(ByteBuffer buffer, int x, int y, int width, int height) {
if (buffer == null) {
logger.warning("Indirect pixel buffer is null");
return;
}
if (this.width * this.height > 640 * 480)
buffer.limit(640 * 480 * 4);
else
buffer.limit(this.width * this.height * 4);
Timing t = new Timing();
BukkitRenderer.update(buffer, x, y, width, height);
if (t.elapsedMS() > 60)
logger.warning("Indirect update took " + t.elapsedMS() + "ms");
}
/**
* Updates the screen when the VM is embedded or when it isn't.
*
* @param x The x of change - passed along to the renderer to use
* @param y The y of change - passed along to the renderer to use
* @param width The width of change - passed along to the renderer to use
* @param height The height of change - passed along to the renderer to use
*/
private void updateScreen(int x, int y, int width, int height) {
if (direct) {
val arr = embedded ? pointer.getByteArray(0L, this.width * this.height * 4) : screenImage;
updateScreenDirectInternal(arr, x, y, width, height);
} else {
val bb = embedded ? pointer.getByteBuffer(0L, (long) this.width * this.height * 4) : screenBuffer;
updateScreenIndirectInternal(bb, x, y, width, height);
}
}
public void stop() {
synchronized (this) {
running = false;
notifyAll();
}
}
}

View file

@ -0,0 +1,52 @@
package sznp.virtualcomputer.util;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.util.Arrays;
public class Utils {
/**
* 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 {
try {
addLibraryPathOld(pathToAdd);
} catch (Throwable t) {
addLibraryPathNew(pathToAdd);
}
}
private static void addLibraryPathOld(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);
}
private static void addLibraryPathNew(String pathToAdd) throws Exception {
MethodHandles.Lookup cl = MethodHandles.privateLookupIn(ClassLoader.class, MethodHandles.lookup());
VarHandle sys_paths = cl.findStaticVarHandle(ClassLoader.class, "sys_paths", String[].class);
final String[] paths = (String[]) sys_paths.get();
final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
newPaths[newPaths.length - 1] = pathToAdd;
sys_paths.set((Object) newPaths);
}
}

View file

@ -0,0 +1,35 @@
computer {
start {
index brigadier:integer;
}
on {
index brigadier:integer;
}
list;
stop;
off;
shutdown;
kill;
powerbutton;
reset;
restart;
fix;
fixscreen;
key {
key brigadier:string single_word;
}
press {
key brigadier:string single_word;
}
mouse {
button brigadier:string single_word;
}
input {
key;
mouse;
}
show {
key;
mouse;
}
}

View file

@ -1,7 +1,11 @@
name: VirtualComputer
main: sznp.virtualcomputer.PluginMain
version: 2.0
version: '3.0'
author: 'NorbiPeti'
commands:
computer:
usage: Use /computer start|stop|reset|key|mouse|input|fix
aliases: c
api-version: '1.13'
depend:
- Chroma-Core

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>VirtualComputer</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View file

@ -1,13 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter

View file

@ -1,144 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer</artifactId>
<version>2.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-g</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration />
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>XPCOM</id>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>org.virtualbox:VirtualBox-MSCOM</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>MSCOM</id>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>org.virtualbox:VirtualBox</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>repo</id>
<url>file://${basedir}/repo</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
<exclusion>
<artifactId>json-simple</artifactId>
<groupId>com.googlecode.json-simple</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
<exclusion>
<artifactId>gson</artifactId>
<groupId>com.google.code.gson</groupId>
</exclusion>
<exclusion>
<artifactId>snakeyaml</artifactId>
<groupId>org.yaml</groupId>
</exclusion>
<exclusion>
<artifactId>bungeecord-chat</artifactId>
<groupId>net.md-5</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox-MSCOM</artifactId>
<version>6.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox-MSCOM</artifactId>
<versioning>
<release>6.0</release>
<versions>
<version>6.0</version>
</versions>
<lastUpdated>20190405225845</lastUpdated>
</versioning>
</metadata>

View file

@ -1,162 +0,0 @@
package sznp.virtualcomputer;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.virtualbox_6_0.VBoxException;
public class Commands implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (args.length == 0)
return false;
switch (args[0].toLowerCase()) {
case "start":
case "poweron":
case "on":
case "startup":
int c = getMachineIndex(args);
if (c == -1) {
sender.sendMessage("§cUsage: /" + label + " start [index]");
return true;
}
Computer.getInstance().Start(sender, c);
break;
case "stop":
case "poweroff":
case "off":
case "shutdown":
case "kill":
Computer.getInstance().Stop(sender);
break;
case "powerbutton":
case "pwrbtn":
case "powerbtn":
c = getMachineIndex(args);
if (c == -1) {
sender.sendMessage("§cUsage: /" + label + " powerbutton [index]");
return true;
}
Computer.getInstance().PowerButton(sender, c);
break;
case "reset":
case "restart":
Computer.getInstance().Reset(sender);
break;
case "fix":
case "fixscreen":
Computer.getInstance().FixScreen(sender);
break;
case "key":
case "press":
case "presskey":
case "keypress":
if (args.length < 2) {
sender.sendMessage("§cUsage: /computer key <key> [down/up|duration(ticks)]");
return true;
}
if (args.length < 3)
Computer.getInstance().PressKey(sender, args[1], "");
else
Computer.getInstance().PressKey(sender, args[1], args[2]);
break;
case "mouse":
boolean showusage = true;
if (args.length < 6) {
try {
// Command overloading, because I can :P
if (args.length > 4) // 4<x<6
{
Computer.getInstance().UpdateMouse(sender, Integer.parseInt(args[1]), Integer.parseInt(args[2]),
Integer.parseInt(args[3]), Integer.parseInt(args[4]), "", false);
showusage = false;
} else {
if (args.length == 3) {
Computer.getInstance().UpdateMouse(sender, 0, 0, 0, 0, args[1], args[2].equals("down"));
showusage = false;
} else if (args.length == 2) {
Computer.getInstance().UpdateMouse(sender, 0, 0, 0, 0, args[1]);
showusage = false;
}
}
}
catch (VBoxException e) {
e.printStackTrace();
}
catch (Exception ignored) { //It will show the usage here
}
}
if (showusage) {
sender.sendMessage("§cUsage: /computer mouse <relx> <rely> <relz> <relw>");
sender.sendMessage("§cOr: /computer mouse <button> [up/down]");
}
break;
case "input":
case "show":
case "showinput":
case "shinput": {
if (!(sender instanceof Player)) {
sender.sendMessage("§cError: Only players can use this command.");
return true;
}
if (args.length < 2) {
sender.sendMessage("§cUsage: /computer input <key|mouse>");
return true;
}
switch (args[1].toLowerCase()) {
case "key":
case "keyboard":
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName()
+ " [\"\",{\"text\":\" [0]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D0\"}},{\"text\":\" [1]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D1\"}},{\"text\":\" [2]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D2\"}},{\"text\":\" [3]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D3\"}},{\"text\":\" [4]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D4\"}},{\"text\":\" [5]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D5\"}},{\"text\":\" [6]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D6\"}},{\"text\":\" [7]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D7\"}},{\"text\":\" [8]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D8\"}},{\"text\":\" [9]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D9\"}}]");
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName()
+ " [\"\",{\"text\":\" [Tab]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key Tab\"}},{\"text\":\" [Q]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key Q\"}},{\"text\":\" [W]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key W\"}},{\"text\":\" [E]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key E\"}},{\"text\":\" [R]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key R\"}},{\"text\":\" [T]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key T\"}},{\"text\":\" [Y]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key Y\"}},{\"text\":\" [U]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key U\"}},{\"text\":\" [I]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key I\"}},{\"text\":\" [O]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key O\"}},{\"text\":\" [P]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key P\"}}]");
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName()
+ " [\"\",{\"text\":\" [CapsLock]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key CapsLock\"}},{\"text\":\" [A]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key A\"}},{\"text\":\" [S]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key S\"}},{\"text\":\" [D]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key D\"}},{\"text\":\" [F]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key F\"}},{\"text\":\" [G]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key G\"}},{\"text\":\" [H]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key H\"}},{\"text\":\" [J]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key J\"}},{\"text\":\" [K]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key K\"}},{\"text\":\" [L]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key L\"}},{\"text\":\" [Enter]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key Enter\"}}]");
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName()
+ " [\"\",{\"text\":\" [Z]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key Z\"}},{\"text\":\" [X]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key X\"}},{\"text\":\" [C]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key C\"}},{\"text\":\" [V]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key V\"}},{\"text\":\" [B]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key B\"}},{\"text\":\" [N]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key N\"}},{\"text\":\" [M]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key M\"}}]");
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName()
+ " [\"\",{\"text\":\" [Ctrl]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key ControlLeft\"}},{\"text\":\" [Alt]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key AltLeft\"}},{\"text\":\" [Space]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key Space\"}},{\"text\":\" [AltGr]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key AltRight\"}},{\"text\":\" [Ctrl]\",\"color\":\"green\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/computer key ControlRight\"}}]");
break;
case "mouse":
MouseLockerPlayerListener.toggleLock((Player) sender);
break;
case "mspeed":
case "mousespeed":
if (args.length < 3) {
sender.sendMessage("§cUsage: /computer input mspeed <number>");
return true;
}
try {
MouseLockerPlayerListener.LockedSpeed = Float.parseFloat(args[2]);
} catch(NumberFormatException e) {
sender.sendMessage("§cThe speed must be a number.");
break;
}
sender.sendMessage("§aMouse speed set to " + MouseLockerPlayerListener.LockedSpeed);
}
break;
}
}
return true;
}
/**
* Checks the 2nd parameter
*
* @return The index of the machine or -1 on usage error
*/
private int getMachineIndex(String[] args) {
int c = 0;
if (args.length >= 2) {
try {
c = Integer.parseInt(args[1]);
} catch (Exception ignored) {
return -1;
}
}
return c;
}
}

View file

@ -1,235 +0,0 @@
package sznp.virtualcomputer;
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.events.MachineEventHandler;
import sznp.virtualcomputer.events.VBoxEventHandler;
import sznp.virtualcomputer.renderer.GPURenderer;
import sznp.virtualcomputer.renderer.MCFrameBuffer;
import sznp.virtualcomputer.util.Scancode;
import javax.annotation.Nullable;
import java.util.Arrays;
public final class Computer {
@Getter
private static Computer instance;
private final PluginMain plugin;
private ISession session;
private IVirtualBox vbox;
private IMachine machine;
private MachineEventHandler handler;
private IEventListener listener;
private VirtualBoxManager manager;
@java.beans.ConstructorProperties({"plugin"})
public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox) {
this.plugin = plugin;
this.manager = manager;
session = manager.getSessionObject();
this.vbox = vbox;
if (instance != null) throw new IllegalStateException("A computer already exists!");
instance = this;
}
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
VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null
} catch (VBoxException e) {
if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited"
sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible");
sendMessage(sender, "§6Make sure that the server is running as root (sudo)");
//TODO: If we have VirtualBox open, it won't close the server's port
//TODO: "The object in question already exists." on second start
//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());
}
}
});
}
/**
* Gets called when the machine is locked after {@link #Start(CommandSender, int)}
*
* @param sender The sender which started the machine
*/
public void onLock(CommandSender sender) {
machine = session.getMachine(); // This is the Machine object we can work with
final IConsole console = session.getConsole();
if (handler != null)
handler.disable();
handler = new MachineEventHandler(Computer.this, sender);
listener = handler.registerTo(console.getEventSource());
IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
handler.setProgress(progress);
handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way?
console.getDisplay().attachFramebuffer(0L,
new IFramebuffer(new MCFrameBuffer(console.getDisplay(), true)));
}
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)) {
onMachineStop(sender); //Needed for session reset
sendMessage(sender, "§eComputer was already off, released it.");
}
return;
}
sendMessage(sender, "§eStopping computer...");
session.getConsole().powerDown();
}
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 = Scancode.getCode("sc_" + key.toLowerCase());
if (code == -1) {
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) throws Exception {
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 Exception("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) throws Exception {
if (checkMachineNotRunning(sender))
return;
UpdateMouse(sender, x, y, z, w, mbs, true);
UpdateMouse(sender, x, y, z, w, mbs, false);
}
public void onMachineStart(CommandSender sender) {
sendMessage(sender, "§eComputer started.");
}
public void onMachineStop(CommandSender sender) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
if (session.getState() == SessionState.Locked) {
session.unlockMachine(); //Needs to be outside of the event handler
handler = null;
machine = null;
session = manager.getSessionObject();
sendMessage(sender, "§eComputer powered off."); //This block runs later
}
});
GPURenderer.update(new byte[1], 0, 0, 0, 0, 640, 480); //Black screen
stopEvents();
MouseLockerPlayerListener.computerStop();
}
public void stopEvents() {
/*if(session!=null && listener!=null)
session.getConsole().getEventSource().unregisterListener(listener);*/
if (listener != null)
handler.disable();
listener = null;
}
public void pluginDisable(CommandSender ccs) {
stopEvents();
if (session.getState() == SessionState.Locked) {
if (session.getMachine().getState().equals(MachineState.Running)) {
ccs.sendMessage("§aSaving machine state...");
session.getMachine().saveState().waitForCompletion(10000);
}
session.unlockMachine();
}
}
}

View file

@ -1,129 +0,0 @@
package sznp.virtualcomputer;
import jnr.ffi.LibraryLoader;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.virtualbox_6_0.IEventListener;
import org.virtualbox_6_0.IVirtualBox;
import org.virtualbox_6_0.VirtualBoxManager;
import sznp.virtualcomputer.events.VBoxEventHandler;
import sznp.virtualcomputer.renderer.BukkitRenderer;
import sznp.virtualcomputer.renderer.GPURenderer;
import sznp.virtualcomputer.renderer.IRenderer;
import sznp.virtualcomputer.util.Utils;
import sznp.virtualcomputer.util.VBoxLib;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Predicate;
public class PluginMain extends JavaPlugin {
private static final int MCX = 5;
private static final int MCY = 4;
private BukkitTask mousetask;
private IEventListener listener;
public static PluginMain Instance;
//public static ByteBuffer allpixels = ByteBuffer.allocate(640 * 480 * 4); // It's set on each change
/**
* Only used if {@link #direct} is false.
*/
public static ByteBuffer allpixels; // It's set on each change
private static ArrayList<IRenderer> renderers = new ArrayList<>();
/*
* Only used if {@link #direct} is true.
*/
//public static PXCLib pxc;
public static boolean direct;
public static boolean sendAll;
// Fired when plugin is first enabled
@Override
public void onEnable() {
Instance = this;
try {
ConsoleCommandSender ccs = getServer().getConsoleSender();
this.getCommand("computer").setExecutor(new Commands());
sendAll = getConfig().getBoolean("sendAll", true);
ccs.sendMessage("§bInitializing VirtualBox...");
String vbpath = System.getProperty("os.name").toLowerCase().contains("mac")
? "/Applications/VirtualBox.app/Contents/MacOS"
: "/opt/virtualbox";
//noinspection ConstantConditions
Predicate<File> notGoodDir= ff->!ff.isDirectory() || Arrays.stream(ff.list()).noneMatch(s -> s.contains("xpcom"));
if (notGoodDir.test(new File(vbpath)))
vbpath = "/usr/lib/virtualbox";
if(notGoodDir.test(new File(vbpath)))
error("Could not find VirtualBox! Download from https://www.virtualbox.org/wiki/Downloads");
if (System.getProperty("vbox.home") == null || System.getProperty("vbox.home").isEmpty())
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);
if (System.getProperty("java.library.path") == null || System.getProperty("java.library.path").isEmpty())
System.setProperty("java.library.path", vbpath);
Utils.addLibraryPath(vbpath);
final VirtualBoxManager manager = VirtualBoxManager.createInstance(getDataFolder().getAbsolutePath());
VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom"); //TODO: Test for MSCOM
vbl.RTR3InitExe(0, "", 0);
IVirtualBox vbox = manager.getVBox();
listener = new VBoxEventHandler().registerTo(vbox.getEventSource());
new Computer(this, manager, vbox); //Saves itself
ccs.sendMessage("§bLoading Screen...");
try {
//throw new NoClassDefFoundError("Test error pls ignore");
for (short i = 0; i < MCX; i++)
for (short j = 0; j < MCY; 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");
direct=true;
ccs.sendMessage("§bUsing Direct Renderer, all good");
} catch (NoClassDefFoundError | Exception e) {
for (short i = 0; i < 20; i++)
renderers.add(new BukkitRenderer(i, Bukkit.getWorlds().get(0), i * 128 * 128 * 4));
direct=false;
e.printStackTrace();
ccs.sendMessage("§6Compatibility error, using slower renderer");
}
ccs.sendMessage("§bLoaded!");
val mlpl = new MouseLockerPlayerListener();
mousetask = getServer().getScheduler().runTaskTimer(this, mlpl, 0, 0);
getServer().getPluginManager().registerEvents(mlpl, this);
} catch (final Exception e) {
e.printStackTrace();
error(e.getMessage());
}
}
private void error(String message) {
getLogger().severe("A fatal error occured, disabling plugin!");
Bukkit.getPluginManager().disablePlugin(this);
throw new RuntimeException(message);
}
// Fired when plugin is disabled
@Override
public void onDisable() {
ConsoleCommandSender ccs = getServer().getConsoleSender();
if (mousetask != null)
mousetask.cancel();
/*try {
source.unregisterListener(listener);
} catch (VBoxException e) { //"Listener was never registered"
e.printStackTrace(); - VBox claims the listener was never registered (can double register as well)
}*/
if (listener != null)
((VBoxEventHandler) listener.getTypedWrapped()).disable(); //The save progress wait locks with the event
if (Computer.getInstance() != null)
Computer.getInstance().pluginDisable(ccs);
ccs.sendMessage("§aHuh.");
saveConfig();
}
}

View file

@ -1,34 +0,0 @@
package sznp.virtualcomputer.events;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler;
import org.virtualbox_6_0.ISessionStateChangedEvent;
import org.virtualbox_6_0.SessionState;
import org.virtualbox_6_0.VBoxEventType;
import sznp.virtualcomputer.Computer;
public class VBoxEventHandler extends EventHandlerBase {
public VBoxEventHandler() {
super(ImmutableMap.of(VBoxEventType.OnSessionStateChanged, ISessionStateChangedEvent.class));
instance = this;
}
@Getter
private static VBoxEventHandler instance;
private String machineID;
private CommandSender sender;
@EventHandler
public void onSessionStateChange(ISessionStateChangedEvent event) {
if (!event.getMachineId().equals(machineID)) return;
if (event.getState() == SessionState.Locked) //Need to check here, because we can't access the console yet
Computer.getInstance().onLock(sender);
}
public void setup(String machineID, CommandSender sender) {
this.machineID = machineID;
this.sender = sender;
}
}

View file

@ -1,154 +0,0 @@
package sznp.virtualcomputer.renderer;
import com.sun.jna.Pointer;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import org.mozilla.interfaces.IFramebuffer;
import org.mozilla.interfaces.IFramebufferOverlay;
import org.mozilla.interfaces.nsISupports;
import org.mozilla.xpcom.Mozilla;
import org.virtualbox_6_0.*;
import sznp.virtualcomputer.PluginMain;
import sznp.virtualcomputer.util.Timing;
import java.util.Arrays;
public class MCFrameBuffer implements IFramebuffer {
private IDisplay display;
private Holder<IDisplaySourceBitmap> holder = new Holder<>();
public MCFrameBuffer(IDisplay display, boolean VBoxDirect) { //TODO: Implement param
this.display = display;
}
@Override
public nsISupports queryInterface(String id) {
return Mozilla.queryInterface(this, id);
}
@Override
public long getBitsPerPixel() {
return 32;
}
@Override
public long getBytesPerLine() {
return 640L;
}
@Override
public long[] getCapabilities(long[] arg0) {
try {
System.out.println("Capabilities queried");
System.out.println("Capabilities: " + Arrays.toString(arg0));
return new long[]{FramebufferCapabilities.UpdateImage.value()};
}
catch(Exception e) {
e.printStackTrace();
return new long[]{};
}
}
@Override
public long getHeight() {
return 480;
}
@Override
public long getHeightReduction() {
return 0;
}
@Override
public IFramebufferOverlay getOverlay() {
return null;
}
@Override
public long getPixelFormat() {
return BitmapFormat.BGRA.value();
}
@Override
public long getVisibleRegion(byte arg0, long arg1) {
return 0;
}
@Override
public long getWidth() {
return 640;
}
@Override
public long getWinId() {
return 0; // Zero means no win id
}
@Override
public void notify3DEvent(long arg0, byte[] arg1) {
}
private BukkitTask tt;
private Pointer pointer;
private int width;
private int height;
@Override
public void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height) {
if (tt != null)
tt.cancel();
tt = Bukkit.getScheduler().runTaskAsynchronously(PluginMain.Instance, () -> {
try {
display.querySourceBitmap(0L, holder);
long[] ptr = new long[1], w = new long[1], h = new long[1], bpp = new long[1], bpl = new long[1], pf = new long[1];
holder.value.getTypedWrapped().queryBitmapInfo(ptr, w, h, bpp, bpl, pf);
if (PluginMain.direct) {
pointer = new Pointer(ptr[0]);
this.width = (int) w[0];
this.height = (int) h[0];
GPURenderer.update(pointer.getByteArray(0L, (int) (w[0] * h[0] * 4)), (int) w[0], (int) h[0], 0, 0, this.width, this.height);
} else {
PluginMain.allpixels = new Pointer(ptr[0]).getByteBuffer(0L, width * height * 4);
if (width * height > 640 * 480)
PluginMain.allpixels.limit(640 * 480 * 4);
else
PluginMain.allpixels.limit((int) (width * height * 4));
}
} catch (VBoxException e) {
if (e.getResultCode() == 0x80070005)
return; // Machine is being powered down
if (e.getResultCode() == 0x80004005) //The function "querySourceBitmap" returned an error condition: "Operation failed (NS_ERROR_FAILURE)"
System.out.println("I don't know why this happens, but stopping the computer helps.");
e.printStackTrace();
} catch (Throwable t) {
t.printStackTrace();
}
});
}
@Override
public void notifyUpdate(long x, long y, long width, long height) {
Timing t = new Timing();
GPURenderer.update(pointer.getByteArray(0L, this.width * this.height * 4), this.width, this.height, (int) x, (int) y, (int) width, (int) height);
if (t.elapsedMS() > 60) //Typically 1ms max
System.out.println("Update took " + t.elapsedMS() + "ms");
}
@Override
public void notifyUpdateImage(long arg0, long arg1, long arg2, long arg3, byte[] arg4) {
System.out.println("Update image!");
}
@Override
public void setVisibleRegion(byte arg0, long arg1) {
}
@Override
public void processVHWACommand(byte b, int i, boolean b1) {
}
@Override
public boolean videoModeSupported(long arg0, long arg1, long arg2) {
return true;
}
}

View file

@ -1,61 +0,0 @@
package sznp.virtualcomputer.util;
import lombok.val;
import org.mozilla.interfaces.IEventListener;
import org.virtualbox_6_0.IEvent;
import org.virtualbox_6_0.IEventSource;
import org.virtualbox_6_0.VBoxEventType;
import org.virtualbox_6_0.xpcom.IUnknown;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
public class Utils {
/**
* 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);
}
//public static void registerListener(IEventSource source, IEventListener listener, VBoxEventType... types) {
public static org.virtualbox_6_0.IEventListener registerListener(IEventSource source, IEventListener listener, List<VBoxEventType> types) {
val ret = new org.virtualbox_6_0.IEventListener(listener);
source.registerListener(ret, types, true);
return ret;
}
@SuppressWarnings("unchecked")
public static <T extends IEvent> T getEvent(org.mozilla.interfaces.IEvent event, Class<T> cl) {
//if (event.getType() != type.value()) return null;
//return (T) T.queryInterface(new IEvent(event)); - Probably won't work
try {
val method = cl.getMethod("queryInterface", IUnknown.class);
return (T) method.invoke(null, new IEvent(event));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>VirtualComputer-MSCOM</artifactId>
<version>2.1-SNAPSHOT</version>
<parent>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer</artifactId>
<version>2.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox-MSCOM</artifactId>
<version>6.1</version>
<optional>true
</optional> <!-- https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html -->
</dependency>
<dependency>
<groupId>net.sf.jacob-project</groupId>
<artifactId>jacob</artifactId>
<version>1.19</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,97 @@
package sznp.virtualcomputer;
import lombok.RequiredArgsConstructor;
import org.virtualbox_6_1.BitmapFormat;
import org.virtualbox_6_1.FramebufferCapabilities;
import org.virtualbox_6_1.IFramebufferOverlay;
import sznp.virtualcomputer.util.IMCFrameBuffer;
import java.util.Arrays;
@RequiredArgsConstructor
public class COMFrameBuffer {
private final IMCFrameBuffer frameBuffer;
public long getBitsPerPixel() {
return 32;
}
public long getBytesPerLine() {
return 640L;
}
public long[] getCapabilities(long[] arg0) {
try {
System.out.println("Capabilities queried");
System.out.println("Capabilities: " + Arrays.toString(arg0));
return new long[]{FramebufferCapabilities.UpdateImage.value()};
} catch (Exception e) {
e.printStackTrace();
return new long[]{};
}
}
public long getHeight() {
return 480;
}
public long getHeightReduction() {
return 0;
}
public IFramebufferOverlay getOverlay() {
return null;
}
public long getPixelFormat() {
return BitmapFormat.BGRA.value();
}
public long getVisibleRegion(byte arg0, long arg1) {
return 0;
}
public long getWidth() {
return 640;
}
public long getWinId() {
return 0; // Zero means no win id
}
public void notify3DEvent(long type, byte[] data) {
System.out.println("3D event! " + type + " - " + Arrays.toString(data));
}
public void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height) {
frameBuffer.notifyChange(screenId, xOrigin, yOrigin, width, height);
}
public void notifyUpdate(long x, long y, long width, long height) {
frameBuffer.notifyUpdate(x, y, width, height);
}
public void notifyUpdateImage(long arg0, long arg1, long arg2, long arg3, byte[] arg4) {
frameBuffer.notifyUpdateImage(arg0, arg1, arg2, arg3, arg4);
}
public void setVisibleRegion(byte arg0, long arg1) {
}
/**
* Posts a Video HW Acceleration Command to the frame buffer for processing.<br />
* <br />
* The commands used for 2D video acceleration (DDraw surface creation/destroying, blitting, scaling, color conversion, overlaying, etc.) are posted from quest to the host to be processed by the host hardware.
*
* @param command Pointer to VBOXVHWACMD containing the command to execute.
* @param enmCmd The validated VBOXVHWACMD::enmCmd value from the command.
* @param fromGuest Set when the command origins from the guest, clear if host.
*/ //https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp#L4645
public void processVHWACommand(byte command, int enmCmd, boolean fromGuest) {
}
public boolean videoModeSupported(long arg0, long arg1, long arg2) {
return true;
}
}

View file

@ -0,0 +1,32 @@
package sznp.virtualcomputer;
import org.virtualbox_6_1.IEvent;
import sznp.virtualcomputer.util.COMObjectBase;
import sznp.virtualcomputer.util.IEventHandler;
/**
* A Bukkit-like event system which calls the appropriate methods on an event.
*/
public final class EventHandler extends COMObjectBase {
private final IEventHandler handler;
private boolean enabled = true;
/**
* New MSCOM event handler.
*
* @param handler The handle method that handles what needs to be handled
*/
public EventHandler(IEventHandler handler) {
this.handler = handler;
}
public final void handleEvent(IEvent iEvent) {
if (!enabled)
return;
handler.handleEvent(iEvent);
}
public void disable() {
enabled = false;
}
}

View file

@ -0,0 +1,10 @@
package sznp.virtualcomputer.util;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
public abstract class COMObjectBase extends Dispatch {
public COMObjectBase() {
super("clsid:{67099191-32E7-4F6C-85EE-422304C71B90}");
}
}

View file

@ -0,0 +1,52 @@
package sznp.virtualcomputer.util;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.*;
import lombok.val;
import org.virtualbox_6_1.*;
import org.virtualbox_6_1.mscom.Helper;
import org.virtualbox_6_1.mscom.IUnknown;
import sznp.virtualcomputer.COMFrameBuffer;
import sznp.virtualcomputer.EventHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
public final class COMUtils {
private COMUtils() {
}
//public static void registerListener(IEventSource source, IEventListener listener, VBoxEventType... types) {
public static org.virtualbox_6_1.IEventListener registerListener(IEventSource source, IEventHandler listener, List<VBoxEventType> types) {
//new DispatchEvents(source.getTypedWrapped(), listener);
val ret = new org.virtualbox_6_1.IEventListener(new EventHandler(listener));
/*com.jacob.activeX.ActiveXComponent.createNewInstance("IEventListener");
new ActiveXComponent("");
source.registerListener(ret, types, true);*/
//registerListener(source, new EventHandler(listener), types, true);
System.out.println("Testing listener...");
ret.handleEvent(null);
System.out.println("Tested");
return ret;
//return null;
}
@SuppressWarnings("unchecked")
public static <T extends IEvent> T getEvent(IEvent event, Class<T> cl) {
try {
val method = cl.getMethod("queryInterface", IUnknown.class);
return (T) method.invoke(null, event);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
public static IFramebuffer gimmeAFramebuffer(IMCFrameBuffer frameBuffer) {
return new IFramebuffer(new Variant(new COMFrameBuffer(frameBuffer)).getDispatch());
}
public static void queryBitmapInfo(IDisplaySourceBitmap bitmap, long[] ptr, long[] w, long[] h, long[] bpp, long[] bpl, long[] pf) {
Dispatch.call(bitmap.getTypedWrapped(), "queryBitmapInfo", ptr, w, h, bpp, bpl, pf);
}
}

View file

@ -0,0 +1,7 @@
package sznp.virtualcomputer.util;
import org.virtualbox_6_1.IEvent;
public interface IEventHandler {
void handleEvent(IEvent iEvent);
}

View file

@ -0,0 +1,9 @@
package sznp.virtualcomputer.util;
public interface IMCFrameBuffer {
void notifyUpdate(long x, long y, long width, long height);
void notifyUpdateImage(long x, long y, long width, long height, byte[] image);
void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height);
}

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>VirtualComputer</artifactId>
<groupId>io.github.NorbiPeti</groupId>
<version>2.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>VirtualComputer-XPCOM</artifactId>
<dependencies>
<dependency>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<version>6.1</version>
<optional>true
</optional> <!-- https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<artifactSet>
<includes>
<include>org.virtualbox:VirtualBox</include>
</includes>
</artifactSet>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,122 @@
package sznp.virtualcomputer;
import lombok.RequiredArgsConstructor;
import org.mozilla.interfaces.IFramebuffer;
import org.mozilla.interfaces.IFramebufferOverlay;
import org.mozilla.interfaces.nsISupports;
import org.mozilla.xpcom.Mozilla;
import org.virtualbox_6_1.BitmapFormat;
import org.virtualbox_6_1.FramebufferCapabilities;
import sznp.virtualcomputer.util.IMCFrameBuffer;
import java.util.Arrays;
@RequiredArgsConstructor
public class COMFrameBuffer implements IFramebuffer {
private final IMCFrameBuffer frameBuffer;
@Override
public nsISupports queryInterface(String id) {
return Mozilla.queryInterface(this, id);
}
@Override
public long getBitsPerPixel() {
return 32;
}
@Override
public long getBytesPerLine() {
return 640L;
}
@Override
public long[] getCapabilities(long[] arg0) {
try {
System.out.println("Capabilities queried");
System.out.println("Capabilities: " + Arrays.toString(arg0));
return new long[]{FramebufferCapabilities.UpdateImage.value()};
} catch (Exception e) {
e.printStackTrace();
return new long[]{};
}
}
@Override
public long getHeight() {
return 480;
}
@Override
public long getHeightReduction() {
return 0;
}
@Override
public IFramebufferOverlay getOverlay() {
return null;
}
@Override
public long getPixelFormat() {
return BitmapFormat.BGRA.value();
}
@Override
public long getVisibleRegion(byte arg0, long arg1) {
return 0;
}
@Override
public long getWidth() {
return 640;
}
@Override
public long getWinId() {
return 0; // Zero means no win id
}
@Override
public void notify3DEvent(long type, byte[] data) {
System.out.println("3D event! " + type + " - " + Arrays.toString(data));
}
@Override
public void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height) {
frameBuffer.notifyChange(screenId, xOrigin, yOrigin, width, height);
}
@Override
public void notifyUpdate(long x, long y, long width, long height) {
frameBuffer.notifyUpdate(x, y, width, height);
}
@Override
public void notifyUpdateImage(long arg0, long arg1, long arg2, long arg3, byte[] arg4) {
frameBuffer.notifyUpdateImage(arg0, arg1, arg2, arg3, arg4);
}
@Override
public void setVisibleRegion(byte arg0, long arg1) {
}
/**
* Posts a Video HW Acceleration Command to the frame buffer for processing.<br />
* <br />
* The commands used for 2D video acceleration (DDraw surface creation/destroying, blitting, scaling, color conversion, overlaying, etc.) are posted from quest to the host to be processed by the host hardware.
*
* @param command Pointer to VBOXVHWACMD containing the command to execute.
* @param enmCmd The validated VBOXVHWACMD::enmCmd value from the command.
* @param fromGuest Set when the command origins from the guest, clear if host.
*/ //https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp#L4645
@Override
public void processVHWACommand(byte command, int enmCmd, boolean fromGuest) {
}
@Override
public boolean videoModeSupported(long arg0, long arg1, long arg2) {
return true;
}
}

View file

@ -0,0 +1,34 @@
package sznp.virtualcomputer;
import org.mozilla.interfaces.IEvent;
import org.mozilla.interfaces.IEventListener;
import sznp.virtualcomputer.util.COMObjectBase;
import sznp.virtualcomputer.util.IEventHandler;
/**
* A Bukkit-like event system which calls the appropriate methods on an event.
*/
public final class EventHandler extends COMObjectBase implements IEventListener {
private final IEventHandler handler;
private boolean enabled = true;
/**
* New XPCOM event handler.
*
* @param handler The handle method that handles what needs to be handled
*/
public EventHandler(IEventHandler handler) {
this.handler = handler;
}
@Override
public final void handleEvent(IEvent iEvent) {
if (!enabled)
return;
handler.handleEvent(new org.virtualbox_6_1.IEvent(iEvent));
}
public void disable() {
enabled = false;
}
}

View file

@ -0,0 +1,41 @@
package sznp.virtualcomputer.util;
import lombok.val;
import org.virtualbox_6_1.*;
import org.virtualbox_6_1.xpcom.IUnknown;
import sznp.virtualcomputer.COMFrameBuffer;
import sznp.virtualcomputer.EventHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
public final class COMUtils {
private COMUtils() {
}
//public static void registerListener(IEventSource source, IEventListener listener, VBoxEventType... types) {
public static org.virtualbox_6_1.IEventListener registerListener(IEventSource source, IEventHandler listener, List<VBoxEventType> types) {
val ret = new org.virtualbox_6_1.IEventListener(new EventHandler(listener));
source.registerListener(ret, types, true);
return ret;
}
@SuppressWarnings("unchecked")
public static <T extends IEvent> T getEvent(IEvent event, Class<T> cl) {
try {
val method = cl.getMethod("queryInterface", IUnknown.class);
return (T) method.invoke(null, event);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
public static IFramebuffer gimmeAFramebuffer(IMCFrameBuffer frameBuffer) {
return new IFramebuffer(new COMFrameBuffer(frameBuffer));
}
public static void queryBitmapInfo(IDisplaySourceBitmap bitmap, long[] ptr, long[] w, long[] h, long[] bpp, long[] bpl, long[] pf) {
bitmap.getTypedWrapped().queryBitmapInfo(ptr, w, h, bpp, bpl, pf);
}
}

View file

@ -0,0 +1,7 @@
package sznp.virtualcomputer.util;
import org.virtualbox_6_1.IEvent;
public interface IEventHandler {
void handleEvent(IEvent iEvent);
}

View file

@ -0,0 +1,9 @@
package sznp.virtualcomputer.util;
public interface IMCFrameBuffer {
void notifyUpdate(long x, long y, long width, long height);
void notifyUpdateImage(long x, long y, long width, long height, byte[] image);
void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height);
}

135
pom.xml Normal file
View file

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.NorbiPeti</groupId>
<artifactId>VirtualComputer</artifactId>
<version>2.1-SNAPSHOT</version>
<modules>
<module>VirtualComputerXPCOM</module>
<module>VirtualComputer-Core</module>
<module>VirtualComputerMSCOM</module>
</modules>
<packaging>pom</packaging>
<name>VirtualComputer</name>
<url>https://github.com/NorbiPeti/VirtualComputer</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>repo</id>
<url>file://${basedir}/../repo</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>jitpack</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.jnr/jnr-ffi -->
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-ffi</artifactId>
<version>2.1.16</version>
</dependency>
<dependency> <!-- javax.annotations.Nullable -->
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.14.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- <dependency> !- Not using anything from here directly so we're not that dependent on versions -
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.16.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency> -->
<dependency>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>Chroma-Core</artifactId>
<version>v1.0.0</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

Binary file not shown.

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>net.sf.jacob-project</groupId>
<artifactId>jacob</artifactId>
<version>1.19</version>
<description>POM was created from install:install-file</description>
</project>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>net.sf.jacob-project</groupId>
<artifactId>jacob</artifactId>
<versioning>
<release>1.19</release>
<versions>
<version>1.19</version>
</versions>
<lastUpdated>20200811112851</lastUpdated>
</versioning>
</metadata>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<version>5.2</version>
<artifactId>VirtualBox-MSCOM</artifactId>
<version>6.1</version>
<description>POM was created from install:install-file</description>
</project>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox-MSCOM</artifactId>
<versioning>
<release>6.1</release>
<versions>
<version>6.0</version>
<version>6.1</version>
</versions>
<lastUpdated>20200731215317</lastUpdated>
</versioning>
</metadata>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<version>5.1</version>
<description>POM was created from install:install-file</description>
</project>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<version>5.2</version>
<description>POM was created from install:install-file</description>
</project>

Binary file not shown.

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<version>5.1</version>
<version>6.1</version>
<description>POM was created from install:install-file</description>
</project>

View file

@ -3,12 +3,13 @@
<groupId>org.virtualbox</groupId>
<artifactId>VirtualBox</artifactId>
<versioning>
<release>6.0</release>
<release>6.1</release>
<versions>
<version>5.1</version>
<version>5.2</version>
<version>6.0</version>
<version>6.0</version>
<version>6.1</version>
</versions>
<lastUpdated>20181221211412</lastUpdated>
<lastUpdated>20200811171440</lastUpdated>
</versioning>
</metadata>