Initial commit. Mephisto lives.

This commit is contained in:
Cody Brocious 2017-09-21 20:38:04 -06:00
commit 42296ddcbd
54 changed files with 10696 additions and 0 deletions

333
Cpu.cpp Normal file
View file

@ -0,0 +1,333 @@
#include "Ctu.h"
void intrHook(uc_engine *uc, uint32_t intNo, void *user_data) {
((Cpu *) user_data)->interruptHook(intNo);
}
bool unmpdHook(uc_engine *uc, uc_mem_type type, gptr addr, int size, guint value, void *user_data) {
return ((Cpu *) user_data)->unmappedHook(type, addr, size, value);
}
void mmioHook(uc_engine *uc, uc_mem_type type, gptr address, int size, gptr value, void *user_data) {
gptr physicalAddress = ((Cpu *) user_data)->mmioHandler->getPhysicalAddressFromVirtual(address);
MmioBase *mmio = ((Cpu *) user_data)->mmioHandler->getMMIOFromPhysicalAddress(address);
assert(mmio != nullptr);
switch(type) {
case UC_MEM_READ:
LOG_DEBUG(Cpu, "MMIO Read at " ADDRFMT " size %x", physicalAddress, size);
((Cpu *) user_data)->readmem(address, &value, size);
LOG_DEBUG(Cpu, "Stored value %x", (int) ((Cpu *) user_data)->read8(address));
break;
case UC_MEM_WRITE:
LOG_DEBUG(Cpu, "MMIO Write at " ADDRFMT " size %x data %lx", physicalAddress, size, value);
/*if() {
((Cpu *) user_data)->writemem(address, &value, size);
}*/
//mmio->swrite(physicalAddress, size, &value);
((Cpu *) user_data)->writemem(address, &value, size);
break;
}
}
void codeBpHook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
auto ctu = (Ctu *) user_data;
cout << "Hit breakpoint at ... " << hex << address << endl;
auto thread = ctu->tm.current();
assert(thread != nullptr);
ctu->tm.requeue();
thread->regs.PC = address;
ctu->cpu.stop();
ctu->gdbStub._break();
}
Cpu::Cpu(Ctu *_ctu) : ctu(_ctu) {
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
CHECKED(uc_mem_map(uc, TERMADDR, 0x1000, UC_PROT_ALL));
guestptr<uint32_t>(TERMADDR) = 0xd503201f; // nop
auto fpv = 3 << 20;
CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
uc_hook hookHandle;
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_INTR, (void *) intrHook, this, 0, -1));
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_MEM_INVALID, (void *) unmpdHook, this, 0, -1));
for(auto i = 0; i < 0x80; ++i)
svcHandlers[i] = nullptr;
}
Cpu::~Cpu() {
CHECKED(uc_close(uc));
}
guint Cpu::call(gptr _pc, guint x0, guint x1, guint x2, guint x3) {
reg(0, x0);
reg(1, x1);
reg(2, x2);
reg(3, x3);
reg(30, TERMADDR);
CHECKED(uc_emu_start(uc, _pc, TERMADDR + 4, 0, 0));
return reg(0);
}
void Cpu::setMmio(Mmio *_mmioHandler) {
mmioHandler = _mmioHandler;
uc_hook hookHandle;
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_MEM_READ, (void *)mmioHook, this, mmioHandler->GetBase(), mmioHandler->GetBase()+mmioHandler->GetSize() ));
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_MEM_WRITE, (void *)mmioHook, this, mmioHandler->GetBase(), mmioHandler->GetBase()+mmioHandler->GetSize() ));
}
void Cpu::exec(size_t insnCount) {
CHECKED(uc_emu_start(uc, pc(), TERMADDR + 4, 0, insnCount));
}
void Cpu::stop() {
CHECKED(uc_emu_stop(uc));
}
bool Cpu::map(gptr addr, guint size) {
CHECKED(uc_mem_map(uc, addr, size, UC_PROT_ALL));
auto temp = new uint8_t[size];
memset(temp, 0, size);
writemem(addr, temp, size);
delete[] temp;
return true;
}
bool Cpu::unmap(gptr addr, guint size) {
CHECKED(uc_mem_unmap(uc, addr, size));
return true;
}
list<tuple<gptr, gptr, int>> Cpu::regions() {
list<tuple<gptr, gptr, int>> ret;
uc_mem_region *regions;
uint32_t count;
CHECKED(uc_mem_regions(uc, &regions, &count));
list<tuple<gptr, gptr>> temp;
for(auto i = 0; i < count; ++i) {
auto region = regions[i];
temp.push_back(make_tuple(region.begin, region.end));
}
uc_free(regions);
temp.sort([](auto a, auto b) { auto [ab, _] = a; auto [bb, __] = b; return ab < bb; });
gptr last = 0;
for(auto [begin, end] : temp) {
if(last != begin)
ret.push_back(make_tuple(last, begin - 1, -1));
ret.push_back(make_tuple(begin, end, 0));
last = end + 1;
}
if(last != 0xFFFFFFFFFFFFFFFF)
ret.push_back(make_tuple(last, 0xFFFFFFFFFFFFFFFF, -1));
return ret;
}
bool Cpu::readmem(gptr addr, void *dest, guint size) {
return uc_mem_read(uc, addr, dest, size) == UC_ERR_OK;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
void Cpu::dumpmem(gptr addr, guint size) {
char *data = (char*)malloc(size);
memset(data, 0, size);
readmem(addr, data, size);
auto hfmt = "%08lx | ";
if((addr + size) & 0xFFFF000000000000)
hfmt = "%016lx | ";
else if((addr + size) & 0xFFFFFFFF00000000)
hfmt = "%012lx | ";
for(uint32_t i = 0; i < size; i += 16) {
printf(hfmt, addr+i);
string ascii = "";
for(uint8_t j = 0; j < 16; j++) {
if((i+j) < size) {
printf("%02x ", (uint8_t)data[i+j]);
if(isprint(data[i+j]))
ascii += data[i+j];
else
ascii += ".";
} else {
printf(" ");
ascii += " ";
}
if(j==7) {
printf(" ");
ascii += " ";
}
}
printf("| %s\n", ascii.c_str());
}
free(data);
}
#pragma clang diagnostic pop
guchar Cpu::read8(gptr addr) {
return *guestptr<guchar>(addr);
}
std::string Cpu::readstring(gptr addr) {
std::string out;
uint32_t offset = 0;
while(guchar c = read8(addr+offset)) {
if(c == 0)
break;
out += c;
offset++;
}
return out;
}
bool Cpu::writemem(gptr addr, void *src, guint size) {
return uc_mem_write(uc, addr, src, size) == UC_ERR_OK;
}
gptr Cpu::pc() {
gptr val;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &val));
return val;
}
void Cpu::pc(gptr val) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &val));
}
guint Cpu::reg(uint regn) {
guint val;
auto treg = UC_ARM64_REG_SP;
if(regn <= 28)
treg = (uc_arm64_reg) (UC_ARM64_REG_X0 + regn);
else if(regn < 31)
treg = (uc_arm64_reg) (UC_ARM64_REG_X29 + regn - 29);
CHECKED(uc_reg_read(uc, treg, &val));
return val;
}
void Cpu::reg(uint regn, guint val) {
auto treg = UC_ARM64_REG_SP;
if(regn <= 28)
treg = (uc_arm64_reg) (UC_ARM64_REG_X0 + regn);
else if(regn < 31)
treg = (uc_arm64_reg) (UC_ARM64_REG_X29 + regn - 29);
CHECKED(uc_reg_write(uc, treg, &val));
}
void Cpu::loadRegs(ThreadRegisters &regs) {
int uregs[32];
void *tregs[32];
CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &regs.SP));
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &regs.PC));
CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &regs.NZCV));
for(auto i = 0; i < 29; ++i) {
uregs[i] = UC_ARM64_REG_X0 + i;
tregs[i] = &regs.gprs[i];
}
CHECKED(uc_reg_write_batch(uc, uregs, tregs, 29));
CHECKED(uc_reg_write(uc, UC_ARM64_REG_X29, &regs.X29));
CHECKED(uc_reg_write(uc, UC_ARM64_REG_X30, &regs.X30));
for(auto i = 0; i < 32; ++i) {
uregs[i] = UC_ARM64_REG_Q0 + i;
tregs[i] = &regs.fprs[i];
}
CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32));
}
void Cpu::storeRegs(ThreadRegisters &regs) {
int uregs[32];
void *tregs[32];
CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &regs.SP));
CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &regs.PC));
CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &regs.NZCV));
for(auto i = 0; i < 29; ++i) {
uregs[i] = UC_ARM64_REG_X0 + i;
tregs[i] = &regs.gprs[i];
}
CHECKED(uc_reg_read_batch(uc, uregs, tregs, 29));
CHECKED(uc_reg_read(uc, UC_ARM64_REG_X29, &regs.X29));
CHECKED(uc_reg_read(uc, UC_ARM64_REG_X30, &regs.X30));
for(auto i = 0; i < 32; ++i) {
uregs[i] = UC_ARM64_REG_Q0 + i;
tregs[i] = &regs.fprs[i];
}
CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32));
}
gptr Cpu::tlsBase() {
gptr base;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
return base;
}
void Cpu::tlsBase(gptr base) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
}
void Cpu::interruptHook(uint32_t intNo) {
uint32_t esr;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
auto ec = esr >> 26;
auto iss = esr & 0xFFFFFF;
switch(ec) {
case 0x15: // SVC
if(iss >= 0x80 || svcHandlers[iss] == nullptr)
LOG_ERROR(Cpu, "Unhandled SVC 0x%02x", iss);
svcHandlers[iss](this);
break;
}
}
bool Cpu::unmappedHook(uc_mem_type type, gptr addr, int size, guint value) {
cout << "!!!!!!!!!!!!!!!!!!!!!!!! Unmapped !!!!!!!!!!!!!!!!!!!!!!!" << endl;
switch(type) {
case UC_MEM_READ_UNMAPPED:
case UC_MEM_READ_PROT:
LOG_INFO(Cpu, "Attempted to read from %s memory at " ADDRFMT " from " ADDRFMT, (type == UC_MEM_READ_UNMAPPED ? "unmapped" : "protected"), addr, pc());
break;
case UC_MEM_FETCH_UNMAPPED:
case UC_MEM_FETCH_PROT:
LOG_INFO(Cpu, "Attempted to fetch from %s memory at " ADDRFMT " from " ADDRFMT, (type == UC_MEM_READ_UNMAPPED ? "unmapped" : "protected"), addr, pc());
break;
case UC_MEM_WRITE_UNMAPPED:
case UC_MEM_WRITE_PROT:
LOG_INFO(Cpu, "Attempted to write to %s memory at " ADDRFMT " from " ADDRFMT, (type == UC_MEM_READ_UNMAPPED ? "unmapped" : "protected"), addr, pc());
break;
}
return false;
}
void Cpu::registerSvcHandler(int num, std::function<void()> handler) {
registerSvcHandler(num, [=](auto _) { handler(); });
}
void Cpu::registerSvcHandler(int num, std::function<void(Cpu *)> handler) {
svcHandlers[num] = handler;
}
hook_t Cpu::addCodeBreakpoint(gptr addr) {
assert(ctu->gdbStub.enabled);
hook_t hookHandle;
CHECKED(uc_hook_add(uc, &hookHandle, UC_HOOK_CODE, (void *)codeBpHook, ctu, addr, addr + 2));
return hookHandle;
}
void Cpu::removeCodeBreakpoint(hook_t hook) {
CHECKED(uc_hook_del(uc, hook));
}

100
Cpu.h Normal file
View file

@ -0,0 +1,100 @@
#pragma once
#include "Ctu.h"
#include <unicorn/unicorn.h>
typedef uc_hook hook_t;
#define CHECKED(expr) do { if(auto _cerr = (expr)) { printf("Call " #expr " failed with error: %u (%s)\n", _cerr, uc_strerror(_cerr)); exit(1); } } while(0)
template<typename T> class Guest;
class Cpu {
public:
Cpu(Ctu *_ctu);
~Cpu();
uint64_t call(gptr addr, uint64_t x0=0, uint64_t x1=0, uint64_t x2=0, uint64_t x3=0);
void exec(size_t insnCount=0);
void stop();
bool map(gptr addr, guint size);
bool unmap(gptr addr, guint size);
list<tuple<gptr, guint, int>> regions();
bool readmem(gptr addr, void *dest, guint size);
guchar read8(gptr addr);
void dumpmem(gptr addr, guint size);
std::string readstring(gptr addr);
bool writemem(gptr addr, void *src, guint size);
gptr pc();
void pc(gptr val);
guint reg(uint reg);
void reg(uint reg, guint val);
void loadRegs(ThreadRegisters &regs);
void storeRegs(ThreadRegisters &regs);
gptr tlsBase();
void tlsBase(gptr base);
template<typename T> Guest<T> guestptr(gptr addr) {
Guest<T> ret(this, addr);
return ret;
}
void registerSvcHandler(int num, std::function<void()> handler);
void registerSvcHandler(int num, std::function<void(Cpu *)> handler);
hook_t addCodeBreakpoint(gptr addr);
void removeCodeBreakpoint(hook_t hook);
void interruptHook(uint32_t intNo);
bool unmappedHook(uc_mem_type type, gptr addr, int size, guint value);
void setMmio(Mmio *_mmioHandler);// { mmioHandler = _mmioHandler; }
Mmio *mmioHandler;
private:
Ctu *ctu;
uc_engine *uc;
std::function<void(Cpu *)> svcHandlers[0x80];
};
template<typename T>
class Guest {
public:
Guest(Cpu *cpu, gptr addr) : cpu(cpu), addr(addr) {
}
const T& operator*() {
cpu->readmem(addr, &store, sizeof(T));
return store;
}
T* operator->() {
cpu->readmem(addr, &store, sizeof(T));
return &store;
}
Guest<T> &operator=(const T &v) {
cpu->writemem(addr, (void *) &v, sizeof(T));
return *this;
}
const T &operator[](int i) {
return *Guest<T>(cpu, addr + i * sizeof(T));
}
Guest<T> operator+(const int &i) {
return Guest<T>(cpu, addr + i * sizeof(T));
}
void writeback() {
cpu->writemem(addr, &store, sizeof(T));
}
private:
Cpu *cpu;
gptr addr;
T store;
};

37
Ctu.cpp Normal file
View file

@ -0,0 +1,37 @@
#include "Ctu.h"
LogLevel g_LogLevel = Info;
Ctu::Ctu() : cpu(this), svc(this), ipc(this), tm(this), mmiohandler(this), bridge(this), gdbStub(this), handleId(0xde00), heapsize(0x0) {
handles[0xffff8001] = make_shared<Process>(this);
}
void Ctu::execProgram(gptr ep) {
auto sp = 7 << 24;
auto ss = 8 * 1024 * 1024;
cpu.map(sp - ss, ss);
cpu.setMmio(&mmiohandler);
mmiohandler.MMIOInitialize();
auto mainThread = tm.create(ep, sp);
mainThread->regs.X1 = mainThread->handle;
mainThread->resume();
tm.start();
}
ghandle Ctu::duplicateHandle(KObject *ptr) {
for(auto elem : handles)
if(elem.second.get() == ptr)
return newHandle(elem.second);
return 0;
}
void Ctu::deleteHandle(ghandle handle) {
if(handles.find(handle) != handles.end()) {
auto hnd = getHandle<KObject>(handle);
handles.erase(handle);
hnd->close();
}
}

171
Ctu.h Normal file
View file

@ -0,0 +1,171 @@
#pragma once
#include <byteswap.h>
#include <netinet/in.h>
#include <stdint.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <algorithm>
#include <array>
#include <cassert>
#include <climits>
#include <forward_list>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <unordered_map>
#include <queue>
#include <string>
#include <thread>
#include <tuple>
#include <type_traits>
#include <vector>
#include <sstream>
using namespace std;
typedef __int128_t int128_t;
typedef __uint128_t uint128_t;
typedef float float32_t;
typedef double float64_t;
typedef uint64_t gptr;
typedef uint64_t guint;
typedef uint8_t guchar;
typedef uint16_t gushort;
typedef uint32_t ghandle;
typedef uint64_t gpid;
const gptr TERMADDR = 1ULL << 61;
#define FOURCC(a, b, c, d) (((d) << 24) | ((c) << 16) | ((b) << 8) | (a))
#define ADDRFMT "%016lx"
#define LONGFMT "%lx"
enum LogLevel {
None = 0,
Error = 1,
Warn = 2,
Debug = 3,
Info = 4
};
extern LogLevel g_LogLevel;
#define LOG_ERROR(module, msg, ...) do { \
if(g_LogLevel >= Error) { \
fprintf(stderr, "[" #module "] ERROR: " msg "\n", ##__VA_ARGS__); \
cerr << endl; \
} \
exit(1); \
} while(0)
#define LOG_INFO(module, msg, ...) do { \
if(g_LogLevel >= Info) { \
printf("[" #module "] INFO: " msg, ##__VA_ARGS__); \
cout << endl; \
} \
} while(0)
#define LOG_DEBUG(module, msg, ...) do { \
if(g_LogLevel >= Debug) { \
printf("[" #module "] DEBUG: " msg, ##__VA_ARGS__); \
cout << endl; \
} \
} while(0)
class Ctu;
#include "optionparser.h"
#include "Lisparser.h"
#include "KObject.h"
#include "ThreadManager.h"
#include "Mmio.h"
#include "Cpu.h"
#include "Sync.h"
#include "Svc.h"
#include "Ipc.h"
#include "Nxo.h"
#include "IpcBridge.h"
#include "GdbStub.h"
template<unsigned long N>
void hexdump(shared_ptr<array<uint8_t, N>> buf, unsigned long count=N) {
if(g_LogLevel < Debug)
return;
for(auto i = 0; i < count; i += 16) {
printf("%04x | ", i);
for(auto j = 0; j < 16; ++j) {
printf("%02x ", buf->data()[i + j]);
if(j == 7)
printf(" ");
}
printf("| ");
for(auto j = 0; j < 16; ++j) {
auto val = buf->data()[i + j];
if(isprint(val))
printf("%c", val);
else
printf(".");
if(j == 7)
printf(" ");
}
printf("\n");
}
printf("%04x\n", (int) count);
}
class Ctu {
public:
Ctu();
void execProgram(gptr ep);
template<typename T>
ghandle newHandle(shared_ptr<T> obj) {
static_assert(std::is_base_of<KObject, T>::value, "T must derive from KObject");
auto hnd = handleId++;
handles[hnd] = dynamic_pointer_cast<KObject>(obj);
return hnd;
}
template<typename T>
shared_ptr<T> getHandle(ghandle handle) {
static_assert(std::is_base_of<KObject, T>::value, "T must derive from KObject");
if(handles.find(handle) == handles.end())
LOG_ERROR(Ctu, "Could not find handle with ID 0x%x !", handle);
auto obj = handles[handle];
auto faux = dynamic_pointer_cast<FauxHandle>(obj);
if(faux != nullptr)
LOG_ERROR(Ctu, "Accessing faux handle! 0x%x", faux->val);
auto temp = dynamic_pointer_cast<T>(obj);
if(temp == nullptr)
LOG_ERROR(Ctu, "Got null pointer after cast. Before: 0x%p", (void *) obj.get());
return temp;
}
ghandle duplicateHandle(KObject *ptr);
void deleteHandle(ghandle handle);
Mmio mmiohandler;
Cpu cpu;
Svc svc;
Ipc ipc;
ThreadManager tm;
IpcBridge bridge;
GdbStub gdbStub;
guint heapsize;
gptr loadbase, loadsize;
private:
ghandle handleId;
unordered_map<ghandle, shared_ptr<KObject>> handles;
};
#include "IpcStubs.h"

720
GdbStub.cpp Normal file
View file

