mirror of
https://github.com/simon987/Much-Assembly-Required.git
synced 2025-04-19 18:46:43 +00:00
Trap flag / Debugger first draft #232
This commit is contained in:
parent
ad08a5ac4f
commit
50b95d1a9c
@ -62,7 +62,7 @@ public class UserCreationListener implements GameEventListener {
|
|||||||
user.setUserCode(config.getString("new_user_code"));
|
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();
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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");
|
||||||
|
@ -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();
|
||||||
|
@ -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<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.simon987.mar.server.websocket;
|
||||||
|
|
||||||
|
import net.simon987.mar.server.GameServer;
|
||||||
|
import net.simon987.mar.server.assembly.CPU;
|
||||||
|
import net.simon987.mar.server.logging.LogManager;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DebugStepHandler implements MessageHandler {
|
||||||
|
|
||||||
|
public static String pausedStatePrompt(Integer line, boolean stateSent) {
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
response.put("t", "paused");
|
||||||
|
response.put("line", line == null ? -1 : line);
|
||||||
|
response.put("stateSent", stateSent);
|
||||||
|
return response.toJSONString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(OnlineUser user, JSONObject json) throws IOException {
|
||||||
|
|
||||||
|
if (json.get("t").equals("debugStep")) {
|
||||||
|
|
||||||
|
LogManager.LOGGER.fine("(WS) Debug step from " + user.getUser().getUsername());
|
||||||
|
|
||||||
|
if (user.getUser().isGuest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CPU cpu = user.getUser().getControlledUnit().getCpu();
|
||||||
|
|
||||||
|
if (!cpu.isPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.get("mode").equals("step")) {
|
||||||
|
GameServer.INSTANCE.execLock.readLock().lock();
|
||||||
|
cpu.step();
|
||||||
|
GameServer.INSTANCE.execLock.readLock().unlock();
|
||||||
|
|
||||||
|
Map<Integer, Integer> lineMap = user.getUser().getCodeLineMap();
|
||||||
|
Integer line = lineMap.get(cpu.getIp());
|
||||||
|
|
||||||
|
// Automatically send state when stepping through code to reduce latency
|
||||||
|
user.getWebSocket().getRemote().sendString(pausedStatePrompt(line, true));
|
||||||
|
StateRequestHandler.sendState(user);
|
||||||
|
|
||||||
|
} else if (json.get("mode").equals("continue")) {
|
||||||
|
cpu.setTrapFlag(false);
|
||||||
|
GameServer.INSTANCE.executeUserCode(user.getUser());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.simon987.mar.server.websocket;
|
||||||
|
|
||||||
|
import net.simon987.mar.server.logging.LogManager;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DisassemblyRequestHandler implements MessageHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(OnlineUser user, JSONObject json) throws IOException {
|
||||||
|
|
||||||
|
if (json.get("t").equals("disassemblyRequest")) {
|
||||||
|
|
||||||
|
LogManager.LOGGER.fine("(WS) Disassembly request from " + user.getUser().getUsername());
|
||||||
|
|
||||||
|
if (user.getUser().isGuest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
|
||||||
|
response.put("t", "disassembly");
|
||||||
|
response.put("lines", user.getUser().getDisassembly());
|
||||||
|
|
||||||
|
user.getWebSocket().getRemote().sendString(response.toJSONString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.simon987.mar.server.websocket;
|
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
|
||||||
*
|
*
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package net.simon987.mar.server.websocket;
|
||||||
|
|
||||||
|
import net.simon987.mar.server.assembly.CPU;
|
||||||
|
import net.simon987.mar.server.logging.LogManager;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class StateRequestHandler implements MessageHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(OnlineUser user, JSONObject json) throws IOException {
|
||||||
|
|
||||||
|
if (json.get("t").equals("stateRequest")) {
|
||||||
|
|
||||||
|
LogManager.LOGGER.fine("(WS) State request from " + user.getUser().getUsername());
|
||||||
|
|
||||||
|
if (user.getUser().isGuest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendState(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendState(OnlineUser user) throws IOException {
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
|
||||||
|
CPU cpu = user.getUser().getControlledUnit().getCpu();
|
||||||
|
|
||||||
|
if (!cpu.isPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.put("t", "state");
|
||||||
|
response.put("memory", cpu.getMemory().toString());
|
||||||
|
response.put("status", cpu.getStatus().toString());
|
||||||
|
response.put("registers", cpu.getRegisterSet().toString());
|
||||||
|
Map<Integer, Integer> codeLineMap = user.getUser().getCodeLineMap();
|
||||||
|
Integer line = codeLineMap == null ? null : codeLineMap.get(cpu.getIp());
|
||||||
|
response.put("line", line == null ? 0 : line);
|
||||||
|
|
||||||
|
user.getWebSocket().getRemote().sendString(response.toJSONString());
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ ivt_offset=0
|
|||||||
grace_instruction_count=10000
|
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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
4
src/main/resources/static/js/ace/mode-mar.js
vendored
4
src/main/resources/static/js/ace/mode-mar.js
vendored
@ -47,12 +47,12 @@ ace.define("ace/mode/mar_rules", ["require", "exports", "ace/lib/oop", "ace/mode
|
|||||||
start:
|
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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
35
src/main/resources/static/js/editor.js
vendored
35
src/main/resources/static/js/editor.js
vendored
@ -27,10 +27,9 @@ function removeComment(line) {
|
|||||||
function checkForLabel(line, result) {
|
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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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";
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user