546 lines
14 KiB
C++
546 lines
14 KiB
C++
#include "stdafx.h"
|
|
|
|
#include "ScriptAPI.h"
|
|
#include <Project64/UserInterface/Debugger/DebugMMU.h>
|
|
#include <Project64/UserInterface/Debugger/debugger.h>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
|
|
#pragma warning(disable : 4702) // disable unreachable code warning
|
|
|
|
using namespace ScriptAPI;
|
|
|
|
static size_t MemTypeSize(MemType t);
|
|
static duk_ret_t ThrowMemoryError(duk_context * ctx, uint32_t address);
|
|
|
|
void ScriptAPI::Define_mem(duk_context * ctx)
|
|
{
|
|
#define MEM_PROXY_FUNCS(T) js_mem__get<T>, js_mem__set<T>
|
|
|
|
const DukPropListEntry props[] = {
|
|
{"getblock", DukCFunction(js_mem_getblock)},
|
|
{"setblock", DukCFunction(js_mem_setblock)},
|
|
{"getstring", DukCFunction(js_mem_getstring)},
|
|
{"setstring", DukCFunction(js_mem_setblock)},
|
|
{"bindvar", DukCFunction(js_mem_bindvar)},
|
|
{"bindvars", DukCFunction(js_mem_bindvars)},
|
|
{"bindstruct", DukCFunction(js_mem_bindstruct)},
|
|
{"typedef", DukCFunction(js_mem_typedef)},
|
|
{"ramSize", DukGetter(js_mem__get_ramsize)},
|
|
{"romSize", DukGetter(js_mem__get_romsize)},
|
|
{"ptr", DukGetter(js_mem__get_ptr)},
|
|
{"u32", DukProxy(MEM_PROXY_FUNCS(uint32_t))},
|
|
{"u16", DukProxy(MEM_PROXY_FUNCS(uint16_t))},
|
|
{"u8", DukProxy(MEM_PROXY_FUNCS(uint8_t))},
|
|
{"s32", DukProxy(MEM_PROXY_FUNCS(int32_t))},
|
|
{"s16", DukProxy(MEM_PROXY_FUNCS(int16_t))},
|
|
{"s8", DukProxy(MEM_PROXY_FUNCS(int8_t))},
|
|
{"f64", DukProxy(MEM_PROXY_FUNCS(double))},
|
|
{"f32", DukProxy(MEM_PROXY_FUNCS(float))},
|
|
{nullptr},
|
|
};
|
|
|
|
DefineGlobalInterface(ctx, "mem", props);
|
|
}
|
|
|
|
template <class T>
|
|
duk_ret_t ScriptAPI::js_mem__get(duk_context * ctx)
|
|
{
|
|
CScriptInstance * inst = GetInstance(ctx);
|
|
|
|
uint32_t addr = (uint32_t)duk_to_number(ctx, 1);
|
|
|
|
T value;
|
|
if (inst->Debugger()->DebugLoad_VAddr<T>(addr, value))
|
|
{
|
|
duk_push_number(ctx, value);
|
|
return 1;
|
|
}
|
|
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
|
|
template <class T>
|
|
duk_ret_t ScriptAPI::js_mem__set(duk_context * ctx)
|
|
{
|
|
CScriptInstance * inst = GetInstance(ctx);
|
|
|
|
uint32_t addr = (uint32_t)duk_to_number(ctx, 1);
|
|
T value = (T)duk_to_number(ctx, 2);
|
|
|
|
if (inst->Debugger()->DebugStore_VAddr<T>(addr, value))
|
|
{
|
|
duk_push_true(ctx);
|
|
return 1;
|
|
}
|
|
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_getblock(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Number, Arg_Number});
|
|
CScriptInstance * inst = GetInstance(ctx);
|
|
|
|
duk_uint_t addr = duk_to_uint(ctx, 0);
|
|
duk_uint_t length = duk_to_uint(ctx, 1);
|
|
|
|
uint8_t * data = (uint8_t *)duk_push_fixed_buffer(ctx, length);
|
|
duk_push_buffer_object(ctx, -1, 0, length, DUK_BUFOBJ_NODEJS_BUFFER);
|
|
|
|
uint32_t paddr;
|
|
uint8_t * memsrc = nullptr;
|
|
uint32_t offsetStart = 0;
|
|
|
|
if (addr < 0x80000000 || addr >= 0xC0000000)
|
|
{
|
|
if (!g_MMU || !g_MMU->VAddrToPAddr(addr, paddr))
|
|
{
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
paddr = addr & 0x1FFFFFFF;
|
|
}
|
|
|
|
if (g_MMU && paddr >= 0x00000000 && (paddr + length) <= g_MMU->RdramSize())
|
|
{
|
|
memsrc = g_MMU->Rdram();
|
|
offsetStart = paddr;
|
|
}
|
|
else if (g_Rom && paddr >= 0x10000000 && ((paddr - 0x10000000) + length) <= g_Rom->GetRomSize())
|
|
{
|
|
memsrc = g_Rom->GetRomAddress();
|
|
offsetStart = paddr - 0x10000000;
|
|
}
|
|
|
|
if (memsrc != nullptr)
|
|
{
|
|
uint32_t offsetEnd = offsetStart + length;
|
|
uint32_t alignedOffsetStart = (offsetStart + 15) & ~15;
|
|
uint32_t alignedOffsetEnd = offsetEnd & ~15;
|
|
uint32_t prefixLen = alignedOffsetStart - offsetStart;
|
|
uint32_t middleLen = alignedOffsetEnd - alignedOffsetStart;
|
|
uint32_t suffixLen = offsetEnd - alignedOffsetEnd;
|
|
|
|
uint32_t * middleDst = (uint32_t *)&data[0 + prefixLen];
|
|
uint32_t * middleDstEnd = (uint32_t *)&data[0 + prefixLen + middleLen];
|
|
uint32_t * middleSrc = (uint32_t *)&memsrc[alignedOffsetStart];
|
|
|
|
for (size_t i = 0; i < prefixLen; i++)
|
|
{
|
|
data[i] = memsrc[(offsetStart + i) ^ 3];
|
|
}
|
|
|
|
while (middleDst < middleDstEnd)
|
|
{
|
|
*middleDst++ = _byteswap_ulong(*middleSrc++);
|
|
*middleDst++ = _byteswap_ulong(*middleSrc++);
|
|
*middleDst++ = _byteswap_ulong(*middleSrc++);
|
|
*middleDst++ = _byteswap_ulong(*middleSrc++);
|
|
}
|
|
|
|
for (size_t i = 0; i < suffixLen; i++)
|
|
{
|
|
data[(length - suffixLen) + i] = memsrc[(alignedOffsetEnd + i) ^ 3];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < length; i++)
|
|
{
|
|
uint8_t byte;
|
|
if (inst->Debugger()->DebugLoad_VAddr<uint8_t>((uint32_t)((UINT_PTR)addr + i), byte))
|
|
{
|
|
data[i] = byte;
|
|
}
|
|
else
|
|
{
|
|
return ThrowMemoryError(ctx, (uint32_t)((UINT_PTR)addr + i));
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_getstring(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Number, Arg_OptNumber});
|
|
CScriptInstance * inst = GetInstance(ctx);
|
|
|
|
duk_idx_t nargs = duk_get_top(ctx);
|
|
|
|
duk_uint_t addr = duk_to_uint(ctx, 0);
|
|
duk_uint_t maxLength = nargs > 1 ? duk_to_uint(ctx, 1) : 0xFFFFFFFF;
|
|
size_t length = 0;
|
|
|
|
for (size_t i = 0; i < maxLength; i++)
|
|
{
|
|
char c;
|
|
if (!inst->Debugger()->DebugLoad_VAddr<char>((uint32_t)((UINT_PTR)addr + i), c))
|
|
{
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
|
|
if (c == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
length++;
|
|
}
|
|
|
|
char * str = new char[length + 1];
|
|
str[length] = '\0';
|
|
|
|
for (size_t i = 0; i < length; i++)
|
|
{
|
|
if (!inst->Debugger()->DebugLoad_VAddr<char>((uint32_t)((UINT_PTR)addr + i), str[i]))
|
|
{
|
|
delete[] str;
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
}
|
|
|
|
duk_push_string(ctx, str);
|
|
delete[] str;
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_setblock(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Number, Arg_Any, Arg_OptNumber});
|
|
CScriptInstance * inst = GetInstance(ctx);
|
|
CDebuggerUI * debugger = inst->Debugger();
|
|
|
|
duk_idx_t nargs = duk_get_top(ctx);
|
|
|
|
char * data;
|
|
duk_size_t dataSize, length;
|
|
|
|
uint32_t address = duk_get_uint(ctx, 0);
|
|
|
|
if (duk_is_buffer_data(ctx, 1))
|
|
{
|
|
data = (char *)duk_get_buffer_data(ctx, 1, &dataSize);
|
|
}
|
|
else if (duk_is_string(ctx, 1))
|
|
{
|
|
data = (char *)duk_get_lstring(ctx, 1, &dataSize);
|
|
}
|
|
else
|
|
{
|
|
return ThrowInvalidArgsError(ctx);
|
|
}
|
|
|
|
if (nargs == 3)
|
|
{
|
|
duk_double_t l = duk_get_number(ctx, 2);
|
|
|
|
if (l < 0 || l > dataSize)
|
|
{
|
|
return DUK_RET_RANGE_ERROR;
|
|
}
|
|
|
|
length = (duk_size_t)l;
|
|
}
|
|
else
|
|
{
|
|
length = dataSize;
|
|
}
|
|
|
|
for (size_t i = 0; i < length; i++)
|
|
{
|
|
if (!debugger->DebugStore_VAddr((uint32_t)((UINT_PTR)address + i), data[i]))
|
|
{
|
|
return ThrowMemoryError(ctx, (uint32_t)((UINT_PTR)address + i));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem__boundget(duk_context * ctx)
|
|
{
|
|
CDebuggerUI * debugger = GetInstance(ctx)->Debugger();
|
|
|
|
uint32_t addr = duk_get_uint(ctx, 0);
|
|
duk_int_t type = duk_get_int(ctx, 1);
|
|
|
|
union
|
|
{
|
|
uint8_t u8;
|
|
uint16_t u16;
|
|
uint32_t u32;
|
|
int8_t s8;
|
|
int16_t s16;
|
|
int32_t s32;
|
|
float f32;
|
|
double f64;
|
|
} retval;
|
|
|
|
#define MEM_BOUNDGET_TRY(addr, T, result, dukpush) \
|
|
if (debugger->DebugLoad_VAddr<T>(addr, result)) \
|
|
{ \
|
|
dukpush(ctx, result); \
|
|
} \
|
|
else \
|
|
{ \
|
|
goto memory_error; \
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case U8:
|
|
MEM_BOUNDGET_TRY(addr, uint8_t, retval.u8, duk_push_uint);
|
|
return 1;
|
|
case U16:
|
|
MEM_BOUNDGET_TRY(addr, uint16_t, retval.u16, duk_push_uint);
|
|
return 1;
|
|
case U32:
|
|
MEM_BOUNDGET_TRY(addr, uint32_t, retval.u32, duk_push_uint);
|
|
return 1;
|
|
case S8:
|
|
MEM_BOUNDGET_TRY(addr, int8_t, retval.s8, duk_push_int);
|
|
return 1;
|
|
case S16:
|
|
MEM_BOUNDGET_TRY(addr, int16_t, retval.s16, duk_push_int);
|
|
return 1;
|
|
case S32:
|
|
MEM_BOUNDGET_TRY(addr, int32_t, retval.s32, duk_push_int);
|
|
return 1;
|
|
case F32:
|
|
MEM_BOUNDGET_TRY(addr, float, retval.f32, duk_push_number);
|
|
return 1;
|
|
case F64:
|
|
MEM_BOUNDGET_TRY(addr, double, retval.f64, duk_push_number);
|
|
return 1;
|
|
}
|
|
|
|
memory_error:
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem__boundset(duk_context * ctx)
|
|
{
|
|
CDebuggerUI * debugger = GetInstance(ctx)->Debugger();
|
|
|
|
uint32_t addr = duk_get_uint(ctx, 0);
|
|
duk_int_t type = duk_get_int(ctx, 1);
|
|
|
|
#define MEM_BOUNDSET_TRY(addr, T, value) \
|
|
if (debugger->DebugStore_VAddr<T>(addr, value)) \
|
|
{ \
|
|
return 1; \
|
|
} \
|
|
else \
|
|
{ \
|
|
goto memory_error; \
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case U8:
|
|
MEM_BOUNDSET_TRY(addr, uint8_t, duk_get_uint(ctx, 2) & 0xFF);
|
|
break;
|
|
case U16:
|
|
MEM_BOUNDSET_TRY(addr, uint16_t, duk_get_uint(ctx, 2) & 0xFFFF);
|
|
break;
|
|
case U32:
|
|
MEM_BOUNDSET_TRY(addr, uint32_t, duk_get_uint(ctx, 2));
|
|
break;
|
|
case S8:
|
|
MEM_BOUNDSET_TRY(addr, int8_t, duk_get_int(ctx, 2) & 0xFF);
|
|
break;
|
|
case S16:
|
|
MEM_BOUNDSET_TRY(addr, int16_t, duk_get_int(ctx, 2) & 0xFFFF);
|
|
break;
|
|
case S32:
|
|
MEM_BOUNDSET_TRY(addr, int32_t, duk_get_int(ctx, 2));
|
|
break;
|
|
case F32:
|
|
MEM_BOUNDSET_TRY(addr, float, (float)duk_get_number(ctx, 2));
|
|
break;
|
|
case F64:
|
|
MEM_BOUNDSET_TRY(addr, double, duk_get_number(ctx, 2));
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
memory_error:
|
|
return ThrowMemoryError(ctx, addr);
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_bindvar(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Object, Arg_Number, Arg_String, Arg_Number});
|
|
|
|
duk_uint_t addr = duk_get_uint(ctx, 1);
|
|
const char * key = duk_get_string(ctx, 2);
|
|
duk_int_t type = duk_get_int(ctx, 3);
|
|
|
|
duk_push_string(ctx, key);
|
|
|
|
// [ ... js_mem__boundget ] -> [ ... js_mem__boundget.bind(obj, addr, type) ]
|
|
duk_push_c_function(ctx, js_mem__boundget, DUK_VARARGS);
|
|
duk_push_string(ctx, "bind");
|
|
duk_dup(ctx, 0);
|
|
duk_push_uint(ctx, addr);
|
|
duk_push_int(ctx, type);
|
|
duk_call_prop(ctx, -5, 3);
|
|
duk_remove(ctx, -2);
|
|
|
|
duk_push_c_function(ctx, js_mem__boundset, DUK_VARARGS);
|
|
duk_push_string(ctx, "bind");
|
|
duk_dup(ctx, 0);
|
|
duk_push_uint(ctx, addr);
|
|
duk_push_int(ctx, type);
|
|
duk_call_prop(ctx, -5, 3);
|
|
duk_remove(ctx, -2);
|
|
|
|
duk_def_prop(ctx, 0, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
|
|
|
|
return 0;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_bindvars(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Object, Arg_Array});
|
|
|
|
duk_size_t length = duk_get_length(ctx, 1);
|
|
|
|
for (duk_uarridx_t i = 0; i < length; i++)
|
|
{
|
|
duk_get_prop_index(ctx, 1, i);
|
|
if (!duk_is_array(ctx, -1) || duk_get_length(ctx, -1) != 3)
|
|
{
|
|
return DUK_RET_TYPE_ERROR;
|
|
}
|
|
|
|
duk_push_c_function(ctx, js_mem_bindvar, 4);
|
|
duk_dup(ctx, 0);
|
|
duk_get_prop_index(ctx, -3, 0);
|
|
duk_get_prop_index(ctx, -4, 1);
|
|
duk_get_prop_index(ctx, -5, 2);
|
|
|
|
if (duk_pcall(ctx, 4) != DUK_EXEC_SUCCESS)
|
|
{
|
|
return duk_throw(ctx);
|
|
}
|
|
|
|
duk_pop_n(ctx, 1);
|
|
}
|
|
|
|
duk_dup(ctx, 0);
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_bindstruct(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Object, Arg_Number, Arg_Object});
|
|
|
|
uint32_t curAddr = duk_get_uint(ctx, 1);
|
|
|
|
duk_enum(ctx, 2, DUK_ENUM_OWN_PROPERTIES_ONLY);
|
|
|
|
while (duk_next(ctx, -1, 1))
|
|
{
|
|
MemType type = (MemType)duk_get_int(ctx, -1);
|
|
|
|
duk_push_c_function(ctx, js_mem_bindvar, 4);
|
|
duk_dup(ctx, 0);
|
|
duk_push_uint(ctx, curAddr);
|
|
duk_pull(ctx, -5);
|
|
duk_pull(ctx, -5);
|
|
|
|
if (duk_pcall(ctx, 4) != DUK_EXEC_SUCCESS)
|
|
{
|
|
return duk_throw(ctx);
|
|
}
|
|
|
|
duk_pop(ctx);
|
|
|
|
curAddr += (uint32_t)((UINT_PTR)MemTypeSize(type));
|
|
}
|
|
|
|
duk_dup(ctx, 0);
|
|
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem__type_constructor(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Object, Arg_Number});
|
|
|
|
duk_push_c_function(ctx, js_mem_bindstruct, 3);
|
|
duk_push_this(ctx);
|
|
duk_pull(ctx, 1);
|
|
duk_pull(ctx, 0);
|
|
|
|
if (duk_pcall(ctx, 3) != DUK_EXEC_SUCCESS)
|
|
{
|
|
return duk_throw(ctx);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem_typedef(duk_context * ctx)
|
|
{
|
|
CheckArgs(ctx, {Arg_Object});
|
|
|
|
duk_push_c_function(ctx, js_mem__type_constructor, DUK_VARARGS);
|
|
duk_push_string(ctx, "bind");
|
|
duk_push_null(ctx);
|
|
duk_dup(ctx, 0);
|
|
duk_call_prop(ctx, -4, 2);
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem__get_ramsize(duk_context * ctx)
|
|
{
|
|
duk_push_number(ctx, g_MMU ? g_MMU->RdramSize() : 0);
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem__get_romsize(duk_context * ctx)
|
|
{
|
|
duk_push_number(ctx, g_Rom ? g_Rom->GetRomSize() : 0);
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t ScriptAPI::js_mem__get_ptr(duk_context * ctx)
|
|
{
|
|
duk_push_pointer(ctx, g_MMU ? g_MMU->Rdram() : nullptr);
|
|
return 1;
|
|
}
|
|
|
|
size_t MemTypeSize(MemType t)
|
|
{
|
|
switch (t)
|
|
{
|
|
case U8:
|
|
case S8:
|
|
return 1;
|
|
case U16:
|
|
case S16:
|
|
return 2;
|
|
case U32:
|
|
case S32:
|
|
case F32:
|
|
return 4;
|
|
case F64:
|
|
return 8;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
duk_ret_t ThrowMemoryError(duk_context * ctx, uint32_t address)
|
|
{
|
|
duk_push_error_object(ctx, DUK_ERR_ERROR, "memory error (can't access 0x%08X)", address);
|
|
return duk_throw(ctx);
|
|
}
|