@ -0,0 +1,720 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
// Integrated into Mephisto/CTUv2 by Cody Brocious
#include "Ctu.h"
const char GDB_STUB_START = '$';
const char GDB_STUB_END = '#';
const char GDB_STUB_ACK = '+';
const char GDB_STUB_NACK = '-';
#ifndef SIGTRAP
const uint32_t SIGTRAP = 5;
#endif
#ifndef SIGTERM
const uint32_t SIGTERM = 15;
#endif
#ifndef MSG_WAITALL
const uint32_t MSG_WAITALL = 8;
#endif
// For sample XML files see the GDB source /gdb/features
// GDB also wants the l character at the start
// This XML defines what the registers are for this specific ARM device
static const char* target_xml =
R"(<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
<feature name="org.gnu.gdb.aarch64.core">
<reg name="x0" bitsize="64"/>
<reg name="x1" bitsize="64"/>
<reg name="x2" bitsize="64"/>
<reg name="x3" bitsize="64"/>
<reg name="x4" bitsize="64"/>
<reg name="x5" bitsize="64"/>
<reg name="x6" bitsize="64"/>
<reg name="x7" bitsize="64"/>
<reg name="x8" bitsize="64"/>
<reg name="x9" bitsize="64"/>
<reg name="x10" bitsize="64"/>
<reg name="x11" bitsize="64"/>
<reg name="x12" bitsize="64"/>
<reg name="x13" bitsize="64"/>
<reg name="x14" bitsize="64"/>
<reg name="x15" bitsize="64"/>
<reg name="x16" bitsize="64"/>
<reg name="x17" bitsize="64"/>
<reg name="x18" bitsize="64"/>
<reg name="x19" bitsize="64"/>
<reg name="x20" bitsize="64"/>
<reg name="x21" bitsize="64"/>
<reg name="x22" bitsize="64"/>
<reg name="x23" bitsize="64"/>
<reg name="x24" bitsize="64"/>
<reg name="x25" bitsize="64"/>
<reg name="x26" bitsize="64"/>
<reg name="x27" bitsize="64"/>
<reg name="x28" bitsize="64"/>
<reg name="x29" bitsize="64"/>
<reg name="x30" bitsize="64"/>
<reg name="sp" bitsize="64" type="data_ptr"/>
<reg name="pc" bitsize="64" type="code_ptr"/>
<reg name="cpsr" bitsize="32"/>
</feature>
</target>)";
uint8_t hexCharToValue(uint8_t hex) {
if(hex >= '0' && hex <= '9')
return hex - '0';
else if(hex >= 'a' && hex <= 'f')
return hex - 'a' + 0xA;
else if(hex >= 'A' && hex <= 'F')
return hex - 'A' + 0xA;
LOG_ERROR(GdbStub, "Invalid nibble: %c (%02x)", hex, hex);
}
uint8_t nibbleToHex(uint8_t n) {
n &= 0xF;
if(n < 0xA)
return '0' + n;
else
return 'A' + n - 0xA;
}
uint64_t hexToInt(const uint8_t* src, size_t len) {
uint64_t output = 0;
while(len-- > 0) {
output = (output << 4) | hexCharToValue(src[0]);
src++;
}
return output;
}
void memToGdbHex(uint8_t* dest, const uint8_t* src, size_t len) {
while(len-- > 0) {
auto tmp = *src++;
*dest++ = nibbleToHex(tmp >> 4);
*dest++ = nibbleToHex(tmp);
}
}
void gdbHexToMem(uint8_t* dest, const uint8_t* src, size_t len) {
while(len-- > 0) {
*dest++ = (uint8_t) ((hexCharToValue(src[0]) << 4) | hexCharToValue(src[1]));
src += 2;
}
}
void intToGdbHex(uint8_t* dest, uint64_t v) {
for(auto i = 0; i < 16; i += 2) {
dest[i + 1] = nibbleToHex((uint8_t) (v >> (4 * i)));
dest[i] = nibbleToHex((uint8_t) (v >> (4 * (i + 1))));
}
}
uint64_t gdbHexToInt(const uint8_t* src) {
uint64_t output = 0;
for(int i = 0; i < 16; i += 2) {
output = (output << 4) | hexCharToValue(src[15 - i - 1]);
output = (output << 4) | hexCharToValue(src[15 - i]);
}
return output;
}
uint8_t calculateChecksum(const uint8_t* buffer, size_t length) {
return static_cast<uint8_t>(accumulate(buffer, buffer + length, 0, plus<uint8_t>()));
}
GdbStub::GdbStub(Ctu *_ctu) : ctu(_ctu) {
memoryBreak = false;
haltLoop = stepLoop = false;
enabled = false;
latestSignal = 0;
}
void GdbStub::enable(uint16_t port) {
LOG_INFO(GdbStub, "Starting GDB server on port %d...", port);
sockaddr_in saddr_server = {};
saddr_server.sin_family = AF_INET;
saddr_server.sin_port = htons(port);
saddr_server.sin_addr.s_addr = INADDR_ANY;
auto tmpsock = socket(PF_INET, SOCK_STREAM, 0);
if(tmpsock == -1)
LOG_ERROR(GdbStub, "Failed to create gdb socket");
auto reuse_enabled = 1;
if(setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled, sizeof(reuse_enabled)) < 0)
LOG_ERROR(GdbStub, "Failed to set gdb socket option");
auto server_addr = reinterpret_cast<const sockaddr*>(&saddr_server);
socklen_t server_addrlen = sizeof(saddr_server);
if(bind(tmpsock, server_addr, server_addrlen) < 0)
LOG_ERROR(GdbStub, "Failed to bind gdb socket");
if(listen(tmpsock, 1) < 0)
LOG_ERROR(GdbStub, "Failed to listen to gdb socket");
LOG_INFO(GdbStub, "Waiting for gdb to connect...");
sockaddr_in saddr_client;
sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
socklen_t client_addrlen = sizeof(saddr_client);
client = accept(tmpsock, client_addr, &client_addrlen);
if(client < 0)
LOG_ERROR(GdbStub, "Failed to accept gdb client");
else
LOG_INFO(GdbStub, "Client connected.");
enabled = true;
haltLoop = true;
}
uint8_t GdbStub::readByte() {
uint8_t c;
auto size = recv(client, reinterpret_cast<char*>(&c), 1, MSG_WAITALL);
if(size != 1)
LOG_ERROR(GdbStub, "recv failed : %ld", size);
return c;
}
guint GdbStub::reg(int x) {
auto thread = ctu->tm.current();
if(thread == nullptr)
thread = ctu->tm.last();
if(thread == nullptr)
return 0;
switch(x) {
case 31:
return thread->regs.SP;
case 32:
return thread->regs.PC;
default:
assert(x < 31);
return thread->regs.gprs[x];
}
}
void GdbStub::reg(int x, guint v) {
auto thread = ctu->tm.current();
if(thread == nullptr)
thread = ctu->tm.last();
if(thread == nullptr)
return;
switch(x) {
case 31:
thread->regs.SP = v;
break;
case 32:
thread->regs.PC = v;
break;
default:
assert(x < 31);
thread->regs.gprs[x] = v;
}
}
auto& GdbStub::getBreakpointList(BreakpointType type) {
switch(type) {
case BreakpointType::Execute:
return breakpointsExecute;
case BreakpointType::Write:
return breakpointsWrite;
case BreakpointType::Read:
case BreakpointType::Access:
case BreakpointType::None: // Should never happen
return breakpointsRead;
}
}
void GdbStub::removeBreakpoint(BreakpointType type, gptr addr) {
auto& p = getBreakpointList(type);
auto bp = p.find(addr);
if(bp != p.end()) {
LOG_DEBUG(GdbStub, "gdb: removed a breakpoint: %016lx bytes at %016lx of type %d",
bp->second.len, bp->second.addr, type);
ctu->cpu.removeCodeBreakpoint(bp->second.hook);
p.erase(addr);
}
}
auto GdbStub::getNextBreakpointFromAddress(gptr addr, BreakpointType type) {
auto& p = getBreakpointList(type);
auto next_breakpoint = p.lower_bound(addr);
BreakpointAddress breakpoint;
if(next_breakpoint != p.end()) {
breakpoint.address = next_breakpoint->first;
breakpoint.type = type;
} else {
breakpoint.address = 0;
breakpoint.type = BreakpointType::None;
}
return breakpoint;
}
bool GdbStub::checkBreakpoint(gptr addr, BreakpointType type) {
auto& p = getBreakpointList(type);
auto bp = p.find(addr);
if(bp != p.end()) {
guint len = bp->second.len;
if(bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
LOG_DEBUG(GdbStub,
"Found breakpoint type %d @ %016lx, range: %016lx - %016lx (%d bytes)", type,
addr, bp->second.addr, bp->second.addr + len, (uint32_t) len);
return true;
}
}
return false;
}
void GdbStub::sendPacket(const char packet) {
if(send(client, &packet, 1, 0) != 1)
LOG_ERROR(GdbStub, "send failed");
}
void GdbStub::sendReply(const char* reply) {
memset(commandBuffer, 0, sizeof(commandBuffer));
commandLength = static_cast<uint32_t>(strlen(reply));
if(commandLength + 4 > sizeof(commandBuffer)) {
LOG_DEBUG(GdbStub, "commandBuffer overflow in sendReply");
return;
}
memcpy(commandBuffer + 1, reply, commandLength);
auto checksum = calculateChecksum(commandBuffer, commandLength + 1);
commandBuffer[0] = GDB_STUB_START;
commandBuffer[commandLength + 1] = GDB_STUB_END;
commandBuffer[commandLength + 2] = nibbleToHex(checksum >> 4);
commandBuffer[commandLength + 3] = nibbleToHex(checksum);
auto ptr = commandBuffer;
auto left = commandLength + 4;
while(left > 0) {
auto sent_size = send(client, reinterpret_cast<char*>(ptr), left, 0);
if(sent_size < 0)
LOG_ERROR(GdbStub, "gdb: send failed");
left -= sent_size;
ptr += sent_size;
}
}
void GdbStub::handleQuery() {
LOG_DEBUG(GdbStub, "gdb: query '%s'", commandBuffer + 1);
auto query = reinterpret_cast<const char*>(commandBuffer + 1);
if(strcmp(query, "TStatus") == 0)
sendReply("T0");
else if(strncmp(query, "Supported", strlen("Supported")) == 0)
sendReply("PacketSize=1600");
else if(strncmp(query, "Xfer:features:read:target.xml:",
strlen("Xfer:features:read:target.xml:")) == 0)
sendReply(target_xml);
else
sendReply("");
}
void GdbStub::handleSetThread() {
if(memcmp(commandBuffer, "Hg0", 3) == 0 || memcmp(commandBuffer, "Hc-1", 4) == 0 ||
memcmp(commandBuffer, "Hc0", 4) == 0 || memcmp(commandBuffer, "Hc1", 4) == 0)
return sendReply("OK");
sendReply("E01");
}
auto stringFromFormat(const char* format, ...) {
char *buf;
va_list args;
va_start(args, format);
if(vasprintf(&buf, format, args) < 0)
LOG_ERROR(GdbStub, "Unable to allocate memory for string");
va_end(args);
string ret = buf;
free(buf);
return ret;
}
void GdbStub::sendSignal(uint32_t signal) {
latestSignal = signal;
string buffer = stringFromFormat("T%02x%02x:%016lx;%02x:%016lx;", latestSignal, 32,
bswap_64(reg(32)), 31, bswap_64(reg(31)));
LOG_DEBUG(GdbStub, "Response: %s", buffer.c_str());
sendReply(buffer.c_str());
}
void GdbStub::readCommand() {
commandLength = 0;
memset(commandBuffer, 0, sizeof(commandBuffer));
uint8_t c = readByte();
if(c == '+') {
// ignore ack
return;
} else if(c == 0x03) {
LOG_INFO(GdbStub, "gdb: found break command");
haltLoop = true;
sendSignal(SIGTRAP);
return;
} else if(c != GDB_STUB_START) {
LOG_DEBUG(GdbStub, "gdb: read invalid byte %02x", c);
return;
}
while((c = readByte()) != GDB_STUB_END) {
if(commandLength >= sizeof(commandBuffer)) {
LOG_ERROR(GdbStub, "gdb: commandBuffer overflow");
sendPacket(GDB_STUB_NACK);
return;
}
commandBuffer[commandLength++] = c;
}
auto checksum_received = hexCharToValue(readByte()) << 4;
checksum_received |= hexCharToValue(readByte());
auto checksum_calculated = calculateChecksum(commandBuffer, commandLength);
if(checksum_received != checksum_calculated)
LOG_ERROR(GdbStub,
"gdb: invalid checksum: calculated %02x and read %02x for $%s# (length: %d)",
checksum_calculated, checksum_received, commandBuffer, commandLength);
sendPacket(GDB_STUB_ACK);
}
bool GdbStub::isDataAvailable() {
fd_set fd_socket;
FD_ZERO(&fd_socket);
FD_SET(client, &fd_socket);
struct timeval t;
t.tv_sec = 0;
t.tv_usec = 0;
if(select(client + 1, &fd_socket, nullptr, nullptr, &t) < 0) {
LOG_ERROR(GdbStub, "select failed");
return false;
}
return FD_ISSET(client, &fd_socket) != 0;
}
void GdbStub::readRegister() {
uint8_t reply[64];
memset(reply, 0, sizeof(reply));
uint32_t id = hexCharToValue(commandBuffer[1]);
if(commandBuffer[2] != '\0') {
id <<= 4;
id |= hexCharToValue(commandBuffer[2]);
}
if(id <= 32)
intToGdbHex(reply, reg(id));
else if(id == 33)
memset(reply, '0', 8);
else
return sendReply("E01");
sendReply(reinterpret_cast<char*>(reply));
}
void GdbStub::readRegisters() {
uint8_t buffer[GDB_BUFFER_SIZE - 4];
memset(buffer, 0, sizeof(buffer));
uint8_t* bufptr = buffer;
for(int i = 0; i <= 32; i++) {
intToGdbHex(bufptr + i * 16, reg(i));
}
bufptr += (33 * 16);
memset(bufptr, '0', 8);
sendReply(reinterpret_cast<char*>(buffer));
}
void GdbStub::writeRegister() {
const uint8_t* buffer_ptr = commandBuffer + 3;
uint32_t id = hexCharToValue(commandBuffer[1]);
if(commandBuffer[2] != '=') {
++buffer_ptr;
id <<= 4;
id |= hexCharToValue(commandBuffer[2]);
}
auto val = gdbHexToInt(buffer_ptr);
if(id <= 32)
reg(id, val);
else if(id == 33) {
}
else
return sendReply("E01");
sendReply("OK");
}
void GdbStub::writeRegisters() {
const uint8_t* buffer_ptr = commandBuffer + 1;
if(commandBuffer[0] != 'G')
return sendReply("E01");
for(auto i = 0; i < 33; ++i)
if(i <= 32)
reg(i, gdbHexToInt(buffer_ptr + i * 16));
sendReply("OK");
}
void GdbStub::readMemory() {
uint8_t reply[GDB_BUFFER_SIZE - 4];
auto start_offset = commandBuffer + 1;
auto addr_pos = find(start_offset, commandBuffer + commandLength, ',');
auto addr = hexToInt(start_offset, static_cast<uint32_t>(addr_pos - start_offset));
start_offset = addr_pos + 1;
auto len = hexToInt(start_offset, static_cast<uint32_t>((commandBuffer + commandLength) - start_offset));
LOG_DEBUG(GdbStub, "gdb: addr: %016lx len: %016lx", addr, len);
if(len * 2 > sizeof(reply)) {
sendReply("E01");
}
auto data = new uint8_t[len];
if(ctu->cpu.readmem(addr, data, len)) {
memToGdbHex(reply, data, len);
reply[len * 2] = '\0';
sendReply(reinterpret_cast<char*>(reply));
} else
sendReply("E00");
delete[] data;
}
void GdbStub::writeMemory() {
auto start_offset = commandBuffer + 1;
auto addr_pos = find(start_offset, commandBuffer + commandLength, ',');
gptr addr = hexToInt(start_offset, static_cast<uint32_t>(addr_pos - start_offset));
start_offset = addr_pos + 1;
auto len_pos = find(start_offset, commandBuffer + commandLength, ':');
auto len = hexToInt(start_offset, static_cast<uint32_t>(len_pos - start_offset));
auto dst = new uint8_t[len];
gdbHexToMem(dst, len_pos + 1, len);
if(ctu->cpu.writemem(addr, dst, len))
sendReply("OK");
else
sendReply("E00");
delete[] dst;
}
void GdbStub::_break(bool is_memoryBreak) {
if(!haltLoop) {
haltLoop = true;
sendSignal(SIGTRAP);
}
memoryBreak = is_memoryBreak;
}
void GdbStub::step() {
stepLoop = true;
haltLoop = true;
}
void GdbStub::_continue() {
memoryBreak = false;
stepLoop = false;
haltLoop = false;
}
bool GdbStub::commitBreakpoint(BreakpointType type, gptr addr, uint32_t len) {
auto& p = getBreakpointList(type);
Breakpoint breakpoint;
breakpoint.active = true;
breakpoint.addr = addr;
breakpoint.len = len;
if(type == BreakpointType::Execute)
breakpoint.hook = ctu->cpu.addCodeBreakpoint(addr);
p.insert({addr, breakpoint});
LOG_DEBUG(GdbStub, "gdb: added %d breakpoint: %016lx bytes at %016lx", type, breakpoint.len,
breakpoint.addr);
return true;
}
void GdbStub::addBreakpoint() {
BreakpointType type;
uint8_t type_id = hexCharToValue(commandBuffer[1]);
switch (type_id) {
case 0:
case 1:
type = BreakpointType::Execute;
break;
case 2:
type = BreakpointType::Write;
break;
case 3:
type = BreakpointType::Read;
break;
case 4:
type = BreakpointType::Access;
break;
default:
return sendReply("E01");
}
auto start_offset = commandBuffer + 3;
auto addr_pos = find(start_offset, commandBuffer + commandLength, ',');
gptr addr = hexToInt(start_offset, static_cast<uint32_t>(addr_pos - start_offset));
start_offset = addr_pos + 1;
auto len = (uint32_t) hexToInt(start_offset, static_cast<uint32_t>((commandBuffer + commandLength) - start_offset));
if(type == BreakpointType::Access) {
type = BreakpointType::Read;
if(!commitBreakpoint(type, addr, len)) {
return sendReply("E02");
}
type = BreakpointType::Write;
}
if(!commitBreakpoint(type, addr, len)) {
return sendReply("E02");
}
sendReply("OK");
}
void GdbStub::removeBreakpoint() {
BreakpointType type;
uint8_t type_id = hexCharToValue(commandBuffer[1]);
switch (type_id) {
case 0:
case 1:
type = BreakpointType::Execute;
break;
case 2:
type = BreakpointType::Write;
break;
case 3:
type = BreakpointType::Read;
break;
case 4:
type = BreakpointType::Access;
break;
default:
return sendReply("E01");
}
auto start_offset = commandBuffer + 3;
auto addr_pos = find(start_offset, commandBuffer + commandLength, ',');
gptr addr = hexToInt(start_offset, static_cast<uint32_t>(addr_pos - start_offset));
if(type == BreakpointType::Access) {
type = BreakpointType::Read;
removeBreakpoint(type, addr);
type = BreakpointType::Write;
}
removeBreakpoint(type, addr);
sendReply("OK");
}
void GdbStub::handlePacket() {
if(!isDataAvailable())
return;
readCommand();
if(commandLength == 0)
return;
LOG_DEBUG(GdbStub, "Packet: %s", commandBuffer);
switch(commandBuffer[0]) {
case 'q':
handleQuery();
break;
case 'H':
handleSetThread();
break;
case '?':
sendSignal(latestSignal);
break;
case 'k':
LOG_ERROR(GdbStub, "killed by gdb");
case 'g':
readRegisters();
break;
case 'G':
writeRegisters();
break;
case 'p':
readRegister();
break;
case 'P':
writeRegister();
break;
case 'm':
readMemory();
break;
case 'M':
writeMemory();
break;
case 's':
step();
return;
case 'C':
case 'c':
_continue();
return;
case 'z':
removeBreakpoint();
break;
case 'Z':
addBreakpoint();
break;
default:
sendReply("");
break;
}
}

85
GdbStub.h Normal file
View file

@ -0,0 +1,85 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
// Integrated into Mephisto/CTUv2 by Cody Brocious
#pragma once
#include "Ctu.h"
#define GDB_BUFFER_SIZE 10000
/// Breakpoint Method
enum class BreakpointType {
None,
Execute,
Read,
Write,
Access
};
struct BreakpointAddress {
gptr address;
BreakpointType type;
};
struct Breakpoint {
bool active;
gptr addr;
guint len;
hook_t hook;
};
class GdbStub {
public:
GdbStub(Ctu *_ctu);
void enable(uint16_t port);
void _break(bool is_memory_break = false);
bool isMemoryBreak();
void handlePacket();
auto getNextBreakpointFromAddress(gptr addr, BreakpointType type);
bool checkBreakpoint(gptr addr, BreakpointType type);
bool memoryBreak, haltLoop, stepLoop, enabled;
private:
auto& getBreakpointList(BreakpointType type);
void removeBreakpoint(BreakpointType type, gptr addr);
uint8_t readByte();
void sendPacket(const char packet);
void sendReply(const char* reply);
void handleQuery();
void handleSetThread();
void sendSignal(uint32_t signal);
void readCommand();
bool isDataAvailable();
void readRegister();
void readRegisters();
void writeRegister();
void writeRegisters();
void readMemory();
void writeMemory();
void step();
void _continue();
bool commitBreakpoint(BreakpointType type, gptr addr, uint32_t len);
void addBreakpoint();
void removeBreakpoint();
guint reg(int x);
void reg(int x, guint v);
Ctu *ctu;
map<gptr, Breakpoint> breakpointsExecute;
map<gptr, Breakpoint> breakpointsRead;
map<gptr, Breakpoint> breakpointsWrite;
int client;
uint8_t commandBuffer[GDB_BUFFER_SIZE];
uint32_t commandLength;
uint32_t latestSignal;
};

235
Ipc.cpp Normal file
View file

@ -0,0 +1,235 @@
#define DEFINE_STUBS
#include "Ctu.h"
string bufferToString(uint8_t *buf, uint size) {
std::stringstream ss;
auto allValid = true;
auto hitNull = false;
for(auto i = 0; i < size; ++i)
if(hitNull && buf[i] != 0) {
allValid = false;
break;
} else if(buf[i] == 0)
hitNull = true;
else if(buf[i] < 0x20 || buf[i] > 0x7E) {
allValid = false;
break;
}
if(allValid) {
ss << '"';
for(auto i = 0; i < size && buf[i] != 0; ++i)
ss << (char) buf[i];
ss << '"';
} else {
ss << "[";
for(auto i = 0; i < size; ++i) {
if(i != 0)
ss << ", ";
ss << "0x" << hex << setw(2) << setfill('0') << (int) buf[i];
}
ss << "]";
}
return ss.str();
}
void NPipe::messageAsync(shared_ptr<array<uint8_t, 0x100>> buf, function<void(uint32_t, bool closeHandle)> cb) {
acquire();
client.wait([=] {
auto obuf = client.pop();
memcpy(buf->data(), obuf->data(), 0x100);
cb(0, false); // XXX: HANDLE RETCODES AND CLOSE
return 1;
});
server.push(buf);
signal(true);
release();
}
shared_ptr<IPipe> NPort::connectSync() {
acquire();
auto pipe = make_shared<NPipe>(ctu, name);
available.push(pipe);
signal(true);
release();
return pipe;
}
shared_ptr<NPipe> NPort::accept() {
acquire();
assert(available.size() > 0);
auto val = available.front();
available.pop();
release();
return val;
}
IncomingIpcMessage::IncomingIpcMessage(uint8_t *_ptr, bool isDomainObject) : ptr(_ptr) {
auto buf = (uint32_t *) ptr;
type = buf[0] & 0xFFFF;
xCount = (buf[0] >> 16) & 0xF;
aCount = (buf[0] >> 20) & 0xF;
bCount = (buf[0] >> 24) & 0xF;
wlen = buf[1] & 0x3FF;
hasC = ((buf[1] >> 10) & 0x3) != 0;
domainHandle = 0;
domainCommand = 0;
auto hasHD = (buf[1] >> 31) == 1;
auto pos = 2;
if(hasHD) {
auto hd = buf[pos++];
hasPid = hd & 1;
copyCount = (hd >> 1) & 0xF;
moveCount = hd >> 5;
if(hasPid) {
pid = *((uint64_t *) &buf[pos]);
pos += 2;
}
copyOffset = pos * 4;
pos += copyCount;
moveOffset = pos * 4;
pos += moveCount;
}
descOffset = pos * 4;
pos += xCount * 2;
pos += aCount * 3;
pos += bCount * 3;
rawOffset = pos * 4;
if(pos & 3)
pos += 4 - (pos & 3);
if(isDomainObject && type == 4) {
domainHandle = buf[pos + 1];
domainCommand = buf[pos] & 0xFF;
pos += 4;
}
assert(type == 2 || (isDomainObject && domainCommand == 2) || buf[pos] == FOURCC('S', 'F', 'C', 'I'));
sfciOffset = pos * 4;
cmdId = getData<uint32_t>(0);
}
OutgoingIpcMessage::OutgoingIpcMessage(uint8_t *_ptr, bool _isDomainObject) : ptr(_ptr), isDomainObject(_isDomainObject) {
}
void OutgoingIpcMessage::initialize(uint _moveCount, uint _copyCount, uint dataBytes) {
moveCount = _moveCount;
copyCount = _copyCount;
auto buf = (uint32_t *) ptr;
buf[0] = 0;
if(moveCount != 0 || copyCount != 0) {
buf[1] = ((moveCount != 0 && !isDomainObject) || copyCount != 0) ? (1U << 31) : 0;
buf[2] = (copyCount << 1) | ((isDomainObject ? 0 : moveCount) << 5);
}
auto pos = 2 + (((moveCount != 0 && !isDomainObject) || copyCount != 0) ? (1 + moveCount + copyCount) : 0);
auto start = pos;
if(pos & 3)
pos += 4 - (pos & 3);
if(isDomainObject)
pos += 4;
realDataOffset = isDomainObject ? moveCount << 2 : 0;
auto dataWords = (realDataOffset >> 2) + (dataBytes & 3) ? (dataBytes >> 2) + 1 : (dataBytes >> 2);
buf[1] |= pos - start + 4 + dataWords;
sfcoOffset = pos * 4;
buf[pos] = FOURCC('S', 'F', 'C', 'O');
}
void OutgoingIpcMessage::commit() {
auto buf = (uint32_t *) ptr;
buf[(sfcoOffset >> 2) + 2] = errCode;
}
uint32_t IpcService::messageSync(shared_ptr<array<uint8_t, 0x100>> buf, bool& closeHandle) {
uint8_t obuf[0x100];
memset(obuf, 0, 0x100);
//hexdump(buf, 0x50);
IncomingIpcMessage msg(buf->data(), isDomainObject);
OutgoingIpcMessage resp(obuf, isDomainObject);
auto ret = 0xf601;
IpcService *target = this;
if(isDomainObject && msg.domainHandle != thisHandle && msg.type == 4) {
if(domainHandles.find(msg.domainHandle) != domainHandles.end())
target = dynamic_pointer_cast<IpcService>(domainHandles[msg.domainHandle]).get();
else
LOG_ERROR(Ipc, "Unknown domain handle! 0x%x", msg.domainHandle);
}
if(!isDomainObject || msg.domainCommand == 1 || msg.type == 2 || msg.type == 5)
switch(msg.type) {
case 2: // Close
closeHandle = true;
resp.initialize(0, 0, 0);
resp.errCode = 0;
ret = 0;
break;
case 4: // Normal
ret = target->dispatch(msg, resp);
break;
case 5: // Control
switch(msg.cmdId) {
case 0: // ConvertSessionToDomain
LOG_DEBUG(Ipc, "ConvertSessionToDomain");
resp.initialize(0, 0, 4);
isDomainObject = true;
*resp.getDataPointer<uint32_t *>(8) = thisHandle;
resp.errCode = 0;
ret = 0;
break;
case 2: // DuplicateSession
LOG_DEBUG(Ipc, "DuplicateSession");
resp.isDomainObject = false;
resp.initialize(1, 0, 0);
resp.move(0, ctu->duplicateHandle(dynamic_cast<KObject *>(this)));
resp.errCode = 0;
ret = 0;
break;
case 3: // QueryPointerBufferSize
LOG_DEBUG(Ipc, "QueryPointerBufferSize");
resp.initialize(0, 0, 4);
*resp.getDataPointer<uint32_t *>(8) = 0x500;
resp.errCode = 0;
ret = 0;
break;
default:
LOG_ERROR(Ipc, "Unknown cmdId to control %u", msg.cmdId);
}
break;
}
else
switch(msg.domainCommand) {
case 2:
domainHandles.erase(msg.domainHandle);
resp.initialize(0, 0, 0);
resp.errCode = 0;
ret = 0;
break;
default:
LOG_ERROR(Ipc, "Unknown cmdId to domain %u", msg.domainCommand);
}
if(ret == 0) {
resp.commit();
memcpy(buf->data(), obuf, 0x100);
//hexdump(buf, 0x50);
}
return ret;
}
ghandle IpcService::fauxNewHandle(shared_ptr<KObject> obj) {
return ctu->newHandle(obj);
}
Ipc::Ipc(Ctu *_ctu) : ctu(_ctu) {
sm = make_shared<SmService>(ctu);
}
ghandle Ipc::ConnectToPort(string name) {
if(name != "sm:")
LOG_ERROR(Ipc, "Attempt to connect to unknown port: \"%s\"", name.c_str());
return ctu->newHandle(sm);
}

