Tsuchinoko Real?!
This commit is contained in:
parent
4c25ed9a98
commit
661f983e1d
906 changed files with 57143 additions and 0 deletions
25
gb_studio_project/build/web/README.md
Normal file
25
gb_studio_project/build/web/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# binjgb
|
||||
|
||||
Fork of binji's Game Boy emulator built as a WebAssembly module.
|
||||
|
||||
It includes changes from [Daid's fork](https://github.com/daid/binjgb) and others to better support GB Studio.
|
||||
|
||||
## License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
21
gb_studio_project/build/web/binjgb.js
Normal file
21
gb_studio_project/build/web/binjgb.js
Normal file
File diff suppressed because one or more lines are too long
BIN
gb_studio_project/build/web/binjgb.wasm
Normal file
BIN
gb_studio_project/build/web/binjgb.wasm
Normal file
Binary file not shown.
367
gb_studio_project/build/web/css/style.css
Normal file
367
gb_studio_project/build/web/css/style.css
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
body {
|
||||
background: #031921;
|
||||
color: #fff;
|
||||
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue",
|
||||
Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
touch-action: none;
|
||||
-webkit-touch-callout: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#game {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#game canvas {
|
||||
object-fit: contain;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -webkit-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#controller {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
height: 210px;
|
||||
width: 100%;
|
||||
touch-action: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#controller_dpad {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 0px;
|
||||
width: 184px;
|
||||
height: 184px;
|
||||
}
|
||||
|
||||
#controller_dpad:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #5c5c5c;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
#5c5c5c 0%,
|
||||
#555 59%,
|
||||
#5c5c5c 60%
|
||||
);
|
||||
position: absolute;
|
||||
left: 68px;
|
||||
top: 68px;
|
||||
}
|
||||
|
||||
#controller_left {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 68px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #666;
|
||||
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#controller_right {
|
||||
position: absolute;
|
||||
left: 116px;
|
||||
top: 68px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #666;
|
||||
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
#controller_up {
|
||||
position: absolute;
|
||||
left: 68px;
|
||||
top: 20px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #666;
|
||||
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
#controller_down {
|
||||
position: absolute;
|
||||
left: 68px;
|
||||
top: 116px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #666;
|
||||
background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%);
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
#controller_a {
|
||||
position: absolute;
|
||||
bottom: 110px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
#controller_b {
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
right: 100px;
|
||||
}
|
||||
|
||||
.roundBtn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
font-size: 32px;
|
||||
color: #440f1f;
|
||||
line-height: 64px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 64px;
|
||||
background: #870a4c;
|
||||
background: radial-gradient(ellipse at center, #ab1465 0%, #8b1e57 100%);
|
||||
box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.capsuleBtn {
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
color: #111;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 40px;
|
||||
text-transform: uppercase;
|
||||
width: 64px;
|
||||
height: 32px;
|
||||
border-radius: 40px;
|
||||
background: #222;
|
||||
background: radial-gradient(ellipse at center, #666 0%, #555 100%);
|
||||
box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#controller_start {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#controller_select {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 100px;
|
||||
}
|
||||
|
||||
.btnPressed {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin: 0px auto;
|
||||
-webkit-animation: rotation 0.8s linear infinite;
|
||||
-moz-animation: rotation 0.8s linear infinite;
|
||||
-o-animation: rotation 0.8s linear infinite;
|
||||
animation: rotation 0.8s linear infinite;
|
||||
border-left: 10px solid #306850;
|
||||
border-right: 10px solid #306850;
|
||||
border-bottom: 10px solid #306850;
|
||||
border-top: 10px solid #88c070;
|
||||
border-radius: 100%;
|
||||
background-color: #031921;
|
||||
}
|
||||
@-webkit-keyframes rotation {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes rotation {
|
||||
from {
|
||||
-moz-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-moz-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes rotation {
|
||||
from {
|
||||
-o-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-o-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
#game canvas {
|
||||
margin-top: 0px;
|
||||
width: 100%;
|
||||
max-width: 512px;
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 812px) and (orientation: portrait) {
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#game {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#game canvas {
|
||||
margin: 0;
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 320px) and (orientation: portrait) {
|
||||
#controller_dpad {
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
#controller_a {
|
||||
right: 5px;
|
||||
bottom: 95px;
|
||||
}
|
||||
|
||||
#controller_b {
|
||||
right: 80px;
|
||||
}
|
||||
|
||||
#controller_start {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
#controller_select {
|
||||
right: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) and (max-height: 400px) {
|
||||
#controller {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small devices in landscape */
|
||||
@media only screen and (max-device-width: 300px) and (orientation: landscape) {
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#game:after {
|
||||
content: "PLEASE ROTATE ↻";
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#game canvas {
|
||||
display: none;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
#controller {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Devices large enough for landscape */
|
||||
@media only screen and (min-width: 300px) and (orientation: landscape) {
|
||||
#controller {
|
||||
bottom: 50%;
|
||||
transform: translateY(50%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
#debug {
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
padding:10px;
|
||||
position:fixed;
|
||||
top:0px;
|
||||
left:0px;
|
||||
right:0px;
|
||||
bottom:0px;
|
||||
background:rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
#debug div {
|
||||
background: white;
|
||||
color: black;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
transform: position;
|
||||
font-size: 11px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#debug span {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
#debug button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-left: 1px solid #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#debug button:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#debug button:active {
|
||||
background: #ddd;
|
||||
}
|
||||
35
gb_studio_project/build/web/index.html
Normal file
35
gb_studio_project/build/web/index.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<title>Slightys Midnight Adventure</title>
|
||||
<meta name="author" content="fate6" />
|
||||
<style type="text/css"> body { background-color:#202850; }</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="game">
|
||||
<canvas id="mainCanvas" width="256" height="224">No Canvas Support</canvas>
|
||||
</div>
|
||||
<div id="controller">
|
||||
<div id="controller_dpad">
|
||||
<div id="controller_left"></div>
|
||||
<div id="controller_right"></div>
|
||||
<div id="controller_up"></div>
|
||||
<div id="controller_down"></div>
|
||||
</div>
|
||||
<div id="controller_select" class="capsuleBtn">Select</div>
|
||||
<div id="controller_start" class="capsuleBtn">Start</div>
|
||||
<div id="controller_b" class="roundBtn">B</div>
|
||||
<div id="controller_a" class="roundBtn">A</div>
|
||||
</div>
|
||||
<script>
|
||||
const customControls = {"up":["ArrowUp","w"],"down":["ArrowDown","s"],"left":["ArrowLeft","a"],"right":["ArrowRight","d"],"a":["Alt","z","j"],"b":["Control","k","x"],"start":["Enter"],"select":["Shift"]}
|
||||
</script>
|
||||
<script src="binjgb.js"></script>
|
||||
<script src="js/script.js"></script>
|
||||
<script src="js/debugger.js"></script>
|
||||
</body>
|
||||
567
gb_studio_project/build/web/js/debugger.js
Normal file
567
gb_studio_project/build/web/js/debugger.js
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
/* global EVENT_NEW_FRAME, EVENT_AUDIO_BUFFER_FULL, EVENT_UNTIL_TICKS, vm, emulator, API */
|
||||
|
||||
let debug;
|
||||
|
||||
// Consts
|
||||
|
||||
const EVENT_BREAKPOINT = 8;
|
||||
const EXECUTING_CTX_SYMBOL = "_executing_ctx";
|
||||
const FIRST_CTX_SYMBOL = "_first_ctx";
|
||||
const SCRIPT_MEMORY_SYMBOL = "_script_memory";
|
||||
const CURRENT_SCENE_SYMBOL = "_current_scene";
|
||||
const MAX_GLOBAL_VARS = "MAX_GLOBAL_VARS";
|
||||
|
||||
// Helpers
|
||||
|
||||
const toAddrHex = (value) =>
|
||||
("0000" + value.toString(16).toUpperCase()).slice(-4);
|
||||
|
||||
const parseDebuggerSymbol = (input) => {
|
||||
const match = input.match(
|
||||
/GBVM\$([^$]+)\$([^$]+)\$([^$]+)\$([^$]+)\$([^$]+)\$([^$]+)/
|
||||
);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
scriptSymbol: match[1],
|
||||
scriptEventId: match[2].replace(/_/g, "-"),
|
||||
sceneId: match[3].replace(/_/g, "-"),
|
||||
entityType: match[4],
|
||||
entityId: match[5].replace(/_/g, "-"),
|
||||
scriptKey: match[6],
|
||||
};
|
||||
};
|
||||
|
||||
const parseDebuggerEndSymbol = (input) => {
|
||||
const match = input.match(/GBVM_END\$([^$]+)\$([^$]+)/);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
scriptSymbol: match[1],
|
||||
};
|
||||
};
|
||||
|
||||
// Debugger
|
||||
|
||||
class Debug {
|
||||
constructor(emulator) {
|
||||
this.emulator = emulator;
|
||||
this.module = emulator.module;
|
||||
this.e = emulator.e;
|
||||
|
||||
this.vramCanvas = document.createElement("canvas");
|
||||
this.vramCanvas.width = 256;
|
||||
this.vramCanvas.height = 256;
|
||||
|
||||
this.memoryMap = {};
|
||||
this.globalVariables = {};
|
||||
this.variableMap = {};
|
||||
this.memoryDict = new Map();
|
||||
|
||||
this.breakpoints = [];
|
||||
this.pauseOnScriptChanged = false;
|
||||
this.pauseOnWatchedVariableChanged = true;
|
||||
this.pauseOnVMStep = false;
|
||||
this.currentScriptSymbol = "";
|
||||
this.scriptContexts = [];
|
||||
this.pausedUI = null;
|
||||
this.prevGlobals = [];
|
||||
this.watchedVariables = [];
|
||||
|
||||
this.debugRunUntil = (ticks) => {
|
||||
while (true) {
|
||||
const event = this.module._emulator_run_until_f64(this.e, ticks);
|
||||
if (event & EVENT_NEW_FRAME) {
|
||||
this.emulator.rewind.pushBuffer();
|
||||
this.emulator.video.uploadTexture();
|
||||
}
|
||||
if (event & EVENT_BREAKPOINT) {
|
||||
// Breakpoint hit
|
||||
const firstCtxAddr = this.memoryMap[FIRST_CTX_SYMBOL];
|
||||
const executingCtxAddr = this.memoryMap[EXECUTING_CTX_SYMBOL];
|
||||
|
||||
const currentCtx = this.readMemInt16(executingCtxAddr);
|
||||
let firstCtx = debug.readMemInt16(firstCtxAddr);
|
||||
let scriptContexts = [];
|
||||
let currentCtxData = undefined;
|
||||
const prevCtxs = this.scriptContexts;
|
||||
|
||||
while (firstCtx !== 0) {
|
||||
const ctxAddr = debug.readMemInt16(firstCtx);
|
||||
const ctxBank = debug.readMem(firstCtx + 2);
|
||||
const ctxStackPtrAddr = debug.readMemInt16(firstCtx + 8);
|
||||
const ctxStackBaseAddr = debug.readMemInt16(firstCtx + 10);
|
||||
|
||||
const closestAddr = debug.getClosestAddress(ctxBank, ctxAddr);
|
||||
const closestSymbol = debug.getSymbol(ctxBank, closestAddr);
|
||||
const closestGBVMSymbol = parseDebuggerSymbol(closestSymbol);
|
||||
const prevCtx = prevCtxs[scriptContexts.length];
|
||||
|
||||
let stackString = "";
|
||||
for (var i = ctxStackBaseAddr; i < ctxStackPtrAddr + 4; i += 2) {
|
||||
stackString += `${i === ctxStackPtrAddr ? "->" : " "}${toAddrHex(
|
||||
i
|
||||
)}: ${debug.readMemInt16(i)}\n`;
|
||||
}
|
||||
|
||||
const ctxData = {
|
||||
address: ctxAddr,
|
||||
bank: ctxBank,
|
||||
current: currentCtx === firstCtx,
|
||||
closestAddr,
|
||||
closestSymbol,
|
||||
closestGBVMSymbol,
|
||||
prevClosestSymbol: prevCtx?.closestSymbol,
|
||||
prevClosestGBVMSymbol: prevCtx?.closestGBVMSymbol,
|
||||
stackString,
|
||||
};
|
||||
|
||||
scriptContexts.push(ctxData);
|
||||
if (ctxData.current) {
|
||||
currentCtxData = ctxData;
|
||||
}
|
||||
|
||||
firstCtx = debug.readMemInt16(firstCtx + 3);
|
||||
}
|
||||
this.scriptContexts = scriptContexts;
|
||||
|
||||
if (currentCtxData) {
|
||||
// If pausing on VM Step and current script block changed
|
||||
if (
|
||||
this.pauseOnVMStep &&
|
||||
currentCtxData.closestGBVMSymbol &&
|
||||
currentCtxData.closestGBVMSymbol.scriptEventId !== "end" &&
|
||||
currentCtxData.closestSymbol !== currentCtxData.prevClosestSymbol
|
||||
) {
|
||||
emulator.pause();
|
||||
this.pauseOnVMStep = false;
|
||||
break;
|
||||
}
|
||||
// If manual breakpoint is hit
|
||||
if (
|
||||
currentCtxData.closestGBVMSymbol &&
|
||||
currentCtxData.address === currentCtxData.closestAddr &&
|
||||
currentCtxData.closestSymbol !==
|
||||
currentCtxData.prevClosestSymbol &&
|
||||
this.breakpoints.includes(
|
||||
currentCtxData.closestGBVMSymbol.scriptEventId
|
||||
)
|
||||
) {
|
||||
this.pauseOnVMStep = true;
|
||||
emulator.pause();
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
this.pauseOnScriptChanged &&
|
||||
// Found matching GBVM event
|
||||
currentCtxData.closestGBVMSymbol &&
|
||||
// GBVM event has changed since last pause
|
||||
(!currentCtxData.prevClosestGBVMSymbol ||
|
||||
currentCtxData.closestGBVMSymbol.scriptSymbol !==
|
||||
currentCtxData.prevClosestGBVMSymbol.scriptSymbol)
|
||||
) {
|
||||
this.pauseOnVMStep = true;
|
||||
emulator.pause();
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.pauseOnWatchedVariableChanged) {
|
||||
const globals = this.getGlobals();
|
||||
if (this.prevGlobals.length > 0) {
|
||||
// Check if watched has change
|
||||
const modified = !this.prevGlobals.every(
|
||||
(v, i) => v === globals[i]
|
||||
);
|
||||
if (modified) {
|
||||
const changedVariable = this.watchedVariables.find(
|
||||
(variableId) => {
|
||||
const variableData = this.variableMap[variableId];
|
||||
const symbol = variableData?.symbol;
|
||||
const variableIndex = this.globalVariables[symbol];
|
||||
if (variableIndex !== undefined) {
|
||||
return (
|
||||
this.prevGlobals[variableIndex] !== undefined &&
|
||||
globals[variableIndex] !==
|
||||
this.prevGlobals[variableIndex]
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
if (changedVariable) {
|
||||
this.pauseOnVMStep = true;
|
||||
emulator.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.prevGlobals = globals;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event & EVENT_AUDIO_BUFFER_FULL && !this.emulator.isRewinding) {
|
||||
this.emulator.audio.pushBuffer();
|
||||
}
|
||||
if (event & EVENT_UNTIL_TICKS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.module._emulator_was_ext_ram_updated(this.e)) {
|
||||
vm.extRamUpdated = true;
|
||||
}
|
||||
};
|
||||
|
||||
// replace the emulator run method with the debug one
|
||||
this.emulator.runUntil = this.debugRunUntil;
|
||||
}
|
||||
|
||||
initialize(
|
||||
memoryMap,
|
||||
globalVariables,
|
||||
variableMap,
|
||||
pauseOnScriptChanged,
|
||||
pauseOnWatchedVarChanged,
|
||||
breakpoints,
|
||||
watchedVariables
|
||||
) {
|
||||
this.memoryMap = memoryMap;
|
||||
this.globalVariables = globalVariables;
|
||||
this.variableMap = variableMap;
|
||||
this.pauseOnScriptChanged = pauseOnScriptChanged;
|
||||
this.pauseOnWatchedVariableChanged = pauseOnWatchedVarChanged;
|
||||
this.breakpoints = breakpoints;
|
||||
this.watchedVariables = watchedVariables;
|
||||
|
||||
const memoryDict = new Map();
|
||||
Object.keys(memoryMap).forEach((k) => {
|
||||
// Banked resources
|
||||
const match = k.match(/___bank_(.*)/);
|
||||
if (match) {
|
||||
const label = `_${match[1]}`;
|
||||
const bank = memoryMap[k];
|
||||
if (memoryMap[label]) {
|
||||
const n = memoryDict.get(bank) ?? new Map();
|
||||
const ptr = memoryMap[label] & 0x0ffff;
|
||||
n.set(ptr, label);
|
||||
memoryDict.set(bank, n);
|
||||
}
|
||||
}
|
||||
// Script debug symbols
|
||||
// const matchGBVM = k.match(/GBVM\$([^$]*)\$([^$]*)/);
|
||||
const matchGBVM = parseDebuggerSymbol(k);
|
||||
if (matchGBVM) {
|
||||
const bankLabel = `___bank_${matchGBVM.scriptSymbol}`;
|
||||
const label = k;
|
||||
const bank = memoryMap[bankLabel];
|
||||
if (memoryMap[label]) {
|
||||
const n = memoryDict.get(bank) ?? new Map();
|
||||
const ptr = memoryMap[label] & 0x0ffff;
|
||||
n.set(ptr, label);
|
||||
memoryDict.set(bank, n);
|
||||
}
|
||||
}
|
||||
|
||||
const matchEnd = parseDebuggerEndSymbol(k);
|
||||
if (matchEnd) {
|
||||
const bankLabel = `___bank_${matchEnd.scriptSymbol}`;
|
||||
const label = k;
|
||||
const bank = memoryMap[bankLabel];
|
||||
if (memoryMap[label]) {
|
||||
const n = memoryDict.get(bank) ?? new Map();
|
||||
const ptr = memoryMap[label] & 0x0ffff;
|
||||
if (!n.get(ptr)) {
|
||||
n.set(ptr, label);
|
||||
memoryDict.set(bank, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.memoryDict = memoryDict;
|
||||
|
||||
// Break on VM_STEP
|
||||
this.module._emulator_set_breakpoint(this.e, memoryMap["_VM_STEP"]);
|
||||
|
||||
// Add paused UI
|
||||
|
||||
this.initializeUI();
|
||||
this.initializeKeyboardShortcuts();
|
||||
}
|
||||
|
||||
initializeUI() {
|
||||
const pausedUI = document.createElement("div");
|
||||
const pausedUIContainer = document.createElement("div");
|
||||
const pausedUILabel = document.createElement("span");
|
||||
const pausedUIResumeBtn = document.createElement("button");
|
||||
const pausedUIStepBtn = document.createElement("button");
|
||||
const pausedUIStepFrameBtn = document.createElement("button");
|
||||
|
||||
document.body.appendChild(pausedUI);
|
||||
pausedUI.appendChild(pausedUIContainer);
|
||||
pausedUIContainer.appendChild(pausedUILabel);
|
||||
pausedUIContainer.appendChild(pausedUIResumeBtn);
|
||||
pausedUIContainer.appendChild(pausedUIStepBtn);
|
||||
pausedUIContainer.appendChild(pausedUIStepFrameBtn);
|
||||
|
||||
pausedUI.id = "debug";
|
||||
pausedUILabel.innerHTML = "Paused in debugger";
|
||||
|
||||
pausedUIResumeBtn.innerHTML = `<svg width="15" height="15" viewBox="0 0 24 24"><path d="M2 3H6V21H2V3Z" /><path d="M22 12L7 21L7 3L22 12Z" /></svg>`;
|
||||
pausedUIResumeBtn.title = "Resume execution - F8";
|
||||
pausedUIResumeBtn.addEventListener("click", this.resume.bind(this));
|
||||
|
||||
pausedUIStepBtn.innerHTML = `<svg width="15" height="15" viewBox="0 0 24 24"><path d="M16 8v-4l8 8-8 8v-4h-5v-8h5zm-7 0h-2v8h2v-8zm-4.014 0h-1.986v8h1.986v-8zm-3.986 0h-1v8h1v-8z" /></svg>`;
|
||||
pausedUIStepBtn.title = "Step - F9";
|
||||
pausedUIStepBtn.addEventListener("click", this.step.bind(this));
|
||||
|
||||
pausedUIStepFrameBtn.innerHTML = `<svg width="15" height="15" viewBox="0 0 24 24"><path d="M19 12l-18 12v-24l18 12zm4-11h-4v22h4v-22z" /></svg>`;
|
||||
pausedUIStepFrameBtn.title = "Step Frame - F10";
|
||||
pausedUIStepFrameBtn.addEventListener("click", this.stepFrame.bind(this));
|
||||
|
||||
this.pausedUI = pausedUI;
|
||||
}
|
||||
|
||||
initializeKeyboardShortcuts() {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key === "F8") {
|
||||
this.togglePlayPause();
|
||||
} else if (e.key === "F9") {
|
||||
this.step();
|
||||
} else if (e.key === "F10") {
|
||||
this.stepFrame();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getClosestAddress(bank, address) {
|
||||
const bankScripts = this.memoryDict.get(bank);
|
||||
const currentAddress = address;
|
||||
let closestAddress = -1;
|
||||
if (bankScripts) {
|
||||
const addresses = Array.from(bankScripts.keys()).sort();
|
||||
for (let i = 0; i < addresses.length; i++) {
|
||||
if (addresses[i] > currentAddress) {
|
||||
break;
|
||||
} else {
|
||||
closestAddress = addresses[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestAddress;
|
||||
}
|
||||
|
||||
getSymbol(bank, address) {
|
||||
const symbol = this.memoryDict.get(bank)?.get(address) ?? "";
|
||||
return symbol.replace(/^_/, "");
|
||||
}
|
||||
|
||||
readMem(addr) {
|
||||
return this.module._emulator_read_mem(this.e, addr);
|
||||
}
|
||||
|
||||
readMemInt16(addr) {
|
||||
return (
|
||||
(this.module._emulator_read_mem(this.e, addr + 1) << 8) |
|
||||
this.module._emulator_read_mem(this.e, addr)
|
||||
);
|
||||
}
|
||||
|
||||
writeMem(addr, value) {
|
||||
this.module._emulator_write_mem(this.e, addr, value & 0xff);
|
||||
}
|
||||
|
||||
writeMemInt16(addr, value) {
|
||||
this.module._emulator_write_mem(this.e, addr, value & 0xff);
|
||||
this.module._emulator_write_mem(this.e, addr + 1, value >> 8);
|
||||
}
|
||||
|
||||
readVariables(addr, size) {
|
||||
const ptr = this.module._emulator_get_wram_ptr(this.e) - 0xc000;
|
||||
return new Int16Array(
|
||||
this.module.HEAP8.buffer.slice(ptr + addr, ptr + addr + size * 2)
|
||||
);
|
||||
}
|
||||
|
||||
renderVRam() {
|
||||
var ctx = this.vramCanvas.getContext("2d");
|
||||
var imgData = ctx.createImageData(256, 256);
|
||||
var ptr = this.module._malloc(4 * 256 * 256);
|
||||
this.module._emulator_render_vram(this.e, ptr);
|
||||
var buffer = new Uint8Array(this.module.HEAP8.buffer, ptr, 4 * 256 * 256);
|
||||
imgData.data.set(buffer);
|
||||
ctx.putImageData(imgData, 0, 0);
|
||||
this.module._free(ptr);
|
||||
return this.vramCanvas.toDataURL("image/png");
|
||||
}
|
||||
|
||||
setBreakPoints(breakpoints) {
|
||||
this.breakpoints = breakpoints;
|
||||
}
|
||||
|
||||
setWatchedVariables(watchedVariables) {
|
||||
this.watchedVariables = watchedVariables;
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.pauseOnVMStep = true;
|
||||
this.emulator.pause();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.pauseOnVMStep = false;
|
||||
this.emulator.resume();
|
||||
}
|
||||
|
||||
togglePlayPause() {
|
||||
if (this.isPaused()) {
|
||||
this.resume();
|
||||
} else {
|
||||
this.pause();
|
||||
}
|
||||
}
|
||||
|
||||
step() {
|
||||
if (this.isPaused()) {
|
||||
this.resume();
|
||||
this.pauseOnVMStep = true;
|
||||
}
|
||||
}
|
||||
|
||||
stepFrame() {
|
||||
if (this.isPaused()) {
|
||||
const ticks = this.module._emulator_get_ticks_f64(this.e) + 70224;
|
||||
this.emulator.runUntil(ticks);
|
||||
this.emulator.video.renderTexture();
|
||||
}
|
||||
}
|
||||
|
||||
isPaused() {
|
||||
return this.emulator.isPaused || this.pauseOnVMStep;
|
||||
}
|
||||
|
||||
getGlobals() {
|
||||
const variablesStartAddr = this.memoryMap[SCRIPT_MEMORY_SYMBOL];
|
||||
const variablesLength = this.globalVariables[MAX_GLOBAL_VARS];
|
||||
return this.readVariables(variablesStartAddr, variablesLength);
|
||||
}
|
||||
|
||||
setGlobal(symbol, value) {
|
||||
const offset = (this.globalVariables[symbol] ?? 0) * 2;
|
||||
const variablesStartAddr = this.memoryMap[SCRIPT_MEMORY_SYMBOL];
|
||||
this.writeMemInt16(variablesStartAddr + offset, value);
|
||||
this.prevGlobals = this.getGlobals();
|
||||
}
|
||||
|
||||
getCurrentSceneSymbol() {
|
||||
const currentSceneAddr = this.memoryMap[CURRENT_SCENE_SYMBOL];
|
||||
return this.getSymbol(
|
||||
this.readMem(currentSceneAddr),
|
||||
this.readMemInt16(currentSceneAddr + 1)
|
||||
);
|
||||
}
|
||||
|
||||
getNumScriptCtxs() {
|
||||
const firstCtxAddr = this.memoryMap[FIRST_CTX_SYMBOL];
|
||||
let firstCtx = debug.readMemInt16(firstCtxAddr);
|
||||
let numCtxs = 0;
|
||||
while (firstCtx !== 0) {
|
||||
numCtxs++;
|
||||
firstCtx = debug.readMemInt16(firstCtx + 3);
|
||||
}
|
||||
return numCtxs;
|
||||
}
|
||||
}
|
||||
|
||||
// Debugger Initialisation
|
||||
|
||||
let ready = setInterval(() => {
|
||||
const debugEnabled = window.location.href.includes("debug=true");
|
||||
if (!debugEnabled) {
|
||||
// Debugging not enabled
|
||||
clearInterval(ready);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Waiting for emulator...", emulator);
|
||||
if (emulator !== null) {
|
||||
debug = new Debug(emulator);
|
||||
clearInterval(ready);
|
||||
|
||||
API.debugger.sendToProjectWindow({
|
||||
action: "initialized",
|
||||
});
|
||||
|
||||
API.events.debugger.data.subscribe((_, packet) => {
|
||||
const { action, data } = packet;
|
||||
|
||||
switch (action) {
|
||||
case "listener-ready":
|
||||
debug.initialize(
|
||||
data.memoryMap,
|
||||
data.globalVariables,
|
||||
data.variableMap,
|
||||
data.pauseOnScriptChanged,
|
||||
data.pauseOnWatchedVariableChanged,
|
||||
data.breakpoints,
|
||||
data.watchedVariables
|
||||
);
|
||||
|
||||
setInterval(() => {
|
||||
if (debug.pausedUI) {
|
||||
debug.pausedUI.style.visibility = debug.isPaused()
|
||||
? "visible"
|
||||
: "hidden";
|
||||
}
|
||||
|
||||
const scriptContexts =
|
||||
debug.getNumScriptCtxs() > 0 ? debug.scriptContexts : [];
|
||||
|
||||
if (scriptContexts.length === 0) {
|
||||
debug.pauseOnVMStep = false;
|
||||
}
|
||||
|
||||
API.debugger.sendToProjectWindow({
|
||||
action: "update-globals",
|
||||
data: debug.getGlobals(),
|
||||
vram: debug.renderVRam(),
|
||||
isPaused: debug.isPaused(),
|
||||
scriptContexts,
|
||||
currentSceneSymbol: debug.getCurrentSceneSymbol(),
|
||||
});
|
||||
}, 100);
|
||||
break;
|
||||
case "set-breakpoints":
|
||||
debug.setBreakPoints(data);
|
||||
break;
|
||||
case "pause":
|
||||
debug.pause();
|
||||
break;
|
||||
case "resume":
|
||||
debug.resume();
|
||||
break;
|
||||
case "step":
|
||||
debug.step();
|
||||
break;
|
||||
case "step-frame":
|
||||
debug.stepFrame();
|
||||
break;
|
||||
case "pause-on-script":
|
||||
debug.pauseOnScriptChanged = data;
|
||||
break;
|
||||
case "pause-on-var":
|
||||
debug.pauseOnWatchedVariableChanged = data;
|
||||
break;
|
||||
case "set-global":
|
||||
debug.setGlobal(data.symbol, data.value);
|
||||
break;
|
||||
case "set-watched":
|
||||
debug.setWatchedVariables(data);
|
||||
break;
|
||||
default:
|
||||
// console.warn(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
1386
gb_studio_project/build/web/js/script.js
Normal file
1386
gb_studio_project/build/web/js/script.js
Normal file
File diff suppressed because it is too large
Load diff
1
gb_studio_project/build/web/rom/README.md
Normal file
1
gb_studio_project/build/web/rom/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add your ROM here named as game.gb
|
||||
BIN
gb_studio_project/build/web/rom/game.gbc
Normal file
BIN
gb_studio_project/build/web/rom/game.gbc
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue