Trap flag / Debugger first draft #232

This commit is contained in:
simon987 2020-08-01 20:27:39 -04:00
parent ad08a5ac4f
commit 50b95d1a9c
28 changed files with 851 additions and 171 deletions

View File

@ -62,7 +62,7 @@ public class UserCreationListener implements GameEventListener {
user.setUserCode(config.getString("new_user_code")); user.setUserCode(config.getString("new_user_code"));
GameEvent initEvent = new CpuInitialisationEvent(cpu, cubot); GameEvent initEvent = new CpuInitialisationEvent(cpu, cubot);
GameServer.INSTANCE.getEventDispatcher().dispatch(event); GameServer.INSTANCE.getEventDispatcher().dispatch(initEvent);
if (initEvent.isCancelled()) { if (initEvent.isCancelled()) {
throw new CancelledException(); throw new CancelledException();
} }

View File

@ -5,7 +5,7 @@ import net.simon987.mar.server.game.objects.Action;
public class ExecuteCpuTask extends NPCTask { 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 @Override
public boolean checkCompleted() { public boolean checkCompleted() {
@ -18,9 +18,10 @@ public class ExecuteCpuTask extends NPCTask {
HackedNPC hNpc = (HackedNPC) npc; HackedNPC hNpc = (HackedNPC) npc;
//Execute code //Execute code
int timeout = Math.min(hNpc.getEnergy(), MAX_EXEC_TIME); int allocation = Math.min(hNpc.getEnergy() * 10000, MAX_EXEC_INSTRUCTIONS);
hNpc.getCpu().reset(); hNpc.getCpu().reset();
int cost = hNpc.getCpu().execute(timeout); hNpc.getCpu().setInstructionAlloction(allocation);
int cost = hNpc.getCpu().execute();
hNpc.spendEnergy(cost); hNpc.spendEnergy(cost);
if (hNpc.getCurrentAction() == Action.WALKING) { if (hNpc.getCurrentAction() == Action.WALKING) {

View File

@ -22,6 +22,7 @@ import net.simon987.mar.npc.event.VaultCompleteListener;
import net.simon987.mar.npc.event.VaultWorldUpdateListener; import net.simon987.mar.npc.event.VaultWorldUpdateListener;
import net.simon987.mar.npc.world.TileVaultFloor; import net.simon987.mar.npc.world.TileVaultFloor;
import net.simon987.mar.npc.world.TileVaultWall; 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.CryptoProvider;
import net.simon987.mar.server.crypto.SecretKeyGenerator; import net.simon987.mar.server.crypto.SecretKeyGenerator;
import net.simon987.mar.server.event.*; import net.simon987.mar.server.event.*;
@ -41,6 +42,9 @@ import org.bson.Document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; 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 { public class GameServer implements Runnable {
@ -53,7 +57,7 @@ public class GameServer implements Runnable {
private SocketServer socketServer; private SocketServer socketServer;
private final int maxExecutionTime; private final int maxExecutionInstructions;
private final DayNightCycle dayNightCycle; private final DayNightCycle dayNightCycle;
@ -69,6 +73,8 @@ public class GameServer implements Runnable {
private String secretKey; private String secretKey;
public final ReadWriteLock execLock;
public GameServer() { public GameServer() {
this.config = new ServerConfiguration("config.properties"); this.config = new ServerConfiguration("config.properties");
@ -86,7 +92,7 @@ public class GameServer implements Runnable {
gameUniverse.setMongo(mongo); gameUniverse.setMongo(mongo);
gameRegistry = new GameRegistry(); gameRegistry = new GameRegistry();
maxExecutionTime = config.getInt("user_timeout"); maxExecutionInstructions = config.getInt("user_instructions_per_tick");
cryptoProvider = new CryptoProvider(); cryptoProvider = new CryptoProvider();
@ -104,7 +110,7 @@ public class GameServer implements Runnable {
registerEventListeners(); registerEventListeners();
registerGameObjects(); registerGameObjects();
execLock = new ReentrantReadWriteLock();
} }
private void registerEventListeners() { private void registerEventListeners() {
@ -266,12 +272,14 @@ public class GameServer implements Runnable {
if (user.getControlledUnit() != null && user.getControlledUnit().getCpu() != null) { if (user.getControlledUnit() != null && user.getControlledUnit().getCpu() != null) {
try { 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(); if (!cpu.isPaused()) {
int cost = user.getControlledUnit().getCpu().execute(timeout); cpu.reset();
user.getControlledUnit().spendEnergy(cost); executeUserCode(user);
user.addTime(cost); }
} catch (Exception e) { } catch (Exception e) {
LogManager.LOGGER.severe("Error executing " + user.getUsername() + "'s code"); LogManager.LOGGER.severe("Error executing " + user.getUsername() + "'s code");
@ -281,6 +289,7 @@ public class GameServer implements Runnable {
} }
//Process each worlds //Process each worlds
GameServer.INSTANCE.execLock.writeLock().lock();
for (World world : gameUniverse.getWorlds()) { for (World world : gameUniverse.getWorlds()) {
if (world.shouldUpdate()) { if (world.shouldUpdate()) {
world.update(); world.update();
@ -291,10 +300,23 @@ public class GameServer implements Runnable {
if (gameUniverse.getTime() % config.getInt("save_interval") == 0) { if (gameUniverse.getTime() % config.getInt("save_interval") == 0) {
save(); save();
} }
GameServer.INSTANCE.execLock.writeLock().unlock();
socketServer.tick(); 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() { void load() {
LogManager.LOGGER.info("Loading all data from MongoDB"); LogManager.LOGGER.info("Loading all data from MongoDB");

View File

@ -61,7 +61,6 @@ public class Assembler {
* @return The line without its label part * @return The line without its label part
*/ */
private static String removeLabel(String line) { private static String removeLabel(String line) {
return line.replaceAll(labelPattern, ""); return line.replaceAll(labelPattern, "");
} }
@ -246,7 +245,7 @@ public class Assembler {
int factor = Integer.decode(valueTokens[0]); int factor = Integer.decode(valueTokens[0]);
if (factor > MEM_SIZE) { 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(')')); String value = valueTokens[1].substring(4, valueTokens[1].lastIndexOf(')'));
@ -285,14 +284,15 @@ public class Assembler {
* *
* @param line Current line * @param line Current line
*/ */
private static void checkForSectionDeclaration(String line, AssemblyResult result, private void checkForSectionDeclaration(String line, AssemblyResult result,
int currentLine, int currentOffset) throws AssemblyException { int currentLine, int currentOffset) throws AssemblyException {
String[] tokens = line.split("\\s+"); String[] tokens = line.split("\\s+");
if (tokens[0].toUpperCase().equals(".TEXT")) { if (tokens[0].toUpperCase().equals(".TEXT")) {
result.defineSection(Section.TEXT, currentLine, currentOffset); result.defineSection(Section.TEXT, currentLine, currentOffset);
result.disassemblyLines.add(".text");
throw new PseudoInstructionException(currentLine); throw new PseudoInstructionException(currentLine);
} else if (tokens[0].toUpperCase().equals(".DATA")) { } else if (tokens[0].toUpperCase().equals(".DATA")) {
@ -417,10 +417,10 @@ public class Assembler {
int currentOffset = 0; int currentOffset = 0;
for (int currentLine = 0; currentLine < lines.length; currentLine++) { for (int currentLine = 0; currentLine < lines.length; currentLine++) {
try { try {
checkForLabel(lines[currentLine], result, (char)currentOffset); checkForLabel(lines[currentLine], result, (char) currentOffset);
//Increment offset //Increment offset
currentOffset += parseInstruction(lines[currentLine], currentLine, instructionSet).length / 2; currentOffset += parseInstruction(result, lines[currentLine], currentLine, currentOffset, instructionSet).length / 2;
if (currentOffset >= MEM_SIZE) { if (currentOffset >= MEM_SIZE) {
throw new OffsetOverflowException(currentOffset, MEM_SIZE, currentLine); throw new OffsetOverflowException(currentOffset, MEM_SIZE, currentLine);
@ -455,8 +455,15 @@ public class Assembler {
checkForEQUInstruction(line, result.labels, currentLine); checkForEQUInstruction(line, result.labels, currentLine);
checkForORGInstruction(line, result, 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 //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; currentOffset += bytes.length / 2;
if (currentOffset >= MEM_SIZE) { if (currentOffset >= MEM_SIZE) {
@ -487,8 +494,8 @@ public class Assembler {
* @param currentLine Current line * @param currentLine Current line
* @return The encoded instruction * @return The encoded instruction
*/ */
private byte[] parseInstruction(String line, int currentLine, InstructionSet instructionSet) throws AssemblyException { private byte[] parseInstruction(AssemblyResult result, String line, int currentLine, int offset, InstructionSet instructionSet) throws AssemblyException {
return parseInstruction(line, currentLine, null, instructionSet, true); return parseInstruction(result, line, currentLine, offset, null, instructionSet, true);
} }
/** /**
@ -499,10 +506,10 @@ public class Assembler {
* @param labels List of labels * @param labels List of labels
* @return The encoded instruction * @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,
InstructionSet instructionSet) HashMap<String, Character> labels, InstructionSet instructionSet)
throws AssemblyException { 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 * @param assumeLabels Assume that unknown operands are labels
* @return The encoded instruction * @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) InstructionSet instructionSet, boolean assumeLabels)
throws AssemblyException { throws AssemblyException {
@ -555,6 +562,8 @@ public class Assembler {
throw new InvalidMnemonicException(mnemonic, currentLine); throw new InvalidMnemonicException(mnemonic, currentLine);
} }
StringBuilder disassembly = new StringBuilder();
//Check operands and encode instruction //Check operands and encode instruction
final int beginIndex = line.indexOf(mnemonic) + mnemonic.length(); final int beginIndex = line.indexOf(mnemonic) + mnemonic.length();
if (line.contains(",")) { if (line.contains(",")) {
@ -576,6 +585,17 @@ public class Assembler {
//Get instruction by name //Get instruction by name
instructionSet.get(mnemonic).encode(out, o1, o2, currentLine); 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) { } else if (tokens.length > 1) {
//1 operand //1 operand
@ -591,12 +611,32 @@ public class Assembler {
//Encode instruction //Encode instruction
//Get instruction by name //Get instruction by name
instructionSet.get(mnemonic).encode(out, o1, currentLine); 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 { } else {
//No operand //No operand
//Encode instruction //Encode instruction
//Get instruction by name //Get instruction by name
instructionSet.get(mnemonic).encode(out, currentLine); 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(); return out.toByteArray();

View File

@ -9,6 +9,8 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* Result of an assembly attempt * Result of an assembly attempt
@ -59,8 +61,14 @@ public class AssemblyResult {
*/ */
private boolean dataSectionSet = false; private boolean dataSectionSet = false;
public final Map<Integer, Integer> codeLineMap;
public List<String> disassemblyLines;
AssemblyResult(IServerConfiguration config) { AssemblyResult(IServerConfiguration config) {
origin = config.getInt("org_offset"); origin = config.getInt("org_offset");
codeLineMap = new HashMap<>();
disassemblyLines = new ArrayList<>();
} }
/** /**

View File

@ -20,11 +20,10 @@ import org.bson.Document;
*/ */
public class CPU implements MongoSerializable { public class CPU implements MongoSerializable {
/**
*
*/
private final Status status; private final Status status;
private boolean trapFlag = false;
/** /**
* Memory associated with the CPU, 64kb max * Memory associated with the CPU, 64kb max
*/ */
@ -50,12 +49,15 @@ public class CPU implements MongoSerializable {
private final int graceInstructionCount; private final int graceInstructionCount;
private int graceInstructionsLeft; private int graceInstructionsLeft;
private boolean isGracePeriod; private boolean isGracePeriod;
private int instructionAllocation;
/** /**
* Instruction pointer, always points to the next instruction * Instruction pointer, always points to the next instruction
*/ */
private int ip; private int ip;
public static final int INSTRUCTION_COST = 10000;
/** /**
* Hardware is connected to the hardwareHost * 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 * Sets the IP to IVT + number and pushes flags then the old IP
*/ */
public void interrupt(int number) { public void interrupt(int number) {
if (number == 3) {
trapFlag = true;
return;
}
Instruction push = instructionSet.get(PushInstruction.OPCODE); Instruction push = instructionSet.get(PushInstruction.OPCODE);
push.execute(status.toWord(), status); push.execute(status.toWord(), status);
push.execute(ip, status); push.execute(ip, status);
@ -144,11 +152,15 @@ public class CPU implements MongoSerializable {
ip = codeSectionOffset; ip = codeSectionOffset;
graceInstructionsLeft = graceInstructionCount; graceInstructionsLeft = graceInstructionCount;
isGracePeriod = false; 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; int counter = 0;
status.clear(); status.clear();
@ -158,44 +170,52 @@ public class CPU implements MongoSerializable {
counter++; counter++;
if (isGracePeriod) { if (isGracePeriod) {
if (graceInstructionsLeft-- == 0) { if (graceInstructionsLeft-- <= 0) {
writeExecutionStats(timeout, counter); writeExecutionStats(counter);
return timeout; return counter / INSTRUCTION_COST;
}
} else if (counter % 10000 == 0) {
if (System.currentTimeMillis() > (startTime + timeout)) {
interrupt(IntInstruction.INT_EXEC_LIMIT_REACHED);
isGracePeriod = true;
} }
} else if (instructionAllocation-- <= 0) {
interrupt(IntInstruction.INT_EXEC_LIMIT_REACHED);
isGracePeriod = true;
} }
//fetch instruction step();
int machineCode = memory.get(ip);
/* if (trapFlag) {
* Contents of machineCode should look like this: break;
* 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());
} }
int elapsed = (int) (System.currentTimeMillis() - startTime);
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz"); // 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) { public void step() {
memory.set(EXECUTION_COST_ADDR, timeout); //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, Util.getHigherWord(counter));
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter)); memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
} }
@ -458,7 +478,6 @@ public class CPU implements MongoSerializable {
@Override @Override
public String toString() { public String toString() {
String str = registerSet.toString(); String str = registerSet.toString();
str += status.toString(); str += status.toString();
@ -483,4 +502,12 @@ public class CPU implements MongoSerializable {
status.clone() status.clone()
); );
} }
public boolean isPaused() {
return trapFlag;
}
public void setTrapFlag(boolean trapFlag) {
this.trapFlag = trapFlag;
}
} }

View File

@ -195,4 +195,31 @@ public class Memory implements Target, MongoSerializable, Cloneable {
System.arraycopy(words, 0, memory.words, 0, words.length); System.arraycopy(words, 0, memory.words, 0, words.length);
return memory; 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();
}
} }

View File

@ -264,6 +264,22 @@ public class Operand {
} catch (NumberFormatException e2) { } catch (NumberFormatException e2) {
return false; 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; return false;
@ -281,4 +297,20 @@ public class Operand {
public int getData() { public int getData() {
return data; 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;
}
} }

View File

@ -185,13 +185,20 @@ public class RegisterSet implements Target, MongoSerializable, Cloneable {
@Override @Override
public String toString() { public String toString() {
String str = ""; StringBuilder sb = new StringBuilder();
for (int i = 1; i <= size; i++) { 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 @Override

View File

@ -56,10 +56,14 @@ public class Status implements Cloneable {
} }
public String toString() { public String toString() {
return "" + (signFlag ? 1 : 0) + ' ' + return String.format(
(zeroFlag ? 1 : 0) + ' ' + "C=%d Z=%s S=%s O=%s B=%s",
(carryFlag ? 1 : 0) + ' ' + carryFlag ? 1 : 0,
(overflowFlag ? 1 : 0) + '\n'; zeroFlag ? 1 : 0,
signFlag ? 1 : 0,
overflowFlag ? 1 : 0,
breakFlag ? 1 : 0
);
} }
/** /**

View File

@ -47,20 +47,20 @@ public class Util {
public static String toHex(byte[] byteArray) { public static String toHex(byte[] byteArray) {
String result = ""; StringBuilder result = new StringBuilder();
int count = 0; int count = 0;
for (byte b : byteArray) { for (byte b : byteArray) {
result += String.format("%02X ", b); result.append(String.format("%02X ", b));
if (count == 16) { if (count == 16) {
count = -1; count = -1;
result += "\n"; result.append("\n");
} }
count++; count++;
} }
return result; return result.toString();
} }
/** /**

View File

@ -8,6 +8,8 @@ import net.simon987.mar.server.game.objects.ControllableUnit;
import net.simon987.mar.server.io.MongoSerializable; import net.simon987.mar.server.io.MongoSerializable;
import org.bson.Document; import org.bson.Document;
import java.util.*;
public class User implements MongoSerializable { public class User implements MongoSerializable {
private String username; private String username;
@ -23,6 +25,10 @@ public class User implements MongoSerializable {
private UserStats stats; private UserStats stats;
private Map<Integer, Integer> codeLineMap;
private List<String> disassemblyLines;
public User() throws CancelledException { public User() throws CancelledException {
GameEvent event = new UserCreationEvent(this); GameEvent event = new UserCreationEvent(this);
GameServer.INSTANCE.getEventDispatcher().dispatch(event); GameServer.INSTANCE.getEventDispatcher().dispatch(event);
@ -49,6 +55,16 @@ public class User implements MongoSerializable {
dbObject.put("password", password); dbObject.put("password", password);
dbObject.put("moderator", moderator); dbObject.put("moderator", moderator);
dbObject.put("stats", stats.mongoSerialise()); 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; return dbObject;
} }
@ -59,10 +75,20 @@ public class User implements MongoSerializable {
user.getControlledUnit().setParent(user); user.getControlledUnit().setParent(user);
user.username = (String) obj.get("username"); user.username = (String) obj.get("username");
user.userCode = (String) obj.get("code"); user.userCode = (String) obj.get("code");
user.password = (String) obj.get("password"); user.password = (String) obj.get("password");
user.moderator = (boolean) obj.get("moderator"); user.moderator = (boolean) obj.get("moderator");
user.stats = new UserStats((Document) obj.get("stats")); 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; return user;
} }
@ -78,6 +104,14 @@ public class User implements MongoSerializable {
this.userCode = userCode; this.userCode = userCode;
} }
public void setCodeLineMap(Map<Integer, Integer> codeLineMap) {
this.codeLineMap = codeLineMap;
}
public Map<Integer, Integer> getCodeLineMap() {
return codeLineMap;
}
public String getUsername() { public String getUsername() {
return username; return username;
} }
@ -125,4 +159,27 @@ public class User implements MongoSerializable {
public UserStats getStats() { public UserStats getStats() {
return stats; 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();
}
} }

View File

@ -29,6 +29,9 @@ public class CodeUploadHandler implements MessageHandler {
cpu.getRegisterSet(), cpu.getRegisterSet(),
GameServer.INSTANCE.getConfig()).parse(user.getUser().getUserCode()); GameServer.INSTANCE.getConfig()).parse(user.getUser().getUserCode());
user.getUser().setCodeLineMap(ar.codeLineMap);
user.getUser().setDisassembly(ar.disassemblyLines);
cpu.getMemory().clear(); cpu.getMemory().clear();
//Write assembled code to mem //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.getMemory().write((char) ar.origin, assembledCode, 0, assembledCode.length);
cpu.setCodeSectionOffset(ar.getCodeSectionOffset()); cpu.setCodeSectionOffset(ar.getCodeSectionOffset());
cpu.reset();
//Clear keyboard buffer //Clear keyboard buffer
if (user.getUser().getControlledUnit() != null && if (user.getUser().getControlledUnit() != null &&
user.getUser().getControlledUnit().getKeyboardBuffer() != null) { user.getUser().getControlledUnit().getKeyboardBuffer() != null) {
user.getUser().getControlledUnit().getKeyboardBuffer().clear(); user.getUser().getControlledUnit().getKeyboardBuffer().clear();
} }
//Clear registers
cpu.getRegisterSet().clear();
JSONObject response = new JSONObject(); JSONObject response = new JSONObject();
response.put("t", "codeResponse"); response.put("t", "codeResponse");
response.put("bytes", ar.bytes.length); response.put("bytes", ar.bytes.length);

View File

@ -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());
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -1,5 +1,6 @@
package net.simon987.mar.server.websocket; package net.simon987.mar.server.websocket;
import net.simon987.mar.server.user.User;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import java.util.ArrayList; import java.util.ArrayList;
@ -23,6 +24,21 @@ public class OnlineUserManager {
return null; 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 * Add an user to the list
* *

View File

@ -16,6 +16,7 @@ import org.json.simple.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
@WebSocket(maxTextMessageSize = SocketServer.MAX_TXT_MESSAGE_SIZE) @WebSocket(maxTextMessageSize = SocketServer.MAX_TXT_MESSAGE_SIZE)
public class SocketServer { public class SocketServer {
@ -44,6 +45,9 @@ public class SocketServer {
messageDispatcher.addHandler(new CodeRequestHandler()); messageDispatcher.addHandler(new CodeRequestHandler());
messageDispatcher.addHandler(new KeypressHandler()); messageDispatcher.addHandler(new KeypressHandler());
messageDispatcher.addHandler(new DebugCommandHandler()); messageDispatcher.addHandler(new DebugCommandHandler());
messageDispatcher.addHandler(new StateRequestHandler());
messageDispatcher.addHandler(new DisassemblyRequestHandler());
messageDispatcher.addHandler(new DebugStepHandler());
} }
@OnWebSocketConnect @OnWebSocketConnect
@ -117,6 +121,10 @@ public class SocketServer {
onlineUser.setAuthenticated(true); onlineUser.setAuthenticated(true);
sendString(session, AUTH_OK_MESSAGE); sendString(session, AUTH_OK_MESSAGE);
if (user.getControlledUnit().getCpu().isPaused()) {
promptUserPausedState(user);
}
} }
/** /**
@ -186,4 +194,13 @@ public class SocketServer {
return jsonInts; 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));
}
}
} }

View File

@ -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());
}
}

View File

@ -49,7 +49,7 @@ ivt_offset=0
grace_instruction_count=10000 grace_instruction_count=10000
stack_bottom=65536 stack_bottom=65536
memory_size=65536 memory_size=65536
user_timeout=100 user_instructions_per_tick=100000
#User creation #User creation
new_user_worldX=32767 new_user_worldX=32767
new_user_worldY=32767 new_user_worldY=32767
@ -80,7 +80,7 @@ harvester_regen=5
harvester_biomass_drop_count=8 harvester_biomass_drop_count=8
radio_tower_range=3 radio_tower_range=3
hacked_npc_mem_size=8192 hacked_npc_mem_size=8192
npc_exec_time=5 npc_exec_instructions=50000
hacked_npc_die_on_no_energy=1 hacked_npc_die_on_no_energy=1
#Vaults #Vaults
vault_door_open_time=4 vault_door_open_time=4

View File

@ -321,3 +321,120 @@
-o-animation: rotating 2s linear infinite; -o-animation: rotating 2s linear infinite;
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;
}

View File

@ -47,12 +47,12 @@ ace.define("ace/mode/mar_rules", ["require", "exports", "ace/lib/oop", "ace/mode
start: start:
[{ [{
token: 'keyword.function.assembly', 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 caseInsensitive: true
}, },
{ {
token: 'keyword.operator.assembly', 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 caseInsensitive: true
}, },
{ {

View File

@ -27,10 +27,9 @@ function removeComment(line) {
function checkForLabel(line, result) { function checkForLabel(line, result) {
line = removeComment(line); line = removeComment(line);
var match; let match;
if ((match = /^[a-zA-Z_]\w*:/.exec(line)) !== null) { if ((match = /^\s*([a-zA-Z_]\w*):/.exec(line)) !== null) {
result.labels.push(match[1]);
result.labels.push(match[0].substring(0, match[0].length - 1));
} }
} }
@ -333,7 +332,7 @@ function parseInstruction(line, result, currentLine) {
strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length).trim(); strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length).trim();
//Validate operand number //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({ result.annotations.push({
row: currentLine, row: currentLine,
column: 0, column: 0,
@ -366,7 +365,7 @@ function parseInstruction(line, result, currentLine) {
} else { } else {
//No operand //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 //Validate operand number
result.annotations.push({ result.annotations.push({
@ -422,26 +421,34 @@ function parse() {
editor.getSession().setAnnotations(result.annotations); 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() { function tabWorldClick() {
hideTabs();
document.getElementById("tab-world").classList.add("active"); document.getElementById("tab-world").classList.add("active");
document.getElementById("tab-world-sm").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("world-tab").setAttribute("style", "");
document.getElementById("editor-tab").setAttribute("style", "display: none");
} }
function tabEditorClick() { function tabEditorClick() {
document.getElementById("tab-world").classList.remove("active"); hideTabs();
document.getElementById("tab-world-sm").classList.remove("active");
document.getElementById("tab-editor").classList.add("active"); document.getElementById("tab-editor").classList.add("active");
document.getElementById("tab-editor-sm").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", ""); 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 //Check if browser supports local storage if not than bad luck, use something else than IE7

View File

@ -22,6 +22,16 @@
class="mi">account_circle</i> Account</a> class="mi">account_circle</i> Account</a>
</li> </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"> <li class="nav-item">
<a class="nav-link" target="_blank" href="https://github.com/simon987/Much-Assembly-Required"><img <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> width="23px" src="images/GitHub-Mark-32px.png"> Contribute</a>

View File

@ -3,11 +3,6 @@
#set ($title = $gamePageTitle) #set ($title = $gamePageTitle)
#set ($cur_page = "play") #set ($cur_page = "play")
#parse("head.vm") #parse("head.vm")
<style>
html, body {
overflow-y: hidden;
}
</style>
<body> <body>
#parse("header.vm") #parse("header.vm")
@ -29,19 +24,13 @@
class="mi">code</i></a> class="mi">code</i></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link regular-screen" target="_blank" <a id="tab-debug" class="nav-link regular-screen" href="#" onclick="tabDebuggerClick()"><i
href="https://github.com/simon987/Much-Assembly-Required/wiki"><i class="mi">launch</i> class="mi">bug_report</i> Debugger
Documentation</a> <span id="paused" style="display: none" class="badge badge-danger"><i
<a class="nav-link small-screen" target="_blank" class="mi">pause</i></span>
href="https://github.com/simon987/Much-Assembly-Required/wiki"><i class="mi">launch</i></a> </a>
</li> <a id="tab-debug-sm" class="nav-link small-screen" href="#" onclick="tabDebuggerClick()"><i
<li class="nav-item"> class="mi">bug_report</i></a>
<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>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link regular-screen" onclick="mar.client.findMyRobot()" href="#"><i class="mi">my_location</i> <a class="nav-link regular-screen" onclick="mar.client.findMyRobot()" href="#"><i class="mi">my_location</i>
@ -119,75 +108,97 @@
#end #end
</div> </div>
</div>
</div>
</div>
<div class="bottom-panel" style="height: 0">
<div class="splitter-horizontal"></div> ## DEBUGGER
<div class="console-wrapper"> <div id="debug-tab" style="display: none">
<div class="console-side-bar"> <div class="row">
<p> <div class="col-grow">
<button id="colorButton" class="btn btn-outline-info" <button class="btn btn-shadow btn-primary text-mono"
data-container="body" data-toggle="popover" data-trigger="hover" onclick="mar.client.debugStep('step')" href="#">
data-placement="right" data-content="Invert colors"><i class="mi">invert_colors_off</i></button> <i class="mi">arrow_downward</i>Step</button>
</p> <button class="btn btn-shadow btn-primary text-mono"
<p> onclick="mar.client.debugStep('continue')" href="#">
<button id="scrollButton" class="btn btn-outline-info" <i class="mi">arrow_forward</i>Continue</button>
data-container="body" data-toggle="popover" data-trigger="hover" <div id="state-disassembly"></div>
data-placement="right" data-content="Toggle auto scroll"><i class="mi">swap_vert</i></button> </div>
</p> <div class="col-shrink">
<p> <div id="state-registers"></div>
<div class="btn-group dropright"> <div id="state-status"></div>
<button type="button" class="btn btn-shadow btn-info" data-toggle="dropdown" aria-haspopup="true" <div id="state-memory"></div>
aria-expanded="false"> </div>
<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>
</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> </div>
</div>
<script src="js/ace/ace.js"></script> <div class="bottom-panel" style="height: 0">
<script src="js/popper.min.js"></script> <div class="splitter-horizontal"></div>
<script src="js/jquery.min.js"></script> <div class="console-wrapper">
<script src="js/bootstrap.min.js"></script> <div class="console-side-bar">
<script src="js/jquery-resizable.min.js"></script> <p>
<script src="js/phaser.js"></script> <button id="colorButton" class="btn btn-outline-info"
<script src="js/phaser-plugin-isometric.js"></script> data-container="body" data-toggle="popover" data-trigger="hover"
<script src="js/mar.js"></script> data-placement="right" data-content="Invert colors"><i class="mi">invert_colors_off</i>
<script src="js/editor.js"></script> </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> <p>
$(".bottom-panel").resizable({ <button id="resetButton" class="btn btn-danger btn-shadow" style="margin-left: 0"
handleSelector: ".splitter-horizontal", data-container="body" data-toggle="popover" data-trigger="hover"
resizeWidth: false, data-placement="right" data-content="Clear console"><i class="mi">delete_sweep</i></button>
resizeHeightFrom: "top" </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 () { <script src="js/ace/ace.js"></script>
$('[data-toggle="popover"]').popover() <script src="js/popper.min.js"></script>
}) <script src="js/jquery.min.js"></script>
</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> </body>
</html> </html>

View File

@ -6,7 +6,7 @@ let defaultText =
"|___|___|| __|___._|____||__|__||__||____|_____|__| | __|\n" + "|___|___|| __|___._|____||__|__||__||____|_____|__| | __|\n" +
" |__| |__|\n" + " |__| |__|\n" +
"\n" + "\n" +
"Version 1.5A, 1985-05-17\n" + "Version 1.6A, 1985-05-17\n" +
"Initialising Universal Communication Port connection...Done\n" + "Initialising Universal Communication Port connection...Done\n" +
"Current date is 2790-04-28\n" + "Current date is 2790-04-28\n" +
"Cubot Status: Much Assembly Required"; "Cubot Status: Much Assembly Required";

View File

@ -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 { class AuthListener implements MessageListener {
@ -241,6 +328,7 @@ class CodeResponseListener implements MessageListener {
} }
handle(message): void { handle(message): void {
mar.client.requestDisassembly();
alert("Uploaded and assembled " + message.bytes + " bytes (" + message.exceptions + " errors)"); alert("Uploaded and assembled " + message.bytes + " bytes (" + message.exceptions + " errors)");
} }
@ -311,6 +399,7 @@ class GameClient {
if (DEBUG) { if (DEBUG) {
console.log("[MAR] Uploaded code"); console.log("[MAR] Uploaded code");
} }
mar.isPaused = false;
this.socket.send(JSON.stringify({t: "uploadCode", code: code})) 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})); 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 { public sendDebugCommand(json): void {
this.socket.send(JSON.stringify(json)); this.socket.send(JSON.stringify(json));
@ -411,6 +529,9 @@ class GameClient {
self.listeners.push(new CodeResponseListener()); self.listeners.push(new CodeResponseListener());
self.listeners.push(new CodeListener()); self.listeners.push(new CodeListener());
self.listeners.push(new DebugResponseListener()); 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) { self.socket.onmessage = function (received) {

View File

@ -1,5 +1,23 @@
class MarGame { 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; isoGroup: Phaser.Group;
textGroup: Phaser.Group; textGroup: Phaser.Group;
hudGroup: Phaser.Group; hudGroup: Phaser.Group;

View File

@ -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; DEBUG = false;
let mar = new MarGame(); let mar = new MarGame();