249
Ipc.h Normal file
View file

@ -0,0 +1,249 @@
#pragma once
#include "Ctu.h"
// Used to clarify IPC stubs
#define IN
#define OUT
#define buildInterface(cls, ...) make_shared<cls>(ctu, ##__VA_ARGS__)
string bufferToString(uint8_t *buf, uint size);
class IPipe : public Waitable {
public:
virtual ~IPipe() {}
virtual bool isAsync() = 0;
virtual uint32_t messageSync(shared_ptr<array<uint8_t, 0x100>> buf, bool& closeHandle) { return 0; }
virtual void messageAsync(shared_ptr<array<uint8_t, 0x100>> buf, function<void(uint32_t, bool closeHandle)> cb) {}
};
class IPort : public Waitable {
public:
virtual ~IPort() {}
virtual bool isAsync() = 0;
virtual shared_ptr<IPipe> connectSync() { return nullptr; }
virtual void connectAsync(function<void(shared_ptr<IPipe>)> cb) {}
};
class PipeEnd : public Waitable {
public:
PipeEnd() : closed(false) {}
void close() override {
if(closed)
return;
closed = true;
signal();
}
void push(shared_ptr<array<uint8_t, 0x100>> message) {
acquire();
messages.push(message);
release();
}
shared_ptr<array<uint8_t, 0x100>> pop() {
acquire();
if(closed) {
release();
return nullptr;
}
auto temp = messages.front();
messages.pop();
release();
return temp;
}
bool closed;
private:
queue<shared_ptr<array<uint8_t, 0x100>>> messages;
};
class NPipe : public IPipe {
public:
NPipe(Ctu *_ctu, string _name) : ctu(_ctu), name(_name), closed(false) {}
bool isAsync() override { return true; }
void messageAsync(shared_ptr<array<uint8_t, 0x100>> buf, function<void(uint32_t, bool closeHandle)> cb) override;
void close() override {
if(closed)
return;
closed = true;
server.close();
client.close();
signal();
}
string name;
PipeEnd server, client;
bool closed;
private:
Ctu *ctu;
};
class NPort : public IPort {
public:
NPort(Ctu *_ctu, string _name) : ctu(_ctu), name(_name) {}
~NPort() override {}
bool isAsync() override { return false; }
shared_ptr<IPipe> connectSync() override;
shared_ptr<NPipe> accept();
string name;
private:
Ctu *ctu;
queue<shared_ptr<NPipe>> available;
};
class IncomingIpcMessage {
public:
IncomingIpcMessage(uint8_t *_ptr, bool isDomainObject);
template<typename T>
T getData(uint offset) {
return *((T *) (ptr + sfciOffset + 8 + offset));
}
template<typename T>
T getDataPointer(uint offset) {
return (T) (ptr + sfciOffset + 8 + offset);
}
gptr getBuffer(int btype, int num, guint& size) {
size = 0;
auto ax = (btype & 3) == 1;
auto flags_ = btype & 0xC0;
auto flags = flags_ == 0x80 ? 3 : (flags_ == 0x40 ? 1 : 0);
auto cx = (btype & 0xC) == 8;
switch((ax << 1) | cx) {
case 0: { // B
auto t = (uint32_t *) (ptr + descOffset + xCount * 8 + aCount * 12 + num * 12);
gptr a = t[0], b = t[1], c = t[2];
size = (guint) (a | (((c >> 24) & 0xF) << 32));
if((c & 0x3) != flags)
LOG_ERROR(Ipc, "B descriptor flags don't match: %u vs expected %u", (uint) (c & 0x3), flags);
return b | (((((c >> 2) << 4) & 0x70) | ((c >> 28) & 0xF)) << 32);
}
case 1: { // C
assert(num == 0);
auto t = (uint32_t *) (ptr + rawOffset + wlen * 4);
gptr a = t[0], b = t[1];
size = b >> 16;
return a | ((b & 0xFFFF) << 32);
}
case 2: { // A
auto t = (uint32_t *) (ptr + descOffset + xCount * 8 + num * 12);
gptr a = t[0], b = t[1], c = t[2];
size = (guint) (a | (((c >> 24) & 0xF) << 32));
if((c & 0x3) != flags)
LOG_ERROR(Ipc, "A descriptor flags don't match: %u vs expected %u", (uint) (c & 0x3), flags);
return b | (((((c >> 2) << 4) & 0x70) | ((c >> 28) & 0xF)) << 32);
}
case 3: { // X
auto t = (uint32_t *) (ptr + descOffset + num * 8);
gptr a = t[0], b = t[1];
size = (guint) (a >> 16);
return b | ((((a >> 12) & 0xF) | ((a >> 2) & 0x70)) << 32);
}
}
return 0;
}
ghandle getMoved(int off) {
return *(ghandle *) (ptr + moveOffset + off * 4);
}
ghandle getCopied(int off) {
return *(ghandle *) (ptr + copyOffset + off * 4);
}
uint cmdId, type;
uint aCount, bCount, cCount, hasC, xCount, hasPid, moveCount, copyCount;
gpid pid;
ghandle domainHandle;
uint domainCommand;
private:
uint wlen, rawOffset, sfciOffset, descOffset, copyOffset, moveOffset;
uint8_t *ptr;
};
class OutgoingIpcMessage {
public:
OutgoingIpcMessage(uint8_t *_ptr, bool _isDomainObject);
void initialize(uint _moveCount, uint _copyCount, uint dataBytes);
void commit();
template<typename T>
T getDataPointer(uint offset) {
return (T) (ptr + sfcoOffset + 8 + offset + (offset < 8 ? 0 : realDataOffset));
}
void copy(int offset, ghandle hnd) {
auto buf = (uint32_t *) ptr;
buf[3 + offset] = hnd;
}
void move(int offset, ghandle hnd) {
auto buf = (uint32_t *) ptr;
if(isDomainObject)
buf[(sfcoOffset >> 2) + 4 + offset] = hnd;
else
buf[3 + copyCount + offset] = hnd;
}
uint32_t errCode;
uint moveCount, copyCount;
bool isDomainObject;
private:
uint sfcoOffset;
uint realDataOffset;
uint8_t *ptr;
};
class IpcService : public IPipe {
public:
IpcService(Ctu *_ctu) : ctu(_ctu), domainOwner(nullptr), isDomainObject(false), domainHandleIter(0xf001), thisHandle(0xf000) {}
~IpcService() override {}
bool isAsync() override { return false; }
uint32_t messageSync(shared_ptr<array<uint8_t, 0x100>> buf, bool& closeHandle) override;
virtual uint32_t dispatch(IncomingIpcMessage &req, OutgoingIpcMessage &resp) { return 0xF601; }
protected:
Ctu *ctu;
IpcService *domainOwner;
bool isDomainObject;
int domainHandleIter, thisHandle;
unordered_map<uint32_t, shared_ptr<KObject>> domainHandles;
template<typename T>
ghandle createHandle(shared_ptr<T> obj) {
static_assert(std::is_base_of<KObject, T>::value, "T must derive from KObject");
if(domainOwner)
return domainOwner->createHandle(obj);
else if(isDomainObject) {
auto hnd = domainHandleIter++;
auto temp = dynamic_pointer_cast<IpcService>(obj);
if(temp != nullptr)
temp->domainOwner = this;
domainHandles[hnd] = dynamic_pointer_cast<KObject>(obj);
return hnd;
} else
return fauxNewHandle(dynamic_pointer_cast<KObject>(obj));
}
ghandle fauxNewHandle(shared_ptr<KObject> obj);
};
class IUnknown : public IpcService {
};
class SmService;
class Ipc {
public:
Ipc(Ctu *_ctu);
ghandle ConnectToPort(string name);
shared_ptr<SmService> sm;
private:
Ctu *ctu;
};

218
IpcBridge.cpp Normal file
View file

