Massive speed improvement
|
@ -1,14 +0,0 @@
|
|||
BasedOnStyle: Google
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 150
|
||||
AccessModifierOffset: -2
|
||||
TabWidth: 4
|
||||
NamespaceIndentation: All
|
||||
UseTab: ForContinuationAndIndentation
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
Cpp11BracedListStyle: true
|
||||
PackConstructorInitializers: BinPack
|
||||
AlignAfterOpenBracket: BlockIndent
|
15
.github/Alber.desktop
vendored
|
@ -1,15 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Alber
|
||||
GenericName=3DS Emulator
|
||||
GenericName[fr]=Émulateur 3DS
|
||||
Comment=Nintendo 3DS video game console emulator
|
||||
Comment[fr]=Émulateur de console de jeu Nintendo 3DS
|
||||
Icon=Alber
|
||||
TryExec=Alber
|
||||
Exec=Alber %f
|
||||
Categories=Game;Emulator;
|
||||
MimeType=application/x-ctr-3dsx;application/x-ctr-cci;application/x-ctr-cia;application/x-ctr-cxi;
|
||||
Keywords=3DS;Nintendo;
|
||||
PrefersNonDefaultGPU=true
|
2
.github/FUNDING.yml
vendored
|
@ -1,2 +0,0 @@
|
|||
patreon: wheremyfoodat
|
||||
ko_fi: wheremyfoodat
|
280
.github/gles.patch
vendored
|
@ -1,280 +0,0 @@
|
|||
diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp
|
||||
index a11a6ffa..77486a09 100644
|
||||
--- a/src/core/renderer_gl/renderer_gl.cpp
|
||||
+++ b/src/core/renderer_gl/renderer_gl.cpp
|
||||
@@ -357,27 +357,27 @@ void RendererGL::bindTexturesToSlots() {
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + 3);
|
||||
- glBindTexture(GL_TEXTURE_1D_ARRAY, lightLUTTextureArray);
|
||||
+ // glBindTexture(GL_TEXTURE_1D_ARRAY, lightLUTTextureArray);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void RendererGL::updateLightingLUT() {
|
||||
- gpu.lightingLUTDirty = false;
|
||||
- std::array<u16, GPU::LightingLutSize> u16_lightinglut;
|
||||
-
|
||||
- for (int i = 0; i < gpu.lightingLUT.size(); i++) {
|
||||
- uint64_t value = gpu.lightingLUT[i] & ((1 << 12) - 1);
|
||||
- u16_lightinglut[i] = value * 65535 / 4095;
|
||||
- }
|
||||
-
|
||||
- glActiveTexture(GL_TEXTURE0 + 3);
|
||||
- glBindTexture(GL_TEXTURE_1D_ARRAY, lightLUTTextureArray);
|
||||
- glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_R16, 256, Lights::LUT_Count, 0, GL_RED, GL_UNSIGNED_SHORT, u16_lightinglut.data());
|
||||
- glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
- glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
- glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
- glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
- glActiveTexture(GL_TEXTURE0);
|
||||
+ // gpu.lightingLUTDirty = false;
|
||||
+ // std::array<u16, GPU::LightingLutSize> u16_lightinglut;
|
||||
+
|
||||
+ // for (int i = 0; i < gpu.lightingLUT.size(); i++) {
|
||||
+ // uint64_t value = gpu.lightingLUT[i] & ((1 << 12) - 1);
|
||||
+ // u16_lightinglut[i] = value * 65535 / 4095;
|
||||
+ // }
|
||||
+
|
||||
+ // glActiveTexture(GL_TEXTURE0 + 3);
|
||||
+ // glBindTexture(GL_TEXTURE_1D_ARRAY, lightLUTTextureArray);
|
||||
+ // glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_R16, 256, Lights::LUT_Count, 0, GL_RED, GL_UNSIGNED_SHORT, u16_lightinglut.data());
|
||||
+ // glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
+ // glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
+ // glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
+ // glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
+ // glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> vertices) {
|
||||
diff --git a/src/host_shaders/opengl_display.frag b/src/host_shaders/opengl_display.frag
|
||||
index 612671c8..1937f711 100644
|
||||
--- a/src/host_shaders/opengl_display.frag
|
||||
+++ b/src/host_shaders/opengl_display.frag
|
||||
@@ -1,4 +1,5 @@
|
||||
-#version 410 core
|
||||
+#version 300 es
|
||||
+precision mediump float;
|
||||
in vec2 UV;
|
||||
out vec4 FragColor;
|
||||
|
||||
diff --git a/src/host_shaders/opengl_display.vert b/src/host_shaders/opengl_display.vert
|
||||
index 990e2f80..2e7842ac 100644
|
||||
--- a/src/host_shaders/opengl_display.vert
|
||||
+++ b/src/host_shaders/opengl_display.vert
|
||||
@@ -1,4 +1,5 @@
|
||||
-#version 410 core
|
||||
+#version 300 es
|
||||
+precision mediump float;
|
||||
out vec2 UV;
|
||||
|
||||
void main() {
|
||||
diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag
|
||||
index f6fa6c55..bb88e278 100644
|
||||
--- a/src/host_shaders/opengl_fragment_shader.frag
|
||||
+++ b/src/host_shaders/opengl_fragment_shader.frag
|
||||
@@ -1,4 +1,5 @@
|
||||
-#version 410 core
|
||||
+#version 300 es
|
||||
+precision mediump float;
|
||||
|
||||
in vec3 v_tangent;
|
||||
in vec3 v_normal;
|
||||
@@ -27,7 +28,7 @@ uniform bool u_depthmapEnable;
|
||||
uniform sampler2D u_tex0;
|
||||
uniform sampler2D u_tex1;
|
||||
uniform sampler2D u_tex2;
|
||||
-uniform sampler1DArray u_tex_lighting_lut;
|
||||
+// uniform sampler1DArray u_tex_lighting_lut;
|
||||
|
||||
uniform uint u_picaRegs[0x200 - 0x48];
|
||||
|
||||
@@ -145,16 +146,23 @@ vec4 tevCalculateCombiner(int tev_id) {
|
||||
#define RR_LUT 6u
|
||||
|
||||
float lutLookup(uint lut, uint light, float value) {
|
||||
- if (lut >= FR_LUT && lut <= RR_LUT) lut -= 1;
|
||||
- if (lut == SP_LUT) lut = light + 8;
|
||||
- return texture(u_tex_lighting_lut, vec2(value, lut)).r;
|
||||
+ // if (lut >= FR_LUT && lut <= RR_LUT) lut -= 1;
|
||||
+ // if (lut == SP_LUT) lut = light + 8;
|
||||
+ // return texture(u_tex_lighting_lut, vec2(value, lut)).r;
|
||||
+ return 0.0;
|
||||
+}
|
||||
+
|
||||
+// some gles versions have bitfieldExtract and complain if you redefine it, some don't and compile error, using this instead
|
||||
+uint bitfieldExtractCompat(uint val, int off, int size) {
|
||||
+ uint mask = uint((1 << size) - 1);
|
||||
+ return uint(val >> off) & mask;
|
||||
}
|
||||
|
||||
vec3 regToColor(uint reg) {
|
||||
// Normalization scale to convert from [0...255] to [0.0...1.0]
|
||||
const float scale = 1.0 / 255.0;
|
||||
|
||||
- return scale * vec3(float(bitfieldExtract(reg, 20, 8)), float(bitfieldExtract(reg, 10, 8)), float(bitfieldExtract(reg, 00, 8)));
|
||||
+ return scale * vec3(float(bitfieldExtractCompat(reg, 20, 8)), float(bitfieldExtractCompat(reg, 10, 8)), float(bitfieldExtractCompat(reg, 00, 8)));
|
||||
}
|
||||
|
||||
// Convert an arbitrary-width floating point literal to an f32
|
||||
@@ -189,7 +197,7 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
vec3 view = normalize(v_view);
|
||||
|
||||
uint GPUREG_LIGHTING_ENABLE = readPicaReg(0x008Fu);
|
||||
- if (bitfieldExtract(GPUREG_LIGHTING_ENABLE, 0, 1) == 0u) {
|
||||
+ if (bitfieldExtractCompat(GPUREG_LIGHTING_ENABLE, 0, 1) == 0u) {
|
||||
primary_color = secondary_color = vec4(1.0);
|
||||
return;
|
||||
}
|
||||
@@ -213,7 +221,7 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
bool error_unimpl = false;
|
||||
|
||||
for (uint i = 0u; i < GPUREG_LIGHTING_NUM_LIGHTS; i++) {
|
||||
- uint light_id = bitfieldExtract(GPUREG_LIGHTING_LIGHT_PERMUTATION, int(i * 3u), 3);
|
||||
+ uint light_id = bitfieldExtractCompat(GPUREG_LIGHTING_LIGHT_PERMUTATION, int(i * 3u), 3);
|
||||
|
||||
uint GPUREG_LIGHTi_SPECULAR0 = readPicaReg(0x0140u + 0x10u * light_id);
|
||||
uint GPUREG_LIGHTi_SPECULAR1 = readPicaReg(0x0141u + 0x10u * light_id);
|
||||
@@ -224,14 +232,14 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
uint GPUREG_LIGHTi_CONFIG = readPicaReg(0x0149u + 0x10u * light_id);
|
||||
|
||||
vec3 light_vector = normalize(vec3(
|
||||
- decodeFP(bitfieldExtract(GPUREG_LIGHTi_VECTOR_LOW, 0, 16), 5u, 10u), decodeFP(bitfieldExtract(GPUREG_LIGHTi_VECTOR_LOW, 16, 16), 5u, 10u),
|
||||
- decodeFP(bitfieldExtract(GPUREG_LIGHTi_VECTOR_HIGH, 0, 16), 5u, 10u)
|
||||
+ decodeFP(bitfieldExtractCompat(GPUREG_LIGHTi_VECTOR_LOW, 0, 16), 5u, 10u), decodeFP(bitfieldExtractCompat(GPUREG_LIGHTi_VECTOR_LOW, 16, 16), 5u, 10u),
|
||||
+ decodeFP(bitfieldExtractCompat(GPUREG_LIGHTi_VECTOR_HIGH, 0, 16), 5u, 10u)
|
||||
));
|
||||
|
||||
vec3 half_vector;
|
||||
|
||||
// Positional Light
|
||||
- if (bitfieldExtract(GPUREG_LIGHTi_CONFIG, 0, 1) == 0u) {
|
||||
+ if (bitfieldExtractCompat(GPUREG_LIGHTi_CONFIG, 0, 1) == 0u) {
|
||||
// error_unimpl = true;
|
||||
half_vector = normalize(normalize(light_vector + v_view) + view);
|
||||
}
|
||||
@@ -242,12 +250,12 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
}
|
||||
|
||||
for (int c = 0; c < 7; c++) {
|
||||
- if (bitfieldExtract(GPUREG_LIGHTING_CONFIG1, 16 + c, 1) == 0u) {
|
||||
- uint scale_id = bitfieldExtract(GPUREG_LIGHTING_LUTINPUT_SCALE, c * 4, 3);
|
||||
+ if (bitfieldExtractCompat(GPUREG_LIGHTING_CONFIG1, 16 + c, 1) == 0u) {
|
||||
+ uint scale_id = bitfieldExtractCompat(GPUREG_LIGHTING_LUTINPUT_SCALE, c * 4, 3);
|
||||
float scale = float(1u << scale_id);
|
||||
if (scale_id >= 6u) scale /= 256.0;
|
||||
|
||||
- uint input_id = bitfieldExtract(GPUREG_LIGHTING_LUTINPUT_SELECT, c * 4, 3);
|
||||
+ uint input_id = bitfieldExtractCompat(GPUREG_LIGHTING_LUTINPUT_SELECT, c * 4, 3);
|
||||
if (input_id == 0u)
|
||||
d[c] = dot(normal, half_vector);
|
||||
else if (input_id == 1u)
|
||||
@@ -260,9 +268,9 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
uint GPUREG_LIGHTi_SPOTDIR_LOW = readPicaReg(0x0146u + 0x10u * light_id);
|
||||
uint GPUREG_LIGHTi_SPOTDIR_HIGH = readPicaReg(0x0147u + 0x10u * light_id);
|
||||
vec3 spot_light_vector = normalize(vec3(
|
||||
- decodeFP(bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 16), 1u, 11u),
|
||||
- decodeFP(bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 16), 1u, 11u),
|
||||
- decodeFP(bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 16), 1u, 11u)
|
||||
+ decodeFP(bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 16), 1u, 11u),
|
||||
+ decodeFP(bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 16), 1u, 11u),
|
||||
+ decodeFP(bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 16), 1u, 11u)
|
||||
));
|
||||
d[c] = dot(-light_vector, spot_light_vector); // -L dot P (aka Spotlight aka SP);
|
||||
} else if (input_id == 5u) {
|
||||
@@ -273,13 +281,13 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
}
|
||||
|
||||
d[c] = lutLookup(uint(c), light_id, d[c] * 0.5 + 0.5) * scale;
|
||||
- if (bitfieldExtract(GPUREG_LIGHTING_LUTINPUT_ABS, 2 * c, 1) != 0u) d[c] = abs(d[c]);
|
||||
+ if (bitfieldExtractCompat(GPUREG_LIGHTING_LUTINPUT_ABS, 2 * c, 1) != 0u) d[c] = abs(d[c]);
|
||||
} else {
|
||||
d[c] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
- uint lookup_config = bitfieldExtract(GPUREG_LIGHTi_CONFIG, 4, 4);
|
||||
+ uint lookup_config = bitfieldExtractCompat(GPUREG_LIGHTi_CONFIG, 4, 4);
|
||||
if (lookup_config == 0u) {
|
||||
d[D1_LUT] = 0.0;
|
||||
d[FR_LUT] = 0.0;
|
||||
@@ -310,7 +318,7 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
float NdotL = dot(normal, light_vector); // Li dot N
|
||||
|
||||
// Two sided diffuse
|
||||
- if (bitfieldExtract(GPUREG_LIGHTi_CONFIG, 1, 1) == 0u)
|
||||
+ if (bitfieldExtractCompat(GPUREG_LIGHTi_CONFIG, 1, 1) == 0u)
|
||||
NdotL = max(0.0, NdotL);
|
||||
else
|
||||
NdotL = abs(NdotL);
|
||||
@@ -321,8 +329,8 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
|
||||
secondary_color.rgb += light_factor * (regToColor(GPUREG_LIGHTi_SPECULAR0) * d[D0_LUT] +
|
||||
regToColor(GPUREG_LIGHTi_SPECULAR1) * d[D1_LUT] * vec3(d[RR_LUT], d[RG_LUT], d[RB_LUT]));
|
||||
}
|
||||
- uint fresnel_output1 = bitfieldExtract(GPUREG_LIGHTING_CONFIG0, 2, 1);
|
||||
- uint fresnel_output2 = bitfieldExtract(GPUREG_LIGHTING_CONFIG0, 3, 1);
|
||||
+ uint fresnel_output1 = bitfieldExtractCompat(GPUREG_LIGHTING_CONFIG0, 2, 1);
|
||||
+ uint fresnel_output2 = bitfieldExtractCompat(GPUREG_LIGHTING_CONFIG0, 3, 1);
|
||||
|
||||
if (fresnel_output1 == 1u) primary_color.a = d[FR_LUT];
|
||||
if (fresnel_output2 == 1u) secondary_color.a = d[FR_LUT];
|
||||
diff --git a/src/host_shaders/opengl_vertex_shader.vert b/src/host_shaders/opengl_vertex_shader.vert
|
||||
index a25d7a6d..7cf40398 100644
|
||||
--- a/src/host_shaders/opengl_vertex_shader.vert
|
||||
+++ b/src/host_shaders/opengl_vertex_shader.vert
|
||||
@@ -1,4 +1,6 @@
|
||||
-#version 410 core
|
||||
+#version 300 es
|
||||
+precision mediump float;
|
||||
+precision mediump int;
|
||||
|
||||
layout(location = 0) in vec4 a_coords;
|
||||
layout(location = 1) in vec4 a_quaternion;
|
||||
@@ -20,7 +22,7 @@ out vec2 v_texcoord2;
|
||||
flat out vec4 v_textureEnvColor[6];
|
||||
flat out vec4 v_textureEnvBufferColor;
|
||||
|
||||
-out float gl_ClipDistance[2];
|
||||
+// out float gl_ClipDistance[2];
|
||||
|
||||
// TEV uniforms
|
||||
uniform uint u_textureEnvColor[6];
|
||||
@@ -93,6 +95,6 @@ void main() {
|
||||
);
|
||||
|
||||
// There's also another, always-on clipping plane based on vertex z
|
||||
- gl_ClipDistance[0] = -a_coords.z;
|
||||
- gl_ClipDistance[1] = dot(clipData, a_coords);
|
||||
+ // gl_ClipDistance[0] = -a_coords.z;
|
||||
+ // gl_ClipDistance[1] = dot(clipData, a_coords);
|
||||
}
|
||||
diff --git a/third_party/opengl/opengl.hpp b/third_party/opengl/opengl.hpp
|
||||
index f368f573..5ead7f63 100644
|
||||
--- a/third_party/opengl/opengl.hpp
|
||||
+++ b/third_party/opengl/opengl.hpp
|
||||
@@ -520,21 +520,21 @@ namespace OpenGL {
|
||||
static void enableBlend() { glEnable(GL_BLEND); }
|
||||
static void disableBlend() { glDisable(GL_BLEND); }
|
||||
static void enableLogicOp() { glEnable(GL_COLOR_LOGIC_OP); }
|
||||
- static void disableLogicOp() { glDisable(GL_COLOR_LOGIC_OP); }
|
||||
+ static void disableLogicOp() { /* glDisable(GL_COLOR_LOGIC_OP); */ }
|
||||
static void enableDepth() { glEnable(GL_DEPTH_TEST); }
|
||||
static void disableDepth() { glDisable(GL_DEPTH_TEST); }
|
||||
static void enableStencil() { glEnable(GL_STENCIL_TEST); }
|
||||
static void disableStencil() { glDisable(GL_STENCIL_TEST); }
|
||||
|
||||
- static void enableClipPlane(GLuint index) { glEnable(GL_CLIP_DISTANCE0 + index); }
|
||||
- static void disableClipPlane(GLuint index) { glDisable(GL_CLIP_DISTANCE0 + index); }
|
||||
+ static void enableClipPlane(GLuint index) { /* glEnable(GL_CLIP_DISTANCE0 + index); */ }
|
||||
+ static void disableClipPlane(GLuint index) { /* glDisable(GL_CLIP_DISTANCE0 + index); */ }
|
||||
|
||||
static void setDepthFunc(DepthFunc func) { glDepthFunc(static_cast<GLenum>(func)); }
|
||||
static void setColourMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a) { glColorMask(r, g, b, a); }
|
||||
static void setDepthMask(GLboolean mask) { glDepthMask(mask); }
|
||||
|
||||
// TODO: Add a proper enum for this
|
||||
- static void setLogicOp(GLenum op) { glLogicOp(op); }
|
||||
+ static void setLogicOp(GLenum op) { /* glLogicOp(op); */ }
|
||||
|
||||
enum Primitives {
|
||||
Triangle = GL_TRIANGLES,
|
9
.github/linux-appimage-qt.sh
vendored
|
@ -1,9 +0,0 @@
|
|||
# Prepare Tools for building the AppImage
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
|
||||
chmod a+x linuxdeploy-x86_64.AppImage
|
||||
chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
|
||||
# Build AppImage
|
||||
QMAKE=/usr/lib/qt6/bin/qmake ./linuxdeploy-x86_64.AppImage --appdir AppDir -d ./.github/Alber.desktop -e ./build/Alber -i ./docs/img/Alber.png --plugin qt --output appimage
|
6
.github/linux-appimage.sh
vendored
|
@ -1,6 +0,0 @@
|
|||
# Prepare Tools for building the AppImage
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod a+x linuxdeploy-x86_64.AppImage
|
||||
|
||||
# Build AppImage
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir -d ./.github/Alber.desktop -e ./build/Alber -i ./docs/img/Alber.png --output appimage
|
52
.github/mac-bundle-qt.sh
vendored
|
@ -1,52 +0,0 @@
|
|||
# Taken from pcsx-redux create-app-bundle.sh
|
||||
# For Plist buddy
|
||||
PATH="$PATH:/usr/libexec"
|
||||
|
||||
|
||||
# Construct the app iconset.
|
||||
mkdir alber.iconset
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 16x16 alber.iconset/icon_16x16.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 32x32 alber.iconset/icon_16x16@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 32x32 alber.iconset/icon_32x32.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 64x64 alber.iconset/icon_32x32@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 128x128 alber.iconset/icon_128x128.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 256x256 alber.iconset/icon_128x128@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 256x256 alber.iconset/icon_256x256.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 512x512 alber.iconset/icon_256x256@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 512x512 alber.iconset/icon_512x512.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 1024x1024 alber.iconset/icon_512x512@2x.png
|
||||
iconutil --convert icns alber.iconset
|
||||
|
||||
# Set up the .app directory
|
||||
mkdir -p Alber.app/Contents/MacOS/Libraries
|
||||
mkdir Alber.app/Contents/Resources
|
||||
|
||||
|
||||
# Copy binary into App
|
||||
cp ./build/Alber Alber.app/Contents/MacOS/Alber
|
||||
chmod a+x Alber.app/Contents/Macos/Alber
|
||||
|
||||
# Copy icons into App
|
||||
cp alber.icns Alber.app/Contents/Resources/AppIcon.icns
|
||||
|
||||
# Fix up Plist stuff
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDisplayName string Alber"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconName string AppIcon"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconFile string AppIcon"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool true"
|
||||
PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber"
|
||||
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleExecutable string Alber"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDevelopmentRegion string en"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleInfoDictionaryVersion string 6.0"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleName string Panda3DS"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundlePackageType string APPL"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHumanReadableCopyright string Copyright 2023 Panda3DS Team"
|
||||
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add LSMinimumSystemVersion string 10.15"
|
||||
|
||||
# Bundle dylibs
|
||||
ruby .github/mac-libs.rb ./build/
|
||||
|
||||
# relative rpath
|
||||
install_name_tool -add_rpath @loader_path/../Frameworks Alber.app/Contents/MacOS/Alber
|
52
.github/mac-bundle.sh
vendored
|
@ -1,52 +0,0 @@
|
|||
# Taken from pcsx-redux create-app-bundle.sh
|
||||
# For Plist buddy
|
||||
PATH="$PATH:/usr/libexec"
|
||||
|
||||
|
||||
# Construct the app iconset.
|
||||
mkdir alber.iconset
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 16x16 alber.iconset/icon_16x16.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 32x32 alber.iconset/icon_16x16@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 32x32 alber.iconset/icon_32x32.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 64x64 alber.iconset/icon_32x32@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 128x128 alber.iconset/icon_128x128.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 256x256 alber.iconset/icon_128x128@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 256x256 alber.iconset/icon_256x256.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 512x512 alber.iconset/icon_256x256@2x.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 512x512 alber.iconset/icon_512x512.png
|
||||
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 1024x1024 alber.iconset/icon_512x512@2x.png
|
||||
iconutil --convert icns alber.iconset
|
||||
|
||||
# Set up the .app directory
|
||||
mkdir -p Alber.app/Contents/MacOS/Libraries
|
||||
mkdir Alber.app/Contents/Resources
|
||||
|
||||
|
||||
# Copy binary into App
|
||||
cp ./build/Alber Alber.app/Contents/MacOS/Alber
|
||||
chmod a+x Alber.app/Contents/Macos/Alber
|
||||
|
||||
# Copy icons into App
|
||||
cp alber.icns Alber.app/Contents/Resources/AppIcon.icns
|
||||
|
||||
# Fix up Plist stuff
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDisplayName string Alber"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconName string AppIcon"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconFile string AppIcon"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool true"
|
||||
PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber"
|
||||
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleExecutable string Alber"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDevelopmentRegion string en"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleInfoDictionaryVersion string 6.0"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleName string Panda3DS"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundlePackageType string APPL"
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHumanReadableCopyright string Copyright 2023 Panda3DS Team"
|
||||
|
||||
PlistBuddy Alber.app/Contents/Info.plist -c "add LSMinimumSystemVersion string 10.15"
|
||||
|
||||
# Bundle dylibs
|
||||
dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath -s /Users/runner/work/Panda3DS/Panda3DS/VULKAN_SDK/lib
|
||||
|
||||
# relative rpath
|
||||
install_name_tool -add_rpath @loader_path/../Frameworks Alber.app/Contents/MacOS/Alber
|
255
.github/mac-libs.rb
vendored
|
@ -1,255 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "open3"
|
||||
require "fileutils"
|
||||
|
||||
$app_name = "Alber"
|
||||
$build_dmg = false
|
||||
$build_dir = ""
|
||||
$bundle = ""
|
||||
$fallback_rpaths = []
|
||||
|
||||
def frameworks_dir
|
||||
File.join($bundle, "Contents", "Frameworks")
|
||||
end
|
||||
|
||||
def executable
|
||||
File.join($bundle, "Contents", "MacOS", $app_name)
|
||||
end
|
||||
|
||||
def get_rpaths(lib)
|
||||
out, _ = Open3.capture2("otool", "-l", lib)
|
||||
out = out.split("\n")
|
||||
rpaths = []
|
||||
|
||||
out.each_with_index do |line, i|
|
||||
if line.match(/^ *cmd LC_RPATH$/)
|
||||
rpaths << out[i + 2].strip.split(" ")[1]
|
||||
end
|
||||
end
|
||||
|
||||
return rpaths
|
||||
end
|
||||
|
||||
def get_load_libs(lib)
|
||||
out, _ = Open3.capture2("otool", "-L", lib)
|
||||
out.split("\n")
|
||||
.drop(1)
|
||||
.map { |it| it.strip.gsub(/ \(.*/, "") }
|
||||
end
|
||||
|
||||
def expand_load_path(lib, path)
|
||||
if path.match(/@(rpath|loader_path|executable_path)/)
|
||||
path_type = $1
|
||||
file_name = path.gsub(/^@#{path_type}\//, "")
|
||||
|
||||
case path_type
|
||||
when "rpath"
|
||||
get_rpaths(lib).each do |rpath|
|
||||
file = File.join(rpath, file_name)
|
||||
return file, :rpath if File.exist? file
|
||||
if rpath.match(/^@executable_path(.*)/) != nil
|
||||
relative = rpath.sub(/^@executable_path/, "")
|
||||
return "#{$bundle}/Contents/MacOS#{relative}/#{file_name}", :executable_path
|
||||
end
|
||||
end
|
||||
file = $fallback_rpaths
|
||||
.map { |it| File.join(it, file_name) }
|
||||
.find { |it| File.exist? it }
|
||||
if file == nil
|
||||
path = File.join(File.dirname(lib), file_name)
|
||||
file = path if File.exist? path
|
||||
end
|
||||
return file, :rpath if file
|
||||
when "executable_path"
|
||||
file = File.join(File.dirname(executable), file_name)
|
||||
return file, :executable_path if File.exist? file
|
||||
when "loader_path"
|
||||
file = File.join(File.dirname(lib), file_name)
|
||||
return file, :loader_path if File.exist? file
|
||||
else
|
||||
throw "Unknown @path type"
|
||||
end
|
||||
else
|
||||
return File.absolute_path(path), :absolute
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
def system_path?(path)
|
||||
path.match(/^\/usr\/lib|^\/System/) != nil
|
||||
end
|
||||
|
||||
def system_lib?(lib)
|
||||
system_path? File.dirname(lib)
|
||||
end
|
||||
|
||||
def install_name_tool(exec, action, path1, path2 = nil)
|
||||
args = ["-#{action.to_s}", path1]
|
||||
args << path2 if path2 != nil
|
||||
|
||||
Open3.popen3("install_name_tool", *args, exec) do |stdin, stdout, stderr, thread|
|
||||
print stdout.read
|
||||
err = stderr.read
|
||||
unless err.match? "code signature"
|
||||
print err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def strip(lib)
|
||||
out, _ = Open3.capture2("strip", "-no_code_signature_warning", "-Sx", lib)
|
||||
print out
|
||||
end
|
||||
|
||||
def fixup_libs(prog, orig_path)
|
||||
throw "fixup_libs: #{prog} doesn't exist" unless File.exist? prog
|
||||
|
||||
libs = get_load_libs(prog).map { |it| expand_load_path(orig_path, it) }.select { |it| not system_lib? it[0] }
|
||||
|
||||
FileUtils.chmod("u+w", prog)
|
||||
strip prog
|
||||
|
||||
libs.each do |lib|
|
||||
libpath, libtype = lib
|
||||
if File.basename(libpath) == File.basename(prog)
|
||||
if libtype == :absolute
|
||||
install_name_tool prog, :change, libpath, File.join("@rpath", File.basename(libpath))
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
framework = libpath.match(/(.*).framework/)
|
||||
framework = framework.to_s if framework
|
||||
|
||||
if framework
|
||||
fwlib = libpath.sub(framework + "/", "")
|
||||
fwname = File.basename(framework)
|
||||
|
||||
unless libtype == :rpath
|
||||
install_name_tool prog, :change, libpath, File.join("@rpath", fwname, fwlib)
|
||||
end
|
||||
|
||||
next if File.exist? File.join(frameworks_dir, fwname)
|
||||
expath, _ = expand_load_path(orig_path, framework)
|
||||
FileUtils.cp_r(expath, frameworks_dir, preserve: true)
|
||||
FileUtils.chmod_R("u+w", File.join(frameworks_dir, fwname))
|
||||
fixup_libs File.join(frameworks_dir, fwname, fwlib), libpath
|
||||
else
|
||||
libname = File.basename(libpath)
|
||||
dest = File.join(frameworks_dir, libname)
|
||||
|
||||
if libtype == :absolute
|
||||
install_name_tool prog, :change, libpath, File.join("@rpath", libname)
|
||||
end
|
||||
|
||||
next if File.exist? dest
|
||||
expath, _ = expand_load_path(orig_path, libpath)
|
||||
FileUtils.copy expath, frameworks_dir
|
||||
FileUtils.chmod("u+w", dest)
|
||||
fixup_libs dest, libpath
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ARGV[0] == "--dmg"
|
||||
$build_dmg = true
|
||||
ARGV.shift
|
||||
end
|
||||
|
||||
if ARGV.length != 1
|
||||
puts "Usage: #{Process.argv0} [--dmg] <build-dir>"
|
||||
return
|
||||
end
|
||||
|
||||
$build_dir = ARGV[0]
|
||||
unless File.exist? $build_dir
|
||||
puts "#{$build_dir} doesn't exist"
|
||||
end
|
||||
|
||||
|
||||
$bundle = "#{$app_name}.app"
|
||||
|
||||
unless File.exist? $bundle and File.exist? File.join($build_dir, "CMakeCache.txt")
|
||||
puts "#{$build_dir} doesn't look like a valid build directory"
|
||||
exit 1
|
||||
end
|
||||
|
||||
File.read(File.join($build_dir, "CMakeCache.txt"))
|
||||
.split("\n")
|
||||
.find { |it| it.match /Qt(.)_DIR:PATH=(.*)/ }
|
||||
|
||||
qt_major = $1
|
||||
qt_dir = $2
|
||||
qt_dir = File.absolute_path("#{qt_dir}/../../..")
|
||||
|
||||
for lib in get_load_libs(executable) do
|
||||
next if system_lib? lib
|
||||
|
||||
path = File.dirname(lib)
|
||||
|
||||
if path.match? ".framework"
|
||||
path = path.sub(/\/[^\/]+\.framework.*/, "")
|
||||
end
|
||||
|
||||
$fallback_rpaths << path unless $fallback_rpaths.include? path
|
||||
end
|
||||
|
||||
$fallback_rpaths << File.join(qt_dir, "lib")
|
||||
|
||||
plugin_paths = [
|
||||
File.join(qt_dir, "libexec", "qt#{qt_major}", "plugins"),
|
||||
File.join(qt_dir, "plugins"),
|
||||
File.join(qt_dir, "share", "qt", "plugins")
|
||||
]
|
||||
|
||||
qt_plugins = plugin_paths.find { |file| File.exist? file }
|
||||
|
||||
if qt_plugins == nil
|
||||
puts "Couldn't find Qt plugins, tried looking for:"
|
||||
plugin_paths.each { |path| puts " - #{path}" }
|
||||
exit 1
|
||||
end
|
||||
|
||||
FileUtils.mkdir_p(frameworks_dir)
|
||||
fixup_libs(executable, executable)
|
||||
|
||||
bundle_plugins = File.join($bundle, "Contents", "PlugIns")
|
||||
|
||||
want_plugins = ["styles/libqmacstyle.dylib", "platforms/libqcocoa.dylib", "imageformats/libqsvg.dylib"]
|
||||
want_plugins.each do |plug|
|
||||
destdir = File.join(bundle_plugins, File.dirname(plug))
|
||||
FileUtils.mkdir_p(destdir)
|
||||
FileUtils.copy(File.join(qt_plugins, plug), destdir)
|
||||
fixup_libs File.join(bundle_plugins, plug), File.join(qt_plugins, plug)
|
||||
end
|
||||
|
||||
want_rpath = "@executable_path/../Frameworks"
|
||||
exec_rpaths = get_rpaths(executable)
|
||||
exec_rpaths.select { |path| path != want_rpath }.each do |path|
|
||||
install_name_tool executable, :delete_rpath, path
|
||||
end
|
||||
|
||||
unless exec_rpaths.include? want_rpath
|
||||
install_name_tool executable, :add_rpath, want_rpath
|
||||
end
|
||||
|
||||
exec_rpaths = get_rpaths(executable)
|
||||
|
||||
Dir.glob("#{frameworks_dir}/**/Headers").each do |dir|
|
||||
FileUtils.rm_rf dir
|
||||
end
|
||||
|
||||
out, _ = Open3.capture2("codesign", "-s", "-", "-f", "--deep", $bundle)
|
||||
print out
|
||||
|
||||
if $build_dmg
|
||||
dmg_dir = File.join($build_dir, "dmg")
|
||||
FileUtils.mkdir_p(dmg_dir)
|
||||
FileUtils.cp_r($bundle, dmg_dir, preserve: true)
|
||||
FileUtils.ln_s("/Applications", File.join(dmg_dir, "Applications"))
|
||||
|
||||
`hdiutil create -fs HFS+ -volname melonDS -srcfolder "#{dmg_dir}" -ov -format UDBZ "#{$build_dir}/melonDS.dmg"`
|
||||
FileUtils.rm_rf(dmg_dir)
|
||||
end
|
119
.github/workflows/Android_Build.yml
vendored
|
@ -1,119 +0,0 @@
|
|||
name: Android Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
x64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type:
|
||||
- release
|
||||
|
||||
steps:
|
||||
- name: Set BUILD_TYPE variable
|
||||
run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DENABLE_VULKAN=0 -DENABLE_USER_BUILD=ON
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
# Apply patch for GLES compatibility
|
||||
git apply ./.github/gles.patch
|
||||
# Build the project with CMake
|
||||
cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }}
|
||||
|
||||
# Strip the generated library and move it to the appropriate location
|
||||
${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --strip-unneeded ./build/libAlber.so
|
||||
mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/
|
||||
|
||||
# Build the Android app with Gradle
|
||||
cd src/pandroid
|
||||
./gradlew assemble${{ env.BUILD_TYPE }}
|
||||
cd ../..
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Android APKs (x86-64)
|
||||
path: |
|
||||
./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk
|
||||
|
||||
arm64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build_type:
|
||||
- release
|
||||
|
||||
steps:
|
||||
- name: Set BUILD_TYPE variable
|
||||
run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DENABLE_VULKAN=0 -DENABLE_USER_BUILD=ON -DCMAKE_CXX_FLAGS="-march=armv8-a+crypto"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
# Apply patch for GLES compatibility
|
||||
git apply ./.github/gles.patch
|
||||
# Build the project with CMake
|
||||
cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }}
|
||||
|
||||
# Strip the generated library and move it to the appropriate location
|
||||
${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --strip-unneeded ./build/libAlber.so
|
||||
mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/
|
||||
|
||||
# Build the Android app with Gradle
|
||||
cd src/pandroid
|
||||
./gradlew assemble${{ env.BUILD_TYPE }}
|
||||
ls -R app/build/outputs
|
||||
cd ../..
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Android APKs (arm64)
|
||||
path: |
|
||||
./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk
|
46
.github/workflows/HTTP_Build.yml
vendored
|
@ -1,46 +0,0 @@
|
|||
name: HTTP Server Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install newer Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x ./llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DENABLE_HTTP_SERVER=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
135
.github/workflows/Hydra_Build.yml
vendored
|
@ -1,135 +0,0 @@
|
|||
name: Hydra Core Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Windows core
|
||||
path: '${{github.workspace}}/build/Release/Alber.dll'
|
||||
|
||||
|
||||
MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: MacOS core
|
||||
path: '${{github.workspace}}/build/libAlber.dylib'
|
||||
|
||||
Linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: |
|
||||
sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2 libwayland-dev
|
||||
|
||||
- name: Install newer Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x ./llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Linux core
|
||||
path: '${{github.workspace}}/build/libAlber.so'
|
||||
|
||||
Android-x64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: |
|
||||
sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2 libwayland-dev
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DBUILD_HYDRA_CORE=1 -DENABLE_VULKAN=0
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Android core
|
||||
path: '${{github.workspace}}/build/libAlber.so'
|
58
.github/workflows/Linux_AppImage_Build.yml
vendored
|
@ -1,58 +0,0 @@
|
|||
name: Linux AppImage Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2
|
||||
|
||||
- name: Install newer Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x ./llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
run: |
|
||||
wget -qO - http://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
|
||||
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-focal.list http://packages.lunarg.com/vulkan/lunarg-vulkan-focal.list
|
||||
sudo apt update
|
||||
sudo apt install vulkan-sdk
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Run AppImage packaging script
|
||||
run: ./.github/linux-appimage.sh
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Linux executable
|
||||
path: './Alber-x86_64.AppImage'
|
55
.github/workflows/Linux_Build.yml
vendored
|
@ -1,55 +0,0 @@
|
|||
name: Linux Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev
|
||||
|
||||
- name: Install newer Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x ./llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Linux executable
|
||||
path: './build/Alber'
|
58
.github/workflows/MacOS_Build.yml
vendored
|
@ -1,58 +0,0 @@
|
|||
name: MacOS Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Install bundle dependencies
|
||||
run: brew install dylibbundler imagemagick
|
||||
|
||||
- name: Run bundle script
|
||||
run: ./.github/mac-bundle.sh
|
||||
|
||||
- name: Sign the App
|
||||
run: codesign --force -s - -vvvv Alber.app
|
||||
|
||||
- name: Zip it up
|
||||
run: zip -r Alber Alber.app
|
||||
|
||||
- name: Upload MacOS App
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: MacOS Alber App Bundle
|
||||
path: 'Alber.zip'
|
141
.github/workflows/Qt_Build.yml
vendored
|
@ -1,141 +0,0 @@
|
|||
name: Qt Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
arch: win64_msvc2019_64
|
||||
version: 6.2.0
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DENABLE_QT_GUI=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Deploy
|
||||
run: |
|
||||
mkdir upload
|
||||
move build/Release/Alber.exe upload
|
||||
windeployqt --dir upload upload/Alber.exe
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Windows executable
|
||||
path: upload
|
||||
|
||||
MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Install bundle dependencies
|
||||
run: |
|
||||
brew install dylibbundler imagemagick
|
||||
|
||||
- name: Install qt
|
||||
run: brew install qt && which macdeployqt
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DENABLE_QT_GUI=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Run bundle script
|
||||
run: |
|
||||
chmod +x .github/mac-bundle-qt.sh
|
||||
./.github/mac-bundle-qt.sh
|
||||
|
||||
- name: Sign the App
|
||||
run: codesign --force -s - -vvvv Alber.app
|
||||
|
||||
- name: Zip it up
|
||||
run: zip -r Alber Alber.app
|
||||
|
||||
- name: Upload MacOS App
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: MacOS Alber App Bundle
|
||||
path: 'Alber.zip'
|
||||
|
||||
Linux:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: |
|
||||
sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2 libwayland-dev
|
||||
sudo add-apt-repository -y ppa:savoury1/qt-6-2
|
||||
sudo apt update
|
||||
sudo apt install qt6-base-dev qt6-base-private-dev
|
||||
|
||||
- name: Install newer Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x ./llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
run: |
|
||||
wget -qO - http://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
|
||||
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-focal.list http://packages.lunarg.com/vulkan/lunarg-vulkan-focal.list
|
||||
sudo apt update
|
||||
sudo apt install vulkan-sdk
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DENABLE_QT_GUI=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Run AppImage packaging script
|
||||
run: |
|
||||
chmod +x .github/linux-appimage-qt.sh
|
||||
./.github/linux-appimage-qt.sh
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Linux executable
|
||||
path: './Alber-x86_64.AppImage'
|
46
.github/workflows/Windows_Build.yml
vendored
|
@ -1,46 +0,0 @@
|
|||
name: Windows Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-use-cache: true
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Windows executable
|
||||
path: './build/Release/Alber.exe'
|
63
.gitmodules
vendored
|
@ -1,63 +0,0 @@
|
|||
[submodule "third_party/elfio"]
|
||||
path = third_party/elfio
|
||||
url = https://github.com/serge1/ELFIO
|
||||
[submodule "third_party/SDL2"]
|
||||
path = third_party/SDL2
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "third_party/cryptopp/cryptopp"]
|
||||
path = third_party/cryptopp/cryptopp
|
||||
url = https://github.com/weidai11/cryptopp
|
||||
[submodule "third_party/xbyak"]
|
||||
path = third_party/xbyak
|
||||
url = https://github.com/herumi/xbyak
|
||||
[submodule "third_party/toml11"]
|
||||
path = third_party/toml11
|
||||
url = https://github.com/ToruNiina/toml11
|
||||
[submodule "cpp-httplib"]
|
||||
path = third_party/httplib
|
||||
url = https://github.com/yhirose/cpp-httplib
|
||||
[submodule "stb"]
|
||||
path = third_party/stb
|
||||
url = https://github.com/nothings/stb
|
||||
[submodule "third_party/cmrc"]
|
||||
path = third_party/cmrc
|
||||
url = https://github.com/vector-of-bool/cmrc
|
||||
[submodule "third_party/glm"]
|
||||
path = third_party/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "third_party/discord-rpc"]
|
||||
path = third_party/discord-rpc
|
||||
url = https://github.com/Panda3DS-emu/discord-rpc
|
||||
[submodule "third_party/LuaJIT"]
|
||||
path = third_party/LuaJIT
|
||||
url = https://github.com/Panda3DS-emu/LuaJIT
|
||||
[submodule "third_party/mio"]
|
||||
path = third_party/mio
|
||||
url = https://github.com/vimpunk/mio
|
||||
[submodule "third_party/hydra_core"]
|
||||
path = third_party/hydra_core
|
||||
url = https://github.com/hydra-emu/core
|
||||
[submodule "third_party/zep"]
|
||||
path = third_party/zep
|
||||
url = https://github.com/Panda3DS-emu/zep
|
||||
[submodule "third_party/oaknut"]
|
||||
path = third_party/oaknut
|
||||
url = https://github.com/merryhime/oaknut
|
||||
[submodule "third_party/luv"]
|
||||
path = third_party/luv
|
||||
url = https://github.com/luvit/luv
|
||||
[submodule "third_party/libuv"]
|
||||
path = third_party/libuv
|
||||
url = https://github.com/libuv/libuv
|
||||
[submodule "third_party/miniaudio"]
|
||||
path = third_party/miniaudio
|
||||
url = https://github.com/mackron/miniaudio
|
||||
[submodule "third_party/teakra"]
|
||||
path = third_party/teakra
|
||||
url = https://github.com/wwylele/teakra
|
||||
[submodule "third_party/boost"]
|
||||
path = third_party/boost
|
||||
url = https://github.com/Panda3DS-emu/ext-boost
|
||||
[submodule "third_party/dynarmic"]
|
||||
path = third_party/dynarmic
|
||||
url = https://github.com/Panda3DS-emu/dynarmic
|
511
CMakeLists.txt
|
@ -1,511 +1,8 @@
|
|||
# We need to be able to use enable_language(OBJC) on Mac, so we need CMake 3.16 vs the 3.10 we use otherwise. Blame Apple.
|
||||
if (APPLE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
else()
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 12)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fbracket-depth=4096")
|
||||
endif()
|
||||
project(Alber CXX)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
project(Alber)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
if(APPLE)
|
||||
enable_language(OBJC)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-nonliteral -Wno-format-security")
|
||||
endif()
|
||||
|
||||
option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" ON)
|
||||
option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF)
|
||||
option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON)
|
||||
option(ENABLE_VULKAN "Enable Vulkan rendering backend" ON)
|
||||
option(ENABLE_LTO "Enable link-time optimization" OFF)
|
||||
option(ENABLE_USER_BUILD "Make a user-facing build. These builds have various assertions disabled, LTO, and more" OFF)
|
||||
option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF)
|
||||
option(ENABLE_DISCORD_RPC "Compile with Discord RPC support (disabled by default)" ON)
|
||||
option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON)
|
||||
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
|
||||
option(BUILD_HYDRA_CORE "Build a Hydra core" OFF)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include/)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include/kernel)
|
||||
include_directories (${FMT_INCLUDE_DIR})
|
||||
include_directories(third_party/boost/)
|
||||
include_directories(third_party/elfio/)
|
||||
include_directories(third_party/imgui/)
|
||||
include_directories(third_party/dynarmic/src)
|
||||
include_directories(third_party/cryptopp/)
|
||||
include_directories(third_party/cityhash/include)
|
||||
include_directories(third_party/result/include)
|
||||
include_directories(third_party/xxhash/include)
|
||||
include_directories(third_party/httplib)
|
||||
include_directories(third_party/stb)
|
||||
include_directories(third_party/opengl)
|
||||
include_directories(third_party/miniaudio)
|
||||
include_directories(third_party/mio/single_include)
|
||||
|
||||
add_compile_definitions(NOMINMAX) # Make windows.h not define min/max macros because third-party deps don't like it
|
||||
add_compile_definitions(WIN32_LEAN_AND_MEAN) # Make windows.h not include literally everything
|
||||
add_compile_definitions(SDL_MAIN_HANDLED)
|
||||
|
||||
if(BUILD_HYDRA_CORE)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
add_subdirectory(third_party/discord-rpc)
|
||||
include_directories(third_party/discord-rpc/include)
|
||||
endif()
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||
|
||||
# We can't use qt_standard_project_setup since it's Qt 6.3+ and we don't need to set the minimum that high
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
endif()
|
||||
|
||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_TEST OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(third_party/SDL2)
|
||||
|
||||
add_subdirectory(third_party/toml11)
|
||||
include_directories(${SDL2_INCLUDE_DIR})
|
||||
include_directories(third_party/toml11)
|
||||
include_directories(third_party/glm)
|
||||
|
||||
add_subdirectory(third_party/cmrc)
|
||||
|
||||
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/third_party/boost")
|
||||
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third_party/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS ON)
|
||||
add_compile_definitions(BOOST_NO_CXX98_FUNCTION_BASE) # Forbid Boost from using std::unary_function (Fixes MacOS build)
|
||||
|
||||
add_library(boost INTERFACE)
|
||||
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
|
||||
|
||||
if(ANDROID)
|
||||
set(CRYPTOPP_OPT_DISABLE_ASM ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
|
||||
set(CRYPTOPP_BUILD_TESTING OFF)
|
||||
add_subdirectory(third_party/cryptopp)
|
||||
add_subdirectory(third_party/glad)
|
||||
|
||||
if(ENABLE_LUAJIT)
|
||||
add_subdirectory(third_party/LuaJIT luajit)
|
||||
include_directories(third_party/LuaJIT/src ${CMAKE_BINARY_DIR}/luajit)
|
||||
set_target_properties(luajit PROPERTIES EXCLUDE_FROM_ALL 1)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_definitions(libluajit PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
target_compile_definitions(minilua PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
target_compile_definitions(buildvm PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Check for x64
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
set(HOST_X64 TRUE)
|
||||
add_subdirectory(third_party/xbyak) # Add xbyak submodule for x86 JITs
|
||||
include_directories(third_party/xbyak)
|
||||
add_compile_definitions(PANDA3DS_DYNAPICA_SUPPORTED)
|
||||
add_compile_definitions(PANDA3DS_X64_HOST)
|
||||
else()
|
||||
set(HOST_X64 FALSE)
|
||||
endif()
|
||||
|
||||
# Check for arm64
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
|
||||
set(HOST_ARM64 TRUE)
|
||||
add_subdirectory(third_party/oaknut) # Add Oaknut submodule for arm64 JITs
|
||||
include_directories(third_party/oaknut/include)
|
||||
add_compile_definitions(PANDA3DS_DYNAPICA_SUPPORTED)
|
||||
add_compile_definitions(PANDA3DS_ARM64_HOST)
|
||||
else()
|
||||
set(HOST_ARM64 FALSE)
|
||||
endif()
|
||||
|
||||
if(HOST_X64 OR HOST_ARM64)
|
||||
set(DYNARMIC_TESTS OFF)
|
||||
#set(DYNARMIC_NO_BUNDLED_FMT ON)
|
||||
set(DYNARMIC_FRONTENDS "A32" CACHE STRING "")
|
||||
add_subdirectory(third_party/dynarmic)
|
||||
add_compile_definitions(CPU_DYNARMIC)
|
||||
else()
|
||||
message(FATAL_ERROR "Currently unsupported CPU architecture")
|
||||
endif()
|
||||
add_subdirectory(third_party/teakra EXCLUDE_FROM_ALL)
|
||||
|
||||
set(SOURCE_FILES src/emulator.cpp src/io_file.cpp src/config.cpp
|
||||
src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp
|
||||
src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp
|
||||
src/http_server.cpp src/stb_image_write.c src/core/cheats.cpp src/core/action_replay.cpp
|
||||
src/discord_rpc.cpp src/lua.cpp src/memory_mapped_file.cpp src/miniaudio.cpp
|
||||
)
|
||||
set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
||||
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
||||
src/core/kernel/memory_management.cpp src/core/kernel/ports.cpp
|
||||
src/core/kernel/events.cpp src/core/kernel/threads.cpp
|
||||
src/core/kernel/address_arbiter.cpp src/core/kernel/error.cpp
|
||||
src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp
|
||||
src/core/kernel/idle_thread.cpp src/core/kernel/timers.cpp
|
||||
)
|
||||
set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp
|
||||
src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp
|
||||
src/core/services/ndm.cpp src/core/services/dsp.cpp src/core/services/cfg.cpp
|
||||
src/core/services/ptm.cpp src/core/services/mic.cpp src/core/services/cecd.cpp
|
||||
src/core/services/ac.cpp src/core/services/am.cpp src/core/services/boss.cpp
|
||||
src/core/services/frd.cpp src/core/services/nim.cpp src/core/services/mcu/mcu_hwc.cpp
|
||||
src/core/services/y2r.cpp src/core/services/cam.cpp src/core/services/ldr_ro.cpp
|
||||
src/core/services/act.cpp src/core/services/nfc.cpp src/core/services/dlp_srvr.cpp
|
||||
src/core/services/ir_user.cpp src/core/services/http.cpp src/core/services/soc.cpp
|
||||
src/core/services/ssl.cpp src/core/services/news_u.cpp src/core/services/amiibo_device.cpp
|
||||
src/core/services/csnd.cpp src/core/services/nwm_uds.cpp
|
||||
)
|
||||
set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp
|
||||
src/core/PICA/shader_interpreter.cpp src/core/PICA/dynapica/shader_rec.cpp
|
||||
src/core/PICA/dynapica/shader_rec_emitter_x64.cpp src/core/PICA/pica_hash.cpp
|
||||
src/core/PICA/dynapica/shader_rec_emitter_arm64.cpp
|
||||
)
|
||||
|
||||
set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/3dsx.cpp src/core/loader/lz77.cpp)
|
||||
set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp
|
||||
src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp
|
||||
src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp src/core/fs/archive_system_save_data.cpp
|
||||
)
|
||||
|
||||
set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp
|
||||
src/core/applets/error_applet.cpp
|
||||
)
|
||||
set(AUDIO_SOURCE_FILES src/core/audio/dsp_core.cpp src/core/audio/null_core.cpp src/core/audio/teakra_core.cpp
|
||||
src/core/audio/miniaudio_device.cpp
|
||||
)
|
||||
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
|
||||
|
||||
# Frontend source files
|
||||
if(NOT ANDROID)
|
||||
if(ENABLE_QT_GUI)
|
||||
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
||||
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp
|
||||
)
|
||||
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
|
||||
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
||||
)
|
||||
|
||||
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
||||
source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES})
|
||||
include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
include_directories(third_party/zep/include) # Include zep for text editor usage
|
||||
configure_file(third_party/zep/cmake/config_app.h.cmake ${CMAKE_BINARY_DIR}/zep_config/config_app.h)
|
||||
include_directories(${CMAKE_BINARY_DIR}/zep_config)
|
||||
else()
|
||||
set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp)
|
||||
set(FRONTEND_HEADER_FILES "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||
include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/renderer.hpp include/kernel/kernel.hpp
|
||||
include/dynarmic_cp15.hpp include/kernel/resource_limits.hpp include/kernel/kernel_types.hpp
|
||||
include/kernel/config_mem.hpp include/services/service_manager.hpp include/services/apt.hpp
|
||||
include/kernel/handles.hpp include/services/hid.hpp include/services/fs.hpp
|
||||
include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp include/renderer_null/renderer_null.hpp
|
||||
include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp
|
||||
include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp
|
||||
include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/loader/3dsx.hpp include/io_file.hpp
|
||||
include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_self_ncch.hpp
|
||||
include/services/dsp.hpp include/services/cfg.hpp include/services/region_codes.hpp
|
||||
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp
|
||||
include/services/mic.hpp include/services/cecd.hpp include/services/ac.hpp
|
||||
include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.hpp
|
||||
include/fs/archive_ext_save_data.hpp include/fs/archive_ncch.hpp include/services/mcu/mcu_hwc.hpp
|
||||
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ssl.hpp
|
||||
include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp
|
||||
include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp
|
||||
include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp
|
||||
include/PICA/dynapica/shader_rec_emitter_x64.hpp include/PICA/pica_hash.hpp include/result/result.hpp
|
||||
include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp
|
||||
include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp
|
||||
include/crypto/aes_engine.hpp include/metaprogramming.hpp include/PICA/pica_vertex.hpp
|
||||
include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
|
||||
include/action_replay.hpp include/renderer_sw/renderer_sw.hpp include/compiler_builtins.hpp
|
||||
include/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp
|
||||
include/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp
|
||||
include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp
|
||||
include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp
|
||||
include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp
|
||||
include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp include/applets/error_applet.hpp
|
||||
include/audio/dsp_core.hpp include/audio/null_core.hpp include/audio/teakra_core.hpp
|
||||
include/audio/miniaudio_device.hpp include/ring_buffer.hpp
|
||||
)
|
||||
|
||||
cmrc_add_resource_library(
|
||||
resources_console_fonts
|
||||
NAMESPACE ConsoleFonts
|
||||
WHENCE "src/core/services/fonts/"
|
||||
"src/core/services/fonts/CitraSharedFontUSRelocated.bin"
|
||||
)
|
||||
|
||||
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
||||
third_party/imgui/imgui_draw.cpp
|
||||
third_party/imgui/imgui_tables.cpp
|
||||
third_party/imgui/imgui_widgets.cpp
|
||||
third_party/imgui/imgui_demo.cpp
|
||||
|
||||
third_party/cityhash/cityhash.cpp
|
||||
third_party/xxhash/xxhash.c
|
||||
)
|
||||
|
||||
if(ENABLE_LUAJIT AND NOT ANDROID)
|
||||
# Build luv and libuv for Lua TCP server usage if we're not on Android
|
||||
include_directories(third_party/luv/src)
|
||||
include_directories(third_party/luv/deps/lua-compat-5.3/c-api)
|
||||
include_directories(third_party/libuv/include)
|
||||
set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/luv/src/luv.c)
|
||||
set(LIBUV_BUILD_SHARED OFF)
|
||||
|
||||
add_subdirectory(third_party/libuv)
|
||||
endif()
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
include_directories(third_party/duckstation)
|
||||
set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/duckstation/window_info.cpp third_party/duckstation/gl/context.cpp)
|
||||
|
||||
if(APPLE)
|
||||
set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/duckstation/gl/context_agl.mm)
|
||||
elseif(WIN32)
|
||||
set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/duckstation/gl/context_wgl.cpp)
|
||||
else()
|
||||
set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/duckstation/gl/context_egl.cpp third_party/duckstation/gl/context_egl_wayland.cpp
|
||||
third_party/duckstation/gl/context_egl_x11.cpp third_party/duckstation/gl/context_glx.cpp third_party/duckstation/gl/x11_window.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
source_group("Source Files\\Core" FILES ${SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Crypto" FILES ${CRYPTO_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Filesystem" FILES ${FS_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Applets" FILES ${APPLET_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Audio" FILES ${AUDIO_SOURCE_FILES})
|
||||
source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_FILES})
|
||||
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
|
||||
|
||||
set(RENDERER_GL_SOURCE_FILES "") # Empty by default unless we are compiling with the GL renderer
|
||||
set(RENDERER_VK_SOURCE_FILES "") # Empty by default unless we are compiling with the VK renderer
|
||||
|
||||
if(ENABLE_OPENGL)
|
||||
# This may look weird but opengl.hpp is our header even if it's in the third_party folder
|
||||
set(RENDERER_GL_INCLUDE_FILES third_party/opengl/opengl.hpp
|
||||
include/renderer_gl/renderer_gl.hpp include/renderer_gl/textures.hpp
|
||||
include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp
|
||||
include/renderer_gl/gl_state.hpp
|
||||
)
|
||||
|
||||
set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp
|
||||
src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp
|
||||
src/core/renderer_gl/gl_state.cpp src/host_shaders/opengl_display.frag
|
||||
src/host_shaders/opengl_display.vert src/host_shaders/opengl_vertex_shader.vert
|
||||
src/host_shaders/opengl_fragment_shader.frag
|
||||
)
|
||||
|
||||
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_GL_INCLUDE_FILES})
|
||||
source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES})
|
||||
|
||||
cmrc_add_resource_library(
|
||||
resources_renderer_gl
|
||||
NAMESPACE RendererGL
|
||||
WHENCE "src/host_shaders/"
|
||||
"src/host_shaders/opengl_display.frag"
|
||||
"src/host_shaders/opengl_display.vert"
|
||||
"src/host_shaders/opengl_vertex_shader.vert"
|
||||
"src/host_shaders/opengl_fragment_shader.frag"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ENABLE_VULKAN)
|
||||
find_package(
|
||||
Vulkan 1.3.206 REQUIRED
|
||||
COMPONENTS glslangValidator
|
||||
)
|
||||
|
||||
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
|
||||
include/renderer_vk/vk_api.hpp include/renderer_vk/vk_debug.hpp
|
||||
include/renderer_vk/vk_descriptor_heap.hpp
|
||||
include/renderer_vk/vk_descriptor_update_batch.hpp
|
||||
include/renderer_vk/vk_sampler_cache.hpp
|
||||
include/renderer_vk/vk_memory.hpp include/renderer_vk/vk_pica.hpp
|
||||
)
|
||||
|
||||
set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp
|
||||
src/core/renderer_vk/vk_api.cpp src/core/renderer_vk/vk_debug.cpp
|
||||
src/core/renderer_vk/vk_descriptor_heap.cpp
|
||||
src/core/renderer_vk/vk_descriptor_update_batch.cpp
|
||||
src/core/renderer_vk/vk_sampler_cache.cpp
|
||||
src/core/renderer_vk/vk_memory.cpp src/core/renderer_vk/vk_pica.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES})
|
||||
source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES})
|
||||
|
||||
set(RENDERER_VK_HOST_SHADERS_SOURCE
|
||||
"src/host_shaders/vulkan_display.frag"
|
||||
"src/host_shaders/vulkan_display.vert"
|
||||
)
|
||||
|
||||
set(RENDERER_VK_HOST_SHADERS_FLAGS -e main --target-env vulkan1.1)
|
||||
|
||||
if(GPU_DEBUG_INFO)
|
||||
# generate nonsemantic shader debug information with source
|
||||
set(RENDERER_VK_HOST_SHADERS_FLAGS ${RENDERER_VK_HOST_SHADERS_FLAGS} -gVS)
|
||||
else()
|
||||
set(RENDERER_VK_HOST_SHADERS_FLAGS ${RENDERER_VK_HOST_SHADERS_FLAGS} -g0)
|
||||
endif()
|
||||
|
||||
# Compile each vulkan shader into an .spv file
|
||||
foreach( HOST_SHADER_SOURCE ${RENDERER_VK_HOST_SHADERS_SOURCE} )
|
||||
get_filename_component( FILE_NAME ${HOST_SHADER_SOURCE} NAME )
|
||||
set( HOST_SHADER_SPIRV "${PROJECT_BINARY_DIR}/host_shaders/${FILE_NAME}.spv" )
|
||||
add_custom_command(
|
||||
OUTPUT ${HOST_SHADER_SPIRV}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||
COMMAND Vulkan::glslangValidator ${RENDERER_VK_HOST_SHADERS_FLAGS} -V "${PROJECT_SOURCE_DIR}/${HOST_SHADER_SOURCE}" -o ${HOST_SHADER_SPIRV}
|
||||
DEPENDS ${HOST_SHADER_SOURCE}
|
||||
)
|
||||
list( APPEND RENDERER_VK_HOST_SHADERS_SPIRV ${HOST_SHADER_SPIRV} )
|
||||
endforeach()
|
||||
|
||||
cmrc_add_resource_library(
|
||||
resources_renderer_vk
|
||||
NAMESPACE RendererVK
|
||||
WHENCE "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||
${RENDERER_VK_HOST_SHADERS_SPIRV}
|
||||
)
|
||||
endif()
|
||||
|
||||
source_group("Header Files\\Core" FILES ${HEADER_FILES})
|
||||
set(ALL_SOURCES ${SOURCE_FILES} ${FRONTEND_SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES}
|
||||
${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES}
|
||||
${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES})
|
||||
|
||||
if(ENABLE_OPENGL)
|
||||
# Add the OpenGL source files to ALL_SOURCES
|
||||
set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_GL_SOURCE_FILES})
|
||||
endif()
|
||||
|
||||
if(ENABLE_VULKAN)
|
||||
# Add the Vulkan source files to ALL_SOURCES
|
||||
set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_VK_SOURCE_FILES})
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
set(ALL_SOURCES ${ALL_SOURCES} src/jni_driver.cpp)
|
||||
endif()
|
||||
|
||||
if(BUILD_HYDRA_CORE)
|
||||
include_directories(third_party/hydra_core/include)
|
||||
add_library(Alber SHARED ${ALL_SOURCES} src/hydra_core.cpp)
|
||||
target_compile_definitions(Alber PRIVATE PANDA3DS_HYDRA_CORE=1)
|
||||
else()
|
||||
add_executable(Alber ${ALL_SOURCES})
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
target_link_libraries(Alber PRIVATE EGL log)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LTO OR ENABLE_USER_BUILD)
|
||||
set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(Alber PRIVATE dynarmic cryptopp glad resources_console_fonts teakra)
|
||||
|
||||
if(NOT ANDROID)
|
||||
target_link_libraries(Alber PRIVATE SDL2-static)
|
||||
endif()
|
||||
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_DISCORD_RPC=1")
|
||||
target_link_libraries(Alber PRIVATE discord-rpc)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LUAJIT)
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1")
|
||||
target_link_libraries(Alber PRIVATE libluajit)
|
||||
|
||||
# If we're not on Android, link libuv too
|
||||
if (NOT ANDROID)
|
||||
target_link_libraries(Alber PRIVATE uv_a)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_OPENGL)
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_OPENGL=1")
|
||||
target_link_libraries(Alber PRIVATE resources_renderer_gl)
|
||||
endif()
|
||||
|
||||
if(ENABLE_VULKAN)
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_VULKAN=1")
|
||||
target_link_libraries(Alber PRIVATE Vulkan::Vulkan resources_renderer_vk)
|
||||
endif()
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_FRONTEND_QT=1")
|
||||
target_compile_definitions(Alber PUBLIC "ZEP_QT=1")
|
||||
target_compile_definitions(Alber PUBLIC "ZEP_FEATURE_CPP_FILE_SYSTEM=1")
|
||||
|
||||
target_link_libraries(Alber PRIVATE Qt6::Widgets)
|
||||
|
||||
if(LINUX OR FREEBSD)
|
||||
find_package(X11 REQUIRED)
|
||||
target_link_libraries(Alber PRIVATE ${X11_LIBRARIES})
|
||||
|
||||
if(ENABLE_OPENGL)
|
||||
find_package(OpenGL REQUIRED COMPONENTS OpenGL EGL GLX)
|
||||
target_link_libraries(Alber PRIVATE OpenGL::OpenGL OpenGL::EGL OpenGL::GLX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
qt_add_resources(Alber "app_images"
|
||||
PREFIX "/"
|
||||
FILES
|
||||
docs/img/rsob_icon.png docs/img/rstarstruck_icon.png
|
||||
)
|
||||
else()
|
||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_FRONTEND_SDL=1")
|
||||
endif()
|
||||
|
||||
if(GPU_DEBUG_INFO)
|
||||
target_compile_definitions(Alber PRIVATE GPU_DEBUG_INFO=1)
|
||||
endif()
|
||||
|
||||
if(ENABLE_USER_BUILD)
|
||||
target_compile_definitions(Alber PRIVATE PANDA3DS_USER_BUILD=1)
|
||||
endif()
|
||||
|
||||
if(ENABLE_USER_BUILD OR DISABLE_PANIC_DEV)
|
||||
target_compile_definitions(Alber PRIVATE PANDA3DS_LIMITED_PANICS=1)
|
||||
endif()
|
||||
|
||||
if(ENABLE_HTTP_SERVER)
|
||||
target_compile_definitions(Alber PRIVATE PANDA3DS_ENABLE_HTTP_SERVER=1)
|
||||
endif()
|
||||
add_executable(${PROJECT_NAME} src/main.cpp)
|
||||
|
|
Before Width: | Height: | Size: 1 MiB |
Before Width: | Height: | Size: 71 KiB |
BIN
docs/img/MK7.png
Before Width: | Height: | Size: 389 KiB |
Before Width: | Height: | Size: 297 KiB |
Before Width: | Height: | Size: 2 MiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 8.9 KiB |
|
@ -1,13 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
#include "vertex_loader_rec.hpp"
|
||||
|
||||
// Common file for our PICA JITs (From vertex config -> CPU assembly and from PICA shader -> CPU assembly)
|
||||
|
||||
namespace Dynapica {
|
||||
#ifdef PANDA3DS_DYNAPICA_SUPPORTED
|
||||
static constexpr bool supported() { return true; }
|
||||
#else
|
||||
static constexpr bool supported() { return false; }
|
||||
#endif
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
#pragma once
|
||||
#include "PICA/shader.hpp"
|
||||
|
||||
#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && (defined(PANDA3DS_X64_HOST) || defined(PANDA3DS_ARM64_HOST))
|
||||
#define PANDA3DS_SHADER_JIT_SUPPORTED
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef PANDA3DS_X64_HOST
|
||||
#include "shader_rec_emitter_x64.hpp"
|
||||
#elif defined(PANDA3DS_ARM64_HOST)
|
||||
#include "shader_rec_emitter_arm64.hpp"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class ShaderJIT {
|
||||
#ifdef PANDA3DS_SHADER_JIT_SUPPORTED
|
||||
using Hash = PICAShader::Hash;
|
||||
using ShaderCache = std::unordered_map<Hash, std::unique_ptr<ShaderEmitter>>;
|
||||
ShaderEmitter::PrologueCallback prologueCallback;
|
||||
ShaderEmitter::InstructionCallback entrypointCallback;
|
||||
|
||||
ShaderCache cache;
|
||||
#endif
|
||||
|
||||
public:
|
||||
#ifdef PANDA3DS_SHADER_JIT_SUPPORTED
|
||||
// Call this before starting to process a batch of vertices
|
||||
// This will read the PICA config (uploaded shader and shader operand descriptors) and search if we've already compiled this shader
|
||||
// If yes, it sets it as the active shader. if not, then it compiles it, adds it to the cache, and sets it as active,
|
||||
// The caller must make sure the entrypoint has been properly set beforehand
|
||||
void prepare(PICAShader& shaderUnit);
|
||||
void reset();
|
||||
void run(PICAShader& shaderUnit) { prologueCallback(shaderUnit, entrypointCallback); }
|
||||
|
||||
static constexpr bool isAvailable() { return true; }
|
||||
#else
|
||||
void prepare(PICAShader& shaderUnit) {
|
||||
Helpers::panic("Vertex Loader JIT: Tried to run ShaderJIT::Prepare on platform that does not support shader jit");
|
||||
}
|
||||
|
||||
void run(PICAShader& shaderUnit) {
|
||||
Helpers::panic("Vertex Loader JIT: Tried to run ShaderJIT::Run on platform that does not support shader jit");
|
||||
}
|
||||
|
||||
// Define dummy callback. This should never be called if the shader JIT is not supported
|
||||
using Callback = void (*)(PICAShader& shaderUnit);
|
||||
Callback activeShaderCallback = nullptr;
|
||||
|
||||
void reset() {}
|
||||
static constexpr bool isAvailable() { return false; }
|
||||
#endif
|
||||
};
|
|
@ -1,134 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// Only do anything if we're on an x64 target with JIT support enabled
|
||||
#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_ARM64_HOST)
|
||||
#include <array>
|
||||
#include <oaknut/code_block.hpp>
|
||||
#include <oaknut/oaknut.hpp>
|
||||
|
||||
#include "PICA/shader.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
class ShaderEmitter : private oaknut::CodeBlock, public oaknut::CodeGenerator {
|
||||
static constexpr size_t executableMemorySize = PICAShader::maxInstructionCount * 96; // How much executable memory to alloc for each shader
|
||||
// Allocate some extra space as padding for security purposes in the extremely unlikely occasion we manage to overflow the above size
|
||||
static constexpr size_t allocSize = executableMemorySize + 0x1000;
|
||||
|
||||
// If the swizzle field is this value then the swizzle pattern is .xyzw so we don't need a shuffle
|
||||
static constexpr uint noSwizzle = 0x1B;
|
||||
|
||||
using f24 = Floats::f24;
|
||||
using vec4f = std::array<f24, 4>;
|
||||
|
||||
// An array of labels (incl pointers) to each compiled (to x64) PICA instruction
|
||||
std::array<oaknut::Label, PICAShader::maxInstructionCount> instructionLabels;
|
||||
// A vector of PCs that can potentially return based on the state of the PICA callstack.
|
||||
// Filled before compiling a shader by scanning the code for call instructions
|
||||
std::vector<u32> returnPCs;
|
||||
|
||||
// An array of 128-bit masks for blending registers together to perform masked writes.
|
||||
// Eg for writing only the x and y components, the mask is 0x00000000'00000000'FFFFFFFF'FFFF
|
||||
oaknut::Label blendMasks;
|
||||
|
||||
u32 recompilerPC = 0; // PC the recompiler is currently recompiling @
|
||||
u32 loopLevel = 0; // The current loop nesting level (0 = not in a loop)
|
||||
|
||||
// Shows whether the loaded shader has any log2 and exp2 instructions
|
||||
bool codeHasLog2 = false;
|
||||
bool codeHasExp2 = false;
|
||||
|
||||
oaknut::Label log2Func, exp2Func;
|
||||
oaknut::Label emitLog2Func();
|
||||
oaknut::Label emitExp2Func();
|
||||
|
||||
template <typename T>
|
||||
T getLabelPointer(const oaknut::Label& label) {
|
||||
auto pointer = reinterpret_cast<u8*>(oaknut::CodeBlock::ptr()) + label.offset();
|
||||
return reinterpret_cast<T>(pointer);
|
||||
}
|
||||
|
||||
// Compile all instructions from [current recompiler PC, end)
|
||||
void compileUntil(const PICAShader& shaderUnit, u32 endPC);
|
||||
// Compile instruction "instr"
|
||||
void compileInstruction(const PICAShader& shaderUnit);
|
||||
|
||||
bool isCall(u32 instruction) {
|
||||
const u32 opcode = instruction >> 26;
|
||||
return (opcode == ShaderOpcodes::CALL) || (opcode == ShaderOpcodes::CALLC) || (opcode == ShaderOpcodes::CALLU);
|
||||
}
|
||||
|
||||
// Scan the shader code for call instructions to fill up the returnPCs vector before starting compilation
|
||||
// We also scan for log2/exp2 instructions to see whether to emit the relevant functions
|
||||
void scanCode(const PICAShader& shaderUnit);
|
||||
|
||||
// Load register with number "srcReg" indexed by index "idx" into the arm64 register "reg"
|
||||
template <int sourceIndex>
|
||||
void loadRegister(oaknut::QReg dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor);
|
||||
void storeRegister(oaknut::QReg source, const PICAShader& shader, u32 dest, u32 operandDescriptor);
|
||||
|
||||
const vec4f& getSourceRef(const PICAShader& shader, u32 src);
|
||||
const vec4f& getDestRef(const PICAShader& shader, u32 dest);
|
||||
|
||||
// Check the value of the cmp register for instructions like ifc and callc
|
||||
// Result is returned in the zero flag. If the comparison is true then zero == 1, else zero == 0
|
||||
void checkCmpRegister(const PICAShader& shader, u32 instruction);
|
||||
|
||||
// Check the value of the bool uniform for instructions like ifu and callu
|
||||
// Result is returned in the zero flag. If the comparison is true then zero == 0, else zero == 1 (Opposite of checkCmpRegister)
|
||||
void checkBoolUniform(const PICAShader& shader, u32 instruction);
|
||||
|
||||
// Instruction recompilation functions
|
||||
void recADD(const PICAShader& shader, u32 instruction);
|
||||
void recCALL(const PICAShader& shader, u32 instruction);
|
||||
void recCALLC(const PICAShader& shader, u32 instruction);
|
||||
void recCALLU(const PICAShader& shader, u32 instruction);
|
||||
void recCMP(const PICAShader& shader, u32 instruction);
|
||||
void recDP3(const PICAShader& shader, u32 instruction);
|
||||
void recDP4(const PICAShader& shader, u32 instruction);
|
||||
void recDPH(const PICAShader& shader, u32 instruction);
|
||||
void recEMIT(const PICAShader& shader, u32 instruction);
|
||||
void recEND(const PICAShader& shader, u32 instruction);
|
||||
void recEX2(const PICAShader& shader, u32 instruction);
|
||||
void recFLR(const PICAShader& shader, u32 instruction);
|
||||
void recIFC(const PICAShader& shader, u32 instruction);
|
||||
void recIFU(const PICAShader& shader, u32 instruction);
|
||||
void recJMPC(const PICAShader& shader, u32 instruction);
|
||||
void recJMPU(const PICAShader& shader, u32 instruction);
|
||||
void recLG2(const PICAShader& shader, u32 instruction);
|
||||
void recLOOP(const PICAShader& shader, u32 instruction);
|
||||
void recMAD(const PICAShader& shader, u32 instruction);
|
||||
void recMAX(const PICAShader& shader, u32 instruction);
|
||||
void recMIN(const PICAShader& shader, u32 instruction);
|
||||
void recMOVA(const PICAShader& shader, u32 instruction);
|
||||
void recMOV(const PICAShader& shader, u32 instruction);
|
||||
void recMUL(const PICAShader& shader, u32 instruction);
|
||||
void recRCP(const PICAShader& shader, u32 instruction);
|
||||
void recRSQ(const PICAShader& shader, u32 instruction);
|
||||
void recSETEMIT(const PICAShader& shader, u32 instruction);
|
||||
void recSGE(const PICAShader& shader, u32 instruction);
|
||||
void recSLT(const PICAShader& shader, u32 instruction);
|
||||
|
||||
MAKE_LOG_FUNCTION(log, shaderJITLogger)
|
||||
|
||||
public:
|
||||
// Callback type used for instructions
|
||||
using InstructionCallback = const void (*)(PICAShader& shaderUnit);
|
||||
// Callback type used for the JIT prologue. This is what the caller will call
|
||||
using PrologueCallback = const void (*)(PICAShader& shaderUnit, InstructionCallback cb);
|
||||
|
||||
PrologueCallback prologueCb = nullptr;
|
||||
|
||||
// Initialize our emitter with "allocSize" bytes of memory allocated for the code buffer
|
||||
ShaderEmitter() : oaknut::CodeBlock(allocSize), oaknut::CodeGenerator(oaknut::CodeBlock::ptr()) {}
|
||||
|
||||
// PC must be a valid entrypoint here. It doesn't have that much overhead in this case, so we use std::array<>::at() to assert it does
|
||||
InstructionCallback getInstructionCallback(u32 pc) {
|
||||
return getLabelPointer<InstructionCallback>(instructionLabels.at(pc));
|
||||
}
|
||||
|
||||
PrologueCallback getPrologueCallback() { return prologueCb; }
|
||||
void compile(const PICAShader& shaderUnit);
|
||||
};
|
||||
|
||||
#endif // arm64 recompiler check
|
|
@ -1,152 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// Only do anything if we're on an x64 target with JIT support enabled
|
||||
#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST)
|
||||
#include <vector>
|
||||
|
||||
#include "PICA/shader.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "x64_regs.hpp"
|
||||
#include "xbyak/xbyak.h"
|
||||
#include "xbyak/xbyak_util.h"
|
||||
|
||||
class ShaderEmitter : public Xbyak::CodeGenerator {
|
||||
static constexpr size_t executableMemorySize = PICAShader::maxInstructionCount * 96; // How much executable memory to alloc for each shader
|
||||
// Allocate some extra space as padding for security purposes in the extremely unlikely occasion we manage to overflow the above size
|
||||
static constexpr size_t allocSize = executableMemorySize + 0x1000;
|
||||
|
||||
// If the swizzle field is this value then the swizzle pattern is .xyzw so we don't need a shuffle
|
||||
static constexpr uint noSwizzle = 0x1B;
|
||||
|
||||
using f24 = Floats::f24;
|
||||
using vec4f = std::array<f24, 4>;
|
||||
|
||||
// An array of labels (incl pointers) to each compiled (to x64) PICA instruction
|
||||
std::array<Xbyak::Label, PICAShader::maxInstructionCount> instructionLabels;
|
||||
// A vector of PCs that can potentially return based on the state of the PICA callstack.
|
||||
// Filled before compiling a shader by scanning the code for call instructions
|
||||
std::vector<u32> returnPCs;
|
||||
|
||||
// Vector value of (-0.0, -0.0, -0.0, -0.0) for negating vectors via pxor
|
||||
Label negateVector;
|
||||
// Vector value of (1.0, 1.0, 1.0, 1.0) for SLT(i)/SGE(i)
|
||||
Label onesVector;
|
||||
|
||||
u32 recompilerPC = 0; // PC the recompiler is currently recompiling @
|
||||
u32 loopLevel = 0; // The current loop nesting level (0 = not in a loop)
|
||||
|
||||
bool haveSSE4_1 = false; // Shows if the CPU supports SSE4.1
|
||||
bool haveAVX = false; // Shows if the CPU supports AVX (NOT AVX2, NOT AVX512. Regular AVX)
|
||||
bool haveFMA3 = false; // Shows if the CPU supports FMA3
|
||||
|
||||
// Shows whether the loaded shader has any log2 and exp2 instructions
|
||||
bool codeHasLog2 = false;
|
||||
bool codeHasExp2 = false;
|
||||
|
||||
Xbyak::Label log2Func, exp2Func;
|
||||
Xbyak::Label emitLog2Func();
|
||||
Xbyak::Label emitExp2Func();
|
||||
Xbyak::util::Cpu cpuCaps;
|
||||
|
||||
// Compile all instructions from [current recompiler PC, end)
|
||||
void compileUntil(const PICAShader& shaderUnit, u32 endPC);
|
||||
// Compile instruction "instr"
|
||||
void compileInstruction(const PICAShader& shaderUnit);
|
||||
|
||||
bool isCall(u32 instruction) {
|
||||
const u32 opcode = instruction >> 26;
|
||||
return (opcode == ShaderOpcodes::CALL) || (opcode == ShaderOpcodes::CALLC) || (opcode == ShaderOpcodes::CALLU);
|
||||
}
|
||||
|
||||
// Scan the shader code for call instructions to fill up the returnPCs vector before starting compilation
|
||||
// We also scan for log2/exp2 instructions to see whether to emit the relevant functions
|
||||
void scanCode(const PICAShader& shaderUnit);
|
||||
|
||||
// Load register with number "srcReg" indexed by index "idx" into the xmm register "reg"
|
||||
template <int sourceIndex>
|
||||
void loadRegister(Xmm dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor);
|
||||
void storeRegister(Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor);
|
||||
|
||||
const vec4f& getSourceRef(const PICAShader& shader, u32 src);
|
||||
const vec4f& getDestRef(const PICAShader& shader, u32 dest);
|
||||
|
||||
// Check the value of the cmp register for instructions like ifc and callc
|
||||
// Result is returned in the zero flag. If the comparison is true then zero == 1, else zero == 0
|
||||
void checkCmpRegister(const PICAShader& shader, u32 instruction);
|
||||
|
||||
// Check the value of the bool uniform for instructions like ifu and callu
|
||||
// Result is returned in the zero flag. If the comparison is true then zero == 0, else zero == 1 (Opposite of checkCmpRegister)
|
||||
void checkBoolUniform(const PICAShader& shader, u32 instruction);
|
||||
|
||||
// Prints a log. This is not meant to be used outside of debugging so it is very slow with our internal ABI.
|
||||
void emitPrintLog(const PICAShader& shaderUnit);
|
||||
static void printLog(const PICAShader& shaderUnit);
|
||||
|
||||
// Instruction recompilation functions
|
||||
void recADD(const PICAShader& shader, u32 instruction);
|
||||
void recCALL(const PICAShader& shader, u32 instruction);
|
||||
void recCALLC(const PICAShader& shader, u32 instruction);
|
||||
void recCALLU(const PICAShader& shader, u32 instruction);
|
||||
void recCMP(const PICAShader& shader, u32 instruction);
|
||||
void recDP3(const PICAShader& shader, u32 instruction);
|
||||
void recDP4(const PICAShader& shader, u32 instruction);
|
||||
void recDPH(const PICAShader& shader, u32 instruction);
|
||||
void recEMIT(const PICAShader& shader, u32 instruction);
|
||||
void recEND(const PICAShader& shader, u32 instruction);
|
||||
void recEX2(const PICAShader& shader, u32 instruction);
|
||||
void recFLR(const PICAShader& shader, u32 instruction);
|
||||
void recIFC(const PICAShader& shader, u32 instruction);
|
||||
void recIFU(const PICAShader& shader, u32 instruction);
|
||||
void recJMPC(const PICAShader& shader, u32 instruction);
|
||||
void recJMPU(const PICAShader& shader, u32 instruction);
|
||||
void recLG2(const PICAShader& shader, u32 instruction);
|
||||
void recLOOP(const PICAShader& shader, u32 instruction);
|
||||
void recMAD(const PICAShader& shader, u32 instruction);
|
||||
void recMAX(const PICAShader& shader, u32 instruction);
|
||||
void recMIN(const PICAShader& shader, u32 instruction);
|
||||
void recMOVA(const PICAShader& shader, u32 instruction);
|
||||
void recMOV(const PICAShader& shader, u32 instruction);
|
||||
void recMUL(const PICAShader& shader, u32 instruction);
|
||||
void recRCP(const PICAShader& shader, u32 instruction);
|
||||
void recRSQ(const PICAShader& shader, u32 instruction);
|
||||
void recSETEMIT(const PICAShader& shader, u32 instruction);
|
||||
void recSGE(const PICAShader& shader, u32 instruction);
|
||||
void recSLT(const PICAShader& shader, u32 instruction);
|
||||
|
||||
MAKE_LOG_FUNCTION(log, shaderJITLogger)
|
||||
|
||||
public:
|
||||
// Callback type used for instructions
|
||||
using InstructionCallback = const void (*)(PICAShader& shaderUnit);
|
||||
// Callback type used for the JIT prologue. This is what the caller will call
|
||||
using PrologueCallback = const void (*)(PICAShader& shaderUnit, InstructionCallback cb);
|
||||
|
||||
PrologueCallback prologueCb = nullptr;
|
||||
|
||||
// Initialize our emitter with "allocSize" bytes of RWX memory
|
||||
ShaderEmitter() : Xbyak::CodeGenerator(allocSize) {
|
||||
cpuCaps = Xbyak::util::Cpu();
|
||||
|
||||
haveSSE4_1 = cpuCaps.has(Xbyak::util::Cpu::tSSE41);
|
||||
haveAVX = cpuCaps.has(Xbyak::util::Cpu::tAVX);
|
||||
haveFMA3 = cpuCaps.has(Xbyak::util::Cpu::tFMA);
|
||||
|
||||
if (!cpuCaps.has(Xbyak::util::Cpu::tSSE3)) {
|
||||
Helpers::panic("This CPU does not support SSE3. Please use the shader interpreter instead");
|
||||
}
|
||||
}
|
||||
|
||||
void compile(const PICAShader& shaderUnit);
|
||||
|
||||
// PC must be a valid entrypoint here. It doesn't have that much overhead in this case, so we use std::array<>::at() to assert it does
|
||||
InstructionCallback getInstructionCallback(u32 pc) {
|
||||
// Cast away the constness because casting to a function pointer is hard otherwise. Legal as long as we don't write to *ptr
|
||||
uint8_t* ptr = const_cast<uint8_t*>(instructionLabels.at(pc).getAddress());
|
||||
return reinterpret_cast<InstructionCallback>(ptr);
|
||||
}
|
||||
|
||||
PrologueCallback getPrologueCallback() { return prologueCb; }
|
||||
};
|
||||
|
||||
#endif // x64 recompiler check
|
|
@ -1,28 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
#include "renderer_gl/renderer_gl.hpp"
|
||||
#include "x64_regs.hpp"
|
||||
|
||||
// Recompiler that takes the current vertex attribute configuration, ie the format of vertices (VAO in OpenGL) and emits optimized
|
||||
// code in our CPU's native architecture for loading vertices. Unimplemented at the moment, we've only got a skeleton of it here for later
|
||||
|
||||
class VertexLoaderJIT {
|
||||
using PICARegs = const std::array<u32, 0x300>&;
|
||||
using Callback = void(*)(Vertex* output, size_t count); // A function pointer to JIT-emitted code
|
||||
Callback compileConfig(PICARegs regs);
|
||||
|
||||
public:
|
||||
#if defined(PANDA3DS_DYNAPICA_SUPPORTED) && defined(PANDA3DS_X64_HOST)
|
||||
#define PANDA3DS_VERTEX_LOADER_JIT_SUPPORTED
|
||||
|
||||
void loadVertices(Vertex* output, size_t count, PICARegs regs);
|
||||
static constexpr bool isAvailable() { return true; }
|
||||
|
||||
#else
|
||||
void loadVertices(Vertex* output, size_t count, PICARegs regs) {
|
||||
Helpers::panic("Vertex Loader JIT: Tried to load vertices with JIT on platform that does not support vertex loader jit");
|
||||
}
|
||||
|
||||
static constexpr bool isAvailable() { return false; }
|
||||
#endif
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef PANDA3DS_X64_HOST
|
||||
#include "xbyak/xbyak.h"
|
||||
using namespace Xbyak;
|
||||
using namespace Xbyak::util;
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
|
||||
#define PANDA3DS_MS_ABI
|
||||
constexpr Reg32 arg1 = ecx; // register where first arg is stored
|
||||
constexpr Reg32 arg2 = edx; // register where second arg is stored
|
||||
constexpr Reg32 arg3 = r8d; // register where third arg is stored
|
||||
constexpr Reg32 arg4 = r9d; // register where fourth arg is stored
|
||||
|
||||
// Similar for floating point and vector arguemnts.
|
||||
constexpr Xmm arg1f = xmm0;
|
||||
constexpr Xmm arg2f = xmm1;
|
||||
constexpr Xmm arg3f = xmm2;
|
||||
constexpr Xmm arg4f = xmm3;
|
||||
|
||||
constexpr bool isWindows() { return true; }
|
||||
|
||||
#else // System V calling convention
|
||||
#define PANDA3DS_SYSV_ABI
|
||||
constexpr Reg32 arg1 = edi;
|
||||
constexpr Reg32 arg2 = esi;
|
||||
constexpr Reg32 arg3 = edx;
|
||||
constexpr Reg32 arg4 = ecx;
|
||||
|
||||
constexpr Xmm arg1f = xmm0;
|
||||
constexpr Xmm arg2f = xmm1;
|
||||
constexpr Xmm arg3f = xmm2;
|
||||
constexpr Xmm arg4f = xmm3;
|
||||
constexpr Xmm arg5f = xmm4;
|
||||
constexpr Xmm arg6f = xmm5;
|
||||
constexpr Xmm arg7f = xmm6;
|
||||
constexpr Xmm arg8f = xmm7;
|
||||
|
||||
constexpr bool isWindows() { return false; }
|
||||
#endif
|
||||
#endif // PANDA3DS_X64_HOST
|
|
@ -1,161 +0,0 @@
|
|||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
// Slightly adapted for the purposes of this project
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace Floats {
|
||||
/**
|
||||
* Template class for converting arbitrary Pica float types to IEEE 754 32-bit single-precision
|
||||
* floating point.
|
||||
*
|
||||
* When decoding, format is as follows:
|
||||
* - The first `M` bits are the mantissa
|
||||
* - The next `E` bits are the exponent
|
||||
* - The last bit is the sign bit
|
||||
*
|
||||
* @todo Verify on HW if this conversion is sufficiently accurate.
|
||||
*/
|
||||
template <unsigned M, unsigned E>
|
||||
struct Float {
|
||||
public:
|
||||
static Float<M, E> fromFloat32(float val) {
|
||||
Float<M, E> ret;
|
||||
ret.value = val;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Float<M, E> fromRaw(u32 hex) {
|
||||
Float<M, E> res;
|
||||
|
||||
const int width = M + E + 1;
|
||||
const int bias = 128 - (1 << (E - 1));
|
||||
int exponent = (hex >> M) & ((1 << E) - 1);
|
||||
const unsigned mantissa = hex & ((1 << M) - 1);
|
||||
const unsigned sign = (hex >> (E + M)) << 31;
|
||||
|
||||
if (hex & ((1 << (width - 1)) - 1)) {
|
||||
if (exponent == (1 << E) - 1)
|
||||
exponent = 255;
|
||||
else
|
||||
exponent += bias;
|
||||
hex = sign | (mantissa << (23 - M)) | (exponent << 23);
|
||||
}
|
||||
else {
|
||||
hex = sign;
|
||||
}
|
||||
|
||||
std::memcpy(&res.value, &hex, sizeof(float));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static Float<M, E> zero() {
|
||||
return fromFloat32(0.f);
|
||||
}
|
||||
|
||||
// Not recommended for anything but logging
|
||||
float toFloat32() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
double toFloat64() const {
|
||||
return static_cast<double>(value);
|
||||
}
|
||||
|
||||
operator float() {
|
||||
return toFloat32();
|
||||
}
|
||||
|
||||
operator double() {
|
||||
return toFloat64();
|
||||
}
|
||||
|
||||
Float<M, E> operator*(const Float<M, E>& flt) const {
|
||||
float result = value * flt.toFloat32();
|
||||
// PICA gives 0 instead of NaN when multiplying by inf
|
||||
if (std::isnan(result))
|
||||
if (!std::isnan(value) && !std::isnan(flt.toFloat32()))
|
||||
result = 0.f;
|
||||
return Float<M, E>::fromFloat32(result);
|
||||
}
|
||||
|
||||
Float<M, E> operator/(const Float<M, E>& flt) const {
|
||||
return Float<M, E>::fromFloat32(toFloat32() / flt.toFloat32());
|
||||
}
|
||||
|
||||
Float<M, E> operator+(const Float<M, E>& flt) const {
|
||||
return Float<M, E>::fromFloat32(toFloat32() + flt.toFloat32());
|
||||
}
|
||||
|
||||
Float<M, E> operator-(const Float<M, E>& flt) const {
|
||||
return Float<M, E>::fromFloat32(toFloat32() - flt.toFloat32());
|
||||
}
|
||||
|
||||
Float<M, E>& operator*=(const Float<M, E>& flt) {
|
||||
value = operator*(flt).value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Float<M, E>& operator/=(const Float<M, E>& flt) {
|
||||
value /= flt.toFloat32();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Float<M, E>& operator+=(const Float<M, E>& flt) {
|
||||
value += flt.toFloat32();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Float<M, E>& operator-=(const Float<M, E>& flt) {
|
||||
value -= flt.toFloat32();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Float<M, E> operator-() const {
|
||||
return Float<M, E>::fromFloat32(-toFloat32());
|
||||
}
|
||||
|
||||
bool operator<(const Float<M, E>& flt) const {
|
||||
return toFloat32() < flt.toFloat32();
|
||||
}
|
||||
|
||||
bool operator>(const Float<M, E>& flt) const {
|
||||
return toFloat32() > flt.toFloat32();
|
||||
}
|
||||
|
||||
bool operator>=(const Float<M, E>& flt) const {
|
||||
return toFloat32() >= flt.toFloat32();
|
||||
}
|
||||
|
||||
bool operator<=(const Float<M, E>& flt) const {
|
||||
return toFloat32() <= flt.toFloat32();
|
||||
}
|
||||
|
||||
bool operator==(const Float<M, E>& flt) const {
|
||||
return toFloat32() == flt.toFloat32();
|
||||
}
|
||||
|
||||
bool operator!=(const Float<M, E>& flt) const {
|
||||
return toFloat32() != flt.toFloat32();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr unsigned MASK = (1 << (M + E + 1)) - 1;
|
||||
static constexpr unsigned MANTISSA_MASK = (1 << M) - 1;
|
||||
static constexpr unsigned EXPONENT_MASK = (1 << E) - 1;
|
||||
|
||||
// Stored as a regular float, merely for convenience
|
||||
// TODO: Perform proper arithmetic on this!
|
||||
float value;
|
||||
};
|
||||
|
||||
using f24 = Float<16, 7>;
|
||||
using f20 = Float<12, 7>;
|
||||
using f16 = Float<10, 5>;
|
||||
|
||||
} // namespace Floats
|
|
@ -1,170 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
|
||||
#include "PICA/dynapica/shader_rec.hpp"
|
||||
#include "PICA/float_types.hpp"
|
||||
#include "PICA/pica_vertex.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "PICA/shader_unit.hpp"
|
||||
#include "config.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "renderer.hpp"
|
||||
|
||||
class GPU {
|
||||
static constexpr u32 regNum = 0x300;
|
||||
static constexpr u32 extRegNum = 0x1000;
|
||||
|
||||
using vec4f = std::array<Floats::f24, 4>;
|
||||
using Registers = std::array<u32, regNum>; // Internal registers (named registers in short since they're the main ones)
|
||||
using ExternalRegisters = std::array<u32, extRegNum>;
|
||||
|
||||
Memory& mem;
|
||||
EmulatorConfig& config;
|
||||
ShaderUnit shaderUnit;
|
||||
ShaderJIT shaderJIT; // Doesn't do anything if JIT is disabled or not supported
|
||||
|
||||
u8* vram = nullptr;
|
||||
MAKE_LOG_FUNCTION(log, gpuLogger)
|
||||
|
||||
static constexpr u32 maxAttribCount = 12; // Up to 12 vertex attributes
|
||||
static constexpr u32 vramSize = u32(6_MB);
|
||||
Registers regs; // GPU internal registers
|
||||
std::array<vec4f, 16> currentAttributes; // Vertex attributes before being passed to the shader
|
||||
|
||||
std::array<vec4f, 16> immediateModeAttributes; // Vertex attributes uploaded via immediate mode submission
|
||||
std::array<PICA::Vertex, 3> immediateModeVertices;
|
||||
uint immediateModeVertIndex;
|
||||
uint immediateModeAttrIndex; // Index of the immediate mode attribute we're uploading
|
||||
|
||||
template <bool indexed, bool useShaderJIT>
|
||||
void drawArrays();
|
||||
|
||||
// Silly method of avoiding linking problems. TODO: Change to something less silly
|
||||
void drawArrays(bool indexed);
|
||||
|
||||
struct AttribInfo {
|
||||
u32 offset = 0; // Offset from base vertex array
|
||||
int size = 0; // Bytes per vertex
|
||||
u32 config1 = 0;
|
||||
u32 config2 = 0;
|
||||
u32 componentCount = 0; // Number of components for the attribute
|
||||
|
||||
u64 getConfigFull() { return u64(config1) | (u64(config2) << 32); }
|
||||
};
|
||||
|
||||
u64 getVertexShaderInputConfig() {
|
||||
return u64(regs[PICA::InternalRegs::VertexShaderInputCfgLow]) | (u64(regs[PICA::InternalRegs::VertexShaderInputCfgHigh]) << 32);
|
||||
}
|
||||
|
||||
std::array<AttribInfo, maxAttribCount> attributeInfo; // Info for each of the 12 attributes
|
||||
u32 totalAttribCount = 0; // Number of vertex attributes to send to VS
|
||||
u32 fixedAttribMask = 0; // Which attributes are fixed?
|
||||
|
||||
u32 fixedAttribIndex = 0; // Which fixed attribute are we writing to ([0, 11] range)
|
||||
u32 fixedAttribCount = 0; // How many attribute components have we written? When we get to 4 the attr will actually get submitted
|
||||
std::array<u32, 3> fixedAttrBuff; // Buffer to hold fixed attributes in until they get submitted
|
||||
|
||||
// Command processor pointers for GPU command lists
|
||||
u32* cmdBuffStart = nullptr;
|
||||
u32* cmdBuffEnd = nullptr;
|
||||
u32* cmdBuffCurr = nullptr;
|
||||
|
||||
std::unique_ptr<Renderer> renderer;
|
||||
PICA::Vertex getImmediateModeVertex();
|
||||
|
||||
public:
|
||||
// 256 entries per LUT with each LUT as its own row forming a 2D image 256 * LUT_COUNT
|
||||
// Encoded in PICA native format
|
||||
static constexpr size_t LightingLutSize = PICA::Lights::LUT_Count * 256;
|
||||
std::array<uint32_t, LightingLutSize> lightingLUT;
|
||||
|
||||
// Used to prevent uploading the lighting_lut on every draw call
|
||||
// Set to true when the CPU writes to the lighting_lut
|
||||
// Set to false by the renderer when the lighting_lut is uploaded ot the GPU
|
||||
bool lightingLUTDirty = false;
|
||||
|
||||
GPU(Memory& mem, EmulatorConfig& config);
|
||||
void display() { renderer->display(); }
|
||||
void screenshot(const std::string& name) { renderer->screenshot(name); }
|
||||
void deinitGraphicsContext() { renderer->deinitGraphicsContext(); }
|
||||
|
||||
#if defined(PANDA3DS_FRONTEND_SDL)
|
||||
void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); }
|
||||
#elif defined(PANDA3DS_FRONTEND_QT)
|
||||
void initGraphicsContext(GL::Context* context) { renderer->initGraphicsContext(context); }
|
||||
#endif
|
||||
|
||||
void fireDMA(u32 dest, u32 source, u32 size);
|
||||
void reset();
|
||||
|
||||
Registers& getRegisters() { return regs; }
|
||||
ExternalRegisters& getExtRegisters() { return externalRegs; }
|
||||
void startCommandList(u32 addr, u32 size);
|
||||
|
||||
// Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked
|
||||
u32 readReg(u32 address);
|
||||
void writeReg(u32 address, u32 value);
|
||||
|
||||
u32 readExternalReg(u32 index);
|
||||
void writeExternalReg(u32 index, u32 value);
|
||||
|
||||
// Used when processing GPU command lists
|
||||
u32 readInternalReg(u32 index);
|
||||
void writeInternalReg(u32 index, u32 value, u32 mask);
|
||||
|
||||
// Used for setting the size of the window we'll be outputting graphics to
|
||||
void setOutputSize(u32 width, u32 height) { renderer->setOutputSize(width, height); }
|
||||
|
||||
// TODO: Emulate the transfer engine & its registers
|
||||
// Then this can be emulated by just writing the appropriate values there
|
||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { renderer->clearBuffer(startAddress, endAddress, value, control); }
|
||||
|
||||
// TODO: Emulate the transfer engine & its registers
|
||||
// Then this can be emulated by just writing the appropriate values there
|
||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
renderer->displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
|
||||
}
|
||||
|
||||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
renderer->textureCopy(inputAddr, outputAddr, totalBytes, inputSize, outputSize, flags);
|
||||
}
|
||||
|
||||
// Read a value of type T from physical address paddr
|
||||
// This is necessary because vertex attribute fetching uses physical addresses
|
||||
template <typename T>
|
||||
T readPhysical(u32 paddr) {
|
||||
if (paddr >= PhysicalAddrs::FCRAM && paddr <= PhysicalAddrs::FCRAMEnd) {
|
||||
u8* fcram = mem.getFCRAM();
|
||||
u32 index = paddr - PhysicalAddrs::FCRAM;
|
||||
|
||||
return *(T*)&fcram[index];
|
||||
} else {
|
||||
Helpers::panic("[PICA] Read unimplemented paddr %08X", paddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a pointer of type T* to the data starting from physical address paddr
|
||||
template <typename T>
|
||||
T* getPointerPhys(u32 paddr, u32 size = 0) {
|
||||
if (paddr >= PhysicalAddrs::FCRAM && paddr + size <= PhysicalAddrs::FCRAMEnd) {
|
||||
u8* fcram = mem.getFCRAM();
|
||||
u32 index = paddr - PhysicalAddrs::FCRAM;
|
||||
|
||||
return (T*)&fcram[index];
|
||||
} else if (paddr >= PhysicalAddrs::VRAM && paddr + size <= PhysicalAddrs::VRAMEnd) {
|
||||
u32 index = paddr - PhysicalAddrs::VRAM;
|
||||
return (T*)&vram[index];
|
||||
} else [[unlikely]] {
|
||||
Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
|
||||
}
|
||||
}
|
||||
|
||||
Renderer* getRenderer() { return renderer.get(); }
|
||||
private:
|
||||
// GPU external registers
|
||||
// We have them in the end of the struct for cache locality reasons. Tl;dr we want the more commonly used things to be packed in the start
|
||||
// Of the struct, instead of externalRegs being in the middle
|
||||
ExternalRegisters externalRegs;
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
// Defines to pick which hash algorithm to use for the PICA (For hashing shaders, etc)
|
||||
// Please only define one of them
|
||||
// Available algorithms:
|
||||
// xxh3: 64-bit non-cryptographic hash using SIMD, default.
|
||||
// Google CityHash64: 64-bit non-cryptographic hash, generated using regular 64-bit arithmetic
|
||||
|
||||
//#define PANDA3DS_PICA_CITYHASH
|
||||
#define PANDA3DS_PICA_XXHASH3
|
||||
|
||||
namespace PICAHash {
|
||||
#if defined(PANDA3DS_PICA_CITYHASH) || defined(PANDA3DS_PICA_XXHASH3)
|
||||
using HashType = std::uint64_t;
|
||||
#endif
|
||||
|
||||
HashType computeHash(const char* buf, std::size_t len);
|
||||
} // namespace PICAHash
|
|
@ -1,45 +0,0 @@
|
|||
#pragma once
|
||||
#include "PICA/float_types.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace PICA {
|
||||
// A representation of the output vertex as it comes out of the vertex shader, with padding and all
|
||||
struct Vertex {
|
||||
using vec2f = std::array<Floats::f24, 2>;
|
||||
using vec3f = std::array<Floats::f24, 3>;
|
||||
using vec4f = std::array<Floats::f24, 4>;
|
||||
|
||||
union {
|
||||
struct {
|
||||
vec4f positions; // Vertex position
|
||||
vec4f quaternion; // Quaternion specifying the normal/tangent frame (for fragment lighting)
|
||||
vec4f colour; // Vertex color
|
||||
vec2f texcoord0; // Texcoords for texture unit 0 (Only U and V, W is stored separately for 3D textures!)
|
||||
vec2f texcoord1; // Texcoords for TU 1
|
||||
Floats::f24 texcoord0_w; // W component for texcoord 0 if using a 3D texture
|
||||
u32 padding; // Unused
|
||||
|
||||
vec3f view; // View vector (for fragment lighting)
|
||||
u32 padding2; // Unused
|
||||
vec2f texcoord2; // Texcoords for TU 2
|
||||
} s;
|
||||
|
||||
// The software, non-accelerated vertex loader writes here and then reads specific components from the above struct
|
||||
Floats::f24 raw[0x20];
|
||||
};
|
||||
Vertex() {}
|
||||
};
|
||||
} // namespace PICA
|
||||
|
||||
// Float is used here instead of Floats::f24 to ensure that Floats::f24 is properly sized for direct interpretations as a float by the render backend
|
||||
#define ASSERT_POS(member, pos) static_assert(offsetof(PICA::Vertex, s.member) == pos * sizeof(float), "PICA::Vertex struct is broken!");
|
||||
|
||||
ASSERT_POS(positions, 0)
|
||||
ASSERT_POS(quaternion, 4)
|
||||
ASSERT_POS(colour, 8)
|
||||
ASSERT_POS(texcoord0, 12)
|
||||
ASSERT_POS(texcoord1, 14)
|
||||
ASSERT_POS(texcoord0_w, 16)
|
||||
ASSERT_POS(view, 18)
|
||||
ASSERT_POS(texcoord2, 22)
|
||||
#undef ASSERT_POS
|
|
@ -1,347 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace PICA {
|
||||
namespace InternalRegs {
|
||||
enum : u32 {
|
||||
// Rasterizer registers
|
||||
ViewportWidth = 0x41,
|
||||
ViewportInvw = 0x42,
|
||||
ViewportHeight = 0x43,
|
||||
ViewportInvh = 0x44,
|
||||
|
||||
// Clipping plane control
|
||||
ClipEnable = 0x47,
|
||||
ClipData0 = 0x48,
|
||||
ClipData1 = 0x49,
|
||||
ClipData2 = 0x4A,
|
||||
ClipData3 = 0x4B,
|
||||
|
||||
DepthScale = 0x4D,
|
||||
DepthOffset = 0x4E,
|
||||
ShaderOutputCount = 0x4F,
|
||||
ShaderOutmap0 = 0x50,
|
||||
|
||||
ViewportXY = 0x68,
|
||||
DepthmapEnable = 0x6D,
|
||||
|
||||
// Texture registers
|
||||
TexUnitCfg = 0x80,
|
||||
Tex0BorderColor = 0x81,
|
||||
Tex1BorderColor = 0x91,
|
||||
Tex2BorderColor = 0x99,
|
||||
TexEnvUpdateBuffer = 0xE0,
|
||||
TexEnvBufferColor = 0xFD,
|
||||
|
||||
// clang-format off
|
||||
#define defineTexEnv(index, offset) \
|
||||
TexEnv##index##Source = offset + 0, \
|
||||
TexEnv##index##Operand = offset + 1, \
|
||||
TexEnv##index##Combiner = offset + 2, \
|
||||
TexEnv##index##Color = offset + 3, \
|
||||
TexEnv##index##Scale = offset + 4,
|
||||
|
||||
defineTexEnv(0, 0xC0)
|
||||
defineTexEnv(1, 0xC8)
|
||||
defineTexEnv(2, 0xD0)
|
||||
defineTexEnv(3, 0xD8)
|
||||
defineTexEnv(4, 0xF0)
|
||||
defineTexEnv(5, 0xF8)
|
||||
|
||||
#undef defineTexEnv
|
||||
// clang-format on
|
||||
|
||||
// Framebuffer registers
|
||||
ColourOperation = 0x100,
|
||||
BlendFunc = 0x101,
|
||||
LogicOp = 0x102,
|
||||
BlendColour = 0x103,
|
||||
AlphaTestConfig = 0x104,
|
||||
StencilTest = 0x105,
|
||||
StencilOp = 0x106,
|
||||
DepthAndColorMask = 0x107,
|
||||
DepthBufferWrite = 0x115,
|
||||
DepthBufferFormat = 0x116,
|
||||
ColourBufferFormat = 0x117,
|
||||
DepthBufferLoc = 0x11C,
|
||||
ColourBufferLoc = 0x11D,
|
||||
FramebufferSize = 0x11E,
|
||||
|
||||
//LightingRegs
|
||||
LightingLUTIndex = 0x01C5,
|
||||
LightingLUTData0 = 0x01C8,
|
||||
LightingLUTData1 = 0x01C9,
|
||||
LightingLUTData2 = 0x01CA,
|
||||
LightingLUTData3 = 0x01CB,
|
||||
LightingLUTData4 = 0x01CC,
|
||||
LightingLUTData5 = 0x01CD,
|
||||
LightingLUTData6 = 0x01CE,
|
||||
LightingLUTData7 = 0x01CF,
|
||||
|
||||
// Geometry pipeline registers
|
||||
VertexAttribLoc = 0x200,
|
||||
AttribFormatLow = 0x201,
|
||||
AttribFormatHigh = 0x202,
|
||||
IndexBufferConfig = 0x227,
|
||||
VertexCountReg = 0x228,
|
||||
VertexOffsetReg = 0x22A,
|
||||
SignalDrawArrays = 0x22E,
|
||||
SignalDrawElements = 0x22F,
|
||||
|
||||
Attrib0Offset = 0x203,
|
||||
Attrib1Offset = 0x206,
|
||||
Attrib2Offset = 0x209,
|
||||
Attrib3Offset = 0x20C,
|
||||
Attrib4Offset = 0x20F,
|
||||
Attrib5Offset = 0x212,
|
||||
Attrib6Offset = 0x215,
|
||||
Attrib7Offset = 0x218,
|
||||
Attrib8Offset = 0x21B,
|
||||
Attrib9Offset = 0x21E,
|
||||
Attrib10Offset = 0x221,
|
||||
Attrib11Offset = 0x224,
|
||||
|
||||
Attrib0Config2 = 0x205,
|
||||
Attrib1Config2 = 0x208,
|
||||
Attrib2Config2 = 0x20B,
|
||||
Attrib3Config2 = 0x20E,
|
||||
Attrib4Config2 = 0x211,
|
||||
Attrib5Config2 = 0x214,
|
||||
Attrib6Config2 = 0x217,
|
||||
Attrib7Config2 = 0x21A,
|
||||
Attrib8Config2 = 0x21D,
|
||||
Attrib9Config2 = 0x220,
|
||||
Attrib10Config2 = 0x223,
|
||||
Attrib11Config2 = 0x226,
|
||||
|
||||
AttribInfoStart = Attrib0Offset,
|
||||
AttribInfoEnd = Attrib11Config2,
|
||||
|
||||
// Fixed attribute registers
|
||||
FixedAttribIndex = 0x232,
|
||||
FixedAttribData0 = 0x233,
|
||||
FixedAttribData1 = 0x234,
|
||||
FixedAttribData2 = 0x235,
|
||||
|
||||
// Command processor registers
|
||||
CmdBufSize0 = 0x238,
|
||||
CmdBufSize1 = 0x239,
|
||||
CmdBufAddr0 = 0x23A,
|
||||
CmdBufAddr1 = 0x23B,
|
||||
CmdBufTrigger0 = 0x23C,
|
||||
CmdBufTrigger1 = 0x23D,
|
||||
|
||||
PrimitiveConfig = 0x25E,
|
||||
PrimitiveRestart = 0x25F,
|
||||
|
||||
// Vertex shader registers
|
||||
VertexShaderAttrNum = 0x242,
|
||||
VertexBoolUniform = 0x2B0,
|
||||
VertexIntUniform0 = 0x2B1,
|
||||
VertexIntUniform1 = 0x2B2,
|
||||
VertexIntUniform2 = 0x2B3,
|
||||
VertexIntUniform3 = 0x2B4,
|
||||
|
||||
VertexShaderEntrypoint = 0x2BA,
|
||||
VertexShaderTransferEnd = 0x2BF,
|
||||
VertexFloatUniformIndex = 0x2C0,
|
||||
VertexFloatUniformData0 = 0x2C1,
|
||||
VertexFloatUniformData1 = 0x2C2,
|
||||
VertexFloatUniformData2 = 0x2C3,
|
||||
VertexFloatUniformData3 = 0x2C4,
|
||||
VertexFloatUniformData4 = 0x2C5,
|
||||
VertexFloatUniformData5 = 0x2C6,
|
||||
VertexFloatUniformData6 = 0x2C7,
|
||||
VertexFloatUniformData7 = 0x2C8,
|
||||
|
||||
VertexShaderInputBufferCfg = 0x2B9,
|
||||
VertexShaderInputCfgLow = 0x2BB,
|
||||
VertexShaderInputCfgHigh = 0x2BC,
|
||||
|
||||
VertexShaderTransferIndex = 0x2CB,
|
||||
VertexShaderData0 = 0x2CC,
|
||||
VertexShaderData1 = 0x2CD,
|
||||
VertexShaderData2 = 0x2CE,
|
||||
VertexShaderData3 = 0x2CF,
|
||||
VertexShaderData4 = 0x2D0,
|
||||
VertexShaderData5 = 0x2D1,
|
||||
VertexShaderData6 = 0x2D2,
|
||||
VertexShaderData7 = 0x2D3,
|
||||
VertexShaderOpDescriptorIndex = 0x2D5,
|
||||
VertexShaderOpDescriptorData0 = 0x2D6,
|
||||
VertexShaderOpDescriptorData1 = 0x2D7,
|
||||
VertexShaderOpDescriptorData2 = 0x2D8,
|
||||
VertexShaderOpDescriptorData3 = 0x2D9,
|
||||
VertexShaderOpDescriptorData4 = 0x2DA,
|
||||
VertexShaderOpDescriptorData5 = 0x2DB,
|
||||
VertexShaderOpDescriptorData6 = 0x2DC,
|
||||
VertexShaderOpDescriptorData7 = 0x2DD,
|
||||
};
|
||||
}
|
||||
|
||||
namespace ExternalRegs {
|
||||
enum : u32 {
|
||||
MemFill1BufferStartPaddr = 0x3,
|
||||
MemFill1BufferEndPAddr = 0x4,
|
||||
MemFill1Value = 0x5,
|
||||
MemFill1Control = 0x6,
|
||||
MemFill2BufferStartPaddr = 0x7,
|
||||
MemFill2BufferEndPAddr = 0x8,
|
||||
MemFill2Value = 0x9,
|
||||
MemFill2Control = 0xA,
|
||||
VramBankControl = 0xB,
|
||||
GPUBusy = 0xC,
|
||||
BacklightControl = 0xBC,
|
||||
Framebuffer0Size = 0x118,
|
||||
Framebuffer0AFirstAddr = 0x119,
|
||||
Framebuffer0ASecondAddr = 0x11A,
|
||||
Framebuffer0Config = 0x11B,
|
||||
Framebuffer0Select = 0x11D,
|
||||
Framebuffer0Stride = 0x123,
|
||||
Framebuffer0BFirstAddr = 0x124,
|
||||
Framebuffer0BSecondAddr = 0x125,
|
||||
Framebuffer1Size = 0x156,
|
||||
Framebuffer1AFirstAddr = 0x159,
|
||||
Framebuffer1ASecondAddr = 0x15A,
|
||||
Framebuffer1Config = 0x15B,
|
||||
Framebuffer1Select = 0x15D,
|
||||
Framebuffer1Stride = 0x163,
|
||||
Framebuffer1BFirstAddr = 0x164,
|
||||
Framebuffer1BSecondAddr = 0x165,
|
||||
TransferInputPAddr = 0x2FF,
|
||||
TransferOutputPAddr = 0x300,
|
||||
DisplayTransferOutputDim = 0x301,
|
||||
DisplayTransferInputDim = 0x302,
|
||||
TransferFlags = 0x303,
|
||||
TransferTrigger = 0x305,
|
||||
TextureCopyTotalBytes = 0x307,
|
||||
TextureCopyInputLineGap = 0x308,
|
||||
TextureCopyOutputLineGap = 0x309,
|
||||
};
|
||||
}
|
||||
|
||||
enum class Scaling : u32 {
|
||||
None = 0,
|
||||
X = 1,
|
||||
XY = 2,
|
||||
};
|
||||
|
||||
namespace Lights {
|
||||
enum : u32 {
|
||||
LUT_D0 = 0,
|
||||
LUT_D1,
|
||||
LUT_FR,
|
||||
LUT_RB,
|
||||
LUT_RG,
|
||||
LUT_RR,
|
||||
LUT_SP0 = 0x8,
|
||||
LUT_SP1,
|
||||
LUT_SP2,
|
||||
LUT_SP3,
|
||||
LUT_SP4,
|
||||
LUT_SP5,
|
||||
LUT_SP6,
|
||||
LUT_SP7,
|
||||
LUT_DA0 = 0x10,
|
||||
LUT_DA1,
|
||||
LUT_DA2,
|
||||
LUT_DA3,
|
||||
LUT_DA4,
|
||||
LUT_DA5,
|
||||
LUT_DA6,
|
||||
LUT_DA7,
|
||||
LUT_Count
|
||||
};
|
||||
}
|
||||
|
||||
enum class TextureFmt : u32 {
|
||||
RGBA8 = 0x0,
|
||||
RGB8 = 0x1,
|
||||
RGBA5551 = 0x2,
|
||||
RGB565 = 0x3,
|
||||
RGBA4 = 0x4,
|
||||
IA8 = 0x5,
|
||||
RG8 = 0x6,
|
||||
I8 = 0x7,
|
||||
A8 = 0x8,
|
||||
IA4 = 0x9,
|
||||
I4 = 0xA,
|
||||
A4 = 0xB,
|
||||
ETC1 = 0xC,
|
||||
ETC1A4 = 0xD,
|
||||
};
|
||||
|
||||
enum class ColorFmt : u32 {
|
||||
RGBA8 = 0x0,
|
||||
RGB8 = 0x1,
|
||||
RGBA5551 = 0x2,
|
||||
RGB565 = 0x3,
|
||||
RGBA4 = 0x4,
|
||||
};
|
||||
|
||||
enum class DepthFmt : u32 {
|
||||
Depth16 = 0,
|
||||
Unknown1 = 1, // Technically selectable, but function is unknown
|
||||
Depth24 = 2,
|
||||
Depth24Stencil8 = 3,
|
||||
};
|
||||
|
||||
// Returns the string representation of a texture format
|
||||
inline constexpr const char* textureFormatToString(TextureFmt fmt) {
|
||||
switch (fmt) {
|
||||
case TextureFmt::RGBA8: return "RGBA8";
|
||||
case TextureFmt::RGB8: return "RGB8";
|
||||
case TextureFmt::RGBA5551: return "RGBA5551";
|
||||
case TextureFmt::RGB565: return "RGB565";
|
||||
case TextureFmt::RGBA4: return "RGBA4";
|
||||
case TextureFmt::IA8: return "IA8";
|
||||
case TextureFmt::RG8: return "RG8";
|
||||
case TextureFmt::I8: return "I8";
|
||||
case TextureFmt::A8: return "A8";
|
||||
case TextureFmt::IA4: return "IA4";
|
||||
case TextureFmt::I4: return "I4";
|
||||
case TextureFmt::A4: return "A4";
|
||||
case TextureFmt::ETC1: return "ETC1";
|
||||
case TextureFmt::ETC1A4: return "ETC1A4";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr const char* textureFormatToString(ColorFmt fmt) {
|
||||
return textureFormatToString(static_cast<TextureFmt>(fmt));
|
||||
}
|
||||
|
||||
inline constexpr bool hasStencil(DepthFmt format) { return format == PICA::DepthFmt::Depth24Stencil8; }
|
||||
|
||||
// Size occupied by each pixel in bytes
|
||||
|
||||
// All formats are 16BPP except for RGBA8 (32BPP) and BGR8 (24BPP)
|
||||
inline constexpr usize sizePerPixel(TextureFmt format) {
|
||||
switch (format) {
|
||||
case TextureFmt::RGB8: return 3;
|
||||
case TextureFmt::RGBA8: return 4;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr usize sizePerPixel(ColorFmt format) {
|
||||
return sizePerPixel(static_cast<TextureFmt>(format));
|
||||
}
|
||||
|
||||
inline constexpr usize sizePerPixel(DepthFmt format) {
|
||||
switch (format) {
|
||||
case DepthFmt::Depth16: return 2;
|
||||
case DepthFmt::Depth24: return 3;
|
||||
case DepthFmt::Depth24Stencil8: return 4;
|
||||
default: return 1; // Invalid format
|
||||
}
|
||||
}
|
||||
|
||||
enum class PrimType : u32 {
|
||||
TriangleList = 0,
|
||||
TriangleStrip = 1,
|
||||
TriangleFan = 2,
|
||||
GeometryPrimitive = 3,
|
||||
};
|
||||
|
||||
} // namespace PICA
|
|
@ -1,296 +0,0 @@
|
|||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
#include "PICA/float_types.hpp"
|
||||
#include "PICA/pica_hash.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
enum class ShaderType {
|
||||
Vertex,
|
||||
Geometry,
|
||||
};
|
||||
|
||||
namespace ShaderOpcodes {
|
||||
enum : u32 {
|
||||
ADD = 0x00,
|
||||
DP3 = 0x01,
|
||||
DP4 = 0x02,
|
||||
DPH = 0x03,
|
||||
DST = 0x04,
|
||||
EX2 = 0x05,
|
||||
LG2 = 0x06,
|
||||
LIT = 0x07,
|
||||
MUL = 0x08,
|
||||
SGE = 0x09,
|
||||
SLT = 0x0A,
|
||||
FLR = 0x0B,
|
||||
MAX = 0x0C,
|
||||
MIN = 0x0D,
|
||||
RCP = 0x0E,
|
||||
RSQ = 0x0F,
|
||||
MOVA = 0x12,
|
||||
MOV = 0x13,
|
||||
DPHI = 0x18,
|
||||
DSTI = 0x19,
|
||||
SGEI = 0x1A,
|
||||
SLTI = 0x1B,
|
||||
BREAK = 0x20,
|
||||
NOP = 0x21,
|
||||
END = 0x22,
|
||||
BREAKC = 0x23,
|
||||
CALL = 0x24,
|
||||
CALLC = 0x25,
|
||||
CALLU = 0x26,
|
||||
IFU = 0x27,
|
||||
IFC = 0x28,
|
||||
LOOP = 0x29,
|
||||
EMIT = 0x2A,
|
||||
SETEMIT = 0x2B,
|
||||
JMPC = 0x2C,
|
||||
JMPU = 0x2D,
|
||||
CMP1 = 0x2E, // Both of these instructions are CMP
|
||||
CMP2 = 0x2F,
|
||||
MAD = 0x38 // Everything between 0x38-0x3F is a MAD but fuck it
|
||||
};
|
||||
}
|
||||
|
||||
// Note: All PICA f24 vec4 registers must have the alignas(16) specifier to make them easier to access in SSE/NEON code in the JIT
|
||||
class PICAShader {
|
||||
using f24 = Floats::f24;
|
||||
using vec4f = std::array<f24, 4>;
|
||||
|
||||
struct Loop {
|
||||
u32 startingPC; // PC at the start of the loop
|
||||
u32 endingPC; // PC at the end of the loop
|
||||
u32 iterations; // How many iterations of the loop to run
|
||||
u32 increment; // How much to increment the loop counter after each iteration
|
||||
};
|
||||
|
||||
// Info for ifc/ifu stack
|
||||
struct ConditionalInfo {
|
||||
u32 endingPC; // PC at the end of the if block (= DST)
|
||||
u32 newPC; // PC after the if block is done executing (= DST + NUM)
|
||||
};
|
||||
|
||||
struct CallInfo {
|
||||
u32 endingPC; // PC at the end of the function
|
||||
u32 returnPC; // PC to return to after the function ends
|
||||
};
|
||||
|
||||
int bufferIndex; // Index of the next instruction to overwrite for shader uploads
|
||||
int opDescriptorIndex; // Index of the next operand descriptor we'll overwrite
|
||||
u32 floatUniformIndex = 0; // Which float uniform are we writing to? ([0, 95] range)
|
||||
u32 floatUniformWordCount = 0; // How many words have we buffered for the current uniform transfer?
|
||||
bool f32UniformTransfer = false; // Are we transferring an f32 uniform or an f24 uniform?
|
||||
|
||||
std::array<u32, 4> floatUniformBuffer; // Buffer for temporarily caching float uniform data
|
||||
|
||||
public:
|
||||
// These are placed close to the temp registers and co because it helps the JIT generate better code
|
||||
u32 entrypoint = 0; // Initial shader PC
|
||||
u32 boolUniform;
|
||||
std::array<std::array<u8, 4>, 4> intUniforms;
|
||||
alignas(16) std::array<vec4f, 96> floatUniforms;
|
||||
|
||||
alignas(16) std::array<vec4f, 16> fixedAttributes; // Fixed vertex attributes
|
||||
alignas(16) std::array<vec4f, 16> inputs; // Attributes passed to the shader
|
||||
alignas(16) std::array<vec4f, 16> outputs;
|
||||
alignas(16) vec4f dummy = vec4f({f24::zero(), f24::zero(), f24::zero(), f24::zero()}); // Dummy register used by the JIT
|
||||
|
||||
protected:
|
||||
std::array<u32, 128> operandDescriptors;
|
||||
alignas(16) std::array<vec4f, 16> tempRegisters; // General purpose registers the shader can use for temp values
|
||||
std::array<s32, 2> addrRegister; // Address register
|
||||
bool cmpRegister[2]; // Comparison registers where the result of CMP is stored in
|
||||
u32 loopCounter;
|
||||
|
||||
u32 pc = 0; // Program counter: Index of the next instruction we're going to execute
|
||||
u32 loopIndex = 0; // The index of our loop stack (0 = empty, 4 = full)
|
||||
u32 ifIndex = 0; // The index of our IF stack
|
||||
u32 callIndex = 0; // The index of our CALL stack
|
||||
|
||||
std::array<Loop, 4> loopInfo;
|
||||
std::array<ConditionalInfo, 8> conditionalInfo;
|
||||
std::array<CallInfo, 4> callInfo;
|
||||
ShaderType type;
|
||||
|
||||
// We use a hashmap for matching 3DS shaders to their equivalent compiled code in our shader cache in the shader JIT
|
||||
// We choose our hash type to be a 64-bit integer by default, as the collision chance is very tiny and generating it is decently optimal
|
||||
// Ideally we want to be able to support multiple different types of hash depending on compilation settings, but let's get this working first
|
||||
using Hash = PICAHash::HashType;
|
||||
|
||||
Hash lastCodeHash = 0; // Last hash computed for the shader code (Used for the JIT caching mechanism)
|
||||
Hash lastOpdescHash = 0; // Last hash computed for the operand descriptors (Also used for the JIT)
|
||||
|
||||
bool codeHashDirty = false;
|
||||
bool opdescHashDirty = false;
|
||||
|
||||
// Add these as friend classes for the JIT so it has access to all important state
|
||||
friend class ShaderJIT;
|
||||
friend class ShaderEmitter;
|
||||
|
||||
vec4f getSource(u32 source);
|
||||
vec4f& getDest(u32 dest);
|
||||
|
||||
private:
|
||||
// Interpreter functions for the various shader functions
|
||||
void add(u32 instruction);
|
||||
void call(u32 instruction);
|
||||
void callc(u32 instruction);
|
||||
void callu(u32 instruction);
|
||||
void cmp(u32 instruction);
|
||||
void dp3(u32 instruction);
|
||||
void dp4(u32 instruction);
|
||||
void dphi(u32 instruction);
|
||||
void ex2(u32 instruction);
|
||||
void flr(u32 instruction);
|
||||
void ifc(u32 instruction);
|
||||
void ifu(u32 instruction);
|
||||
void jmpc(u32 instruction);
|
||||
void jmpu(u32 instruction);
|
||||
void lg2(u32 instruction);
|
||||
void loop(u32 instruction);
|
||||
void mad(u32 instruction);
|
||||
void madi(u32 instruction);
|
||||
void max(u32 instruction);
|
||||
void min(u32 instruction);
|
||||
void mov(u32 instruction);
|
||||
void mova(u32 instruction);
|
||||
void mul(u32 instruction);
|
||||
void rcp(u32 instruction);
|
||||
void rsq(u32 instruction);
|
||||
void sge(u32 instruction);
|
||||
void sgei(u32 instruction);
|
||||
void slt(u32 instruction);
|
||||
void slti(u32 instruction);
|
||||
|
||||
// src1, src2 and src3 have different negation & component swizzle bits in the operand descriptor
|
||||
// https://problemkaputt.github.io/gbatek.htm#3dsgpushaderinstructionsetopcodesummary in the
|
||||
// "Shader Operand Descriptors" section
|
||||
template <int sourceIndex>
|
||||
vec4f swizzle(vec4f& source, u32 opDescriptor) {
|
||||
vec4f ret;
|
||||
u32 compSwizzle;
|
||||
bool negate;
|
||||
|
||||
using namespace Helpers;
|
||||
if constexpr (sourceIndex == 1) { // SRC1
|
||||
negate = (getBit<4>(opDescriptor)) != 0;
|
||||
compSwizzle = getBits<5, 8>(opDescriptor);
|
||||
} else if constexpr (sourceIndex == 2) { // SRC2
|
||||
negate = (getBit<13>(opDescriptor)) != 0;
|
||||
compSwizzle = getBits<14, 8>(opDescriptor);
|
||||
} else if constexpr (sourceIndex == 3) { // SRC3
|
||||
negate = (getBit<22>(opDescriptor)) != 0;
|
||||
compSwizzle = getBits<23, 8>(opDescriptor);
|
||||
}
|
||||
|
||||
// Iterate through every component of the swizzled vector in reverse order
|
||||
// And get which source component's index to match it with
|
||||
for (int comp = 0; comp < 4; comp++) {
|
||||
int index = compSwizzle & 3; // Get index for this component
|
||||
compSwizzle >>= 2; // Move to next component index
|
||||
ret[3 - comp] = source[index];
|
||||
}
|
||||
|
||||
// Negate result if the negate bit is set
|
||||
if (negate) {
|
||||
ret[0] = -ret[0];
|
||||
ret[1] = -ret[1];
|
||||
ret[2] = -ret[2];
|
||||
ret[3] = -ret[3];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <int sourceIndex>
|
||||
vec4f getSourceSwizzled(u32 source, u32 opDescriptor) {
|
||||
vec4f srcVector = getSource(source);
|
||||
srcVector = swizzle<sourceIndex>(srcVector, opDescriptor);
|
||||
|
||||
return srcVector;
|
||||
}
|
||||
|
||||
u8 getIndexedSource(u32 source, u32 index);
|
||||
bool isCondTrue(u32 instruction);
|
||||
|
||||
public:
|
||||
static constexpr size_t maxInstructionCount = 4096;
|
||||
std::array<u32, maxInstructionCount> loadedShader; // Currently loaded & active shader
|
||||
std::array<u32, maxInstructionCount> bufferedShader; // Shader to be transferred when the SH_CODETRANSFER_END reg gets written to
|
||||
|
||||
PICAShader(ShaderType type) : type(type) {}
|
||||
|
||||
// Theese functions are in the header to be inlined more easily, though with LTO I hope I'll be able to move them
|
||||
void finalize() { std::memcpy(&loadedShader[0], &bufferedShader[0], 4096 * sizeof(u32)); }
|
||||
|
||||
void setBufferIndex(u32 index) { bufferIndex = index & 0xfff; }
|
||||
void setOpDescriptorIndex(u32 index) { opDescriptorIndex = index & 0x7f; }
|
||||
|
||||
void uploadWord(u32 word) {
|
||||
if (bufferIndex >= 4095) {
|
||||
Helpers::panic("o no, shader upload overflew");
|
||||
}
|
||||
|
||||
bufferedShader[bufferIndex++] = word;
|
||||
bufferIndex &= 0xfff;
|
||||
|
||||
codeHashDirty = true; // Signal the JIT if necessary that the program hash has potentially changed
|
||||
}
|
||||
|
||||
void uploadDescriptor(u32 word) {
|
||||
operandDescriptors[opDescriptorIndex++] = word;
|
||||
opDescriptorIndex &= 0x7f;
|
||||
|
||||
opdescHashDirty = true; // Signal the JIT if necessary that the program hash has potentially changed
|
||||
}
|
||||
|
||||
void setFloatUniformIndex(u32 word) {
|
||||
floatUniformIndex = word & 0xff;
|
||||
floatUniformWordCount = 0;
|
||||
f32UniformTransfer = (word & 0x80000000) != 0;
|
||||
}
|
||||
|
||||
void uploadFloatUniform(u32 word) {
|
||||
floatUniformBuffer[floatUniformWordCount++] = word;
|
||||
if (floatUniformIndex >= 96) {
|
||||
Helpers::panic("[PICA] Tried to write float uniform %d", floatUniformIndex);
|
||||
}
|
||||
|
||||
if ((f32UniformTransfer && floatUniformWordCount >= 4) || (!f32UniformTransfer && floatUniformWordCount >= 3)) {
|
||||
vec4f& uniform = floatUniforms[floatUniformIndex++];
|
||||
floatUniformWordCount = 0;
|
||||
|
||||
if (f32UniformTransfer) {
|
||||
uniform[0] = f24::fromFloat32(*(float*)&floatUniformBuffer[3]);
|
||||
uniform[1] = f24::fromFloat32(*(float*)&floatUniformBuffer[2]);
|
||||
uniform[2] = f24::fromFloat32(*(float*)&floatUniformBuffer[1]);
|
||||
uniform[3] = f24::fromFloat32(*(float*)&floatUniformBuffer[0]);
|
||||
} else {
|
||||
uniform[0] = f24::fromRaw(floatUniformBuffer[2] & 0xffffff);
|
||||
uniform[1] = f24::fromRaw(((floatUniformBuffer[1] & 0xffff) << 8) | (floatUniformBuffer[2] >> 24));
|
||||
uniform[2] = f24::fromRaw(((floatUniformBuffer[0] & 0xff) << 16) | (floatUniformBuffer[1] >> 16));
|
||||
uniform[3] = f24::fromRaw(floatUniformBuffer[0] >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uploadIntUniform(int index, u32 word) {
|
||||
using namespace Helpers;
|
||||
|
||||
auto& u = intUniforms[index];
|
||||
u[0] = word & 0xff;
|
||||
u[1] = getBits<8, 8>(word);
|
||||
u[2] = getBits<16, 8>(word);
|
||||
u[3] = getBits<24, 8>(word);
|
||||
}
|
||||
|
||||
void run();
|
||||
void reset();
|
||||
|
||||
Hash getCodeHash();
|
||||
Hash getOpdescHash();
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
#pragma once
|
||||
#include "PICA/shader.hpp"
|
||||
|
||||
class ShaderUnit {
|
||||
|
||||
public:
|
||||
PICAShader vs; // Vertex shader
|
||||
PICAShader gs; // Geometry shader
|
||||
|
||||
ShaderUnit() : vs(ShaderType::Vertex), gs(ShaderType::Geometry) {}
|
||||
void reset();
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "services/hid.hpp"
|
||||
|
||||
class ActionReplay {
|
||||
using Cheat = std::vector<u32>; // A cheat is really just a bunch of 64-bit opcodes neatly encoded into 32-bit chunks
|
||||
static constexpr size_t ifStackSize = 32; // TODO: How big is this, really?
|
||||
|
||||
u32 offset1, offset2; // Memory offset registers. Non-persistent.
|
||||
u32 data1, data2; // Data offset registers. Non-persistent.
|
||||
u32 storage1, storage2; // Storage registers. Persistent.
|
||||
|
||||
// When an instruction does not specify which offset or data register to use, we use the "active" one
|
||||
// Which is by default #1 and may be changed by certain AR operations
|
||||
u32 *activeOffset, *activeData, *activeStorage;
|
||||
u32 ifStackIndex; // Our index in the if stack. Shows how many entries we have at the moment.
|
||||
u32 loopStackIndex; // Same but for loops
|
||||
std::bitset<32> ifStack;
|
||||
|
||||
// Program counter
|
||||
u32 pc = 0;
|
||||
Memory& mem;
|
||||
HIDService& hid;
|
||||
|
||||
// Has the cheat ended?
|
||||
bool running = false;
|
||||
// Run 1 AR instruction
|
||||
void runInstruction(const Cheat& cheat, u32 instruction);
|
||||
|
||||
// Action Replay has a billion D-type opcodes so this handles all of them
|
||||
void executeDType(const Cheat& cheat, u32 instruction);
|
||||
|
||||
u8 read8(u32 addr);
|
||||
u16 read16(u32 addr);
|
||||
u32 read32(u32 addr);
|
||||
|
||||
void write8(u32 addr, u8 value);
|
||||
void write16(u32 addr, u16 value);
|
||||
void write32(u32 addr, u32 value);
|
||||
|
||||
void pushConditionBlock(bool condition);
|
||||
|
||||
public:
|
||||
ActionReplay(Memory& mem, HIDService& hid);
|
||||
void runCheat(const Cheat& cheat);
|
||||
void reset();
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace AndroidUtils {
|
||||
int openDocument(const char* directory, const char* mode);
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "kernel/kernel_types.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
namespace Applets {
|
||||
namespace AppletIDs {
|
||||
enum : u32 {
|
||||
None = 0,
|
||||
SysAppletMask = 0x100,
|
||||
HomeMenu = 0x101,
|
||||
AltMenu = 0x103,
|
||||
Camera = 0x110,
|
||||
Friends = 0x112,
|
||||
GameNotes = 0x113,
|
||||
Browser = 0x114,
|
||||
InstructionManual = 0x115,
|
||||
Notifications = 0x116,
|
||||
Miiverse = 0x117,
|
||||
MiiversePosting = 0x118,
|
||||
AmiiboSettings = 0x119,
|
||||
SysLibraryAppletMask = 0x200,
|
||||
SoftwareKeyboard = 0x201,
|
||||
MiiSelector = 0x202,
|
||||
PNote = 0x204, // TODO: What dis?
|
||||
SNote = 0x205, // What is this too?
|
||||
ErrDisp = 0x206,
|
||||
EshopMint = 0x207,
|
||||
CirclePadProCalib = 0x208,
|
||||
Notepad = 0x209,
|
||||
Application = 0x300,
|
||||
EshopTiger = 0x301,
|
||||
LibraryAppletMask = 0x400,
|
||||
SoftwareKeyboard2 = 0x401,
|
||||
MiiSelector2 = 0x402,
|
||||
Pnote2 = 0x404,
|
||||
SNote2 = 0x405,
|
||||
ErrDisp2 = 0x406,
|
||||
EshopMint2 = 0x407,
|
||||
CirclePadProCalib2 = 0x408,
|
||||
Notepad2 = 0x409,
|
||||
};
|
||||
}
|
||||
|
||||
enum class APTSignal : u32 {
|
||||
None = 0x0,
|
||||
Wakeup = 0x1,
|
||||
Request = 0x2,
|
||||
Response = 0x3,
|
||||
Exit = 0x4,
|
||||
Message = 0x5,
|
||||
HomeButtonSingle = 0x6,
|
||||
HomeButtonDouble = 0x7,
|
||||
DspSleep = 0x8,
|
||||
DspWakeup = 0x9,
|
||||
WakeupByExit = 0xA,
|
||||
WakeupByPause = 0xB,
|
||||
WakeupByCancel = 0xC,
|
||||
WakeupByCancelAll = 0xD,
|
||||
WakeupByPowerButtonClick = 0xE,
|
||||
WakeupToJumpHome = 0xF,
|
||||
RequestForSysApplet = 0x10,
|
||||
WakeupToLaunchApplication = 0x11,
|
||||
};
|
||||
|
||||
struct Parameter {
|
||||
u32 senderID; // ID of the parameter sender
|
||||
u32 destID; // ID of the app to receive parameter
|
||||
u32 signal; // Signal type (eg request)
|
||||
u32 object; // Some applets will also respond with shared memory handles for transferring data between the sender and called
|
||||
std::vector<u8> data; // Misc data
|
||||
};
|
||||
|
||||
class AppletBase {
|
||||
protected:
|
||||
Memory& mem;
|
||||
std::optional<Parameter>& nextParameter;
|
||||
|
||||
public:
|
||||
virtual const char* name() = 0;
|
||||
|
||||
// Called by APT::StartLibraryApplet and similar
|
||||
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) = 0;
|
||||
// Transfer parameters from application -> applet
|
||||
virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0;
|
||||
virtual void reset() = 0;
|
||||
|
||||
AppletBase(Memory& mem, std::optional<Parameter>& nextParam) : mem(mem), nextParameter(nextParam) {}
|
||||
};
|
||||
} // namespace Applets
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
#include <optional>
|
||||
|
||||
#include "applets/error_applet.hpp"
|
||||
#include "applets/mii_selector.hpp"
|
||||
#include "applets/software_keyboard.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
namespace Applets {
|
||||
class AppletManager {
|
||||
MiiSelectorApplet miiSelector;
|
||||
SoftwareKeyboardApplet swkbd;
|
||||
ErrorApplet error;
|
||||
std::optional<Applets::Parameter> nextParameter = std::nullopt;
|
||||
|
||||
public:
|
||||
AppletManager(Memory& mem);
|
||||
void reset();
|
||||
AppletBase* getApplet(u32 id);
|
||||
|
||||
Applets::Parameter glanceParameter();
|
||||
Applets::Parameter receiveParameter();
|
||||
};
|
||||
} // namespace Applets
|
|
@ -1,15 +0,0 @@
|
|||
#include <string>
|
||||
|
||||
#include "applets/applet.hpp"
|
||||
|
||||
namespace Applets {
|
||||
class ErrorApplet final : public AppletBase {
|
||||
public:
|
||||
virtual const char* name() override { return "Error/EULA Agreement"; }
|
||||
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) override;
|
||||
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
||||
virtual void reset() override;
|
||||
|
||||
ErrorApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
||||
};
|
||||
} // namespace Applets
|
|
@ -1,83 +0,0 @@
|
|||
#include <string>
|
||||
|
||||
#include "applets/applet.hpp"
|
||||
#include "swap.hpp"
|
||||
|
||||
namespace Applets {
|
||||
struct MiiConfig {
|
||||
u8 enableCancelButton;
|
||||
u8 enableGuestMii;
|
||||
u8 showOnTopScreen;
|
||||
std::array<u8, 0x5> pad1;
|
||||
std::array<u16_le, 0x40> title;
|
||||
std::array<u8, 0x4> pad2;
|
||||
u8 showGuestMiis;
|
||||
std::array<u8, 0x3> pad3;
|
||||
u32 initiallySelectedIndex;
|
||||
std::array<u8, 0x6> guestMiiWhitelist;
|
||||
std::array<u8, 0x64> userMiiWhitelist;
|
||||
std::array<u8, 0x2> pad4;
|
||||
u32 magicValue;
|
||||
};
|
||||
static_assert(sizeof(MiiConfig) == 0x104, "Mii config size is wrong");
|
||||
|
||||
// Some members of this struct are not properly aligned so we need pragma pack
|
||||
#pragma pack(push, 1)
|
||||
struct MiiData {
|
||||
u8 version;
|
||||
u8 miiOptions;
|
||||
u8 miiPos;
|
||||
u8 consoleID;
|
||||
|
||||
u64_be systemID;
|
||||
u32_be miiID;
|
||||
std::array<u8, 0x6> creatorMAC;
|
||||
u16 padding;
|
||||
|
||||
u16_be miiDetails;
|
||||
std::array<char16_t, 0xA> miiName;
|
||||
u8 height;
|
||||
u8 width;
|
||||
|
||||
u8 faceStyle;
|
||||
u8 faceDetails;
|
||||
u8 hairStyle;
|
||||
u8 hairDetails;
|
||||
u32_be eyeDetails;
|
||||
u32_be eyebrowDetails;
|
||||
u16_be noseDetails;
|
||||
u16_be mouthDetails;
|
||||
u16_be moustacheDetails;
|
||||
u16_be beardDetails;
|
||||
u16_be glassesDetails;
|
||||
u16_be moleDetails;
|
||||
|
||||
std::array<char16_t, 0xA> authorName;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(MiiData) == 0x5C, "MiiData structure has incorrect size");
|
||||
|
||||
struct MiiResult {
|
||||
u32_be returnCode;
|
||||
u32_be isGuestMiiSelected;
|
||||
u32_be selectedGuestMiiIndex;
|
||||
MiiData selectedMiiData;
|
||||
u16_be unknown1;
|
||||
u16_be miiChecksum;
|
||||
std::array<u16_le, 0xC> guestMiiName;
|
||||
};
|
||||
static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
|
||||
|
||||
class MiiSelectorApplet final : public AppletBase {
|
||||
public:
|
||||
virtual const char* name() override { return "Mii Selector"; }
|
||||
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) override;
|
||||
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
||||
virtual void reset() override;
|
||||
|
||||
MiiResult output;
|
||||
MiiConfig config;
|
||||
MiiResult getDefaultMii();
|
||||
MiiSelectorApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
||||
};
|
||||
} // namespace Applets
|
|
@ -1,162 +0,0 @@
|
|||
#include <array>
|
||||
|
||||
#include "applets/applet.hpp"
|
||||
#include "swap.hpp"
|
||||
|
||||
namespace Applets {
|
||||
// Software keyboard definitions adapted from libctru/Citra
|
||||
// Keyboard input filtering flags. Allows the caller to specify what input is explicitly not allowed
|
||||
namespace SoftwareKeyboardFilter {
|
||||
enum Filter : u32 {
|
||||
Digits = 1, // Disallow the use of more than a certain number of digits (0 or more)
|
||||
At = 1 << 1, // Disallow the use of the @ sign.
|
||||
Percent = 1 << 2, // Disallow the use of the % sign.
|
||||
Backslash = 1 << 3, // Disallow the use of the \ sign.
|
||||
Profanity = 1 << 4, // Disallow profanity using Nintendo's profanity filter.
|
||||
Callback = 1 << 5, // Use a callback in order to check the input.
|
||||
};
|
||||
} // namespace SoftwareKeyboardFilter
|
||||
|
||||
// Keyboard features.
|
||||
namespace SoftwareKeyboardFeature {
|
||||
enum Feature {
|
||||
Parental = 1, // Parental PIN mode.
|
||||
DarkenTopScreen = 1 << 1, // Darken the top screen when the keyboard is shown.
|
||||
PredictiveInput = 1 << 2, // Enable predictive input (necessary for Kanji input in JPN systems).
|
||||
Multiline = 1 << 3, // Enable multiline input.
|
||||
FixedWidth = 1 << 4, // Enable fixed-width mode.
|
||||
AllowHome = 1 << 5, // Allow the usage of the HOME button.
|
||||
AllowReset = 1 << 6, // Allow the usage of a software-reset combination.
|
||||
AllowPower = 1 << 7, // Allow the usage of the POWER button.
|
||||
DefaultQWERTY = 1 << 9, // Default to the QWERTY page when the keyboard is shown.
|
||||
};
|
||||
} // namespace SoftwareKeyboardFeature
|
||||
|
||||
class SoftwareKeyboardApplet final : public AppletBase {
|
||||
public:
|
||||
static constexpr int MAX_BUTTON = 3; // Maximum number of buttons that can be in the keyboard.
|
||||
static constexpr int MAX_BUTTON_TEXT_LEN = 16; // Maximum button text length, in UTF-16 code units.
|
||||
static constexpr int MAX_HINT_TEXT_LEN = 64; // Maximum hint text length, in UTF-16 code units.
|
||||
static constexpr int MAX_CALLBACK_MSG_LEN = 256; // Maximum filter callback error message length, in UTF-16 code units.
|
||||
|
||||
// Keyboard types
|
||||
enum class SoftwareKeyboardType : u32 {
|
||||
Normal, // Normal keyboard with several pages (QWERTY/accents/symbol/mobile)
|
||||
QWERTY, // QWERTY keyboard only.
|
||||
NumPad, // Number pad.
|
||||
Western, // On JPN systems, a text keyboard without Japanese input capabilities, otherwise same as SWKBD_TYPE_NORMAL.
|
||||
};
|
||||
|
||||
// Keyboard dialog buttons.
|
||||
enum class SoftwareKeyboardButtonConfig : u32 {
|
||||
SingleButton, // Ok button
|
||||
DualButton, // Cancel | Ok buttons
|
||||
TripleButton, // Cancel | I Forgot | Ok buttons
|
||||
NoButton, // No button (returned by swkbdInputText in special cases)
|
||||
};
|
||||
|
||||
// Accepted input types.
|
||||
enum class SoftwareKeyboardValidInput : u32 {
|
||||
Anything, // All inputs are accepted.
|
||||
NotEmpty, // Empty inputs are not accepted.
|
||||
NotEmptyNotBlank, // Empty or blank inputs (consisting solely of whitespace) are not accepted.
|
||||
NotBlank, // Blank inputs (consisting solely of whitespace) are not accepted, but empty inputs are.
|
||||
FixedLen, // The input must have a fixed length (specified by maxTextLength in swkbdInit)
|
||||
};
|
||||
|
||||
// Keyboard password modes.
|
||||
enum class SoftwareKeyboardPasswordMode : u32 {
|
||||
None, // Characters are not concealed.
|
||||
Hide, // Characters are concealed immediately.
|
||||
HideDelay, // Characters are concealed a second after they've been typed.
|
||||
};
|
||||
|
||||
// Keyboard filter callback return values.
|
||||
enum class SoftwareKeyboardCallbackResult : u32 {
|
||||
OK, // Specifies that the input is valid.
|
||||
Close, // Displays an error message, then closes the keyboard.
|
||||
Continue, // Displays an error message and continues displaying the keyboard.
|
||||
};
|
||||
|
||||
// Keyboard return values.
|
||||
enum class SoftwareKeyboardResult : s32 {
|
||||
None = -1, // Dummy/unused.
|
||||
InvalidInput = -2, // Invalid parameters to swkbd.
|
||||
OutOfMem = -3, // Out of memory.
|
||||
|
||||
D0Click = 0, // The button was clicked in 1-button dialogs.
|
||||
D1Click0, // The left button was clicked in 2-button dialogs.
|
||||
D1Click1, // The right button was clicked in 2-button dialogs.
|
||||
D2Click0, // The left button was clicked in 3-button dialogs.
|
||||
D2Click1, // The middle button was clicked in 3-button dialogs.
|
||||
D2Click2, // The right button was clicked in 3-button dialogs.
|
||||
|
||||
HomePressed = 10, // The HOME button was pressed.
|
||||
ResetPressed, // The soft-reset key combination was pressed.
|
||||
PowerPressed, // The POWER button was pressed.
|
||||
|
||||
ParentalOK = 20, // The parental PIN was verified successfully.
|
||||
ParentalFail, // The parental PIN was incorrect.
|
||||
|
||||
BannedInput = 30, // The filter callback returned SoftwareKeyboardCallback::CLOSE.
|
||||
};
|
||||
|
||||
struct SoftwareKeyboardConfig {
|
||||
enum_le<SoftwareKeyboardType> type;
|
||||
enum_le<SoftwareKeyboardButtonConfig> numButtonsM1;
|
||||
enum_le<SoftwareKeyboardValidInput> validInput;
|
||||
enum_le<SoftwareKeyboardPasswordMode> passwordMode;
|
||||
s32_le isParentalScreen;
|
||||
s32_le darkenTopScreen;
|
||||
u32_le filterFlags;
|
||||
u32_le saveStateFlags;
|
||||
u16_le maxTextLength;
|
||||
u16_le dictWordCount;
|
||||
u16_le maxDigits;
|
||||
std::array<std::array<u16_le, MAX_BUTTON_TEXT_LEN + 1>, MAX_BUTTON> buttonText;
|
||||
std::array<u16_le, 2> numpadKeys;
|
||||
std::array<u16_le, MAX_HINT_TEXT_LEN + 1> hintText; // Text to display when asking the user for input
|
||||
bool predictiveInput;
|
||||
bool multiline;
|
||||
bool fixedWidth;
|
||||
bool allowHome;
|
||||
bool allowReset;
|
||||
bool allowPower;
|
||||
bool unknown;
|
||||
bool defaultQwerty;
|
||||
std::array<bool, 4> buttonSubmitsText;
|
||||
u16_le language;
|
||||
|
||||
u32_le initialTextOffset; // Offset of the default text in the output SharedMemory
|
||||
u32_le dictOffset;
|
||||
u32_le initialStatusOffset;
|
||||
u32_le initialLearningOffset;
|
||||
u32_le sharedMemorySize; // Size of the SharedMemory
|
||||
u32_le version;
|
||||
|
||||
enum_le<SoftwareKeyboardResult> returnCode;
|
||||
|
||||
u32_le statusOffset;
|
||||
u32_le learningOffset;
|
||||
|
||||
u32_le textOffset; // Offset in the SharedMemory where the output text starts
|
||||
u16_le textLength; // Length in characters of the output text
|
||||
|
||||
enum_le<SoftwareKeyboardCallbackResult> callbackResult;
|
||||
std::array<u16_le, MAX_CALLBACK_MSG_LEN + 1> callbackMessage;
|
||||
bool skipAtCheck;
|
||||
std::array<u8, 0xAB> pad;
|
||||
};
|
||||
static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software keyboard config size is wrong");
|
||||
|
||||
virtual const char* name() override { return "Software Keyboard"; }
|
||||
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) override;
|
||||
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
||||
virtual void reset() override;
|
||||
|
||||
SoftwareKeyboardApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
||||
void closeKeyboard(u32 appID);
|
||||
|
||||
SoftwareKeyboardConfig config;
|
||||
};
|
||||
} // namespace Applets
|
|
@ -1,68 +0,0 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Status register definitions
|
||||
namespace CPSR {
|
||||
enum : std::uint32_t {
|
||||
// Privilege modes
|
||||
UserMode = 16,
|
||||
FIQMode = 17,
|
||||
IRQMode = 18,
|
||||
SVCMode = 19,
|
||||
AbortMode = 23,
|
||||
UndefMode = 27,
|
||||
SystemMode = 31,
|
||||
|
||||
// CPSR flag fields
|
||||
Thumb = 1 << 5,
|
||||
FIQDisable = 1 << 6,
|
||||
IRQDisable = 1 << 7,
|
||||
StickyOverflow = 1 << 27,
|
||||
Overflow = 1 << 28,
|
||||
Carry = 1 << 29,
|
||||
Zero = 1 << 30,
|
||||
Sign = 1U << 31U
|
||||
};
|
||||
}
|
||||
|
||||
namespace FPSCR {
|
||||
// FPSCR Flags
|
||||
enum : std::uint32_t {
|
||||
Sign = 1U << 31U, // Negative condition flag
|
||||
Zero = 1 << 30, // Zero condition flag
|
||||
Carry = 1 << 29, // Carry condition flag
|
||||
Overflow = 1 << 28, // Overflow condition flag
|
||||
|
||||
QC = 1 << 27, // Cumulative saturation bit
|
||||
AHP = 1 << 26, // Alternative half-precision control bit
|
||||
DefaultNan = 1 << 25, // Default NaN mode control bit
|
||||
FlushToZero = 1 << 24, // Flush abnormals to 0 control bit
|
||||
RmodeMask = 3 << 22, // Rounding Mode bit mask
|
||||
StrideMask = 3 << 20, // Vector stride bit mask
|
||||
LengthMask = 7 << 16, // Vector length bit mask
|
||||
|
||||
IDE = 1 << 15, // Input Denormal exception trap enable.
|
||||
IXE = 1 << 12, // Inexact exception trap enable
|
||||
UFE = 1 << 11, // Undeflow exception trap enable
|
||||
OFE = 1 << 10, // Overflow exception trap enable
|
||||
DZE = 1 << 9, // Division by Zero exception trap enable
|
||||
IOE = 1 << 8, // Invalid Operation exception trap enable
|
||||
|
||||
IDC = 1 << 7, // Input Denormal cumulative exception bit
|
||||
IXC = 1 << 4, // Inexact cumulative exception bit
|
||||
UFC = 1 << 3, // Undeflow cumulative exception bit
|
||||
OFC = 1 << 2, // Overflow cumulative exception bit
|
||||
DZC = 1 << 1, // Division by Zero cumulative exception bit
|
||||
IOC = 1 << 0, // Invalid Operation cumulative exception bit
|
||||
|
||||
// VFP rounding modes
|
||||
RoundNearest = 0 << 22,
|
||||
RoundPlusInf = 1 << 22,
|
||||
RoundMinusInf = 2 << 22,
|
||||
RoundToZero = 3 << 22,
|
||||
|
||||
// Default FPSCR value for threads
|
||||
ThreadDefault = DefaultNan | FlushToZero | RoundToZero,
|
||||
MainThreadDefault = ThreadDefault | IXC
|
||||
};
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "ring_buffer.hpp"
|
||||
|
||||
// The DSP core must have access to the DSP service to be able to trigger interrupts properly
|
||||
class DSPService;
|
||||
class Memory;
|
||||
|
||||
namespace Audio {
|
||||
// There are 160 stereo samples in 1 audio frame, so 320 samples total
|
||||
static constexpr u64 samplesInFrame = 160;
|
||||
// 1 frame = 4096 DSP cycles = 8192 ARM11 cycles
|
||||
static constexpr u64 cyclesPerFrame = samplesInFrame * 8192;
|
||||
// For LLE DSP cores, we run the DSP for N cycles at a time, every N*2 arm11 cycles since the ARM11 runs twice as fast
|
||||
static constexpr u64 lleSlice = 16384;
|
||||
|
||||
class DSPCore {
|
||||
using Samples = Common::RingBuffer<s16, 1024>;
|
||||
|
||||
protected:
|
||||
Memory& mem;
|
||||
Scheduler& scheduler;
|
||||
DSPService& dspService;
|
||||
|
||||
Samples sampleBuffer;
|
||||
bool audioEnabled = false;
|
||||
|
||||
MAKE_LOG_FUNCTION(log, dspLogger)
|
||||
|
||||
public:
|
||||
enum class Type { Null, Teakra };
|
||||
DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService)
|
||||
: mem(mem), scheduler(scheduler), dspService(dspService) {}
|
||||
virtual ~DSPCore() {}
|
||||
|
||||
virtual void reset() = 0;
|
||||
virtual void runAudioFrame() = 0;
|
||||
virtual u8* getDspMemory() = 0;
|
||||
|
||||
virtual u16 recvData(u32 regId) = 0;
|
||||
virtual bool recvDataIsReady(u32 regId) = 0;
|
||||
virtual void setSemaphore(u16 value) = 0;
|
||||
virtual void writeProcessPipe(u32 channel, u32 size, u32 buffer) = 0;
|
||||
virtual std::vector<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) = 0;
|
||||
virtual void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) = 0;
|
||||
virtual void unloadComponent() = 0;
|
||||
virtual void setSemaphoreMask(u16 value) = 0;
|
||||
|
||||
static Audio::DSPCore::Type typeFromString(std::string inString);
|
||||
static const char* typeToString(Audio::DSPCore::Type type);
|
||||
|
||||
Samples& getSamples() { return sampleBuffer; }
|
||||
virtual void setAudioEnabled(bool enable) { audioEnabled = enable; }
|
||||
};
|
||||
|
||||
std::unique_ptr<DSPCore> makeDSPCore(DSPCore::Type type, Memory& mem, Scheduler& scheduler, DSPService& dspService);
|
||||
} // namespace Audio
|
|
@ -1,31 +0,0 @@
|
|||
#pragma once
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "miniaudio.h"
|
||||
#include "ring_buffer.hpp"
|
||||
|
||||
class MiniAudioDevice {
|
||||
using Samples = Common::RingBuffer<ma_int16, 1024>;
|
||||
static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate
|
||||
static constexpr ma_uint32 channelCount = 2; // Audio output is stereo
|
||||
|
||||
ma_context context;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device device;
|
||||
ma_resampler resampler;
|
||||
Samples* samples = nullptr;
|
||||
|
||||
bool initialized = false;
|
||||
bool running = false;
|
||||
|
||||
std::vector<std::string> audioDevices;
|
||||
public:
|
||||
MiniAudioDevice();
|
||||
// If safe is on, we create a null audio device
|
||||
void init(Samples& samples, bool safe = false);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
};
|
|
@ -1,46 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
|
||||
#include "audio/dsp_core.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace Audio {
|
||||
class NullDSP : public DSPCore {
|
||||
enum class DSPState : u32 {
|
||||
Off,
|
||||
On,
|
||||
Slep,
|
||||
};
|
||||
|
||||
// Number of DSP pipes
|
||||
static constexpr size_t pipeCount = 8;
|
||||
DSPState dspState;
|
||||
|
||||
std::array<std::vector<u8>, pipeCount> pipeData; // The data of each pipe
|
||||
std::array<u8, Memory::DSP_RAM_SIZE> dspRam;
|
||||
|
||||
void resetAudioPipe();
|
||||
bool loaded = false; // Have we loaded a component?
|
||||
|
||||
public:
|
||||
NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {}
|
||||
~NullDSP() override {}
|
||||
|
||||
void reset() override;
|
||||
void runAudioFrame() override;
|
||||
|
||||
u8* getDspMemory() override { return dspRam.data(); }
|
||||
|
||||
u16 recvData(u32 regId) override;
|
||||
bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready
|
||||
void writeProcessPipe(u32 channel, u32 size, u32 buffer) override;
|
||||
std::vector<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override;
|
||||
|
||||
// NOPs for null DSP core
|
||||
void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) override;
|
||||
void unloadComponent() override;
|
||||
void setSemaphore(u16 value) override {}
|
||||
void setSemaphoreMask(u16 value) override {}
|
||||
};
|
||||
|
||||
} // namespace Audio
|
|
@ -1,104 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
|
||||
#include "audio/dsp_core.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "swap.hpp"
|
||||
#include "teakra/teakra.h"
|
||||
|
||||
namespace Audio {
|
||||
class TeakraDSP : public DSPCore {
|
||||
Teakra::Teakra teakra;
|
||||
u32 pipeBaseAddr;
|
||||
bool running; // Is the DSP running?
|
||||
bool loaded; // Have we finished loading a binary with LoadComponent?
|
||||
bool signalledData;
|
||||
bool signalledSemaphore;
|
||||
|
||||
uint audioFrameIndex = 0; // Index in our audio frame
|
||||
std::array<s16, 160 * 2> audioFrame;
|
||||
|
||||
// Get a pointer to a data memory address
|
||||
u8* getDataPointer(u32 address) { return getDspMemory() + Memory::DSP_DATA_MEMORY_OFFSET + address; }
|
||||
|
||||
enum class PipeDirection {
|
||||
DSPtoCPU = 0,
|
||||
CPUtoDSP = 1,
|
||||
};
|
||||
|
||||
// A lot of Teakra integration code, especially pipe stuff is based on Citra's integration here:
|
||||
// https://github.com/citra-emu/citra/blob/master/src/audio_core/lle/lle.cpp
|
||||
struct PipeStatus {
|
||||
// All addresses and sizes here refer to byte values, NOT 16-bit values.
|
||||
u16_le address;
|
||||
u16_le byteSize;
|
||||
u16_le readPointer;
|
||||
u16_le writePointer;
|
||||
u8 slot;
|
||||
u8 flags;
|
||||
|
||||
static constexpr u16 wrapBit = 0x8000;
|
||||
static constexpr u16 pointerMask = 0x7FFF;
|
||||
|
||||
bool isFull() const { return (readPointer ^ writePointer) == wrapBit; }
|
||||
bool isEmpty() const { return (readPointer ^ writePointer) == 0; }
|
||||
|
||||
// isWrapped: Are read and write pointers in different memory passes.
|
||||
// true: xxxx]----[xxxx (data is wrapping around the end of memory)
|
||||
// false: ----[xxxx]----
|
||||
bool isWrapped() const { return (readPointer ^ writePointer) >= wrapBit; }
|
||||
};
|
||||
static_assert(sizeof(PipeStatus) == 10, "Teakra: Pipe Status size is wrong");
|
||||
static constexpr u8 pipeToSlotIndex(u8 pipe, PipeDirection direction) { return (pipe * 2) + u8(direction); }
|
||||
|
||||
PipeStatus getPipeStatus(u8 pipe, PipeDirection direction) {
|
||||
PipeStatus ret;
|
||||
const u8 index = pipeToSlotIndex(pipe, direction);
|
||||
|
||||
std::memcpy(&ret, getDataPointer(pipeBaseAddr * 2 + index * sizeof(PipeStatus)), sizeof(PipeStatus));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void updatePipeStatus(const PipeStatus& status) {
|
||||
u8 slot = status.slot;
|
||||
u8* statusAddress = getDataPointer(pipeBaseAddr * 2 + slot * sizeof(PipeStatus));
|
||||
|
||||
if (slot % 2 == 0) {
|
||||
std::memcpy(statusAddress + 4, &status.readPointer, sizeof(u16));
|
||||
} else {
|
||||
std::memcpy(statusAddress + 6, &status.writePointer, sizeof(u16));
|
||||
}
|
||||
}
|
||||
// Run 1 slice of DSP instructions
|
||||
void runSlice() {
|
||||
if (running) {
|
||||
teakra.Run(Audio::lleSlice);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService);
|
||||
~TeakraDSP() override {}
|
||||
|
||||
void reset() override;
|
||||
|
||||
// Run 1 slice of DSP instructions and schedule the next audio frame
|
||||
void runAudioFrame() override {
|
||||
runSlice();
|
||||
scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2);
|
||||
}
|
||||
|
||||
void setAudioEnabled(bool enable) override;
|
||||
u8* getDspMemory() override { return teakra.GetDspMemory().data(); }
|
||||
|
||||
u16 recvData(u32 regId) override { return teakra.RecvData(regId); }
|
||||
bool recvDataIsReady(u32 regId) override { return teakra.RecvDataIsReady(regId); }
|
||||
void setSemaphore(u16 value) override { teakra.SetSemaphore(value); }
|
||||
void setSemaphoreMask(u16 value) override { teakra.MaskSemaphore(value); }
|
||||
|
||||
void writeProcessPipe(u32 channel, u32 size, u32 buffer) override;
|
||||
std::vector<u8> readPipe(u32 channel, u32 peer, u32 size, u32 buffer) override;
|
||||
void loadComponent(std::vector<u8>& data, u32 programMask, u32 dataMask) override;
|
||||
void unloadComponent() override;
|
||||
};
|
||||
} // namespace Audio
|
|
@ -1,42 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "action_replay.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "services/hid.hpp"
|
||||
|
||||
// Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time
|
||||
class Memory;
|
||||
|
||||
class Cheats {
|
||||
public:
|
||||
enum class CheatType {
|
||||
None, // Cheat has been removed by the frontend or is invalid
|
||||
ActionReplay, // CTRPF cheats
|
||||
};
|
||||
|
||||
struct Cheat {
|
||||
bool enabled = true;
|
||||
CheatType type = CheatType::ActionReplay;
|
||||
std::vector<u32> instructions;
|
||||
};
|
||||
|
||||
Cheats(Memory& mem, HIDService& hid);
|
||||
u32 addCheat(const Cheat& cheat);
|
||||
u32 addCheat(const u8* data, size_t size);
|
||||
void removeCheat(u32 id);
|
||||
void enableCheat(u32 id);
|
||||
void disableCheat(u32 id);
|
||||
void reset();
|
||||
void run();
|
||||
|
||||
void clear();
|
||||
bool haveCheats() const { return cheatsLoaded; }
|
||||
static constexpr u32 badCheatHandle = 0xFFFFFFFF;
|
||||
|
||||
private:
|
||||
ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes
|
||||
std::vector<Cheat> cheats;
|
||||
bool cheatsLoaded = false;
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
|
||||
// Helpers functions for converting colour channels between bit depths
|
||||
namespace Colour {
|
||||
inline static constexpr u8 convert4To8Bit(u8 c) {
|
||||
return (c << 4) | c;
|
||||
}
|
||||
|
||||
inline static constexpr u8 convert5To8Bit(u8 c) {
|
||||
return (c << 3) | (c >> 2);
|
||||
}
|
||||
|
||||
inline static constexpr u8 convert6To8Bit(u8 c) {
|
||||
return (c << 2) | (c >> 4);
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ALWAYS_INLINE __forceinline
|
||||
#else
|
||||
#define ALWAYS_INLINE __attribute__((always_inline))
|
||||
#endif
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
#include <filesystem>
|
||||
|
||||
#include "audio/dsp_core.hpp"
|
||||
#include "renderer.hpp"
|
||||
|
||||
// Remember to initialize every field here to its default value otherwise bad things will happen
|
||||
struct EmulatorConfig {
|
||||
// Only enable the shader JIT by default on platforms where it's completely tested
|
||||
#ifdef PANDA3DS_X64_HOST
|
||||
static constexpr bool shaderJitDefault = true;
|
||||
#else
|
||||
static constexpr bool shaderJitDefault = false;
|
||||
#endif
|
||||
|
||||
bool shaderJitEnabled = shaderJitDefault;
|
||||
bool discordRpcEnabled = false;
|
||||
RendererType rendererType = RendererType::OpenGL;
|
||||
Audio::DSPCore::Type dspType = Audio::DSPCore::Type::Null;
|
||||
|
||||
bool sdCardInserted = true;
|
||||
bool sdWriteProtected = false;
|
||||
bool usePortableBuild = false;
|
||||
|
||||
bool audioEnabled = false;
|
||||
bool vsyncEnabled = true;
|
||||
|
||||
bool chargerPlugged = true;
|
||||
// Default to 3% battery to make users suffer
|
||||
int batteryPercentage = 3;
|
||||
|
||||
std::filesystem::path filePath;
|
||||
|
||||
EmulatorConfig(const std::filesystem::path& path);
|
||||
void load();
|
||||
void save();
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef CPU_DYNARMIC
|
||||
#include "cpu_dynarmic.hpp"
|
||||
#elif defined(CPU_KVM)
|
||||
#error KVM CPU is not implemented yet
|
||||
#else
|
||||
#error No CPU core implemented :(
|
||||
#endif
|
|
@ -1,185 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "dynarmic/interface/A32/a32.h"
|
||||
#include "dynarmic/interface/A32/config.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic_cp15.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "kernel.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
class Emulator;
|
||||
class CPU;
|
||||
|
||||
class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
|
||||
public:
|
||||
u64 ticksLeft = 0;
|
||||
Memory& mem;
|
||||
Kernel& kernel;
|
||||
Scheduler& scheduler;
|
||||
|
||||
u64 getCyclesForInstruction(bool isThumb, u32 instruction);
|
||||
|
||||
u8 MemoryRead8(u32 vaddr) override {
|
||||
return mem.read8(vaddr);
|
||||
}
|
||||
|
||||
u16 MemoryRead16(u32 vaddr) override {
|
||||
return mem.read16(vaddr);
|
||||
}
|
||||
|
||||
u32 MemoryRead32(u32 vaddr) override {
|
||||
return mem.read32(vaddr);
|
||||
}
|
||||
|
||||
u64 MemoryRead64(u32 vaddr) override {
|
||||
return mem.read64(vaddr);
|
||||
}
|
||||
|
||||
void MemoryWrite8(u32 vaddr, u8 value) override {
|
||||
mem.write8(vaddr, value);
|
||||
}
|
||||
|
||||
void MemoryWrite16(u32 vaddr, u16 value) override {
|
||||
mem.write16(vaddr, value);
|
||||
}
|
||||
|
||||
void MemoryWrite32(u32 vaddr, u32 value) override {
|
||||
mem.write32(vaddr, value);
|
||||
}
|
||||
|
||||
void MemoryWrite64(u32 vaddr, u64 value) override {
|
||||
mem.write64(vaddr, value);
|
||||
}
|
||||
|
||||
#define makeExclusiveWriteHandler(size) \
|
||||
bool MemoryWriteExclusive##size(u32 vaddr, u##size value, u##size expected) override { \
|
||||
u##size current = mem.read##size(vaddr); /* Get current value */ \
|
||||
if (current == expected) { /* Perform the write if current == expected */ \
|
||||
mem.write##size(vaddr, value); \
|
||||
return true; /* Exclusive write succeeded */ \
|
||||
} \
|
||||
\
|
||||
return false; /* Exclusive write failed */ \
|
||||
}
|
||||
|
||||
makeExclusiveWriteHandler(8)
|
||||
makeExclusiveWriteHandler(16)
|
||||
makeExclusiveWriteHandler(32)
|
||||
makeExclusiveWriteHandler(64)
|
||||
|
||||
#undef makeExclusiveWriteHandler
|
||||
|
||||
void InterpreterFallback(u32 pc, size_t num_instructions) override {
|
||||
// This is never called in practice.
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
void CallSVC(u32 swi) override {
|
||||
kernel.serviceSVC(swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
||||
switch (exception) {
|
||||
case Dynarmic::A32::Exception::UnpredictableInstruction:
|
||||
Helpers::panic("Unpredictable instruction at pc = %08X", pc);
|
||||
break;
|
||||
|
||||
default: Helpers::panic("Fired exception oops");
|
||||
}
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
scheduler.currentTimestamp += ticks;
|
||||
|
||||
if (ticks > ticksLeft) {
|
||||
ticksLeft = 0;
|
||||
return;
|
||||
}
|
||||
ticksLeft -= ticks;
|
||||
}
|
||||
|
||||
u64 GetTicksRemaining() override {
|
||||
return ticksLeft;
|
||||
}
|
||||
|
||||
u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override {
|
||||
return getCyclesForInstruction(isThumb, instruction);
|
||||
}
|
||||
|
||||
MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {}
|
||||
};
|
||||
|
||||
class CPU {
|
||||
std::unique_ptr<Dynarmic::A32::Jit> jit;
|
||||
std::shared_ptr<CP15> cp15;
|
||||
|
||||
// Make exclusive monitor with only 1 CPU core
|
||||
Dynarmic::ExclusiveMonitor exclusiveMonitor{1};
|
||||
MyEnvironment env;
|
||||
Memory& mem;
|
||||
Scheduler& scheduler;
|
||||
Emulator& emu;
|
||||
|
||||
public:
|
||||
static constexpr u64 ticksPerSec = Scheduler::arm11Clock;
|
||||
|
||||
CPU(Memory& mem, Kernel& kernel, Emulator& emu);
|
||||
void reset();
|
||||
|
||||
void setReg(int index, u32 value) {
|
||||
jit->Regs()[index] = value;
|
||||
}
|
||||
|
||||
u32 getReg(int index) {
|
||||
return jit->Regs()[index];
|
||||
}
|
||||
|
||||
std::span<u32, 16> regs() { return jit->Regs(); }
|
||||
|
||||
// Get reference to array of FPRs. This array consists of the FPRs as single precision values
|
||||
// Hence why its base type is u32
|
||||
std::span<u32, 32> fprs() { return std::span(jit->ExtRegs()).first<32>(); }
|
||||
|
||||
void setCPSR(u32 value) {
|
||||
jit->SetCpsr(value);
|
||||
}
|
||||
|
||||
u32 getCPSR() {
|
||||
return jit->Cpsr();
|
||||
}
|
||||
|
||||
void setFPSCR(u32 value) {
|
||||
jit->SetFpscr(value);
|
||||
}
|
||||
|
||||
u32 getFPSCR() {
|
||||
return jit->Fpscr();
|
||||
}
|
||||
|
||||
// Set the base pointer to thread-local storage, stored in a CP15 register on the 3DS
|
||||
void setTLSBase(u32 value) {
|
||||
cp15->setTLSBase(value);
|
||||
}
|
||||
|
||||
u64 getTicks() {
|
||||
return scheduler.currentTimestamp;
|
||||
}
|
||||
|
||||
// Get reference to tick count. Memory needs access to this
|
||||
u64& getTicksRef() {
|
||||
return scheduler.currentTimestamp;
|
||||
}
|
||||
|
||||
Scheduler& getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
void addTicks(u64 ticks) { env.AddTicks(ticks); }
|
||||
|
||||
void clearCache() { jit->ClearCache(); }
|
||||
void runFrame();
|
||||
};
|
|
@ -1,166 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <climits>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace Crypto {
|
||||
constexpr std::size_t AesKeySize = 0x10;
|
||||
using AESKey = std::array<u8, AesKeySize>;
|
||||
|
||||
template <std::size_t N>
|
||||
static std::array<u8, N> rolArray(const std::array<u8, N>& value, std::size_t bits) {
|
||||
const auto bitWidth = N * CHAR_BIT;
|
||||
|
||||
bits %= bitWidth;
|
||||
|
||||
const auto byteShift = bits / CHAR_BIT;
|
||||
const auto bitShift = bits % CHAR_BIT;
|
||||
|
||||
std::array<u8, N> result;
|
||||
|
||||
for (std::size_t i = 0; i < N; i++) {
|
||||
result[i] = ((value[(i + byteShift) % N] << bitShift) | (value[(i + byteShift + 1) % N] >> (CHAR_BIT - bitShift))) & UINT8_MAX;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
static std::array<u8, N> addArray(const std::array<u8, N>& a, const std::array<u8, N>& b) {
|
||||
std::array<u8, N> result;
|
||||
std::size_t sum = 0;
|
||||
std::size_t carry = 0;
|
||||
|
||||
for (std::int64_t i = N - 1; i >= 0; i--) {
|
||||
sum = a[i] + b[i] + carry;
|
||||
carry = sum >> CHAR_BIT;
|
||||
result[i] = static_cast<u8>(sum & UINT8_MAX);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
static std::array<u8, N> xorArray(const std::array<u8, N>& a, const std::array<u8, N>& b) {
|
||||
std::array<u8, N> result;
|
||||
|
||||
for (std::size_t i = 0; i < N; i++) {
|
||||
result[i] = a[i] ^ b[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<AESKey> createKeyFromHex(const std::string& hex) {
|
||||
if (hex.size() < 32) {
|
||||
return {};
|
||||
}
|
||||
|
||||
AESKey rawKey;
|
||||
for (std::size_t i = 0; i < rawKey.size(); i++) {
|
||||
rawKey[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), 0, 16));
|
||||
}
|
||||
|
||||
return rawKey;
|
||||
}
|
||||
|
||||
struct AESKeySlot {
|
||||
std::optional<AESKey> keyX = std::nullopt;
|
||||
std::optional<AESKey> keyY = std::nullopt;
|
||||
std::optional<AESKey> normalKey = std::nullopt;
|
||||
};
|
||||
|
||||
enum KeySlotId : std::size_t {
|
||||
NCCHKey0 = 0x2C,
|
||||
NCCHKey1 = 0x25,
|
||||
NCCHKey2 = 0x18,
|
||||
NCCHKey3 = 0x1B,
|
||||
};
|
||||
|
||||
class AESEngine {
|
||||
private:
|
||||
constexpr static std::size_t AesKeySlotCount = 0x40;
|
||||
|
||||
std::optional<AESKey> m_generator = std::nullopt;
|
||||
std::array<AESKeySlot, AesKeySlotCount> m_slots;
|
||||
bool keysLoaded = false;
|
||||
|
||||
constexpr void updateNormalKey(std::size_t slotId) {
|
||||
if (m_generator.has_value() && hasKeyX(slotId) && hasKeyY(slotId)) {
|
||||
auto& keySlot = m_slots.at(slotId);
|
||||
AESKey keyX = keySlot.keyX.value();
|
||||
AESKey keyY = keySlot.keyY.value();
|
||||
|
||||
keySlot.normalKey = rolArray(addArray(xorArray(rolArray(keyX, 2), keyY), m_generator.value()), 87);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AESEngine() {}
|
||||
void loadKeys(const std::filesystem::path& path);
|
||||
bool haveKeys() { return keysLoaded; }
|
||||
bool haveGenerator() { return m_generator.has_value(); }
|
||||
|
||||
constexpr bool hasKeyX(std::size_t slotId) {
|
||||
if (slotId >= AesKeySlotCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_slots.at(slotId).keyX.has_value();
|
||||
}
|
||||
|
||||
constexpr AESKey getKeyX(std::size_t slotId) {
|
||||
return m_slots.at(slotId).keyX.value_or(AESKey{});
|
||||
}
|
||||
|
||||
constexpr void setKeyX(std::size_t slotId, const AESKey &key) {
|
||||
if (slotId < AesKeySlotCount) {
|
||||
m_slots.at(slotId).keyX = key;
|
||||
updateNormalKey(slotId);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool hasKeyY(std::size_t slotId) {
|
||||
if (slotId >= AesKeySlotCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_slots.at(slotId).keyY.has_value();
|
||||
}
|
||||
|
||||
constexpr AESKey getKeyY(std::size_t slotId) {
|
||||
return m_slots.at(slotId).keyY.value_or(AESKey{});
|
||||
}
|
||||
|
||||
constexpr void setKeyY(std::size_t slotId, const AESKey &key) {
|
||||
if (slotId < AesKeySlotCount) {
|
||||
m_slots.at(slotId).keyY = key;
|
||||
updateNormalKey(slotId);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool hasNormalKey(std::size_t slotId) {
|
||||
if (slotId >= AesKeySlotCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_slots.at(slotId).normalKey.has_value();
|
||||
}
|
||||
|
||||
constexpr AESKey getNormalKey(std::size_t slotId) {
|
||||
return m_slots.at(slotId).normalKey.value_or(AESKey{});
|
||||
}
|
||||
|
||||
constexpr void setNormalKey(std::size_t slotId, const AESKey &key) {
|
||||
if (slotId < AesKeySlotCount) {
|
||||
m_slots.at(slotId).normalKey = key;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||
#include <discord_rpc.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Discord {
|
||||
enum class RPCStatus { Idling, Playing };
|
||||
|
||||
class RPC {
|
||||
std::uint64_t startTimestamp;
|
||||
bool enabled = false;
|
||||
|
||||
public:
|
||||
void init();
|
||||
void update(RPCStatus status, const std::string& title);
|
||||
void stop();
|
||||
};
|
||||
} // namespace Discord
|
||||
|
||||
#endif
|
|
@ -1,71 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "dynarmic/interface/A32/a32.h"
|
||||
#include "dynarmic/interface/A32/config.h"
|
||||
#include "dynarmic/interface/A32/coprocessor.h"
|
||||
#include "helpers.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
class CP15 final : public Dynarmic::A32::Coprocessor {
|
||||
using Callback = Dynarmic::A32::Coprocessor::Callback;
|
||||
using CoprocReg = Dynarmic::A32::CoprocReg;
|
||||
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
|
||||
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
|
||||
|
||||
u32 threadStoragePointer; // Pointer to thread-local storage
|
||||
u32 dummy; // MCR writes here for registers whose values are ignored
|
||||
|
||||
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1,
|
||||
CoprocReg CRd, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) override {
|
||||
if (!two && opc1 == 0 && CRn == CoprocReg::C7 && CRm == CoprocReg::C10 && opc2 == 4) {
|
||||
return &dummy; // Normally inserts a "Data Synchronization Barrier"
|
||||
}
|
||||
|
||||
if (!two && opc1 == 0 && CRn == CoprocReg::C7 && CRm == CoprocReg::C10 && opc2 == 5) {
|
||||
return &dummy; // Normally inserts a "Data Memory Barrier"
|
||||
}
|
||||
Helpers::panic("CP15: CompileSendOneWord\nopc1: %d CRn: %d CRm: %d opc2: %d\n", opc1, (int)CRn, (int)CRm, opc2);
|
||||
}
|
||||
|
||||
CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override {
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) override {
|
||||
// Stores a pointer to thread-local storage, accessed via mrc p15, 0, rd, c13, c0, 3
|
||||
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 3) {
|
||||
return &threadStoragePointer;
|
||||
}
|
||||
|
||||
Helpers::panic("CP15: CompileGetOneWord\nopc1: %d CRn: %d CRm: %d opc2: %d\n", opc1, (int)CRn, (int)CRm, opc2);
|
||||
}
|
||||
|
||||
CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override {
|
||||
return std::monostate{};
|
||||
}
|
||||
|
||||
std::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
public:
|
||||
void setTLSBase(u32 value) {
|
||||
threadStoragePointer = value;
|
||||
}
|
||||
|
||||
// Currently does nothing but may be needed in the future
|
||||
void reset() {}
|
||||
};
|
|
@ -1,150 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include "PICA/gpu.hpp"
|
||||
#include "audio/dsp_core.hpp"
|
||||
#include "audio/miniaudio_device.hpp"
|
||||
#include "cheats.hpp"
|
||||
#include "config.hpp"
|
||||
#include "cpu.hpp"
|
||||
#include "crypto/aes_engine.hpp"
|
||||
#include "discord_rpc.hpp"
|
||||
#include "fs/romfs.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "lua_manager.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||
#include "http_server.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
#include "gl/context.h"
|
||||
#endif
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
enum class ROMType {
|
||||
None,
|
||||
ELF,
|
||||
NCSD,
|
||||
CXI,
|
||||
HB_3DSX,
|
||||
};
|
||||
|
||||
class Emulator {
|
||||
EmulatorConfig config;
|
||||
CPU cpu;
|
||||
GPU gpu;
|
||||
Memory memory;
|
||||
Kernel kernel;
|
||||
std::unique_ptr<Audio::DSPCore> dsp;
|
||||
Scheduler scheduler;
|
||||
|
||||
Crypto::AESEngine aesEngine;
|
||||
MiniAudioDevice audioDevice;
|
||||
Cheats cheats;
|
||||
|
||||
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
|
||||
// This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state
|
||||
// And so the user can still use the keyboard to control the analog
|
||||
bool keyboardAnalogX = false;
|
||||
bool keyboardAnalogY = false;
|
||||
|
||||
// For tracking whether to update gyroscope
|
||||
// We bind gyro to right click + mouse movement
|
||||
bool holdingRightClick = false;
|
||||
|
||||
public:
|
||||
static constexpr u32 width = 400;
|
||||
static constexpr u32 height = 240 * 2; // * 2 because 2 screens
|
||||
ROMType romType = ROMType::None;
|
||||
bool running = false; // Is the emulator running a game?
|
||||
bool programRunning = false; // Is the emulator program itself running?
|
||||
|
||||
private:
|
||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||
HttpServer httpServer;
|
||||
friend struct HttpServer;
|
||||
#endif
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||
Discord::RPC discordRpc;
|
||||
#endif
|
||||
void setAudioEnabled(bool enable);
|
||||
void updateDiscord();
|
||||
|
||||
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it
|
||||
// This is currently only used for ELFs, NCSDs use the IOFile API instead
|
||||
std::ifstream loadedELF;
|
||||
NCSD loadedNCSD;
|
||||
|
||||
std::optional<std::filesystem::path> romPath = std::nullopt;
|
||||
LuaManager lua;
|
||||
|
||||
public:
|
||||
// Decides whether to reload or not reload the ROM when resetting. We use enum class over a plain bool for clarity.
|
||||
// If NoReload is selected, the emulator will not reload its selected ROM. This is useful for things like booting up the emulator, or resetting to
|
||||
// change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current
|
||||
// ROM and just resets the emu
|
||||
enum class ReloadOption { NoReload, Reload };
|
||||
// Used in CPU::runFrame
|
||||
bool frameDone = false;
|
||||
|
||||
Emulator();
|
||||
~Emulator();
|
||||
|
||||
void step();
|
||||
void render();
|
||||
void reset(ReloadOption reload);
|
||||
void run(void* frontend = nullptr);
|
||||
void runFrame();
|
||||
// Poll the scheduler for events
|
||||
void pollScheduler();
|
||||
|
||||
void resume(); // Resume the emulator
|
||||
void pause(); // Pause the emulator
|
||||
void togglePause();
|
||||
|
||||
bool loadAmiibo(const std::filesystem::path& path);
|
||||
bool loadROM(const std::filesystem::path& path);
|
||||
bool loadNCSD(const std::filesystem::path& path, ROMType type);
|
||||
bool load3DSX(const std::filesystem::path& path);
|
||||
bool loadELF(const std::filesystem::path& path);
|
||||
bool loadELF(std::ifstream& file);
|
||||
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
// For passing the GL context from Qt to the renderer
|
||||
void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); }
|
||||
#else
|
||||
void initGraphicsContext(SDL_Window* window) { gpu.initGraphicsContext(window); }
|
||||
#endif
|
||||
|
||||
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
||||
void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); }
|
||||
void deinitGraphicsContext() { gpu.deinitGraphicsContext(); }
|
||||
|
||||
EmulatorConfig& getConfig() { return config; }
|
||||
Cheats& getCheats() { return cheats; }
|
||||
ServiceManager& getServiceManager() { return kernel.getServiceManager(); }
|
||||
LuaManager& getLua() { return lua; }
|
||||
Scheduler& getScheduler() { return scheduler; }
|
||||
Memory& getMemory() { return memory; }
|
||||
|
||||
RendererType getRendererType() const { return config.rendererType; }
|
||||
Renderer* getRenderer() { return gpu.getRenderer(); }
|
||||
u64 getTicks() { return cpu.getTicks(); }
|
||||
|
||||
std::filesystem::path getConfigPath();
|
||||
std::filesystem::path getAndroidAppPath();
|
||||
// Get the root path for the emulator's app data
|
||||
std::filesystem::path getAppDataRoot();
|
||||
|
||||
std::span<u8> getSMDH();
|
||||
};
|
|
@ -1,266 +0,0 @@
|
|||
#pragma once
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "helpers.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "result.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
using Result::HorizonResult;
|
||||
|
||||
namespace PathType {
|
||||
enum : u32 {
|
||||
Invalid = 0,
|
||||
Empty = 1,
|
||||
Binary = 2,
|
||||
ASCII = 3,
|
||||
UTF16 = 4,
|
||||
};
|
||||
}
|
||||
|
||||
namespace ArchiveID {
|
||||
enum : u32 {
|
||||
SelfNCCH = 3,
|
||||
SaveData = 4,
|
||||
ExtSaveData = 6,
|
||||
SharedExtSaveData = 7,
|
||||
SystemSaveData = 8,
|
||||
SDMC = 9,
|
||||
SDMCWriteOnly = 0xA,
|
||||
|
||||
SavedataAndNcch = 0x2345678A,
|
||||
// 3DBrew: This is the same as the regular SaveData archive, except with this the savedata ID and mediatype is loaded from the input archive
|
||||
// lowpath.
|
||||
UserSaveData1 = 0x567890B2,
|
||||
// 3DBrew: Similar to 0x567890B2 but can only access Accessible Save specified in exheader?
|
||||
UserSaveData2 = 0x567890B4,
|
||||
};
|
||||
|
||||
static std::string toString(u32 id) {
|
||||
switch (id) {
|
||||
case SelfNCCH: return "SelfNCCH";
|
||||
case SaveData: return "SaveData";
|
||||
case ExtSaveData: return "ExtSaveData";
|
||||
case SharedExtSaveData: return "SharedExtSaveData";
|
||||
case SystemSaveData: return "SystemSaveData";
|
||||
case SDMC: return "SDMC";
|
||||
case SDMCWriteOnly: return "SDMC (Write-only)";
|
||||
case SavedataAndNcch: return "Savedata & NCCH (archive 0x2345678A)";
|
||||
default: return "Unknown archive";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FSPath {
|
||||
u32 type = PathType::Invalid;
|
||||
|
||||
std::vector<u8> binary; // Path data for binary paths
|
||||
std::string string; // Path data for ASCII paths
|
||||
std::u16string utf16_string;
|
||||
|
||||
FSPath() {}
|
||||
|
||||
FSPath(u32 type, const std::vector<u8>& vec) : type(type) {
|
||||
switch (type) {
|
||||
case PathType::Binary:
|
||||
binary = std::move(vec);
|
||||
break;
|
||||
|
||||
case PathType::ASCII:
|
||||
string.resize(vec.size() - 1); // -1 because of the null terminator
|
||||
std::memcpy(string.data(), vec.data(), vec.size() - 1); // Copy string data
|
||||
break;
|
||||
|
||||
case PathType::UTF16: {
|
||||
const size_t size = vec.size() / sizeof(u16) - 1; // Character count. -1 because null terminator here too
|
||||
utf16_string.resize(size);
|
||||
std::memcpy(utf16_string.data(), vec.data(), size * sizeof(u16));
|
||||
break;
|
||||
}
|
||||
; }
|
||||
}
|
||||
};
|
||||
|
||||
struct FilePerms {
|
||||
u32 raw;
|
||||
|
||||
FilePerms(u32 val) : raw(val) {}
|
||||
bool read() const { return (raw & 1) != 0; }
|
||||
bool write() const { return (raw & 2) != 0; }
|
||||
bool create() const { return (raw & 4) != 0; }
|
||||
};
|
||||
|
||||
class ArchiveBase;
|
||||
struct FileSession {
|
||||
ArchiveBase* archive = nullptr;
|
||||
FILE* fd = nullptr; // File descriptor for file sessions that require them.
|
||||
FSPath path;
|
||||
FSPath archivePath;
|
||||
u32 priority = 0; // TODO: What does this even do
|
||||
bool isOpen;
|
||||
|
||||
FileSession(ArchiveBase* archive, const FSPath& filePath, const FSPath& archivePath, FILE* fd, bool isOpen = true) :
|
||||
archive(archive), path(filePath), archivePath(archivePath), fd(fd), isOpen(isOpen), priority(0) {}
|
||||
|
||||
// For cloning a file session
|
||||
FileSession(const FileSession& other) : archive(other.archive), path(other.path),
|
||||
archivePath(other.archivePath), fd(other.fd), isOpen(other.isOpen), priority(other.priority) {}
|
||||
};
|
||||
|
||||
struct ArchiveSession {
|
||||
ArchiveBase* archive = nullptr;
|
||||
FSPath path;
|
||||
bool isOpen;
|
||||
|
||||
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(filePath), isOpen(isOpen) {}
|
||||
};
|
||||
|
||||
struct DirectoryEntry {
|
||||
std::filesystem::path path;
|
||||
bool isDirectory;
|
||||
};
|
||||
|
||||
struct DirectorySession {
|
||||
ArchiveBase* archive = nullptr;
|
||||
// For directories which are mirrored to a specific path on the disk, this contains that path
|
||||
// Otherwise this is a nullopt
|
||||
std::optional<std::filesystem::path> pathOnDisk;
|
||||
|
||||
// The list of directory entries + the index of the entry we're currently inspecting
|
||||
std::vector<DirectoryEntry> entries;
|
||||
size_t currentEntry;
|
||||
|
||||
bool isOpen;
|
||||
|
||||
DirectorySession(ArchiveBase* archive, std::filesystem::path path, bool isOpen = true) : archive(archive), pathOnDisk(path), isOpen(isOpen) {
|
||||
currentEntry = 0; // Start from entry 0
|
||||
|
||||
// Read all directory entries, cache them
|
||||
for (auto& e : std::filesystem::directory_iterator(path)) {
|
||||
DirectoryEntry entry;
|
||||
entry.path = e.path();
|
||||
entry.isDirectory = std::filesystem::is_directory(e);
|
||||
entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Represents a file descriptor obtained from OpenFile. If the optional is nullopt, opening the file failed.
|
||||
// Otherwise the fd of the opened file is returned (or nullptr if the opened file doesn't require one)
|
||||
using FileDescriptor = std::optional<FILE*>;
|
||||
|
||||
class ArchiveBase {
|
||||
public:
|
||||
struct FormatInfo {
|
||||
u32 size; // Archive size
|
||||
u32 numOfDirectories; // Number of directories
|
||||
u32 numOfFiles; // Number of files
|
||||
bool duplicateData; // Whether to duplicate data or not
|
||||
};
|
||||
|
||||
protected:
|
||||
using Handle = u32;
|
||||
|
||||
static constexpr FileDescriptor NoFile = nullptr;
|
||||
static constexpr FileDescriptor FileError = std::nullopt;
|
||||
Memory& mem;
|
||||
|
||||
// Returns if a specified 3DS path in UTF16 or ASCII format is safe or not
|
||||
// A 3DS path is considered safe if its first character is '/' which means we're not trying to access anything outside the root of the fs
|
||||
// And if it doesn't contain enough instances of ".." (Indicating "climb up a folder" in filesystems) to let the software climb up the directory tree
|
||||
// And access files outside of the emulator's app data folder
|
||||
template <u32 format>
|
||||
bool isPathSafe(const FSPath& path) {
|
||||
static_assert(format == PathType::ASCII || format == PathType::UTF16);
|
||||
using String = typename std::conditional<format == PathType::UTF16, std::u16string, std::string>::type; // String type for the path
|
||||
using Char = typename String::value_type; // Char type for the path
|
||||
|
||||
String pathString, dots;
|
||||
if constexpr (std::is_same<String, std::u16string>()) {
|
||||
pathString = path.utf16_string;
|
||||
dots = u"..";
|
||||
} else {
|
||||
pathString = path.string;
|
||||
dots = "..";
|
||||
}
|
||||
|
||||
// If the path string doesn't begin with / then that means it's accessing outside the FS root, which is invalid & unsafe
|
||||
if (pathString[0] != Char('/')) return false;
|
||||
|
||||
// Counts how many folders sans the root our file is nested under.
|
||||
// If it's < 0 at any point of parsing, then the path is unsafe and tries to crawl outside our file sandbox.
|
||||
// If it's 0 then this is the FS root.
|
||||
// If it's > 0 then we're in a subdirectory of the root.
|
||||
int level = 0;
|
||||
|
||||
// Split the string on / characters and see how many of the substrings are ".."
|
||||
size_t pos = 0;
|
||||
while ((pos = pathString.find(Char('/'))) != String::npos) {
|
||||
String token = pathString.substr(0, pos);
|
||||
pathString.erase(0, pos + 1);
|
||||
|
||||
if (token == dots) {
|
||||
level--;
|
||||
if (level < 0) return false;
|
||||
} else {
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
virtual std::string name() = 0;
|
||||
virtual u64 getFreeBytes() = 0;
|
||||
virtual HorizonResult createFile(const FSPath& path, u64 size) = 0;
|
||||
virtual HorizonResult deleteFile(const FSPath& path) = 0;
|
||||
|
||||
virtual Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) {
|
||||
Helpers::panic("Unimplemented GetFormatInfo for %s archive", name().c_str());
|
||||
// Return a dummy struct just to avoid the UB of not returning anything, even if we panic
|
||||
return Ok(FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false });
|
||||
}
|
||||
|
||||
virtual HorizonResult createDirectory(const FSPath& path) {
|
||||
Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str());
|
||||
return Result::FS::AlreadyExists;
|
||||
}
|
||||
|
||||
// Returns nullopt if opening the file failed, otherwise returns a file descriptor to it (nullptr if none is needed)
|
||||
virtual FileDescriptor openFile(const FSPath& path, const FilePerms& perms) = 0;
|
||||
virtual Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) = 0;
|
||||
|
||||
virtual Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) {
|
||||
Helpers::panic("Unimplemented OpenDirectory for %s archive", name().c_str());
|
||||
return Err(Result::FS::FileNotFoundAlt);
|
||||
}
|
||||
|
||||
virtual void format(const FSPath& path, const FormatInfo& info) {
|
||||
Helpers::panic("Unimplemented Format for %s archive", name().c_str());
|
||||
}
|
||||
|
||||
virtual HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) {
|
||||
Helpers::panic("Unimplemented RenameFile for %s archive", name().c_str());
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
// Read size bytes from a file starting at offset "offset" into a certain buffer in memory
|
||||
// Returns the number of bytes read, or nullopt if the read failed
|
||||
virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0;
|
||||
|
||||
ArchiveBase(Memory& mem) : mem(mem) {}
|
||||
};
|
||||
|
||||
struct ArchiveResource {
|
||||
u32 sectorSize; // Size of a sector in bytes
|
||||
u32 clusterSize; // Size of a cluster in bytes
|
||||
u32 partitionCapacityInClusters;
|
||||
u32 freeSpaceInClusters;
|
||||
};
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
|
||||
class ExtSaveDataArchive : public ArchiveBase {
|
||||
public:
|
||||
ExtSaveDataArchive(Memory& mem, const std::string& folder, bool isShared = false) : ArchiveBase(mem),
|
||||
isShared(isShared), backingFolder(folder) {}
|
||||
|
||||
u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; }
|
||||
std::string name() override { return "ExtSaveData::" + backingFolder; }
|
||||
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||
|
||||
Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) override {
|
||||
Helpers::warn("Stubbed ExtSaveData::GetFormatInfo");
|
||||
return Ok(FormatInfo{.size = 1_GB, .numOfDirectories = 255, .numOfFiles = 255, .duplicateData = false});
|
||||
}
|
||||
|
||||
// Takes in a binary ExtSaveData path, outputs a combination of the backing folder with the low and high save entries of the path
|
||||
// Used for identifying the archive format info files
|
||||
std::string getExtSaveDataPathFromBinary(const FSPath& path);
|
||||
|
||||
bool isShared = false;
|
||||
std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path.
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
|
||||
class NCCHArchive : public ArchiveBase {
|
||||
public:
|
||||
NCCHArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||
|
||||
u64 getFreeBytes() override { Helpers::panic("NCCH::GetFreeBytes unimplemented"); return 0; }
|
||||
std::string name() override { return "NCCH"; }
|
||||
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||
|
||||
// Returns whether the cart has a RomFS
|
||||
bool hasRomFS() {
|
||||
auto cxi = mem.getCXI();
|
||||
return (cxi != nullptr && cxi->hasRomFS());
|
||||
}
|
||||
|
||||
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
|
||||
bool hasExeFS() {
|
||||
auto cxi = mem.getCXI();
|
||||
return (cxi != nullptr && cxi->hasExeFS());
|
||||
}
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
|
||||
class SaveDataArchive : public ArchiveBase {
|
||||
public:
|
||||
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||
|
||||
u64 getFreeBytes() override { return 32_MB; }
|
||||
std::string name() override { return "SaveData"; }
|
||||
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||
|
||||
void format(const FSPath& path, const FormatInfo& info) override;
|
||||
Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) override;
|
||||
|
||||
std::filesystem::path getFormatInfoPath() {
|
||||
return IOFile::getAppData() / "FormatInfo" / "SaveData.format";
|
||||
}
|
||||
|
||||
// Returns whether the cart has save data or not
|
||||
bool cartHasSaveData() {
|
||||
auto cxi = mem.getCXI();
|
||||
return (cxi != nullptr && cxi->hasSaveData()); // We need to have a CXI file with more than 0 bytes of save data
|
||||
}
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
using Result::HorizonResult;
|
||||
|
||||
class SDMCArchive : public ArchiveBase {
|
||||
bool isWriteOnly = false; // There's 2 variants of the SDMC archive: Regular one (Read/Write) and write-only
|
||||
|
||||
public:
|
||||
SDMCArchive(Memory& mem, bool writeOnly = false) : ArchiveBase(mem), isWriteOnly(writeOnly) {}
|
||||
|
||||
u64 getFreeBytes() override { return 1_GB; }
|
||||
std::string name() override { return "SDMC"; }
|
||||
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
|
||||
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||
};
|
|
@ -1,30 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
|
||||
class SelfNCCHArchive : public ArchiveBase {
|
||||
public:
|
||||
SelfNCCHArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||
|
||||
u64 getFreeBytes() override { return 0; }
|
||||
std::string name() override { return "SelfNCCH"; }
|
||||
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||
|
||||
// Returns whether the cart has a RomFS
|
||||
bool hasRomFS() {
|
||||
auto cxi = mem.getCXI();
|
||||
auto hb3dsx = mem.get3DSX();
|
||||
return (cxi != nullptr && cxi->hasRomFS()) || (hb3dsx != nullptr && hb3dsx->hasRomFs());
|
||||
}
|
||||
|
||||
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
|
||||
bool hasExeFS() {
|
||||
auto cxi = mem.getCXI();
|
||||
return (cxi != nullptr && cxi->hasExeFS());
|
||||
}
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
|
||||
class SystemSaveDataArchive : public ArchiveBase {
|
||||
public:
|
||||
SystemSaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||
|
||||
u64 getFreeBytes() override {
|
||||
Helpers::warn("Unimplemented GetFreeBytes for SystemSaveData archive");
|
||||
return 32_MB;
|
||||
}
|
||||
|
||||
std::string name() override { return "SystemSaveData"; }
|
||||
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
|
||||
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override {
|
||||
Helpers::panic("Unimplemented ReadFile for SystemSaveData archive");
|
||||
return {};
|
||||
};
|
||||
};
|
|
@ -1,31 +0,0 @@
|
|||
#pragma once
|
||||
#include "archive_base.hpp"
|
||||
|
||||
class UserSaveDataArchive : public ArchiveBase {
|
||||
u32 archiveID;
|
||||
public:
|
||||
UserSaveDataArchive(Memory& mem, u32 archiveID) : ArchiveBase(mem), archiveID(archiveID) {}
|
||||
|
||||
u64 getFreeBytes() override { return 32_MB; }
|
||||
std::string name() override { return "UserSaveData"; }
|
||||
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
|
||||
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||
|
||||
void format(const FSPath& path, const FormatInfo& info) override;
|
||||
Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) override;
|
||||
|
||||
std::filesystem::path getFormatInfoPath() { return IOFile::getAppData() / "FormatInfo" / "SaveData.format"; }
|
||||
|
||||
// Returns whether the cart has save data or not
|
||||
bool cartHasSaveData() {
|
||||
auto cxi = mem.getCXI();
|
||||
return (cxi != nullptr && cxi->hasSaveData()); // We need to have a CXI file with more than 0 bytes of save data
|
||||
}
|
||||
};
|
|
@ -1,132 +0,0 @@
|
|||
// Generated with https://github.com/B3n30/citra_system_archives
|
||||
#pragma once
|
||||
|
||||
const unsigned char BAD_WORD_LIST_DATA[] = {
|
||||
0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x34, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
|
||||
0x4c, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x24, 0x03, 0x00, 0x00,
|
||||
0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00,
|
||||
0xc0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x8c, 0x01, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xe4, 0x01, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x3c, 0x02, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00,
|
||||
0x2c, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xb8, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x31, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x30, 0x00,
|
||||
0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xb0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x74, 0x00,
|
||||
0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x31, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x33, 0x00,
|
||||
0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x34, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x74, 0x00,
|
||||
0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
|
||||
0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x31, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x36, 0x00,
|
||||
0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xb8, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00,
|
||||
0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00, 0x34, 0x00, 0x2e, 0x00,
|
||||
0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3c, 0x02, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00,
|
||||
0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x36, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xdc, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x37, 0x00, 0x2e, 0x00,
|
||||
0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xc0, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x38, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x39, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x20, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x76, 0x00, 0x65, 0x00,
|
||||
0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2e, 0x00,
|
||||
0x64, 0x00, 0x61, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
|
||||
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
|
||||
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
|
||||
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
|
||||
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
|
||||
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
|
||||
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
|
||||
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
|
||||
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
|
||||
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
|
||||
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
|
||||
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
|
||||
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
|
||||
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
|
||||
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
|
||||
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
|
||||
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
|
||||
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
|
||||
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
|
||||
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
|
||||
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
|
||||
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
|
||||
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
|
||||
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
|
||||
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00
|
||||
};
|
||||
const unsigned int BAD_WORD_LIST_DATA_len = 1508;
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace IVFC {
|
||||
struct IVFCLevel {
|
||||
u64 logicalOffset;
|
||||
u64 size;
|
||||
u64 blockSize;
|
||||
};
|
||||
|
||||
struct IVFC {
|
||||
u64 masterHashSize;
|
||||
std::vector<IVFCLevel> levels;
|
||||
};
|
||||
|
||||
size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc);
|
||||
} // namespace IVFC
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace RomFS {
|
||||
struct RomFSNode {
|
||||
std::u16string name;
|
||||
// The file/directory offset relative to the start of the RomFS
|
||||
u64 metadataOffset = 0;
|
||||
u64 dataOffset = 0;
|
||||
u64 dataSize = 0;
|
||||
bool isDirectory = false;
|
||||
|
||||
std::vector<std::unique_ptr<RomFSNode>> directories;
|
||||
std::vector<std::unique_ptr<RomFSNode>> files;
|
||||
};
|
||||
|
||||
// Result codes when dumping RomFS. These are used by the frontend to print appropriate error messages if RomFS dumping fails
|
||||
enum class DumpingResult {
|
||||
Success = 0,
|
||||
InvalidFormat = 1, // ROM is a format that doesn't support RomFS, such as ELF
|
||||
NoRomFS = 2
|
||||
};
|
||||
|
||||
std::unique_ptr<RomFSNode> parseRomFSTree(uintptr_t romFS, u64 romFSSize);
|
||||
} // namespace RomFS
|
|
@ -1,183 +0,0 @@
|
|||
#pragma once
|
||||
#include <climits>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "termcolor.hpp"
|
||||
|
||||
// We have to detect and special-case AppleClang at the moment since its C++20 support is finicky and doesn't quite support std::bit_cast
|
||||
#if defined(__clang__) && defined(__apple_build_version__)
|
||||
#define HELPERS_APPLE_CLANG
|
||||
#else
|
||||
#include <bit>
|
||||
#endif
|
||||
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
using usize = std::size_t;
|
||||
using uint = unsigned int;
|
||||
|
||||
using s8 = std::int8_t;
|
||||
using s16 = std::int16_t;
|
||||
using s32 = std::int32_t;
|
||||
using s64 = std::int64_t;
|
||||
|
||||
namespace Helpers {
|
||||
template <class... Args>
|
||||
std::string format(const std::string& fmt, Args&&... args) {
|
||||
const int size = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1;
|
||||
if (size <= 0) {
|
||||
return {};
|
||||
}
|
||||
const auto buf = std::make_unique<char[]>(size);
|
||||
std::snprintf(buf.get(), size, fmt.c_str(), args ...);
|
||||
return std::string(buf.get(), buf.get() + size - 1);
|
||||
}
|
||||
|
||||
// Unconditional panic, unlike panicDev which does not panic on user builds
|
||||
template <class... Args>
|
||||
[[noreturn]] static void panic(const char* fmt, Args&&... args) {
|
||||
std::cout << termcolor::on_red << "[FATAL] ";
|
||||
std::printf(fmt, args...);
|
||||
std::cout << termcolor::reset << "\n";
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef PANDA3DS_LIMITED_PANICS
|
||||
template <class... Args>
|
||||
static void panicDev(const char* fmt, Args&&... args) {}
|
||||
#else
|
||||
template <class... Args>
|
||||
[[noreturn]] static void panicDev(const char* fmt, Args&&... args) {
|
||||
panic(fmt, args...);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class... Args>
|
||||
static void warn(const char* fmt, Args&&... args) {
|
||||
std::cout << termcolor::on_red << "[Warning] ";
|
||||
std::printf(fmt, args...);
|
||||
std::cout << termcolor::reset << "\n";
|
||||
}
|
||||
|
||||
static constexpr bool buildingInDebugMode() {
|
||||
#ifdef NDEBUG
|
||||
return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr bool isUserBuild() {
|
||||
#ifdef PANDA3DS_USER_BUILD
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr bool isHydraCore() {
|
||||
#ifdef PANDA3DS_HYDRA_CORE
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr bool isAndroid() {
|
||||
#ifdef __ANDROID__
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static void debug_printf(const char* fmt, ...) {
|
||||
if constexpr (buildingInDebugMode()) {
|
||||
std::va_list args;
|
||||
va_start(args, fmt);
|
||||
std::vprintf(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign extend an arbitrary-size value to 32 bits
|
||||
static constexpr u32 inline signExtend32(u32 value, u32 startingSize) {
|
||||
auto temp = (s32)value;
|
||||
auto bitsToShift = 32 - startingSize;
|
||||
return (u32)(temp << bitsToShift >> bitsToShift);
|
||||
}
|
||||
|
||||
/// Sign extend an arbitrary-size value to 16 bits
|
||||
static constexpr u16 signExtend16(u16 value, u32 startingSize) {
|
||||
auto temp = (s16)value;
|
||||
auto bitsToShift = 16 - startingSize;
|
||||
return (u16)(temp << bitsToShift >> bitsToShift);
|
||||
}
|
||||
|
||||
/// Create a mask with `count` number of one bits.
|
||||
template <typename T, usize count>
|
||||
static constexpr T ones() {
|
||||
constexpr usize bitsize = CHAR_BIT * sizeof(T);
|
||||
static_assert(count <= bitsize, "count larger than bitsize of T");
|
||||
|
||||
if (count == T(0)) {
|
||||
return T(0);
|
||||
}
|
||||
return static_cast<T>(~static_cast<T>(0)) >> (bitsize - count);
|
||||
}
|
||||
|
||||
/// Extract bits from an integer-type
|
||||
template <usize offset, typename T>
|
||||
static constexpr T getBit(T value) {
|
||||
return (value >> offset) & T(1);
|
||||
}
|
||||
|
||||
/// Extract bits from an integer-type
|
||||
template <usize offset, usize bits, typename ReturnT, typename ValueT>
|
||||
static constexpr ReturnT getBits(ValueT value) {
|
||||
static_assert((offset + bits) <= (CHAR_BIT * sizeof(ValueT)), "Invalid bit range");
|
||||
static_assert(bits > 0, "Invalid bit size");
|
||||
return ReturnT(ValueT(value >> offset) & ones<ValueT, bits>());
|
||||
}
|
||||
|
||||
template <usize offset, usize bits, typename ValueT>
|
||||
static constexpr ValueT getBits(ValueT value) {
|
||||
return getBits<offset, bits, ValueT, ValueT>(value);
|
||||
}
|
||||
|
||||
#if defined(HELPERS_APPLE_CLANG) || defined(__ANDROID__) || !defined(__cpp_lib_bit_cast)
|
||||
template <class To, class From>
|
||||
constexpr To bit_cast(const From& from) noexcept {
|
||||
return *reinterpret_cast<const To*>(&from);
|
||||
}
|
||||
#else
|
||||
template <class To, class From>
|
||||
constexpr To bit_cast(const From& from) noexcept {
|
||||
return std::bit_cast<To, From>(from);
|
||||
}
|
||||
#endif
|
||||
|
||||
static std::vector<std::string> split(const std::string& s, const char c) {
|
||||
std::istringstream tmp(s);
|
||||
std::vector<std::string> result(1);
|
||||
|
||||
while (std::getline(tmp, *result.rbegin(), c)) {
|
||||
result.emplace_back();
|
||||
}
|
||||
|
||||
// Remove temporary slot
|
||||
result.pop_back();
|
||||
return result;
|
||||
}
|
||||
}; // namespace Helpers
|
||||
|
||||
// UDLs for memory size values
|
||||
constexpr size_t operator""_KB(unsigned long long int x) { return 1024ULL * x; }
|
||||
constexpr size_t operator""_MB(unsigned long long int x) { return 1024_KB * x; }
|
||||
constexpr size_t operator""_GB(unsigned long long int x) { return 1024_MB * x; }
|
|
@ -1,81 +0,0 @@
|
|||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
enum class HttpActionType { None, Screenshot, Key, TogglePause, Reset, LoadRom, Step };
|
||||
|
||||
class Emulator;
|
||||
namespace httplib {
|
||||
class Server;
|
||||
struct Response;
|
||||
}
|
||||
|
||||
// Wrapper for httplib::Response that allows the HTTP server to wait for the response to be ready
|
||||
struct DeferredResponseWrapper {
|
||||
DeferredResponseWrapper(httplib::Response& response) : inner_response(response) {}
|
||||
|
||||
httplib::Response& inner_response;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
// Actions derive from this class and are used to communicate with the HTTP server
|
||||
class HttpAction {
|
||||
HttpActionType type;
|
||||
|
||||
public:
|
||||
HttpAction(HttpActionType type) : type(type) {}
|
||||
virtual ~HttpAction() = default;
|
||||
|
||||
HttpActionType getType() const { return type; }
|
||||
|
||||
static std::unique_ptr<HttpAction> createScreenshotAction(DeferredResponseWrapper& response);
|
||||
static std::unique_ptr<HttpAction> createKeyAction(u32 key, bool state);
|
||||
static std::unique_ptr<HttpAction> createLoadRomAction(DeferredResponseWrapper& response, const std::filesystem::path& path, bool paused);
|
||||
static std::unique_ptr<HttpAction> createTogglePauseAction();
|
||||
static std::unique_ptr<HttpAction> createResetAction();
|
||||
static std::unique_ptr<HttpAction> createStepAction(DeferredResponseWrapper& response, int frames);
|
||||
};
|
||||
|
||||
struct HttpServer {
|
||||
HttpServer(Emulator* emulator);
|
||||
~HttpServer();
|
||||
void processActions();
|
||||
|
||||
private:
|
||||
static constexpr const char* httpServerScreenshotPath = "screenshot.png";
|
||||
|
||||
Emulator* emulator;
|
||||
std::unique_ptr<httplib::Server> server;
|
||||
|
||||
std::thread httpServerThread;
|
||||
std::queue<std::unique_ptr<HttpAction>> actionQueue;
|
||||
std::mutex actionQueueMutex;
|
||||
std::unique_ptr<HttpAction> currentStepAction {};
|
||||
|
||||
std::map<std::string, u32> keyMap;
|
||||
bool paused = false;
|
||||
int framesToRun = 0;
|
||||
|
||||
void startHttpServer();
|
||||
void pushAction(std::unique_ptr<HttpAction> action);
|
||||
std::string status();
|
||||
u32 stringToKey(const std::string& key_name);
|
||||
|
||||
HttpServer(const HttpServer&) = delete;
|
||||
HttpServer& operator=(const HttpServer&) = delete;
|
||||
};
|
||||
|
||||
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
class IOFile {
|
||||
FILE* handle = nullptr;
|
||||
static inline std::filesystem::path appData = ""; // Directory for holding app data. AppData on Windows
|
||||
|
||||
public:
|
||||
IOFile() : handle(nullptr) {}
|
||||
IOFile(FILE* handle) : handle(handle) {}
|
||||
IOFile(const std::filesystem::path& path, const char* permissions = "rb");
|
||||
|
||||
bool isOpen() { return handle != nullptr; }
|
||||
bool open(const std::filesystem::path& path, const char* permissions = "rb");
|
||||
bool open(const char* filename, const char* permissions = "rb");
|
||||
void close();
|
||||
|
||||
std::pair<bool, std::size_t> read(void* data, std::size_t length, std::size_t dataSize);
|
||||
std::pair<bool, std::size_t> readBytes(void* data, std::size_t count);
|
||||
|
||||
std::pair<bool, std::size_t> write(const void* data, std::size_t length, std::size_t dataSize);
|
||||
std::pair<bool, std::size_t> writeBytes(const void* data, std::size_t count);
|
||||
|
||||
std::optional<std::uint64_t> size();
|
||||
|
||||
bool seek(std::int64_t offset, int origin = SEEK_SET);
|
||||
bool rewind();
|
||||
bool flush();
|
||||
FILE* getHandle();
|
||||
static void setAppDataDir(const std::filesystem::path& dir);
|
||||
static std::filesystem::path getAppData() { return appData; }
|
||||
|
||||
// Sets the size of the file to "size" and returns whether it succeeded or not
|
||||
bool setSize(std::uint64_t size);
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace IPC {
|
||||
constexpr std::uint32_t responseHeader(std::uint32_t commandID, std::uint32_t normalResponses, std::uint32_t translateResponses) {
|
||||
// TODO: Maybe validate the response count stuff fits in 6 bits
|
||||
return (commandID << 16) | (normalResponses << 6) | translateResponses;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
|
||||
// Configuration memory addresses
|
||||
namespace ConfigMem {
|
||||
enum : u32 {
|
||||
KernelVersionMinor = 0x1FF80002,
|
||||
KernelVersionMajor = 0x1FF80003,
|
||||
SyscoreVer = 0x1FF80010,
|
||||
EnvInfo = 0x1FF80014,
|
||||
AppMemAlloc = 0x1FF80040,
|
||||
FirmUnknown = 0x1FF80060,
|
||||
FirmRevision = 0x1FF80061,
|
||||
FirmVersionMinor = 0x1FF80062,
|
||||
FirmVersionMajor = 0x1FF80063,
|
||||
FirmSyscoreVer = 0x1FF80064,
|
||||
FirmSdkVer = 0x1FF80068,
|
||||
|
||||
HardwareType = 0x1FF81004,
|
||||
Datetime0 = 0x1FF81020,
|
||||
WifiMac = 0x1FF81060,
|
||||
WifiLevel = 0x1FF81066,
|
||||
NetworkState = 0x1FF81067,
|
||||
SliderState3D = 0x1FF81080,
|
||||
LedState3D = 0x1FF81084,
|
||||
BatteryState = 0x1FF81085,
|
||||
Unknown1086 = 0x1FF81086,
|
||||
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
|
||||
};
|
||||
|
||||
// Shows what type of hardware we're running on
|
||||
namespace HardwareCodes {
|
||||
enum : u8 { Product = 1, Devboard = 2, Debugger = 3, Capture = 4 };
|
||||
}
|
||||
} // namespace ConfigMem
|
|
@ -1,113 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
|
||||
using Handle = u32;
|
||||
|
||||
namespace KernelHandles {
|
||||
enum : u32 {
|
||||
Max = 0xFFFF7FFF, // Max handle the kernel can automagically allocate
|
||||
|
||||
// Hardcoded handles
|
||||
CurrentThread = 0xFFFF8000, // Used by the original kernel
|
||||
CurrentProcess = 0xFFFF8001, // Used by the original kernel
|
||||
AC, // Something network related
|
||||
ACT, // Handles NNID accounts
|
||||
AM, // Application manager
|
||||
APT, // App Title something service?
|
||||
BOSS, // Streetpass stuff?
|
||||
CAM, // Camera service
|
||||
CECD, // More Streetpass stuff?
|
||||
CFG_U, // CFG service (Console & region info)
|
||||
CFG_I,
|
||||
CFG_S, // Used by most system apps in lieu of cfg:u
|
||||
CSND, // Plays audio directly from PCM samples
|
||||
DLP_SRVR, // Download Play: Server. Used for network play.
|
||||
DSP, // DSP service (Used for audio decoding and output)
|
||||
HID, // HID service (Handles input-related things including gyro. Does NOT handle New3DS controls or CirclePadPro)
|
||||
HTTP, // HTTP service (Handles HTTP requests)
|
||||
IR_USER, // One of 3 infrared communication services
|
||||
FRD_A, // Friend service (Miiverse friend service)
|
||||
FRD_U,
|
||||
FS, // Filesystem service
|
||||
GPU, // GPU service
|
||||
LCD, // LCD service (Used for configuring the displays)
|
||||
LDR_RO, // Loader service. Used for loading CROs.
|
||||
MCU_HWC, // Used for various MCU hardware-related things like battery control
|
||||
MIC, // MIC service (Controls the microphone)
|
||||
NFC, // NFC (Duh), used for Amiibo
|
||||
NIM, // Updates, DLC, etc
|
||||
NDM, // ?????
|
||||
NS_S, // Nintendo Shell service
|
||||
NWM_UDS, // Local multiplayer
|
||||
NEWS_U, // This service literally has 1 command (AddNotification) and I don't even understand what it does
|
||||
PTM_U, // PTM service (Used for accessing various console info, such as battery, shell and pedometer state)
|
||||
PTM_SYSM, // PTM system service
|
||||
PTM_PLAY, // PTM Play service, used for retrieving play history
|
||||
SOC, // Socket service
|
||||
SSL, // SSL service (Totally didn't expect that)
|
||||
Y2R, // Also does camera stuff
|
||||
|
||||
MinServiceHandle = AC,
|
||||
MaxServiceHandle = Y2R,
|
||||
|
||||
GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory
|
||||
FontSharedMemHandle,
|
||||
CSNDSharedMemHandle,
|
||||
APTCaptureSharedMemHandle, // Shared memory for display capture info,
|
||||
HIDSharedMemHandle,
|
||||
|
||||
MinSharedMemHandle = GSPSharedMemHandle,
|
||||
MaxSharedMemHandle = HIDSharedMemHandle,
|
||||
};
|
||||
|
||||
// Returns whether "handle" belongs to one of the OS services
|
||||
static constexpr bool isServiceHandle(Handle handle) {
|
||||
return handle >= MinServiceHandle && handle <= MaxServiceHandle;
|
||||
}
|
||||
|
||||
// Returns whether "handle" belongs to one of the OS services' shared memory areas
|
||||
static constexpr bool isSharedMemHandle(Handle handle) {
|
||||
return handle >= MinSharedMemHandle && handle <= MaxSharedMemHandle;
|
||||
}
|
||||
|
||||
// Returns the name of a handle as a string based on the given handle
|
||||
static const char* getServiceName(Handle handle) {
|
||||
switch (handle) {
|
||||
case AC: return "AC";
|
||||
case ACT: return "ACT";
|
||||
case AM: return "AM";
|
||||
case APT: return "APT";
|
||||
case BOSS: return "BOSS";
|
||||
case CAM: return "CAM";
|
||||
case CECD: return "CECD";
|
||||
case CFG_U: return "CFG:U";
|
||||
case CFG_I: return "CFG:I";
|
||||
case CSND: return "CSND";
|
||||
case DSP: return "DSP";
|
||||
case DLP_SRVR: return "DLP::SRVR";
|
||||
case HID: return "HID";
|
||||
case HTTP: return "HTTP";
|
||||
case IR_USER: return "IR:USER";
|
||||
case FRD_A: return "FRD:A";
|
||||
case FRD_U: return "FRD:U";
|
||||
case FS: return "FS";
|
||||
case GPU: return "GSP::GPU";
|
||||
case LCD: return "GSP::LCD";
|
||||
case LDR_RO: return "LDR:RO";
|
||||
case MCU_HWC: return "MCU::HWC";
|
||||
case MIC: return "MIC";
|
||||
case NDM: return "NDM";
|
||||
case NEWS_U: return "NEWS_U";
|
||||
case NWM_UDS: return "nwm::UDS";
|
||||
case NFC: return "NFC";
|
||||
case NIM: return "NIM";
|
||||
case PTM_U: return "PTM:U";
|
||||
case PTM_SYSM: return "PTM:SYSM";
|
||||
case PTM_PLAY: return "PTM:PLAY";
|
||||
case SOC: return "SOC";
|
||||
case SSL: return "SSL";
|
||||
case Y2R: return "Y2R";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,249 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "resource_limits.hpp"
|
||||
#include "services/service_manager.hpp"
|
||||
|
||||
class CPU;
|
||||
|
||||
class Kernel {
|
||||
std::span<u32, 16> regs;
|
||||
CPU& cpu;
|
||||
Memory& mem;
|
||||
|
||||
// The handle number for the next kernel object to be created
|
||||
u32 handleCounter;
|
||||
// A list of our OS threads, the max number of which depends on the resource limit (hardcoded 32 per process on retail it seems).
|
||||
// We have an extra thread for when no thread is capable of running. This thread is called the "idle thread" in our code
|
||||
// This thread is set up in setupIdleThread and just yields in a loop to see if any other thread has woken up
|
||||
std::array<Thread, appResourceLimits.maxThreads + 1> threads;
|
||||
static constexpr int idleThreadIndex = appResourceLimits.maxThreads;
|
||||
// Our waitlist system uses a bitfield of 64 bits to show which threads are waiting on an object.
|
||||
// That means we can have a maximum of 63 threads + 1 idle thread. This assert should never trigger because the max thread # is 32
|
||||
// But we have it here for safety purposes
|
||||
static_assert(appResourceLimits.maxThreads <= 63, "The waitlist system is built on the premise that <= 63 threads max can be active");
|
||||
|
||||
std::vector<KernelObject> objects;
|
||||
std::vector<Handle> portHandles;
|
||||
std::vector<Handle> mutexHandles;
|
||||
std::vector<Handle> timerHandles;
|
||||
|
||||
// Thread indices, sorted by priority
|
||||
std::vector<int> threadIndices;
|
||||
|
||||
Handle currentProcess;
|
||||
Handle mainThread;
|
||||
int currentThreadIndex;
|
||||
Handle srvHandle; // Handle for the special service manager port "srv:"
|
||||
Handle errorPortHandle; // Handle for the err:f port used for displaying errors
|
||||
|
||||
u32 arbiterCount;
|
||||
u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32)
|
||||
u32 aliveThreadCount; // How many of these threads are actually alive?
|
||||
ServiceManager serviceManager;
|
||||
|
||||
// Top 8 bits are the major version, bottom 8 are the minor version
|
||||
u16 kernelVersion = 0;
|
||||
|
||||
// Shows whether a reschedule will be need
|
||||
bool needReschedule = false;
|
||||
|
||||
Handle makeArbiter();
|
||||
Handle makeProcess(u32 id);
|
||||
Handle makePort(const char* name);
|
||||
Handle makeSession(Handle port);
|
||||
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
||||
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
||||
|
||||
public:
|
||||
// Needs to be public to be accessible to the APT/HID services
|
||||
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
|
||||
// Needs to be public to be accessible to the APT/DSP services
|
||||
Handle makeMutex(bool locked = false);
|
||||
// Needs to be public to be accessible to the service manager port
|
||||
Handle makeSemaphore(u32 initialCount, u32 maximumCount);
|
||||
Handle makeTimer(ResetType resetType);
|
||||
void pollTimers();
|
||||
|
||||
// Signals an event, returns true on success or false if the event does not exist
|
||||
bool signalEvent(Handle e);
|
||||
// Run the callback for "special" events that have callbacks
|
||||
void runEventCallback(Event::CallbackType callback);
|
||||
|
||||
void clearEvent(Handle e) {
|
||||
KernelObject* object = getObject(e, KernelObjectType::Event);
|
||||
if (object != nullptr) {
|
||||
object->getData<Event>()->fired = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void signalArbiter(u32 waitingAddress, s32 threadCount);
|
||||
void sleepThread(s64 ns);
|
||||
void sleepThreadOnArbiter(u32 waitingAddress);
|
||||
void switchThread(int newThreadIndex);
|
||||
void sortThreads();
|
||||
std::optional<int> getNextThread();
|
||||
void rescheduleThreads();
|
||||
bool canThreadRun(const Thread& t);
|
||||
bool shouldWaitOnObject(KernelObject* object);
|
||||
void releaseMutex(Mutex* moo);
|
||||
void cancelTimer(Timer* timer);
|
||||
void signalTimer(Handle timerHandle, Timer* timer);
|
||||
u64 getWakeupTick(s64 ns);
|
||||
|
||||
// Wake up the thread with the highest priority out of all threads in the waitlist
|
||||
// Returns the index of the woken up thread
|
||||
// Do not call this function with an empty waitlist!!!
|
||||
int wakeupOneThread(u64 waitlist, Handle handle);
|
||||
void wakeupAllThreads(u64 waitlist, Handle handle);
|
||||
|
||||
std::optional<Handle> getPortHandle(const char* name);
|
||||
void deleteObjectData(KernelObject& object);
|
||||
|
||||
KernelObject* getProcessFromPID(Handle handle);
|
||||
s32 getCurrentResourceValue(const KernelObject* limit, u32 resourceName);
|
||||
u32 getMaxForResource(const KernelObject* limit, u32 resourceName);
|
||||
u32 getTLSPointer();
|
||||
void setupIdleThread();
|
||||
|
||||
void acquireSyncObject(KernelObject* object, const Thread& thread);
|
||||
bool isWaitable(const KernelObject* object);
|
||||
|
||||
// Functions for the err:f port
|
||||
void handleErrorSyncRequest(u32 messagePointer);
|
||||
void throwError(u32 messagePointer);
|
||||
|
||||
std::string getProcessName(u32 pid);
|
||||
const char* resetTypeToString(u32 type);
|
||||
|
||||
MAKE_LOG_FUNCTION(log, kernelLogger)
|
||||
MAKE_LOG_FUNCTION(logSVC, svcLogger)
|
||||
MAKE_LOG_FUNCTION(logThread, threadLogger)
|
||||
MAKE_LOG_FUNCTION(logError, errorLogger)
|
||||
MAKE_LOG_FUNCTION(logFileIO, fileIOLogger)
|
||||
MAKE_LOG_FUNCTION_USER(logDebugString, debugStringLogger)
|
||||
|
||||
// SVC implementations
|
||||
void arbitrateAddress();
|
||||
void createAddressArbiter();
|
||||
void createMemoryBlock();
|
||||
void createThread();
|
||||
void controlMemory();
|
||||
void duplicateHandle();
|
||||
void exitThread();
|
||||
void mapMemoryBlock();
|
||||
void unmapMemoryBlock();
|
||||
void queryMemory();
|
||||
void getCurrentProcessorNumber();
|
||||
void getProcessID();
|
||||
void getProcessInfo();
|
||||
void getResourceLimit();
|
||||
void getResourceLimitLimitValues();
|
||||
void getResourceLimitCurrentValues();
|
||||
void getSystemInfo();
|
||||
void getSystemTick();
|
||||
void getThreadContext();
|
||||
void getThreadID();
|
||||
void getThreadIdealProcessor();
|
||||
void getThreadPriority();
|
||||
void sendSyncRequest();
|
||||
void setThreadPriority();
|
||||
void svcCancelTimer();
|
||||
void svcClearEvent();
|
||||
void svcClearTimer();
|
||||
void svcCloseHandle();
|
||||
void svcCreateEvent();
|
||||
void svcCreateMutex();
|
||||
void svcCreateSemaphore();
|
||||
void svcCreateTimer();
|
||||
void svcReleaseMutex();
|
||||
void svcReleaseSemaphore();
|
||||
void svcSignalEvent();
|
||||
void svcSetTimer();
|
||||
void svcSleepThread();
|
||||
void connectToPort();
|
||||
void outputDebugString();
|
||||
void waitSynchronization1();
|
||||
void waitSynchronizationN();
|
||||
|
||||
// File operations
|
||||
void handleFileOperation(u32 messagePointer, Handle file);
|
||||
void closeFile(u32 messagePointer, Handle file);
|
||||
void flushFile(u32 messagePointer, Handle file);
|
||||
void readFile(u32 messagePointer, Handle file);
|
||||
void writeFile(u32 messagePointer, Handle file);
|
||||
void getFileSize(u32 messagePointer, Handle file);
|
||||
void openLinkFile(u32 messagePointer, Handle file);
|
||||
void setFileSize(u32 messagePointer, Handle file);
|
||||
void setFilePriority(u32 messagePointer, Handle file);
|
||||
|
||||
// Directory operations
|
||||
void handleDirectoryOperation(u32 messagePointer, Handle directory);
|
||||
void closeDirectory(u32 messagePointer, Handle directory);
|
||||
void readDirectory(u32 messagePointer, Handle directory);
|
||||
|
||||
public:
|
||||
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config);
|
||||
void initializeFS() { return serviceManager.initializeFS(); }
|
||||
void setVersion(u8 major, u8 minor);
|
||||
void serviceSVC(u32 svc);
|
||||
void reset();
|
||||
|
||||
void requireReschedule() { needReschedule = true; }
|
||||
|
||||
void evalReschedule() {
|
||||
if (needReschedule) {
|
||||
needReschedule = false;
|
||||
rescheduleThreads();
|
||||
}
|
||||
}
|
||||
|
||||
Handle makeObject(KernelObjectType type) {
|
||||
if (handleCounter > KernelHandles::Max) [[unlikely]] {
|
||||
Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
|
||||
}
|
||||
|
||||
objects.push_back(KernelObject(handleCounter, type));
|
||||
log("Created %s object with handle %d\n", kernelObjectTypeToString(type), handleCounter);
|
||||
return handleCounter++;
|
||||
}
|
||||
|
||||
std::vector<KernelObject>& getObjects() {
|
||||
return objects;
|
||||
}
|
||||
|
||||
// Get pointer to the object with the specified handle
|
||||
KernelObject* getObject(Handle handle) {
|
||||
// Accessing an object that has not been created
|
||||
if (handle >= objects.size()) [[unlikely]] {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &objects[handle];
|
||||
}
|
||||
|
||||
// Get pointer to the object with the specified handle and type
|
||||
KernelObject* getObject(Handle handle, KernelObjectType type) {
|
||||
if (handle >= objects.size() || objects[handle].type != type) [[unlikely]] {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &objects[handle];
|
||||
}
|
||||
|
||||
ServiceManager& getServiceManager() { return serviceManager; }
|
||||
|
||||
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
|
||||
void clearInstructionCache();
|
||||
};
|
|
@ -1,245 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include "fs/archive_base.hpp"
|
||||
#include "handles.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
enum class KernelObjectType : u8 {
|
||||
AddressArbiter, Archive, Directory, File, MemoryBlock, Process, ResourceLimit, Session, Dummy,
|
||||
// Bundle waitable objects together in the enum to let the compiler optimize certain checks better
|
||||
Event, Mutex, Port, Semaphore, Timer, Thread
|
||||
};
|
||||
|
||||
enum class ResourceLimitCategory : int {
|
||||
Application = 0,
|
||||
SystemApplet = 1,
|
||||
LibraryApplet = 2,
|
||||
Misc = 3
|
||||
};
|
||||
|
||||
// Reset types (for use with events and timers)
|
||||
enum class ResetType {
|
||||
OneShot = 0, // When the primitive is signaled, it will wake up exactly one thread and will clear itself automatically.
|
||||
Sticky = 1, // When the primitive is signaled, it will wake up all threads and it won't clear itself automatically.
|
||||
Pulse = 2, // Only meaningful for timers: same as ONESHOT but it will periodically signal the timer instead of just once.
|
||||
};
|
||||
|
||||
enum class ArbitrationType {
|
||||
Signal = 0,
|
||||
WaitIfLess = 1,
|
||||
DecrementAndWaitIfLess = 2,
|
||||
WaitIfLessTimeout = 3,
|
||||
DecrementAndWaitIfLessTimeout = 4
|
||||
};
|
||||
|
||||
enum class ProcessorID : s32 {
|
||||
AllCPUs = -1,
|
||||
Default = -2,
|
||||
|
||||
AppCore = 0,
|
||||
Syscore = 1,
|
||||
New3DSExtra1 = 2,
|
||||
New3DSExtra2 = 3
|
||||
};
|
||||
|
||||
struct AddressArbiter {};
|
||||
|
||||
struct ResourceLimits {
|
||||
Handle handle;
|
||||
|
||||
s32 currentCommit = 0;
|
||||
};
|
||||
|
||||
struct Process {
|
||||
// Resource limits for this process
|
||||
ResourceLimits limits;
|
||||
// Process ID
|
||||
u32 id;
|
||||
|
||||
Process(u32 id) : id(id) {}
|
||||
};
|
||||
|
||||
struct Event {
|
||||
// Some events (for now, only the DSP semaphore events) need to execute a callback when signalled
|
||||
// This enum stores what kind of callback they should execute
|
||||
enum class CallbackType : u32 {
|
||||
None, DSPSemaphore,
|
||||
};
|
||||
|
||||
u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event
|
||||
ResetType resetType = ResetType::OneShot;
|
||||
CallbackType callback = CallbackType::None;
|
||||
bool fired = false;
|
||||
|
||||
Event(ResetType resetType) : resetType(resetType), waitlist(0) {}
|
||||
Event(ResetType resetType, CallbackType cb) : resetType(resetType), waitlist(0), callback(cb) {}
|
||||
};
|
||||
|
||||
struct Port {
|
||||
static constexpr u32 maxNameLen = 11;
|
||||
|
||||
char name[maxNameLen + 1] = {};
|
||||
bool isPublic = false; // Setting name=NULL creates a private port not accessible from svcConnectToPort.
|
||||
|
||||
Port(const char* name) {
|
||||
// If the name is empty (ie the first char is the null terminator) then the port is private
|
||||
isPublic = name[0] != '\0';
|
||||
std::strncpy(this->name, name, maxNameLen);
|
||||
}
|
||||
};
|
||||
|
||||
struct Session {
|
||||
Handle portHandle; // The port this session is subscribed to
|
||||
Session(Handle portHandle) : portHandle(portHandle) {}
|
||||
};
|
||||
|
||||
enum class ThreadStatus {
|
||||
Running, // Currently running
|
||||
Ready, // Ready to run
|
||||
WaitArbiter, // Waiting on an address arbiter
|
||||
WaitSleep, // Waiting due to a SleepThread SVC
|
||||
WaitSync1, // Waiting for the single object in the wait list to be ready
|
||||
WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready
|
||||
WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready
|
||||
WaitIPC, // Waiting for the reply from an IPC request
|
||||
Dormant, // Created but not yet made ready
|
||||
Dead // Run to completion, or forcefully terminated
|
||||
};
|
||||
|
||||
struct Thread {
|
||||
u32 initialSP; // Initial r13 value
|
||||
u32 entrypoint; // Initial r15 value
|
||||
u32 priority;
|
||||
u32 arg;
|
||||
ProcessorID processorID;
|
||||
ThreadStatus status;
|
||||
Handle handle; // OS handle for this thread
|
||||
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
|
||||
|
||||
// The waiting address for threads that are waiting on an AddressArbiter
|
||||
u32 waitingAddress;
|
||||
|
||||
// For WaitSynchronization(N): A vector of objects this thread is waiting for
|
||||
std::vector<Handle> waitList;
|
||||
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
|
||||
bool waitAll;
|
||||
// For WaitSynchronizationN: The "out" pointer
|
||||
u32 outPointer;
|
||||
u64 wakeupTick;
|
||||
|
||||
// Thread context used for switching between threads
|
||||
std::array<u32, 16> gprs;
|
||||
std::array<u32, 32> fprs; // Stored as u32 because dynarmic does it
|
||||
u32 cpsr;
|
||||
u32 fpscr;
|
||||
u32 tlsBase; // Base pointer for thread-local storage
|
||||
|
||||
// A list of threads waiting for this thread to terminate. Yes, threads are sync objects too.
|
||||
u64 threadsWaitingForTermination;
|
||||
};
|
||||
|
||||
static const char* kernelObjectTypeToString(KernelObjectType t) {
|
||||
switch (t) {
|
||||
case KernelObjectType::AddressArbiter: return "address arbiter";
|
||||
case KernelObjectType::Archive: return "archive";
|
||||
case KernelObjectType::Directory: return "directory";
|
||||
case KernelObjectType::Event: return "event";
|
||||
case KernelObjectType::File: return "file";
|
||||
case KernelObjectType::MemoryBlock: return "memory block";
|
||||
case KernelObjectType::Port: return "port";
|
||||
case KernelObjectType::Process: return "process";
|
||||
case KernelObjectType::ResourceLimit: return "resource limit";
|
||||
case KernelObjectType::Session: return "session";
|
||||
case KernelObjectType::Mutex: return "mutex";
|
||||
case KernelObjectType::Semaphore: return "semaphore";
|
||||
case KernelObjectType::Thread: return "thread";
|
||||
case KernelObjectType::Dummy: return "dummy";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct Mutex {
|
||||
u64 waitlist; // Refer to the getWaitlist function below for documentation
|
||||
Handle ownerThread = 0; // Index of the thread that holds the mutex if it's locked
|
||||
Handle handle; // Handle of the mutex itself
|
||||
u32 lockCount; // Number of times this mutex has been locked by its daddy. 0 = not locked
|
||||
bool locked;
|
||||
|
||||
Mutex(bool lock, Handle handle) : locked(lock), waitlist(0), lockCount(lock ? 1 : 0), handle(handle) {}
|
||||
};
|
||||
|
||||
struct Semaphore {
|
||||
u64 waitlist; // Refer to the getWaitlist function below for documentation
|
||||
s32 availableCount;
|
||||
s32 maximumCount;
|
||||
|
||||
Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {}
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
u64 waitlist; // Refer to the getWaitlist function below for documentation
|
||||
ResetType resetType = ResetType::OneShot;
|
||||
|
||||
u64 fireTick; // CPU tick the timer will be fired
|
||||
u64 interval; // Number of ns until the timer fires for the second and future times
|
||||
bool fired; // Has this timer been signalled?
|
||||
bool running; // Is this timer running or stopped?
|
||||
|
||||
Timer(ResetType type) : resetType(type), fireTick(0), interval(0), waitlist(0), fired(false), running(false) {}
|
||||
};
|
||||
|
||||
struct MemoryBlock {
|
||||
u32 addr = 0;
|
||||
u32 size = 0;
|
||||
u32 myPermission = 0;
|
||||
u32 otherPermission = 0;
|
||||
bool mapped = false;
|
||||
|
||||
MemoryBlock(u32 addr, u32 size, u32 myPerm, u32 otherPerm) : addr(addr), size(size), myPermission(myPerm), otherPermission(otherPerm),
|
||||
mapped(false) {}
|
||||
};
|
||||
|
||||
// Generic kernel object class
|
||||
struct KernelObject {
|
||||
Handle handle = 0; // A u32 the OS will use to identify objects
|
||||
void* data = nullptr;
|
||||
KernelObjectType type;
|
||||
|
||||
KernelObject(Handle handle, KernelObjectType type) : handle(handle), type(type) {}
|
||||
|
||||
// Our destructor does not free the data in order to avoid it being freed when our std::vector is expanded
|
||||
// Thus, the kernel needs to delete it when appropriate
|
||||
~KernelObject() {}
|
||||
|
||||
template <typename T>
|
||||
T* getData() {
|
||||
return static_cast<T*>(data);
|
||||
}
|
||||
|
||||
const char* getTypeName() const {
|
||||
return kernelObjectTypeToString(type);
|
||||
}
|
||||
|
||||
// Retrieves a reference to the waitlist for a specified object
|
||||
// We return a reference because this function is only called in the kernel threading internals
|
||||
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
|
||||
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
|
||||
// Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object
|
||||
// For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object
|
||||
u64& getWaitlist() {
|
||||
// This code is actually kinda trash but eh good enough
|
||||
switch (type) {
|
||||
case KernelObjectType::Event: return getData<Event>()->waitlist;
|
||||
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
|
||||
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
|
||||
case KernelObjectType::Thread: return getData<Thread>()->threadsWaitingForTermination;
|
||||
case KernelObjectType::Timer: return getData<Timer>()->waitlist;
|
||||
|
||||
// This should be unreachable once we fully implement sync objects
|
||||
default: [[unlikely]]
|
||||
Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
#pragma once
|
||||
#include "helpers.hpp"
|
||||
|
||||
// Values and resource limit structure taken from Citra
|
||||
|
||||
struct ResourceLimitValues {
|
||||
u32 maxPriority;
|
||||
u32 maxCommit;
|
||||
u32 maxThreads;
|
||||
u32 maxEvents;
|
||||
u32 maxMutexes;
|
||||
u32 maxSemaphores;
|
||||
u32 maxTimers;
|
||||
u32 maxSharedMem;
|
||||
u32 maxAddressArbiters;
|
||||
u32 maxCPUTime;
|
||||
};
|
||||
|
||||
// APPLICATION resource limit
|
||||
static constexpr ResourceLimitValues appResourceLimits = {
|
||||
.maxPriority = 0x18,
|
||||
.maxCommit = 0x4000000,
|
||||
.maxThreads = 0x20,
|
||||
.maxEvents = 0x20,
|
||||
.maxMutexes = 0x20,
|
||||
.maxSemaphores = 0x8,
|
||||
.maxTimers = 0x8,
|
||||
.maxSharedMem = 0x20,
|
||||
.maxAddressArbiters = 0x2,
|
||||
.maxCPUTime = 0x1E
|
||||
};
|
||||
|
||||
// SYS_APPLET resource limit
|
||||
static constexpr ResourceLimitValues sysAppletResourceLimits = {
|
||||
.maxPriority = 0x4,
|
||||
.maxCommit = 0x5E00000,
|
||||
.maxThreads = 0x1D,
|
||||
.maxEvents = 0xB,
|
||||
.maxMutexes = 0x8,
|
||||
.maxSemaphores = 0x4,
|
||||
.maxTimers = 0x4,
|
||||
.maxSharedMem = 0x8,
|
||||
.maxAddressArbiters = 0x3,
|
||||
.maxCPUTime = 0x2710
|
||||
};
|
||||
|
||||
// LIB_APPLET resource limit
|
||||
static constexpr ResourceLimitValues libAppletResourceLimits = {
|
||||
.maxPriority = 0x4,
|
||||
.maxCommit = 0x600000,
|
||||
.maxThreads = 0xE,
|
||||
.maxEvents = 0x8,
|
||||
.maxMutexes = 0x8,
|
||||
.maxSemaphores = 0x4,
|
||||
.maxTimers = 0x4,
|
||||
.maxSharedMem = 0x8,
|
||||
.maxAddressArbiters = 0x1,
|
||||
.maxCPUTime = 0x2710
|
||||
};
|
||||
|
||||
// OTHER resource limit
|
||||
static constexpr ResourceLimitValues otherResourceLimits = {
|
||||
.maxPriority = 0x4,
|
||||
.maxCommit = 0x2180000,
|
||||
.maxThreads = 0xE1,
|
||||
.maxEvents = 0x108,
|
||||
.maxMutexes = 0x25,
|
||||
.maxSemaphores = 0x43,
|
||||
.maxTimers = 0x2C,
|
||||
.maxSharedMem = 0x1F,
|
||||
.maxAddressArbiters = 0x2D,
|
||||
.maxCPUTime = 0x3E8
|
||||
};
|
||||
|
||||
namespace ResourceType {
|
||||
enum : u32 {
|
||||
Priority = 0,
|
||||
Commit = 1,
|
||||
Thread = 2,
|
||||
Events = 3,
|
||||
Mutex = 4,
|
||||
Semaphore = 5,
|
||||
Timer = 6,
|
||||
SharedMem = 7,
|
||||
AddressArbiter = 8,
|
||||
CPUTime = 9
|
||||
};
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include "helpers.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "loader/ncch.hpp"
|
||||
|
||||
struct HB3DSX {
|
||||
// File layout:
|
||||
// - File header
|
||||
// - Code, rodata and data relocation table headers
|
||||
// - Code segment
|
||||
// - Rodata segment
|
||||
// - Loadable (non-BSS) part of the data segment
|
||||
// - Code relocation table
|
||||
// - Rodata relocation table
|
||||
// - Data relocation table
|
||||
|
||||
// Memory layout before relocations are applied:
|
||||
// [0..codeSegSize) -> code segment
|
||||
// [codeSegSize..rodataSegSize) -> rodata segment
|
||||
// [rodataSegSize..dataSegSize) -> data segment
|
||||
|
||||
// Memory layout after relocations are applied: well, however the loader sets it up :)
|
||||
// The entrypoint is always the start of the code segment.
|
||||
// The BSS section must be cleared manually by the application.
|
||||
|
||||
// File header
|
||||
struct Header {
|
||||
// minus char magic[4]
|
||||
u16 headerSize;
|
||||
u16 relocHeaderSize;
|
||||
u32 formatVer;
|
||||
u32 flags;
|
||||
|
||||
// Sizes of the code, rodata and data segments +
|
||||
// size of the BSS section (uninitialized latter half of the data segment)
|
||||
u32 codeSegSize, rodataSegSize, dataSegSize, bssSize;
|
||||
};
|
||||
|
||||
// Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts.
|
||||
struct RelocHeader {
|
||||
u32 absoluteCount; // # of absolute relocations (that is, fix address to post-relocation memory layout)
|
||||
u32 relativeCount; // # of cross-segment relative relocations (that is, 32bit signed offsets that need to be patched)
|
||||
// more?
|
||||
|
||||
// Relocations are written in this order:
|
||||
// - Absolute relocs
|
||||
// - Relative relocs
|
||||
};
|
||||
|
||||
enum class RelocType {
|
||||
Absolute,
|
||||
Relative,
|
||||
};
|
||||
|
||||
// Relocation entry: from the current pointer, skip X words and patch Y words
|
||||
struct Reloc {
|
||||
u16 skip, patch;
|
||||
};
|
||||
|
||||
// _prm structure
|
||||
static constexpr std::array<char, 4> PRM_MAGIC = {'_', 'P', 'R', 'M'};
|
||||
struct PrmStruct {
|
||||
char magic[4];
|
||||
u32 pSrvOverride;
|
||||
u32 aptAppId;
|
||||
u32 heapSize, linearHeapSize;
|
||||
u32 pArgList;
|
||||
u32 runFlags;
|
||||
};
|
||||
|
||||
IOFile file;
|
||||
|
||||
static constexpr u32 entrypoint = 0x00100000; // Initial ARM11 PC
|
||||
u32 romFSSize = 0;
|
||||
u32 romFSOffset = 0;
|
||||
|
||||
bool hasRomFs() const;
|
||||
std::pair<bool, std::size_t> readRomFSBytes(void *dst, std::size_t offset, std::size_t size);
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include "helpers.hpp"
|
||||
|
||||
// For parsing the LZ77 format used for compressing the .code file in the ExeFS
|
||||
namespace CartLZ77 {
|
||||
// Retrieves the uncompressed size of the compressed LZ77 data stored in buffer with a specific compressed size
|
||||
u32 decompressedSize(const u8* buffer, u32 compressedSize);
|
||||
|
||||
template <typename T>
|
||||
static u32 decompressedSize(const std::vector<T>& buffer) {
|
||||
return decompressedSize((u8*)buffer.data(), u32(buffer.size() * sizeof(T)));
|
||||
}
|
||||
|
||||
// Decompresses an LZ77-compressed buffer stored in input to output
|
||||
bool decompress(std::vector<u8>& output, const std::vector<u8>& input);
|
||||
} // End namespace CartLZ77
|
|
@ -1,88 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto/aes_engine.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
struct NCCH {
|
||||
struct EncryptionInfo {
|
||||
Crypto::AESKey normalKey;
|
||||
Crypto::AESKey initialCounter;
|
||||
};
|
||||
|
||||
struct FSInfo { // Info on the ExeFS/RomFS
|
||||
u64 offset = 0;
|
||||
u64 size = 0;
|
||||
u64 hashRegionSize = 0;
|
||||
std::optional<EncryptionInfo> encryptionInfo;
|
||||
};
|
||||
|
||||
// Descriptions for .text, .data and .rodata sections
|
||||
struct CodeSetInfo {
|
||||
u32 address = 0;
|
||||
u32 pageCount = 0;
|
||||
u32 size = 0;
|
||||
|
||||
// Extract the code set info from the relevant header data
|
||||
void extract(const u8 *headerEntry) {
|
||||
address = *(u32 *)&headerEntry[0];
|
||||
pageCount = *(u32 *)&headerEntry[4];
|
||||
size = *(u32 *)&headerEntry[8];
|
||||
}
|
||||
};
|
||||
|
||||
u64 partitionIndex = 0;
|
||||
u64 programID = 0;
|
||||
u64 fileOffset = 0;
|
||||
|
||||
bool isNew3DS = false;
|
||||
bool initialized = false;
|
||||
bool compressCode = false; // Shows whether the .code file in the ExeFS is compressed
|
||||
bool mountRomFS = false;
|
||||
bool encrypted = false;
|
||||
bool fixedCryptoKey = false;
|
||||
bool seedCrypto = false;
|
||||
u8 secondaryKeySlot = 0;
|
||||
|
||||
static constexpr u64 mediaUnit = 0x200;
|
||||
u64 size = 0; // Size of NCCH converted to bytes
|
||||
u32 stackSize = 0;
|
||||
u32 bssSize = 0;
|
||||
u32 exheaderSize = 0;
|
||||
|
||||
FSInfo exheaderInfo;
|
||||
FSInfo exeFS;
|
||||
FSInfo romFS;
|
||||
CodeSetInfo text, data, rodata;
|
||||
FSInfo partitionInfo;
|
||||
|
||||
// Contents of the .code file in the ExeFS
|
||||
std::vector<u8> codeFile;
|
||||
// Contains of the cart's save data
|
||||
std::vector<u8> saveData;
|
||||
// The cart region. Only the CXI's region matters to us. Necessary to get past region locking
|
||||
std::optional<Regions> region = std::nullopt;
|
||||
std::vector<u8> smdh;
|
||||
|
||||
// Returns true on success, false on failure
|
||||
// Partition index/offset/size must have been set before this
|
||||
bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile &file, const FSInfo &info);
|
||||
|
||||
bool hasExtendedHeader() { return exheaderSize != 0; }
|
||||
bool hasExeFS() { return exeFS.size != 0; }
|
||||
bool hasRomFS() { return romFS.size != 0; }
|
||||
bool hasCode() { return codeFile.size() != 0; }
|
||||
bool hasSaveData() { return saveData.size() != 0; }
|
||||
|
||||
// Parse SMDH for region info and such. Returns false on failure, true on success
|
||||
bool parseSMDH(const std::vector<u8> &smdh);
|
||||
|
||||
std::pair<bool, Crypto::AESKey> getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
||||
std::pair<bool, Crypto::AESKey> getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
||||
|
||||
std::pair<bool, std::size_t> readFromFile(IOFile &file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size);
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include "helpers.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "loader/ncch.hpp"
|
||||
|
||||
struct NCSD {
|
||||
static constexpr u64 mediaUnit = 0x200;
|
||||
|
||||
struct Partition {
|
||||
u64 offset = 0; // Offset of partition converted to bytes
|
||||
u64 length = 0; // Length of partition converted to bytes
|
||||
NCCH ncch;
|
||||
};
|
||||
|
||||
IOFile file;
|
||||
u64 size = 0; // Image size according to the header converted to bytes
|
||||
std::array<Partition, 8> partitions; // NCCH partitions
|
||||
|
||||
u32 entrypoint; // Initial ARM11 PC
|
||||
};
|
|
@ -1,93 +0,0 @@
|
|||
#pragma once
|
||||
#include <cstdarg>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
namespace Log {
|
||||
// Our logger class
|
||||
template <bool enabled>
|
||||
class Logger {
|
||||
public:
|
||||
void log(const char* fmt, ...) {
|
||||
if constexpr (!enabled) return;
|
||||
|
||||
std::va_list args;
|
||||
va_start(args, fmt);
|
||||
#ifdef __ANDROID__
|
||||
__android_log_vprint(ANDROID_LOG_DEFAULT, "Panda3DS", fmt, args);
|
||||
#else
|
||||
std::vprintf(fmt, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
}
|
||||
};
|
||||
|
||||
// Our loggers here. Enable/disable by toggling the template param
|
||||
static Logger<false> kernelLogger;
|
||||
// Enables output for the outputDebugString SVC
|
||||
static Logger<true> debugStringLogger;
|
||||
static Logger<false> errorLogger;
|
||||
static Logger<false> fileIOLogger;
|
||||
static Logger<false> svcLogger;
|
||||
static Logger<false> threadLogger;
|
||||
static Logger<false> gpuLogger;
|
||||
static Logger<false> rendererLogger;
|
||||
static Logger<false> shaderJITLogger;
|
||||
static Logger<false> dspLogger;
|
||||
|
||||
// Service loggers
|
||||
static Logger<false> acLogger;
|
||||
static Logger<false> actLogger;
|
||||
static Logger<false> amLogger;
|
||||
static Logger<false> aptLogger;
|
||||
static Logger<false> bossLogger;
|
||||
static Logger<false> camLogger;
|
||||
static Logger<false> cecdLogger;
|
||||
static Logger<false> cfgLogger;
|
||||
static Logger<false> csndLogger;
|
||||
static Logger<false> dspServiceLogger;
|
||||
static Logger<false> dlpSrvrLogger;
|
||||
static Logger<false> frdLogger;
|
||||
static Logger<false> fsLogger;
|
||||
static Logger<false> hidLogger;
|
||||
static Logger<false> httpLogger;
|
||||
static Logger<false> irUserLogger;
|
||||
static Logger<false> gspGPULogger;
|
||||
static Logger<false> gspLCDLogger;
|
||||
static Logger<false> ldrLogger;
|
||||
static Logger<false> mcuLogger;
|
||||
static Logger<false> micLogger;
|
||||
static Logger<false> newsLogger;
|
||||
static Logger<false> nfcLogger;
|
||||
static Logger<false> nwmUdsLogger;
|
||||
static Logger<false> nimLogger;
|
||||
static Logger<false> ndmLogger;
|
||||
static Logger<false> ptmLogger;
|
||||
static Logger<false> socLogger;
|
||||
static Logger<false> sslLogger;
|
||||
static Logger<false> y2rLogger;
|
||||
static Logger<false> srvLogger;
|
||||
|
||||
// We have 2 ways to create a log function
|
||||
// MAKE_LOG_FUNCTION: Creates a log function which is toggleable but always killed for user-facing builds
|
||||
// MAKE_LOG_FUNCTION_USER: Creates a log function which is toggleable, may be on for user builds as well
|
||||
// We need this because sadly due to the loggers taking variadic arguments, compilers will not properly
|
||||
// Kill them fully even when they're disabled. The only way they will is if the function with varargs is totally empty
|
||||
|
||||
#define MAKE_LOG_FUNCTION_USER(functionName, logger) \
|
||||
template <typename... Args> \
|
||||
void functionName(const char* fmt, Args&&... args) { \
|
||||
Log::logger.log(fmt, args...); \
|
||||
}
|
||||
|
||||
#ifdef PANDA3DS_USER_BUILD
|
||||
#define MAKE_LOG_FUNCTION(functionName, logger) \
|
||||
template <typename... Args> \
|
||||
void functionName(const char* fmt, Args&&... args) {}
|
||||
#else
|
||||
#define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger)
|
||||
#endif
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
// The kinds of events that can cause a Lua call.
|
||||
// Frame: Call program on frame end
|
||||
// TODO: Add more
|
||||
enum class LuaEvent {
|
||||
Frame,
|
||||
};
|
||||
|
||||
class Emulator;
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_LUA
|
||||
extern "C" {
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
|
||||
#include "luajit.h"
|
||||
}
|
||||
|
||||
class LuaManager {
|
||||
lua_State* L = nullptr;
|
||||
bool initialized = false;
|
||||
bool haveScript = false;
|
||||
|
||||
void signalEventInternal(LuaEvent e);
|
||||
|
||||
public:
|
||||
// For Lua we must have some global pointers to our emulator objects to use them in script code via thunks. See the thunks in lua.cpp as an
|
||||
// example
|
||||
static Emulator* g_emulator;
|
||||
|
||||
LuaManager(Emulator& emulator) { g_emulator = &emulator; }
|
||||
|
||||
void close();
|
||||
void initialize();
|
||||
void initializeThunks();
|
||||
void loadFile(const char* path);
|
||||
void loadString(const std::string& code);
|
||||
|
||||
void reset();
|
||||
void signalEvent(LuaEvent e) {
|
||||
if (haveScript) [[unlikely]] {
|
||||
signalEventInternal(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#else // Lua not enabled, Lua manager does nothing
|
||||
class LuaManager {
|
||||
public:
|
||||
LuaManager(Emulator& emulator) {}
|
||||
|
||||
void close() {}
|
||||
void initialize() {}
|
||||
void loadFile(const char* path) {}
|
||||
void loadString(const std::string& code) {}
|
||||
void reset() {}
|
||||
void signalEvent(LuaEvent e) {}
|
||||
};
|
||||
#endif
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project / 2023 Panda3DS Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Math {
|
||||
|
||||
template <class T>
|
||||
struct Rectangle {
|
||||
T left{};
|
||||
T top{};
|
||||
T right{};
|
||||
T bottom{};
|
||||
|
||||
constexpr Rectangle() = default;
|
||||
|
||||
constexpr Rectangle(T left, T top, T right, T bottom)
|
||||
: left(left), top(top), right(right), bottom(bottom) {}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
|
||||
return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
|
||||
(bottom == rhs.bottom);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Rectangle<T> operator*(const T value) const {
|
||||
return Rectangle{left * value, top * value, right * value, bottom * value};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr Rectangle<T> operator/(const T value) const {
|
||||
return Rectangle{left / value, top / value, right / value, bottom / value};
|
||||
}
|
||||
|
||||
[[nodiscard]] T getWidth() const {
|
||||
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
|
||||
}
|
||||
|
||||
[[nodiscard]] T getHeight() const {
|
||||
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
|
||||
}
|
||||
|
||||
[[nodiscard]] T getArea() const {
|
||||
return getWidth() * getHeight();
|
||||
}
|
||||
|
||||
[[nodiscard]] Rectangle<T> translateX(const T x) const {
|
||||
return Rectangle{left + x, top, right + x, bottom};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rectangle<T> translateY(const T y) const {
|
||||
return Rectangle{left, top + y, right, bottom + y};
|
||||
}
|
||||
|
||||
[[nodiscard]] Rectangle<T> scale(const float s) const {
|
||||
return Rectangle{left, top, static_cast<T>(left + getWidth() * s),
|
||||
static_cast<T>(top + getHeight() * s)};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Rectangle(T, T, T, T) -> Rectangle<T>;
|
||||
|
||||
template <typename T>
|
||||
using Rect = Rectangle<T>;
|
||||
|
||||
} // end namespace Math
|
|
@ -1,291 +0,0 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "crypto/aes_engine.hpp"
|
||||
#include "handles.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "loader/ncsd.hpp"
|
||||
#include "loader/3dsx.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
namespace PhysicalAddrs {
|
||||
enum : u32 {
|
||||
VRAM = 0x18000000,
|
||||
VRAMEnd = VRAM + 0x005FFFFF,
|
||||
FCRAM = 0x20000000,
|
||||
FCRAMEnd = FCRAM + 0x07FFFFFF
|
||||
};
|
||||
}
|
||||
|
||||
namespace VirtualAddrs {
|
||||
enum : u32 {
|
||||
ExecutableStart = 0x00100000,
|
||||
MaxExeSize = 0x03F00000,
|
||||
ExecutableEnd = 0x00100000 + 0x03F00000,
|
||||
|
||||
// Stack for main ARM11 thread.
|
||||
// Typically 0x4000 bytes, determined by exheader
|
||||
StackTop = 0x10000000,
|
||||
DefaultStackSize = 0x4000,
|
||||
|
||||
NormalHeapStart = 0x08000000,
|
||||
LinearHeapStartOld = 0x14000000, // If kernel version < 0x22C
|
||||
LinearHeapEndOld = 0x1C000000,
|
||||
|
||||
LinearHeapStartNew = 0x30000000,
|
||||
LinearHeapEndNew = 0x40000000,
|
||||
|
||||
// Start of TLS for first thread. Next thread's storage will be at TLSBase + 0x1000, and so on
|
||||
TLSBase = 0xFF400000,
|
||||
TLSSize = 0x1000,
|
||||
|
||||
VramStart = 0x1F000000,
|
||||
VramSize = 0x00600000,
|
||||
FcramTotalSize = 128_MB,
|
||||
DSPMemStart = 0x1FF00000
|
||||
};
|
||||
}
|
||||
|
||||
// Types for svcQueryMemory
|
||||
namespace KernelMemoryTypes {
|
||||
// This makes no sense
|
||||
enum MemoryState : u32 {
|
||||
Free = 0,
|
||||
Reserved = 1,
|
||||
IO = 2,
|
||||
Static = 3,
|
||||
Code = 4,
|
||||
Private = 5,
|
||||
Shared = 6,
|
||||
Continuous = 7,
|
||||
Aliased = 8,
|
||||
Alias = 9,
|
||||
AliasCode = 10,
|
||||
Locked = 11,
|
||||
|
||||
PERMISSION_R = 1 << 0,
|
||||
PERMISSION_W = 1 << 1,
|
||||
PERMISSION_X = 1 << 2
|
||||
};
|
||||
|
||||
// I assume this is referring to a single piece of allocated memory? If it's for pages, it makes no sense.
|
||||
// If it's for multiple allocations, it also makes no sense
|
||||
struct MemoryInfo {
|
||||
u32 baseAddr; // Base process virtual address. Used as a paddr in lockedMemoryInfo instead
|
||||
u32 size; // Of what?
|
||||
u32 perms; // Is this referring to a single page or?
|
||||
u32 state;
|
||||
|
||||
u32 end() { return baseAddr + size; }
|
||||
MemoryInfo(u32 baseAddr, u32 size, u32 perms, u32 state) : baseAddr(baseAddr), size(size)
|
||||
, perms(perms), state(state) {}
|
||||
};
|
||||
|
||||
// Shared memory block for HID, GSP:GPU etc
|
||||
struct SharedMemoryBlock {
|
||||
u32 paddr; // Physical address of this block's memory
|
||||
u32 size; // Size of block
|
||||
u32 handle; // The handle of the shared memory block
|
||||
bool mapped; // Has this block been mapped at least once?
|
||||
|
||||
SharedMemoryBlock(u32 paddr, u32 size, u32 handle) : paddr(paddr), size(size), handle(handle), mapped(false) {}
|
||||
};
|
||||
}
|
||||
|
||||
class Memory {
|
||||
u8* fcram;
|
||||
u8* dspRam; // Provided to us by Audio
|
||||
u8* vram; // Provided to the memory class by the GPU class
|
||||
|
||||
u64& cpuTicks; // Reference to the CPU tick counter
|
||||
using SharedMemoryBlock = KernelMemoryTypes::SharedMemoryBlock;
|
||||
|
||||
// Our dynarmic core uses page tables for reads and writes with 4096 byte pages
|
||||
std::vector<uintptr_t> readTable, writeTable;
|
||||
|
||||
// This tracks our OS' memory allocations
|
||||
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
||||
|
||||
std::array<SharedMemoryBlock, 5> sharedMemBlocks = {
|
||||
SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem
|
||||
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
||||
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle), // HID shared memory
|
||||
SharedMemoryBlock(0, 0x3000, KernelHandles::CSNDSharedMemHandle), // CSND shared memory
|
||||
SharedMemoryBlock(0, 0xE7000, KernelHandles::APTCaptureSharedMemHandle), // APT Capture Buffer memory
|
||||
};
|
||||
|
||||
public:
|
||||
static constexpr u32 pageShift = 12;
|
||||
static constexpr u32 pageSize = 1 << pageShift;
|
||||
static constexpr u32 pageMask = pageSize - 1;
|
||||
static constexpr u32 totalPageCount = 1 << (32 - pageShift);
|
||||
|
||||
static constexpr u32 FCRAM_SIZE = u32(128_MB);
|
||||
static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB);
|
||||
static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize;
|
||||
static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize;
|
||||
|
||||
static constexpr u32 DSP_RAM_SIZE = u32(512_KB);
|
||||
static constexpr u32 DSP_CODE_MEMORY_OFFSET = u32(0_KB);
|
||||
static constexpr u32 DSP_DATA_MEMORY_OFFSET = u32(256_KB);
|
||||
|
||||
private:
|
||||
std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages;
|
||||
std::optional<u32> findPaddr(u32 size);
|
||||
u64 timeSince3DSEpoch();
|
||||
|
||||
// https://www.3dbrew.org/wiki/Configuration_Memory#ENVINFO
|
||||
// Report a retail unit without JTAG
|
||||
static constexpr u32 envInfo = 1;
|
||||
|
||||
// Stored in Configuration Memory starting @ 0x1FF80060
|
||||
struct FirmwareInfo {
|
||||
u8 unk; // Usually 0 according to 3DBrew
|
||||
u8 revision;
|
||||
u8 minor;
|
||||
u8 major;
|
||||
u32 syscoreVer;
|
||||
u32 sdkVer;
|
||||
};
|
||||
|
||||
// Values taken from 3DBrew and Citra
|
||||
static constexpr FirmwareInfo firm{.unk = 0, .revision = 0, .minor = 0x34, .major = 2, .syscoreVer = 2, .sdkVer = 0x0000F297};
|
||||
// Adjusted upon loading a ROM based on the ROM header. Used by CFG::SecureInfoGetArea to get past region locks
|
||||
Regions region = Regions::USA;
|
||||
const EmulatorConfig& config;
|
||||
|
||||
static constexpr std::array<u8, 6> MACAddress = {0x40, 0xF4, 0x07, 0xFF, 0xFF, 0xEE};
|
||||
|
||||
public:
|
||||
u16 kernelVersion = 0;
|
||||
u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
|
||||
u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore)
|
||||
|
||||
Memory(u64& cpuTicks, const EmulatorConfig& config);
|
||||
void reset();
|
||||
void* getReadPointer(u32 address);
|
||||
void* getWritePointer(u32 address);
|
||||
std::optional<u32> loadELF(std::ifstream& file);
|
||||
std::optional<u32> load3DSX(const std::filesystem::path& path);
|
||||
std::optional<NCSD> loadNCSD(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
||||
std::optional<NCSD> loadCXI(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
||||
|
||||
bool mapCXI(NCSD& ncsd, NCCH& cxi);
|
||||
bool map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header);
|
||||
|
||||
u8 read8(u32 vaddr);
|
||||
u16 read16(u32 vaddr);
|
||||
u32 read32(u32 vaddr);
|
||||
u64 read64(u32 vaddr);
|
||||
std::string readString(u32 vaddr, u32 maxCharacters);
|
||||
|
||||
void write8(u32 vaddr, u8 value);
|
||||
void write16(u32 vaddr, u16 value);
|
||||
void write32(u32 vaddr, u32 value);
|
||||
void write64(u32 vaddr, u64 value);
|
||||
|
||||
u32 getLinearHeapVaddr();
|
||||
u8* getFCRAM() { return fcram; }
|
||||
|
||||
// Total amount of OS-only FCRAM available (Can vary depending on how much FCRAM the app requests via the cart exheader)
|
||||
u32 totalSysFCRAM() {
|
||||
return FCRAM_SIZE - FCRAM_APPLICATION_SIZE;
|
||||
}
|
||||
|
||||
// Amount of OS-only FCRAM currently available
|
||||
u32 remainingSysFCRAM() {
|
||||
return totalSysFCRAM() - usedSystemMemory;
|
||||
}
|
||||
|
||||
// Physical FCRAM index to the start of OS FCRAM
|
||||
// We allocate the first part of physical FCRAM for the application, and the rest to the OS. So the index for the OS = application ram size
|
||||
u32 sysFCRAMIndex() {
|
||||
return FCRAM_APPLICATION_SIZE;
|
||||
}
|
||||
|
||||
enum class BatteryLevel {
|
||||
Empty = 0, AlmostEmpty, OneBar, TwoBars, ThreeBars, FourBars
|
||||
};
|
||||
u8 getBatteryState(bool adapterConnected, bool charging, BatteryLevel batteryLevel) {
|
||||
u8 value = static_cast<u8>(batteryLevel) << 2; // Bits 2:4 are the battery level from 0 to 5
|
||||
if (adapterConnected) value |= 1 << 0; // Bit 0 shows if the charger is connected
|
||||
if (charging) value |= 1 << 1; // Bit 1 shows if we're charging
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
NCCH* getCXI() {
|
||||
if (loadedCXI.has_value()) {
|
||||
return &loadedCXI.value();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
HB3DSX* get3DSX() {
|
||||
if (loaded3DSX.has_value()) {
|
||||
return &loaded3DSX.value();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether "addr" is aligned to a page (4096 byte) boundary
|
||||
static constexpr bool isAligned(u32 addr) {
|
||||
return (addr & pageMask) == 0;
|
||||
}
|
||||
|
||||
// Allocate "size" bytes of RAM starting from FCRAM index "paddr" (We pick it ourself if paddr == 0)
|
||||
// And map them to virtual address "vaddr" (We also pick it ourself if vaddr == 0).
|
||||
// If the "linear" flag is on, the paddr pages must be adjacent in FCRAM
|
||||
// This function is for interacting with the *user* portion of FCRAM mainly. For OS RAM, we use other internal functions below
|
||||
// r, w, x: Permissions for the allocated memory
|
||||
// adjustAddrs: If it's true paddr == 0 or vaddr == 0 tell the allocator to pick its own addresses. Used for eg svc ControlMemory
|
||||
// isMap: Shows whether this is a reserve operation, that allocates memory and maps it to the addr space, or if it's a map operation,
|
||||
// which just maps memory from paddr to vaddr without hassle. The latter is useful for shared memory mapping, the "map" ControlMemory, op, etc
|
||||
// Returns the vaddr the FCRAM was mapped to or nullopt if allocation failed
|
||||
std::optional<u32> allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r = true, bool w = true, bool x = true,
|
||||
bool adjustsAddrs = false, bool isMap = false);
|
||||
KernelMemoryTypes::MemoryInfo queryMemory(u32 vaddr);
|
||||
|
||||
// For internal use
|
||||
// Allocates a "size"-sized chunk of system FCRAM and returns the index of physical FCRAM used for the allocation
|
||||
// Used for allocating things like shared memory and the like
|
||||
u32 allocateSysMemory(u32 size);
|
||||
|
||||
// Map a shared memory block to virtual address vaddr with permissions "myPerms"
|
||||
// The kernel has a second permission parameter in MapMemoryBlock but not sure what's used for
|
||||
// TODO: Find out
|
||||
// Returns a pointer to the FCRAM block used for the memory if allocation succeeded
|
||||
u8* mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerms);
|
||||
|
||||
// Mirrors the page mapping for "size" bytes starting from sourceAddress, to "size" bytes in destAddress
|
||||
// All of the above must be page-aligned.
|
||||
void mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size);
|
||||
|
||||
// Backup of the game's CXI partition info, if any
|
||||
std::optional<NCCH> loadedCXI = std::nullopt;
|
||||
std::optional<HB3DSX> loaded3DSX = std::nullopt;
|
||||
// File handle for reading the loaded ncch
|
||||
IOFile CXIFile;
|
||||
|
||||
std::optional<u64> getProgramID();
|
||||
|
||||
u8* getDSPMem() { return dspRam; }
|
||||
u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; }
|
||||
u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; }
|
||||
u32 getUsedUserMem() { return usedUserMemory; }
|
||||
|
||||
void setVRAM(u8* pointer) { vram = pointer; }
|
||||
void setDSPMem(u8* pointer) { dspRam = pointer; }
|
||||
|
||||
bool allocateMainThreadStack(u32 size);
|
||||
Regions getConsoleRegion();
|
||||
void copySharedFont(u8* ptr);
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "mio/mio.hpp"
|
||||
|
||||
// Minimal RAII wrapper over memory mapped files
|
||||
|
||||
class MemoryMappedFile {
|
||||
std::filesystem::path filePath = ""; // path of our file
|
||||
mio::mmap_sink map; // mmap sink for our file
|
||||
|
||||
u8* pointer = nullptr; // Pointer to the contents of the memory mapped file
|
||||
bool opened = false;
|
||||
|
||||
public:
|
||||
bool exists() const { return opened; }
|
||||
u8* data() const { return pointer; }
|
||||
|
||||
std::error_code flush();
|
||||
MemoryMappedFile();
|
||||
MemoryMappedFile(const std::filesystem::path& path);
|
||||
|
||||
~MemoryMappedFile();
|
||||
// Returns true on success
|
||||
bool open(const std::filesystem::path& path);
|
||||
void close();
|
||||
|
||||
// TODO: For memory-mapped output files we'll need some more stuff such as a constructor that takes path/size/shouldCreate as parameters
|
||||
|
||||
u8& operator[](size_t index) { return pointer[index]; }
|
||||
const u8& operator[](size_t index) const { return pointer[index]; }
|
||||
|
||||
auto begin() { return map.begin(); }
|
||||
auto end() { return map.end(); }
|
||||
auto cbegin() { return map.cbegin(); }
|
||||
auto cend() { return map.cend(); }
|
||||
|
||||
mio::mmap_sink& getSink() { return map; }
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
#pragma once
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace Helpers {
|
||||
/// Used to make the compiler evaluate beeg loops at compile time for things like generating compile-time tables
|
||||
template <typename T, T Begin, class Func, T... Is>
|
||||
static constexpr void static_for_impl(Func&& f, std::integer_sequence<T, Is...>) {
|
||||
(f(std::integral_constant<T, Begin + Is>{}), ...);
|
||||
}
|
||||
|
||||
template <typename T, T Begin, T End, class Func>
|
||||
static constexpr void static_for(Func&& f) {
|
||||
static_for_impl<T, Begin>(std::forward<Func>(f), std::make_integer_sequence<T, End - Begin>{});
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDialog>
|
||||
#include <QWidget>
|
||||
|
||||
class AboutWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AboutWindow(QWidget* parent = nullptr);
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QAction>
|
||||
#include <QWidget>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "emulator.hpp"
|
||||
|
||||
class QListWidget;
|
||||
|
||||
class CheatsWindow final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatsWindow(Emulator* emu, const std::filesystem::path& path, QWidget* parent = nullptr);
|
||||
~CheatsWindow() = default;
|
||||
|
||||
private:
|
||||
void addEntry();
|
||||
void removeClicked();
|
||||
|
||||
QListWidget* cheatList;
|
||||
std::filesystem::path cheatPath;
|
||||
Emulator* emu;
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QPalette>
|
||||
#include <QWidget>
|
||||
#include <QtWidgets>
|
||||
|
||||
class ConfigWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
enum class Theme : int {
|
||||
System = 0,
|
||||
Light = 1,
|
||||
Dark = 2,
|
||||
GreetingsCat = 3,
|
||||
};
|
||||
|
||||
Theme currentTheme;
|
||||
QComboBox* themeSelect = nullptr;
|
||||
|
||||
void setTheme(Theme theme);
|
||||
|
||||
public:
|
||||
ConfigWindow(QWidget* parent = nullptr);
|
||||
~ConfigWindow();
|
||||
};
|
|
@ -1,123 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMenuBar>
|
||||
#include <QtWidgets>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "emulator.hpp"
|
||||
#include "panda_qt/about_window.hpp"
|
||||
#include "panda_qt/config_window.hpp"
|
||||
#include "panda_qt/cheats_window.hpp"
|
||||
#include "panda_qt/screen.hpp"
|
||||
#include "panda_qt/text_editor.hpp"
|
||||
#include "services/hid.hpp"
|
||||
|
||||
struct CheatMessage {
|
||||
u32 handle;
|
||||
std::vector<uint8_t> cheat;
|
||||
std::function<void(u32)> callback;
|
||||
};
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
// Types of messages we might send from the GUI thread to the emulator thread
|
||||
enum class MessageType {
|
||||
LoadROM,
|
||||
Reset,
|
||||
Pause,
|
||||
Resume,
|
||||
TogglePause,
|
||||
DumpRomFS,
|
||||
PressKey,
|
||||
ReleaseKey,
|
||||
SetCirclePadX,
|
||||
SetCirclePadY,
|
||||
LoadLuaScript,
|
||||
EditCheat,
|
||||
PressTouchscreen,
|
||||
ReleaseTouchscreen,
|
||||
};
|
||||
|
||||
// Tagged union representing our message queue messages
|
||||
struct EmulatorMessage {
|
||||
MessageType type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
std::filesystem::path* p;
|
||||
} path;
|
||||
|
||||
struct {
|
||||
u32 key;
|
||||
} key;
|
||||
|
||||
struct {
|
||||
s16 value;
|
||||
} circlepad;
|
||||
|
||||
struct {
|
||||
std::string* str;
|
||||
} string;
|
||||
|
||||
struct {
|
||||
CheatMessage* c;
|
||||
} cheat;
|
||||
|
||||
struct {
|
||||
u16 x;
|
||||
u16 y;
|
||||
} touchscreen;
|
||||
};
|
||||
};
|
||||
|
||||
// This would normally be an std::unique_ptr but it's shared between threads so definitely not
|
||||
Emulator* emu = nullptr;
|
||||
std::thread emuThread;
|
||||
|
||||
std::atomic<bool> appRunning = true; // Is the application itself running?
|
||||
// Used for synchronizing messages between the emulator and UI
|
||||
std::mutex messageQueueMutex;
|
||||
std::vector<EmulatorMessage> messageQueue;
|
||||
|
||||
ScreenWidget screen;
|
||||
AboutWindow* aboutWindow;
|
||||
ConfigWindow* configWindow;
|
||||
CheatsWindow* cheatsEditor;
|
||||
TextEditorWindow* luaEditor;
|
||||
QMenuBar* menuBar = nullptr;
|
||||
|
||||
void swapEmuBuffer();
|
||||
void emuThreadMainLoop();
|
||||
void selectLuaFile();
|
||||
void selectROM();
|
||||
void dumpRomFS();
|
||||
void openLuaEditor();
|
||||
void openCheatsEditor();
|
||||
void showAboutMenu();
|
||||
void sendMessage(const EmulatorMessage& message);
|
||||
void dispatchMessage(const EmulatorMessage& message);
|
||||
|
||||
// Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer
|
||||
bool usingGL = false;
|
||||
bool usingVk = false;
|
||||
|
||||
public:
|
||||
MainWindow(QApplication* app, QWidget* parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
void loadLuaScript(const std::string& code);
|
||||
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
|
||||
};
|