mirror of
https://github.com/simon987/Much-Assembly-Required.git
synced 2025-04-10 14:26:45 +00:00
Trap flag / Debugger first draft #232
This commit is contained in:
parent
ad08a5ac4f
commit
50b95d1a9c
@ -62,7 +62,7 @@ public class UserCreationListener implements GameEventListener {
|
||||
user.setUserCode(config.getString("new_user_code"));
|
||||
|
||||
GameEvent initEvent = new CpuInitialisationEvent(cpu, cubot);
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(event);
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(initEvent);
|
||||
if (initEvent.isCancelled()) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import net.simon987.mar.server.game.objects.Action;
|
||||
|
||||
public class ExecuteCpuTask extends NPCTask {
|
||||
|
||||
private static final int MAX_EXEC_TIME = GameServer.INSTANCE.getConfig().getInt("npc_exec_time");
|
||||
private static final int MAX_EXEC_INSTRUCTIONS = GameServer.INSTANCE.getConfig().getInt("npc_exec_instructions");
|
||||
|
||||
@Override
|
||||
public boolean checkCompleted() {
|
||||
@ -18,9 +18,10 @@ public class ExecuteCpuTask extends NPCTask {
|
||||
HackedNPC hNpc = (HackedNPC) npc;
|
||||
|
||||
//Execute code
|
||||
int timeout = Math.min(hNpc.getEnergy(), MAX_EXEC_TIME);
|
||||
int allocation = Math.min(hNpc.getEnergy() * 10000, MAX_EXEC_INSTRUCTIONS);
|
||||
hNpc.getCpu().reset();
|
||||
int cost = hNpc.getCpu().execute(timeout);
|
||||
hNpc.getCpu().setInstructionAlloction(allocation);
|
||||
int cost = hNpc.getCpu().execute();
|
||||
hNpc.spendEnergy(cost);
|
||||
|
||||
if (hNpc.getCurrentAction() == Action.WALKING) {
|
||||
|
@ -22,6 +22,7 @@ import net.simon987.mar.npc.event.VaultCompleteListener;
|
||||
import net.simon987.mar.npc.event.VaultWorldUpdateListener;
|
||||
import net.simon987.mar.npc.world.TileVaultFloor;
|
||||
import net.simon987.mar.npc.world.TileVaultWall;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.crypto.CryptoProvider;
|
||||
import net.simon987.mar.server.crypto.SecretKeyGenerator;
|
||||
import net.simon987.mar.server.event.*;
|
||||
@ -41,6 +42,9 @@ import org.bson.Document;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class GameServer implements Runnable {
|
||||
|
||||
@ -53,7 +57,7 @@ public class GameServer implements Runnable {
|
||||
|
||||
private SocketServer socketServer;
|
||||
|
||||
private final int maxExecutionTime;
|
||||
private final int maxExecutionInstructions;
|
||||
|
||||
private final DayNightCycle dayNightCycle;
|
||||
|
||||
@ -69,6 +73,8 @@ public class GameServer implements Runnable {
|
||||
|
||||
private String secretKey;
|
||||
|
||||
public final ReadWriteLock execLock;
|
||||
|
||||
public GameServer() {
|
||||
this.config = new ServerConfiguration("config.properties");
|
||||
|
||||
@ -86,7 +92,7 @@ public class GameServer implements Runnable {
|
||||
gameUniverse.setMongo(mongo);
|
||||
gameRegistry = new GameRegistry();
|
||||
|
||||
maxExecutionTime = config.getInt("user_timeout");
|
||||
maxExecutionInstructions = config.getInt("user_instructions_per_tick");
|
||||
|
||||
cryptoProvider = new CryptoProvider();
|
||||
|
||||
@ -104,7 +110,7 @@ public class GameServer implements Runnable {
|
||||
registerEventListeners();
|
||||
registerGameObjects();
|
||||
|
||||
|
||||
execLock = new ReentrantReadWriteLock();
|
||||
}
|
||||
|
||||
private void registerEventListeners() {
|
||||
@ -266,12 +272,14 @@ public class GameServer implements Runnable {
|
||||
if (user.getControlledUnit() != null && user.getControlledUnit().getCpu() != null) {
|
||||
try {
|
||||
|
||||
int timeout = Math.min(user.getControlledUnit().getEnergy(), maxExecutionTime);
|
||||
CPU cpu = user.getControlledUnit().getCpu();
|
||||
int allocation = Math.min(user.getControlledUnit().getEnergy() * CPU.INSTRUCTION_COST, maxExecutionInstructions);
|
||||
cpu.setInstructionAlloction(allocation);
|
||||
|
||||
user.getControlledUnit().getCpu().reset();
|
||||
int cost = user.getControlledUnit().getCpu().execute(timeout);
|
||||
user.getControlledUnit().spendEnergy(cost);
|
||||
user.addTime(cost);
|
||||
if (!cpu.isPaused()) {
|
||||
cpu.reset();
|
||||
executeUserCode(user);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LogManager.LOGGER.severe("Error executing " + user.getUsername() + "'s code");
|
||||
@ -281,6 +289,7 @@ public class GameServer implements Runnable {
|
||||
}
|
||||
|
||||
//Process each worlds
|
||||
GameServer.INSTANCE.execLock.writeLock().lock();
|
||||
for (World world : gameUniverse.getWorlds()) {
|
||||
if (world.shouldUpdate()) {
|
||||
world.update();
|
||||
@ -291,10 +300,23 @@ public class GameServer implements Runnable {
|
||||
if (gameUniverse.getTime() % config.getInt("save_interval") == 0) {
|
||||
save();
|
||||
}
|
||||
GameServer.INSTANCE.execLock.writeLock().unlock();
|
||||
|
||||
socketServer.tick();
|
||||
}
|
||||
|
||||
public void executeUserCode(User user) {
|
||||
GameServer.INSTANCE.execLock.readLock().lock();
|
||||
int cost = user.getControlledUnit().getCpu().execute();
|
||||
user.getControlledUnit().spendEnergy(cost);
|
||||
user.addTime(cost);
|
||||
GameServer.INSTANCE.execLock.readLock().unlock();
|
||||
|
||||
if (user.getControlledUnit().getCpu().isPaused()) {
|
||||
socketServer.promptUserPausedState(user);
|
||||
}
|
||||
}
|
||||
|
||||
void load() {
|
||||
|
||||
LogManager.LOGGER.info("Loading all data from MongoDB");
|
||||
|
@ -61,7 +61,6 @@ public class Assembler {
|
||||
* @return The line without its label part
|
||||
*/
|
||||
private static String removeLabel(String line) {
|
||||
|
||||
return line.replaceAll(labelPattern, "");
|
||||
}
|
||||
|
||||
@ -246,7 +245,7 @@ public class Assembler {
|
||||
int factor = Integer.decode(valueTokens[0]);
|
||||
|
||||
if (factor > MEM_SIZE) {
|
||||
throw new InvalidOperandException("Factor '"+factor+"' exceeds total memory size", currentLine);
|
||||
throw new InvalidOperandException("Factor '" + factor + "' exceeds total memory size", currentLine);
|
||||
}
|
||||
|
||||
String value = valueTokens[1].substring(4, valueTokens[1].lastIndexOf(')'));
|
||||
@ -285,14 +284,15 @@ public class Assembler {
|
||||
*
|
||||
* @param line Current line
|
||||
*/
|
||||
private static void checkForSectionDeclaration(String line, AssemblyResult result,
|
||||
int currentLine, int currentOffset) throws AssemblyException {
|
||||
private void checkForSectionDeclaration(String line, AssemblyResult result,
|
||||
int currentLine, int currentOffset) throws AssemblyException {
|
||||
|
||||
String[] tokens = line.split("\\s+");
|
||||
|
||||
if (tokens[0].toUpperCase().equals(".TEXT")) {
|
||||
|
||||
result.defineSection(Section.TEXT, currentLine, currentOffset);
|
||||
result.disassemblyLines.add(".text");
|
||||
throw new PseudoInstructionException(currentLine);
|
||||
|
||||
} else if (tokens[0].toUpperCase().equals(".DATA")) {
|
||||
@ -417,10 +417,10 @@ public class Assembler {
|
||||
int currentOffset = 0;
|
||||
for (int currentLine = 0; currentLine < lines.length; currentLine++) {
|
||||
try {
|
||||
checkForLabel(lines[currentLine], result, (char)currentOffset);
|
||||
checkForLabel(lines[currentLine], result, (char) currentOffset);
|
||||
|
||||
//Increment offset
|
||||
currentOffset += parseInstruction(lines[currentLine], currentLine, instructionSet).length / 2;
|
||||
currentOffset += parseInstruction(result, lines[currentLine], currentLine, currentOffset, instructionSet).length / 2;
|
||||
|
||||
if (currentOffset >= MEM_SIZE) {
|
||||
throw new OffsetOverflowException(currentOffset, MEM_SIZE, currentLine);
|
||||
@ -455,8 +455,15 @@ public class Assembler {
|
||||
checkForEQUInstruction(line, result.labels, currentLine);
|
||||
checkForORGInstruction(line, result, currentLine);
|
||||
|
||||
for (String label : result.labels.keySet()) {
|
||||
if (result.labels.get(label) == result.origin + currentOffset) {
|
||||
result.disassemblyLines.add(String.format(" %s:", label));
|
||||
}
|
||||
}
|
||||
|
||||
//Encode instruction
|
||||
byte[] bytes = parseInstruction(line, currentLine, result.labels, instructionSet);
|
||||
byte[] bytes = parseInstruction(result, line, currentLine, result.origin + currentOffset, result.labels, instructionSet);
|
||||
result.codeLineMap.put(result.origin + currentOffset, result.disassemblyLines.size() - 1);
|
||||
currentOffset += bytes.length / 2;
|
||||
|
||||
if (currentOffset >= MEM_SIZE) {
|
||||
@ -487,8 +494,8 @@ public class Assembler {
|
||||
* @param currentLine Current line
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private byte[] parseInstruction(String line, int currentLine, InstructionSet instructionSet) throws AssemblyException {
|
||||
return parseInstruction(line, currentLine, null, instructionSet, true);
|
||||
private byte[] parseInstruction(AssemblyResult result, String line, int currentLine, int offset, InstructionSet instructionSet) throws AssemblyException {
|
||||
return parseInstruction(result, line, currentLine, offset, null, instructionSet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -499,10 +506,10 @@ public class Assembler {
|
||||
* @param labels List of labels
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private byte[] parseInstruction(String line, int currentLine, HashMap<String, Character> labels,
|
||||
InstructionSet instructionSet)
|
||||
private byte[] parseInstruction(AssemblyResult result, String line, int currentLine, int offset,
|
||||
HashMap<String, Character> labels, InstructionSet instructionSet)
|
||||
throws AssemblyException {
|
||||
return parseInstruction(line, currentLine, labels, instructionSet, false);
|
||||
return parseInstruction(result, line, currentLine, offset, labels, instructionSet, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -514,7 +521,7 @@ public class Assembler {
|
||||
* @param assumeLabels Assume that unknown operands are labels
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private byte[] parseInstruction(String line, int currentLine, HashMap<String, Character> labels,
|
||||
private byte[] parseInstruction(AssemblyResult result, String line, int currentLine, int offset, HashMap<String, Character> labels,
|
||||
InstructionSet instructionSet, boolean assumeLabels)
|
||||
throws AssemblyException {
|
||||
|
||||
@ -555,6 +562,8 @@ public class Assembler {
|
||||
throw new InvalidMnemonicException(mnemonic, currentLine);
|
||||
}
|
||||
|
||||
StringBuilder disassembly = new StringBuilder();
|
||||
|
||||
//Check operands and encode instruction
|
||||
final int beginIndex = line.indexOf(mnemonic) + mnemonic.length();
|
||||
if (line.contains(",")) {
|
||||
@ -576,6 +585,17 @@ public class Assembler {
|
||||
//Get instruction by name
|
||||
instructionSet.get(mnemonic).encode(out, o1, o2, currentLine);
|
||||
|
||||
if (!assumeLabels) {
|
||||
byte[] bytes = out.toByteArray();
|
||||
for (int i = 0; i < bytes.length; i += 2) {
|
||||
disassembly.append(String.format("%02X%02X ", bytes[i], bytes[i + 1]));
|
||||
}
|
||||
result.disassemblyLines.add(String.format(
|
||||
"%04X %-15s %s %s, %s", offset, disassembly, mnemonic.toUpperCase(),
|
||||
o1.toString(registerSet), o2.toString(registerSet)
|
||||
));
|
||||
}
|
||||
|
||||
} else if (tokens.length > 1) {
|
||||
//1 operand
|
||||
|
||||
@ -591,12 +611,32 @@ public class Assembler {
|
||||
//Encode instruction
|
||||
//Get instruction by name
|
||||
instructionSet.get(mnemonic).encode(out, o1, currentLine);
|
||||
|
||||
if (!assumeLabels) {
|
||||
byte[] bytes = out.toByteArray();
|
||||
for (int i = 0; i < bytes.length; i += 2) {
|
||||
disassembly.append(String.format("%02X%02X ", bytes[i], bytes[i + 1]));
|
||||
}
|
||||
result.disassemblyLines.add(String.format(
|
||||
"%04X %-15s %s %s", offset, disassembly, mnemonic.toUpperCase(), o1.toString(registerSet)
|
||||
));
|
||||
}
|
||||
} else {
|
||||
//No operand
|
||||
|
||||
//Encode instruction
|
||||
//Get instruction by name
|
||||
instructionSet.get(mnemonic).encode(out, currentLine);
|
||||
|
||||
if (!assumeLabels) {
|
||||
byte[] bytes = out.toByteArray();
|
||||
for (int i = 0; i < bytes.length; i += 2) {
|
||||
disassembly.append(String.format("%02X%02X ", bytes[i], bytes[i + 1]));
|
||||
}
|
||||
result.disassemblyLines.add(String.format(
|
||||
"%04X %-15s %s", offset, disassembly, mnemonic.toUpperCase()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
|
@ -9,6 +9,8 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Result of an assembly attempt
|
||||
@ -59,8 +61,14 @@ public class AssemblyResult {
|
||||
*/
|
||||
private boolean dataSectionSet = false;
|
||||
|
||||
public final Map<Integer, Integer> codeLineMap;
|
||||
|
||||
public List<String> disassemblyLines;
|
||||
|
||||
AssemblyResult(IServerConfiguration config) {
|
||||
origin = config.getInt("org_offset");
|
||||
codeLineMap = new HashMap<>();
|
||||
disassemblyLines = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,11 +20,10 @@ import org.bson.Document;
|
||||
*/
|
||||
public class CPU implements MongoSerializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private final Status status;
|
||||
|
||||
private boolean trapFlag = false;
|
||||
|
||||
/**
|
||||
* Memory associated with the CPU, 64kb max
|
||||
*/
|
||||
@ -50,12 +49,15 @@ public class CPU implements MongoSerializable {
|
||||
private final int graceInstructionCount;
|
||||
private int graceInstructionsLeft;
|
||||
private boolean isGracePeriod;
|
||||
private int instructionAllocation;
|
||||
|
||||
/**
|
||||
* Instruction pointer, always points to the next instruction
|
||||
*/
|
||||
private int ip;
|
||||
|
||||
public static final int INSTRUCTION_COST = 10000;
|
||||
|
||||
/**
|
||||
* Hardware is connected to the hardwareHost
|
||||
*/
|
||||
@ -132,6 +134,12 @@ public class CPU implements MongoSerializable {
|
||||
* Sets the IP to IVT + number and pushes flags then the old IP
|
||||
*/
|
||||
public void interrupt(int number) {
|
||||
|
||||
if (number == 3) {
|
||||
trapFlag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Instruction push = instructionSet.get(PushInstruction.OPCODE);
|
||||
push.execute(status.toWord(), status);
|
||||
push.execute(ip, status);
|
||||
@ -144,11 +152,15 @@ public class CPU implements MongoSerializable {
|
||||
ip = codeSectionOffset;
|
||||
graceInstructionsLeft = graceInstructionCount;
|
||||
isGracePeriod = false;
|
||||
trapFlag = false;
|
||||
}
|
||||
|
||||
public int execute(int timeout) {
|
||||
public void setInstructionAlloction(int instructionAllocation) {
|
||||
this.instructionAllocation = instructionAllocation;
|
||||
}
|
||||
|
||||
public int execute() {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
int counter = 0;
|
||||
status.clear();
|
||||
|
||||
@ -158,44 +170,52 @@ public class CPU implements MongoSerializable {
|
||||
counter++;
|
||||
|
||||
if (isGracePeriod) {
|
||||
if (graceInstructionsLeft-- == 0) {
|
||||
writeExecutionStats(timeout, counter);
|
||||
return timeout;
|
||||
}
|
||||
} else if (counter % 10000 == 0) {
|
||||
if (System.currentTimeMillis() > (startTime + timeout)) {
|
||||
interrupt(IntInstruction.INT_EXEC_LIMIT_REACHED);
|
||||
isGracePeriod = true;
|
||||
if (graceInstructionsLeft-- <= 0) {
|
||||
writeExecutionStats(counter);
|
||||
return counter / INSTRUCTION_COST;
|
||||
}
|
||||
} else if (instructionAllocation-- <= 0) {
|
||||
interrupt(IntInstruction.INT_EXEC_LIMIT_REACHED);
|
||||
isGracePeriod = true;
|
||||
}
|
||||
|
||||
//fetch instruction
|
||||
int machineCode = memory.get(ip);
|
||||
step();
|
||||
|
||||
/*
|
||||
* Contents of machineCode should look like this:
|
||||
* SSSS SDDD DDOO OOOO
|
||||
* Where S is source, D is destination and O is the opCode
|
||||
*/
|
||||
Instruction instruction = instructionSet.get(machineCode & 0x03F); // 0000 0000 00XX XXXX
|
||||
|
||||
int source = (machineCode >> 11) & 0x001F; // XXXX X000 0000 0000
|
||||
int destination = (machineCode >> 6) & 0x001F; // 0000 0XXX XX00 0000
|
||||
|
||||
executeInstruction(instruction, source, destination);
|
||||
// LogManager.LOGGER.info(instruction.getMnemonic());
|
||||
if (trapFlag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
int elapsed = (int) (System.currentTimeMillis() - startTime);
|
||||
|
||||
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
|
||||
|
||||
writeExecutionStats(elapsed, counter);
|
||||
writeExecutionStats(counter);
|
||||
|
||||
return elapsed;
|
||||
return counter / INSTRUCTION_COST;
|
||||
}
|
||||
|
||||
private void writeExecutionStats(int timeout, int counter) {
|
||||
memory.set(EXECUTION_COST_ADDR, timeout);
|
||||
public void step() {
|
||||
//fetch instruction
|
||||
int machineCode = memory.get(ip);
|
||||
|
||||
/*
|
||||
* Contents of machineCode should look like this:
|
||||
* SSSS SDDD DDOO OOOO
|
||||
* Where S is source, D is destination and O is the opCode
|
||||
*/
|
||||
Instruction instruction = instructionSet.get(machineCode & 0x03F); // 0000 0000 00XX XXXX
|
||||
|
||||
int source = (machineCode >> 11) & 0x001F; // XXXX X000 0000 0000
|
||||
int destination = (machineCode >> 6) & 0x001F; // 0000 0XXX XX00 0000
|
||||
|
||||
executeInstruction(instruction, source, destination);
|
||||
// LogManager.LOGGER.info(instruction.getMnemonic());
|
||||
|
||||
if (status.isBreakFlag()) {
|
||||
trapFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeExecutionStats(int counter) {
|
||||
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
|
||||
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
|
||||
}
|
||||
@ -458,7 +478,6 @@ public class CPU implements MongoSerializable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String str = registerSet.toString();
|
||||
str += status.toString();
|
||||
|
||||
@ -483,4 +502,12 @@ public class CPU implements MongoSerializable {
|
||||
status.clone()
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return trapFlag;
|
||||
}
|
||||
|
||||
public void setTrapFlag(boolean trapFlag) {
|
||||
this.trapFlag = trapFlag;
|
||||
}
|
||||
}
|
||||
|
@ -195,4 +195,31 @@ public class Memory implements Target, MongoSerializable, Cloneable {
|
||||
System.arraycopy(words, 0, memory.words, 0, words.length);
|
||||
return memory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
result.append(" ");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
result.append(String.format("%4X ", i));
|
||||
}
|
||||
result.append("\n");
|
||||
result.append(" 0 ");
|
||||
|
||||
int count = 1;
|
||||
for (int i = 0; i < words.length; i++) {
|
||||
result.append(String.format("%04X ", (int) words[i]));
|
||||
if (count == 16) {
|
||||
count = 0;
|
||||
result.append("\n");
|
||||
if (i + 1 != words.length) {
|
||||
result.append(String.format("%4X ", i + 1));
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
@ -264,6 +264,22 @@ public class Operand {
|
||||
} catch (NumberFormatException e2) {
|
||||
return false;
|
||||
}
|
||||
} else if (expr.startsWith("+0o")) {
|
||||
try {
|
||||
data = Integer.parseInt(expr.substring(3), 8);
|
||||
value += registerSet.size() * 2; //refers to memory with disp
|
||||
return true;
|
||||
} catch (NumberFormatException e2) {
|
||||
return false;
|
||||
}
|
||||
} else if (expr.startsWith("-0o")) {
|
||||
try {
|
||||
data = -Integer.parseInt(expr.substring(3), 8);
|
||||
value += registerSet.size() * 2; //refers to memory with disp
|
||||
return true;
|
||||
} catch (NumberFormatException e2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -281,4 +297,20 @@ public class Operand {
|
||||
public int getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String toString(RegisterSet registerSet) {
|
||||
switch (type) {
|
||||
case REGISTER16:
|
||||
return registerSet.getRegister(value).getName();
|
||||
case MEMORY_IMM16:
|
||||
return String.format("[%04X]", data);
|
||||
case MEMORY_REG16:
|
||||
return String.format("[%s]", registerSet.getRegister(value - registerSet.size()).getName());
|
||||
case MEMORY_REG_DISP16:
|
||||
return String.format("[%s + %04X]", registerSet.getRegister(value - registerSet.size() * 2).getName(), data);
|
||||
case IMMEDIATE16:
|
||||
return String.format("%04X", data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -185,13 +185,20 @@ public class RegisterSet implements Target, MongoSerializable, Cloneable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String str = "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 1; i <= size; i++) {
|
||||
str += i + " " + getRegister(i).getName() + "=" + Util.toHex(getRegister(i).getValue()) + "\n";
|
||||
Register reg = getRegister(i);
|
||||
sb.append(reg.getName());
|
||||
sb.append("=");
|
||||
if (i == size) {
|
||||
sb.append(String.format("%04X", (int)reg.getValue()));
|
||||
} else {
|
||||
sb.append(String.format("%04X ", (int)reg.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -56,10 +56,14 @@ public class Status implements Cloneable {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "" + (signFlag ? 1 : 0) + ' ' +
|
||||
(zeroFlag ? 1 : 0) + ' ' +
|
||||
(carryFlag ? 1 : 0) + ' ' +
|
||||
(overflowFlag ? 1 : 0) + '\n';
|
||||
return String.format(
|
||||
"C=%d Z=%s S=%s O=%s B=%s",
|
||||
carryFlag ? 1 : 0,
|
||||
zeroFlag ? 1 : 0,
|
||||
signFlag ? 1 : 0,
|
||||
overflowFlag ? 1 : 0,
|
||||
breakFlag ? 1 : 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,20 +47,20 @@ public class Util {
|
||||
|
||||
public static String toHex(byte[] byteArray) {
|
||||
|
||||
String result = "";
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (byte b : byteArray) {
|
||||
result += String.format("%02X ", b);
|
||||
result.append(String.format("%02X ", b));
|
||||
if (count == 16) {
|
||||
count = -1;
|
||||
result += "\n";
|
||||
result.append("\n");
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,6 +8,8 @@ import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class User implements MongoSerializable {
|
||||
|
||||
private String username;
|
||||
@ -23,6 +25,10 @@ public class User implements MongoSerializable {
|
||||
|
||||
private UserStats stats;
|
||||
|
||||
private Map<Integer, Integer> codeLineMap;
|
||||
|
||||
private List<String> disassemblyLines;
|
||||
|
||||
public User() throws CancelledException {
|
||||
GameEvent event = new UserCreationEvent(this);
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(event);
|
||||
@ -49,6 +55,16 @@ public class User implements MongoSerializable {
|
||||
dbObject.put("password", password);
|
||||
dbObject.put("moderator", moderator);
|
||||
dbObject.put("stats", stats.mongoSerialise());
|
||||
dbObject.put("disassembly", disassemblyLines);
|
||||
|
||||
List<List<Integer>> codeLineList = new ArrayList<>();
|
||||
if (codeLineMap != null) {
|
||||
for (int offset: codeLineMap.keySet()) {
|
||||
codeLineList.add(Arrays.asList(offset, codeLineMap.get(offset)));
|
||||
}
|
||||
}
|
||||
dbObject.put("codeLineMap", codeLineList);
|
||||
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
@ -59,10 +75,20 @@ public class User implements MongoSerializable {
|
||||
user.getControlledUnit().setParent(user);
|
||||
user.username = (String) obj.get("username");
|
||||
user.userCode = (String) obj.get("code");
|
||||
|
||||
user.password = (String) obj.get("password");
|
||||
user.moderator = (boolean) obj.get("moderator");
|
||||
user.stats = new UserStats((Document) obj.get("stats"));
|
||||
|
||||
List<List<Integer>> codeLineList = (List<List<Integer>>) obj.get("codeLineMap");
|
||||
if (codeLineList != null) {
|
||||
user.codeLineMap = new HashMap<>(codeLineList.size());
|
||||
for (List<Integer> tuple: codeLineList) {
|
||||
user.codeLineMap.put(tuple.get(0), tuple.get(1));
|
||||
}
|
||||
}
|
||||
user.disassemblyLines = (List<String>) obj.get("disassembly");
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@ -78,6 +104,14 @@ public class User implements MongoSerializable {
|
||||
this.userCode = userCode;
|
||||
}
|
||||
|
||||
public void setCodeLineMap(Map<Integer, Integer> codeLineMap) {
|
||||
this.codeLineMap = codeLineMap;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getCodeLineMap() {
|
||||
return codeLineMap;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
@ -125,4 +159,27 @@ public class User implements MongoSerializable {
|
||||
public UserStats getStats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<String> getDisassembly() {
|
||||
return disassemblyLines;
|
||||
}
|
||||
|
||||
public void setDisassembly(List<String> disassemblyLines) {
|
||||
this.disassemblyLines = disassemblyLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
User user = (User) o;
|
||||
|
||||
return username.equals(user.username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return username.hashCode();
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,9 @@ public class CodeUploadHandler implements MessageHandler {
|
||||
cpu.getRegisterSet(),
|
||||
GameServer.INSTANCE.getConfig()).parse(user.getUser().getUserCode());
|
||||
|
||||
user.getUser().setCodeLineMap(ar.codeLineMap);
|
||||
user.getUser().setDisassembly(ar.disassemblyLines);
|
||||
|
||||
cpu.getMemory().clear();
|
||||
|
||||
//Write assembled code to mem
|
||||
@ -37,15 +40,13 @@ public class CodeUploadHandler implements MessageHandler {
|
||||
cpu.getMemory().write((char) ar.origin, assembledCode, 0, assembledCode.length);
|
||||
cpu.setCodeSectionOffset(ar.getCodeSectionOffset());
|
||||
|
||||
cpu.reset();
|
||||
//Clear keyboard buffer
|
||||
if (user.getUser().getControlledUnit() != null &&
|
||||
user.getUser().getControlledUnit().getKeyboardBuffer() != null) {
|
||||
user.getUser().getControlledUnit().getKeyboardBuffer().clear();
|
||||
}
|
||||
|
||||
//Clear registers
|
||||
cpu.getRegisterSet().clear();
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("t", "codeResponse");
|
||||
response.put("bytes", ar.bytes.length);
|
||||
|
@ -0,0 +1,56 @@
|
||||
package net.simon987.mar.server.websocket;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class DebugStepHandler implements MessageHandler {
|
||||
|
||||
public static String pausedStatePrompt(Integer line, boolean stateSent) {
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("t", "paused");
|
||||
response.put("line", line == null ? -1 : line);
|
||||
response.put("stateSent", stateSent);
|
||||
return response.toJSONString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(OnlineUser user, JSONObject json) throws IOException {
|
||||
|
||||
if (json.get("t").equals("debugStep")) {
|
||||
|
||||
LogManager.LOGGER.fine("(WS) Debug step from " + user.getUser().getUsername());
|
||||
|
||||
if (user.getUser().isGuest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CPU cpu = user.getUser().getControlledUnit().getCpu();
|
||||
|
||||
if (!cpu.isPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.get("mode").equals("step")) {
|
||||
GameServer.INSTANCE.execLock.readLock().lock();
|
||||
cpu.step();
|
||||
GameServer.INSTANCE.execLock.readLock().unlock();
|
||||
|
||||
Map<Integer, Integer> lineMap = user.getUser().getCodeLineMap();
|
||||
Integer line = lineMap.get(cpu.getIp());
|
||||
|
||||
// Automatically send state when stepping through code to reduce latency
|
||||
user.getWebSocket().getRemote().sendString(pausedStatePrompt(line, true));
|
||||
StateRequestHandler.sendState(user);
|
||||
|
||||
} else if (json.get("mode").equals("continue")) {
|
||||
cpu.setTrapFlag(false);
|
||||
GameServer.INSTANCE.executeUserCode(user.getUser());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.simon987.mar.server.websocket;
|
||||
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DisassemblyRequestHandler implements MessageHandler {
|
||||
@Override
|
||||
public void handle(OnlineUser user, JSONObject json) throws IOException {
|
||||
|
||||
if (json.get("t").equals("disassemblyRequest")) {
|
||||
|
||||
LogManager.LOGGER.fine("(WS) Disassembly request from " + user.getUser().getUsername());
|
||||
|
||||
if (user.getUser().isGuest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
|
||||
response.put("t", "disassembly");
|
||||
response.put("lines", user.getUser().getDisassembly());
|
||||
|
||||
user.getWebSocket().getRemote().sendString(response.toJSONString());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.simon987.mar.server.websocket;
|
||||
|
||||
import net.simon987.mar.server.user.User;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -23,6 +24,21 @@ public class OnlineUserManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<OnlineUser> getUser(User user) {
|
||||
|
||||
List<OnlineUser> _onlineUsers = new ArrayList<>(onlineUsers);
|
||||
|
||||
List<OnlineUser> result = new ArrayList<>();
|
||||
|
||||
for (OnlineUser onlineUser : _onlineUsers) {
|
||||
if (onlineUser.getUser().equals(user)) {
|
||||
result.add(onlineUser);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an user to the list
|
||||
*
|
||||
|
@ -16,6 +16,7 @@ import org.json.simple.JSONObject;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@WebSocket(maxTextMessageSize = SocketServer.MAX_TXT_MESSAGE_SIZE)
|
||||
public class SocketServer {
|
||||
@ -44,6 +45,9 @@ public class SocketServer {
|
||||
messageDispatcher.addHandler(new CodeRequestHandler());
|
||||
messageDispatcher.addHandler(new KeypressHandler());
|
||||
messageDispatcher.addHandler(new DebugCommandHandler());
|
||||
messageDispatcher.addHandler(new StateRequestHandler());
|
||||
messageDispatcher.addHandler(new DisassemblyRequestHandler());
|
||||
messageDispatcher.addHandler(new DebugStepHandler());
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
@ -117,6 +121,10 @@ public class SocketServer {
|
||||
onlineUser.setAuthenticated(true);
|
||||
|
||||
sendString(session, AUTH_OK_MESSAGE);
|
||||
|
||||
if (user.getControlledUnit().getCpu().isPaused()) {
|
||||
promptUserPausedState(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,4 +194,13 @@ public class SocketServer {
|
||||
|
||||
return jsonInts;
|
||||
}
|
||||
|
||||
public void promptUserPausedState(User user) {
|
||||
for (OnlineUser onlineUser : onlineUserManager.getUser(user)) {
|
||||
Map<Integer, Integer> lineMap = user.getCodeLineMap();
|
||||
Integer line = lineMap.get(user.getControlledUnit().getCpu().getIp());
|
||||
|
||||
sendString(onlineUser.getWebSocket(), DebugStepHandler.pausedStatePrompt(line, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
package net.simon987.mar.server.websocket;
|
||||
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class StateRequestHandler implements MessageHandler {
|
||||
@Override
|
||||
public void handle(OnlineUser user, JSONObject json) throws IOException {
|
||||
|
||||
if (json.get("t").equals("stateRequest")) {
|
||||
|
||||
LogManager.LOGGER.fine("(WS) State request from " + user.getUser().getUsername());
|
||||
|
||||
if (user.getUser().isGuest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendState(user);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendState(OnlineUser user) throws IOException {
|
||||
JSONObject response = new JSONObject();
|
||||
|
||||
CPU cpu = user.getUser().getControlledUnit().getCpu();
|
||||
|
||||
if (!cpu.isPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
response.put("t", "state");
|
||||
response.put("memory", cpu.getMemory().toString());
|
||||
response.put("status", cpu.getStatus().toString());
|
||||
response.put("registers", cpu.getRegisterSet().toString());
|
||||
Map<Integer, Integer> codeLineMap = user.getUser().getCodeLineMap();
|
||||
Integer line = codeLineMap == null ? null : codeLineMap.get(cpu.getIp());
|
||||
response.put("line", line == null ? 0 : line);
|
||||
|
||||
user.getWebSocket().getRemote().sendString(response.toJSONString());
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ ivt_offset=0
|
||||
grace_instruction_count=10000
|
||||
stack_bottom=65536
|
||||
memory_size=65536
|
||||
user_timeout=100
|
||||
user_instructions_per_tick=100000
|
||||
#User creation
|
||||
new_user_worldX=32767
|
||||
new_user_worldY=32767
|
||||
@ -80,7 +80,7 @@ harvester_regen=5
|
||||
harvester_biomass_drop_count=8
|
||||
radio_tower_range=3
|
||||
hacked_npc_mem_size=8192
|
||||
npc_exec_time=5
|
||||
npc_exec_instructions=50000
|
||||
hacked_npc_die_on_no_energy=1
|
||||
#Vaults
|
||||
vault_door_open_time=4
|
||||
|
@ -321,3 +321,120 @@
|
||||
-o-animation: rotating 2s linear infinite;
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
#state-memory {
|
||||
padding: 0.5em;
|
||||
background: #2D2D2D;
|
||||
text-align: right;
|
||||
overflow-y: scroll;
|
||||
max-height: 488px;
|
||||
|
||||
display: block;
|
||||
white-space: pre;
|
||||
margin: 1em 0;
|
||||
|
||||
font-size: 16px;
|
||||
font-family: fixedsys, monospace;
|
||||
}
|
||||
|
||||
#state-disassembly {
|
||||
padding: 0.5em 0 0.5em 0.5em;
|
||||
background: #2D2D2D;
|
||||
text-align: left;
|
||||
overflow-y: auto;
|
||||
max-height: 600px;
|
||||
|
||||
display: block;
|
||||
white-space: pre;
|
||||
margin: 1em 0;
|
||||
|
||||
font-size: 16px;
|
||||
font-family: fixedsys, monospace;
|
||||
}
|
||||
|
||||
#state-registers {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
margin: 1em 0;
|
||||
|
||||
font-size: 16px;
|
||||
font-family: fixedsys, monospace;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#state-status {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
margin: 1em 0;
|
||||
|
||||
font-size: 16px;
|
||||
font-family: fixedsys, monospace;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#disassembly-hl {
|
||||
background: #ff000077;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.i3 {
|
||||
background: rgba(255, 0, 0, 0.10);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
._0 {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
._l {
|
||||
color: #6699CC;
|
||||
}
|
||||
|
||||
._a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
._k {
|
||||
color: #CC99CC;
|
||||
}
|
||||
|
||||
._o {
|
||||
color: #66CCCC;
|
||||
}
|
||||
|
||||
._b {
|
||||
color: #F2777A;
|
||||
}
|
||||
|
||||
.col-grow {
|
||||
padding: 0 1em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.col-shrink {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
#paused > .mi {
|
||||
font-size: 16px;
|
||||
}
|
||||
#paused {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
|
||||
background-color: #FFFFFF33;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
background-color: #FFFFFF77;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
|
||||
background-color: #0c0d16;
|
||||
}
|
||||
|
4
src/main/resources/static/js/ace/mode-mar.js
vendored
4
src/main/resources/static/js/ace/mode-mar.js
vendored
@ -47,12 +47,12 @@ ace.define("ace/mode/mar_rules", ["require", "exports", "ace/lib/oop", "ace/mode
|
||||
start:
|
||||
[{
|
||||
token: 'keyword.function.assembly',
|
||||
regex: '\\b(?:mov|add|sub|and|or|test|cmp|shl|shr|mul|push|pop|div|xor|dw|nop|equ|neg|hwq|not|ror|rol|sal|sar|inc|dec|rcl|xchg|rcr|pushf|popf)\\b',
|
||||
regex: '\\b(?:mov|add|sub|and|or|test|cmp|shl|shr|mul|push|pop|div|xor|dw|nop|equ|neg|hwq|not|ror|rol|sal|sar|inc|dec|rcl|xchg|rcr|pushf|popf|seta|setnbe|setae|setnb|setnc|setbe|setna|setb|setc|setnae|sete|setz|setne|setnz|setg|setnle|setge|setnl|setle|setng|setl|setnge|seto|setno|sets|setns)\\b',
|
||||
caseInsensitive: true
|
||||
},
|
||||
{
|
||||
token: 'keyword.operator.assembly',
|
||||
regex: '\\b(?:call|ret|jmp|jnz|jg|jl|jge|jle|hwi|jz|js|jns|jc|jnc|jo|jno|ja|jna|seta|setnbe|setae|setnb|setnc|setbe|setna|setb|setc|setnae|sete|setz|setne|setnz|setg|setnle|setge|setnl|setle|setng|setl|setnge|seto|setno|sets|setns)\\b',
|
||||
regex: '\\b(?:call|ret|jmp|jnz|jg|jl|jge|jle|hwi|jz|js|jns|jc|jnc|jo|jno|ja|jna|int|iret|into)\\b',
|
||||
caseInsensitive: true
|
||||
},
|
||||
{
|
||||
|
35
src/main/resources/static/js/editor.js
vendored
35
src/main/resources/static/js/editor.js
vendored
@ -27,10 +27,9 @@ function removeComment(line) {
|
||||
function checkForLabel(line, result) {
|
||||
line = removeComment(line);
|
||||
|
||||
var match;
|
||||
if ((match = /^[a-zA-Z_]\w*:/.exec(line)) !== null) {
|
||||
|
||||
result.labels.push(match[0].substring(0, match[0].length - 1));
|
||||
let match;
|
||||
if ((match = /^\s*([a-zA-Z_]\w*):/.exec(line)) !== null) {
|
||||
result.labels.push(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,7 +332,7 @@ function parseInstruction(line, result, currentLine) {
|
||||
strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length).trim();
|
||||
|
||||
//Validate operand number
|
||||
if (!new RegExp('\\b(?:push|mul|pop|div|neg|call|jnz|jg|jl|jge|jle|hwi|hwq|jz|js|jns|ret|jmp|not|jc|jnc|jo|jno|inc|dec|ja|jna|seta|setnbe|setae|setnb|setnc|setbe|setna|setb|setc|setnae|sete|setz|setne|setnz|setg|setnle|setge|setnl|setle|setng|setl|setnge|seto|setno|sets|setns)\\b').test(mnemonic.toLowerCase())) {
|
||||
if (!new RegExp('\\b(?:push|mul|pop|div|neg|call|jnz|jg|jl|jge|jle|hwi|hwq|jz|js|jns|ret|jmp|not|jc|jnc|jo|jno|inc|dec|ja|jna|seta|setnbe|setae|setnb|setnc|setbe|setna|setb|setc|setnae|sete|setz|setne|setnz|setg|setnle|setge|setnl|setle|setng|setl|setnge|seto|setno|sets|setns|int)\\b').test(mnemonic.toLowerCase())) {
|
||||
result.annotations.push({
|
||||
row: currentLine,
|
||||
column: 0,
|
||||
@ -366,7 +365,7 @@ function parseInstruction(line, result, currentLine) {
|
||||
|
||||
} else {
|
||||
//No operand
|
||||
if (!new RegExp('\\b(?:ret|brk|nop|pushf|popf)\\b').test(mnemonic.toLowerCase())) {
|
||||
if (!new RegExp('\\b(?:ret|brk|nop|pushf|popf|into|iret)\\b').test(mnemonic.toLowerCase())) {
|
||||
|
||||
//Validate operand number
|
||||
result.annotations.push({
|
||||
@ -422,26 +421,34 @@ function parse() {
|
||||
editor.getSession().setAnnotations(result.annotations);
|
||||
}
|
||||
|
||||
function hideTabs() {
|
||||
["tab-world", "tab-world-sm", "tab-editor", "tab-editor-sm", "tab-debug", "tab-debug-sm"]
|
||||
.forEach(tab => document.getElementById(tab).classList.remove("active"));
|
||||
["world-tab", "editor-tab", "debug-tab"]
|
||||
.forEach(tab => document.getElementById(tab).setAttribute("style", "display: none"))
|
||||
}
|
||||
|
||||
function tabWorldClick() {
|
||||
hideTabs();
|
||||
document.getElementById("tab-world").classList.add("active");
|
||||
document.getElementById("tab-world-sm").classList.add("active");
|
||||
document.getElementById("tab-editor").classList.remove("active");
|
||||
document.getElementById("tab-editor-sm").classList.remove("active");
|
||||
|
||||
document.getElementById("world-tab").setAttribute("style", "");
|
||||
document.getElementById("editor-tab").setAttribute("style", "display: none");
|
||||
}
|
||||
|
||||
function tabEditorClick() {
|
||||
document.getElementById("tab-world").classList.remove("active");
|
||||
document.getElementById("tab-world-sm").classList.remove("active");
|
||||
hideTabs();
|
||||
document.getElementById("tab-editor").classList.add("active");
|
||||
document.getElementById("tab-editor-sm").classList.add("active");
|
||||
|
||||
document.getElementById("world-tab").setAttribute("style", "display: none");
|
||||
document.getElementById("editor-tab").setAttribute("style", "");
|
||||
}
|
||||
|
||||
function tabDebuggerClick() {
|
||||
hideTabs();
|
||||
document.getElementById("tab-debug").classList.add("active");
|
||||
document.getElementById("tab-debug-sm").classList.add("active");
|
||||
document.getElementById("debug-tab").setAttribute("style", "");
|
||||
}
|
||||
|
||||
//-----
|
||||
|
||||
//Check if browser supports local storage if not than bad luck, use something else than IE7
|
||||
|
@ -22,6 +22,16 @@
|
||||
class="mi">account_circle</i> Account</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://join.slack.com/t/muchassemblyrequired/shared_invite/enQtMjY3Mjc1OTUwNjEwLWRjMjRkZTg2N2EyNWRjN2YyMDc0YzIyMTUyYzFiNTBmMTU3OGQ1ZjA0MWY0M2IyYjUxZTA4NjRkMWVkNDk2NzY"><i
|
||||
class="mi">chat</i> Slack</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/simon987/Much-Assembly-Required/wiki"><i
|
||||
class="mi">launch</i> Documentation</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" target="_blank" href="https://github.com/simon987/Much-Assembly-Required"><img
|
||||
width="23px" src="images/GitHub-Mark-32px.png"> Contribute</a>
|
||||
|
@ -3,11 +3,6 @@
|
||||
#set ($title = $gamePageTitle)
|
||||
#set ($cur_page = "play")
|
||||
#parse("head.vm")
|
||||
<style>
|
||||
html, body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
|
||||
#parse("header.vm")
|
||||
@ -29,19 +24,13 @@
|
||||
class="mi">code</i></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link regular-screen" target="_blank"
|
||||
href="https://github.com/simon987/Much-Assembly-Required/wiki"><i class="mi">launch</i>
|
||||
Documentation</a>
|
||||
<a class="nav-link small-screen" target="_blank"
|
||||
href="https://github.com/simon987/Much-Assembly-Required/wiki"><i class="mi">launch</i></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link regular-screen" target="_blank"
|
||||
href="https://join.slack.com/t/muchassemblyrequired/shared_invite/enQtMjY3Mjc1OTUwNjEwLWRjMjRkZTg2N2EyNWRjN2YyMDc0YzIyMTUyYzFiNTBmMTU3OGQ1ZjA0MWY0M2IyYjUxZTA4NjRkMWVkNDk2NzY"><i
|
||||
class="mi">chat</i> Slack</a>
|
||||
<a class="nav-link small-screen" target="_blank"
|
||||
href="https://join.slack.com/t/muchassemblyrequired/shared_invite/enQtMjY3Mjc1OTUwNjEwLWRjMjRkZTg2N2EyNWRjN2YyMDc0YzIyMTUyYzFiNTBmMTU3OGQ1ZjA0MWY0M2IyYjUxZTA4NjRkMWVkNDk2NzY"><i
|
||||
class="mi">chat</i></a>
|
||||
<a id="tab-debug" class="nav-link regular-screen" href="#" onclick="tabDebuggerClick()"><i
|
||||
class="mi">bug_report</i> Debugger
|
||||
<span id="paused" style="display: none" class="badge badge-danger"><i
|
||||
class="mi">pause</i></span>
|
||||
</a>
|
||||
<a id="tab-debug-sm" class="nav-link small-screen" href="#" onclick="tabDebuggerClick()"><i
|
||||
class="mi">bug_report</i></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link regular-screen" onclick="mar.client.findMyRobot()" href="#"><i class="mi">my_location</i>
|
||||
@ -119,75 +108,97 @@
|
||||
#end
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-panel" style="height: 0">
|
||||
<div class="splitter-horizontal"></div>
|
||||
<div class="console-wrapper">
|
||||
<div class="console-side-bar">
|
||||
<p>
|
||||
<button id="colorButton" class="btn btn-outline-info"
|
||||
data-container="body" data-toggle="popover" data-trigger="hover"
|
||||
data-placement="right" data-content="Invert colors"><i class="mi">invert_colors_off</i></button>
|
||||
</p>
|
||||
<p>
|
||||
<button id="scrollButton" class="btn btn-outline-info"
|
||||
data-container="body" data-toggle="popover" data-trigger="hover"
|
||||
data-placement="right" data-content="Toggle auto scroll"><i class="mi">swap_vert</i></button>
|
||||
</p>
|
||||
<p>
|
||||
<div class="btn-group dropright">
|
||||
<button type="button" class="btn btn-shadow btn-info" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="mi">short_text</i>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<h6 class="dropdown-header">Line width</h6>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(0)">16 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(1)">24 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(2)">40 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(3)">56 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(4)">64 chars</button>
|
||||
|
||||
## DEBUGGER
|
||||
<div id="debug-tab" style="display: none">
|
||||
<div class="row">
|
||||
<div class="col-grow">
|
||||
<button class="btn btn-shadow btn-primary text-mono"
|
||||
onclick="mar.client.debugStep('step')" href="#">
|
||||
<i class="mi">arrow_downward</i>Step</button>
|
||||
<button class="btn btn-shadow btn-primary text-mono"
|
||||
onclick="mar.client.debugStep('continue')" href="#">
|
||||
<i class="mi">arrow_forward</i>Continue</button>
|
||||
<div id="state-disassembly"></div>
|
||||
</div>
|
||||
<div class="col-shrink">
|
||||
<div id="state-registers"></div>
|
||||
<div id="state-status"></div>
|
||||
<div id="state-memory"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button id="resetButton" class="btn btn-danger btn-shadow" style="margin-left: 0"
|
||||
data-container="body" data-toggle="popover" data-trigger="hover"
|
||||
data-placement="right" data-content="Clear console"><i class="mi">delete_sweep</i></button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="noisy">
|
||||
<div id="consoleText" class="piece output noclick ctr-selection ctr-text"></div>
|
||||
<div class="piece scanlines noclick"></div>
|
||||
<div class="piece glow noclick"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/ace/ace.js"></script>
|
||||
<script src="js/popper.min.js"></script>
|
||||
<script src="js/jquery.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/jquery-resizable.min.js"></script>
|
||||
<script src="js/phaser.js"></script>
|
||||
<script src="js/phaser-plugin-isometric.js"></script>
|
||||
<script src="js/mar.js"></script>
|
||||
<script src="js/editor.js"></script>
|
||||
<div class="bottom-panel" style="height: 0">
|
||||
<div class="splitter-horizontal"></div>
|
||||
<div class="console-wrapper">
|
||||
<div class="console-side-bar">
|
||||
<p>
|
||||
<button id="colorButton" class="btn btn-outline-info"
|
||||
data-container="body" data-toggle="popover" data-trigger="hover"
|
||||
data-placement="right" data-content="Invert colors"><i class="mi">invert_colors_off</i>
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button id="scrollButton" class="btn btn-outline-info"
|
||||
data-container="body" data-toggle="popover" data-trigger="hover"
|
||||
data-placement="right" data-content="Toggle auto scroll"><i class="mi">swap_vert</i>
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<div class="btn-group dropright">
|
||||
<button type="button" class="btn btn-shadow btn-info" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="mi">short_text</i>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<h6 class="dropdown-header">Line width</h6>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(0)">16 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(1)">24 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(2)">40 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(3)">56 chars</button>
|
||||
<button class="dropdown-item" onclick="mar.client.consoleScreen.setMode(4)">64 chars</button>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<script>
|
||||
$(".bottom-panel").resizable({
|
||||
handleSelector: ".splitter-horizontal",
|
||||
resizeWidth: false,
|
||||
resizeHeightFrom: "top"
|
||||
});
|
||||
<p>
|
||||
<button id="resetButton" class="btn btn-danger btn-shadow" style="margin-left: 0"
|
||||
data-container="body" data-toggle="popover" data-trigger="hover"
|
||||
data-placement="right" data-content="Clear console"><i class="mi">delete_sweep</i></button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="noisy">
|
||||
<div id="consoleText" class="piece output noclick ctr-selection ctr-text"></div>
|
||||
<div class="piece scanlines noclick"></div>
|
||||
<div class="piece glow noclick"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover()
|
||||
})
|
||||
</script>
|
||||
<script src="js/ace/ace.js"></script>
|
||||
<script src="js/popper.min.js"></script>
|
||||
<script src="js/jquery.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/jquery-resizable.min.js"></script>
|
||||
<script src="js/phaser.js"></script>
|
||||
<script src="js/phaser-plugin-isometric.js"></script>
|
||||
<script src="js/mar.js"></script>
|
||||
<script src="js/editor.js"></script>
|
||||
|
||||
<script>
|
||||
$(".bottom-panel").resizable({
|
||||
handleSelector: ".splitter-horizontal",
|
||||
resizeWidth: false,
|
||||
resizeHeightFrom: "top"
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -6,7 +6,7 @@ let defaultText =
|
||||
"|___|___|| __|___._|____||__|__||__||____|_____|__| | __|\n" +
|
||||
" |__| |__|\n" +
|
||||
"\n" +
|
||||
"Version 1.5A, 1985-05-17\n" +
|
||||
"Version 1.6A, 1985-05-17\n" +
|
||||
"Initialising Universal Communication Port connection...Done\n" +
|
||||
"Current date is 2790-04-28\n" +
|
||||
"Cubot Status: Much Assembly Required";
|
||||
|
@ -119,6 +119,93 @@ class UserInfoListener implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
class PausedPromptListener implements MessageListener {
|
||||
|
||||
getListenedMessageType() {
|
||||
return "paused";
|
||||
}
|
||||
|
||||
handle(message): void {
|
||||
mar.pausedLine = message.line;
|
||||
mar.isPaused = true;
|
||||
if (!message.stateSent) {
|
||||
mar.client.requestState();
|
||||
}
|
||||
|
||||
if (mar.disassembly == null) {
|
||||
mar.client.requestDisassembly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StateListener implements MessageListener {
|
||||
|
||||
getListenedMessageType() {
|
||||
return "state";
|
||||
}
|
||||
|
||||
handle(message): void {
|
||||
const stateMemory = document.getElementById("state-memory");
|
||||
while (stateMemory.firstChild) {
|
||||
stateMemory.removeChild(stateMemory.firstChild);
|
||||
}
|
||||
const stateRegisters = document.getElementById("state-registers");
|
||||
while (stateRegisters.firstChild) {
|
||||
stateRegisters.removeChild(stateRegisters.firstChild);
|
||||
}
|
||||
const stateStatus = document.getElementById("state-status");
|
||||
while (stateStatus.firstChild) {
|
||||
stateStatus.removeChild(stateStatus.firstChild);
|
||||
}
|
||||
|
||||
stateMemory.insertAdjacentHTML("beforeend", message.memory.replace(/(0000 )+/g, '<span class="_0">$&</span>'));
|
||||
// stateMemory.insertAdjacentHTML("beforeend", message.memory);
|
||||
stateRegisters.insertAdjacentHTML("beforeend", message.registers.replace(/(0000 )+/g, '<span class="_0">$&</span>'));
|
||||
|
||||
stateStatus.insertAdjacentHTML("beforeend", message.status.replace(/=0/g, '=<span class="_0">0</span>'));
|
||||
updateDisassemblyPane()
|
||||
}
|
||||
}
|
||||
|
||||
function updateDisassemblyPane() {
|
||||
const line = mar.pausedLine;
|
||||
const lines = mar.disassembly.slice();
|
||||
const stateDisassembly = document.getElementById("state-disassembly");
|
||||
|
||||
while (stateDisassembly.firstChild) {
|
||||
stateDisassembly.removeChild(stateDisassembly.firstChild);
|
||||
}
|
||||
|
||||
if (line != -1 && mar.isPaused) {
|
||||
lines[line] = `<span id="disassembly-hl">${lines[line]}</span>`
|
||||
}
|
||||
|
||||
stateDisassembly.innerHTML = lines.join("\n")
|
||||
.replace(/^\s*([a-zA-Z_]\w*):/gm, '<span class="_l"> $1:</span>')
|
||||
.replace(/^.*INT 0003$/gm, '<span class="i3">$&</span>')
|
||||
.replace(/^[0-9A-F]{4}/gm, '<span class="_a">$&</span>')
|
||||
.replace(/ (MOV|ADD|SUB|AND|OR|TEST|CMP|SHL|SHR|MUL|PUSH|POP|DIV|XOR|DW|NOP|EQU|NEG|HWQ|NOT|ROR|ROL|SAL|SAR|INC|DEC|RCL|XCHG|RCR|PUSHF|POPF|INT|IRET|INTO|SETA|SETNBE|SETAE|SETNB|SETNC|SETBE|SETNA|SETB|SETC|SETNAE|SETE|SETZ|SETNE|SETNZ|SETG|SETNLE|SETGE|SETNL|SETLE|SETNG|SETL|SETNGE|SETO|SETNO|SETS|SETNS)/g, '<span class="_k"> $1</span>')
|
||||
.replace(/ (CALL|RET|JMP|JNZ|JG|JL|JGE|JLE|HWI|JZ|JS|JNS|JC|JNC|JO|JNO|JA|JNA) /g, '<span class="_o"> $1 </span>')
|
||||
.replace(/ (BRK)$/gm, '<span class="_b"> $1</span>')
|
||||
|
||||
const hl = document.getElementById("disassembly-hl");
|
||||
if (hl != null) {
|
||||
hl.scrollIntoView({block: "center"});
|
||||
}
|
||||
}
|
||||
|
||||
class DisassemblyListener implements MessageListener {
|
||||
|
||||
getListenedMessageType() {
|
||||
return "disassembly";
|
||||
}
|
||||
|
||||
handle(message): void {
|
||||
mar.disassembly = message.lines;
|
||||
updateDisassemblyPane();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AuthListener implements MessageListener {
|
||||
|
||||
@ -241,6 +328,7 @@ class CodeResponseListener implements MessageListener {
|
||||
}
|
||||
|
||||
handle(message): void {
|
||||
mar.client.requestDisassembly();
|
||||
alert("Uploaded and assembled " + message.bytes + " bytes (" + message.exceptions + " errors)");
|
||||
}
|
||||
|
||||
@ -311,6 +399,7 @@ class GameClient {
|
||||
if (DEBUG) {
|
||||
console.log("[MAR] Uploaded code");
|
||||
}
|
||||
mar.isPaused = false;
|
||||
|
||||
this.socket.send(JSON.stringify({t: "uploadCode", code: code}))
|
||||
}
|
||||
@ -343,6 +432,35 @@ class GameClient {
|
||||
this.socket.send(JSON.stringify({t: "object", x: this.worldX, y: this.worldY, dimension: this.dimension}));
|
||||
}
|
||||
|
||||
public requestState(): void {
|
||||
if (DEBUG) {
|
||||
console.log("[MAR] Requesting CPU state");
|
||||
}
|
||||
|
||||
this.socket.send(JSON.stringify({t: "stateRequest"}))
|
||||
}
|
||||
|
||||
public requestDisassembly(): void {
|
||||
if (DEBUG) {
|
||||
console.log("[MAR] Requesting disassembly");
|
||||
}
|
||||
|
||||
this.socket.send(JSON.stringify({t: "disassemblyRequest"}))
|
||||
}
|
||||
|
||||
public debugStep(mode): void {
|
||||
if (DEBUG) {
|
||||
console.log("[MAR] Debug step " + mode);
|
||||
}
|
||||
|
||||
if (mode == "continue") {
|
||||
mar.isPaused = false;
|
||||
updateDisassemblyPane();
|
||||
}
|
||||
|
||||
this.socket.send(JSON.stringify({t: "debugStep", mode: mode}))
|
||||
}
|
||||
|
||||
public sendDebugCommand(json): void {
|
||||
|
||||
this.socket.send(JSON.stringify(json));
|
||||
@ -411,6 +529,9 @@ class GameClient {
|
||||
self.listeners.push(new CodeResponseListener());
|
||||
self.listeners.push(new CodeListener());
|
||||
self.listeners.push(new DebugResponseListener());
|
||||
self.listeners.push(new StateListener());
|
||||
self.listeners.push(new DisassemblyListener());
|
||||
self.listeners.push(new PausedPromptListener());
|
||||
|
||||
self.socket.onmessage = function (received) {
|
||||
|
||||
|
@ -1,5 +1,23 @@
|
||||
class MarGame {
|
||||
|
||||
disassembly : string[];
|
||||
pausedLine: number = -1;
|
||||
|
||||
_isPaused = false
|
||||
|
||||
get isPaused() {
|
||||
return this._isPaused
|
||||
}
|
||||
|
||||
set isPaused(val) {
|
||||
if (val) {
|
||||
document.getElementById("paused").style.display = "";
|
||||
} else {
|
||||
document.getElementById("paused").style.display = "none";
|
||||
}
|
||||
this._isPaused = val;
|
||||
}
|
||||
|
||||
isoGroup: Phaser.Group;
|
||||
textGroup: Phaser.Group;
|
||||
hudGroup: Phaser.Group;
|
||||
|
@ -262,6 +262,14 @@ class Debug {
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", ev => {
|
||||
if (ev.key === "F8") {
|
||||
mar.client.debugStep("step");
|
||||
} else if (ev.key === "F9") {
|
||||
mar.client.debugStep("continue");
|
||||
}
|
||||
})
|
||||
|
||||
DEBUG = false;
|
||||
|
||||
let mar = new MarGame();
|
||||
|
Loading…
x
Reference in New Issue
Block a user