@ -0,0 +1,218 @@
#include "Ctu.h"
#include "IpcStubs.h"
#define BRIDGE_PORT 31337
IpcBridge::IpcBridge(Ctu *_ctu) : ctu(_ctu) {
struct sockaddr_in addr;
serv = socket(AF_INET, SOCK_STREAM, 0);
auto enable = 1;
setsockopt(serv, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
addr.sin_family = AF_INET;
addr.sin_port = htons(BRIDGE_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
memset(addr.sin_zero, '\0', sizeof addr.sin_zero);
bind(serv, (struct sockaddr *) &addr, sizeof addr);
listen(serv, 10);
client = -1;
waitingForAsync = false;
}
void IpcBridge::start() {
LOG_INFO(IpcBridge, "Starting");
for(auto i = 0; i < 24; ++i) {
auto addr = (i + 1) * (1 << 20) + (1 << 28);
ctu->cpu.map(addr, 1024 * 1024);
buffers[i] = addr;
}
auto thread = ctu->tm.createNative([this] { run(); });
thread->resume();
}
bool isReadable(int fd) {
struct timeval tv;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
tv.tv_sec = tv.tv_usec = 0;
return select(fd + 1, &rfds, nullptr, nullptr, &tv) != -1 && FD_ISSET(fd, &rfds);
}
uint64_t IpcBridge::readint() {
uint64_t val = 0;
recv(client, &val, 8, 0);
return val;
}
uint64_t IpcBridge::readint(bool &closed) {
uint64_t val = 0;
if(recv(client, &val, 8, 0) == 0)
closed = true;
return val;
}
string IpcBridge::readstring() {
auto size = readint();
auto buf = new char[size];
recv(client, buf, size, 0);
string ret(buf, size);
delete[] buf;
return ret;
}
IpcData IpcBridge::readdata() {
auto size = readint();
auto buf = new uint8_t[size];
recv(client, buf, size, 0);
auto bufptr = buffers[bufferOff++];
ctu->cpu.writemem(bufptr, buf, size);
delete[] buf;
return IpcData(bufptr, size);
}
void IpcBridge::writeint(uint64_t val) {
send(client, &val, 8, 0);
}
void IpcBridge::writedesc(vector<tuple<IpcData, int>> descs) {
writeint(descs.size());
for(auto [data, flag] : descs) {
writeint(data.size);
auto buf = new uint8_t[data.size];
ctu->cpu.readmem(data.ptr, buf, data.size);
send(client, buf, data.size, 0);
writeint(flag);
}
}
void IpcBridge::writedesc(vector<IpcData> descs) {
writeint(descs.size());
if(descs.size() > 0)
LOG_ERROR(IpcBridge, "C descriptors unsupported");
}
void IpcBridge::sendResponse(ghandle hnd, uint32_t res, bool close, shared_ptr<array<uint8_t, 0x100>> buf, const OutgoingBridgeMessage &orig) {
if(close) {
openHandles.erase(hnd);
ctu->deleteHandle(hnd);
writeint(0xf601);
return;
}
if(res != 0) {
writeint(res);
return;
}
hexdump(buf);
IncomingBridgeMessage msg(buf);
writeint(0);
writeint(msg.data.size()); for(auto v : msg.data) writeint(v);
writeint(msg.copiedHandles.size()); for(auto v : msg.copiedHandles) writeint(v);
writeint(msg.movedHandles.size());
for(auto v : msg.movedHandles) {
openHandles[v] = ctu->getHandle<KObject>(v);
writeint(v);
}
writedesc(orig.a);
writedesc(orig.b);
writedesc(orig.c);
writedesc(orig.x);
writeint(msg.type);
}
void IpcBridge::run() {
if(waitingForAsync)
return;
if(client == -1) {
if(!isReadable(serv))
return;
struct sockaddr_storage client_addr;
socklen_t addr_size = sizeof client_addr;
client = accept(serv, (struct sockaddr *) &client_addr, &addr_size);
LOG_INFO(IpcBridge, "IPC bridge got connection");
} else {
if(!isReadable(client))
return;
auto closed = false;
auto cmd = readint(closed);
if(closed) {
LOG_INFO(IpcBridge, "Client disconnected");
client = -1;
for(auto [hnd, _] : openHandles)
ctu->deleteHandle(hnd);
openHandles.clear();
return;
}
switch(cmd) {
case 0: { // Open service
auto name = readstring();
LOG_DEBUG(IpcBridge, "Attempting to open service %s", name.c_str());
auto sm = ctu->ipc.sm;
if(sm->ports.find(name) == sm->ports.end()) {
LOG_DEBUG(IpcBridge, "Unknown service!");
writeint(0);
return;
}
auto obj = sm->ports[name]->connectSync();
auto hnd = ctu->newHandle(obj);
openHandles[hnd] = obj;
writeint(hnd);
break;
}
case 1: { // Close handle
auto hnd = (ghandle) readint();
openHandles.erase(hnd);
ctu->deleteHandle(hnd);
break;
}
case 2: { // Message
OutgoingBridgeMessage msg;
bufferOff = 0;
msg.type = readint();
msg.data = readarray([this] { return readint(); });
msg.pid = readint();
msg.copiedHandles = readarray([this] { return (ghandle) readint(); });
msg.movedHandles = readarray([this] { return (ghandle) readint(); });
msg.a = readarray([this] { return tuple{readdata(), (int) readint()}; });
msg.b = readarray([this] { return tuple{readdata(), (int) readint()}; });
msg.c = readarray([this] { return readdata(); });
msg.x = readarray([this] { return tuple{readdata(), (int) readint()}; });
auto hnd = (ghandle) readint();
auto packed = msg.pack();
if(openHandles.find(hnd) == openHandles.end()) {
writeint(0xe401); // Bad handle
break;
}
auto obj = dynamic_pointer_cast<IPipe>(openHandles[hnd]);
if(obj->isAsync()) {
waitingForAsync = true;
obj->messageAsync(packed, [=](auto res, auto close) {
sendResponse(hnd, res, close, packed, msg);
waitingForAsync = false;
});
} else {
auto close = false;
auto ret = obj->messageSync(packed, close);
sendResponse(hnd, ret, close, packed, msg);
}
break;
}
default:
close(client);
client = -1;
break;
}
}
}

169
IpcBridge.h Normal file
View file

@ -0,0 +1,169 @@
#pragma once
#include "Ctu.h"
class IpcData {
public:
IpcData(gptr _ptr, uint64_t _size) : ptr(_ptr), size(_size) {}
gptr ptr;
uint64_t size;
};
class OutgoingBridgeMessage {
public:
auto pack() {
auto ret = make_shared<array<uint8_t, 0x100>>();
auto pos = 0;
auto buf = (uint32_t *) ret->data();
buf[0] = (uint32_t) (type | (x.size() << 16) | (a.size() << 20) | (b.size() << 24));
buf[1] = (pid != (uint64_t) -1 || copiedHandles.size() > 0 || movedHandles.size() > 0) ? 1U << 31 : 0;
buf[1] |= c.size() > 0 ? 2 << 10 : 0;
pos = 2;
if(pid != (uint64_t) -1 || copiedHandles.size() > 0 || movedHandles.size() > 0) {
buf[pos] = pid != (uint64_t) -1 ? 1 : 0;
buf[pos] |= copiedHandles.size() << 1;
buf[pos++] |= movedHandles.size() << 5;
if(pid != (uint64_t) -1) {
buf[pos++] = pid & 0xFFFFFFFF;
buf[pos++] = pid >> 32;
}
for(auto hnd : copiedHandles)
buf[pos++] = hnd;
for(auto hnd : movedHandles)
buf[pos++] = hnd;
}
for(auto [ed, ec] : x) {
auto laddr = ed.ptr & 0xFFFFFFFF, haddr = ed.ptr >> 32;
buf[pos++] = (uint32_t) (
(ec & 0x3F) |
(((haddr & 0x70) >> 4) << 6) |
(ec & 0xE00) |
((haddr & 0xF) << 12) |
(ed.size << 16)
);
buf[pos++] = (uint32_t) laddr;
}
vector<tuple<IpcData, int>> ab;
ab.insert(ab.end(), a.begin(), a.end());
ab.insert(ab.end(), b.begin(), b.end());
for(auto [ad, af] : ab) {
auto laddr = ad.ptr & 0xFFFFFFFF, haddr = ad.ptr >> 32;
auto lsize = ad.size & 0xFFFFFFFF, hsize = ad.size >> 32;
buf[pos++] = (uint32_t) lsize;
buf[pos++] = (uint32_t) laddr;
buf[pos++] = (uint32_t) (
af |
(((haddr & 0x70) >> 4) << 2) |
((hsize & 0xF) << 24) |
((haddr & 0xF) << 28)
);
}
auto epos = pos;
while(pos & 3)
buf[pos++] = 0;
buf[pos++] = FOURCC('S', 'F', 'C', 'I');
buf[pos++] = 0;
for(auto elem : data) {
buf[pos++] = (uint32_t) elem;
buf[pos++] = (uint32_t) (elem >> 32);
}
assert(c.size() == 0); // XXX: Add c descriptor packing eventually. One day.
buf[1] |= pos - epos + 2;
return ret;
}
uint64_t type, pid;
vector<uint64_t> data;
vector<ghandle> copiedHandles, movedHandles;
vector<tuple<IpcData, int>> a, b, x;
vector<IpcData> c;
};
class IncomingBridgeMessage {
public:
IncomingBridgeMessage(shared_ptr<array<uint8_t, 0x100>> _buf) {
auto buf = (uint32_t *) _buf->data();
type = buf[0] & 0xFFFF;
auto nx = (buf[0] >> 16) & 0xF, na = (buf[0] >> 20) & 0xF, nb = (buf[0] >> 24) & 0xF;
auto hasHD = buf[1] >> 31;
auto hasC = (buf[1] >> 10) & 3;
if(nx || na || nb || hasC)
LOG_DEBUG(IpcBridge, "Warning! Message coming back from IPC bridge has descriptors. This is unhandled and may lose data.");
auto pos = 2;
if(hasHD) {
auto hasPid = buf[pos] & 1, nc = (buf[pos] >> 1) & 0xF, nm = (buf[pos++] >> 5) & 0xF;
if(hasPid) {
pid = buf[pos] | (((uint64_t) buf[pos+1]) << 32);
pos += 2;
}
for(auto i = 0; i < nc; ++i)
copiedHandles.push_back((ghandle) buf[pos++]);
for(auto i = 0; i < nm; ++i)
movedHandles.push_back((ghandle) buf[pos++]);
}
pos += nx * 2 + na * 3 + nb * 3;
auto epos = pos + (buf[1] & 0x3FF) - 2;
while(pos & 3)
pos++;
assert(buf[pos++] == FOURCC('S', 'F', 'C', 'O'));
pos++;
for( ; pos < epos; pos += 2)
data.push_back(buf[pos] | (((uint64_t) buf[pos+1]) << 32));
}
uint64_t type, pid;
vector<uint64_t> data;
vector<ghandle> copiedHandles, movedHandles;
};
class IpcBridge {
public:
IpcBridge(Ctu *_ctu);
void start();
private:
void run();
uint64_t readint();
uint64_t readint(bool &closed);
template<typename T>
auto readarray(T &&cb) {
vector<decltype(cb())> vec;
auto count = readint();
vec.reserve(count);
for(auto i = 0; i < count; ++i)
vec.push_back(cb());
return vec;
}
string readstring();
IpcData readdata();
void writeint(uint64_t val);
void writedesc(vector<tuple<IpcData, int>> descs);
void writedesc(vector<IpcData> descs);
void sendResponse(ghandle hnd, uint32_t res, bool closed, shared_ptr<array<uint8_t, 0x100>> buf, const OutgoingBridgeMessage &orig);
Ctu *ctu;
int serv;
int client;
unordered_map<ghandle, shared_ptr<KObject>> openHandles;
gptr buffers[24];
int bufferOff;
bool waitingForAsync;
};

24
KObject.h Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include "Ctu.h"
class KObject {
public:
virtual ~KObject() {}
virtual void close() {}
};
class Process : public KObject {
public:
Process(Ctu *_ctu) : ctu(_ctu) {}
private:
Ctu *ctu;
};
class FauxHandle : public KObject {
public:
FauxHandle(uint32_t _val) : val(_val) {}
uint32_t val;
};

86
Lisparser.cpp Normal file
View file

@ -0,0 +1,86 @@
#include "Ctu.h"
#define BETWEEN(x, a, b) (((x) >= (a)) && ((x) <= (b)))
string nextToken(string &code, int &off) {
auto clen = code.length();
while(off < clen && (code[off] == ' ' || code[off] == '\n' || code[off] == '\t' || code[off] == '\r'))
++off;
auto rlen = clen - off;
auto orig = off;
if(rlen <= 0)
return "";
if(code[off] == '(') {
off++;
return "(";
} else if(code[off] == ')') {
off++;
return ")";
} else if(rlen >= 3 && code[off] == '0' && code[off+1] == 'x') {
int te;
for(te = off + 2; te < clen; ++te) {
if(code[te] == ' ' || code[te] == '\n' || code[te] == '\r' || code[te] == '\t' || code[te] == ')')
break;
assert(BETWEEN(code[te], '0', '9') || BETWEEN(code[te], 'a', 'f') || BETWEEN(code[te], 'A', 'F'));
}
off = te;
return code.substr(orig, off - orig);
} else if(BETWEEN(code[off], '0', '9')) {
int te;
for(te = off + 1; te < clen; ++te) {
if(code[te] == ' ' || code[te] == '\n' || code[te] == '\r' || code[te] == '\t' || code[te] == ')')
break;
assert(BETWEEN(code[te], '0', '9'));
}
off = te;
return code.substr(orig, off - orig);
} else if(code[off] == '"') {
string out = "\""; // Signal to the parser that this should be a String rather than Symbol
for(++off ; off < clen && code[off] != '"'; ++off) {
if(code[off] == '\\') {
} else
out += code[off];
}
assert(code[off++] == '"');
return out;
} else if(BETWEEN(code[off], '!', '\'') || BETWEEN(code[off], '*', '~')) {
int te;
for(te = off + 1; te < clen; ++te) {
if(code[te] == ' ' || code[te] == '\n' || code[te] == '\r' || code[te] == '\t' || code[te] == ')')
break;
assert(BETWEEN(code[off], '!', '\'') || BETWEEN(code[off], '*', '~'));
}
off = te;
return code.substr(orig, off - orig);
}
LOG_ERROR(Lisparser, "Unknown token at offset %i", off);
}
shared_ptr<Atom> parseLisp(string code) {
auto off = 0;
auto cur = make_shared<Atom>();
forward_list<shared_ptr<Atom>> stack;
while(true) {
auto token = nextToken(code, off);
if(token == "")
break;
else if(token == "(") {
auto ne = make_shared<Atom>();
cur->children.push_back(ne);
stack.push_front(cur);
cur = ne;
} else if(token == ")") {
cur = stack.front();
stack.pop_front();
} else if(token[0] == '"')
cur->children.push_back(make_shared<Atom>(String, token.substr(1)));
else if(token.length() >= 3 && token[0] == '0' && token[1] == 'x')
cur->children.push_back(make_shared<Atom>(stoull(token.substr(2), nullptr, 16)));
else if(BETWEEN(token[0], '0', '9'))
cur->children.push_back(make_shared<Atom>(stoull(token, nullptr, 10)));
else
cur->children.push_back(make_shared<Atom>(Symbol, token));
}
assert(stack.empty());
return cur;
}

29
Lisparser.h Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "Ctu.h"
enum AtomType {
Number, String, Symbol, List
};
class Atom {
public:
Atom() {
type = List;
}
Atom(AtomType _type, string val) {
assert(_type == String || _type == Symbol);
type = _type;
strVal = val;
}
Atom(guint val) {
type = Number;
numVal = val;
}
AtomType type;
guint numVal;
string strVal;
vector<shared_ptr<Atom>> children;
};
shared_ptr<Atom> parseLisp(string code);

64
Mmio.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "Ctu.h"
Mmio::Mmio(Ctu *_ctu) {
MMIORegister(0x70000000, 0x1000, new ApbMmio());
MMIORegister(0x702ec000, 0x2000, new ApbMmio());
MMIORegister(0x70030000, 0x8000, new ApbMmio());
MMIORegister(0x702ef700, 0x40, new ApbMmio());
MMIORegister(0x702f9000, 0x1000, new ApbMmio());
//MMIORegister(0x70030000, 0x8000, new ApbMmio());
ctu = _ctu;
//MMIOInitialize();
}
void Mmio::MMIORegister(gptr base, guint size, MmioBase *mmioBase) {
LOG_DEBUG(Mmio, "Registered MMIO " ADDRFMT, base);
mmioBase->setSize(size);
mmioBases[base] = mmioBase;
}
void Mmio::MMIOInitialize() {
for(auto item : mmioBases) {
item.second->setOffset(mmioBaseSize);
mmioBaseSize += item.second->mmioSize;
}
if(mmioBaseSize & 0xFFF)
mmioBaseSize = (mmioBaseSize & ~0xFFF) + 0x1000;
LOG_DEBUG(Mmio, "Mapping 0x" ADDRFMT " size 0x%x", mmioBaseAddr, (uint) mmioBaseSize);
ctu->cpu.map(mmioBaseAddr, mmioBaseSize);
}
gptr Mmio::getVirtualAddressFromAddr(gptr addr) {
for(auto item : mmioBases) {
if(addr >= item.first && addr <= (item.first+item.second->mmioSize)) {
return mmioBaseAddr+item.second->offsetFromMMIO;
}
}
return 0x0;
}
gptr Mmio::getPhysicalAddressFromVirtual(gptr addr) {
if(addr < mmioBaseAddr) return 0x0;
gptr offset = addr - mmioBaseAddr;
for(auto item : mmioBases) {
if(item.second->offsetFromMMIO >= offset && offset <= item.second->offsetFromMMIO+item.second->mmioSize) {
return item.first+offset;
}
}
return 0x0;
}
MmioBase *Mmio::getMMIOFromPhysicalAddress(gptr addr) {
if(addr < mmioBaseAddr) return nullptr;
gptr offset = addr - mmioBaseAddr;
for(auto item : mmioBases) {
if(item.second->offsetFromMMIO >= offset && offset <= item.second->offsetFromMMIO+item.second->mmioSize) {
return item.second;
}
}
return nullptr;
}

71
Mmio.h Normal file
View file

@ -0,0 +1,71 @@
#pragma once
#include "Ctu.h"
class MmioBase {
public:
MmioBase() {}
virtual ~MmioBase() {
for(auto entry : storedValues) {
free(entry.second);
}
}
void Setup();
virtual bool sread(gptr addr, guint size, void *out) {
return false;
}
virtual bool swrite(gptr addr, guint size, void *value) {
cout << "no" << endl;
return false;
}
void setSize(guint _size) {
mmioSize = _size;
}
void setOffset(guint _offset) {
offsetFromMMIO = _offset;
}
guint offsetFromMMIO;
guint mmioSize;
private:
unordered_map<gptr, void *> storedValues;
};
class Mmio {
public:
Mmio(Ctu *ctu);
virtual ~Mmio() {}
gptr getVirtualAddressFromAddr(gptr addr);
gptr getPhysicalAddressFromVirtual(gptr addr);
MmioBase *getMMIOFromPhysicalAddress(gptr addr);
void MMIOInitialize();
gptr GetBase() {
return mmioBaseAddr;
}
guint GetSize() {
return mmioBaseSize;
}
private:
Ctu *ctu;
void MMIORegister(gptr base, guint size, MmioBase *mmioBase);
gptr mmioBaseAddr = 0x4000000;//1 << 58;
guint mmioBaseSize = 0;
unordered_map<gptr, MmioBase *> mmioBases;
};
class ApbMmio : public MmioBase {
public:
bool sread(gptr addr, guint size, void *out) {
return false;
}
bool swrite(gptr addr, guint size, void *value) {
//*value = 0xdeadbeef;
return false;
}
};

54
Nxo.cpp Normal file
View file

@ -0,0 +1,54 @@
#include "Ctu.h"
#include <lz4.h>
Nxo::Nxo(string fn) {
fp.open(fn, ios::in | ios::binary);
fp.seekg(0, ios_base::end);
length = (uint32_t) fp.tellg();
fp.seekg(0);
}
typedef struct {
uint32_t magic, pad0, pad1, pad2;
uint32_t textOff, textLoc, textSize, pad3;
uint32_t rdataOff, rdataLoc, rdataSize, pad4;
uint32_t dataOff, dataLoc, dataSize;
uint32_t bssSize;
} NsoHeader;
char *decompress(ifstream &fp, uint32_t offset, uint32_t csize, uint32_t usize) {
fp.seekg(offset);
char *buf = new char[csize];
char *obuf = new char[usize];
fp.read(buf, csize);
assert(LZ4_decompress_safe(buf, obuf, csize, usize) == usize);
delete[] buf;
return obuf;
}
guint Nso::load(Ctu &ctu, gptr base, bool relocate) {
NsoHeader hdr;
fp.read((char *) &hdr, sizeof(NsoHeader));
if(hdr.magic != FOURCC('N', 'S', 'O', '0'))
return 0;
gptr tsize = hdr.dataLoc + hdr.dataSize + hdr.bssSize;
if(tsize & 0xFFF)
tsize = (tsize & ~0xFFF) + 0x1000;
ctu.cpu.map(base, tsize);
char *text = decompress(fp, hdr.textOff, hdr.rdataOff - hdr.textOff, hdr.textSize);
ctu.cpu.writemem(base + hdr.textLoc, text, hdr.textSize);
delete[] text;
char *rdata = decompress(fp, hdr.rdataOff, hdr.dataOff - hdr.rdataOff, hdr.rdataSize);
ctu.cpu.writemem(base + hdr.rdataLoc, rdata, hdr.rdataSize);
delete[] rdata;
char *data = decompress(fp, hdr.dataOff, length - hdr.dataOff, hdr.dataSize);
ctu.cpu.writemem(base + hdr.dataLoc, data, hdr.dataSize);
delete[] data;
return tsize;
}

23
Nxo.h Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include "Ctu.h"
class Nxo {
public:
Nxo(string fn);
virtual ~Nxo() {}
virtual guint load(Ctu &ctu, gptr base, bool relocate=false) = 0;
protected:
ifstream fp;
uint32_t length;
};
class Nso : Nxo {
public:
Nso(string fn) : Nxo(fn) {}
virtual ~Nso() override {}
guint load(Ctu &ctu, gptr base, bool relocate=false) override;
};

627
Svc.cpp Normal file
View file

@ -0,0 +1,627 @@
#include "Ctu.h"
#define IX0 (ctu->cpu.reg(0))
#define IX1 (ctu->cpu.reg(1))
#define IX2 (ctu->cpu.reg(2))
#define IX3 (ctu->cpu.reg(3))
#define IX4 (ctu->cpu.reg(4))
#define IX5 (ctu->cpu.reg(5))
#define registerSvc(num, func, ...) do { \
ctu->cpu.registerSvcHandler((num), [&] { \
(func)(__VA_ARGS__); \
}); \
} while(0)
#define registerSvc_ret_X0_X1_X2(num, func, ...) do { \
ctu->cpu.registerSvcHandler((num), [&] { \
ctu->tm.switched = false; \
auto [_0, _1, _2] = (func)(__VA_ARGS__); \
if(ctu->tm.switched) { \
ctu->tm.switched = false; \
return; \
} \
ctu->cpu.reg(0, _0); \
ctu->cpu.reg(1, _1); \
ctu->cpu.reg(2, _2); \
}); \
} while(0)
#define registerSvc_ret_X0(num, func, ...) do { \
ctu->cpu.registerSvcHandler((num), [&] { \
ctu->tm.switched = false; \
auto v = (func)(__VA_ARGS__); \
if(ctu->tm.switched) { \
ctu->tm.switched = false; \
return; \
} \
ctu->cpu.reg(0, v); \
}); \
} while(0)
#define registerSvc_ret_X1(num, func, ...) do { \
ctu->cpu.registerSvcHandler((num), [&] { \
ctu->tm.switched = false; \
auto v = (func)(__VA_ARGS__); \
if(ctu->tm.switched) { \
ctu->tm.switched = false; \
return; \
} \
ctu->cpu.reg(1, v); \
}); \
} while(0)
#define registerSvc_ret_X0_X1(num, func, ...) do { \
ctu->cpu.registerSvcHandler((num), [&] { \
ctu->tm.switched = false; \
auto [_0, _1] = (func)(__VA_ARGS__); \
if(ctu->tm.switched) { \
ctu->tm.switched = false; \
return; \
} \
ctu->cpu.reg(0, _0); \
ctu->cpu.reg(1, _1); \
}); \
} while(0)
Svc::Svc(Ctu *_ctu) : ctu(_ctu) {
registerSvc_ret_X0_X1( 0x01, SetHeapSize, IX1);
registerSvc_ret_X0( 0x03, SetMemoryAttribute, IX0, IX1, IX2, IX3);
registerSvc_ret_X0( 0x04, MirrorStack, IX0, IX1, IX2);
registerSvc_ret_X0( 0x05, UnmapMemory, IX0, IX1, IX2);
registerSvc_ret_X0_X1( 0x06, QueryMemory, IX0, IX1, IX2);
registerSvc( 0x07, ExitProcess);
registerSvc_ret_X0_X1( 0x08, CreateThread, IX1, IX2, IX3, IX4, IX5);
registerSvc_ret_X0( 0x09, StartThread, (ghandle) IX0);
registerSvc( 0x0A, ExitThread);
registerSvc_ret_X0( 0x0B, SleepThread, IX0);
registerSvc_ret_X0_X1( 0x0C, GetThreadPriority, (ghandle) IX0);
registerSvc_ret_X0( 0x0D, SetThreadPriority, (ghandle) IX0, IX1);
registerSvc_ret_X0_X1_X2( 0x0E, GetThreadCoreMask, IX0);
registerSvc_ret_X0( 0x0F, SetThreadCoreMask, IX0);
registerSvc_ret_X0( 0x10, GetCurrentProcessorNumber, IX0);
registerSvc_ret_X0( 0x11, SignalEvent, (ghandle) IX0);
registerSvc_ret_X0( 0x12, ClearEvent, (ghandle) IX0);
registerSvc_ret_X0( 0x13, MapMemoryBlock, (ghandle) IX0, IX1, IX2, IX3);
registerSvc_ret_X0_X1( 0x15, CreateTransferMemory, IX0, IX1, IX2);
registerSvc_ret_X0( 0x16, CloseHandle, (ghandle) IX0);
registerSvc_ret_X0( 0x17, ResetSignal, (ghandle) IX0);
registerSvc_ret_X0_X1( 0x18, WaitSynchronization, IX1, IX2, IX3);
registerSvc_ret_X0( 0x19, CancelSynchronization, (ghandle) IX0);
registerSvc_ret_X0( 0x1A, LockMutex, (ghandle) IX0, IX1, (ghandle) IX2);
registerSvc( 0x1B, UnlockMutex, IX0);
registerSvc( 0x1C, WaitProcessWideKeyAtomic, IX0, IX1, (ghandle) IX2, IX3);
registerSvc_ret_X0( 0x1D, SignalProcessWideKey, IX0, IX1);
registerSvc_ret_X0_X1( 0x1F, ConnectToPort, IX1);
registerSvc_ret_X0( 0x21, SendSyncRequest, (ghandle) IX0);
registerSvc_ret_X0( 0x22, SendSyncRequestEx, IX0, IX1, (ghandle) IX2);
registerSvc_ret_X0_X1( 0x24, GetProcessID, (ghandle) IX1);
registerSvc_ret_X0_X1( 0x25, GetThreadId);
registerSvc_ret_X0( 0x26, Break, IX0, IX1, IX2);
registerSvc_ret_X0( 0x27, OutputDebugString, IX0, IX1);
registerSvc_ret_X0_X1( 0x29, GetInfo, IX1, (ghandle) IX2, IX3);
registerSvc_ret_X0_X1_X2( 0x40, CreateSession, (ghandle) IX0, (ghandle) IX1, IX2);
registerSvc_ret_X0_X1( 0x41, AcceptSession, (ghandle) IX1);
registerSvc_ret_X0_X1( 0x43, ReplyAndReceive, IX1, IX2, (ghandle) IX3, IX4);
registerSvc_ret_X0_X1_X2( 0x45, CreateEvent, (ghandle) IX0, (ghandle) IX1, IX2);
registerSvc_ret_X0_X1( 0x4E, ReadWriteRegister, IX1, IX2, IX3);
registerSvc_ret_X0_X1( 0x50, CreateMemoryBlock, IX1, IX2);
registerSvc_ret_X0( 0x51, MapTransferMemory, (ghandle) IX0, IX1, IX2, IX3);
registerSvc_ret_X0( 0x52, UnmapTransferMemory, (ghandle) IX0, IX1, IX2);
registerSvc_ret_X0_X1( 0x53, CreateInterruptEvent, IX1);
registerSvc_ret_X0_X1( 0x55, QueryIoMapping, IX1, IX2);
registerSvc_ret_X0_X1( 0x56, CreateDeviceAddressSpace, IX1, IX2);
registerSvc_ret_X0_X1( 0x57, AttachDeviceAddressSpace, (ghandle) IX0, IX1, IX2);
registerSvc_ret_X0_X1( 0x59, MapDeviceAddressSpaceByForce, (ghandle) IX0, (ghandle) IX1, IX2, IX3, IX4, IX5);
registerSvc_ret_X0( 0x5c, UnmapDeviceAddressSpace, IX0, (ghandle) IX1, IX2, IX3);
registerSvc_ret_X0( 0x74, MapProcessMemory, IX0, (ghandle) IX1, IX2, IX3);
registerSvc_ret_X0( 0x75, UnmapProcessMemory, IX0, (ghandle) IX1, IX2, IX3);
registerSvc_ret_X0( 0x77, MapProcessCodeMemory, (ghandle) IX0, IX1, IX2, IX3);
registerSvc_ret_X0( 0x78, UnmapProcessCodeMemory, (ghandle) IX0, IX1, IX2, IX3);
}
tuple<guint, guint> Svc::SetHeapSize(guint size) {
LOG_DEBUG(Svc[0x01], "SetHeapSize 0x" LONGFMT, size);
ctu->heapsize = size;
ctu->cpu.map(0xaa0000000, size);
return make_tuple(0, 0xaa0000000);
}
guint Svc::SetMemoryAttribute(gptr addr, guint size, guint state0, guint state1) {
LOG_DEBUG(Svc[0x03], "SetMemoryAttribute 0x" ADDRFMT " 0x" LONGFMT " -> 0x" LONGFMT " 0x" LONGFMT, addr, size, state0, state1);
return 0;
}
guint Svc::MirrorStack(gptr dest, gptr src, guint size) {
LOG_DEBUG(Svc[0x04], "MirrorStack 0x" ADDRFMT " 0x" ADDRFMT " - 0x" LONGFMT, dest, src, size);
ctu->cpu.map(dest, size);
auto temp = new uint8_t[size];
ctu->cpu.readmem(src, temp, size);
ctu->cpu.writemem(dest, temp, size);
delete[] temp;
return 0;
}
guint Svc::UnmapMemory(gptr dest, gptr src, guint size) {
LOG_DEBUG(Svc[0x05], "UnmapMemory 0x" ADDRFMT " 0x" ADDRFMT " - 0x" LONGFMT, dest, src, size);
return 0;
}
typedef struct {
gptr begin;
gptr size;
gptr perms;
guint cperm;
} MemInfo;
tuple<guint, guint> Svc::QueryMemory(gptr meminfo, gptr pageinfo, gptr addr) {
LOG_DEBUG(Svc[0x06], "QueryMemory 0x" ADDRFMT, addr);
for(auto [begin, end, perm] : ctu->cpu.regions()) {
if(begin <= addr && addr <= end) {
LOG_DEBUG(Svc[0x06], "Found region at 0x" ADDRFMT "-0x" ADDRFMT, begin, end);
MemInfo minfo;
minfo.begin = begin;
minfo.size = end - begin + 1;
minfo.perms = perm == -1 ? 0 : 3; // FREE or CODE
minfo.cperm = 0;
if(perm != -1) {
auto offset = *ctu->cpu.guestptr<uint32_t>(begin + 4);
if(begin + offset + 4 < end && *ctu->cpu.guestptr<uint32_t>(begin + offset) == FOURCC('M', 'O', 'D', '0'))
minfo.cperm = 5;
else
minfo.cperm = 3;
}
ctu->cpu.guestptr<MemInfo>(meminfo) = minfo;
break;
}
}
return make_tuple(0, 0);
}
void Svc::ExitProcess() {
LOG_DEBUG(Svc[0x07], "ExitProcess");
exit(0);
}
tuple<guint, guint> Svc::CreateThread(guint pc, guint x0, guint sp, guint prio, guint proc) {
LOG_DEBUG(Svc[0x08], "CreateThread pc=0x" LONGFMT " X0=0x" LONGFMT " SP=0x" LONGFMT, pc, x0, sp);
auto thread = ctu->tm.create(pc, sp);
thread->regs.X0 = x0;
return make_tuple(0, thread->handle);
}
guint Svc::StartThread(ghandle handle) {
LOG_DEBUG(Svc[0x09], "StartThread 0x%x", handle);
auto thread = ctu->getHandle<Thread>(handle);
thread->resume();
return 0;
}
void Svc::ExitThread() {
LOG_DEBUG(Svc[0x0A], "ExitThread");
ctu->tm.current()->terminate();
}
guint Svc::SleepThread(guint ns) {
LOG_DEBUG(Svc[0x0B], "SleepThread 0x" LONGFMT " ns", ns);
return 0;
}
tuple<guint, guint> Svc::GetThreadPriority(ghandle handle) {
LOG_DEBUG(Svc[0x0C], "GetThreadPriority 0x%x", handle);
return make_tuple(0, 0);
}
guint Svc::SetThreadPriority(ghandle handle, guint priority) {
LOG_DEBUG(Svc[0x0D], "SetThreadPriority 0x%x -> 0x" LONGFMT, handle, priority);
return 0;
}
tuple<guint, guint, guint> Svc::GetThreadCoreMask(guint tmp) {
LOG_DEBUG(Svc[0x0E], "GetThreadCoreMask");
return make_tuple(0, 0xFF, 0xFF);
}
guint Svc::SetThreadCoreMask(guint tmp) {
LOG_DEBUG(Svc[0x0F], "GetThreadCoreMask");
return 0;
}
guint Svc::GetCurrentProcessorNumber(guint tmp) {
LOG_DEBUG(Svc[0x10], "GetCurrentProcessorNumber");
return 0;
}
guint Svc::SignalEvent(ghandle handle) {
LOG_DEBUG(Svc[0x11], "SignalEvent 0x%x", handle);
return 0;
}
guint Svc::ClearEvent(ghandle handle) {
LOG_DEBUG(Svc[0x12], "ClearEvent 0x%x", handle);
return 0;
}
guint Svc::MapMemoryBlock(ghandle handle, gptr addr, guint size, guint perm) {
LOG_DEBUG(Svc[0x13], "MapMemoryBlock 0x%x 0x" LONGFMT " 0x" LONGFMT " 0x" LONGFMT, handle, addr, size, perm);
auto obj = ctu->getHandle<MemoryBlock>(handle);
assert(obj->size == size);
assert(obj->addr == 0);
ctu->cpu.map(addr, size);
obj->addr = addr;
return 0;
}
tuple<guint, guint> Svc::CreateTransferMemory(gptr addr, guint size, guint perm) {
LOG_DEBUG(Svc[0x15], "CreateTransferMemory 0x" LONGFMT " 0x" LONGFMT " 0x" LONGFMT, addr, size, perm);
return make_tuple(0, 0);
}
guint Svc::CloseHandle(ghandle handle) {
LOG_DEBUG(Svc[0x16], "CloseHandle 0x%x", handle);
ctu->deleteHandle(handle);
return 0;
}
guint Svc::ResetSignal(ghandle handle) {
LOG_DEBUG(Svc[0x17], "ResetSignal 0x%x", handle);
return 0;
}
tuple<guint, guint> Svc::WaitSynchronization(gptr handles, guint numHandles, guint timeout) {
LOG_DEBUG(Svc[0x18], "WaitSynchronization %u", (uint) numHandles);
auto thread = ctu->tm.current();
thread->suspend([=] {
auto triggered = make_shared<bool>(false);
auto cb = [=](int i, bool canceled) {
if(*triggered)
return -1;
*triggered = true;
thread->resume([=] {
if(canceled) {
thread->regs.X0 = 0xec01;
thread->regs.X1 = 0;
} else {
thread->regs.X0 = 0;
thread->regs.X1 = i;
}
});
return 1;
};
auto hdls = ctu->cpu.guestptr<ghandle>(handles);
for(auto i = 0; i < numHandles; ++i) {
auto hnd = ctu->getHandle<Waitable>(hdls[i]);
auto port = dynamic_pointer_cast<NPort>(hnd);
if(port != nullptr)
LOG_DEBUG(Svc[0x18], "Waiting on port %s", port->name.c_str());
hnd->wait([=](auto canceled) { return cb(i, canceled); });
}
});
return make_tuple(0, 0);
}
guint Svc::CancelSynchronization(ghandle handle) {
LOG_DEBUG(Svc[0x19], "CancelSynchronization 0x%x", handle);
return 0;
}
shared_ptr<Mutex> Svc::ensureMutex(gptr mutexAddr) {
if(mutexes.find(mutexAddr) == mutexes.end()) {
LOG_DEBUG(Svc, "Making new mutex for 0x" LONGFMT, mutexAddr);
mutexes[mutexAddr] = make_shared<Mutex>(ctu->cpu.guestptr<uint32_t>(mutexAddr));
}
return mutexes[mutexAddr];
}
guint Svc::LockMutex(ghandle curthread, gptr mutexAddr, ghandle reqthread) {
LOG_DEBUG(Svc[0x1A], "LockMutex 0x%x 0x" LONGFMT " 0x%x", curthread, mutexAddr, reqthread);
auto mutex = ensureMutex(mutexAddr);
auto owner = mutex->owner();
auto thread = ctu->getHandle<Thread>(reqthread);
if(owner != 0 && owner != reqthread) {
LOG_DEBUG(Svc[0x1A], "Could not get mutex lock. Waiting.");
mutex->hasWaiters(true);
thread->suspend([=] {
mutex->wait([=] {
if(mutex->owner() == 0) {
mutex->owner(reqthread);
thread->resume([=] {
thread->regs.X0 = 0;
});
return 1;
} else
return 0;
});
});
} else {
mutex->owner(reqthread);
if(!thread->active) {
thread->regs.X0 = 0;
thread->resume();
}
}
return 0;
}
void Svc::UnlockMutex(gptr mutexAddr) {
LOG_DEBUG(Svc[0x1B], "UnlockMutex 0x" ADDRFMT, mutexAddr);
auto mutex = ensureMutex(mutexAddr);
auto owner = mutex->owner();
assert(owner == 0 || owner == ctu->tm.current()->handle);
mutex->guestRelease();
}
shared_ptr<Semaphore> Svc::ensureSemaphore(gptr semaAddr) {
if(semaphores.find(semaAddr) == semaphores.end()) {
LOG_DEBUG(Svc, "Making new semaphore for 0x" LONGFMT, semaAddr);
semaphores[semaAddr] = make_shared<Semaphore>(ctu->cpu.guestptr<uint32_t>(semaAddr));
}
return semaphores[semaAddr];
}
void Svc::WaitProcessWideKeyAtomic(gptr mutexAddr, gptr semaAddr, ghandle threadHandle, guint timeout) {
LOG_DEBUG(Svc[0x1C], "WaitProcessWideKeyAtomic 0x" LONGFMT " 0x" LONGFMT " 0x%x 0x" LONGFMT, mutexAddr, semaAddr, threadHandle, timeout);
auto mutex = ensureMutex(mutexAddr);
auto semaphore = ensureSemaphore(semaAddr);
assert(mutex->owner() == threadHandle);
if(semaphore->value() > 0) {
semaphore->decrement();
return;
}
auto thread = ctu->getHandle<Thread>(threadHandle);
thread->suspend([=] {
semaphore->wait([=] {
semaphore->decrement();
thread->resume([=] {
LockMutex(0, mutexAddr, threadHandle);
});
return 1;
});
});
mutex->guestRelease();
}
guint Svc::SignalProcessWideKey(gptr semaAddr, guint target) {
LOG_DEBUG(Svc[0x1D], "SignalProcessWideKey 0x" LONGFMT " 0x" LONGFMT, semaAddr, target);
auto semaphore = ensureSemaphore(semaAddr);
semaphore->increment();
if(target == 1)
semaphore->signal(true);
else if(target == 0xffffffff)
semaphore->signal();
return 0;
}
tuple<guint, ghandle> Svc::ConnectToPort(guint name) {
LOG_DEBUG(Svc[0x1F], "ConnectToPort");
return make_tuple(0, ctu->ipc.ConnectToPort(ctu->cpu.readstring(name)));
}
guint Svc::SendSyncRequest(ghandle handle) {
LOG_DEBUG(Svc[0x21], "SendSyncRequest 0x%x", handle);
auto thread = ctu->tm.current();
auto hnd = ctu->getHandle<IPipe>(handle);
auto buf = make_shared<array<uint8_t, 0x100>>();
ctu->cpu.readmem(thread->tlsBase, buf->data(), 0x100);
if(hnd->isAsync()) {
thread->suspend([=] {
hnd->messageAsync(buf, [=](auto res, auto close) {
ctu->cpu.writemem(thread->tlsBase, buf->data(), 0x100);
if(close)
ctu->deleteHandle(handle);
thread->resume([=] {
thread->regs.X0 = res;
});
});
});
return 0;
} else {
auto close = false;
auto ret = hnd->messageSync(buf, close);
if(close)
ctu->deleteHandle(handle);
ctu->cpu.writemem(thread->tlsBase, buf->data(), 0x100);
return ret;
}
}
guint Svc::SendSyncRequestEx(gptr buf, guint size, ghandle handle) {
LOG_ERROR(Svc[0x22], "SendSyncRequestEx not implemented");
return 0xf601;
}
tuple<guint, guint> Svc::GetProcessID(ghandle handle) {
LOG_DEBUG(Svc[0x24], "GetProcessID 0x%x", handle);
return make_tuple(0, 0);
}
tuple<guint, guint> Svc::GetThreadId() {
LOG_DEBUG(Svc[0x25], "GetThreadId");
return make_tuple(0, ctu->tm.current()->id);
}
guint Svc::Break(guint X0, guint X1, guint info) {
LOG_DEBUG(Svc[0x26], "Break");
exit(1);
}
guint Svc::OutputDebugString(guint ptr, guint size) {
LOG_DEBUG(Svc[0x27], "OutputDebugString");
char *debugStr = (char*) malloc(size + 1);
memset(debugStr, 0, size+1);
ctu->cpu.readmem(ptr, debugStr, size);
LOG_DEBUG(Svc[0x27], "Debug String: %s", debugStr);
free(debugStr);
return 0;
}
#define matchone(a, v) do { if(id1 == (a)) return make_tuple(0, (v)); } while(0)
#define matchpair(a, b, v) do { if(id1 == (a) && id2 == (b)) return make_tuple(0, (v)); } while(0)
tuple<guint, guint> Svc::GetInfo(guint id1, ghandle handle, guint id2) {
LOG_DEBUG(Svc[0x29], "GetInfo handle=0x%x id1=0x" LONGFMT " id2=" LONGFMT, handle, id1, id2);
matchpair(0, 0, 0xF);
matchpair(1, 0, 0xFFFFFFFF00000000);
matchpair(2, 0, 0x7100000000);
matchpair(3, 0, 0x1000000000);
matchpair(4, 0, 0xaa0000000);
matchpair(5, 0, ctu->heapsize); // Heap region size
matchpair(6, 0, 0x100000);
matchpair(7, 0, 0x10000);
matchpair(12, 0, 0x8000000);
matchpair(13, 0, 0x7ff8000000);
matchpair(14, 0, ctu->loadbase);
matchpair(15, 0, ctu->loadsize);
matchpair(18, 0, 0x0100000000000036); // Title ID
matchone(11, 0);
LOG_ERROR(Svc[0x29], "Unknown getinfo");
}
tuple<guint, guint, guint> Svc::CreateSession(ghandle clientOut, ghandle serverOut, guint unk) {
LOG_DEBUG(Svc[0x40], "CreateSession");
auto pipe = make_shared<NPipe>(ctu, "Session");
return make_tuple(0, ctu->newHandle(pipe), ctu->newHandle(pipe));
}
tuple<guint, guint> Svc::AcceptSession(ghandle hnd) {
LOG_DEBUG(Svc[0x41], "AcceptSession 0x%x", hnd);
auto pipe = ctu->getHandle<NPort>(hnd)->accept();
return make_tuple(0, ctu->newHandle(pipe));
}
tuple<guint, guint> Svc::ReplyAndReceive(gptr handles, guint numHandles, ghandle replySession, guint timeout) {
LOG_DEBUG(Svc[0x43], "ReplyAndReceive");
auto thread = ctu->tm.current();
if(replySession != 0) {
LOG_DEBUG(Svc[0x43], "Sending message back from native service");
auto hnd = ctu->getHandle<NPipe>(replySession);
auto buf = make_shared<array<uint8_t, 0x100>>();
ctu->cpu.readmem(thread->tlsBase, buf->data(), 0x100);
hnd->client.push(buf);
hnd->client.signal(true);
}
if(numHandles == 0)
return make_tuple(0xf601, 0);
assert(numHandles == 1);
auto handle = *ctu->cpu.guestptr<ghandle>(handles);
auto hnd = ctu->getHandle<NPipe>(handle);
auto buf = hnd->server.pop();
if(buf == nullptr) {
ctu->deleteHandle(handle);
return make_tuple(0xf601, 0);
}
LOG_DEBUG(Svc[0x43], "Got message for native service");
hexdump(buf);
ctu->cpu.writemem(thread->tlsBase, buf->data(), 0x100);
return make_tuple(0, 0);
}
tuple<guint, guint, guint> Svc::CreateEvent(ghandle clientOut, ghandle serverOut, guint unk) {
LOG_DEBUG(Svc[0x45], "CreateEvent");
return make_tuple(0, 0, 0);
}
tuple<guint, guint> Svc::ReadWriteRegister(guint reg, guint rwm, guint val) {
LOG_DEBUG(Svc[0x4E], "ReadWriteRegister");
return make_tuple(0, 0);
}
tuple<guint, guint> Svc::CreateMemoryBlock(guint size, guint perm) {
LOG_DEBUG(Svc[0x50], "CreateMemoryBlock");
return make_tuple(0, ctu->newHandle(make_shared<MemoryBlock>(size, perm)));
}
guint Svc::MapTransferMemory(ghandle handle, gptr addr, guint size, guint perm) {
LOG_DEBUG(Svc[0x51], "MapTransferMemory");
ctu->cpu.map(addr, size);
return 0;
}
guint Svc::UnmapTransferMemory(ghandle handle, gptr addr, guint size) {
LOG_DEBUG(Svc[0x52], "UnmapTransferMemory");
ctu->cpu.unmap(addr, size);
return 0;
}
tuple<guint, guint> Svc::CreateInterruptEvent(guint irq) {
LOG_DEBUG(Svc[0x53], "CreateInterruptEvent");
return make_tuple(0, 0);
}
tuple<guint, guint> Svc::QueryIoMapping(gptr physaddr, guint size) {
LOG_DEBUG(Svc[0x55], "QueryIoMapping");
gptr addr = ctu->mmiohandler.getVirtualAddressFromAddr(physaddr);
if(addr == 0x0) { // force exit for now
cout << "!Unknown physical address!" << endl;
exit(1);
}
return make_tuple(0x0, addr);
}
tuple<guint, guint> Svc::CreateDeviceAddressSpace(guint base, guint size) {
LOG_DEBUG(Svc[0x56], "CreateDeviceAddressSpace");
return make_tuple(0, 0);
}
tuple<guint, guint> Svc::AttachDeviceAddressSpace(ghandle handle, guint dev, gptr addr) {
LOG_DEBUG(Svc[0x57], "AttachDeviceAddressSpace");
return make_tuple(0, 0);
}
tuple<guint, guint> Svc::MapDeviceAddressSpaceByForce(ghandle handle, ghandle phandle, gptr paddr, guint size, gptr maddr, guint perm) {
LOG_DEBUG(Svc[0x59], "MapDeviceAddressSpaceByForce");
return make_tuple(0, 0);
}
guint Svc::UnmapDeviceAddressSpace(guint unk0, ghandle phandle, gptr maddr, guint size) {
LOG_DEBUG(Svc[0x5c], "UnmapDeviceAddressSpace");
return 0;
}
guint Svc::MapProcessMemory(gptr dstaddr, ghandle handle, gptr srcaddr, guint size) {
LOG_DEBUG(Svc[0x74], "MapProcessMemory");
ctu->cpu.map(dstaddr, size);
char *mem = (char *) malloc(size);
memset(mem, 0, size);
ctu->cpu.readmem(srcaddr, mem, size);
ctu->cpu.writemem(dstaddr, mem, size);
free(mem);
return 0;
}
guint Svc::UnmapProcessMemory(gptr dstaddr, ghandle handle, gptr srcaddr, guint size) {
LOG_DEBUG(Svc[0x75], "UnmapProcessMemory");
ctu->cpu.unmap(dstaddr, size);
return 0;
}
guint Svc::MapProcessCodeMemory(ghandle handle, gptr dstaddr, gptr srcaddr, guint size) {
LOG_DEBUG(Svc[0x77], "MapProcessCodeMemory");
ctu->cpu.map(dstaddr, size);
return 0;
}
guint Svc::UnmapProcessCodeMemory(ghandle handle, gptr dstaddr, gptr srcaddr, guint size) {
LOG_DEBUG(Svc[0x78], "UnmapProcessCodeMemory");
ctu->cpu.unmap(dstaddr, size);
return 0;
}

