Add shitty filesystem executor

This commit is contained in:
Linnea Gräf 2024-07-22 04:31:48 +02:00
parent 5545dc1ab8
commit 7c76b5ca1a
No known key found for this signature in database
GPG key ID: AA563E93EB628D91
3 changed files with 160 additions and 29 deletions

View file

@ -14,7 +14,7 @@ public record AjarDirectory(
builder ->
builder.group(Codec.unboundedMap(Codec.STRING, Codec.lazyInitialized(() -> AjarFile.CODEC))
.fieldOf("listing").forGetter(AjarDirectory::listing))
.apply(builder, AjarDirectory::new));
.apply(builder, map -> new AjarDirectory(new HashMap<>(map))));
public static AjarDirectory ofEmpty() {
return new AjarDirectory(new HashMap<>());

View file

@ -6,6 +6,7 @@ import dev.exhq.ajarc.network.ComputerScreenUpdate;
import dev.exhq.ajarc.register.Register;
import dev.exhq.ajarc.vm.JsVm;
//import dev.exhq.ajarc.vm.WasmVm;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
@ -15,8 +16,10 @@ import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -50,6 +53,10 @@ public class ComputerBlockEntity extends BlockEntity {
.getOrThrow();
}
public AjarFileSystem getFileSystem() {
return fileSystem;
}
@Override
protected void saveAdditional(@NotNull CompoundTag pTag, HolderLookup.@NotNull Provider pRegistries) {
super.saveAdditional(pTag, pRegistries);
@ -83,10 +90,31 @@ public class ComputerBlockEntity extends BlockEntity {
} else if (line.equals("big")) {
screen = ComputerTerminal.ofSize(20, 30);
lines.add("Made big!");
} else if (line.startsWith("add")) {
lines.add(jsVm.add(69, 42) + "");
} else if (line.startsWith("resetfs")) {
var root = fileSystem.root().listing();
var directory = (AjarDirectory) root.compute("bin", (ignored, old) ->
old instanceof AjarDirectory ? old : AjarDirectory.ofEmpty());
Minecraft.getInstance().getResourceManager()
.listResources("bin", pred -> pred.getNamespace().equals(Ajar.MODID) && pred.getPath().endsWith(".js"))
.forEach(
(resourceLocation, resource) ->
{
try {
directory.listing().put(
resourceLocation.getPath().replace("bin/", ""),
new AjarRegularFile(IOUtils.toByteArray(resource.open()))
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
);
} else {
lines.add("Made unknown!");
var bin = (AjarDirectory) fileSystem.root().listing().get("bin");
var parts = line.split(" ");
var commandFile =(AjarRegularFile) bin.listing().get(parts[0] + ".js");
var commandText = commandFile.getTextContent();
jsVm.executeScript(commandText, Arrays.copyOfRange(parts, 1, parts.length));
}
}
@ -96,4 +124,8 @@ public class ComputerBlockEntity extends BlockEntity {
screen.dimensions().rows(),
screen.dimensions().columns());
}
public void addLine(String line) {
lines.add(line);
}
}

View file

@ -2,52 +2,151 @@ package dev.exhq.ajarc.vm;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import delight.nashornsandbox.internal.JsSanitizer;
import dev.exhq.ajarc.Ajar;
import dev.exhq.ajarc.computer.AjarDirectory;
import dev.exhq.ajarc.computer.AjarFile;
import dev.exhq.ajarc.computer.AjarRegularFile;
import dev.exhq.ajarc.computer.AjarSymlinkFile;
import dev.exhq.ajarc.computer.ComputerBlockEntity;
import dev.exhq.ajarc.computer.ComputerTerminal;
import net.minecraft.client.Minecraft;
import org.apache.commons.io.IOUtils;
import javax.script.ScriptException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class JsVm {
private final NashornSandbox sandbox;
private final ComputerBlockEntity entity;
public JsVm(ComputerBlockEntity entity) {
sandbox = NashornSandboxes.create();
sandbox.inject("screen", new ScreenHelper(entity));
this.sandbox = NashornSandboxes.create();
this.entity = entity;
this.sandbox.inject("screen", new ScreenHelper(entity));
this.sandbox.inject("fs", new FileSystemHelper(entity));
this.sandbox.allow(ProcessHelper.class);
}
public record ProcessHelper(String[] args) {
}
private record BoundFile(AjarDirectory parent, String name) {
public boolean exists() {
return parent.listing().containsKey(name);
}
public AjarFile resolve() {
return parent.listing().get(name);
}
public void set(AjarFile file) {
parent.listing().put(name, file);
}
public void unlink() {
parent.listing().remove(name);
}
}
public record FileSystemHelper(ComputerBlockEntity entity) {
private List<String> splitPath(String path) {
var array = new ArrayList<>(Arrays.asList(path.split("[/\\\\]")));
for (int i = 0; i < array.size(); i++) {
var str = array.get(i);
if ("..".equals(str) && i > 1) {
array.remove(i);
array.remove(i - 1);
i--;
} else if (".".equals(str) || "".equals(str)) {
array.remove(i);
i--;
}
}
return array;
}
private BoundFile resolveFile(
String path
) {
AjarDirectory node = entity.getFileSystem().root();
List<String> splitPath = splitPath(path);
if (splitPath.isEmpty())
return null;
for (int i = 0; i < splitPath.size(); i++) {
var segment = splitPath.get(i);
var child = node.listing().get(segment);
if (child == null) return null;
if (child instanceof AjarSymlinkFile symlink) {
// TODO: symlink infinite recursion protection
if (symlink.target().startsWith("/")) {
return resolveFile(symlink.target() + "/" + String.join("/", splitPath.subList(i + 1, splitPath.size())));
} else {
return resolveFile(String.join("/", splitPath.subList(0, i)) + "/" + symlink.target() + splitPath.subList(i + 1, splitPath.size()));
}
}
if (i == splitPath.size() - 1) break;
if (!(child instanceof AjarDirectory directory)) {
throw new RuntimeException("No such file or directory " + path);
}
node = directory;
}
return new BoundFile(node, splitPath.getLast());
}
public boolean unlink(String path) {
var boundFile = resolveFile(path);
if (boundFile == null || !boundFile.exists())
return false;
boundFile.unlink();
return true;
}
public String readFile(String path) {
// TODO: resolve relative to working directory
var boundFile = resolveFile(path);
if (boundFile == null || !boundFile.exists())
throw new RuntimeException("No such file: " + path);
var file = boundFile.resolve();
if (file instanceof AjarRegularFile regularFile) {
return regularFile.getTextContent();
}
throw new RuntimeException("File is not a text file");
}
public void writeFile(String path, String content) {
var boundFile = resolveFile(path);
if (boundFile == null) {
throw new RuntimeException("Could not resolve file (parent directory does not exist): " + path);
}
var existingFile = boundFile.resolve();
if (existingFile != null && !(existingFile instanceof AjarRegularFile)) {
throw new RuntimeException("Cannot overwrite non text file: " + path);
}
boundFile.set(new AjarRegularFile(content.getBytes(StandardCharsets.UTF_8)));
}
}
public record ScreenHelper(ComputerBlockEntity entity) {
public void setScreenSize(int rows, int cols) {
entity.setTerminal(ComputerTerminal.ofSize(rows, cols));
}
public void print(String line) {
entity.addLine(line);
}
}
public int add(int left, int right) {
String text;
public void executeScript(String source, String[] args) {
try {
var stream = Minecraft.getInstance()
.getResourceManager()
.getResourceOrThrow(Ajar.identifier("script.js"))
.open();
text = IOUtils.toString(stream, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
// TODO: provide an import facility of some sort
var bindings = sandbox.createBindings();
bindings.put("left", left);
bindings.put("right", right);
try {
sandbox.eval(text, bindings);
bindings.put("process", new ProcessHelper(args));
sandbox.eval(source, bindings);
} catch (ScriptException e) {
throw new RuntimeException(e);
Ajar.LOGGER.error("Could not execute script", e);
entity.addLine("Could not execute script: " + e.getMessage());
}
return (int) (double) bindings.get("result");
}
}