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"));
GameEvent initEvent = new CpuInitialisationEvent(cpu, cubot);
GameServer.INSTANCE.getEventDispatcher().dispatch(event);
GameServer.INSTANCE.getEventDispatcher().dispatch(initEvent);
if (initEvent.isCancelled()) {
throw new CancelledException();
}

View File

@ -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) {

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.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");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

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;
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
*

View File

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

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
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

View File

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

View File

@ -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
},
{

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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";

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 {
@ -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) {

View File

@ -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;

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