78
Svc.h Normal file
View file

@ -0,0 +1,78 @@
#pragma once
#include "Ctu.h"
class MemoryBlock : public KObject {
public:
MemoryBlock(guint _size, guint _perms) : size(_size), perms(_perms), addr(0) {}
guint size, perms;
gptr addr;
};
class Svc {
public:
Svc(Ctu *ctu);
private:
Ctu *ctu;
unordered_map<gptr, shared_ptr<Semaphore>> semaphores;
unordered_map<gptr, shared_ptr<Mutex>> mutexes;
shared_ptr<Mutex> ensureMutex(gptr mutexAddr);
shared_ptr<Semaphore> ensureSemaphore(gptr semaAddr);
tuple<guint, guint> SetHeapSize(guint size); // 0x01
guint SetMemoryAttribute(gptr addr, guint size, guint state0, guint state1); // 0x03
guint MirrorStack(gptr dest, gptr src, guint size); // 0x04
guint UnmapMemory(gptr dest, gptr src, guint size); // 0x05
tuple<guint, guint> QueryMemory(gptr meminfo, gptr pageinfo, gptr addr); // 0x06
void ExitProcess(); // 0x07
tuple<guint, guint> CreateThread(guint pc, guint x0, guint sp, guint prio, guint proc); // 0x08
guint StartThread(ghandle handle); // 0x09
void ExitThread(); // 0x0A
guint SleepThread(guint ns); // 0x0B
tuple<guint, guint> GetThreadPriority(ghandle handle); // 0x0C
guint SetThreadPriority(ghandle handle, guint priority); // 0x0D
tuple<guint, guint, guint> GetThreadCoreMask(guint); // 0x0E
guint SetThreadCoreMask(guint); // 0x0F
guint GetCurrentProcessorNumber(guint); // 0x10
guint SignalEvent(ghandle handle); // 0x11
guint ClearEvent(ghandle handle); // 0x12
guint MapMemoryBlock(ghandle handle, gptr addr, guint size, guint perm); // 0x13
tuple<guint, guint> CreateTransferMemory(gptr addr, guint size, guint perm); // 0x15
guint CloseHandle(ghandle handle); // 0x16
guint ResetSignal(ghandle handle); // 0x17
tuple<guint, guint> WaitSynchronization(gptr handles, guint numHandles, guint timeout); // 0x18
guint CancelSynchronization(ghandle handle); // 0x19
guint LockMutex(ghandle curthread, gptr mutexAddr, ghandle reqthread); // 0x1A
void UnlockMutex(gptr mutexAddr); // 0x1B
void WaitProcessWideKeyAtomic(gptr mutexAddr, gptr semaAddr, ghandle threadHandle, guint timeout); // 0x1C
guint SignalProcessWideKey(gptr semaAddr, guint target); // 0x1D
tuple<guint, ghandle> ConnectToPort(guint name); // 0x1F
guint SendSyncRequest(ghandle handle); // 0x21
guint SendSyncRequestEx(gptr buf, guint size, ghandle handle); // 0x22
tuple<guint, guint> GetProcessID(ghandle handle); // 0x24
tuple<guint, guint> GetThreadId(); // 0x25
guint Break(guint X0, guint X1, guint info); // 0x26
guint OutputDebugString(guint ptr, guint size); // 0x27
tuple<guint, guint> GetInfo(guint id1, ghandle handle, guint id2); // 0x29
tuple<guint, guint, guint> CreateSession(ghandle clientOut, ghandle serverOut, guint unk); // 0x40
tuple<guint, guint> AcceptSession(ghandle port); // 0x41
tuple<guint, guint> ReplyAndReceive(gptr handles, guint numHandles, ghandle replySession, guint timeout); // 0x43
tuple<guint, guint, guint> CreateEvent(ghandle clientOut, ghandle serverOut, guint unk); // 0x45
tuple<guint, guint> ReadWriteRegister(guint reg, guint rwm, guint val); // 0x4E
tuple<guint, guint> CreateMemoryBlock(guint size, guint perm); // 0x50
guint MapTransferMemory(ghandle handle, gptr addr, guint size, guint perm); // 0x51
guint UnmapTransferMemory(ghandle handle, gptr addr, guint size); // 0x52
tuple<guint, guint> CreateInterruptEvent(guint irq); // 0x53
tuple<guint, guint> QueryIoMapping(gptr physaddr, guint size); // 0x55
tuple<guint, guint> CreateDeviceAddressSpace(guint base, guint size); // 0x56
tuple<guint, guint> AttachDeviceAddressSpace(ghandle handle, guint dev, gptr addr); // 0x57
tuple<guint, guint> MapDeviceAddressSpaceByForce(ghandle handle, ghandle phandle, gptr paddr, guint size, gptr maddr, guint perm); // 0x59
guint UnmapDeviceAddressSpace(guint unk0, ghandle phandle, gptr maddr, guint size); // 0x5c
guint MapProcessMemory(gptr dstaddr, ghandle handle, gptr srcaddr, guint size); // 0x74
guint UnmapProcessMemory(gptr dstaddr, ghandle handle, gptr srcaddr, guint size); // 0x75
guint MapProcessCodeMemory(ghandle handle, gptr dstaddr, gptr srcaddr, guint size); // 0x77
guint UnmapProcessCodeMemory(ghandle handle, gptr dstaddr, gptr srcaddr, guint size); // 0x78
};

105
Sync.cpp Normal file
View file

@ -0,0 +1,105 @@
#include "Ctu.h"
Waitable::Waitable() : presignaled(false), canceled(false) {
}
void Waitable::acquire() {
lock.lock();
}
void Waitable::release() {
lock.unlock();
}
void Waitable::wait(function<int()> cb) {
wait([cb](auto _) { return cb(); });
}
void Waitable::wait(function<int(bool)> cb) {
acquire();
if(!presignaled || (presignaled && cb(canceled) == 0))
waiters.push_back(cb);
presignaled = false;
canceled = false;
release();
}
void Waitable::signal(bool one) {
acquire();
if(waiters.size() == 0 && presignalable())
presignaled = true;
else {
auto realhit = false;
for(auto iter = waiters.begin(); iter != waiters.end();) {
auto res = (*iter)(canceled);
if(res != 0) {
iter = waiters.erase(iter);
} else {
iter++;
}
if(res != -1) {
realhit = true;
if(one)
break;
}
}
if(!realhit && presignalable())
presignaled = true;
}
release();
}
void Waitable::cancel() {
acquire();
canceled = true;
signal();
release();
}
Semaphore::Semaphore(Guest<uint32_t> _vptr) : vptr(_vptr) {
}
void Semaphore::increment() {
acquire();
vptr = *vptr + 1;
release();
}
void Semaphore::decrement() {
acquire();
vptr = *vptr - 1;
release();
}
uint32_t Semaphore::value() {
return *vptr;
}
Mutex::Mutex(Guest<uint32_t> _vptr) : vptr(_vptr) {
}
uint32_t Mutex::value() {
return *vptr;
}
void Mutex::value(uint32_t val) {
vptr = val;
}
ghandle Mutex::owner() {
return value() & 0xBFFFFFFF;
}
void Mutex::owner(ghandle val) {
value((value() & 0x40000000) | val);
}
bool Mutex::hasWaiters() {
return (value() >> 28) != 0;
}
void Mutex::hasWaiters(bool val) {
value((value() & 0xBFFFFFFF) | ((int) val << 30));
}
void Mutex::guestRelease() {
owner(0);
signal();
}

65
Sync.h Normal file
View file

@ -0,0 +1,65 @@
#pragma once
#include "Ctu.h"
class Waitable : public KObject {
public:
Waitable();
void acquire();
void release();
void wait(function<int()> cb);
void wait(function<int(bool)> cb);
void signal(bool one=false);
void cancel();
virtual void close() override {}
protected:
virtual bool presignalable() { return true; }
private:
recursive_mutex lock;
list<function<int(bool)>> waiters;
bool presignaled, canceled;
};
class Semaphore : public Waitable {
public:
Semaphore(Guest<uint32_t> _vptr);
void increment();
void decrement();
uint32_t value();
protected:
bool presignalable() override { return false; }
private:
Guest<uint32_t> vptr;
};
class Mutex : public Waitable {
public:
Mutex(Guest<uint32_t> _vptr);
uint32_t value();
void value(uint32_t val);
ghandle owner();
void owner(ghandle val);
bool hasWaiters();
void hasWaiters(bool val);
void guestRelease();
protected:
bool presignalable() override { return false; }
private:
Guest<uint32_t> vptr;
};

265
ThreadManager.cpp Normal file
View file

@ -0,0 +1,265 @@
#include "Ctu.h"
#define INSN_PER_SLICE 1000000
Thread::Thread(Ctu *_ctu, int _id) : ctu(_ctu), id(_id) {
active = false;
memset(&regs, 0, sizeof(ThreadRegisters));
auto tlsSize = 1024 * 1024;
tlsBase = (1 << 24) + tlsSize * _id;
ctu->cpu.map(tlsBase, tlsSize);
ctu->cpu.guestptr<gptr>(tlsBase + 0x1F8) = tlsBase + 0x200;
}
void Thread::assignHandle(uint32_t _handle) {
handle = _handle;
ctu->cpu.guestptr<uint32_t>(tlsBase + 0x3B8) = _handle;
}
void Thread::terminate() {
ctu->tm.terminate(id);
}
void Thread::suspend(function<void()> cb) {
if(!active)
return;
active = false;
if(id == ctu->tm.current()->id) {
freeze();
if(cb != nullptr)
cb();
ctu->tm.next(true);
} else if(cb != nullptr)
cb();
}
void Thread::resume(function<void()> cb) {
if(active)
return;
if(cb != nullptr)
onWake(cb);
active = true;
ctu->tm.enqueue(id);
}
void Thread::freeze() {
ctu->cpu.storeRegs(regs);
LOG_DEBUG(Thread, "Froze thread 0x%x with PC 0x" ADDRFMT, id, regs.PC);
}
void Thread::thaw() {
ctu->cpu.tlsBase(tlsBase);
if(wakeCallbacks.size()) {
for(auto cb : wakeCallbacks)
cb();
wakeCallbacks.clear();
}
ctu->cpu.loadRegs(regs);
LOG_DEBUG(Thread, "Thawed thread 0x%x with PC 0x" ADDRFMT, id, regs.PC);
}
void Thread::onWake(function<void()> cb) {
wakeCallbacks.push_back(cb);
}
NativeThread::NativeThread(Ctu *_ctu, function<void()> _runner, int _id) : ctu(_ctu), runner(_runner), id(_id), active(false) {
}
void NativeThread::terminate() {
ctu->tm.terminate(id);
}
void NativeThread::suspend() {
active = false;
}
void NativeThread::resume() {
if(active)
return;
active = true;
ctu->tm.enqueue(id);
}
void NativeThread::run() {
runner();
}
ThreadManager::ThreadManager(Ctu *_ctu) : ctu(_ctu) {
threadId = 0;
_current = nullptr;
_last = nullptr;
switched = false;
first = true;
wasNativeLast = false;
}
void ThreadManager::start() {
next();
while(true) {
if(ctu->gdbStub.enabled) {
ctu->gdbStub.handlePacket();
if(ctu->gdbStub.haltLoop && !ctu->gdbStub.stepLoop)
continue;
auto wasStep = ctu->gdbStub.stepLoop;
ctu->gdbStub.haltLoop = ctu->gdbStub.stepLoop = false;
if(_current == nullptr) {
next();
continue;
}
ctu->cpu.exec(ctu->gdbStub.stepLoop ? 1 : INSN_PER_SLICE);
if(wasStep) {
ctu->gdbStub._break();
ctu->gdbStub.haltLoop = ctu->gdbStub.stepLoop = false;
}
} else {
next();
ctu->cpu.exec(INSN_PER_SLICE);
}
}
}
shared_ptr<Thread> ThreadManager::create(gptr pc, gptr sp) {
auto thread = make_shared<Thread>(ctu, ++threadId);
thread->assignHandle(ctu->newHandle(thread));
threads[thread->id] = thread;
thread->regs.PC = pc;
thread->regs.SP = sp;
thread->regs.X30 = TERMADDR;
return thread;
}
shared_ptr<NativeThread> ThreadManager::createNative(function<void()> runner) {
auto thread = make_shared<NativeThread>(ctu, runner, ++threadId);
nativeThreads[thread->id] = thread;
return thread;
}
void ThreadManager::requeue() {
if(_current == nullptr)
return;
_current->freeze();
running.push_back(_current);
_last = _current;
_current = nullptr;
}
void ThreadManager::enqueue(int id) {
if(isNative(id)) {
for(auto other : runningNative)
if(other->id == id)
return;
runningNative.push_back(nativeThreads[id]);
return;
}
if(threads.find(id) == threads.end())
return;
enqueue(threads[id]);
}
void ThreadManager::enqueue(shared_ptr<Thread> thread) {
for(auto other : running)
if(other->id == thread->id)
return;
running.push_back(thread);
}
void ThreadManager::tryRunNative() {
if(wasNativeLast)
wasNativeLast = false;
else {
while(runningNative.size() > 0) {
auto native = runningNative.front();
runningNative.pop_front();
if(!native->active)
continue;
native->run();
if(native->active)
runningNative.push_back(native);
break;
}
wasNativeLast = true;
}
}
void ThreadManager::next(bool force) {
tryRunNative();
if(force) {
if(_current != nullptr)
_last = _current;
_current = nullptr;
}
shared_ptr<Thread> nt = nullptr;
while(true) {
if(running.size() == 0) {
tryRunNative();
if(ctu->gdbStub.enabled) {
if(ctu->gdbStub.haltLoop) {
ctu->cpu.stop();
return;
}
ctu->gdbStub.handlePacket();
if(ctu->gdbStub.haltLoop) {
ctu->cpu.stop();
return;
}
}
if(_current == nullptr) {
if(first) {
LOG_DEBUG(Thread, "No threads left to run!");
ctu->bridge.start();
}
first = false;
} else
return;
usleep(50);
continue;
}
nt = running.front();
running.pop_front();
if(nt->active)
break;
}
switched = true;
if(_current != nullptr) {
_current->freeze();
if(_current->active)
enqueue(_current);
_last = _current;
}
nt->thaw();
_current = nt;
}
void ThreadManager::terminate(int id) {
if(threads.find(id) == threads.end()) {
if(isNative(id)) {
nativeThreads[id]->active = false;
nativeThreads.erase(id);
}
return;
}
threads[id]->active = false;
threads.erase(id);
next(true);
}
shared_ptr<Thread> ThreadManager::current() {
return _current;
}
shared_ptr<Thread> ThreadManager::last() {
return _last;
}
bool ThreadManager::isNative(int id) {
return nativeThreads.find(id) != nativeThreads.end();
}

106
ThreadManager.h Normal file
View file

@ -0,0 +1,106 @@
#pragma once
#include "Ctu.h"
typedef struct {
float64_t low;
float64_t high;
} float128;
typedef struct {
guint SP, PC, NZCV;
union {
struct {
guint gprs[31];
float128 fprs[32];
};
struct {
guint X0, X1, X2, X3,
X4, X5, X6, X7,
X8, X9, X10, X11,
X12, X13, X14, X15,
X16, X17, X18, X19,
X20, X21, X22, X23,
X24, X25, X26, X27,
X28, X29, X30;
float128 Q0, Q1, Q2, Q3,
Q4, Q5, Q6, Q7,
Q8, Q9, Q10, Q11,
Q12, Q13, Q14, Q15,
Q16, Q17, Q18, Q19,
Q20, Q21, Q22, Q23,
Q24, Q25, Q26, Q27,
Q28, Q29, Q30, Q31;
};
};
} ThreadRegisters;
class Thread : public KObject {
public:
Thread(Ctu *_ctu, int _id);
void assignHandle(ghandle handle);
void terminate();
void suspend(function<void()> cb=nullptr);
void resume(function<void()> cb=nullptr);
void freeze();
void thaw();
void onWake(function<void()> cb);
int id;
ghandle handle;
bool active;
ThreadRegisters regs;
gptr tlsBase;
private:
Ctu *ctu;
list<function<void()>> wakeCallbacks;
};
class NativeThread {
public:
NativeThread(Ctu *_ctu, function<void()> _runner, int _id);
void terminate();
void suspend();
void resume();
void run();
int id;
bool active;
private:
Ctu *ctu;
function<void()> runner;
};
class ThreadManager {
public:
ThreadManager(Ctu *_ctu);
void start();
shared_ptr<Thread> create(gptr pc=0, gptr sp=0);
shared_ptr<NativeThread> createNative(function<void()> runner);
void requeue();
void enqueue(int id);
void enqueue(shared_ptr<Thread> thread);
void next(bool force=false);
void terminate(int id);
shared_ptr<Thread> current();
shared_ptr<Thread> last();
bool switched;
private:
void tryRunNative();
bool isNative(int id);
Ctu *ctu;
unordered_map<int, shared_ptr<Thread>> threads;
unordered_map<int, shared_ptr<NativeThread>> nativeThreads;
int threadId;
list<shared_ptr<Thread>> running;
list<shared_ptr<NativeThread>> runningNative;
shared_ptr<Thread> _current, _last;
bool wasNativeLast;
bool first;
};

2548
genallipc.py Normal file

File diff suppressed because it is too large Load diff

394
generateIpcStubs.py Normal file
View file

@ -0,0 +1,394 @@
import glob, hashlib, json, os, os.path, re, sys
from pprint import pprint
import idparser, partialparser
def emitInt(x):
return '0x%x' % x if x > 9 else str(x)
typemap = dict(
i8='int8_t',
i16='int16_t',
i32='int32_t',
i64='int64_t',
i128='int128_t',
u8='uint8_t',
u16='uint16_t',
u32='uint32_t',
u64='uint64_t',
u128='uint128_t',
f32='float32_t',
pid='gpid',
bool='bool',
)
typesizes = dict(
i8=1,
i16=2,
i32=4,
i64=8,
i128=16,
u8=1,
u16=2,
u32=4,
u64=8,
u128=16,
f32=4,
pid=8,
bool=1,
)
allTypes = None
def typeSize(type):
if type[0] in ('unknown', 'i8', 'u8'):
return 1
elif type[0] == 'bytes':
return type[1]
elif type[0] in allTypes:
return typeSize(allTypes[type[0]])
elif type[0] in typesizes:
return typesizes[type[0]]
return 1
def splitByNs(obj):
ons = {}
for type, x in obj.items():
ns = type.rsplit('::', 1)[0] if '::' in type else None
name = type.rsplit('::', 1)[-1]
if ns not in ons:
ons[ns] = {}
ons[ns][name] = x
return ons
def retype(spec, noIndex=False):
if spec[0] == 'unknown':
return 'uint8_t'
elif spec[0] == 'bytes':
return 'uint8_t%s' % ('[%s]' % emitInt(spec[1]) if not noIndex else ' *')
else:
return typemap[spec[0]] if spec[0] in typemap else spec[0];
def formatParam(param, input, i):
name, spec = param
if name is None:
name = '_%i' % i
hasSize = False
if spec[0] == 'align':
return formatParam((name, spec[2]), input, i)
if spec[0] == 'bytes':
type = 'uint8_t *'
elif spec[0] == 'unknown':
assert False
elif spec[0] == 'buffer':
type = '%s *' % retype(spec[1])
hasSize = True
elif spec[0] == 'array':
type = retype(spec[1]) + ' *'
hasSize = True
elif spec[0] == 'object':
type = 'shared_ptr<%s>' % spec[1][0]
elif spec[0] == 'KObject':
type = 'shared_ptr<KObject>'
else:
type = typemap[spec[0]] if spec[0] in typemap else spec[0]
if type.endswith(']'):
arrspec = type[type.index('['):]
type = type[:-len(arrspec)]
else:
arrspec = ''
return '%s %s%s %s%s%s' % ('IN' if input else 'OUT', type, '&' if not input and (not type.endswith('*') and not arrspec) else '', name, arrspec, ', guint %s_size' % name if hasSize else '')
def generatePrototype(func):
return ', '.join([formatParam(x, True, i) for i, x in enumerate(func['inputs'])] + [formatParam(x, False, i + len(func['inputs'])) for i, x in enumerate(func['outputs'])])
def isPointerType(type):
if type[0] in typesizes:
return False
elif type[0] == 'bytes':
return True
elif type[0] in allTypes:
return isPointerType(allTypes[type[0]])
return True
AFTER = 'AFTER'
def generateCaller(qname, fname, func):
def tempname():
tempI[0] += 1
return 'temp%i' % tempI[0]
params = []
logFmt, logElems = [], []
tempI = [0]
inpOffset = 8
bufOffs = {}
hndOff = 0
objOff = 0
bufSizes = 0
for name, elem in func['inputs']:
type, rest = elem[0], elem[1:]
if type in ('array', 'buffer'):
if rest[1] not in bufOffs:
bufOffs[rest[1]] = 0
cbo = bufOffs[rest[1]]
bufOffs[rest[1]] += 1
an, sn, bn = tempname(), tempname(), tempname()
yield 'guint %s;' % sn
yield 'auto %s = req.getBuffer(%s, %i, %s);' % (an, emitInt(rest[1]), cbo, sn)
yield 'auto %s = new uint8_t[%s];' % (bn, sn)
yield 'ctu->cpu.readmem(%s, %s, %s);' % (an, bn, sn)
params.append('(%s *) %s' % (retype(rest[0]), bn))
params.append(sn)
logFmt.append('%s *%s= buffer<0x" ADDRFMT ">' % (retype(rest[0]), '%s ' % name if name else ''))
logElems.append(sn)
bufSizes += 1
yield AFTER, 'delete[] %s;' % bn
elif type == 'object':
params.append('ctu->getHandle<%s>(req.getMoved(%i))' % (rest[0][0], objOff))
logFmt.append('%s %s= 0x%%x' % (rest[0][0], '%s ' % name if name else ''))
logElems.append('req.getMoved(%i)' % objOff)
objOff += 1
elif type == 'KObject':
params.append('ctu->getHandle<KObject>(req.getCopied(%i))' % hndOff)
logFmt.append('KObject %s= 0x%%x' % ('%s ' % name if name else ''))
logElems.append('req.getCopied(%i)' % objOff)
hndOff += 1
elif type == 'pid':
params.append('req.pid')
else:
if elem[0] == 'align':
alignment = elem[1]
elem = elem[2]
else:
alignment = min(8, typeSize(elem))
while inpOffset % alignment:
inpOffset += 1
if isPointerType(elem):
params.append('req.getDataPointer<%s>(%s)' % (retype(elem, noIndex=True), emitInt(inpOffset)))
logFmt.append('%s %s= %%s' % (retype(elem), '%s ' % name if name else ''))
logElems.append('bufferToString(req.getDataPointer<uint8_t *>(%s), %s).c_str()' % (emitInt(inpOffset), emitInt(typeSize(elem))))
else:
params.append('req.getData<%s>(%s)' % (retype(elem), emitInt(inpOffset)))
if typeSize(elem) == 16:
logFmt.append('%s %s= %%s' % (retype(elem), '%s ' % name if name else ''))
logElems.append('bufferToString(req.getDataPointer<uint8_t *>(%s), %s).c_str()' % (emitInt(inpOffset), emitInt(typeSize(elem))))
else:
type = retype(elem)
ct = '0x%x'
if type == 'float32_t':
ct = '%f'
elif typeSize(elem) == 8:
ct = '0x" ADDRFMT "'
logFmt.append('%s %s= %s' % (type, '%s ' % name if name else '', ct))
logElems.append('%sreq.getData<%s>(%s)' % ('(double) ' if type == 'float32_t' else '', type, emitInt(inpOffset)))
inpOffset += typeSize(elem)
outOffset = 8
hndOff = 0
objOff = 0
for _, elem in func['outputs']:
type, rest = elem[0], elem[1:]
if type in ('array', 'buffer'):
if rest[1] not in bufOffs:
bufOffs[rest[1]] = 0
cbo = bufOffs[rest[1]]
bufOffs[rest[1]] += 1
an, sn, bn = tempname(), tempname(), tempname()
yield 'guint %s;' % sn
yield 'auto %s = req.getBuffer(%s, %i, %s);' % (an, emitInt(rest[1]), cbo, sn)
yield 'auto %s = new uint8_t[%s];' % (bn, sn)
params.append('(%s *) %s' % (retype(rest[0]), bn))
params.append(sn)
bufSizes += 1
yield AFTER, 'ctu->cpu.writemem(%s, %s, %s);' % (an, bn, sn)
yield AFTER, 'delete[] %s;' % bn
elif type == 'object':
tn = tempname()
yield 'shared_ptr<%s> %s;' % (rest[0][0], tn)
params.append(tn)
yield AFTER, 'if(%s != nullptr)' % tn
yield AFTER, '\tresp.move(%i, createHandle(%s));' % (objOff, tn)
objOff += 1
elif type == 'KObject':
tn = tempname()
yield 'shared_ptr<KObject> %s;' % tn
params.append(tn)
yield AFTER, 'if(%s != nullptr)' % tn
yield AFTER, '\tresp.copy(%i, ctu->newHandle(%s));' % (hndOff, tn)
hndOff += 1
elif type == 'pid':
assert False
else:
if elem[0] == 'align':
alignment = elem[1]
elem = elem[2]
else:
alignment = min(8, typeSize(elem))
while outOffset % alignment:
outOffset += 1
if isPointerType(elem):
tn = tempname()
yield 'auto %s = resp.getDataPointer<%s>(%s);' % (tn, retype(elem, noIndex=True), emitInt(outOffset))
params.append(tn)
else:
params.append('*resp.getDataPointer<%s *>(%s)' % (retype(elem), emitInt(outOffset)))
outOffset += typeSize(elem)
if len(func['outputs']) + len(func['inputs']) + bufSizes != len(params):
yield 'return 0xf601;'
return
yield 'resp.initialize(%i, %i, %i);' % (objOff, hndOff, outOffset - 8)
if len(logFmt):
yield 'LOG_DEBUG(IpcStubs, "IPC message to %s: %s"%s);' % (qname + '::' + fname, ', '.join(logFmt), (', ' + ', '.join(logElems)) if logElems else '')
else:
yield 'LOG_DEBUG(IpcStubs, "IPC message to %s");' % (qname + '::' + fname)
yield 'resp.errCode = %s(%s);' % (fname, ', '.join(params))
yield AFTER
yield 'return 0;'
def reorder(gen):
after = []
for x in gen:
if x == AFTER:
for elem in after:
yield elem
elif isinstance(x, tuple) and x[0] == AFTER:
after.append(x[1])
else:
yield x
def parsePartials(code):
code = '\n'.join(re.findall(r'/\*\$IPC\$(.*?)\*/', code, re.M|re.S))
return partialparser.parse(code)
usedInts = []
def uniqInt(*args):
args = ''.join(map(str, args))
i = int(hashlib.md5(args).hexdigest()[:8], 16)
while True:
if i not in usedInts:
usedInts.append(i)
return i
i += 1
def main():
global allTypes
fns = ['ipcdefs/auto.id'] + [x for x in glob.glob('ipcdefs/*.id') if x != 'ipcdefs/auto.id']
if os.path.exists('ipcdefs/cache') and all(os.path.getmtime('ipcdefs/cache') > os.path.getmtime(x) for x in fns):
res = json.load(file('ipcdefs/cache'))
else:
res = idparser.parse('\n'.join(file(fn).read() for fn in fns))
with file('ipcdefs/cache', 'w') as fp:
json.dump(res, fp)
types, ifaces, services = res
allTypes = types
typesByNs = splitByNs(types)
ifacesByNs = splitByNs(ifaces)
namespaces = {x : [] for x in typesByNs.keys() + ifacesByNs.keys()}
for ns, types in typesByNs.items():
for name, spec in sorted(types.items(), key=lambda x: x[0]):
retyped, plain = retype(spec, noIndex=True), retype(spec)
namespaces[ns].append('using %s = %s;%s' % (name, retyped, ' // ' + plain if retyped != plain else ''))
for ns, ifaces in ifacesByNs.items():
for name in sorted(ifaces.keys()):
namespaces[ns].append('class %s;' % name)
with file('IpcStubs.h', 'w') as fp:
print >>fp, '#pragma once'
print >>fp, '#include "Ctu.h"'
print >>fp
print >>fp, '#define SERVICE_MAPPING() do { \\'
for iname, snames in sorted(services.items(), key=lambda x: x[0]):
for sname in snames:
print >>fp, '\tSERVICE("%s", %s); \\' % (sname, iname)
print >>fp, '} while(0)'
print >>fp
for ns, elems in sorted(namespaces.items(), key=lambda x: x[0]):
if ns is not None:
print >>fp, 'namespace %s {' % ns
hasUsing = False
for elem in elems:
if not hasUsing and elem.startswith('using'):
hasUsing = True
elif hasUsing and elem.startswith('class'):
print >>fp
hasUsing = False
print >>fp, ('\t' if ns is not None else '') + elem
if ns is not None:
print >>fp, '}'
print >>fp
allcode = '\n'.join(file(fn, 'r').read() for fn in glob.glob('ipcimpl/*.cpp'))
partials = parsePartials(allcode)
for ns, ifaces in sorted(ifacesByNs.items(), key=lambda x: x[0]):
print >>fp, '%snamespace %s {' % ('//// ' if ns is None else '', ns)
for name, funcs in sorted(ifaces.items(), key=lambda x: x[0]):
qname = '%s::%s' % (ns, name) if ns else name
partial = partials[qname] if qname in partials else None
print >>fp, '\tclass %s : public IpcService {' % name
print >>fp, '\tpublic:'
if re.search('(^|[^a-zA-Z0-9:])%s::%s[^a-zA-Z0-9:]' % (qname, name), allcode):
print >>fp, '\t\t%s(Ctu *_ctu%s);' % (name, ', ' + ', '.join('%s _%s' % (k, v) for k, v in partial[1]) if partial and partial[1] else '')
else:
print >>fp, '\t\t%s(Ctu *_ctu%s) : IpcService(_ctu)%s {}' % (name, ', ' + ', '.join('%s _%s' % (k, v) for k, v in partial[1]) if partial and partial[1] else '', ', ' + ', '.join('%s(_%s)' % (v, v) for k, v in partial[1]) if partial and partial[1] else '')
print >>fp, '\t\tuint32_t dispatch(IncomingIpcMessage &req, OutgoingIpcMessage &resp) {'
print >>fp, '\t\t\tswitch(req.cmdId) {'
for fname, func in sorted(funcs.items(), key=lambda x: x[1]['cmdId']):
print >>fp, '\t\t\tcase %i: {' % func['cmdId'];
print >>fp, '\n'.join('\t\t\t\t' + x for x in reorder(generateCaller(qname, fname, func)))
print >>fp, '\t\t\t}'
print >>fp, '\t\t\tdefault:'
print >>fp, '\t\t\t\tLOG_ERROR(IpcStubs, "Unknown message cmdId %%u to interface %s", req.cmdId);' % ('%s::%s' % (ns, name) if ns else name)
print >>fp, '\t\t\t}'
print >>fp, '\t\t}'
for fname, func in sorted(funcs.items(), key=lambda x: x[0]):
implemented = re.search('[^a-zA-Z0-9:]%s::%s[^a-zA-Z0-9:]' % (qname, fname), allcode)
print >>fp, '\t\tuint32_t %s(%s);' % (fname, generatePrototype(func))
if partial:
for x in partial[0]:
print >>fp, '\t\t%s' % x
print >>fp, '\t};'
print >>fp, '%s}' % ('//// ' if ns is None else '')
print >>fp, '#ifdef DEFINE_STUBS'
for name, funcs in sorted(ifaces.items(), key=lambda x: x[0]):
qname = '%s::%s' % (ns, name) if ns else name
partial = partials[qname] if qname in partials else None
for fname, func in sorted(funcs.items(), key=lambda x: x[0]):
implemented = re.search('[^a-zA-Z0-9:]%s::%s[^a-zA-Z0-9:]' % (qname, fname), allcode)
if not implemented:
print >>fp, 'uint32_t %s::%s(%s) {' % (qname, fname, generatePrototype(func))
print >>fp, '\tLOG_DEBUG(IpcStubs, "Stub implementation for %s::%s");' % (qname, fname)
for i, (name, elem) in enumerate(func['outputs']):
if elem[0] == 'object' and elem[1][0] != 'IUnknown':
name = name if name else '_%i' % (len(func['inputs']) + i)
print >>fp, '\t%s = buildInterface(%s);' % (name, elem[1][0])
if elem[1][0] in partials and partials[elem[1][0]][1]:
print 'Bare construction of interface %s requiring parameters. Created in %s::%s for parameter %s' % (elem[1][0], qname, fname, name)
sys.exit(1)
elif elem[0] == 'KObject':
name = name if name else '_%i' % (len(func['inputs']) + i)
print >>fp, '\t%s = make_shared<FauxHandle>(0x%x);' % (name, uniqInt(qname, fname, name))
print >>fp, '\treturn 0;'
print >>fp, '}'
print >>fp, '#endif // DEFINE_STUBS'
if __name__=='__main__':
main(*sys.argv[1:])

95
idparser.py Normal file
View file

@ -0,0 +1,95 @@
import sys, tatsu
grammar = '''
start = { def }+ $ ;
number
=
| /0x[0-9a-fA-F]+/
| /[0-9]+/
;
def
=
| typeDef
| interface
;
expression
=
| type
| number
;
name = /[a-zA-Z_][a-zA-Z0-9_:]*/ ;
sname = /[a-zA-Z_][a-zA-Z0-9_:\-]*/ ;
serviceNameList = @:','.{ sname } ;
template = '<' @:','.{ expression } '>' ;
type = name:name template:[ template ] ;
typeDef = 'type' name:name '=' type:type ';' ;
interface = 'interface' name:name [ 'is' serviceNames:serviceNameList ] '{' functions:{ funcDef }* '}' ;
namedTuple = '(' @:','.{ type [ name ] } ')' ;
namedType = type [ name ] ;
funcDef = '[' cmdId:number ']' name:name inputs:namedTuple [ '->' outputs:( namedType | namedTuple ) ] ';' ;
'''
class Semantics(object):
def number(self, ast):
if ast.startswith('0x'):
return int(ast[2:], 16)
return int(ast)
def namedTuple(self, ast):
return [elem if isinstance(elem, list) else [elem, None] for elem in ast]
def namedType(self, ast):
return [ast if isinstance(ast, list) else [ast, None]]
def parseType(type):
if not isinstance(type, tatsu.ast.AST) or 'template' not in type:
return type
name, template = type['name'], type['template']
if template is None:
return [name]
else:
return [name] + map(parseType, template)
def parse(data):
ast = tatsu.parse(grammar, data, semantics=Semantics(), eol_comments_re=r'\/\/.*?$')
types = {}
for elem in ast:
if 'type' not in elem:
continue
#assert elem['name'] not in types
types[elem['name']] = parseType(elem['type'])
ifaces = {}
services = {}
for elem in ast:
if 'functions' not in elem:
continue
#assert elem['name'] not in ifaces
ifaces[elem['name']] = iface = {}
if elem['serviceNames']:
services[elem['name']] = list(elem['serviceNames'])
for func in elem['functions']:
if func['name'] in iface:
print >>sys.stderr, 'Duplicate function %s in %s' % (func['name'], elem['name'])
sys.exit(1)
assert func['name'] not in iface
iface[func['name']] = fdef = {}
fdef['cmdId'] = func['cmdId']
fdef['inputs'] = [(name, parseType(type)) for type, name in func['inputs']]
if func['outputs'] is None:
fdef['outputs'] = []
elif isinstance(func['outputs'], tatsu.ast.AST):
fdef['outputs'] = [(None, parseType(func['outputs']))]
else:
fdef['outputs'] = [(name, parseType(type)) for type, name in func['outputs']]
return types, ifaces, services

8
ipcdefs/bgtc.id Normal file
View file

@ -0,0 +1,8 @@
interface nn::bgtc::IStateControlService is bgtc:sc {
}
interface nn::bgtc::ITaskService is bgtc:t {
[3] Unknown3() -> KObject;
[5] Unknown5(buffer<unknown, 9, 0>);
[14] Unknown14() -> KObject;
}

7
ipcdefs/capsrv.id Normal file
View file

@ -0,0 +1,7 @@
interface nn::capsrv::sf::IAlbumControlService is caps:c {
}
interface nn::capsrv::sf::IAlbumAccessorService is caps:a {
}

2
ipcdefs/es.id Normal file
View file

@ -0,0 +1,2 @@
interface nn::es::IETicketService is es {
}

5
ipcdefs/fatal.id Normal file
View file

@ -0,0 +1,5 @@
interface nn::fatalsrv::IService is fatal:u {
[0] Unknown0(u64, u64, pid);
[1] Unknown1(u64, u64, pid);
[2] TransitionToFatalError(u64 errorCode, u64, buffer<unknown, 0x15, 0x110> errorBuf, pid);
}

156
ipcdefs/fspsrv.id Normal file
View file

@ -0,0 +1,156 @@
type nn::fssrv::sf::SaveStruct = bytes<0x40>;
type nn::fssrv::sf::SaveCreateStruct = bytes<0x40>;
type nn::fssrv::sf::Partition = u32;
// --------------------------------------------- FSP-SRV ---------------------------------------------
interface nn::fssrv::sf::IFileSystemProxy is fsp-srv {
[1] Initialize(u64, pid);
[2] OpenDataFileSystemByCurrentProcess() -> object<nn::fssrv::sf::IFileSystem>;
[7] MountContent7(nn::ApplicationId tid, u32 ncaType) -> object<nn::fssrv::sf::IFileSystem>;
[8] MountContent(nn::ApplicationId tid, u32 flag, buffer<i8, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFileSystem> contentFs;
[9] OpenDataFileSystemByApplicationId(nn::ApplicationId tid) -> object<nn::fssrv::sf::IFileSystem> dataFiles;
[11] MountBis(nn::fssrv::sf::Partition partitionID, buffer<i8, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFileSystem> Bis;
[12] OpenBisPartition(nn::fssrv::sf::Partition partitionID) -> object<nn::fssrv::sf::IStorage> BisPartition;
[13] InvalidateBisCache();
[17] OpenHostFileSystemImpl(buffer<i8, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFileSystem>;
[18] MountSdCard() -> object<nn::fssrv::sf::IFileSystem> sdCard;
[19] FormatSdCard();
[21] DeleteSaveData(nn::ApplicationId tid);
[22] CreateSaveData(nn::fssrv::sf::SaveStruct saveStruct, nn::fssrv::sf::SaveCreateStruct saveCreate, u128 input);
[23] CreateSystemSaveData(nn::fssrv::sf::SaveStruct saveStruct, nn::fssrv::sf::SaveCreateStruct saveCreate);
[24] RegisterSaveDataAtomicDeletion(buffer<void, 5, 0>);
[25] DeleteSaveDataWithSpaceId(u8, u64);
[26] FormatSdCardDryRun();
[27] IsExFatSupported() -> u8 isSupported;
[30] OpenGameCardPartition(nn::fssrv::sf::Partition partitionID, u32) -> object<nn::fssrv::sf::IStorage> gameCardFs;
[31] MountGameCardPartition(u32, u32) -> object<nn::fssrv::sf::IFileSystem> gameCardPartitionFs;
[32] ExtendSaveData(u8, u64, u64, u64);
[51] MountSaveData(u8 input, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> saveDataFs;
[52] MountSystemSaveData(u8 input, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> systemSaveDataFs;
[53] MountSaveDataReadOnly(u8 input, nn::fssrv::sf::SaveStruct saveStruct) -> object<nn::fssrv::sf::IFileSystem> saveDataFs;
[57] ReadSaveDataFileSystemExtraDataWithSpaceId (u8, u64) -> buffer<void, 6, 0>;
[58] ReadSaveDataFileSystemExtraData(u64) -> buffer<void, 6, 0>;
[59] WriteSaveDataFileSystemExtraData(u64, u8, buffer<void, 5, 0>);
[60] OpenSaveDataInfoReader() -> object<nn::fssrv::sf::ISaveDataInfoReader>;
[61] OpenSaveDataIterator(u8) -> object<IUnknown>;
[80] OpenSaveDataThumbnailFile(u8, bytes<0x40>, u32) -> object<nn::fssrv::sf::IFile> thumbnail;
[100] MountImageDirectory(u32) -> object<nn::fssrv::sf::IFileSystem> imageFs;
[110] MountContentStorage(u32 contentStorageID) -> object<nn::fssrv::sf::IFileSystem> contentFs;
[200] OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage;
[201] OpenDataStorageByApplicationId(nn::ApplicationId tid) -> object<nn::fssrv::sf::IStorage> dataStorage;
[202] OpenDataStorageByDataId(nn::ApplicationId tid, u8 storageId) -> object<nn::fssrv::sf::IStorage> dataStorage;
[203] OpenRomStorage() -> object<nn::fssrv::sf::IStorage>;
[400] OpenDeviceOperator() -> object<nn::fssrv::sf::IDeviceOperator>;
[500] OpenSdCardDetectionEventNotifier() -> object<nn::fssrv::sf::IEventNotifier> SdEventNotify;
[501] OpenGameCardDetectionEventNotifier() -> object<nn::fssrv::sf::IEventNotifier> GameCardEventNotify;
[600] SetCurrentPosixTime(u64 time);
[601] QuerySaveDataTotalSize(u64, u64) -> u64 saveDataSize;
[602] VerifySaveData(nn::ApplicationId tid) -> buffer<void, 6, 0>;
[603] CorruptSaveDataForDebug(nn::ApplicationId tid);
[604] CreatePaddingFile(u64 size);
[605] DeleteAllPaddingFiles();
[606] GetRightsId(u64, u8) -> u128 rights;
[607] RegisterExternalKey(u128, u128);
[608] UnregisterExternalKey();
[609] GetRightsIdByPath(buffer<i8, 0x19, 0x301> path) -> u128 rights;
[610] GetRightsIdByPath2(buffer<i8, 0x19, 0x301> path) -> (u128 rights, u8);
[620] SetSdCardEncryptionSeed(u128 seedmaybe);
[800] GetAndClearFileSystemProxyErrorInfo() -> bytes<0x80> errorInfo;
[1000] SetBisRootForHost(u32, buffer<i8, 0x19, 0x301> path);
[1001] SetSaveDataSize(u64, u64);
[1002] SetSaveDataRootPath(buffer<i8, 0x19, 0x301> path);
[1003] DisableAutoSaveDataCreation();
[1004] SetGlobalAccessLogMode(u32 mode);
[1005] GetGlobalAccessLogMode() -> u32 logMode;
[1006] OutputAccessLogToSdCard(buffer<void, 5, 0> logText);
}
interface nn::fssrv::sf::IStorage {
[0] Read(u64 offset, u64 length) -> buffer<i8, 0x46, 0> buffer;
[1] Write(u64 offset, u64 length, buffer<i8, 0x45, 0> data);
[2] Flush();
[3] SetSize(u64 size);
[4] GetSize() -> u64 size;
}
interface nn::fssrv::sf::IFileSystem {
[0] CreateFile(u64 mode, u32 size, buffer<i8, 0x19, 0x301> path);
[1] DeleteFile(buffer<i8, 0x19, 0x301> path);
[2] CreateDirectory(buffer<i8, 0x19, 0x301> path);
[3] DeleteDirectory(buffer<i8, 0x19, 0x301> path);
[4] DeleteDirectoryRecursively(buffer<i8, 0x19, 0x301> path);
[5] RenameFile(buffer<i8, 0x19, 0x301> oldPath, buffer<i8, 0x19, 0x301> newPath);
[6] RenameDirectory(buffer<i8, 0x19, 0x301> oldPath, buffer<i8, 0x19, 0x301> newPath);
[7] GetEntryType(buffer<i8, 0x19, 0x301> path) -> u32;
[8] OpenFile(u32 mode, buffer<i8, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file;
[9] OpenDirectory(u32, buffer<i8, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory;
[10] Commit();
[11] GetFreeSpaceSize(buffer<i8, 0x19, 0x301> path) -> u64 totalFreeSpace;
[12] GetTotalSpaceSize(buffer<i8, 0x19, 0x301> path) -> u64 totalSize;
[13] CleanDirectoryRecursively(buffer<i8, 0x19, 0x301> path);
[14] GetFileTimeStampRaw(buffer<i8, 0x19, 0x301> path) -> bytes<0x20> timestamp;
}
interface nn::fssrv::sf::IDeviceOperator {
[0] IsSdCardInserted() -> u8 isSdInserted;
[1] GetSdCardSpeedMode() -> u64 sdSpeed;
[2] GetSdCardCid(u64) -> buffer<unknown, 6, 0> cid;
[3] GetSdCardUserAreaSize() -> u64 size;
[4] GetSdCardProtectedAreaSize() -> u64 protectedSize;
[5] GetAndClearSdCardErrorInfo(u64) -> (u128, u64, buffer<unknown, 6, 0>);
[100] GetMmcCid(u64) -> buffer<unknown, 6, 0> cid;
[101] GetMmcSpeedMode() -> u64 speedMode;
[110] EraseMmc(u32);
[111] GetMmcPartitionSize(u32) -> u64 paritionSize;
[112] GetMmcPatrolCount() -> u32 patrolCount;
[113] GetAndClearMmcErrorInfo(u64) -> (u128, u64, buffer<unknown, 6, 0>);
[114] GetMmcExtendedCsd(u64) -> buffer<unknown, 6, 0>;
[200] IsGameCardInserted() -> u8 isGameInserted;
[201] EraseGameCard(u32, u64);
[202] GetGameCardHandle() -> u32 gamecardHandle;
[203] GetGameCardUpdatePartitionInfo(u32) -> (u32 version, nn::ApplicationId TID);
[204] FinalizeGameCardDriver();
[205] GetGameCardAttribute(u32) -> u8 attribute;
[206] GetGameCardDeviceCertificate(u64, u32) -> buffer<unknown, 6, 0> certificate;
[207] GetGameCardAsicInfo(u64, u64, buffer<unknown, 5, 0>) -> buffer<unknown, 6, 0>;
[208] GetGameCardIdSet(u64) -> buffer<unknown, 6, 0>;
[209] WriteToGameCard(u64, u64) -> buffer<unknown, 6, 0>;
[210] SetVerifyWriteEnalbleFlag(u8 flag);
[211] GetGameCardImageHash(u64, u32) -> buffer<unknown, 6, 0> imageHash;
[212] GetGameCardDeviceIdForProdCard(u64, u64, buffer<unknown, 5, 0>) -> buffer<unknown, 6, 0> errorInfo;
[213] EraseAndWriteParamDirectly(u64, buffer<unknown, 5, 0>);
[214] GetGameCardCid(u64) -> buffer<unknown, 6, 0> cid;
[215] ForceEraseGameCard();
[216] GetGameCardErrorInfo() -> u128 errorInfo;
[217] GetGameCardErrorReportInfo() -> bytes<0x40> errorReportInfo;
[218] GetGameCardDeviceId(u64) -> buffer<unknown, 6, 0> deviceID;
[300] SetSpeedEmulationMode(u32 mode);
[301] GetSpeedEmulationMode() -> u32 emuMode;
}
interface nn::fssrv::sf::IDirectory {
[0] Read() -> (u64, buffer<unknown, 6, 0>);
[1] GetEntryCount() -> u64;
}
interface nn::fssrv::sf::IFile {
[0] Read(u64, u64 offset, u32 size) -> (u64 out_size, buffer<i8, 0x46, 0> out_buf);
[1] Write(u64, u64 offset, u32 size, buffer<i8, 0x45, 0> buf);
[2] Flush();
[3] SetSize(u64 size);
[4] GetSize() -> u64 fileSize;
}
// --------------------------------------------- FSP-PR ---------------------------------------------
interface nn::fssrv::sf::IProgramRegistry {
[0] SetFsPermissions(u64, u64, u8, u64, u64, buffer<unknown, 5, 0>, buffer<unknown, 5, 0>);
[1] ClearFsPermissions(u64 pid);
[256] SetEnabledProgramVerification(u8 enabled);
}
// --------------------------------------------- FSP-LDR ---------------------------------------------
interface nn::fssrv::sf::IFileSystemProxyForLoader {
[0] MountCode(nn::ApplicationId TID, buffer<i8, 0x19, 0x301> contentPath) -> object<nn::fssrv::sf::IFileSystem> contentFs;
[1] IsCodeMounted(nn::ApplicationId TID) -> u8 isMounted;
}

0
ipcdefs/general.id Normal file
View file

28
ipcdefs/gpio.id Normal file
View file

@ -0,0 +1,28 @@
interface nn::gpio::IManager is gpio {
[0] Unknown0(u32) -> object<IUnknown>;
[1] GetPadSession(u32) -> object<nn::gpio::IPadSession>;
[2] Unknown2(u32) -> object<IUnknown>;
[3] Unknown3(u32) -> u8;
[4] Unknown4() -> u128;
[5] Unknown5(u32, u8);
[6] Unknown6(u8);
}
interface nn::gpio::IPadSession {
[0] Unknown0(u32);
[1] Unknown1() -> u32;
[2] Unknown2(u32);
[3] Unknown3() -> u32;
[4] Unknown4(u8);
[5] Unknown5() -> u8;
[6] Unknown6() -> u32;
[7] Unknown7();
[8] Unknown8(u32);
[9] Unknown9() -> u32;
[10] Unknown10() -> KObject;
[11] Unknown11();
[12] Unknown12(u8);
[13] Unknown13() -> u8;
[14] Unknown14(u32);
[15] Unknown15() -> u32;
}

4
ipcdefs/ldr.id Normal file
View file

@ -0,0 +1,4 @@
interface nn::ro::detail::ILdrShellInterface is ldr:shel {
[0] AddProcessToLaunchQueue(buffer<unknown, 0x19, 0x200>, u32 size, nn::ncm::ApplicationId appID);
[1] ClearLaunchQueue();
}

8
ipcdefs/lm.id Normal file
View file

@ -0,0 +1,8 @@
interface nn::lm::ILogService is lm {
[0] Initialize(u64, pid) -> object<nn::lm::ILogger> Log;
}
interface nn::lm::ILogger {
[0] Unknown0(buffer<unknown, 0x21, 0>);
[1] Unknown1(u32);
}

24
ipcdefs/ncm.id Normal file
View file

@ -0,0 +1,24 @@
interface nn::ncm::detail::INcmInterface4Unknown {
[10] Unknown10();
[13] Unknown13() -> u64;
}
interface nn::ncm::detail::INcmInterface5Unknown {
[5] Unknown5() -> u64;
[7] Unknown7() -> u64;
[8] Unknown8();
[15] Unknown15();
}
interface nn::ncm::detail::INcmInterface is ncm {
[2] Unknown2() -> u64;
[3] Unknown3() -> u64;
[4] Unknown4() -> object<nn::ncm::detail::INcmInterface4Unknown>;
[5] Unknown5() -> object<nn::ncm::detail::INcmInterface5Unknown>;
[9] Unknown9() -> u64;
[11] Unknown11() -> u64;
}
interface nn::ncm::detail::LocationResolverInterface is lr {
}

9
ipcdefs/nim.id Normal file
View file

@ -0,0 +1,9 @@
interface nn::nim::detail::INetworkInstallManager is nim {
[2] Unknown2(buffer<unknown, 6, 0x200>) -> (u64, u64);
[8] Unknown8() -> (u64, u64);
[40] Unknown40(buffer<unknown, 6, 0x200>) -> (u64, u64);
}
interface nn::nim::detail::IShopServiceManager is nim:shp {
}

8
ipcdefs/npns.id Normal file
View file

@ -0,0 +1,8 @@
interface nn::npns::Weird {
}
interface nn::npns::INpnsSystem is npns:s {
[5] SetInterfaceVersion() -> KObject;
[7] Unknown7() -> KObject;
[103] Unknown103() -> KObject;
}

3
ipcdefs/ovln.id Normal file
View file

@ -0,0 +1,3 @@
interface nn::ovln::ISender is ovln:snd {
[0] Unknown0(u64 unk1, u64 unk2, u64 unk3, u64 unk4, u64 unk5, u64 unk6, u64 unk7, u64 unk8, u64 unk9, u64 unk10, u64 unk11, u64 unk12, u64 unk13, u64 unk14, u64 unk15, u64 unk16, u64 unk17);
}

9
ipcdefs/pm.id Normal file
View file

@ -0,0 +1,9 @@
interface Pm::Shell is pm:shell {
[0] LaunchTitle(u64, nn::ApplicationId tid);
[3] GetProcessEventWaiter() -> KObject;
}
interface Pm::Bm is pm:bm {
[0] Init() -> (u64);
[1] EnableMaintenanceMode();
}

10
ipcdefs/psc.id Normal file
View file

@ -0,0 +1,10 @@
interface nn::psc::sf::IPmControl is psc:c {
}
interface nn::psc::sf::IPmModule {
[0] Unknown0(u32, buffer<unknown, 5, 0>) -> KObject;
}
interface nn::psc::sf::IPmService is psc:m {
[0] GetIPmModule() -> object<nn::psc::sf::IPmModule>;
}

8
ipcdefs/sm.id Normal file
View file

@ -0,0 +1,8 @@
type ServiceName = bytes<8>;
interface SmService {
[0] Initialize();
[1] GetService(ServiceName name) -> object<IPipe>;
[2] RegisterService(ServiceName name) -> object<NPort>;
[3] UnregisterService(ServiceName name);
}

7
ipcimpl/account.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "Ctu.h"
uint32_t nn::account::detail::INotifier::GetSystemEvent(OUT shared_ptr<KObject>& _0) {
LOG_INFO(Account, "Stub implementation for nn::account::detail::INotifier::GetSystemEvent");
_0 = make_shared<Waitable>();
return 0;
}

7
ipcimpl/bgtc.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "Ctu.h"
uint32_t nn::bgtc::ITaskService::Unknown14(OUT shared_ptr<KObject>& _0) {
LOG_DEBUG(IpcStubs, "Stub implementation for nn::bgtc::ITaskService::Unknown14");
_0 = make_shared<Waitable>();
return 0;
}

14
ipcimpl/fatal.cpp Normal file
View file

@ -0,0 +1,14 @@
#include "Ctu.h"
uint32_t nn::fatalsrv::IService::TransitionToFatalError(IN uint64_t errorCode, IN uint64_t _1, IN uint8_t * errorBuf, guint errorBuf_size, IN gpid _3) {
LOG_DEBUG(Fatal, "!!! FATAL ERROR !!! ERROR CODE: " ADDRFMT, errorCode);
uint64_t stackSize = *(uint64_t *)(&errorBuf[0x240]);
LOG_DEBUG(Fatal, "Stack trace");
LOG_DEBUG(Fatal, "----------------------------");
for(uint64_t i = 0; i < stackSize; i++) {
uint64_t addr = *(uint64_t *)(&errorBuf[0x130 + (i*8)]);
LOG_DEBUG(Fatal, "\t[%x] " ADDRFMT, (uint) i, addr);
}
LOG_DEBUG(Fatal, "----------------------------");
return 0;
}

371
ipcimpl/fsp.cpp Normal file
View file

@ -0,0 +1,371 @@
#include "Ctu.h"
/*$IPC$
partial nn::fssrv::sf::IFile {
[ctor] string fn;
[ctor] uint32_t mode;
void *fp;
bool isOpen;
long bufferOffset;
}
partial nn::fssrv::sf::IFileSystem {
[ctor] string fnPath;
}
partial nn::fssrv::sf::IStorage {
[ctor] string fn;
void *fp;
bool isOpen;
long bufferOffset;
}
*/
/* ---------------------------------------- Start of IFileSystem ---------------------------------------- */
// Interface
nn::fssrv::sf::IStorage::IStorage(Ctu *_ctu, string _fn) : IpcService(_ctu), fn(_fn) {
fn = "SwitchFS/" + _fn;
LOG_DEBUG(Fsp, "Open IStorage \"%s\"", fn.c_str());
fp = fopen(fn.c_str(), "r+");
if(fp) {
isOpen = true;
} else {
LOG_DEBUG(Fsp, "FILE NOT FOUND!");
isOpen = false;
}
}
uint32_t nn::fssrv::sf::IStorage::Flush() {
if(isOpen && fp != nullptr)
fflush((FILE *)fp);
return 0;
}
uint32_t nn::fssrv::sf::IStorage::GetSize(OUT uint64_t& size) {
if(isOpen && fp != nullptr) {
bufferOffset = ftell((FILE *)fp);
fseek((FILE *)fp, 0, SEEK_END);
long fSize = ftell((FILE *)fp);
fseek((FILE *)fp, bufferOffset, SEEK_SET);
size = fSize;
return 0;
}
LOG_DEBUG(Fsp, "Failed to get file size!");
return 0;
}
uint32_t nn::fssrv::sf::IStorage::Read(IN uint64_t offset, IN uint64_t length, OUT int8_t * buffer, guint buffer_size) {
if(isOpen && fp != nullptr) {
uint32_t s = ((uint32_t)buffer_size < (uint32_t)length ? (uint32_t)buffer_size : (uint32_t)length);
bufferOffset = offset;
fseek((FILE *)fp, offset, SEEK_SET);
fread(buffer, 1, s, (FILE *)fp);
bufferOffset = ftell((FILE *)fp);
}
return 0;
}
uint32_t nn::fssrv::sf::IStorage::SetSize(IN uint64_t size) {
if(isOpen && fp != nullptr) {
fseek((FILE *)fp, 0, SEEK_END);
uint32_t curSize = (uint32_t)ftell((FILE *)fp);
if(curSize < (uint32_t)size) {
uint32_t remaining = (uint32_t)size-curSize;
char *buf = (char*)malloc(remaining);
memset(buf, 0, remaining);
fwrite(buf, 1, remaining, (FILE *)fp);
free(buf);
}
fseek((FILE *)fp, bufferOffset, SEEK_SET);
}
return 0;
}
uint32_t nn::fssrv::sf::IStorage::Write(IN uint64_t offset, IN uint64_t length, IN int8_t * data, guint data_size) {
if(isOpen && fp != nullptr) {
bufferOffset = offset;
uint32_t s = ((uint32_t)data_size < (uint32_t)length ? (uint32_t)data_size : (uint32_t)length);
fseek((FILE *)fp, offset, SEEK_SET);
fwrite(data, 1, s, (FILE *)fp);
if(length-s > 0) {
char *buf2 = (char*)malloc(length-s);
memset(buf2, 0, length-s);
fwrite(buf2, 1, length-s, (FILE *)fp);
free(buf2);
}
bufferOffset = ftell((FILE *)fp);
}
return 0;
}
// Funcs
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenBisPartition(IN nn::fssrv::sf::Partition partitionID, OUT shared_ptr<nn::fssrv::sf::IStorage>& BisPartition) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenBisPartition");
BisPartition = buildInterface(nn::fssrv::sf::IStorage, "bis.istorage");
return 0x0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenDataStorageByApplicationId(IN nn::ApplicationId tid, OUT shared_ptr<nn::fssrv::sf::IStorage>& dataStorage) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenDataStorageByApplicationId 0x" ADDRFMT, tid);
std::stringstream ss;
ss << "tid_archives_" << hex << tid << ".istorage";
dataStorage = buildInterface(nn::fssrv::sf::IStorage, ss.str());
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenDataStorageByCurrentProcess(OUT shared_ptr<nn::fssrv::sf::IStorage>& dataStorage) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenDataStorageByCurrentProcess");
LOG_ERROR(Fsp, "UNIMPLEMENTED!!!");
dataStorage = buildInterface(nn::fssrv::sf::IStorage, "");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenDataStorageByDataId(IN nn::ApplicationId tid, IN uint8_t storageId, OUT shared_ptr<nn::fssrv::sf::IStorage>& dataStorage) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenDataStorageByDataId 0x" ADDRFMT, 0x0100000000000800+(uint64_t)storageId);
std::stringstream ss;
ss << "archives/" << hex << setw(16) << setfill('0') << 0x0100000000000800+(uint64_t)storageId << ".istorage";
dataStorage = buildInterface(nn::fssrv::sf::IStorage, ss.str());
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenGameCardPartition(IN nn::fssrv::sf::Partition partitionID, IN uint32_t _1, OUT shared_ptr<nn::fssrv::sf::IStorage>& gameCardFs) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenGameCardPartition");
gameCardFs = buildInterface(nn::fssrv::sf::IStorage, "GamePartition.istorage");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenRomStorage(OUT shared_ptr<nn::fssrv::sf::IStorage>& _0) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenRomStorage");
_0 = buildInterface(nn::fssrv::sf::IStorage, "RomStorage.istorage");
return 0;
}
/* ---------------------------------------- End of IStorage ---------------------------------------- */
/* ---------------------------------------- Start of IFileSystem ---------------------------------------- */
// Interface
nn::fssrv::sf::IFileSystem::IFileSystem(Ctu *_ctu, string _fnPath) : IpcService(_ctu), fnPath(_fnPath) {
fnPath = "SwitchFS/" + _fnPath;
LOG_DEBUG(Fsp, "Open path %s", fnPath.c_str());
}
uint32_t nn::fssrv::sf::IFileSystem::DeleteFile(IN int8_t * path, guint path_size) {
LOG_DEBUG(Fsp, "Delete file %s", (fnPath+string((char*)path)).c_str());
remove((fnPath+string((char*)path)).c_str());
return 0;
}
uint32_t nn::fssrv::sf::IFileSystem::CreateFile(IN uint64_t mode, IN uint32_t size, IN int8_t * path, guint path_size) {
LOG_DEBUG(Fsp, "Create file %s", (fnPath+string((char*)path)).c_str());
FILE *fp = fopen((fnPath+string((char*)path)).c_str(), "wb");
if(!fp)
return 0x7d402;
fclose(fp);
return 0;
}
// Funcs
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenDataFileSystemByCurrentProcess(OUT shared_ptr<nn::fssrv::sf::IFileSystem>& _0) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenDataFileSystemByCurrentProcess");
_0 = buildInterface(nn::fssrv::sf::IFileSystem, "");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountContent7(IN nn::ApplicationId tid, IN uint32_t ncaType, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& _2) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountContent7");
_2 = buildInterface(nn::fssrv::sf::IFileSystem, "");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountContent(IN nn::ApplicationId tid, IN uint32_t flag, IN int8_t * path, guint path_size, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& contentFs) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountContent");
contentFs = buildInterface(nn::fssrv::sf::IFileSystem, "");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenDataFileSystemByApplicationId(IN nn::ApplicationId tid, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& dataFiles) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenDataFileSystemByApplicationId");
dataFiles = buildInterface(nn::fssrv::sf::IFileSystem, "");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountBis(IN nn::fssrv::sf::Partition partitionID, IN int8_t * path, guint path_size, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& Bis) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountBis");
Bis = buildInterface(nn::fssrv::sf::IFileSystem, string("BIS/") + to_string(partitionID));
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenHostFileSystemImpl(IN int8_t * path, guint path_size, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& _1) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenHostFileSystemImpl");
_1 = buildInterface(nn::fssrv::sf::IFileSystem, "");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountSdCard(OUT shared_ptr<nn::fssrv::sf::IFileSystem>& sdCard) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountSdCard");
sdCard = buildInterface(nn::fssrv::sf::IFileSystem, "SDCard");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountGameCardPartition(IN uint32_t _0, IN uint32_t _1, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& gameCardPartitionFs) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountGameCardPartition");
gameCardPartitionFs = buildInterface(nn::fssrv::sf::IFileSystem, "GameCard");
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountSaveData(IN uint8_t input, IN nn::fssrv::sf::SaveStruct saveStruct, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& saveDataFs) {
uint64_t tid = *(uint64_t *)(&saveStruct[0x18]);
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountSaveData 0x" ADDRFMT, tid);
std::stringstream ss;
ss << "save_" << hex << tid;
saveDataFs = buildInterface(nn::fssrv::sf::IFileSystem, ss.str());
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountSystemSaveData(IN uint8_t input, IN nn::fssrv::sf::SaveStruct saveStruct, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& systemSaveDataFs) {
uint64_t tid = *(uint64_t *)(&saveStruct[0x18]);
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountSystemSaveData 0x" ADDRFMT, tid);
std::stringstream ss;
ss << "syssave_" << hex << tid;
systemSaveDataFs = buildInterface(nn::fssrv::sf::IFileSystem, ss.str());
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountSaveDataReadOnly(IN uint8_t input, IN nn::fssrv::sf::SaveStruct saveStruct, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& saveDataFs) {
uint64_t tid = *(uint64_t *)(&saveStruct[0x18]);
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountSaveDataReadOnly 0x" ADDRFMT, tid);
std::stringstream ss;
ss << "save_" << hex << tid;
saveDataFs = buildInterface(nn::fssrv::sf::IFileSystem, ss.str());
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountImageDirectory(IN uint32_t _0, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& imageFs) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountImageDirectory");
imageFs = buildInterface(nn::fssrv::sf::IFileSystem, string("Image_") + to_string(_0));
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::MountContentStorage(IN uint32_t contentStorageID, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& contentFs) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::MountContentStorage");
contentFs = buildInterface(nn::fssrv::sf::IFileSystem, string("CS_") + to_string(contentStorageID));
return 0;
}
uint32_t nn::fssrv::sf::IFileSystemProxyForLoader::MountCode(IN nn::ApplicationId TID, IN int8_t * contentPath, guint contentPath_size, OUT shared_ptr<nn::fssrv::sf::IFileSystem>& contentFs) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxyForLoader::MountCode");
contentFs = buildInterface(nn::fssrv::sf::IFileSystem, "");
return 0;
}
/* ---------------------------------------- End of IFileSystem ---------------------------------------- */
/* ---------------------------------------- Start of IFile ---------------------------------------- */
// Interface
nn::fssrv::sf::IFile::IFile(Ctu *_ctu, string _fn, uint32_t _mode) : IpcService(_ctu), fn(_fn), mode(_mode) {
LOG_DEBUG(Fsp, "IFile: File path \"%s\"", fn.c_str());
string openModes[] = {"", "rb", "wb+", "wb+", "ab+", "ab+", "ab+", "ab+"};
fp = fopen(fn.c_str(), openModes[_mode].c_str());
if(fp) {
isOpen = true;
} else {
LOG_DEBUG(Fsp, "FILE NOT FOUND!");
isOpen = false;
}
}
uint32_t nn::fssrv::sf::IFile::GetSize(OUT uint64_t& fileSize) {
if(isOpen && fp != nullptr) {
bufferOffset = ftell((FILE *)fp);
fseek((FILE *)fp, 0, SEEK_END);
long fSize = ftell((FILE *)fp);
fseek((FILE *)fp, bufferOffset, SEEK_SET);
fileSize = fSize;
return 0;
}
LOG_DEBUG(Fsp, "Failed to get file size!");
return 0;
}
uint32_t nn::fssrv::sf::IFile::Read(IN uint64_t _0, IN uint64_t offset, IN uint32_t size, OUT uint64_t& out_size, OUT int8_t * out_buf, guint out_buf_size) {
if(isOpen && fp != nullptr) {
uint32_t s = ((uint32_t)out_buf_size < size ? (uint32_t)out_buf_size : size);
bufferOffset = offset;
fseek((FILE *)fp, offset, SEEK_SET);
fread(out_buf, 1, s, (FILE *)fp);
bufferOffset = ftell((FILE *)fp);
out_size = (uint32_t)s;
}
return 0x0;
}
uint32_t nn::fssrv::sf::IFile::Write(IN uint64_t _0, IN uint64_t offset, IN uint32_t size, IN int8_t * buf, guint buf_size) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFile::Write");
if(isOpen && fp != nullptr) {
bufferOffset = offset;
uint32_t s = ((uint32_t)buf_size < size ? (uint32_t)buf_size : size);
fseek((FILE *)fp, offset, SEEK_SET);
fwrite(buf, 1, s, (FILE *)fp);
if(size-s > 0) {
char *buf2 = (char*)malloc(size-s);
memset(buf2, 0, size-s);
fwrite(buf2, 1, size-s, (FILE *)fp);
free(buf2);
}
bufferOffset = ftell((FILE *)fp);
}
return 0;
}
uint32_t nn::fssrv::sf::IFile::Flush() {
if(isOpen && fp != nullptr)
fflush((FILE *)fp);
return 0;
}
uint32_t nn::fssrv::sf::IFile::SetSize(IN uint64_t size) {
if(isOpen && fp != nullptr) {
fseek((FILE *)fp, 0, SEEK_END);
uint32_t curSize = (uint32_t)ftell((FILE *)fp);
if(curSize < (uint32_t)size) {
uint32_t remaining = (uint32_t)size-curSize;
char *buf = (char*)malloc(remaining);
memset(buf, 0, remaining);
fwrite(buf, 1, remaining, (FILE *)fp);
free(buf);
}
fseek((FILE *)fp, bufferOffset, SEEK_SET);
}
return 0;
}
// Funcs
uint32_t nn::fssrv::sf::IFileSystem::OpenFile(IN uint32_t mode, IN int8_t * path, guint path_size, OUT shared_ptr<nn::fssrv::sf::IFile>& file) {
LOG_DEBUG(Fsp, "OpenFile %s", path);
auto tempi = buildInterface(nn::fssrv::sf::IFile, fnPath + "/" + string((char*)path), mode);
if(tempi->isOpen) {
file = tempi;
return 0;
} else
return 0x7d402;
}
uint32_t nn::fssrv::sf::IFileSystemProxy::OpenSaveDataThumbnailFile(IN uint8_t _0, IN uint8_t * _1, IN uint32_t _2, OUT shared_ptr<nn::fssrv::sf::IFile>& thumbnail) {
LOG_DEBUG(Fsp, "Stub implementation for nn::fssrv::sf::IFileSystemProxy::OpenSaveDataThumbnailFile");
thumbnail = buildInterface(nn::fssrv::sf::IFile, string((char*)_1), _0);
return 0;
}
/* ---------------------------------------- End of IFile ---------------------------------------- */
uint32_t nn::fssrv::sf::IEventNotifier::Unknown0(OUT shared_ptr<KObject>& _0) {
LOG_DEBUG(IpcStubs, "Stub implementation for nn::fssrv::sf::IEventNotifier::Unknown0");
_0 = make_shared<Waitable>();
return 0;
}

9
ipcimpl/nim.cpp Normal file
View file

@ -0,0 +1,9 @@
#include "Ctu.h"
uint32_t nn::nim::detail::INetworkInstallManager::Unknown40(IN uint8_t * _0, guint _0_size, OUT uint64_t& _1, OUT uint64_t& _2) {
LOG_DEBUG(IpcStubs, "Stub implementation for nn::nim::detail::INetworkInstallManager::Unknown40");
memset(_0, 0xDE, _0_size);
_1 = 0;
_2 = 0;
return 0;
}

12
ipcimpl/pm.cpp Normal file
View file

@ -0,0 +1,12 @@
#include "Ctu.h"
uint32_t Pm::Shell::LaunchTitle(IN uint64_t _0, IN nn::ApplicationId tid) {
LOG_DEBUG(Pm::Shell, "Attempted to launch title " ADDRFMT, tid);
return 0;
}
uint32_t Pm::Shell::GetProcessEventWaiter(OUT shared_ptr<KObject>& _0) {
LOG_DEBUG(IpcStubs, "Stub implementation for Pm::Shell::GetProcessEventWaiter");
_0 = make_shared<Waitable>();
return 0;
}

7
ipcimpl/psc.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "Ctu.h"
uint32_t nn::psc::sf::IPmModule::Unknown0(IN uint32_t _0, IN uint8_t * _1, guint _1_size, OUT shared_ptr<KObject>& _2) {
LOG_DEBUG(IpcStubs, "Stub implementation for nn::psc::sf::IPmModule::Unknown0");
_2 = make_shared<Waitable>();
return 0;
}

15
ipcimpl/set.cpp Normal file
View file

@ -0,0 +1,15 @@
#include "Ctu.h"
uint32_t nn::settings::ISystemSettingsServer::GetSettingsItemValue(IN nn::settings::SettingsName* cls, guint cls_size, IN nn::settings::SettingsItemKey* key, guint key_size, OUT uint64_t& size, OUT uint8_t* data, guint data_size) {
LOG_DEBUG(Settings, "Attempting to read setting %s!%s", cls, key);
memset(data, 0, data_size);
size = data_size;
return 0;
}
uint32_t nn::settings::ISystemSettingsServer::GetMiiAuthorId(OUT nn::util::Uuid& _0) {
auto buf = (uint64_t *) &_0;
buf[0] = 0xdeadbeefcafebabe;
buf[1] = 0x000000d00db3c001;
return 0;
}

34
ipcimpl/sm.cpp Normal file
View file

@ -0,0 +1,34 @@
#include "Ctu.h"
/*$IPC$
partial SmService {
unordered_map<string, shared_ptr<NPort>> ports;
}
*/
uint32_t SmService::Initialize() {
return 0;
}
#define SERVICE(str, iface) do { if(name == (str)) { svc = buildInterface(iface); return 0; } } while(0)
uint32_t SmService::GetService(IN ServiceName _name, OUT shared_ptr<IPipe>& svc) {
string name((char *) _name, strnlen((char *) _name, 8));
if(ports.find(name) != ports.end()) {
LOG_DEBUG(Sm, "Connecting to native IPC service!");
svc = ports[name]->connectSync();
return 0;
}
SERVICE_MAPPING();
LOG_ERROR(Sm, "Unknown service name %s", name.c_str());
}
uint32_t SmService::RegisterService(IN ServiceName _name, OUT shared_ptr<NPort>& port) {
string name((char *) _name, strnlen((char *) _name, 8));
LOG_DEBUG(Sm, "Registering service %s", name.c_str());
port = buildInterface(NPort, name);
ports[name] = port;
return 0;
}

128
main.cpp Normal file
View file

@ -0,0 +1,128 @@
#include "Ctu.h"
struct Arg: public option::Arg
{
static void printError(const char* msg1, const option::Option& opt, const char* msg2)
{
fprintf(stderr, "%s", msg1);
fwrite(opt.name, opt.namelen, 1, stderr);
fprintf(stderr, "%s", msg2);
}
static option::ArgStatus Unknown(const option::Option& option, bool msg)
{
if (msg) printError("Unknown option '", option, "'\n");
return option::ARG_ILLEGAL;
}
static option::ArgStatus Required(const option::Option& option, bool msg)
{
if (option.arg != nullptr)
return option::ARG_OK;
if (msg) printError("Option '", option, "' requires an argument\n");
return option::ARG_ILLEGAL;
}
static option::ArgStatus NonEmpty(const option::Option& option, bool msg)
{
if (option.arg != nullptr && option.arg[0] != 0)
return option::ARG_OK;
if (msg) printError("Option '", option, "' requires a non-empty argument\n");
return option::ARG_ILLEGAL;
}
static option::ArgStatus Numeric(const option::Option& option, bool msg)
{
char* endptr = nullptr;
if (option.arg != nullptr && strtol(option.arg, &endptr, 10)){};
if (endptr != option.arg && *endptr == 0)
return option::ARG_OK;
if (msg) printError("Option '", option, "' requires a numeric argument\n");
return option::ARG_ILLEGAL;
}
};
enum optionIndex { UNKNOWN, HELP, ENABLE_GDB, PORT, NSO };
const option::Descriptor usage[] =
{
{UNKNOWN, 0, "", "",Arg::None, "USAGE: ctu [options] <load-directory>\n\n"
"Options:" },
{HELP, 0,"","help",Arg::None, " --help \tUnsurprisingly, print this message." },
{ENABLE_GDB, 0,"g","enable-gdb",Arg::None, " --enable-gdb, -g \tEnable GDB stub." },
{PORT, 0,"p","gdb-port",Arg::Numeric, " --gdb-port, -p \tSet port for GDB; default 24689." },
{NSO, 0,"","load-nso",Arg::NonEmpty, " --load-nso \tLoad an NSO without load directory"},
{0,0,nullptr,nullptr,nullptr,nullptr}
};
bool exists(string fn) {
struct stat buffer;
return stat(fn.c_str(), &buffer) == 0;
}
void loadNso(Ctu &ctu, const string &lfn, gptr raddr) {
assert(exists(lfn));
Nso file(lfn);
file.load(ctu, raddr, false);
ctu.loadbase = min(raddr, ctu.loadbase);
auto top = raddr + 0x100000000;
ctu.loadsize = max(top - ctu.loadbase, ctu.loadsize);
}
void runLisp(Ctu &ctu, const string &dir, shared_ptr<Atom> code) {
assert(code->type == List && code->children.size() >= 1);
auto head = code->children[0];
assert(head->type == Symbol);
if(head->strVal == "load-nso") {
assert(code->children.size() == 3);
auto fn = code->children[1], addr = code->children[2];
assert(fn->type == String && addr->type == Number);
auto raddr = addr->numVal;
auto lfn = dir + "/" + fn->strVal;
loadNso(ctu, lfn, raddr);
} else if(head->strVal == "run-from") {
assert(code->children.size() == 2 && code->children[1]->type == Number);
ctu.execProgram(code->children[1]->numVal);
} else
LOG_ERROR(Main, "Unknown function in load script: '%s'", head->strVal.c_str());
}
int main(int argc, char **argv) {
argc -= argc > 0;
argv += argc > 0;
option::Stats stats(usage, argc, argv);
vector<option::Option> options(stats.options_max);
vector<option::Option> buffer(stats.buffer_max);
option::Parser parse(usage, argc, argv, &options[0], &buffer[0]);
if(parse.error())
return 1;
else if(options[HELP].count() || options[UNKNOWN].count() || (options[NSO].count() == 0 && parse.nonOptionsCount() != 1) || (options[NSO].count() == 1 && parse.nonOptionsCount() != 0)) {
option::printUsage(cout, usage);
return 0;
}
Ctu ctu;
ctu.loadbase = 0xFFFFFFFFFFFFFFFF;
ctu.loadsize = 0;
if(options[ENABLE_GDB].count()) {
ctu.gdbStub.enable(options[PORT].count() == 0 ? 24689 : (uint16_t) atoi(options[PORT][0].arg));
} else
assert(options[PORT].count() == 0);
if(options[NSO].count()) {
loadNso(ctu, options[NSO][0].arg, 0x7100000000);
ctu.execProgram(0x7100000000);
} else {
string dir = parse.nonOption(0);
auto lfn = dir + "/load.meph";
if(!exists(lfn))
LOG_ERROR(Main, "File does not exist: %s", lfn.c_str());
auto fp = ifstream(lfn);
auto code = string(istreambuf_iterator<char>(fp), istreambuf_iterator<char>());
auto atom = parseLisp(code);
assert(atom->type == List);
for(auto elem : atom->children)
runLisp(ctu, dir, elem);
}
return 0;
}

2832
optionparser.h Normal file

File diff suppressed because it is too large Load diff

20
partialparser.py Normal file
View file

@ -0,0 +1,20 @@
import re
def parse(data):
partials = {}
for name, body in re.findall('^\s*partial (.*?)\s*{(.*?)}', data, re.M|re.S):
if name not in partials:
partials[name] = [], []
members, params = partials[name]
for elem in body.split(';'):
elem = elem.strip()
if not elem:
continue
if elem.startswith('[ctor]'):
elem = elem[6:].strip()
type, name = re.match('^(.*?)([_a-zA-Z][_a-zA-Z0-9]+)$', elem).groups()
params.append((type, name))
members.append(elem + ';')
return partials