Compare commits

..

56 Commits

Author SHA1 Message Date
lizzie b2be3275da ok windows 2026-09-03 01:54:18 +00:00
lizzie d4b2fbd7e0 ok no sin len 2026-09-03 01:54:18 +00:00
lizzie 5a0319d6a5 fix bsd macro clash 2026-09-03 01:54:18 +00:00
lizzie 4bb9c903b1 fix horrid lag on hb appstore 2026-09-03 01:54:18 +00:00
lizzie f350e4c083 Trigger Build 2026-09-03 01:54:18 +00:00
lizzie 02c9adf491 Trigger Build 2026-09-03 01:54:18 +00:00
lizzie b580311623 no 2026-09-03 01:54:18 +00:00
lizzie 7e39b8d0f0 me when evil android 2026-09-03 01:54:18 +00:00
lizzie aaa3fe8a7f fixups 2026-09-03 01:54:18 +00:00
lizzie d6b5e48c9c fix windows wsa E_DESTADDRREQ 2026-09-03 01:54:18 +00:00
lizzie 9db65da794 fucking shit 2026-09-03 01:54:18 +00:00
lizzie 3163b987ea windows prot=0 2026-09-03 01:54:18 +00:00
lizzie 5b633163c7 fix pings? 2026-09-03 01:54:18 +00:00
lizzie 122700da62 fix winbloat 2026-09-03 01:54:18 +00:00
lizzie 5b986eb59e fractional time -W 2026-09-03 01:54:18 +00:00
lizzie a838ff6b70 ffs windows 2026-09-03 01:54:18 +00:00
lizzie d37b66dfbb evil noperm 2026-09-03 01:54:18 +00:00
lizzie 7a8a0665ae fuck errrno2 2026-09-03 01:54:18 +00:00
lizzie d623fe8fe4 acurater errno 2026-09-03 01:54:18 +00:00
lizzie 440f4f51f2 ALL THE ERROR CODES 2026-09-03 01:54:18 +00:00
lizzie 85b01a26d1 fixup errno 2026-09-03 01:54:18 +00:00
lizzie dd030de217 min of 1sec 2026-09-03 01:54:18 +00:00
lizzie 7d7d03f707 account for timeout 2026-09-03 01:54:18 +00:00
lizzie 7176a7189c fix tcp on ssbu mod 2026-09-03 01:54:18 +00:00
lizzie 95426a6bcf fallback only if it failed 2026-09-03 01:54:18 +00:00
lizzie 9a7a4fdc1b implode windows 2026-09-03 01:54:18 +00:00
lizzie cc8e479b9a better 2026-09-03 01:54:18 +00:00
lizzie c8a2ef9cbe evil ping 2026-09-03 01:54:18 +00:00
lizzie 17a3e35342 don't murder me for this change 2026-09-03 01:54:18 +00:00
lizzie 5ceb287e95 need the extra threads... 2026-09-03 01:54:18 +00:00
lizzie af7febe7a5 more gymnastics to give fake results? 2026-09-03 01:54:18 +00:00
lizzie e6f5678019 first fake icmp impl 2026-09-03 01:54:18 +00:00
lizzie e3882da0e4 fix ldn sockets being regarded as normal socks 2026-09-03 01:54:18 +00:00
lizzie 7bd4cf8d36 fx2 2026-09-03 01:54:18 +00:00
lizzie 213307545d fix??? 2026-09-03 01:54:18 +00:00
lizzie d77df48b73 fix sockets 2026-09-03 01:54:18 +00:00
lizzie b45b4c13f2 struct pollfd 2026-09-03 01:54:18 +00:00
lizzie d511b11c50 fixup tests 2026-09-03 01:54:18 +00:00
lizzie aec09e6a21 fix windows screaming 2026-09-03 01:54:18 +00:00
lizzie 670095c3e8 musl scared of defines ooo 2026-09-03 01:54:18 +00:00
lizzie f3e76e8d84 airplane mode takes priority 2026-09-03 01:54:18 +00:00
lizzie 48fefaf4d9 uh 2026-09-03 01:54:18 +00:00
lizzie 615d77b356 add tcp options 2026-09-03 01:54:18 +00:00
lizzie 06593e9889 nuke extra threads, extra error support; use span instead of vector for poll fds 2026-09-03 01:54:18 +00:00
lizzie 0673767a41 we love when cURL has bugs? 2026-09-03 01:54:18 +00:00
lizzie b332007f89 add extra polling types 2026-09-03 01:54:18 +00:00
lizzie ad3929f11d various fixes to sockopt 2026-09-03 01:54:18 +00:00
lizzie 32a8445767 allow configure socket level 2026-09-03 01:54:18 +00:00
lizzie 88f867607e coalesce the various sockopt 2026-09-03 01:54:18 +00:00
lizzie b3e09d7108 honour the provided MSG flags 2026-09-03 01:54:18 +00:00
lizzie aa9d93b509 extra fixups 2026-09-03 01:54:18 +00:00
lizzie 48f5f71858 fixup stuffs for windows 2026-09-03 01:54:18 +00:00
lizzie 7e225f4520 fix messages native flags 2026-09-03 01:54:18 +00:00
lizzie 6bc5f936c6 properly handle writebuffer and dont write OOB 2026-09-03 01:54:18 +00:00
lizzie 8ed685ac8c fixes for non BSD 2026-09-03 01:54:18 +00:00
lizzie facf8fdf57 [net] refactor to remove uneeded abstraction layer
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-03 01:54:18 +00:00
71 changed files with 1910 additions and 5281 deletions
-2
View File
@@ -84,8 +84,6 @@ option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
# Lossless Scaling frame generation. Only Android. # Lossless Scaling frame generation. Only Android.
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF) cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
option(ENABLE_RESHADE "Enable ReShade FX post-processing effects" ON)
# non-linux bundled qt are static # non-linux bundled qt are static
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX)) if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
set(YUZU_STATIC_BUILD ON) set(YUZU_STATIC_BUILD ON)
-5
View File
@@ -246,11 +246,6 @@
"repo": "stachenov/quazip", "repo": "stachenov/quazip",
"version": "2e95c9001b" "version": "2e95c9001b"
}, },
"reshade": {
"hash": "a1bd3fcf135fb6d018c1831ae45a8942d9777d0418b55e1189921e2ab775d1b53b0a9808924e09ef1c3e68ea11d69b3bcbebc7f4b59de14f6deec2dc24531d5e",
"repo": "crosire/reshade",
"version": "v6.7.3"
},
"sdl3": { "sdl3": {
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3", "hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
"min_version": "3.2.10", "min_version": "3.2.10",
-62
View File
@@ -1,62 +0,0 @@
texture EdenBackBufferTex : COLOR;
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
uniform float Saturation <
ui_type = "slider";
ui_label = "Saturation";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
> = 1.0;
uniform float Brightness <
ui_type = "slider";
ui_label = "Brightness";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
> = 1.0;
uniform float Contrast <
ui_type = "slider";
ui_label = "Contrast";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
> = 1.0;
uniform float Gamma <
ui_type = "slider";
ui_label = "Gamma";
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
> = 1.0;
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
{
uv = float2(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float3 rgb = tex2D(EdenBackBuffer, uv).rgb;
float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722));
rgb = lerp(float3(luma, luma, luma), rgb, Saturation);
rgb *= Brightness;
rgb = (rgb - 0.5) * Contrast + 0.5;
rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.0001));
return float4(saturate(rgb), 1.0);
}
technique EdenColorGrade
{
pass
{
VertexShader = VS_Eden;
PixelShader = PS_ColorGrade;
}
}
-45
View File
@@ -1,45 +0,0 @@
texture EdenBackBufferTex : COLOR;
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
uniform float Amount <
ui_type = "slider";
ui_label = "Amount";
ui_min = 0.0; ui_max = 3.0; ui_step = 0.01;
> = 0.6;
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
{
uv = float2(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
float3 centre = tex2D(EdenBackBuffer, uv).rgb;
float3 blur = tex2D(EdenBackBuffer, uv + float2(-texel.x, 0.0)).rgb;
blur += tex2D(EdenBackBuffer, uv + float2(texel.x, 0.0)).rgb;
blur += tex2D(EdenBackBuffer, uv + float2(0.0, -texel.y)).rgb;
blur += tex2D(EdenBackBuffer, uv + float2(0.0, texel.y)).rgb;
blur *= 0.25;
return float4(centre + (centre - blur) * Amount, 1.0);
}
technique EdenSharpen
{
pass
{
VertexShader = VS_Eden;
PixelShader = PS_Sharpen;
}
}
-50
View File
@@ -1,50 +0,0 @@
texture EdenBackBufferTex : COLOR;
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
uniform float Strength <
ui_type = "slider";
ui_label = "Strength";
ui_tooltip = "How dark the corners become.";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
> = 0.6;
uniform float Aspect <
ui_type = "slider";
ui_label = "Aspect";
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
> = 1.0;
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
{
uv = float2(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 diff = uv - 0.5;
diff.x *= Aspect;
diff.y /= max(Aspect, 0.0001);
float falloff = 1.0 - min(1.0, Strength * dot(diff, diff) * 2.0);
float3 rgb = tex2D(EdenBackBuffer, uv).rgb;
return float4(rgb * falloff, 1.0);
}
technique EdenVignette
{
pass
{
VertexShader = VS_Eden;
PixelShader = PS_Vignette;
}
}
-33
View File
@@ -195,39 +195,6 @@ else()
endif() endif()
endif() endif()
# reshadefx
if (ENABLE_RESHADE)
AddJsonPackage(NAME reshade DOWNLOAD_ONLY)
set(RESHADEFX_SHIM_DIR ${CMAKE_CURRENT_BINARY_DIR}/reshadefx_shim)
file(WRITE ${RESHADEFX_SHIM_DIR}/spirv.hpp "#include <spirv/unified1/spirv.hpp>\n")
file(WRITE ${RESHADEFX_SHIM_DIR}/GLSL.std.450.h "#include <spirv/unified1/GLSL.std.450.h>\n")
add_library(reshadefx STATIC
${reshade_SOURCE_DIR}/source/effect_codegen_spirv.cpp
${reshade_SOURCE_DIR}/source/effect_expression.cpp
${reshade_SOURCE_DIR}/source/effect_lexer.cpp
${reshade_SOURCE_DIR}/source/effect_parser_exp.cpp
${reshade_SOURCE_DIR}/source/effect_parser_stmt.cpp
${reshade_SOURCE_DIR}/source/effect_preprocessor.cpp
${reshade_SOURCE_DIR}/source/effect_symbol_table.cpp
)
target_include_directories(reshadefx SYSTEM PUBLIC ${reshade_SOURCE_DIR}/source)
target_include_directories(reshadefx PRIVATE ${RESHADEFX_SHIM_DIR})
target_link_libraries(reshadefx PUBLIC SPIRV-Headers::SPIRV-Headers)
if (NOT MSVC)
target_compile_options(reshadefx PRIVATE -w)
else()
target_compile_options(reshadefx PRIVATE /w)
endif()
if (NOT TARGET reshadefx::reshadefx)
add_library(reshadefx::reshadefx ALIAS reshadefx)
endif()
endif()
# Catch2 # Catch2
if (YUZU_TESTS OR DYNARMIC_TESTS) if (YUZU_TESTS OR DYNARMIC_TESTS)
AddJsonPackage(catch2) AddJsonPackage(catch2)
@@ -1,103 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativePostProcessing
import kotlin.math.roundToInt
abstract class FxUniformSetting(
protected val index: Int,
protected val uniform: NativePostProcessing.Uniform,
protected val component: Int
) : AbstractSetting {
override val key: String
get() = "fx_${index}_${uniform.name}_$component"
override val isRuntimeModifiable: Boolean
get() = true
override val pairedSettingKey: String
get() = ""
override val isSwitchable: Boolean
get() = false
override val isSaveable: Boolean
get() = true
override var global: Boolean
get() = true
set(_) {}
protected fun currentValue(): Float {
if (NativePostProcessing.hasValue(index, uniform.name)) {
return NativePostProcessing.getValue(index, uniform.name, component)
}
return uniform.defaultAt(component)
}
protected fun commit(value: Float) {
NativePostProcessing.setValue(index, uniform.name, component, value)
NativePostProcessing.store()
}
override fun reset() = commit(uniform.defaultAt(component))
}
class FxUniformSliderSetting(
index: Int,
uniform: NativePostProcessing.Uniform,
component: Int
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
override val defaultValue: Any
get() = ((uniform.defaultAt(component) - uniform.min) / uniform.step).roundToInt()
override fun getInt(needsGlobal: Boolean): Int =
((currentValue() - uniform.min) / uniform.step).roundToInt()
override fun setInt(value: Int) = commit(uniform.min + value * uniform.step)
override fun getValueAsString(needsGlobal: Boolean): String {
if (uniform.kind == NativePostProcessing.KIND_FLOAT) {
return String.format("%.3f", currentValue())
}
return currentValue().roundToInt().toString()
}
}
class FxUniformChoiceSetting(
index: Int,
uniform: NativePostProcessing.Uniform,
component: Int
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
override val defaultValue: Any
get() = uniform.defaultAt(component).roundToInt()
override fun getInt(needsGlobal: Boolean): Int = currentValue().roundToInt()
override fun setInt(value: Int) = commit(value.toFloat())
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
}
class FxUniformBooleanSetting(
index: Int,
uniform: NativePostProcessing.Uniform,
component: Int
) : FxUniformSetting(index, uniform, component), AbstractBooleanSetting {
override val defaultValue: Any
get() = uniform.defaultAt(component) != 0f
override fun getBoolean(needsGlobal: Boolean): Boolean = currentValue() != 0f
override fun setBoolean(value: Boolean) {
if (value) {
commit(1f)
return
}
commit(0f)
}
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean().toString()
}
@@ -12,7 +12,6 @@ object Settings {
SECTION_SYSTEM(R.string.preferences_system), SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics), SECTION_RENDERER(R.string.preferences_graphics),
SECTION_FRAME_GEN(R.string.frame_gen), SECTION_FRAME_GEN(R.string.frame_gen),
SECTION_POST_PROCESSING(R.string.post_processing),
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options), SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
SECTION_INPUT_OVERLAY(R.string.input_overlay_options), SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
SECTION_SOC_OVERLAY(R.string.soc_overlay_options), SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
@@ -13,7 +13,6 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
DEVICE_NAME("device_name"), DEVICE_NAME("device_name"),
LOG_FILTER("log_filter"), LOG_FILTER("log_filter"),
PROGRAM_ARGS("program_args"), PROGRAM_ARGS("program_args"),
POST_SHADER_CHAIN("post_shader_chain"),
WEB_TOKEN("eden_token"), WEB_TOKEN("eden_token"),
WEB_USERNAME("eden_username") WEB_USERNAME("eden_username")
@@ -18,9 +18,6 @@ import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting
@@ -33,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NativePostProcessing
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FullscreenHelper import org.yuzu.yuzu_emu.utils.FullscreenHelper
import androidx.core.content.edit import androidx.core.content.edit
@@ -167,7 +163,6 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl) MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl) MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl) MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl) MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
@@ -195,219 +190,6 @@ class SettingsFragmentPresenter(
} }
} }
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
val usable = NativePostProcessing.catalog().filter { it.valid }
sl.apply {
add(
RunnableSetting(
titleId = R.string.post_processing_reload,
descriptionId = R.string.post_processing_reload_description,
isRunnable = true
) {
NativePostProcessing.reload()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
if (usable.isEmpty()) {
add(
RunnableSetting(
titleId = R.string.post_processing_empty,
descriptionString = NativePostProcessing.getShaderDirectory(),
isRunnable = false
) {}
)
return@apply
}
val labels = mutableListOf<String>()
val files = mutableListOf<String>()
val techniques = mutableListOf<String>()
for (effect in usable) {
for (technique in effect.techniques) {
if (effect.techniques.size == 1) {
labels.add(effect.name)
} else {
labels.add(effect.name + " \u00b7 " + technique)
}
files.add(effect.file)
techniques.add(technique)
}
}
val chain = NativePostProcessing.chain()
for (index in chain.indices) {
val entry = chain[index]
val effect = usable.firstOrNull { it.file == entry.file }
var header = entry.file
if (effect != null) {
header = effect.name
}
add(HeaderSetting(titleString = header))
add(
IntSingleChoiceSetting(
buildSlotSelector(index, entry, files, techniques),
titleId = R.string.post_processing_effect,
choices = labels.toTypedArray(),
values = labels.indices.toList().toTypedArray()
)
)
if (effect != null) {
for (uniform in effect.uniforms) {
addUniform(this, index, uniform)
}
}
if (index > 0) {
add(
RunnableSetting(
titleId = R.string.post_processing_move_up,
isRunnable = true
) {
NativePostProcessing.move(index, -1)
NativePostProcessing.store()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
if (index < chain.size - 1) {
add(
RunnableSetting(
titleId = R.string.post_processing_move_down,
isRunnable = true
) {
NativePostProcessing.move(index, 1)
NativePostProcessing.store()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
add(
RunnableSetting(
titleId = R.string.post_processing_reset,
isRunnable = true
) {
NativePostProcessing.resetValues(index)
NativePostProcessing.store()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
add(
RunnableSetting(
titleId = R.string.post_processing_remove,
isRunnable = true
) {
NativePostProcessing.remove(index)
NativePostProcessing.store()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
add(
RunnableSetting(
titleId = R.string.post_processing_add,
isRunnable = true
) {
NativePostProcessing.append(files[0], techniques[0])
NativePostProcessing.store()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
}
private fun buildSlotSelector(
index: Int,
entry: NativePostProcessing.ChainEntry,
files: List<String>,
techniques: List<String>
): AbstractIntSetting = object : AbstractIntSetting {
override val key = "fx_slot_$index"
override fun getInt(needsGlobal: Boolean): Int {
for (i in files.indices) {
if (files[i] == entry.file && techniques[i] == entry.technique) {
return i
}
}
return -1
}
override fun setInt(value: Int) {
NativePostProcessing.replace(index, files[value], techniques[value])
NativePostProcessing.store()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
override val defaultValue = 0
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
override fun reset() {}
override val isRuntimeModifiable = true
override val pairedSettingKey = ""
override val isSwitchable = false
override val isSaveable = true
override var global: Boolean
get() = true
set(_) {}
}
private fun addUniform(
sl: ArrayList<SettingsItem>,
index: Int,
uniform: NativePostProcessing.Uniform
) {
if (uniform.uiType == NativePostProcessing.UI_CHECKBOX ||
uniform.kind == NativePostProcessing.KIND_BOOL
) {
sl.add(
SwitchSetting(
FxUniformBooleanSetting(index, uniform, 0),
titleString = uniform.label,
descriptionString = uniform.tooltip
)
)
return
}
if (uniform.items.isNotEmpty() &&
(uniform.uiType == NativePostProcessing.UI_COMBO ||
uniform.uiType == NativePostProcessing.UI_RADIO)
) {
sl.add(
IntSingleChoiceSetting(
FxUniformChoiceSetting(index, uniform, 0),
titleString = uniform.label,
descriptionString = uniform.tooltip,
choices = uniform.items.toTypedArray(),
values = uniform.items.indices.toList().toTypedArray()
)
)
return
}
for (component in 0 until uniform.components) {
var title = uniform.label
if (uniform.components > 1) {
title = uniform.label + " [" + component + "]"
}
sl.add(
SliderSetting(
FxUniformSliderSetting(index, uniform, component),
titleString = title,
descriptionString = uniform.tooltip,
min = 0,
max = uniform.steps
)
)
}
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) { private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
add( add(
@@ -383,20 +383,6 @@ class GamePropertiesFragment : Fragment() {
} }
) )
) )
add(
SubmenuProperty(
R.string.post_processing,
R.string.post_processing_per_game_description,
R.drawable.ic_post_processing,
action = {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
args.game,
Settings.MenuTag.SECTION_POST_PROCESSING
)
binding.root.findNavController().navigate(action)
}
)
)
if (GpuDriverHelper.isAdrenoGpu()) { if (GpuDriverHelper.isAdrenoGpu()) {
add( add(
@@ -171,20 +171,6 @@ class HomeSettingsFragment : Fragment() {
) )
) )
} }
add(
HomeSetting(
R.string.post_processing,
R.string.post_processing_description,
R.drawable.ic_post_processing,
{
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
Settings.MenuTag.SECTION_POST_PROCESSING
)
binding.root.findNavController().navigate(action)
}
)
)
add( add(
HomeSetting( HomeSetting(
R.string.lossless_scaling, R.string.lossless_scaling,
@@ -1,175 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import org.json.JSONArray
import org.json.JSONObject
object NativePostProcessing {
const val KIND_BOOL = 0
const val KIND_INT = 1
const val KIND_FLOAT = 2
const val UI_HIDDEN = 0
const val UI_SLIDER = 1
const val UI_DRAG = 2
const val UI_COMBO = 3
const val UI_RADIO = 4
const val UI_CHECKBOX = 5
const val UI_COLOR = 6
const val UI_INPUT_BOX = 7
external fun getCatalogJson(): String
external fun getChainJson(): String
external fun append(file: String, technique: String)
external fun replace(index: Int, file: String, technique: String)
external fun remove(index: Int)
external fun move(index: Int, delta: Int)
external fun resetValues(index: Int)
external fun getValue(index: Int, uniform: String, component: Int): Float
external fun hasValue(index: Int, uniform: String): Boolean
external fun setValue(index: Int, uniform: String, component: Int, value: Float)
external fun store()
external fun reload()
external fun getShaderDirectory(): String
data class Uniform(
val name: String,
val label: String,
val tooltip: String,
val category: String,
val kind: Int,
val uiType: Int,
val components: Int,
val min: Float,
val max: Float,
val step: Float,
val items: List<String>,
val defaults: List<Float>
) {
val steps: Int
get() {
val span = max - min
if (step <= 0f) {
return 1
}
val count = Math.round(span / step)
if (count < 1) {
return 1
}
return count
}
fun defaultAt(component: Int): Float {
if (component < defaults.size) {
return defaults[component]
}
return 0f
}
}
data class Effect(
val file: String,
val name: String,
val error: String,
val techniques: List<String>,
val uniforms: List<Uniform>
) {
val valid: Boolean
get() = error.isEmpty() && techniques.isNotEmpty()
}
data class ChainEntry(val file: String, val technique: String)
fun catalog(): List<Effect> {
val out = mutableListOf<Effect>()
val array = JSONArray(getCatalogJson())
for (i in 0 until array.length()) {
val obj = array.getJSONObject(i)
out.add(
Effect(
file = obj.optString("file"),
name = obj.optString("name"),
error = obj.optString("error"),
techniques = obj.optJSONArray("techniques").toStringList(),
uniforms = obj.optJSONArray("uniforms").toUniformList()
)
)
}
return out
}
fun chain(): List<ChainEntry> {
val out = mutableListOf<ChainEntry>()
val array = JSONArray(getChainJson())
for (i in 0 until array.length()) {
val obj = array.getJSONObject(i)
out.add(ChainEntry(obj.optString("file"), obj.optString("technique")))
}
return out
}
fun findEffect(file: String): Effect? = catalog().firstOrNull { it.file == file }
private fun JSONArray?.toStringList(): List<String> {
if (this == null) {
return emptyList()
}
val out = mutableListOf<String>()
for (i in 0 until length()) {
out.add(optString(i))
}
return out
}
private fun JSONArray?.toFloatList(): List<Float> {
if (this == null) {
return emptyList()
}
val out = mutableListOf<Float>()
for (i in 0 until length()) {
out.add(optDouble(i, 0.0).toFloat())
}
return out
}
private fun JSONArray?.toUniformList(): List<Uniform> {
if (this == null) {
return emptyList()
}
val out = mutableListOf<Uniform>()
for (i in 0 until length()) {
val obj: JSONObject = optJSONObject(i) ?: continue
out.add(
Uniform(
name = obj.optString("name"),
label = obj.optString("label"),
tooltip = obj.optString("tooltip"),
category = obj.optString("category"),
kind = obj.optInt("kind", KIND_FLOAT),
uiType = obj.optInt("uiType", UI_HIDDEN),
components = obj.optInt("components", 1),
min = obj.optDouble("min", 0.0).toFloat(),
max = obj.optDouble("max", 1.0).toFloat(),
step = obj.optDouble("step", 0.01).toFloat(),
items = obj.optJSONArray("items").toStringList(),
defaults = obj.optJSONArray("defaults").toFloatList()
)
)
}
return out
}
}
@@ -17,7 +17,6 @@ add_library(yuzu-android SHARED
android_config.cpp android_config.cpp
android_config.h android_config.h
native_input.cpp native_input.cpp
native_post_processing.cpp
) )
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
@@ -1,218 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <string>
#include <jni.h>
#include <nlohmann/json.hpp>
#include "common/android/android_common.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
#endif
namespace {
#ifdef HAS_RESHADE
nlohmann::json SerializeUniform(const VideoCore::FxUniformDesc& uniform) {
nlohmann::json out;
out["name"] = uniform.name;
out["label"] = uniform.label;
out["tooltip"] = uniform.tooltip;
out["category"] = uniform.category;
out["kind"] = static_cast<int>(uniform.kind);
out["uiType"] = static_cast<int>(uniform.ui_type);
out["components"] = uniform.components;
out["min"] = uniform.ui_min;
out["max"] = uniform.ui_max;
out["step"] = uniform.ui_step;
out["items"] = uniform.items;
nlohmann::json defaults = nlohmann::json::array();
for (u32 i = 0; i < uniform.components; ++i) {
defaults.push_back(uniform.default_value[i]);
}
out["defaults"] = defaults;
return out;
}
std::array<f32, 4> DefaultValueOf(size_t index, const std::string& uniform) {
const auto entries = VideoCore::FxChain::Instance().Entries();
if (index >= entries.size()) {
return {};
}
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entries[index].file);
if (effect == nullptr) {
return {};
}
const VideoCore::FxUniformDesc* desc = VideoCore::FindFxUniform(*effect, uniform);
if (desc == nullptr) {
return {};
}
return desc->default_value;
}
#endif
} // Anonymous namespace
extern "C" {
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getCatalogJson(JNIEnv* env,
jobject obj) {
nlohmann::json out = nlohmann::json::array();
#ifdef HAS_RESHADE
for (const auto& effect : VideoCore::GetFxCatalog()) {
nlohmann::json entry;
entry["file"] = effect.file;
entry["name"] = effect.name;
entry["error"] = effect.error;
entry["techniques"] = effect.techniques;
nlohmann::json uniforms = nlohmann::json::array();
for (const auto& uniform : effect.uniforms) {
uniforms.push_back(SerializeUniform(uniform));
}
entry["uniforms"] = uniforms;
out.push_back(entry);
}
#endif
return Common::Android::ToJString(env, out.dump());
}
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getChainJson(JNIEnv* env, jobject obj) {
nlohmann::json out = nlohmann::json::array();
#ifdef HAS_RESHADE
for (const auto& entry : VideoCore::FxChain::Instance().Entries()) {
nlohmann::json item;
item["file"] = entry.file;
item["technique"] = entry.technique;
nlohmann::json values = nlohmann::json::object();
for (const auto& [name, value] : entry.values) {
values[name] = {value[0], value[1], value[2], value[3]};
}
item["values"] = values;
out.push_back(item);
}
#endif
return Common::Android::ToJString(env, out.dump());
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_append(JNIEnv* env, jobject obj,
jstring jfile, jstring jtechnique) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().Append(Common::Android::GetJString(env, jfile),
Common::Android::GetJString(env, jtechnique));
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_replace(JNIEnv* env, jobject obj,
jint index, jstring jfile,
jstring jtechnique) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
Common::Android::GetJString(env, jfile),
Common::Android::GetJString(env, jtechnique));
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_remove(JNIEnv* env, jobject obj,
jint index) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_move(JNIEnv* env, jobject obj, jint index,
jint delta) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), delta);
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_resetValues(JNIEnv* env, jobject obj,
jint index) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
#endif
}
jfloat Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getValue(JNIEnv* env, jobject obj,
jint index, jstring juniform,
jint component) {
#ifdef HAS_RESHADE
if (component < 0 || component >= 4) {
return 0.0f;
}
const auto value = VideoCore::FxChain::Instance().GetValue(
static_cast<size_t>(index), Common::Android::GetJString(env, juniform));
return value[static_cast<size_t>(component)];
#else
return 0.0f;
#endif
}
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_hasValue(JNIEnv* env, jobject obj,
jint index,
jstring juniform) {
#ifdef HAS_RESHADE
return static_cast<jboolean>(VideoCore::FxChain::Instance().HasValue(
static_cast<size_t>(index), Common::Android::GetJString(env, juniform)));
#else
return static_cast<jboolean>(false);
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setValue(JNIEnv* env, jobject obj,
jint index, jstring juniform,
jint component, jfloat value) {
#ifdef HAS_RESHADE
if (component < 0 || component >= 4) {
return;
}
const std::string uniform = Common::Android::GetJString(env, juniform);
auto& chain = VideoCore::FxChain::Instance();
const auto slot = static_cast<size_t>(index);
auto current = chain.GetValue(slot, uniform);
if (!chain.HasValue(slot, uniform)) {
current = DefaultValueOf(slot, uniform);
}
current[static_cast<size_t>(component)] = value;
chain.SetValue(slot, uniform, current);
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().StoreToSettings();
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
#endif
}
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env,
jobject obj) {
#ifdef HAS_RESHADE
return Common::Android::ToJString(env, VideoCore::GetFxRootDirectory().string());
#else
return Common::Android::ToJString(env, "");
#endif
}
} // extern "C"
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:fillType="evenOdd"
android:pathData="M8.5,3 L19.4,3 Q21,3 21,4.6 L21,16.5 L19.4,16.5 L19.4,4.6 L8.5,4.6 Z M5,7 L15,7 Q17,7 17,9 L17,19 Q17,21 15,21 L5,21 Q3,21 3,19 L3,9 Q3,7 5,7 Z M5.2,8.6 L14.8,8.6 Q15.4,8.6 15.4,9.2 L15.4,18.8 Q15.4,19.4 14.8,19.4 L5.2,19.4 Q4.6,19.4 4.6,18.8 L4.6,9.2 Q4.6,8.6 5.2,8.6 Z M10,10.9 A3.1,3.1 0 0 1 10,17.1 Z"/>
</vector>
@@ -298,18 +298,6 @@
<string name="gpu_driver_fetcher">GPU driver fetcher</string> <string name="gpu_driver_fetcher">GPU driver fetcher</string>
<string name="gpu_driver_manager">GPU driver manager</string> <string name="gpu_driver_manager">GPU driver manager</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="post_processing">Post-Processing Effects</string>
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
<string name="post_processing_effect">Effect</string>
<string name="post_processing_add">Add effect</string>
<string name="post_processing_remove">Remove</string>
<string name="post_processing_move_up">Move up</string>
<string name="post_processing_move_down">Move down</string>
<string name="post_processing_reset">Reset to defaults</string>
<string name="post_processing_reload">Reload from disk</string>
<string name="post_processing_reload_description">Rescan the shader folder for .fx files</string>
<string name="post_processing_empty">No effects found. Place .fx files in this folder:</string>
<string name="frame_gen">Frame generation</string> <string name="frame_gen">Frame generation</string>
<string name="frame_gen_per_game_description">Configure frame generation for this game</string> <string name="frame_gen_per_game_description">Configure frame generation for this game</string>
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string> <string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
-1
View File
@@ -108,7 +108,6 @@ add_library(
settings_input.h settings_input.h
settings_setting.h settings_setting.h
slot_vector.h slot_vector.h
socket_types.h
spin_lock.h spin_lock.h
stb.cpp stb.cpp
stb.h stb.h
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
@@ -11,7 +11,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "web_service/web_result.h" #include "web_service/web_result.h"
namespace AnnounceMultiplayerRoom { namespace AnnounceMultiplayerRoom {
-1
View File
@@ -23,7 +23,6 @@
#define LOSSLESS_DIR "lossless" #define LOSSLESS_DIR "lossless"
#define NAND_DIR "nand" #define NAND_DIR "nand"
#define PLAY_TIME_DIR "play_time" #define PLAY_TIME_DIR "play_time"
#define POST_SHADER_DIR "post_shaders"
#define SCREENSHOTS_DIR "screenshots" #define SCREENSHOTS_DIR "screenshots"
#define SDMC_DIR "sdmc" #define SDMC_DIR "sdmc"
#define SHADER_DIR "shader" #define SHADER_DIR "shader"
-1
View File
@@ -160,7 +160,6 @@ public:
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR); GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR); GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR); GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR); GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR); GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR); GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
-1
View File
@@ -26,7 +26,6 @@ enum class EdenPath {
LosslessDir, // Where the user-supplied Lossless Scaling library is stored. LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
NANDDir, // Where the emulated NAND is stored. NANDDir, // Where the emulated NAND is stored.
PlayTimeDir, // Where play time data is stored. PlayTimeDir, // Where play time data is stored.
PostShaderDir, // Where user post-processing shaders are stored.
SaveDir, // Where save data is stored. SaveDir, // Where save data is stored.
ScreenshotsDir, // Where yuzu screenshots are stored. ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored. SDMCDir, // Where the emulated SDMC is stored.
-8
View File
@@ -388,14 +388,6 @@ struct Values {
true, true,
true}; true};
SwitchableSetting<std::string> post_shader_chain{linkage,
std::string(),
"post_shader_chain",
Category::Renderer,
Specialization::Default,
true,
true};
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer, SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
Specialization::Default, true, false}; Specialization::Default, true, false};
-178
View File
@@ -1,178 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <string>
#include "common/common_types.h"
namespace Network {
/// Address families
enum class Domain : u8 {
Unspecified, ///< Represents 0, used in getaddrinfo hints
INET, ///< Address family for IPv4
};
/// Socket types
enum class Type {
Unspecified, ///< Represents 0, used in getaddrinfo hints
STREAM,
DGRAM,
RAW,
SEQPACKET,
};
/// Protocol values for sockets
enum class Protocol : u8 {
Unspecified, ///< Represents 0, usable in various places
IP,
ICMP,
TCP,
UDP,
IPV6,
RAW,
IGMP,
GGP,
IPV4,
ST,
EGP,
PIGP,
RCCMON,
NVPII,
PUP,
ARGUS,
EMCON,
XNET,
CHAOS,
MUX,
MEAS,
HMP,
PRM,
IDP,
TRUNK1,
TRUNK2,
LEAF1,
LEAF2,
RDP,
IRTP,
TP,
BLT,
NSP,
INP,
DCCP,
//TODO: 3PC,
IDPR,
XTP,
DDP,
CMTP,
TPXX,
IL,
SDRP,
ROUTING,
FRAGMENT,
IDRP,
RSVP,
GRE,
MHRP,
BHA,
ESP,
AH,
INLSP,
SWIPE,
NHRP,
MOBILE,
TLSP,
SKIP,
ICMPV6,
NONE,
DSTOPTS,
AHIP,
CFTP,
HELLO,
SATEXPAK,
KRYPTOLAN,
RVD,
IPPC,
ADFS,
SATMON,
VISA,
IPCV,
CPNX,
CPHB,
WSN,
PVP,
BRSATMON,
ND,
WBMON,
WBEXPAK,
EON,
VMTP,
SVMTP,
VINES,
TTP,
IGP,
DGP,
TCF,
IGRP,
OSPFIGP,
SRPC,
LARP,
MTP,
AX25,
IPEIP,
MICP,
SCCSP,
ETHERIP,
ENCAP,
APES,
GMTP,
IPCOMP,
SCTP,
MH,
UDPLITE,
HIP,
SHIM6,
PIM,
CARP,
PGM,
MPLS,
PFSYNC
};
/// Shutdown mode
enum class ShutdownHow {
RD,
WR,
RDWR,
};
/// Array of IPv4 address
using IPv4Address = std::array<u8, 4>;
/// Cross-platform sockaddr structure
struct SockAddrIn {
Domain family;
IPv4Address ip;
u16 portno;
};
constexpr u32 FLAG_MSG_PEEK = 0x2;
constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
constexpr u32 FLAG_O_NONBLOCK = 0x800;
/// Cross-platform addrinfo structure
struct AddrInfo {
Domain family;
Type socket_type;
Protocol protocol;
SockAddrIn addr;
std::optional<std::string> canon_name;
};
} // namespace Network
+7
View File
@@ -1129,6 +1129,7 @@ add_library(core STATIC
internal_network/network_interface.h internal_network/network_interface.h
internal_network/socket_proxy.cpp internal_network/socket_proxy.cpp
internal_network/socket_proxy.h internal_network/socket_proxy.h
internal_network/socket_types.h
internal_network/sockets.h internal_network/sockets.h
internal_network/wifi_scanner.h internal_network/wifi_scanner.h
launch_timestamp_cache.cpp launch_timestamp_cache.cpp
@@ -1167,6 +1168,12 @@ add_library(core STATIC
tools/renderdoc.cpp tools/renderdoc.cpp
tools/renderdoc.h) tools/renderdoc.h)
if (UNIX AND NOT APPLE)
target_sources(core PRIVATE
internal_network/socket_icmp.cpp
internal_network/socket_icmp.h)
endif()
if (ENABLE_WIFI_SCAN) if (ENABLE_WIFI_SCAN)
target_sources(core PRIVATE internal_network/wifi_scanner.cpp) target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
if (LINUX) if (LINUX)
+1 -1
View File
@@ -18,7 +18,7 @@
#include "common/container/unordered_map.h" #include "common/container/unordered_map.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/ldn/ldn_results.h" #include "core/hle/service/ldn/ldn_results.h"
#include "core/hle/service/ldn/ldn_types.h" #include "core/hle/service/ldn/ldn_types.h"
+7 -8
View File
@@ -129,6 +129,13 @@ ServerManager::~ServerManager() {
} }
} }
void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_threads) {
for (size_t i = 0; i < num_threads; i++) {
auto thread_name = fmt::format("{}:{}", name, i + 1);
m_threads.emplace_back(m_system.Kernel().RunOnHostCoreThread(std::move(thread_name), [&] { this->LoopProcessImpl(); }));
}
}
void ServerManager::RunServer(std::unique_ptr<ServerManager>&& server_manager) { void ServerManager::RunServer(std::unique_ptr<ServerManager>&& server_manager) {
server_manager->m_system.RunServer(std::move(server_manager)); server_manager->m_system.RunServer(std::move(server_manager));
} }
@@ -245,14 +252,6 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
R_SUCCEED(); R_SUCCEED();
} }
void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_threads) {
for (size_t i = 0; i < num_threads; i++) {
auto thread_name = fmt::format("{}:{}", name, i + 1);
m_threads.emplace_back(m_system.Kernel().RunOnHostCoreThread(
std::move(thread_name), [&] { this->LoopProcessImpl(); }));
}
}
Result ServerManager::LoopProcess() { Result ServerManager::LoopProcess() {
SCOPE_EXIT { SCOPE_EXIT {
m_stopped.Set(); m_stopped.Set();
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -48,8 +51,8 @@ public:
Result ManageDeferral(Kernel::KEvent** out_event); Result ManageDeferral(Kernel::KEvent** out_event);
Result LoopProcess(); Result LoopProcess();
void StartAdditionalHostThreads(const char* name, size_t num_threads);
void StartAdditionalHostThreads(const char* name, size_t num_threads);
static void RunServer(std::unique_ptr<ServerManager>&& server); static void RunServer(std::unique_ptr<ServerManager>&& server);
private: private:
+242 -279
View File
@@ -12,7 +12,7 @@
#include <fmt/ranges.h> #include <fmt/ranges.h>
#include "common/logging.h" #include "common/logging.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/k_thread.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
@@ -20,6 +20,9 @@
#include "core/hle/service/sockets/sockets_translate.h" #include "core/hle/service/sockets/sockets_translate.h"
#include "core/internal_network/network.h" #include "core/internal_network/network.h"
#include "core/internal_network/socket_proxy.h" #include "core/internal_network/socket_proxy.h"
#if defined(__unix__) && !defined(__APPLE__)
#include "core/internal_network/socket_icmp.h"
#endif
#include "core/internal_network/sockets.h" #include "core/internal_network/sockets.h"
#include "network/network.h" #include "network/network.h"
#include <common/settings.h> #include <common/settings.h>
@@ -28,15 +31,18 @@ namespace Service::Sockets {
namespace { namespace {
bool IsConnectionBased(Type type) { [[nodiscard]] bool IsConnectionBased(Network::Type type) noexcept {
switch (type) { switch (type) {
case Type::STREAM: case Network::Type::STREAM:
case Network::Type::SEQPACKET:
return true; return true;
case Type::DGRAM: case Network::Type::RAW:
case Network::Type::DGRAM:
case Network::Type::RDM:
case Network::Type::Unspecified:
return false; return false;
default: default:
UNIMPLEMENTED_MSG("Unimplemented type={}", type); UNREACHABLE();
return false;
} }
} }
@@ -92,7 +98,7 @@ void BSD_USA::ConnectWork::Execute(BSD_USA* bsd) {
void BSD_USA::ConnectWork::Response(HLERequestContext& ctx) { void BSD_USA::ConnectWork::Response(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1); rb.Push<s32>(bsd_errno == Network::Errno::E_SUCCESS ? 0 : -1);
rb.PushEnum(bsd_errno); rb.PushEnum(bsd_errno);
} }
@@ -170,10 +176,9 @@ void BSD_USA::Socket(HLERequestContext& ctx) {
const u32 domain = rp.Pop<u32>(); const u32 domain = rp.Pop<u32>();
const u32 type = rp.Pop<u32>(); const u32 type = rp.Pop<u32>();
const u32 protocol = rp.Pop<u32>(); const u32 protocol = rp.Pop<u32>();
LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
const auto [fd, bsd_errno] = SocketImpl(Domain(domain), Type(type), Protocol(protocol)); const auto [fd, bsd_errno] = SocketImpl(Network::Domain(domain), Network::Type(type), Network::Protocol(protocol));
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@@ -189,8 +194,8 @@ void BSD_USA::SocketExempt(HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
auto [fd, bsd_errno] = SocketImpl(Domain(domain), Type(type), Protocol(protocol)); auto [fd, bsd_errno] = SocketImpl(Network::Domain(domain), Network::Type(type), Network::Protocol(protocol));
if (bsd_errno == Errno::SUCCESS) { if (bsd_errno == Network::Errno::E_SUCCESS) {
bsd_errno = ShutdownImpl(fd, 0); bsd_errno = ShutdownImpl(fd, 0);
} }
@@ -264,13 +269,13 @@ void BSD_USA::GetPeerName(HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={}", fd); LOG_DEBUG(Service, "called. fd={}", fd);
std::vector<u8> write_buffer(ctx.GetWriteBufferSize()); std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer); const Network::Errno bsd_errno = GetPeerNameImpl(fd, write_buffer);
ctx.WriteBuffer(write_buffer); ctx.WriteBuffer(write_buffer);
IPC::ResponseBuilder rb{ctx, 5}; IPC::ResponseBuilder rb{ctx, 5};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0); rb.Push<s32>(bsd_errno != Network::Errno::E_SUCCESS ? -1 : 0);
rb.PushEnum(bsd_errno); rb.PushEnum(bsd_errno);
rb.Push<u32>(static_cast<u32>(write_buffer.size())); rb.Push<u32>(static_cast<u32>(write_buffer.size()));
} }
@@ -282,13 +287,13 @@ void BSD_USA::GetSockName(HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={}", fd); LOG_DEBUG(Service, "called. fd={}", fd);
std::vector<u8> write_buffer(ctx.GetWriteBufferSize()); std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
const Errno bsd_errno = GetSockNameImpl(fd, write_buffer); const Network::Errno bsd_errno = GetSockNameImpl(fd, write_buffer);
ctx.WriteBuffer(write_buffer); ctx.WriteBuffer(write_buffer);
IPC::ResponseBuilder rb{ctx, 5}; IPC::ResponseBuilder rb{ctx, 5};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0); rb.Push<s32>(bsd_errno != Network::Errno::E_SUCCESS ? -1 : 0);
rb.PushEnum(bsd_errno); rb.PushEnum(bsd_errno);
rb.Push<u32>(static_cast<u32>(write_buffer.size())); rb.Push<u32>(static_cast<u32>(write_buffer.size()));
} }
@@ -296,21 +301,19 @@ void BSD_USA::GetSockName(HLERequestContext& ctx) {
void BSD_USA::GetSockOpt(HLERequestContext& ctx) { void BSD_USA::GetSockOpt(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const s32 fd = rp.Pop<s32>(); const s32 fd = rp.Pop<s32>();
const u32 level = rp.Pop<u32>(); const auto level = Network::SocketLevel(rp.Pop<u32>());
const auto optname = static_cast<OptName>(rp.Pop<u32>()); const auto optname = Network::OptName(rp.Pop<u32>());
std::vector<u8> optval(ctx.GetWriteBufferSize()); std::vector<u8> optval(ctx.GetWriteBufferSize());
LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} len={:#x}", fd, level, optname, LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} len={:#x}", fd, level, optname, optval.size());
optval.size()); const Network::Errno err = GetSockOptImpl(fd, level, optname, optval);
const Errno err = GetSockOptImpl(fd, level, optname, optval);
ctx.WriteBuffer(optval); ctx.WriteBuffer(optval);
IPC::ResponseBuilder rb{ctx, 5}; IPC::ResponseBuilder rb{ctx, 5};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<s32>(err == Errno::SUCCESS ? 0 : -1); rb.Push<s32>(err == Network::Errno::E_SUCCESS ? 0 : -1);
rb.PushEnum(err); rb.PushEnum(err);
rb.Push<u32>(static_cast<u32>(optval.size())); rb.Push<u32>(static_cast<u32>(optval.size()));
} }
@@ -333,7 +336,7 @@ void BSD_USA::Fcntl(HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg); LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg); const auto [ret, bsd_errno] = FcntlImpl(fd, Network::FcntlCmd(cmd), arg);
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@@ -345,13 +348,11 @@ void BSD_USA::SetSockOpt(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const s32 fd = rp.Pop<s32>(); const s32 fd = rp.Pop<s32>();
const u32 level = rp.Pop<u32>(); const Network::SocketLevel level = Network::SocketLevel(rp.Pop<u32>());
const OptName optname = static_cast<OptName>(rp.Pop<u32>()); const Network::OptName optname = Network::OptName(rp.Pop<u32>());
const auto optval = ctx.ReadBuffer(); const auto optval = ctx.ReadBuffer();
LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} optlen={}", fd, level, LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} optlen={}", fd, level, u32(optname), optval.size());
static_cast<u32>(optname), optval.size());
BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optval)); BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optval));
} }
@@ -473,7 +474,7 @@ void BSD_USA::DuplicateSocket(HLERequestContext& ctx) {
struct OutputParameters { struct OutputParameters {
s32 ret; s32 ret;
Errno bsd_errno; Network::Errno bsd_errno;
}; };
static_assert(sizeof(OutputParameters) == 0x8); static_assert(sizeof(OutputParameters) == 0x8);
@@ -485,7 +486,7 @@ void BSD_USA::DuplicateSocket(HLERequestContext& ctx) {
if (is_user) { if (is_user) {
rb.PushRaw(OutputParameters{ rb.PushRaw(OutputParameters{
.ret = 0, .ret = 0,
.bsd_errno = Errno::INVAL, .bsd_errno = Network::Errno::E_INVAL,
}); });
return; return;
} }
@@ -494,10 +495,10 @@ void BSD_USA::DuplicateSocket(HLERequestContext& ctx) {
if (auto* res = std::get_if<s32>(&res_v)) { if (auto* res = std::get_if<s32>(&res_v)) {
rb.PushRaw(OutputParameters{ rb.PushRaw(OutputParameters{
.ret = *res, .ret = *res,
.bsd_errno = Errno::SUCCESS, .bsd_errno = Network::Errno::E_SUCCESS,
}); });
} else { } else {
auto* err = std::get_if<Errno>(&res_v); auto* err = std::get_if<Network::Errno>(&res_v);
rb.PushRaw(OutputParameters{ rb.PushRaw(OutputParameters{
.ret = 0, .ret = 0,
.bsd_errno = *err, .bsd_errno = *err,
@@ -512,7 +513,7 @@ void BSD_USA::EventFd(HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called. initval={}, flags={}", initval, flags); LOG_WARNING(Service, "(STUBBED) called. initval={}, flags={}", initval, flags);
BuildErrnoResponse(ctx, Errno::SUCCESS); BuildErrnoResponse(ctx, Network::Errno::E_SUCCESS);
} }
template <typename Work> template <typename Work>
@@ -521,132 +522,143 @@ void BSD_USA::ExecuteWork(HLERequestContext& ctx, Work work) {
work.Response(ctx); work.Response(ctx);
} }
std::pair<s32, Errno> BSD_USA::SocketImpl(Domain domain, Type type, Protocol protocol) { std::pair<s32, Network::Errno> BSD_USA::SocketImpl(Network::Domain domain, Network::Type type, Network::Protocol protocol) {
// user bsd:u has restrictions on SOCK_SEQPACKET and SOCK_RAW // user bsd:u has restrictions on SOCK_SEQPACKET and SOCK_RAW
if (is_user && (type == Type::SEQPACKET || type == Type::RAW)) { LOG_DEBUG(Network, "domain={},type={},protocol={}", u32(domain), u32(type), u32(protocol));
if (type == Type::RAW && domain == Domain::INET && protocol == Protocol::ICMP) { if (is_user && (type == Network::Type::SEQPACKET || type == Network::Type::RAW)) {
if (type == Network::Type::RAW && domain == Network::Domain::INET && protocol == Network::Protocol::ICMP) {
// fine, can use on bsd:s and bsd:u // fine, can use on bsd:s and bsd:u
} else { } else {
return {-1, Errno::INVAL}; return {-1, Network::Errno::E_INVAL};
} }
} }
[[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0; [[maybe_unused]] const bool unk_flag = (u32(type) & 0x20000000) != 0;
UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type"); UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type");
type = static_cast<Type>(static_cast<u32>(type) & ~0x20000000); type = Network::Type(u32(type) & ~0x20000000);
const s32 fd = FindFreeFileDescriptorHandle(); const s32 fd = FindFreeFileDescriptorHandle();
if (fd < 0) { if (fd < 0) {
LOG_ERROR(Service, "No more file descriptors available"); LOG_ERROR(Service, "No more file descriptors available");
return {-1, Errno::MFILE}; return {-1, Network::Errno::E_MFILE};
}
if (Settings::values.airplane_mode.GetValue() && IsConnectionBased(type)) {
LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket");
file_descriptors[fd].reset();
return {-1, Network::Errno::E_NOTCONN};
} }
file_descriptors[fd] = FileDescriptor{}; file_descriptors[fd] = FileDescriptor{};
FileDescriptor& descriptor = *file_descriptors[fd]; FileDescriptor& descriptor = *file_descriptors[fd];
// ENONMEM might be thrown here // ENONMEM might be thrown here
LOG_INFO(Service, "New socket fd={},domain={},type={},prot={}", fd, domain, type, protocol);
LOG_INFO(Service, "New socket fd={}", fd); // While room is important -- we need to remember ICMP takes priority over **everything else**
// TODO: rework this so proxy sockets can be done transparently? -- like what if i need
// to browse the internet while playing LDN or something stupid like that?
auto room_member = Network::GetRoomMember().lock(); auto room_member = Network::GetRoomMember().lock();
if (room_member && room_member->IsConnected()) { if ((protocol != Network::Protocol::ICMP && protocol != Network::Protocol::ICMPV6)
&& (room_member && room_member->IsConnected())) {
descriptor.socket = std::make_shared<Network::ProxySocket>(); descriptor.socket = std::make_shared<Network::ProxySocket>();
descriptor.socket->fd = fd;
} else { } else {
descriptor.socket = std::make_shared<Network::Socket>(); descriptor.socket = std::make_shared<Network::Socket>();
} }
auto const bsd_errno = descriptor.socket->Initialize(domain, type, protocol);
descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); #if defined(__unix__) && !defined(__APPLE__)
descriptor.is_connection_based = IsConnectionBased(type); // ...only unix has this issue it seems, ICMP works otherwise fine on win
if ((protocol == Network::Protocol::ICMP || protocol == Network::Protocol::ICMPV6)
if (Settings::values.airplane_mode.GetValue() && descriptor.is_connection_based) { && bsd_errno != Network::Errno::E_SUCCESS) {
LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket"); LOG_WARNING(Network, "Using ICMP emulated socket");
return {-1, Errno::NOTCONN}; descriptor.socket = std::make_shared<Network::IcmpSocket>();
descriptor.socket->fd = fd;
} }
#endif
return {fd, Errno::SUCCESS}; descriptor.is_connection_based = IsConnectionBased(type);
#ifdef _WIN32
if (descriptor.is_connection_based && descriptor.socket->fd == INVALID_SOCKET) {
#else
if (descriptor.is_connection_based && descriptor.socket->fd == Network::Socket::INVALID_SOCKET) {
#endif
file_descriptors[fd].reset();
return {-1, bsd_errno};
}
return {fd, Network::Errno::E_SUCCESS};
} }
std::pair<s32, Errno> BSD_USA::PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, std::pair<s32, Network::Errno> BSD_USA::PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, s32 nfds, s32 timeout) {
s32 nfds, s32 timeout) { LOG_DEBUG(Network, "nfds={},timeout={}", nfds, timeout);
if (nfds <= 0) { if (nfds <= 0) {
// When no entries are provided, -1 is returned with errno zero // When no entries are provided, -1 is returned with errno zero
return {-1, Errno::SUCCESS}; return {-1, Network::Errno::E_SUCCESS};
} }
if (read_buffer.size() < nfds * sizeof(PollFD)) { if (read_buffer.size() < nfds * sizeof(Network::PollFD)) {
return {-1, Errno::INVAL}; return {-1, Network::Errno::E_INVAL};
} }
if (write_buffer.size() < nfds * sizeof(PollFD)) { if (write_buffer.size() < nfds * sizeof(Network::PollFD)) {
return {-1, Errno::INVAL}; return {-1, Network::Errno::E_INVAL};
} }
std::vector<PollFD> fds(nfds); std::span<const Network::PollFD> in_fds(reinterpret_cast<const Network::PollFD*>(read_buffer.data()), nfds);
std::memcpy(fds.data(), read_buffer.data(), nfds * sizeof(PollFD)); std::span<Network::PollFD> out_fds(reinterpret_cast<Network::PollFD*>(write_buffer.data()), nfds);
std::copy(in_fds.begin(), in_fds.end(), out_fds.begin());
if (timeout >= 0) { if (timeout >= 0) {
const s64 seconds = timeout / 1000; const s64 seconds = timeout / 1000;
const u64 nanoseconds = 1'000'000 * (static_cast<u64>(timeout) % 1000); const u64 nanoseconds = 1'000'000 * (u64(timeout) % 1000);
if (seconds < 0) { if (seconds < 0) {
return {-1, Errno::INVAL}; return {-1, Network::Errno::E_INVAL};
} }
if (nanoseconds > 999'999'999) { if (nanoseconds > 999'999'999) {
return {-1, Errno::INVAL}; return {-1, Network::Errno::E_INVAL};
} }
} else if (timeout != -1) { } else if (timeout != -1) {
return {-1, Errno::INVAL}; return {-1, Network::Errno::E_INVAL};
} }
for (PollFD& pollfd : fds) { for (size_t i = 0; i < in_fds.size(); ++i) {
ASSERT(False(pollfd.revents)); ASSERT(out_fds[i].fd == in_fds[i].fd && False(in_fds[i].revents));
if (!IsFileDescriptorValid(in_fds[i].fd)) {
if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) { out_fds[i].revents = {};
LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); if (!file_descriptors[in_fds[i].fd])
pollfd.revents = PollEvents{}; out_fds[i].revents = Network::PollEvents::NVAL;
return {0, Errno::SUCCESS}; return {0, Network::Errno::E_SUCCESS};
}
const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
if (!descriptor) {
LOG_TRACE(Service, "File descriptor handle={} is not allocated", pollfd.fd);
pollfd.revents = PollEvents::Nval;
return {0, Errno::SUCCESS};
} }
} }
std::vector<Network::PollFD> host_pollfds(fds.size()); std::vector<Network::HostPollFD> host_pollfds(in_fds.size());
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [](PollFD pollfd) { std::transform(in_fds.begin(), in_fds.end(), host_pollfds.begin(), [](auto const e) {
Network::PollFD result; Network::HostPollFD result{};
result.socket = file_descriptors[pollfd.fd]->socket.get(); result.socket = file_descriptors[e.fd]->socket.get();
result.events = Translate(pollfd.events); result.events = e.events;
result.revents = Network::PollEvents{}; result.revents = {};
return result; return result;
}); });
auto const res = Network::Poll(host_pollfds, timeout);
const auto result = Network::Poll(host_pollfds, timeout); for (size_t i = 0; i < in_fds.size(); ++i)
out_fds[i].revents = host_pollfds[i].revents;
const size_t num = host_pollfds.size(); return res;
for (size_t i = 0; i < num; ++i) {
fds[i].revents = Translate(host_pollfds[i].revents);
}
std::memcpy(write_buffer.data(), fds.data(), nfds * sizeof(PollFD));
return Translate(result);
} }
std::pair<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) { std::pair<s32, Network::Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
const s32 new_fd = FindFreeFileDescriptorHandle(); const s32 new_fd = FindFreeFileDescriptorHandle();
if (new_fd < 0) { if (new_fd < 0) {
LOG_ERROR(Service, "No more file descriptors available"); LOG_ERROR(Service, "No more file descriptors available");
return {-1, Errno::MFILE}; return {-1, Network::Errno::E_MFILE};
} }
FileDescriptor& descriptor = *file_descriptors[fd]; FileDescriptor& descriptor = *file_descriptors[fd];
auto [result, bsd_errno] = descriptor.socket->Accept(); auto [result, bsd_errno] = descriptor.socket->Accept();
if (bsd_errno != Network::Errno::SUCCESS) { if (bsd_errno != Network::Errno::E_SUCCESS) {
return {-1, Translate(bsd_errno)}; return {-1, bsd_errno};
} }
file_descriptors[new_fd] = FileDescriptor{}; file_descriptors[new_fd] = FileDescriptor{};
@@ -654,267 +666,218 @@ std::pair<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& write_buffer)
new_descriptor.socket = std::move(result.socket); new_descriptor.socket = std::move(result.socket);
new_descriptor.is_connection_based = descriptor.is_connection_based; new_descriptor.is_connection_based = descriptor.is_connection_based;
const SockAddrIn guest_addr_in = Translate(result.sockaddr_in); PutValue(write_buffer, result.sockaddr_in);
PutValue(write_buffer, guest_addr_in); return {new_fd, Network::Errno::E_SUCCESS};
return {new_fd, Errno::SUCCESS};
} }
Errno BSD_USA::BindImpl(s32 fd, std::span<const u8> addr) { Network::Errno BSD_USA::BindImpl(s32 fd, std::span<const u8> addr) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
ASSERT(addr.size() >= 16); ASSERT(addr.size() >= 16);
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
auto addr_in = GetValue<SockAddrIn>(addr); auto addr_in = GetValue<Network::SockAddrIn>(addr);
return file_descriptors[fd]->socket->Bind(addr_in);
return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
} }
Errno BSD_USA::ConnectImpl(s32 fd, std::span<const u8> addr) { Network::Errno BSD_USA::ConnectImpl(s32 fd, std::span<const u8> addr) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
ASSERT(addr.size() >= 16); ASSERT(addr.size() >= 16);
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
auto addr_in = GetValue<SockAddrIn>(addr); auto addr_in = GetValue<Network::SockAddrIn>(addr);
const Network::Errno result = file_descriptors[fd]->socket->Connect(addr_in);
const Errno result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); if (result == Network::Errno::E_ISCONN) {
if (result == Errno::ISCONN) {
LOG_DEBUG(Service, "returned ISCONN - socket already connected"); LOG_DEBUG(Service, "returned ISCONN - socket already connected");
return Errno::SUCCESS; return Network::Errno::E_SUCCESS;
} }
return result; return result;
} }
Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) { Network::Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName(); const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName();
if (bsd_errno != Network::Errno::SUCCESS) { if (bsd_errno != Network::Errno::E_SUCCESS) {
return Translate(bsd_errno); return bsd_errno;
} }
const SockAddrIn guest_addrin = Translate(addr_in); ASSERT(write_buffer.size() >= addr_in.len);
write_buffer.resize(addr_in.len);
ASSERT(write_buffer.size() >= sizeof(guest_addrin)); PutValue(write_buffer, addr_in);
write_buffer.resize(sizeof(guest_addrin)); return bsd_errno;
PutValue(write_buffer, guest_addrin);
return Translate(bsd_errno);
} }
Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) { Network::Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName(); const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName();
if (bsd_errno != Network::Errno::SUCCESS) { if (bsd_errno != Network::Errno::E_SUCCESS) {
return Translate(bsd_errno); return bsd_errno;
} }
const SockAddrIn guest_addrin = Translate(addr_in); ASSERT(write_buffer.size() >= addr_in.len);
write_buffer.resize(addr_in.len);
ASSERT(write_buffer.size() >= sizeof(guest_addrin)); PutValue(write_buffer, addr_in);
write_buffer.resize(sizeof(guest_addrin)); return bsd_errno;
PutValue(write_buffer, guest_addrin);
return Translate(bsd_errno);
} }
Errno BSD_USA::ListenImpl(s32 fd, s32 backlog) { Network::Errno BSD_USA::ListenImpl(s32 fd, s32 backlog) {
LOG_DEBUG(Network, "fd={},backlog={}", fd, backlog);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
return Translate(file_descriptors[fd]->socket->Listen(backlog)); return file_descriptors[fd]->socket->Listen(backlog);
} }
std::pair<s32, Errno> BSD_USA::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { std::pair<s32, Network::Errno> BSD_USA::FcntlImpl(s32 fd, Network::FcntlCmd cmd, s32 arg) {
LOG_DEBUG(Network, "fd={},cmd={},arg={}", fd, u32(cmd), arg);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
FileDescriptor& descriptor = *file_descriptors[fd]; FileDescriptor& descriptor = *file_descriptors[fd];
switch (cmd) { switch (cmd) {
case FcntlCmd::GETFL: case Network::FcntlCmd::GETFL:
ASSERT(arg == 0); ASSERT(arg == 0);
return {descriptor.flags, Errno::SUCCESS}; return {descriptor.flags, Network::Errno::E_SUCCESS};
case FcntlCmd::SETFL: { case Network::FcntlCmd::SETFL: {
const bool enable = (arg & Network::FLAG_O_NONBLOCK) != 0; const bool enable = (arg & u32(Network::FcntlFlags::NONBLOCK_NX)) != 0;
const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable)); const Network::Errno bsd_errno = descriptor.socket->SetNonBlock(enable);
if (bsd_errno != Errno::SUCCESS) { if (bsd_errno != Network::Errno::E_SUCCESS) {
return {-1, bsd_errno}; return {-1, bsd_errno};
} }
descriptor.flags = arg; descriptor.flags = arg;
return {0, Errno::SUCCESS}; return {0, Network::Errno::E_SUCCESS};
} }
default: default:
UNIMPLEMENTED_MSG("Unimplemented cmd={}", cmd); UNIMPLEMENTED_MSG("Unimplemented cmd={}", cmd);
return {-1, Errno::SUCCESS}; return {-1, Network::Errno::E_SUCCESS};
} }
} }
Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& optval) { Network::Errno BSD_USA::GetSockOptImpl(s32 fd, Network::SocketLevel level, Network::OptName optname, std::vector<u8>& optval) {
LOG_DEBUG(Network, "fd={},level={},optname={}", fd, u32(level), u32(optname));
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (level != static_cast<u32>(SocketLevel::SOCKET)) { if (level != Network::SocketLevel::SOCKET) {
UNIMPLEMENTED_MSG("Unknown getsockopt level"); LOG_WARNING(Service, "(stubbed) level fd={}, level={}, optname={}", fd, level, optname);
return Errno::SUCCESS;
} }
Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
switch (optname) { switch (optname) {
case OptName::ERROR_: { case Network::OptName::ERROR_: {
auto [pending_err, getsockopt_err] = socket->GetPendingError(); auto [pending_err, getsockopt_err] = socket->GetPendingError();
if (getsockopt_err == Network::Errno::SUCCESS) { if (getsockopt_err == Network::Errno::E_SUCCESS) {
Errno translated_pending_err = Translate(pending_err);
ASSERT_OR_EXECUTE_MSG( ASSERT_OR_EXECUTE_MSG(
optval.size() == sizeof(Errno), { return Errno::INVAL; }, optval.size() == sizeof(Network::Errno), { return Network::Errno::E_INVAL; },
"Incorrect getsockopt option size"); "Incorrect getsockopt option size");
optval.resize(sizeof(Errno)); optval.resize(sizeof(Network::Errno));
PutValue(optval, translated_pending_err); PutValue(optval, pending_err);
} }
return Translate(getsockopt_err); return getsockopt_err;
} }
default: default:
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
return Errno::SUCCESS; return Network::Errno::E_SUCCESS;
} }
} }
Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8> optval) { Network::Errno BSD_USA::SetSockOptImpl(s32 fd, Network::SocketLevel level, Network::OptName optname, std::span<const u8> optval) {
LOG_DEBUG(Service, "fd={},level={},optname={}", fd, level, optname);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
}
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
LOG_WARNING(Service, "(STUBBED) setsockopt with level={}, optname={}", level, optname);
return Errno::SUCCESS;
} }
Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
return socket->SetSockOpt(level, optname, optval);
if (optname == OptName::LINGER) {
ASSERT(optval.size() == sizeof(Linger));
auto linger = GetValue<Linger>(optval);
ASSERT(linger.onoff == 0 || linger.onoff == 1);
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
}
ASSERT(optval.size() == sizeof(u32));
auto value = GetValue<u32>(optval);
switch (optname) {
case OptName::REUSEADDR:
ASSERT(value == 0 || value == 1);
return Translate(socket->SetReuseAddr(value != 0));
case OptName::KEEPALIVE:
ASSERT(value == 0 || value == 1);
return Translate(socket->SetKeepAlive(value != 0));
case OptName::BROADCAST:
ASSERT(value == 0 || value == 1);
return Translate(socket->SetBroadcast(value != 0));
case OptName::SNDBUF:
return Translate(socket->SetSndBuf(value));
case OptName::RCVBUF:
return Translate(socket->SetRcvBuf(value));
case OptName::SNDTIMEO:
return Translate(socket->SetSndTimeo(value));
case OptName::RCVTIMEO:
return Translate(socket->SetRcvTimeo(value));
case OptName::NOSIGPIPE:
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
return Errno::SUCCESS;
default:
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
return Errno::SUCCESS;
}
} }
Errno BSD_USA::ShutdownImpl(s32 fd, s32 how) { Network::Errno BSD_USA::ShutdownImpl(s32 fd, s32 how) {
LOG_DEBUG(Network, "fd={},how={}", fd, how);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
const Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how)); return file_descriptors[fd]->socket->Shutdown(Network::ShutdownHow(how));
return Translate(file_descriptors[fd]->socket->Shutdown(host_how));
} }
std::pair<s32, Errno> BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) { std::pair<s32, Network::Errno> BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) {
LOG_DEBUG(Network, "fd={},flags={}", fd, flags);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
FileDescriptor& descriptor = *file_descriptors[fd]; FileDescriptor& descriptor = *file_descriptors[fd];
// Apply flags // Apply flags
using Network::FLAG_MSG_DONTWAIT; if ((flags & u32(Network::MsgOpt::DONTWAIT)) != 0) {
using Network::FLAG_O_NONBLOCK; flags &= ~u32(Network::MsgOpt::DONTWAIT);
if ((flags & FLAG_MSG_DONTWAIT) != 0) { if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) {
flags &= ~FLAG_MSG_DONTWAIT;
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
descriptor.socket->SetNonBlock(true); descriptor.socket->SetNonBlock(true);
} }
} }
const auto [ret, bsd_errno] = Translate(descriptor.socket->Recv(flags, message)); const auto [ret, bsd_errno] = descriptor.socket->Recv(flags, message);
// Restore original state // Restore original state
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0)
descriptor.socket->SetNonBlock(false); descriptor.socket->SetNonBlock(false);
}
return {ret, bsd_errno}; return {ret, bsd_errno};
} }
std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, std::pair<s32, Network::Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, std::vector<u8>& addr) {
std::vector<u8>& addr) { LOG_DEBUG(Network, "fd={},flags={}", fd, flags);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
FileDescriptor& descriptor = *file_descriptors[fd]; FileDescriptor& descriptor = *file_descriptors[fd];
@@ -929,19 +892,17 @@ std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>&
} }
// Apply flags // Apply flags
using Network::FLAG_MSG_DONTWAIT; if ((flags & u32(Network::MsgOpt::DONTWAIT)) != 0) {
using Network::FLAG_O_NONBLOCK; flags &= ~u32(Network::MsgOpt::DONTWAIT);
if ((flags & FLAG_MSG_DONTWAIT) != 0) { if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) {
flags &= ~FLAG_MSG_DONTWAIT;
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
descriptor.socket->SetNonBlock(true); descriptor.socket->SetNonBlock(true);
} }
} }
const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in)); const auto [ret, bsd_errno] = descriptor.socket->RecvFrom(flags, message, p_addr_in);
// Restore original state // Restore original state
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) {
descriptor.socket->SetNonBlock(false); descriptor.socket->SetNonBlock(false);
} }
@@ -950,58 +911,59 @@ std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>&
addr.clear(); addr.clear();
} else { } else {
ASSERT(addr.size() >= 16); ASSERT(addr.size() >= 16);
const SockAddrIn result = Translate(addr_in); PutValue(addr, addr_in);
PutValue(addr, result);
} }
} }
return {ret, bsd_errno}; return {ret, bsd_errno};
} }
std::pair<s32, Errno> BSD_USA::SendImpl(s32 fd, u32 flags, std::span<const u8> message) { std::pair<s32, Network::Errno> BSD_USA::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
LOG_DEBUG(Network, "fd={},flags={}", fd, flags);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
return Translate(file_descriptors[fd]->socket->Send(message, flags)); return file_descriptors[fd]->socket->Send(message, flags);
} }
std::pair<s32, Errno> BSD_USA::SendToImpl(s32 fd, u32 flags, std::span<const u8> message, std::pair<s32, Network::Errno> BSD_USA::SendToImpl(s32 fd, u32 flags, std::span<const u8> message, std::span<const u8> addr) {
std::span<const u8> addr) { LOG_DEBUG(Network, "fd={},flags={}", fd, flags);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return {-1, Errno::BADF}; return {-1, Network::Errno::E_BADF};
} }
Network::SockAddrIn addr_in; Network::SockAddrIn addr_in{};
Network::SockAddrIn* p_addr_in = nullptr; Network::SockAddrIn* p_addr_in = nullptr;
if (!addr.empty()) { if (!addr.empty()) {
ASSERT(addr.size() >= 16); ASSERT(addr.size() >= 16);
auto guest_addr_in = GetValue<SockAddrIn>(addr); auto guest_addr_in = GetValue<Network::SockAddrIn>(addr);
addr_in = Translate(guest_addr_in); addr_in = guest_addr_in;
p_addr_in = &addr_in; p_addr_in = &addr_in;
} }
return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in)); return file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in);
} }
Errno BSD_USA::CloseImpl(s32 fd) { Network::Errno BSD_USA::CloseImpl(s32 fd) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
if (!file_descriptors[fd]->socket) { if (!file_descriptors[fd]->socket) {
LOG_WARNING(Service, "Uninitialized socket"); LOG_WARNING(Service, "Uninitialized socket");
return Errno::BADF; return Network::Errno::E_BADF;
} }
const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); auto const bsd_errno = file_descriptors[fd]->socket->Close();
if (bsd_errno != Errno::SUCCESS) { if (bsd_errno != Network::Errno::E_SUCCESS) {
return bsd_errno; return bsd_errno;
} }
@@ -1011,15 +973,16 @@ Errno BSD_USA::CloseImpl(s32 fd) {
return bsd_errno; return bsd_errno;
} }
std::variant<s32, Errno> BSD_USA::DuplicateSocketImpl(s32 fd) { std::variant<s32, Network::Errno> BSD_USA::DuplicateSocketImpl(s32 fd) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return Errno::BADF; return Network::Errno::E_BADF;
} }
const s32 new_fd = FindFreeFileDescriptorHandle(); const s32 new_fd = FindFreeFileDescriptorHandle();
if (new_fd < 0) { if (!IsFileDescriptorValid(new_fd)) {
LOG_ERROR(Service, "No more file descriptors available"); LOG_ERROR(Service, "No more file descriptors available");
return Errno::MFILE; return Network::Errno::E_MFILE;
} }
file_descriptors[new_fd] = FileDescriptor{ file_descriptors[new_fd] = FileDescriptor{
@@ -1031,6 +994,7 @@ std::variant<s32, Errno> BSD_USA::DuplicateSocketImpl(s32 fd) {
} }
std::optional<std::shared_ptr<Network::SocketBase>> BSD_USA::GetSocket(s32 fd) { std::optional<std::shared_ptr<Network::SocketBase>> BSD_USA::GetSocket(s32 fd) {
LOG_DEBUG(Network, "fd={}", fd);
if (!IsFileDescriptorValid(fd)) { if (!IsFileDescriptorValid(fd)) {
return std::nullopt; return std::nullopt;
} }
@@ -1042,41 +1006,40 @@ std::optional<std::shared_ptr<Network::SocketBase>> BSD_USA::GetSocket(s32 fd) {
} }
s32 BSD_USA::FindFreeFileDescriptorHandle() noexcept { s32 BSD_USA::FindFreeFileDescriptorHandle() noexcept {
for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) { // first three file descriptors are reserved for:
if (!file_descriptors[fd]) { // STDOUT_FILENO, STDIN_FILENO and STDERR_FILENO
for (s32 fd = 0; fd < s32(file_descriptors.size()); ++fd)
if (!file_descriptors[fd])
return fd; return fd;
}
}
return -1; return -1;
} }
bool BSD_USA::IsFileDescriptorValid(s32 fd) const noexcept { bool BSD_USA::IsFileDescriptorValid(s32 fd) const noexcept {
if (fd > static_cast<s32>(MAX_FD) || fd < 0) { if (fd < 0 || fd >= s32(file_descriptors.size())) {
LOG_ERROR(Service, "Invalid file descriptor handle={}", fd); LOG_ERROR(Service, "Invalid handle={}", fd);
return false; return false;
} }
if (!file_descriptors[fd]) { if (!file_descriptors[fd]) {
LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd); LOG_ERROR(Service, "handle={} is not allocated", fd);
return false; return false;
} }
return true; return true;
} }
void BSD_USA::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept { void BSD_USA::BuildErrnoResponse(HLERequestContext& ctx, Network::Errno bsd_errno) const noexcept {
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1); rb.Push<s32>(bsd_errno == Network::Errno::E_SUCCESS ? 0 : -1);
rb.PushEnum(bsd_errno); rb.PushEnum(bsd_errno);
} }
void BSD_USA::OnProxyPacketReceived(const Network::ProxyPacket& packet) { void BSD_USA::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
for (auto& optional_descriptor : file_descriptors) { for (auto& optional_descriptor : file_descriptors) {
if (!optional_descriptor.has_value()) { if (optional_descriptor.has_value()) {
continue; FileDescriptor& descriptor = *optional_descriptor;
descriptor.socket.get()->HandleProxyPacket(packet);
} }
FileDescriptor& descriptor = *optional_descriptor;
descriptor.socket.get()->HandleProxyPacket(packet);
} }
} }
+27 -27
View File
@@ -11,7 +11,7 @@
#include <variant> #include <variant>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/hle/service/sockets/sockets.h" #include "core/hle/service/sockets/sockets.h"
#include "network/network.h" #include "network/network.h"
@@ -35,8 +35,8 @@ public:
// These methods are called from SSL; the first two are also called from // These methods are called from SSL; the first two are also called from
// this class for the corresponding IPC methods. // this class for the corresponding IPC methods.
// On the real device, the SSL service makes IPC calls to this service. // On the real device, the SSL service makes IPC calls to this service.
std::variant<s32, Errno> DuplicateSocketImpl(s32 fd); std::variant<s32, Network::Errno> DuplicateSocketImpl(s32 fd);
Errno CloseImpl(s32 fd); Network::Errno CloseImpl(s32 fd);
std::optional<std::shared_ptr<Network::SocketBase>> GetSocket(s32 fd); std::optional<std::shared_ptr<Network::SocketBase>> GetSocket(s32 fd);
private: private:
@@ -58,7 +58,7 @@ private:
std::span<const u8> read_buffer; std::span<const u8> read_buffer;
std::vector<u8> write_buffer; std::vector<u8> write_buffer;
s32 ret{}; s32 ret{};
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
struct AcceptWork { struct AcceptWork {
@@ -68,7 +68,7 @@ private:
s32 fd; s32 fd;
std::vector<u8> write_buffer; std::vector<u8> write_buffer;
s32 ret{}; s32 ret{};
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
struct ConnectWork { struct ConnectWork {
@@ -77,7 +77,7 @@ private:
s32 fd; s32 fd;
std::span<const u8> addr; std::span<const u8> addr;
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
struct RecvWork { struct RecvWork {
@@ -88,7 +88,7 @@ private:
u32 flags; u32 flags;
std::vector<u8> message; std::vector<u8> message;
s32 ret{}; s32 ret{};
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
struct RecvFromWork { struct RecvFromWork {
@@ -100,7 +100,7 @@ private:
std::vector<u8> message; std::vector<u8> message;
std::vector<u8> addr; std::vector<u8> addr;
s32 ret{}; s32 ret{};
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
struct SendWork { struct SendWork {
@@ -111,7 +111,7 @@ private:
u32 flags; u32 flags;
std::span<const u8> message; std::span<const u8> message;
s32 ret{}; s32 ret{};
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
struct SendToWork { struct SendToWork {
@@ -123,7 +123,7 @@ private:
std::span<const u8> message; std::span<const u8> message;
std::span<const u8> addr; std::span<const u8> addr;
s32 ret{}; s32 ret{};
Errno bsd_errno{}; Network::Errno bsd_errno{};
}; };
void RegisterClient(HLERequestContext& ctx); void RegisterClient(HLERequestContext& ctx);
@@ -155,29 +155,29 @@ private:
template <typename Work> template <typename Work>
void ExecuteWork(HLERequestContext& ctx, Work work); void ExecuteWork(HLERequestContext& ctx, Work work);
std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol); std::pair<s32, Network::Errno> SocketImpl(Network::Domain domain, Network::Type type, Network::Protocol protocol);
std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, s32 nfds, s32 timeout); std::pair<s32, Network::Errno> PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, s32 nfds, s32 timeout);
std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer); std::pair<s32, Network::Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
Errno BindImpl(s32 fd, std::span<const u8> addr); Network::Errno BindImpl(s32 fd, std::span<const u8> addr);
Errno ConnectImpl(s32 fd, std::span<const u8> addr); Network::Errno ConnectImpl(s32 fd, std::span<const u8> addr);
Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer); Network::Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer);
Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer); Network::Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
Errno ListenImpl(s32 fd, s32 backlog); Network::Errno ListenImpl(s32 fd, s32 backlog);
std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg); std::pair<s32, Network::Errno> FcntlImpl(s32 fd, Network::FcntlCmd cmd, s32 arg);
Errno GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& optval); Network::Errno GetSockOptImpl(s32 fd, Network::SocketLevel level, Network::OptName optname, std::vector<u8>& optval);
Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8> optval); Network::Errno SetSockOptImpl(s32 fd, Network::SocketLevel level, Network::OptName optname, std::span<const u8> optval);
Errno ShutdownImpl(s32 fd, s32 how); Network::Errno ShutdownImpl(s32 fd, s32 how);
std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message); std::pair<s32, Network::Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, std::pair<s32, Network::Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
std::vector<u8>& addr); std::vector<u8>& addr);
std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, std::span<const u8> message); std::pair<s32, Network::Errno> SendImpl(s32 fd, u32 flags, std::span<const u8> message);
std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, std::span<const u8> message, std::pair<s32, Network::Errno> SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
std::span<const u8> addr); std::span<const u8> addr);
s32 FindFreeFileDescriptorHandle() noexcept; s32 FindFreeFileDescriptorHandle() noexcept;
bool IsFileDescriptorValid(s32 fd) const noexcept; bool IsFileDescriptorValid(s32 fd) const noexcept;
void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept; void BuildErrnoResponse(HLERequestContext& ctx, Network::Errno bsd_errno) const noexcept;
static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{}; static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{};
+40 -44
View File
@@ -102,39 +102,39 @@ static bool IsBlockedHost(const std::string& host) {
[&host](const std::string& domain) { return host.find(domain) != std::string::npos; }); [&host](const std::string& domain) { return host.find(domain) != std::string::npos; });
} }
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) { static NetDbError GetAddrInfoErrorToNetDbError(Network::GetAddrInfoError result) {
// These combinations have been verified on console (but are not // These combinations have been verified on console (but are not
// exhaustive). // exhaustive).
switch (result) { switch (result) {
case GetAddrInfoError::SUCCESS: case Network::GetAddrInfoError::SUCCESS:
return NetDbError::Success; return NetDbError::Success;
case GetAddrInfoError::AGAIN: case Network::GetAddrInfoError::AGAIN:
return NetDbError::TryAgain; return NetDbError::TryAgain;
case GetAddrInfoError::NODATA: case Network::GetAddrInfoError::NODATA:
return NetDbError::HostNotFound; return NetDbError::HostNotFound;
case GetAddrInfoError::SERVICE: case Network::GetAddrInfoError::SERVICE:
return NetDbError::Success; return NetDbError::Success;
default: default:
return NetDbError::HostNotFound; return NetDbError::HostNotFound;
} }
} }
static Errno GetAddrInfoErrorToErrno(GetAddrInfoError result) { static Network::Errno GetAddrInfoErrorToErrno(Network::GetAddrInfoError result) {
// These combinations have been verified on console (but are not // These combinations have been verified on console (but are not
// exhaustive). // exhaustive).
switch (result) { switch (result) {
case GetAddrInfoError::SUCCESS: case Network::GetAddrInfoError::SUCCESS:
// Note: Sometimes a successful lookup sets errno to EADDRNOTAVAIL for // Note: Sometimes a successful lookup sets errno to EADDRNOTAVAIL for
// some reason, but that doesn't seem useful to implement. // some reason, but that doesn't seem useful to implement.
return Errno::SUCCESS; return Network::Errno::E_SUCCESS;
case GetAddrInfoError::AGAIN: case Network::GetAddrInfoError::AGAIN:
return Errno::SUCCESS; return Network::Errno::E_SUCCESS;
case GetAddrInfoError::NODATA: case Network::GetAddrInfoError::NODATA:
return Errno::SUCCESS; return Network::Errno::E_SUCCESS;
case GetAddrInfoError::SERVICE: case Network::GetAddrInfoError::SERVICE:
return Errno::INVAL; return Network::Errno::E_INVAL;
default: default:
return Errno::SUCCESS; return Network::Errno::E_SUCCESS;
} }
} }
@@ -155,9 +155,7 @@ static void AppendNulTerminated(std::vector<u8>& vec, std::string_view str) {
// host's gethostbyname, because it simplifies portability: e.g., getaddrinfo // host's gethostbyname, because it simplifies portability: e.g., getaddrinfo
// behaves the same on Unix and Windows, unlike gethostbyname where Windows // behaves the same on Unix and Windows, unlike gethostbyname where Windows
// doesn't implement h_errno. // doesn't implement h_errno.
static std::vector<u8> SerializeAddrInfoAsHostEnt(const std::vector<Network::AddrInfo>& vec, static std::vector<u8> SerializeAddrInfoAsHostEnt(std::span<const Network::AddrInfo> vec, std::string_view host) {
std::string_view host) {
std::vector<u8> data; std::vector<u8> data;
// h_name: use the input hostname (append nul-terminated) // h_name: use the input hostname (append nul-terminated)
AppendNulTerminated(data, host); AppendNulTerminated(data, host);
@@ -165,12 +163,12 @@ static std::vector<u8> SerializeAddrInfoAsHostEnt(const std::vector<Network::Add
Append<u32_be>(data, 0); // count of h_aliases Append<u32_be>(data, 0); // count of h_aliases
// (If the count were nonzero, the aliases would be appended as nul-terminated here.) // (If the count were nonzero, the aliases would be appended as nul-terminated here.)
Append<u16_be>(data, static_cast<u16>(Domain::INET)); // h_addrtype Append<u16_be>(data, u16(Network::Domain::INET)); // h_addrtype
Append<u16_be>(data, sizeof(Network::IPv4Address)); // h_length Append<u16_be>(data, sizeof(Network::IPv4Address)); // h_length
// h_addr_list: // h_addr_list:
size_t count = vec.size(); size_t count = vec.size();
ASSERT(count <= UINT32_MAX); ASSERT(count <= UINT32_MAX);
Append<u32_be>(data, static_cast<uint32_t>(count)); Append<u32_be>(data, u32(count));
for (const Network::AddrInfo& addrinfo : vec) { for (const Network::AddrInfo& addrinfo : vec) {
// On the Switch, this is passed through htonl despite already being // On the Switch, this is passed through htonl despite already being
// big-endian, so it ends up as little-endian. // big-endian, so it ends up as little-endian.
@@ -182,7 +180,7 @@ static std::vector<u8> SerializeAddrInfoAsHostEnt(const std::vector<Network::Add
return data; return data;
} }
static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestContext& ctx) { static std::pair<u32, Network::GetAddrInfoError> GetHostByNameRequestImpl(HLERequestContext& ctx) {
struct InputParameters { struct InputParameters {
u8 use_nsd_resolve; u8 use_nsd_resolve;
u32 cancel_handle; u32 cancel_handle;
@@ -205,7 +203,7 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
// Prevent resolution of Nintendo servers // Prevent resolution of Nintendo servers
if (IsBlockedHost(host)) { if (IsBlockedHost(host)) {
LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
return {0, GetAddrInfoError::AGAIN}; return {0, Network::GetAddrInfoError::AGAIN};
} }
auto res_v = Network::GetAddressInfo(host, /*service*/ std::nullopt); auto res_v = Network::GetAddressInfo(host, /*service*/ std::nullopt);
@@ -213,10 +211,10 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
const std::vector<u8> data = SerializeAddrInfoAsHostEnt(*res, host); const std::vector<u8> data = SerializeAddrInfoAsHostEnt(*res, host);
const u32 data_size = u32(data.size()); const u32 data_size = u32(data.size());
ctx.WriteBuffer(data, 0); ctx.WriteBuffer(data, 0);
return {data_size, GetAddrInfoError::SUCCESS}; return {data_size, Network::GetAddrInfoError::SUCCESS};
} }
auto* err = std::get_if<Network::GetAddrInfoError>(&res_v); auto* err = std::get_if<Network::GetAddrInfoError>(&res_v);
return {0, Translate(*err)}; return {0, *err};
} }
void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) { void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) {
@@ -224,7 +222,7 @@ void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) {
struct OutputParameters { struct OutputParameters {
NetDbError netdb_error; NetDbError netdb_error;
Errno bsd_errno; Network::Errno bsd_errno;
u32 data_size; u32 data_size;
}; };
static_assert(sizeof(OutputParameters) == 0xc); static_assert(sizeof(OutputParameters) == 0xc);
@@ -244,7 +242,7 @@ void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) {
struct OutputParameters { struct OutputParameters {
u32 data_size; u32 data_size;
NetDbError netdb_error; NetDbError netdb_error;
Errno bsd_errno; Network::Errno bsd_errno;
}; };
static_assert(sizeof(OutputParameters) == 0xc); static_assert(sizeof(OutputParameters) == 0xc);
@@ -257,24 +255,23 @@ void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) {
}); });
} }
static std::vector<u8> SerializeAddrInfo(const std::vector<Network::AddrInfo>& vec, static std::vector<u8> SerializeAddrInfo(std::span<const Network::AddrInfo> vec, std::string_view host) {
std::string_view host) {
// Adapted from // Adapted from
// https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190 // https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190
std::vector<u8> data; std::vector<u8> data;
for (const Network::AddrInfo& addrinfo : vec) { for (const Network::AddrInfo& addrinfo : vec) {
// serialized addrinfo: // serialized addrinfo:
Append<u32_be>(data, 0xBEEFCAFE); // magic Append<u32_be>(data, 0xBEEFCAFE); // magic
Append<u32_be>(data, 0); // ai_flags Append<u32_be>(data, 0); // ai_flags
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.family))); // ai_family Append<u32_be>(data, u32(addrinfo.family)); // ai_family
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.socket_type))); // ai_socktype Append<u32_be>(data, u32(addrinfo.socket_type)); // ai_socktype
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.protocol))); // ai_protocol Append<u32_be>(data, u32(addrinfo.protocol)); // ai_protocol
Append<u32_be>(data, 16); // ai_addrlen Append<u32_be>(data, 16); // ai_addrlen
// ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size // ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size
// ai_addr: // ai_addr:
Append<u16_be>(data, static_cast<u16>(Translate(addrinfo.addr.family))); // sin_family Append<u16_be>(data, u16(addrinfo.addr.family)); // sin_family
// On the Switch, the following fields are passed through htonl despite // On the Switch, the following fields are passed through htonl despite
// already being big-endian, so they end up as little-endian. // already being big-endian, so they end up as little-endian.
Append<u16_le>(data, addrinfo.addr.portno); // sin_port Append<u16_le>(data, addrinfo.addr.portno); // sin_port
@@ -296,7 +293,7 @@ static std::vector<u8> SerializeAddrInfo(const std::vector<Network::AddrInfo>& v
return data; return data;
} }
static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext& ctx) { static std::pair<u32, Network::GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext& ctx) {
struct InputParameters { struct InputParameters {
u8 use_nsd_resolve; u8 use_nsd_resolve;
u32 cancel_handle; u32 cancel_handle;
@@ -321,7 +318,7 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
// Prevent resolution of Nintendo servers // Prevent resolution of Nintendo servers
if (IsBlockedHost(host)) { if (IsBlockedHost(host)) {
LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
return {0, GetAddrInfoError::AGAIN}; return {0, Network::GetAddrInfoError::AGAIN};
} }
std::optional<std::string> service = std::nullopt; std::optional<std::string> service = std::nullopt;
@@ -331,24 +328,23 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
} }
// Serialized hints are also passed in a buffer, but are ignored for now. // Serialized hints are also passed in a buffer, but are ignored for now.
auto res_v = Network::GetAddressInfo(host, service); auto res_v = Network::GetAddressInfo(host, service);
if (auto* res = std::get_if<std::vector<Network::AddrInfo>>(&res_v)) { if (auto* res = std::get_if<std::vector<Network::AddrInfo>>(&res_v)) {
const std::vector<u8> data = SerializeAddrInfo(*res, host); const std::vector<u8> data = SerializeAddrInfo(*res, host);
const u32 data_size = u32(data.size()); const u32 data_size = u32(data.size());
ctx.WriteBuffer(data, 0); ctx.WriteBuffer(data, 0);
return {data_size, GetAddrInfoError::SUCCESS}; return {data_size, Network::GetAddrInfoError::SUCCESS};
} }
auto* err = std::get_if<Network::GetAddrInfoError>(&res_v); auto* err = std::get_if<Network::GetAddrInfoError>(&res_v);
return {0, Translate(*err)}; return {0, *err};
} }
void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) { void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) {
auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx);
struct OutputParameters { struct OutputParameters {
Errno bsd_errno; Network::Errno bsd_errno;
GetAddrInfoError gai_error; Network::GetAddrInfoError gai_error;
u32 data_size; u32 data_size;
}; };
static_assert(sizeof(OutputParameters) == 0xc); static_assert(sizeof(OutputParameters) == 0xc);
@@ -364,7 +360,7 @@ void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) {
void SFDNSRES::GetGaiStringErrorRequest(HLERequestContext& ctx) { void SFDNSRES::GetGaiStringErrorRequest(HLERequestContext& ctx) {
struct InputParameters { struct InputParameters {
GetAddrInfoError gai_errno; Network::GetAddrInfoError gai_errno;
}; };
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
auto input = rp.PopRaw<InputParameters>(); auto input = rp.PopRaw<InputParameters>();
@@ -382,9 +378,9 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
struct OutputParameters { struct OutputParameters {
u32 data_size; u32 data_size;
GetAddrInfoError gai_error; Network::GetAddrInfoError gai_error;
NetDbError netdb_error; NetDbError netdb_error;
Errno bsd_errno; Network::Errno bsd_errno;
}; };
static_assert(sizeof(OutputParameters) == 0x10); static_assert(sizeof(OutputParameters) == 0x10);
+1 -236
View File
@@ -8,6 +8,7 @@
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "core/internal_network/socket_types.h"
namespace Core { namespace Core {
class System; class System;
@@ -15,242 +16,6 @@ class System;
namespace Service::Sockets { namespace Service::Sockets {
enum class Errno : u32 {
SUCCESS = 0,
BADF = 9,
AGAIN = 11,
INVAL = 22,
MFILE = 24,
PIPE = 32,
MSGSIZE = 90,
CONNABORTED = 103,
CONNRESET = 104,
NOTCONN = 107,
TIMEDOUT = 110,
CONNREFUSED = 111,
INPROGRESS = 115,
ISCONN = 106,
};
enum class GetAddrInfoError : s32 {
SUCCESS = 0,
ADDRFAMILY = 1,
AGAIN = 2,
BADFLAGS = 3,
FAIL = 4,
FAMILY = 5,
MEMORY = 6,
NODATA = 7,
NONAME = 8,
SERVICE = 9,
SOCKTYPE = 10,
SYSTEM = 11,
BADHINTS = 12,
PROTOCOL = 13,
OVERFLOW_ = 14, // avoid name collision with Windows macro
OTHER = 15,
};
enum class Domain : u32 {
Unspecified = 0,
INET = 2,
};
enum class Type : u32 {
Unspecified = 0,
STREAM = 1,
DGRAM = 2,
RAW = 3,
SEQPACKET = 5,
};
enum class Protocol : u32 {
IP = 0,
ICMP = 1,
TCP = 6,
UDP = 17,
//
IPV6 = 41,
RAW = 255,
//
HOPOPTS = 0,
IGMP = 2,
GGP = 3,
IPV4 = 4,
ST = 7,
EGP = 8,
PIGP = 9,
RCCMON = 10,
NVPII = 11,
PUP = 12,
ARGUS = 13,
EMCON = 14,
XNET = 15,
CHAOS = 16,
MUX = 18,
MEAS = 19,
HMP = 20,
PRM = 21,
IDP = 22,
TRUNK1 = 23,
TRUNK2 = 24,
LEAF1 = 25,
LEAF2 = 26,
RDP = 27,
IRTP = 28,
TP = 29,
BLT = 30,
NSP = 31,
INP = 32,
DCCP = 33,
//3PC = 34,
IDPR = 35,
XTP = 36,
DDP = 37,
CMTP = 38,
TPXX = 39,
IL = 40,
SDRP = 42,
ROUTING = 43,
FRAGMENT = 44,
IDRP = 45,
RSVP = 46,
GRE = 47,
MHRP = 48,
BHA = 49,
ESP = 50,
AH = 51,
INLSP = 52,
SWIPE = 53,
NHRP = 54,
MOBILE = 55,
TLSP = 56,
SKIP = 57,
ICMPV6 = 58,
NONE = 59,
DSTOPTS = 60,
AHIP = 61,
CFTP = 62,
HELLO = 63,
SATEXPAK = 64,
KRYPTOLAN = 65,
RVD = 66,
IPPC = 67,
ADFS = 68,
SATMON = 69,
VISA = 70,
IPCV = 71,
CPNX = 72,
CPHB = 73,
WSN = 74,
PVP = 75,
BRSATMON = 76,
ND = 77,
WBMON = 78,
WBEXPAK = 79,
EON = 80,
VMTP = 81,
SVMTP = 82,
VINES = 83,
TTP = 84,
IGP = 85,
DGP = 86,
TCF = 87,
IGRP = 88,
OSPFIGP = 89,
SRPC = 90,
LARP = 91,
MTP = 92,
AX25 = 93,
IPEIP = 94,
MICP = 95,
SCCSP = 96,
ETHERIP = 97,
ENCAP = 98,
APES = 99,
GMTP = 100,
IPCOMP = 108,
SCTP = 132,
MH = 135,
UDPLITE = 136,
HIP = 139,
SHIM6 = 140,
PIM = 103,
CARP = 112,
PGM = 113,
MPLS = 137,
PFSYNC = 240,
};
enum class SocketLevel : u32 {
IP = 0,
TCP = 6,
SOCKET = 0xffff, // i.e. SOL_SOCKET
};
enum class OptName : u32 {
REUSEADDR = 0x4,
KEEPALIVE = 0x8,
BROADCAST = 0x20,
LINGER = 0x80,
SNDBUF = 0x1001,
RCVBUF = 0x1002,
SNDTIMEO = 0x1005,
RCVTIMEO = 0x1006,
ERROR_ = 0x1007, // avoid name collision with Windows macro
NOSIGPIPE = 0x800, // at least according to libnx
ACCEPTFILTER = 0x1000,
BINTIME = 0x2000,
NO_OFFLOAD = 0x4000,
NO_DDP = 0x8000,
};
enum class ShutdownHow : s32 {
RD = 0,
WR = 1,
RDWR = 2,
};
enum class FcntlCmd : s32 {
GETFL = 3,
SETFL = 4,
};
struct SockAddrIn {
u8 len;
u8 family;
u16 portno;
std::array<u8, 4> ip;
std::array<u8, 248> zeroes;
};
static_assert(sizeof(SockAddrIn) == 0x100);
enum class PollEvents : u16 {
// Using Pascal case because IN is a macro on Windows.
In = 1 << 0,
Pri = 1 << 1,
Out = 1 << 2,
Err = 1 << 3,
Hup = 1 << 4,
Nval = 1 << 5,
RdNorm = 1 << 6,
RdBand = 1 << 7,
WrBand = 1 << 8,
};
DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
struct PollFD {
s32 fd;
PollEvents events;
PollEvents revents;
};
struct Linger {
u32 onoff;
u32 linger;
};
void LoopProcess(Core::System& system); void LoopProcess(Core::System& system);
} // namespace Service::Sockets } // namespace Service::Sockets
@@ -15,388 +15,42 @@
namespace Service::Sockets { namespace Service::Sockets {
Errno Translate(Network::Errno value) { const char* Translate(Network::GetAddrInfoError error) {
switch (value) {
case Network::Errno::SUCCESS:
return Errno::SUCCESS;
case Network::Errno::BADF:
return Errno::BADF;
case Network::Errno::AGAIN:
return Errno::AGAIN;
case Network::Errno::INVAL:
return Errno::INVAL;
case Network::Errno::MFILE:
return Errno::MFILE;
case Network::Errno::PIPE:
return Errno::PIPE;
case Network::Errno::CONNREFUSED:
return Errno::CONNREFUSED;
case Network::Errno::NOTCONN:
return Errno::NOTCONN;
case Network::Errno::TIMEDOUT:
return Errno::TIMEDOUT;
case Network::Errno::CONNABORTED:
return Errno::CONNABORTED;
case Network::Errno::CONNRESET:
return Errno::CONNRESET;
case Network::Errno::INPROGRESS:
return Errno::INPROGRESS;
case Network::Errno::ISCONN:
return Errno::ISCONN;
default:
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
return Errno::SUCCESS;
}
}
std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) {
return {value.first, Translate(value.second)};
}
GetAddrInfoError Translate(Network::GetAddrInfoError error) {
switch (error) {
case Network::GetAddrInfoError::SUCCESS:
return GetAddrInfoError::SUCCESS;
case Network::GetAddrInfoError::ADDRFAMILY:
return GetAddrInfoError::ADDRFAMILY;
case Network::GetAddrInfoError::AGAIN:
return GetAddrInfoError::AGAIN;
case Network::GetAddrInfoError::BADFLAGS:
return GetAddrInfoError::BADFLAGS;
case Network::GetAddrInfoError::FAIL:
return GetAddrInfoError::FAIL;
case Network::GetAddrInfoError::FAMILY:
return GetAddrInfoError::FAMILY;
case Network::GetAddrInfoError::MEMORY:
return GetAddrInfoError::MEMORY;
case Network::GetAddrInfoError::NODATA:
return GetAddrInfoError::NODATA;
case Network::GetAddrInfoError::NONAME:
return GetAddrInfoError::NONAME;
case Network::GetAddrInfoError::SERVICE:
return GetAddrInfoError::SERVICE;
case Network::GetAddrInfoError::SOCKTYPE:
return GetAddrInfoError::SOCKTYPE;
case Network::GetAddrInfoError::SYSTEM:
return GetAddrInfoError::SYSTEM;
case Network::GetAddrInfoError::BADHINTS:
return GetAddrInfoError::BADHINTS;
case Network::GetAddrInfoError::PROTOCOL:
return GetAddrInfoError::PROTOCOL;
case Network::GetAddrInfoError::OVERFLOW_:
return GetAddrInfoError::OVERFLOW_;
case Network::GetAddrInfoError::OTHER:
return GetAddrInfoError::OTHER;
default:
UNIMPLEMENTED_MSG("Unimplemented GetAddrInfoError={}", error);
return GetAddrInfoError::OTHER;
}
}
const char* Translate(GetAddrInfoError error) {
// https://android.googlesource.com/platform/bionic/+/085543106/libc/dns/net/getaddrinfo.c#254 // https://android.googlesource.com/platform/bionic/+/085543106/libc/dns/net/getaddrinfo.c#254
switch (error) { switch (error) {
case GetAddrInfoError::SUCCESS: case Network::GetAddrInfoError::SUCCESS:
return "Success"; return "Success";
case GetAddrInfoError::ADDRFAMILY: case Network::GetAddrInfoError::ADDRFAMILY:
return "Address family for hostname not supported"; return "Address family for hostname not supported";
case GetAddrInfoError::AGAIN: case Network::GetAddrInfoError::AGAIN:
return "Temporary failure in name resolution"; return "Temporary failure in name resolution";
case GetAddrInfoError::BADFLAGS: case Network::GetAddrInfoError::BADFLAGS:
return "Invalid value for ai_flags"; return "Invalid value for ai_flags";
case GetAddrInfoError::FAIL: case Network::GetAddrInfoError::FAIL:
return "Non-recoverable failure in name resolution"; return "Non-recoverable failure in name resolution";
case GetAddrInfoError::FAMILY: case Network::GetAddrInfoError::FAMILY:
return "ai_family not supported"; return "ai_family not supported";
case GetAddrInfoError::MEMORY: case Network::GetAddrInfoError::MEMORY:
return "Memory allocation failure"; return "Memory allocation failure";
case GetAddrInfoError::NODATA: case Network::GetAddrInfoError::NODATA:
return "No address associated with hostname"; return "No address associated with hostname";
case GetAddrInfoError::NONAME: case Network::GetAddrInfoError::NONAME:
return "hostname nor servname provided, or not known"; return "hostname nor servname provided, or not known";
case GetAddrInfoError::SERVICE: case Network::GetAddrInfoError::SERVICE:
return "servname not supported for ai_socktype"; return "servname not supported for ai_socktype";
case GetAddrInfoError::SOCKTYPE: case Network::GetAddrInfoError::SOCKTYPE:
return "ai_socktype not supported"; return "ai_socktype not supported";
case GetAddrInfoError::SYSTEM: case Network::GetAddrInfoError::SYSTEM:
return "System error returned in errno"; return "System error returned in errno";
case GetAddrInfoError::BADHINTS: case Network::GetAddrInfoError::BADHINTS:
return "Invalid value for hints"; return "Invalid value for hints";
case GetAddrInfoError::PROTOCOL: case Network::GetAddrInfoError::PROTOCOL:
return "Resolved protocol is unknown"; return "Resolved protocol is unknown";
case GetAddrInfoError::OVERFLOW_: case Network::GetAddrInfoError::OVERFLOW_:
return "Argument buffer overflow"; return "Argument buffer overflow";
default: default:
return "Unknown error"; return "Unknown error";
} }
} }
Network::Domain Translate(Domain domain) {
switch (domain) {
case Domain::Unspecified:
return Network::Domain::Unspecified;
case Domain::INET:
return Network::Domain::INET;
default:
UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
return {};
}
}
Domain Translate(Network::Domain domain) {
switch (domain) {
case Network::Domain::Unspecified:
return Domain::Unspecified;
case Network::Domain::INET:
return Domain::INET;
default:
UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
return {};
}
}
Network::Type Translate(Type type) {
switch (type) {
case Type::Unspecified:
return Network::Type::Unspecified;
case Type::STREAM:
return Network::Type::STREAM;
case Type::DGRAM:
return Network::Type::DGRAM;
case Type::RAW:
return Network::Type::RAW;
case Type::SEQPACKET:
return Network::Type::SEQPACKET;
default:
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
return Network::Type{};
}
}
Type Translate(Network::Type type) {
switch (type) {
case Network::Type::Unspecified: return Type::Unspecified;
case Network::Type::STREAM: return Type::STREAM;
case Network::Type::DGRAM: return Type::DGRAM;
case Network::Type::RAW: return Type::RAW;
case Network::Type::SEQPACKET: return Type::SEQPACKET;
default:
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
return Type{};
}
}
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TCP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(UDP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV6) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(RAW) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(GGP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV4) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ST) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(EGP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PIGP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(RCCMON) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(NVPII) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PUP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ARGUS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(EMCON) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(XNET) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(CHAOS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MUX) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MEAS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(HMP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PRM) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IDP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TRUNK1) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TRUNK2) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(LEAF1) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(LEAF2) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(RDP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IRTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(BLT) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(NSP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(INP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(DCCP) \
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(3PC)*/ \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IDPR) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(XTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(DDP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(CMTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TPXX) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IL) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SDRP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ROUTING) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(FRAGMENT) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IDRP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(RSVP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(GRE) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MHRP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(BHA) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ESP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(AH) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(INLSP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SWIPE) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(NHRP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MOBILE) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TLSP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SKIP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMPV6) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(NONE) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(DSTOPTS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(AHIP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(CFTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(HELLO) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SATEXPAK) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(KRYPTOLAN) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(RVD) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IPPC) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ADFS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SATMON) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(VISA) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IPCV) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(CPNX) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(CPHB) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(WSN) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PVP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(BRSATMON) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ND) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(WBMON) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(WBEXPAK) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(EON) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(VMTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SVMTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(VINES) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(DGP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(TCF) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGRP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(OSPFIGP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SRPC) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(LARP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(AX25) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IPEIP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MICP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SCCSP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ETHERIP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ENCAP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(APES) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(GMTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IPCOMP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SCTP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MH) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(HIP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(SHIM6) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PIM) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(CARP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PGM) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(MPLS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PFSYNC)
[[nodiscard]] Network::Protocol Translate(Protocol protocol) {
switch (protocol) {
#define NETWORK_PROTOCOL_TRANSLATE_ELEM(name) case Protocol::name: return Network::Protocol::name;
NETWORK_PROTOCOL_TRANSLATE_LIST
#undef NETWORK_PROTOCOL_TRANSLATE_ELEM
default:
UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
return {};
}
}
[[nodiscard]] Protocol Translate(Network::Protocol protocol) {
switch (protocol) {
#define NETWORK_PROTOCOL_TRANSLATE_ELEM(name) case Network::Protocol::name: return Protocol::name;
NETWORK_PROTOCOL_TRANSLATE_LIST
#undef NETWORK_PROTOCOL_TRANSLATE_ELEM
default:
UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
return {};
}
}
#undef NETWORK_PROTOCOL_TRANSLATE_LIST
Network::PollEvents Translate(PollEvents flags) {
Network::PollEvents result{};
const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) {
if (True(flags & from)) {
flags &= ~from;
result |= to;
}
};
translate(PollEvents::In, Network::PollEvents::In);
translate(PollEvents::Pri, Network::PollEvents::Pri);
translate(PollEvents::Out, Network::PollEvents::Out);
translate(PollEvents::Err, Network::PollEvents::Err);
translate(PollEvents::Hup, Network::PollEvents::Hup);
translate(PollEvents::Nval, Network::PollEvents::Nval);
translate(PollEvents::RdNorm, Network::PollEvents::RdNorm);
translate(PollEvents::RdBand, Network::PollEvents::RdBand);
translate(PollEvents::WrBand, Network::PollEvents::WrBand);
UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags);
return result;
}
PollEvents Translate(Network::PollEvents flags) {
PollEvents result{};
const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) {
if (True(flags & from)) {
flags &= ~from;
result |= to;
}
};
translate(Network::PollEvents::In, PollEvents::In);
translate(Network::PollEvents::Pri, PollEvents::Pri);
translate(Network::PollEvents::Out, PollEvents::Out);
translate(Network::PollEvents::Err, PollEvents::Err);
translate(Network::PollEvents::Hup, PollEvents::Hup);
translate(Network::PollEvents::Nval, PollEvents::Nval);
translate(Network::PollEvents::RdNorm, PollEvents::RdNorm);
translate(Network::PollEvents::RdBand, PollEvents::RdBand);
translate(Network::PollEvents::WrBand, PollEvents::WrBand);
UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags);
return result;
}
Network::SockAddrIn Translate(SockAddrIn value) {
// All lengths are valid, from [0 upto 256]
return {
.family = Translate(Domain(value.family)),
.ip = value.ip,
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
};
}
SockAddrIn Translate(Network::SockAddrIn value) {
return {
.len = 16,
.family = static_cast<u8>(Translate(value.family)),
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
.ip = value.ip,
.zeroes = {},
};
}
Network::ShutdownHow Translate(ShutdownHow how) {
switch (how) {
case ShutdownHow::RD:
return Network::ShutdownHow::RD;
case ShutdownHow::WR:
return Network::ShutdownHow::WR;
case ShutdownHow::RDWR:
return Network::ShutdownHow::RDWR;
default:
UNIMPLEMENTED_MSG("Unimplemented how={}", how);
return {};
}
}
} // namespace Service::Sockets } // namespace Service::Sockets
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,49 +14,7 @@
namespace Service::Sockets { namespace Service::Sockets {
/// Translate abstract errno to guest errno
Errno Translate(Network::Errno value);
/// Translate abstract return value errno pair to guest return value errno pair
std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
/// Translate abstract getaddrinfo error to guest getaddrinfo error
GetAddrInfoError Translate(Network::GetAddrInfoError value);
/// Translate guest error to string /// Translate guest error to string
const char* Translate(GetAddrInfoError value); const char* Translate(Network::GetAddrInfoError value);
/// Translate guest domain to abstract domain
Network::Domain Translate(Domain domain);
/// Translate abstract domain to guest domain
Domain Translate(Network::Domain domain);
/// Translate guest type to abstract type
Network::Type Translate(Type type);
/// Translate abstract type to guest type
Type Translate(Network::Type type);
/// Translate guest protocol to abstract protocol
Network::Protocol Translate(Protocol protocol);
/// Translate abstract protocol to guest protocol
Protocol Translate(Network::Protocol protocol);
/// Translate guest poll event flags to abstract poll event flags
Network::PollEvents Translate(PollEvents flags);
/// Translate abstract poll event flags to guest poll event flags
PollEvents Translate(Network::PollEvents flags);
/// Translate guest socket address structure to abstract socket address structure
Network::SockAddrIn Translate(SockAddrIn value);
/// Translate abstract socket address structure to guest socket address structure
SockAddrIn Translate(Network::SockAddrIn value);
/// Translate guest shutdown mode to abstract shutdown mode
Network::ShutdownHow Translate(ShutdownHow how);
} // namespace Service::Sockets } // namespace Service::Sockets
+2 -2
View File
@@ -132,7 +132,7 @@ public:
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD_USA>("bsd:u"); auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD_USA>("bsd:u");
if (bsd) { if (bsd) {
auto err = bsd->CloseImpl(fd); auto err = bsd->CloseImpl(fd);
if (err != Service::Sockets::Errno::SUCCESS) { if (err != Network::Errno::E_SUCCESS) {
LOG_ERROR(Service_SSL, "Failed to close duplicated socket: {}", err); LOG_ERROR(Service_SSL, "Failed to close duplicated socket: {}", err);
} }
} }
@@ -203,7 +203,7 @@ private:
const bool non_block = mode == IoMode::NonBlocking; const bool non_block = mode == IoMode::NonBlocking;
const Network::Errno error = socket->SetNonBlock(non_block); const Network::Errno error = socket->SetNonBlock(non_block);
if (error != Network::Errno::SUCCESS) { if (error != Network::Errno::E_SUCCESS) {
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
} }
return ResultSuccess; return ResultSuccess;
@@ -290,10 +290,10 @@ public:
BIO_clear_retry_flags(bio); BIO_clear_retry_flags(bio);
auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0); auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0);
switch (err) { switch (err) {
case Network::Errno::SUCCESS: case Network::Errno::E_SUCCESS:
*actual_p = actual; *actual_p = actual;
return 1; return 1;
case Network::Errno::AGAIN: case Network::Errno::E_AGAIN:
BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
return 0; return 0;
default: default:
@@ -309,13 +309,13 @@ public:
BIO_clear_retry_flags(bio); BIO_clear_retry_flags(bio);
auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len}); auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len});
switch (err) { switch (err) {
case Network::Errno::SUCCESS: case Network::Errno::E_SUCCESS:
*actual_p = actual; *actual_p = actual;
if (actual == 0) { if (actual == 0) {
self->got_read_eof = true; self->got_read_eof = true;
} }
return actual ? 1 : 0; return actual ? 1 : 0;
case Network::Errno::AGAIN: case Network::Errno::E_AGAIN:
BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
return 0; return 0;
default: default:
@@ -146,11 +146,11 @@ public:
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size); const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
const auto [actual, err] = socket->Recv(0, read_span); const auto [actual, err] = socket->Recv(0, read_span);
switch (err) { switch (err) {
case Network::Errno::SUCCESS: case Network::Errno::E_SUCCESS:
ASSERT(static_cast<size_t>(actual) <= fill_size); ASSERT(static_cast<size_t>(actual) <= fill_size);
ciphertext_read_buf.resize(offset + actual); ciphertext_read_buf.resize(offset + actual);
return ResultSuccess; return ResultSuccess;
case Network::Errno::AGAIN: case Network::Errno::E_AGAIN:
ciphertext_read_buf.resize(offset); ciphertext_read_buf.resize(offset);
return ResultWouldBlock; return ResultWouldBlock;
default: default:
@@ -165,12 +165,12 @@ public:
while (!ciphertext_write_buf.empty()) { while (!ciphertext_write_buf.empty()) {
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0); const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
switch (err) { switch (err) {
case Network::Errno::SUCCESS: case Network::Errno::E_SUCCESS:
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size()); ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
ciphertext_write_buf.erase(ciphertext_write_buf.begin(), ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
ciphertext_write_buf.begin() + actual); ciphertext_write_buf.begin() + actual);
break; break;
case Network::Errno::AGAIN: case Network::Errno::E_AGAIN:
return ResultWouldBlock; return ResultWouldBlock;
default: default:
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
@@ -195,7 +195,7 @@ public:
LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset, LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
actual, cur.size(), static_cast<s32>(err)); actual, cur.size(), static_cast<s32>(err));
switch (err) { switch (err) {
case Network::Errno::SUCCESS: case Network::Errno::E_SUCCESS:
offset += actual; offset += actual;
if (actual == 0) { if (actual == 0) {
ASSERT(is_read); ASSERT(is_read);
@@ -203,7 +203,7 @@ public:
return errSecEndOfData; return errSecEndOfData;
} }
break; break;
case Network::Errno::AGAIN: case Network::Errno::E_AGAIN:
*dataLength = offset; *dataLength = offset;
return errSSLWouldBlock; return errSSLWouldBlock;
default: default:
+3 -2
View File
@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <common/socket_types.h>
#include <mutex> #include <mutex>
#include "core/internal_network/socket_types.h"
namespace Network { namespace Network {
File diff suppressed because it is too large Load Diff
+9 -63
View File
@@ -13,7 +13,7 @@
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#ifdef _WIN32 #ifdef _WIN32
#include <winsock2.h> #include <winsock2.h>
@@ -31,68 +31,10 @@ namespace Network {
class SocketBase; class SocketBase;
class Socket; class Socket;
/// Error code for network functions struct HostPollFD {
enum class Errno { SocketBase* socket = nullptr;
SUCCESS, Network::PollEvents events = {};
BADF, Network::PollEvents revents = {};
INVAL,
MFILE,
PIPE,
NOTCONN,
AGAIN,
CONNREFUSED,
CONNRESET,
CONNABORTED,
HOSTUNREACH,
NETDOWN,
NETUNREACH,
TIMEDOUT,
MSGSIZE,
INPROGRESS,
ISCONN,
OTHER,
};
enum class GetAddrInfoError {
SUCCESS,
ADDRFAMILY,
AGAIN,
BADFLAGS,
FAIL,
FAMILY,
MEMORY,
NODATA,
NONAME,
SERVICE,
SOCKTYPE,
SYSTEM,
BADHINTS,
PROTOCOL,
OVERFLOW_,
OTHER,
};
/// Cross-platform poll fd structure
enum class PollEvents : u16 {
// Using Pascal case because IN is a macro on Windows.
In = 1 << 0,
Pri = 1 << 1,
Out = 1 << 2,
Err = 1 << 3,
Hup = 1 << 4,
Nval = 1 << 5,
RdNorm = 1 << 6,
RdBand = 1 << 7,
WrBand = 1 << 8,
};
DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
struct PollFD {
SocketBase* socket;
PollEvents events;
PollEvents revents;
}; };
class NetworkInstance { class NetworkInstance {
@@ -101,6 +43,10 @@ public:
~NetworkInstance(); ~NetworkInstance();
}; };
sockaddr_in TranslateFromSockAddrIn(Network::SockAddrIn input);
Network::SockAddrIn TranslateToSockAddrIn(sockaddr_in input);
s32 TranslateMsgOptToNative(s32 flags);
void CancelPendingSocketOperations(); void CancelPendingSocketOperations();
void RestartSocketOperations(); void RestartSocketOperations();
@@ -46,7 +46,7 @@ namespace Network {
#ifdef _WIN32 #ifdef _WIN32
std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() { std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
ULONG buf_size = 0; ULONG buf_size = 0;
if (GetAdaptersAddresses( if (GetAdaptersAddresses(
@@ -66,7 +66,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
return {}; return {};
} }
std::vector<Network::NetworkInterface> result; std::vector<NetworkInterface> result;
for (auto* a = addrs; a; a = a->Next) { for (auto* a = addrs; a; a = a->Next) {
@@ -89,7 +89,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
gw = reinterpret_cast<sockaddr_in*>(a->FirstGatewayAddress->Address.lpSockaddr) gw = reinterpret_cast<sockaddr_in*>(a->FirstGatewayAddress->Address.lpSockaddr)
->sin_addr; ->sin_addr;
result.emplace_back(Network::NetworkInterface{ result.emplace_back(NetworkInterface{
.name = Common::UTF16ToUTF8(std::wstring{a->FriendlyName}), .name = Common::UTF16ToUTF8(std::wstring{a->FriendlyName}),
.ip_address = ip, .ip_address = ip,
.subnet_mask = mask, .subnet_mask = mask,
@@ -103,7 +103,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
#else #else
std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() { std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
#if defined(__ANDROID__) || defined(__linux__) #if defined(__ANDROID__) || defined(__linux__)
struct ifaddrs* ifaddr = nullptr; struct ifaddrs* ifaddr = nullptr;
if (getifaddrs(&ifaddr) != 0) { if (getifaddrs(&ifaddr) != 0) {
@@ -135,7 +135,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
LOG_WARNING(Network, "\"/proc/net/route\" not found - using gateway 0"); LOG_WARNING(Network, "\"/proc/net/route\" not found - using gateway 0");
} }
#endif #endif
std::vector<Network::NetworkInterface> ifaces; std::vector<NetworkInterface> ifaces;
for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr /* Have a netmask and address */ if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr /* Have a netmask and address */
|| ifa->ifa_addr->sa_family != AF_INET /* Must be of kind AF_INET */ || ifa->ifa_addr->sa_family != AF_INET /* Must be of kind AF_INET */
@@ -149,7 +149,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
}); });
in_addr gw; // Solaris defines s_addr as a macro, can't use special C++ shenanigans here in_addr gw; // Solaris defines s_addr as a macro, can't use special C++ shenanigans here
gw.s_addr = it != routes.end() ? it->gateway : 0; gw.s_addr = it != routes.end() ? it->gateway : 0;
ifaces.emplace_back(Network::NetworkInterface{ ifaces.emplace_back(NetworkInterface{
.name = ifa->ifa_name, .name = ifa->ifa_name,
.ip_address = std::bit_cast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr, .ip_address = std::bit_cast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr,
.subnet_mask = std::bit_cast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr, .subnet_mask = std::bit_cast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr,
@@ -159,7 +159,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
freeifaddrs(ifaddr); freeifaddrs(ifaddr);
return ifaces; return ifaces;
#elif defined(__FreeBSD__) #elif defined(__FreeBSD__)
std::vector<Network::NetworkInterface> ifaces; std::vector<NetworkInterface> ifaces;
int fd = ::socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); int fd = ::socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
if (fd < 0) { if (fd < 0) {
LOG_ERROR(Network, "socket: {}", std::strerror(errno)); LOG_ERROR(Network, "socket: {}", std::strerror(errno));
@@ -191,7 +191,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
size_t msglen = rtm->rtm_msglen - sizeof(*ifm); size_t msglen = rtm->rtm_msglen - sizeof(*ifm);
char const* p = (char const*)(ifm + 1); char const* p = (char const*)(ifm + 1);
Network::NetworkInterface iface{}; NetworkInterface iface{};
for (size_t i = 0; i < RTAX_MAX; i++) for (size_t i = 0; i < RTAX_MAX; i++)
if ((ifm->ifm_addrs & (1 << i)) != 0) { if ((ifm->ifm_addrs & (1 << i)) != 0) {
struct sockaddr const* sa = reinterpret_cast<struct sockaddr const*>(p); struct sockaddr const* sa = reinterpret_cast<struct sockaddr const*>(p);
@@ -220,7 +220,7 @@ std::vector<Network::NetworkInterface> GetAvailableNetworkInterfaces() {
#endif // _WIN32 #endif // _WIN32
std::optional<Network::NetworkInterface> GetSelectedNetworkInterface() { std::optional<NetworkInterface> GetSelectedNetworkInterface() {
auto const& sel_if = Settings::values.network_interface.GetValue(); auto const& sel_if = Settings::values.network_interface.GetValue();
if (auto const ifaces = Network::GetAvailableNetworkInterfaces(); ifaces.size() > 0) { if (auto const ifaces = Network::GetAvailableNetworkInterfaces(); ifaces.size() > 0) {
if (sel_if.empty()) if (sel_if.empty())
+277
View File
@@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cerrno>
#include <chrono>
#include <mutex>
#include <thread>
#ifdef __unix__
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include "common/assert.h"
#include "common/logging.h"
#include "core/internal_network/socket_icmp.h"
extern "C" {
extern char **environ;
}
namespace Network {
namespace {
u16 ComputeChecksum(std::span<const u8> data) {
u32 sum = 0;
for (size_t i = 0; i < data.size(); i += 2) {
u32 value = (u32(data[i + 0]) << 8ull) | u32(data[i + 1]); //big endian
sum += value;
}
if (data.size() % 2 != 0){
sum += u16(data[data.size() - 1]) << 8;
}
while ((sum >> 16) != 0)
sum = (sum & 0xffff) + (sum >> 16);
return (~sum) & 0xffff;
}
}
IcmpSocket::~IcmpSocket() {
if (fd == INVALID_SOCKET) {
return;
}
fd = INVALID_SOCKET;
}
Errno IcmpSocket::SetSockOpt(Network::SocketLevel level, Network::OptName optname, std::span<const u8> optval) {
LOG_WARNING(Network, "(stubbed) level={},optname={},optval={}", level, optname, optval.size());
if (optname == Network::OptName::RCVTIMEO) {
if (optval.size() < sizeof(Network::Timeval))
return Errno::E_INVAL;
std::memcpy(&rcv_timeo, optval.data(), sizeof(rcv_timeo));
}
return Errno::E_SUCCESS;
}
Errno IcmpSocket::Initialize(Domain domain, Type type, Protocol socket_protocol) {
return Errno::E_SUCCESS;
}
std::pair<IcmpSocket::AcceptResult, Errno> IcmpSocket::Accept() {
LOG_WARNING(Network, "(stubbed) called");
return {AcceptResult{}, Errno::E_SUCCESS};
}
Errno IcmpSocket::Connect(Network::SockAddrIn addr_in) {
LOG_WARNING(Network, "(stubbed) called");
connected_addr = addr_in;
return Errno::E_SUCCESS;
}
std::pair<Network::SockAddrIn, Errno> IcmpSocket::GetPeerName() {
LOG_WARNING(Network, "(stubbed) called");
return {Network::SockAddrIn{}, Errno::E_SUCCESS};
}
std::pair<Network::SockAddrIn, Errno> IcmpSocket::GetSockName() {
LOG_WARNING(Network, "(stubbed) called");
return {Network::SockAddrIn{}, Errno::E_SUCCESS};
}
Errno IcmpSocket::Bind(Network::SockAddrIn addr) {
LOG_WARNING(Network, "(stubbed) called");
return Errno::E_SUCCESS;
}
Errno IcmpSocket::Listen(s32 backlog) {
LOG_WARNING(Network, "(stubbed) called");
return Errno::E_SUCCESS;
}
Errno IcmpSocket::Shutdown(ShutdownHow how) {
LOG_WARNING(Network, "(stubbed) called");
return Errno::E_SUCCESS;
}
std::pair<s32, Errno> IcmpSocket::Recv(int flags, std::span<u8> message) {
LOG_DEBUG(Network, "(stubbed) called");
return connected_addr.has_value()
? RecvFrom(flags, message, nullptr)
: std::make_pair(s32(0), Errno::E_NOTCONN);
}
std::pair<s32, Errno> IcmpSocket::RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) {
LOG_DEBUG(Network, "(stubbed) called");
ASSERT(flags == 0);
ASSERT(message.size() < std::size_t((std::numeric_limits<int>::max)()));
#if !defined(__OPENORBIS__) && (defined(__FreeBSD__) || defined(__linux__))
const auto rcv_timeout_ms = (s64(rcv_timeo.tv_sec) * 1000) + (s64(rcv_timeo.tv_usec) / 1000);
const auto timestamp = std::chrono::steady_clock::now();
while (true) {
{
std::lock_guard guard(pings_mutex);
// find ping process that is finished running
for (auto it = pings.begin(); it != pings.end();) {
pid_t result = waitpid(it->ping_pid, &it->ping_status, WNOHANG);
// ping process is still running, go to next
if (result != it->ping_pid) {
++it;
continue;
}
// ping process is finished, remove and handle it
it = pings.erase(it);
if (it->ping_status == 0) {
if (addr) {
addr->family = it->family;
addr->ip = it->ip;
addr->portno = it->portno;
addr->len = 16;
addr->zeroes = {};
}
std::array<u8, 8> data{
0,
0,
0, //checksum
0,
it->seq_ident[0],
it->seq_ident[1],
it->seq_ident[2],
it->seq_ident[3]
};
auto const csum = ComputeChecksum(std::span<const u8>{data.begin(), data.end()});
data[2] = u8(csum >> 8); //hi
data[3] = u8(csum); //lo
auto const n = std::min(data.size(), message.size());
std::copy(data.begin(), data.begin() + n, message.begin());
return {s32(n), Errno::E_SUCCESS};
}
}
}
if (!blocking)
return {-1, Errno::E_AGAIN};
const auto time_diff = std::chrono::steady_clock::now() - timestamp;
const auto time_diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count();
if (time_diff_ms > rcv_timeout_ms)
return {-1, Errno::E_TIMEDOUT};
std::this_thread::yield();
}
#endif
return {-1, Errno::E_INVAL};
}
std::pair<s32, Errno> IcmpSocket::Send(std::span<const u8> message, int flags) {
LOG_DEBUG(Network, "(stubbed) called");
if (connected_addr.has_value())
return SendTo(flags, message, std::addressof(connected_addr.value()));
return {s32(0), Errno::E_NOTCONN};
}
std::pair<s32, Errno> IcmpSocket::SendTo(u32 flags, std::span<const u8> message, const Network::SockAddrIn* addr) {
LOG_DEBUG(Network, "(stubbed) called");
ASSERT(message.size() < size_t((std::numeric_limits<int>::max)()));
// 0 -> 8 (IPv4), 128 (IPv6)
// 1 -> 0
// 2..4 -> checksum
// 4..6 -> ident
// 6..8 -> seq
// PLEASE DON'T KILL ME, I SWEAR THIS IS LEGITIMATELY THE BEST WAY TO DO IT
// IF YOU OPEN socket() GOOGLE WILL STRAIGHT UP IP BAN YOU AFTER 2 HOURS
#if !(defined(__OPENORBIS__) || defined(__ANDROID__)) && (defined(__FreeBSD__) || defined(__linux__))
const auto rcv_timeout_ms = (s64(rcv_timeo.tv_sec) * 1000) + (s64(rcv_timeo.tv_usec) / 1000);
if (!addr)
return {-1, Errno::E_DESTADDRREQ};
if (message.size() >= 8) {
std::string ip_str = fmt::format(
"{}.{}.{}.{}",
addr->ip[0],
addr->ip[1],
addr->ip[2],
addr->ip[3]
);
#ifdef __FreeBSD__
// ping -W option is a nonfractional int (milliseconds)
std::string timeout_str = fmt::format("{}", rcv_timeout_ms);
std::vector<char*> argv = {
const_cast<char*>("ping"),
const_cast<char*>("-c"),
const_cast<char*>("1"),
const_cast<char*>("-W"),
timeout_str.data(),
ip_str.data(),
nullptr
};
#elif defined(__linux__)
// ping -W option is a fractional float (seconds)
auto const rcv_timeout_s = f64(rcv_timeout_ms) / 1000.0;
std::string timeout_str = fmt::format("{}", rcv_timeout_s);
std::vector<char*> argv = {
const_cast<char*>("ping"),
const_cast<char*>("-c"),
const_cast<char*>("1"),
const_cast<char*>("-W"),
timeout_str.data(),
ip_str.data(),
nullptr
};
#endif
pid_t ping_pid;
// we should pass in attributes to stop stdout spam, but im too lazy to figure that out
if (posix_spawnp(&ping_pid, "ping", nullptr, nullptr, argv.data(), environ) != 0) {
LOG_ERROR(Network, "Unable to start ping process for emulated ICMP socket");
return {-1, Errno::E_INVAL};
}
std::lock_guard guard(pings_mutex);
if (pings.size() >= pings.max_size())
pings.erase(pings.begin());
pings.push_back(PingProcessData{
.ip = addr->ip,
.portno = addr->portno,
.ping_pid = ping_pid,
.ping_status = 0,
.seq_ident = {
message[4],
message[5],
message[6],
message[7]
},
.family = addr->family,
});
return {s32(message.size()), Errno::E_SUCCESS};
}
#endif
return {-1, Errno::E_INVAL};
}
Errno IcmpSocket::Close() {
LOG_DEBUG(Network, "called");
fd = INVALID_SOCKET;
return Errno::E_SUCCESS;
}
std::pair<Errno, Errno> IcmpSocket::GetPendingError() {
LOG_DEBUG(Network, "called");
return {Errno::E_SUCCESS, Errno::E_SUCCESS};
}
bool IcmpSocket::IsOpened() const {
return fd != INVALID_SOCKET;
}
void IcmpSocket::HandleProxyPacket(const ProxyPacket& packet) {
LOG_WARNING(Network, "(stubbed) called");
}
Errno IcmpSocket::SetNonBlock(bool enable) {
blocking = !enable;
return Errno::E_SUCCESS;
}
} // namespace Network
+56
View File
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <span>
#include <utility>
#include <sys/types.h>
#include <mutex>
#include <boost/container/static_vector.hpp>
#include "core/internal_network/socket_types.h"
#include "core/internal_network/sockets.h"
namespace Network {
struct PingProcessData {
IPv4Address ip;
u16 portno;
pid_t ping_pid;
pid_t ping_status;
std::array<u8, 4> seq_ident;
u8 family;
};
class IcmpSocket : public Network::SocketBase {
public:
explicit IcmpSocket() noexcept = default;
~IcmpSocket() override;
Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override;
Errno Close() override;
std::pair<AcceptResult, Errno> Accept() override;
Errno Connect(Network::SockAddrIn addr_in) override;
std::pair<Network::SockAddrIn, Errno> GetPeerName() override;
std::pair<Network::SockAddrIn, Errno> GetSockName() override;
Errno Bind(Network::SockAddrIn addr) override;
Errno Listen(s32 backlog) override;
Errno Shutdown(ShutdownHow how) override;
std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override;
std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) override;
std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override;
std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, const Network::SockAddrIn* addr) override;
Errno SetSockOpt(Network::SocketLevel level, Network::OptName option, std::span<const u8> value) override;
std::pair<Errno, Errno> GetPendingError() override;
bool IsOpened() const override;
void HandleProxyPacket(const ProxyPacket& packet) override;
Errno SetNonBlock(bool enable) override;
boost::container::static_vector<PingProcessData, 128> pings;
std::optional<SockAddrIn> connected_addr;
std::mutex pings_mutex;
Network::Timeval rcv_timeo;
bool blocking = true;
};
} // namespace Network
+70 -108
View File
@@ -47,71 +47,82 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
received_packets.push(decompressed); received_packets.push(decompressed);
} }
template <typename T> Errno ProxySocket::SetNonBlock(bool enable) {
Errno ProxySocket::SetSockOpt(SOCKET fd_, int option, T value) { blocking = !enable;
LOG_DEBUG(Network, "(STUBBED) called"); return Errno::E_SUCCESS;
return Errno::SUCCESS; }
Errno ProxySocket::SetSockOpt(Network::SocketLevel level, Network::OptName optname, std::span<const u8> optval) {
LOG_DEBUG(Network, "level={},optname={},optval={}", level, optname, optval.size());
// numeric values?
if (optval.size() >= sizeof(u32)) {
u32 value;
std::memcpy(&value, optval.data(), sizeof(value));
if (optname == Network::OptName::BROADCAST)
broadcast = bool(value);
if (optname == Network::OptName::SNDTIMEO)
send_timeout = value;
if (optname == Network::OptName::RCVTIMEO)
receive_timeout = value;
}
return Errno::E_SUCCESS;
} }
Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) { Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) {
protocol = socket_protocol; protocol = socket_protocol;
SetSockOpt(fd, SO_TYPE, type); return Errno::E_SUCCESS;
return Errno::SUCCESS;
} }
std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() { std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
return {AcceptResult{}, Errno::SUCCESS}; return {AcceptResult{}, Errno::E_SUCCESS};
} }
Errno ProxySocket::Connect(SockAddrIn addr_in) { Errno ProxySocket::Connect(Network::SockAddrIn addr_in) {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
return Errno::SUCCESS; return Errno::E_SUCCESS;
} }
std::pair<SockAddrIn, Errno> ProxySocket::GetPeerName() { std::pair<Network::SockAddrIn, Errno> ProxySocket::GetPeerName() {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
return {SockAddrIn{}, Errno::SUCCESS}; return {Network::SockAddrIn{}, Errno::E_SUCCESS};
} }
std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() { std::pair<Network::SockAddrIn, Errno> ProxySocket::GetSockName() {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
return {SockAddrIn{}, Errno::SUCCESS}; return {Network::SockAddrIn{}, Errno::E_SUCCESS};
} }
Errno ProxySocket::Bind(SockAddrIn addr) { Errno ProxySocket::Bind(Network::SockAddrIn addr) {
if (is_bound) { if (is_bound) {
LOG_WARNING(Network, "Rebinding Socket is unimplemented!"); LOG_WARNING(Network, "Rebinding Socket is unimplemented!");
return Errno::SUCCESS; return Errno::E_SUCCESS;
} }
local_endpoint = addr; local_endpoint = addr;
is_bound = true; is_bound = true;
return Errno::E_SUCCESS;
return Errno::SUCCESS;
} }
Errno ProxySocket::Listen(s32 backlog) { Errno ProxySocket::Listen(s32 backlog) {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
return Errno::SUCCESS; return Errno::E_SUCCESS;
} }
Errno ProxySocket::Shutdown(ShutdownHow how) { Errno ProxySocket::Shutdown(ShutdownHow how) {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
return Errno::SUCCESS; return Errno::E_SUCCESS;
} }
std::pair<s32, Errno> ProxySocket::Recv(int flags, std::span<u8> message) { std::pair<s32, Errno> ProxySocket::Recv(int flags, std::span<u8> message) {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
ASSERT(flags == 0); ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)())); ASSERT(message.size() < std::size_t((std::numeric_limits<int>::max)()));
return {s32(0), Errno::E_SUCCESS};
return {static_cast<s32>(0), Errno::SUCCESS};
} }
std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) { std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) {
ASSERT(flags == 0); ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)())); ASSERT(message.size() < std::size_t((std::numeric_limits<int>::max)()));
// TODO (flTobi): Verify the timeout behavior and break when connection is lost // TODO (flTobi): Verify the timeout behavior and break when connection is lost
const auto timestamp = std::chrono::steady_clock::now(); const auto timestamp = std::chrono::steady_clock::now();
@@ -128,88 +139,85 @@ std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, So
} }
if (!blocking) { if (!blocking) {
return {-1, Errno::AGAIN}; return {-1, Errno::E_AGAIN};
} }
std::this_thread::yield(); std::this_thread::yield();
const auto time_diff = std::chrono::steady_clock::now() - timestamp; const auto time_diff = std::chrono::steady_clock::now() - timestamp;
const auto time_diff_ms = const auto time_diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count();
std::chrono::duration_cast<std::chrono::milliseconds>(time_diff).count();
if (time_diff_ms > timeout) { if (time_diff_ms > timeout) {
return {-1, Errno::TIMEDOUT}; return {-1, Errno::E_TIMEDOUT};
} }
} }
} }
std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::span<u8> message, SockAddrIn* addr, std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::span<u8> message, Network::SockAddrIn* addr, std::size_t max_length) {
std::size_t max_length) { LOG_DEBUG(Network, "called");
ProxyPacket& packet = received_packets.front(); ProxyPacket& packet = received_packets.front();
if (addr) { if (addr) {
addr->family = Domain::INET; addr->len = 16;
addr->family = u8(Network::Domain::INET);
addr->ip = packet.local_endpoint.ip; // The senders ip address addr->ip = packet.local_endpoint.ip; // The senders ip address
addr->portno = packet.local_endpoint.portno; // The senders port number addr->portno = packet.local_endpoint.portno; // The senders port number
addr->zeroes = {};
} }
bool peek = (flags & FLAG_MSG_PEEK) != 0; bool peek = (flags & u32(Network::MsgOpt::PEEK)) != 0;
std::size_t read_bytes; std::size_t read_bytes;
if (packet.data.size() > max_length) { if (packet.data.size() > max_length) {
read_bytes = max_length; read_bytes = max_length;
memcpy(message.data(), packet.data.data(), max_length); std::memcpy(message.data(), packet.data.data(), max_length);
if (protocol == Protocol::UDP) { if (protocol == Protocol::UDP) {
if (!peek) { if (!peek) {
received_packets.pop(); received_packets.pop();
} }
return {-1, Errno::MSGSIZE}; return {-1, Errno::E_MSGSIZE};
} else if (protocol == Protocol::TCP) { } else if (protocol == Protocol::TCP) {
std::vector<u8> numArray(packet.data.size() - max_length); std::vector<u8> numArray(packet.data.size() - max_length);
std::copy(packet.data.begin() + max_length, packet.data.end(), std::copy(packet.data.begin() + max_length, packet.data.end(), std::back_inserter(numArray));
std::back_inserter(numArray));
packet.data = numArray; packet.data = numArray;
} }
} else { } else {
read_bytes = packet.data.size(); read_bytes = packet.data.size();
memcpy(message.data(), packet.data.data(), read_bytes); std::memcpy(message.data(), packet.data.data(), read_bytes);
if (!peek) { if (!peek) {
received_packets.pop(); received_packets.pop();
} }
} }
return {static_cast<u32>(read_bytes), Errno::SUCCESS}; return {u32(read_bytes), Errno::E_SUCCESS};
} }
std::pair<s32, Errno> ProxySocket::Send(std::span<const u8> message, int flags) { std::pair<s32, Errno> ProxySocket::Send(std::span<const u8> message, int flags) {
LOG_WARNING(Network, "(STUBBED) called"); LOG_WARNING(Network, "(stubbed) called");
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)())); ASSERT(message.size() < size_t((std::numeric_limits<int>::max)()));
ASSERT(flags == 0); ASSERT(flags == 0);
return {s32(0), Errno::E_SUCCESS};
return {static_cast<s32>(0), Errno::SUCCESS};
} }
void ProxySocket::SendPacket(ProxyPacket& packet) { void ProxySocket::SendPacket(ProxyPacket& packet) {
if (auto room_member = Network::GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) { if (room_member->IsConnected()) {
packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(), packet.data.size());
packet.data.size());
room_member->SendProxyPacket(packet); room_member->SendProxyPacket(packet);
} }
} }
} }
std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message, std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message, const Network::SockAddrIn* addr) {
const SockAddrIn* addr) { LOG_DEBUG(Network, "called");
ASSERT(flags == 0); ASSERT(flags == 0);
if (!is_bound) { if (!is_bound) {
LOG_ERROR(Network, "ProxySocket is not bound!"); LOG_ERROR(Network, "ProxySocket is not bound!");
return {static_cast<s32>(message.size()), Errno::SUCCESS}; return {s32(message.size()), Errno::E_SUCCESS};
} }
if (auto room_member = Network::GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
if (!room_member->IsConnected()) { if (!room_member->IsConnected()) {
return {static_cast<s32>(message.size()), Errno::SUCCESS}; return {s32(message.size()), Errno::E_SUCCESS};
} }
} }
@@ -234,66 +242,20 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message
SendPacket(packet); SendPacket(packet);
return {static_cast<s32>(message.size()), Errno::SUCCESS}; return {s32(message.size()), Errno::E_SUCCESS};
} }
Errno ProxySocket::Close() { Errno ProxySocket::Close() {
LOG_DEBUG(Network, "called");
fd = INVALID_SOCKET; fd = INVALID_SOCKET;
closed = true; closed = true;
return Errno::SUCCESS; return Errno::E_SUCCESS;
}
Errno ProxySocket::SetLinger(bool enable, u32 linger) {
struct Linger {
u16 linger_enable;
u16 linger_time;
} values;
values.linger_enable = enable ? 1 : 0;
values.linger_time = static_cast<u16>(linger);
return SetSockOpt(fd, SO_LINGER, values);
}
Errno ProxySocket::SetReuseAddr(bool enable) {
return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
}
Errno ProxySocket::SetBroadcast(bool enable) {
broadcast = enable;
return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
}
Errno ProxySocket::SetSndBuf(u32 value) {
return SetSockOpt(fd, SO_SNDBUF, value);
}
Errno ProxySocket::SetKeepAlive(bool enable) {
return Errno::SUCCESS;
}
Errno ProxySocket::SetRcvBuf(u32 value) {
return SetSockOpt(fd, SO_RCVBUF, value);
}
Errno ProxySocket::SetSndTimeo(u32 value) {
send_timeout = value;
return SetSockOpt(fd, SO_SNDTIMEO, static_cast<int>(value));
}
Errno ProxySocket::SetRcvTimeo(u32 value) {
receive_timeout = value;
return SetSockOpt(fd, SO_RCVTIMEO, static_cast<int>(value));
}
Errno ProxySocket::SetNonBlock(bool enable) {
blocking = !enable;
return Errno::SUCCESS;
} }
std::pair<Errno, Errno> ProxySocket::GetPendingError() { std::pair<Errno, Errno> ProxySocket::GetPendingError() {
LOG_DEBUG(Network, "(STUBBED) called"); LOG_DEBUG(Network, "called");
return {Errno::SUCCESS, Errno::SUCCESS}; return {Errno::E_SUCCESS, Errno::E_SUCCESS};
} }
bool ProxySocket::IsOpened() const { bool ProxySocket::IsOpened() const {
+10 -27
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -29,13 +29,13 @@ public:
std::pair<AcceptResult, Errno> Accept() override; std::pair<AcceptResult, Errno> Accept() override;
Errno Connect(SockAddrIn addr_in) override; Errno Connect(Network::SockAddrIn addr_in) override;
std::pair<SockAddrIn, Errno> GetPeerName() override; std::pair<Network::SockAddrIn, Errno> GetPeerName() override;
std::pair<SockAddrIn, Errno> GetSockName() override; std::pair<Network::SockAddrIn, Errno> GetSockName() override;
Errno Bind(SockAddrIn addr) override; Errno Bind(Network::SockAddrIn addr) override;
Errno Listen(s32 backlog) override; Errno Listen(s32 backlog) override;
@@ -43,9 +43,9 @@ public:
std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override; std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override;
std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) override; std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) override;
std::pair<s32, Errno> ReceivePacket(int flags, std::span<u8> message, SockAddrIn* addr, std::pair<s32, Errno> ReceivePacket(int flags, std::span<u8> message, Network::SockAddrIn* addr,
std::size_t max_length); std::size_t max_length);
std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override; std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override;
@@ -53,28 +53,11 @@ public:
void SendPacket(ProxyPacket& packet); void SendPacket(ProxyPacket& packet);
std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message,
const SockAddrIn* addr) override; const Network::SockAddrIn* addr) override;
Errno SetLinger(bool enable, u32 linger) override;
Errno SetReuseAddr(bool enable) override;
Errno SetBroadcast(bool enable) override;
Errno SetKeepAlive(bool enable) override;
Errno SetSndBuf(u32 value) override;
Errno SetRcvBuf(u32 value) override;
Errno SetSndTimeo(u32 value) override;
Errno SetRcvTimeo(u32 value) override;
Errno SetNonBlock(bool enable) override; Errno SetNonBlock(bool enable) override;
template <typename T> Errno SetSockOpt(Network::SocketLevel level, Network::OptName option, std::span<const u8> value) override;
Errno SetSockOpt(SOCKET fd, int option, T value);
std::pair<Errno, Errno> GetPendingError() override; std::pair<Errno, Errno> GetPendingError() override;
@@ -86,7 +69,7 @@ private:
u32 send_timeout = 0; u32 send_timeout = 0;
u32 receive_timeout = 0; u32 receive_timeout = 0;
bool is_bound = false; bool is_bound = false;
SockAddrIn local_endpoint{}; Network::SockAddrIn local_endpoint{};
bool blocking = true; bool blocking = true;
std::queue<ProxyPacket> received_packets; std::queue<ProxyPacket> received_packets;
Protocol protocol; Protocol protocol;
+470
View File
@@ -0,0 +1,470 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <string>
#include "common/common_types.h"
#include "common/common_funcs.h"
// Most of these structures are direct mappings of guest's
// expectations for these values, in other words, they're the
// values that HOS is expected to use AND handle.
namespace Network {
enum class Errno : u32 {
E_SUCCESS = 0,
E_PERM = 1,
E_NOENT = 2,
E_SRCH = 3,
E_INTR = 4,
E_IO = 5,
E_NXIO = 6,
E_2BIG = 7,
E_NOEXEC = 8,
E_BADF = 9,
E_CHILD = 10,
E_AGAIN = 11,
E_NOMEM = 12,
E_ACCES = 13,
E_FAULT = 14,
E_NOTBLK = 15,
E_BUSY = 16,
E_EXIST = 17,
E_XDEV = 18,
E_NODEV = 19,
E_NOTDIR = 20,
E_ISDIR = 21,
E_INVAL = 22,
E_NFILE = 23,
E_MFILE = 24,
E_NOTTY = 25,
E_TXTBSY = 26,
E_FBIG = 27,
E_NOSPC = 28,
E_SPIPE = 29,
E_ROFS = 30,
E_MLINK = 31,
E_PIPE = 32,
E_DOM = 33,
E_RANGE = 34,
E_DEADLK = 35,
E_NAMETOOLONG = 36,
E_NOLCK = 37,
E_NOSYS = 38,
E_NOTEMPTY = 39,
E_LOOP = 40,
E_NOMSG = 42,
E_IDRM = 43,
E_CHRNG = 44,
E_L2NSYNC = 45,
E_L3HLT = 46,
E_L3RST = 47,
E_LNRNG = 48,
E_UNATCH = 49,
E_NOCSI = 50,
E_L2HLT = 51,
E_BADE = 52,
E_BADR = 53,
E_XFULL = 54,
E_NOANO = 55,
E_BADRQC = 56,
E_BADSSL = 57,
E_BFONT = 59,
E_NOSTR = 60,
E_NODATA = 61,
E_TIME = 62,
E_NOSR = 63,
E_NONET = 64,
E_NOPKG = 65,
E_REMOTE = 66,
E_NOLINK = 67,
E_ADV = 68,
E_SRMNT = 69,
E_COMM = 70,
E_PROTO = 71,
E_MULTIHOP = 72,
E_DOTDOT = 73,
E_BADMSG = 74,
E_OVERFLOW = 75,
E_NOTUNUQ = 76,
E_BADFD = 77,
E_REMCHG = 78,
E_LIBACC = 79,
E_LIBBAD = 80,
E_LIBSCN = 81,
E_LIBMAX = 82,
E_LIBEXEC = 83,
E_ILSEQ = 84,
E_RESTART = 85,
E_STRPIPE = 86,
E_USERS = 87,
E_NOTSOCK = 88,
E_DESTADDRREQ = 89,
E_MSGSIZE = 90,
E_PROTOTYPE = 91,
E_NOPROTOOPT = 92,
E_PROTONOSUPPORT = 93,
E_SOCKTNOSUPPORT = 94,
E_OPNOTSUPP = 95,
E_PFNOSUPPORT = 96,
E_AFNOSUPPORT = 97,
E_ADDRINUSE = 98,
E_ADDRNOTAVAIL = 99,
E_NETDOWN = 100,
E_NETUNREACH = 101,
E_NETRESET = 102,
E_CONNABORTED = 103,
E_CONNRESET = 104,
E_NOBUFS = 105,
E_ISCONN = 106,
E_NOTCONN = 107,
E_SHUTDOWN = 108,
E_TOOMANYREFS = 109,
E_TIMEDOUT = 110,
E_CONNREFUSED = 111,
E_HOSTDOWN = 112,
E_HOSTUNREACH = 113,
E_ALREADY = 114,
E_INPROGRESS = 115,
E_STALE = 116,
E_UCLEAN = 117,
E_NOTNAM = 118,
E_NAVAIL = 119,
E_ISNAM = 120,
E_REMOTEIO = 121,
E_DQUOT = 122,
E_NOMEDIUM = 123,
E_MEDIUMTYPE = 124,
E_CANCELED = 125,
E_NOKEY = 126,
E_KEYEXPIRED = 127,
E_KEYREVOKED = 128,
E_KEYREJECTED = 129,
E_OWNERDEAD = 130,
E_NOTRECOVERABLE = 131,
E_RFKILL = 132,
E_HWPOISON = 133,
E_PROCLIM = 156,
};
enum class GetAddrInfoError : s32 {
SUCCESS = 0,
ADDRFAMILY = 1,
AGAIN = 2,
BADFLAGS = 3,
FAIL = 4,
FAMILY = 5,
MEMORY = 6,
NODATA = 7,
NONAME = 8,
SERVICE = 9,
SOCKTYPE = 10,
SYSTEM = 11,
BADHINTS = 12,
PROTOCOL = 13,
OVERFLOW_ = 14, // avoid name collision with Windows macro
OTHER = 15,
};
enum class Domain : u32 {
Unspecified = 0,
UNIX = 1,
INET = 2,
IMPLINK = 3,
PUP = 4,
CHAOS = 5,
NETBIOS = 6,
ISO = 7,
ECMA = 8,
DATAKIT = 9,
CCITT = 10,
SNA = 11,
DECnet = 12,
DLI = 13,
LAT = 14,
HYLINK = 15,
APPLETALK = 16,
ROUTE = 17,
LINK = 18,
COIP = 20,
CNT = 21,
IPX = 23,
SIP = 24,
ISDN = 26,
INET6 = 28,
NATM = 29,
ATM = 30,
NETGRAPH = 32,
SLOW = 33,
SCLUSTER = 34,
ARP = 35,
BLUETOOTH = 36,
IEEE80211 = 37,
NETLINK = 38,
INET_SDP = 40,
INET6_SDP = 42,
};
enum class Type : u32 {
Unspecified = 0,
STREAM = 1,
DGRAM = 2,
RAW = 3,
RDM = 4,
SEQPACKET = 5,
};
enum class Protocol : u32 {
IP = 0,
ICMP = 1,
TCP = 6,
UDP = 17,
//
IPV6 = 41,
RAW = 255,
//
HOPOPTS = 0,
IGMP = 2,
GGP = 3,
IPV4 = 4,
ST = 7,
EGP = 8,
PIGP = 9,
RCCMON = 10,
NVPII = 11,
PUP = 12,
ARGUS = 13,
EMCON = 14,
XNET = 15,
CHAOS = 16,
MUX = 18,
MEAS = 19,
HMP = 20,
PRM = 21,
IDP = 22,
TRUNK1 = 23,
TRUNK2 = 24,
LEAF1 = 25,
LEAF2 = 26,
RDP = 27,
IRTP = 28,
TP = 29,
BLT = 30,
NSP = 31,
INP = 32,
DCCP = 33,
//3PC = 34,
IDPR = 35,
XTP = 36,
DDP = 37,
CMTP = 38,
TPXX = 39,
IL = 40,
SDRP = 42,
ROUTING = 43,
FRAGMENT = 44,
IDRP = 45,
RSVP = 46,
GRE = 47,
MHRP = 48,
BHA = 49,
ESP = 50,
AH = 51,
INLSP = 52,
SWIPE = 53,
NHRP = 54,
MOBILE = 55,
TLSP = 56,
SKIP = 57,
ICMPV6 = 58,
NONE = 59,
DSTOPTS = 60,
AHIP = 61,
CFTP = 62,
HELLO = 63,
SATEXPAK = 64,
KRYPTOLAN = 65,
RVD = 66,
IPPC = 67,
ADFS = 68,
SATMON = 69,
VISA = 70,
IPCV = 71,
CPNX = 72,
CPHB = 73,
WSN = 74,
PVP = 75,
BRSATMON = 76,
ND = 77,
WBMON = 78,
WBEXPAK = 79,
EON = 80,
VMTP = 81,
SVMTP = 82,
VINES = 83,
TTP = 84,
IGP = 85,
DGP = 86,
TCF = 87,
IGRP = 88,
OSPFIGP = 89,
SRPC = 90,
LARP = 91,
MTP = 92,
AX25 = 93,
IPEIP = 94,
MICP = 95,
SCCSP = 96,
ETHERIP = 97,
ENCAP = 98,
APES = 99,
GMTP = 100,
IPCOMP = 108,
SCTP = 132,
MH = 135,
UDPLITE = 136,
HIP = 139,
SHIM6 = 140,
PIM = 103,
CARP = 112,
PGM = 113,
MPLS = 137,
PFSYNC = 240,
};
enum class SocketLevel : u32 {
IP = 0,
ICMP = 1,
TCP = 6,
UDP = 17,
CONFIG = 0xfffe,
SOCKET = 0xffff, // i.e. SOL_SOCKET
};
enum class MsgOpt : u32 {
OOB = 0x00001,
PEEK = 0x00002,
DONTROUTE = 0x00004,
EOR_ = 0x00008,
TRUNC = 0x00010,
CTRUNC = 0x00020,
WAITALL = 0x00040,
DONTWAIT = 0x00080,
EOF_ = 0x00100,
NOSIGNAL = 0x20000,
};
enum class OptName : u32 {
DEBUG = 0x0001,
ACCEPTCONN = 0x0002,
REUSEADDR = 0x0004,
KEEPALIVE = 0x0008,
DONTROUTE = 0x0010,
BROADCAST = 0x0020,
USELOOPBACK = 0x0040,
LINGER = 0x0080,
OOBINLINE = 0x0100,
REUSEPORT = 0x0200,
TIMESTAMP = 0x0400,
NOSIGPIPE = 0x0800, // at least according to libnx
ACCEPTFILER = 0x1000,
SNDBUF = 0x1001,
RCVBUF = 0x1002,
SNDTIMEO = 0x1005,
RCVTIMEO = 0x1006,
ERROR_ = 0x1007, // avoid name collision with Windows macro
ACCEPTFILTER = 0x1000,
BINTIME = 0x2000,
NO_OFFLOAD = 0x4000,
NO_DDP = 0x8000,
};
enum class TcpOptName : u32 {
NODELAY = 0x0001,
MAXSEG = 0x0002,
NOPUSH = 0x0004,
NOOPT = 0x0008,
MS5SIG = 0x0010,
INFO = 0x0020
};
enum class ShutdownHow : s32 {
RD = 0,
WR = 1,
RDWR = 2,
};
enum class FcntlCmd : s32 {
GETFL = 3,
SETFL = 4,
};
enum class FcntlFlags : u32 {
NONBLOCK = 0x004,
NONBLOCK_NX = 0x800,
// Provided for convenience
NONBLOCK_ANY = u32(NONBLOCK) | u32(NONBLOCK_NX),
};
/// Array of IPv4 address
using IPv4Address = std::array<u8, 4>;
struct SockAddrIn {
u8 len;
u8 family;
u16 portno;
IPv4Address ip;
std::array<u8, 248> zeroes;
};
static_assert(sizeof(SockAddrIn) == 0x100);
enum class PollEvents : u16 {
// Using Pascal case because IN is a macro on Windows.
IN_ = 0x0001,
PRI_ = 0x0002,
OUT_ = 0x0004,
ERR_ = 0x0008,
HUP_ = 0x0010,
NVAL = 0x0020,
RDNORM = 0x0040,
RDBAND = 0x0080,
WRBAND = 0x0100,
IGNEOF = 0x2000,
};
DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
struct PollFD {
s32 fd;
Network::PollEvents events;
Network::PollEvents revents;
};
static_assert(sizeof(PollFD) == 8);
struct Linger {
s32 onoff;
s32 linger;
};
static_assert(sizeof(Linger) == 8);
struct Timeval {
u64 tv_sec;
u64 tv_usec;
};
static_assert(sizeof(Timeval) == 16);
/// @brief Cross-platform addrinfo structure (not guest)
struct AddrInfo {
Domain family;
Type socket_type;
Protocol protocol;
SockAddrIn addr;
std::optional<std::string> canon_name;
};
} // namespace Network
+19 -54
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -30,7 +30,7 @@ public:
struct AcceptResult { struct AcceptResult {
std::unique_ptr<SocketBase> socket; std::unique_ptr<SocketBase> socket;
SockAddrIn sockaddr_in; Network::SockAddrIn sockaddr_in;
}; };
SocketBase() = default; SocketBase() = default;
@@ -46,13 +46,13 @@ public:
virtual std::pair<AcceptResult, Errno> Accept() = 0; virtual std::pair<AcceptResult, Errno> Accept() = 0;
virtual Errno Connect(SockAddrIn addr_in) = 0; virtual Errno Connect(Network::SockAddrIn addr_in) = 0;
virtual std::pair<SockAddrIn, Errno> GetPeerName() = 0; virtual std::pair<Network::SockAddrIn, Errno> GetPeerName() = 0;
virtual std::pair<SockAddrIn, Errno> GetSockName() = 0; virtual std::pair<Network::SockAddrIn, Errno> GetSockName() = 0;
virtual Errno Bind(SockAddrIn addr) = 0; virtual Errno Bind(Network::SockAddrIn addr) = 0;
virtual Errno Listen(s32 backlog) = 0; virtual Errno Listen(s32 backlog) = 0;
@@ -60,31 +60,16 @@ public:
virtual std::pair<s32, Errno> Recv(int flags, std::span<u8> message) = 0; virtual std::pair<s32, Errno> Recv(int flags, std::span<u8> message) = 0;
virtual std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) = 0; virtual std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) = 0;
virtual std::pair<s32, Errno> Send(std::span<const u8> message, int flags) = 0; virtual std::pair<s32, Errno> Send(std::span<const u8> message, int flags) = 0;
virtual std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, virtual std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, const Network::SockAddrIn* addr) = 0;
const SockAddrIn* addr) = 0;
virtual Errno SetLinger(bool enable, u32 linger) = 0;
virtual Errno SetReuseAddr(bool enable) = 0;
virtual Errno SetKeepAlive(bool enable) = 0;
virtual Errno SetBroadcast(bool enable) = 0;
virtual Errno SetSndBuf(u32 value) = 0;
virtual Errno SetRcvBuf(u32 value) = 0;
virtual Errno SetSndTimeo(u32 value) = 0;
virtual Errno SetRcvTimeo(u32 value) = 0;
virtual Errno SetNonBlock(bool enable) = 0; virtual Errno SetNonBlock(bool enable) = 0;
virtual Errno SetSockOpt(Network::SocketLevel level, Network::OptName option, std::span<const u8> value) = 0;
virtual std::pair<Errno, Errno> GetPendingError() = 0; virtual std::pair<Errno, Errno> GetPendingError() = 0;
virtual bool IsOpened() const = 0; virtual bool IsOpened() const = 0;
@@ -95,7 +80,6 @@ public:
return fd; return fd;
} }
protected:
SOCKET fd = INVALID_SOCKET; SOCKET fd = INVALID_SOCKET;
}; };
@@ -114,13 +98,13 @@ public:
std::pair<AcceptResult, Errno> Accept() override; std::pair<AcceptResult, Errno> Accept() override;
Errno Connect(SockAddrIn addr_in) override; Errno Connect(Network::SockAddrIn addr_in) override;
std::pair<SockAddrIn, Errno> GetPeerName() override; std::pair<Network::SockAddrIn, Errno> GetPeerName() override;
std::pair<SockAddrIn, Errno> GetSockName() override; std::pair<Network::SockAddrIn, Errno> GetSockName() override;
Errno Bind(SockAddrIn addr) override; Errno Bind(Network::SockAddrIn addr) override;
Errno Listen(s32 backlog) override; Errno Listen(s32 backlog) override;
@@ -128,38 +112,19 @@ public:
std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override; std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override;
std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) override; std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) override;
std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override; std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override;
std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, std::pair<s32, Errno> SendTo(u32 flags, std::span<const u8> message, const Network::SockAddrIn* addr) override;
const SockAddrIn* addr) override;
Errno SetLinger(bool enable, u32 linger) override;
Errno SetReuseAddr(bool enable) override;
Errno SetKeepAlive(bool enable) override;
Errno SetBroadcast(bool enable) override;
Errno SetSndBuf(u32 value) override;
Errno SetRcvBuf(u32 value) override;
Errno SetSndTimeo(u32 value) override;
Errno SetRcvTimeo(u32 value) override;
Errno SetNonBlock(bool enable) override; Errno SetNonBlock(bool enable) override;
template <typename T> Errno SetSockOpt(Network::SocketLevel level, Network::OptName option, std::span<const u8> value) override;
Errno SetSockOpt(SOCKET fd, int option, T value);
std::pair<Errno, Errno> GetPendingError() override; std::pair<Errno, Errno> GetPendingError() override;
template <typename T> Errno GetSockOpt(Network::SocketLevel level, Network::OptName optname, std::span<u8> value);
std::pair<T, Errno> GetSockOpt(SOCKET fd, int option);
bool IsOpened() const override; bool IsOpened() const override;
@@ -169,6 +134,6 @@ private:
bool is_non_blocking = false; bool is_non_blocking = false;
}; };
std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout); std::pair<s32, Errno> Poll(std::span<Network::HostPollFD> poll_fds, s32 timeout);
} // namespace Network } // namespace Network
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
@@ -12,7 +12,7 @@
#include <vector> #include <vector>
#include "common/announce_multiplayer_room.h" #include "common/announce_multiplayer_room.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "network/verify_user.h" #include "network/verify_user.h"
namespace Network { namespace Network {
+8 -4
View File
@@ -10,7 +10,7 @@
#include <thread> #include <thread>
#include "common/assert.h" #include "common/assert.h"
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "enet/enet.h" #include "enet/enet.h"
#include "network/packet.h" #include "network/packet.h"
#include "network/room_member.h" #include "network/room_member.h"
@@ -358,19 +358,23 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
// Parse the ProxyPacket from the packet // Parse the ProxyPacket from the packet
u8 local_family; u8 local_family;
packet.Read(local_family); packet.Read(local_family);
proxy_packet.local_endpoint.family = static_cast<Domain>(local_family); proxy_packet.local_endpoint.len = 16;
proxy_packet.local_endpoint.family = u8(Network::Domain(local_family));
packet.Read(proxy_packet.local_endpoint.ip); packet.Read(proxy_packet.local_endpoint.ip);
packet.Read(proxy_packet.local_endpoint.portno); packet.Read(proxy_packet.local_endpoint.portno);
proxy_packet.local_endpoint.zeroes = {};
u8 remote_family; u8 remote_family;
packet.Read(remote_family); packet.Read(remote_family);
proxy_packet.remote_endpoint.family = static_cast<Domain>(remote_family); proxy_packet.remote_endpoint.len = 16;
proxy_packet.remote_endpoint.family = u8(Network::Domain(remote_family));
packet.Read(proxy_packet.remote_endpoint.ip); packet.Read(proxy_packet.remote_endpoint.ip);
packet.Read(proxy_packet.remote_endpoint.portno); packet.Read(proxy_packet.remote_endpoint.portno);
proxy_packet.local_endpoint.zeroes = {};
u8 protocol_type; u8 protocol_type;
packet.Read(protocol_type); packet.Read(protocol_type);
proxy_packet.protocol = static_cast<Protocol>(protocol_type); proxy_packet.protocol = Network::Protocol(protocol_type);
packet.Read(proxy_packet.broadcast); packet.Read(proxy_packet.broadcast);
packet.Read(proxy_packet.data); packet.Read(proxy_packet.data);
+4 -4
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
@@ -12,7 +12,7 @@
#include <vector> #include <vector>
#include "common/announce_multiplayer_room.h" #include "common/announce_multiplayer_room.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/socket_types.h" #include "core/internal_network/socket_types.h"
#include "network/room.h" #include "network/room.h"
namespace Network { namespace Network {
@@ -39,8 +39,8 @@ struct LDNPacket {
/// Information about the received proxy packets. /// Information about the received proxy packets.
struct ProxyPacket { struct ProxyPacket {
SockAddrIn local_endpoint; Network::SockAddrIn local_endpoint;
SockAddrIn remote_endpoint; Network::SockAddrIn remote_endpoint;
Protocol protocol; Protocol protocol;
bool broadcast; bool broadcast;
std::vector<u8> data; std::vector<u8> data;
@@ -150,7 +150,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA " tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
"can produce a more stable picture in lower resolutions.")); "can produce a more stable picture in lower resolutions."));
INSERT(Settings, post_shader_chain, QString(), QString());
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
tr("The method used to render the window in fullscreen.\nBorderless offers the best " tr("The method used to render the window in fullscreen.\nBorderless offers the best "
"compatibility with the on-screen keyboard that some games request for " "compatibility with the on-screen keyboard that some games request for "
+10 -5
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,16 +15,18 @@ TEST_CASE("Network::Errors", "[core]") {
Network::Socket socks[2]; Network::Socket socks[2];
for (Network::Socket& sock : socks) { for (Network::Socket& sock : socks) {
REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM, REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM,
Network::Protocol::TCP) == Network::Errno::SUCCESS); Network::Protocol::TCP) == Network::Errno::E_SUCCESS);
} }
Network::SockAddrIn addr{ Network::SockAddrIn addr{
Network::Domain::INET, 16,
{127, 0, 0, 1}, u8(Network::Domain::INET),
1, // hopefully nobody running this test has something listening on port 1 1, // hopefully nobody running this test has something listening on port 1
{127, 0, 0, 1},
{},
}; };
REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED); REQUIRE(socks[0].Connect(addr) == Network::Errno::E_CONNREFUSED);
std::vector<u8> message{1, 2, 3, 4}; std::vector<u8> message{1, 2, 3, 4};
REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN); REQUIRE(socks[1].Recv(0, message).second == Network::Errno::E_NOTCONN);
} }
-15
View File
@@ -299,21 +299,6 @@ if (ENABLE_LSFG)
target_compile_definitions(video_core PUBLIC HAS_LSFG) target_compile_definitions(video_core PUBLIC HAS_LSFG)
endif() endif()
if (ENABLE_RESHADE)
target_sources(video_core PRIVATE
post_processing/fx_chain.cpp
post_processing/fx_chain.h
post_processing/fx_compile.cpp
post_processing/fx_compile.h
post_processing/fx_effect.cpp
post_processing/fx_effect.h
renderer_vulkan/present/post_process.cpp
renderer_vulkan/present/post_process.h
)
target_link_libraries(video_core PRIVATE reshadefx::reshadefx)
target_compile_definitions(video_core PUBLIC HAS_RESHADE)
endif()
if (ENABLE_OPENGL) if (ENABLE_OPENGL)
target_sources(video_core PRIVATE target_sources(video_core PRIVATE
renderer_opengl/present/filters.cpp renderer_opengl/present/filters.cpp
-293
View File
@@ -1,293 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstdlib>
#include <fmt/format.h>
#include "common/settings.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
namespace VideoCore {
namespace {
std::vector<std::string_view> Split(std::string_view value, char separator) {
std::vector<std::string_view> out;
size_t start = 0;
while (start <= value.size()) {
size_t end = value.find(separator, start);
if (end == std::string_view::npos) {
end = value.size();
}
out.push_back(value.substr(start, end - start));
start = end + 1;
}
return out;
}
bool IsSerializableName(std::string_view value) {
return value.find_first_of(";|,=") == std::string_view::npos;
}
} // Anonymous namespace
std::vector<FxChainEntry> ParseFxChain(std::string_view value) {
std::vector<FxChainEntry> parsed;
for (const std::string_view record : Split(value, ';')) {
if (record.empty()) {
continue;
}
const auto fields = Split(record, '|');
if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) {
continue;
}
FxChainEntry entry;
entry.file = std::string(fields[0]);
entry.technique = std::string(fields[1]);
if (fields.size() >= 3) {
for (const std::string_view assignment : Split(fields[2], ',')) {
const size_t equals = assignment.find('=');
if (equals == std::string_view::npos) {
continue;
}
const std::string name(assignment.substr(0, equals));
if (name.empty()) {
continue;
}
std::array<f32, 4> components{};
size_t index = 0;
for (const std::string_view piece : Split(assignment.substr(equals + 1), '/')) {
if (index >= components.size()) {
break;
}
const std::string text(piece);
if (!text.empty()) {
components[index] = std::strtof(text.c_str(), nullptr);
}
++index;
}
entry.values.emplace(name, components);
}
}
parsed.push_back(std::move(entry));
}
return parsed;
}
std::string SerializeFxChain(std::span<const FxChainEntry> entries) {
std::string out;
for (const auto& entry : entries) {
if (!IsSerializableName(entry.file) || !IsSerializableName(entry.technique)) {
continue;
}
if (!out.empty()) {
out += ';';
}
out += entry.file;
out += '|';
out += entry.technique;
out += '|';
bool first = true;
for (const auto& [name, value] : entry.values) {
if (!IsSerializableName(name)) {
continue;
}
if (!first) {
out += ',';
}
first = false;
out += name;
out += '=';
for (size_t i = 0; i < value.size(); ++i) {
if (i > 0) {
out += '/';
}
out += fmt::format("{}", value[i]);
}
}
}
return out;
}
FxChain& FxChain::Instance() {
static FxChain instance;
return instance;
}
FxChainSnapshot FxChain::Snapshot() const {
std::scoped_lock lock{mutex};
return FxChainSnapshot{
.entries = entries,
.generation = generation.load(std::memory_order_relaxed),
};
}
std::vector<FxChainEntry> FxChain::Entries() const {
std::scoped_lock lock{mutex};
return entries;
}
size_t FxChain::Size() const {
std::scoped_lock lock{mutex};
return entries.size();
}
void FxChain::Append(std::string_view file, std::string_view technique) {
{
std::scoped_lock lock{mutex};
FxChainEntry entry;
entry.file = std::string(file);
entry.technique = std::string(technique);
entries.push_back(std::move(entry));
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Replace(size_t index, std::string_view file, std::string_view technique) {
{
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
if (entries[index].file == file && entries[index].technique == technique) {
return;
}
FxChainEntry entry;
entry.file = std::string(file);
entry.technique = std::string(technique);
entries[index] = std::move(entry);
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Remove(size_t index) {
{
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
entries.erase(entries.begin() + static_cast<std::ptrdiff_t>(index));
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Move(size_t index, int delta) {
{
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
const std::ptrdiff_t target = static_cast<std::ptrdiff_t>(index) + delta;
if (target < 0 || target >= static_cast<std::ptrdiff_t>(entries.size())) {
return;
}
std::swap(entries[index], entries[static_cast<size_t>(target)]);
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Clear() {
{
std::scoped_lock lock{mutex};
entries.clear();
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value) {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
entries[index].values[std::string(uniform)] = value;
}
std::array<f32, 4> FxChain::GetValue(size_t index, std::string_view uniform) const {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return {};
}
const auto it = entries[index].values.find(std::string(uniform));
if (it == entries[index].values.end()) {
return {};
}
return it->second;
}
std::map<std::string, std::array<f32, 4>> FxChain::EntryValues(size_t index) const {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return {};
}
return entries[index].values;
}
bool FxChain::HasValue(size_t index, std::string_view uniform) const {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return false;
}
return entries[index].values.contains(std::string(uniform));
}
void FxChain::ResetValues(size_t index) {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
entries[index].values.clear();
}
void FxChain::LoadFromSettings() {
auto loaded = ParseFxChain(Settings::values.post_shader_chain.GetValue());
std::scoped_lock lock{mutex};
entries = std::move(loaded);
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::StoreToSettings() const {
std::string serialized;
{
std::scoped_lock lock{mutex};
serialized = SerializeFxChain(entries);
}
Settings::values.post_shader_chain.SetValue(serialized);
}
void FxChain::DropUnknownEntries() {
bool changed = false;
{
std::scoped_lock lock{mutex};
const auto removed = std::remove_if(entries.begin(), entries.end(), [](const FxChainEntry& entry) {
const FxEffectDesc* effect = FindFxEffect(entry.file);
if (effect == nullptr || !effect->Valid()) {
return true;
}
return std::find(effect->techniques.begin(), effect->techniques.end(),
entry.technique) == effect->techniques.end();
});
if (removed != entries.end()) {
entries.erase(removed, entries.end());
changed = true;
}
}
if (changed) {
generation.fetch_add(1, std::memory_order_relaxed);
}
}
} // namespace VideoCore
-78
View File
@@ -1,78 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <map>
#include <mutex>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
namespace VideoCore {
struct FxChainEntry {
std::string file;
std::string technique;
std::map<std::string, std::array<f32, 4>> values;
};
struct FxChainSnapshot {
std::vector<FxChainEntry> entries;
u64 generation{};
};
std::vector<FxChainEntry> ParseFxChain(std::string_view value);
std::string SerializeFxChain(std::span<const FxChainEntry> entries);
class FxChain {
public:
static FxChain& Instance();
FxChainSnapshot Snapshot() const;
std::vector<FxChainEntry> Entries() const;
size_t Size() const;
void Append(std::string_view file, std::string_view technique);
void Replace(size_t index, std::string_view file, std::string_view technique);
void Remove(size_t index);
void Move(size_t index, int delta);
void Clear();
void SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value);
std::array<f32, 4> GetValue(size_t index, std::string_view uniform) const;
std::map<std::string, std::array<f32, 4>> EntryValues(size_t index) const;
bool HasValue(size_t index, std::string_view uniform) const;
void ResetValues(size_t index);
void LoadFromSettings();
void StoreToSettings() const;
void DropUnknownEntries();
private:
FxChain() = default;
mutable std::mutex mutex;
std::vector<FxChainEntry> entries;
std::atomic<u64> generation{1};
};
} // namespace VideoCore
@@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include <memory>
#include <set>
#include "effect_codegen.hpp"
#include "effect_parser.hpp"
#include "effect_preprocessor.hpp"
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "video_core/post_processing/fx_compile.h"
#include "video_core/post_processing/fx_effect.h"
namespace VideoCore {
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
u32 color_bit_depth) {
FxCompileResult result;
if (!Common::FS::Exists(path)) {
result.error = "Effect file not found: " + Common::FS::PathToUTF8String(path);
return result;
}
reshadefx::preprocessor preprocessor;
preprocessor.add_macro_definition("__RESHADE__", "50000");
preprocessor.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "1");
preprocessor.add_macro_definition("__RENDERER__", "0x20000");
preprocessor.add_macro_definition("__VENDOR__", "0");
preprocessor.add_macro_definition("__DEVICE__", "0");
preprocessor.add_macro_definition("__APPLICATION__", "0");
preprocessor.add_macro_definition("BUFFER_WIDTH", std::to_string(width));
preprocessor.add_macro_definition("BUFFER_HEIGHT", std::to_string(height));
preprocessor.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)");
preprocessor.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)");
preprocessor.add_macro_definition("BUFFER_COLOR_DEPTH", std::to_string(color_bit_depth));
preprocessor.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", std::to_string(color_bit_depth));
for (const auto& include : GetFxIncludePaths(path)) {
preprocessor.add_include_path(include);
}
if (!preprocessor.append_file(path)) {
result.error = preprocessor.errors();
if (result.error.empty()) {
result.error = "Failed to preprocess " + Common::FS::PathToUTF8String(path);
}
return result;
}
std::unique_ptr<reshadefx::codegen> backend(
reshadefx::create_codegen_spirv(true, false, false, false, true));
reshadefx::parser parser;
if (!parser.parse(preprocessor.output(), backend.get())) {
result.error = parser.errors();
if (result.error.empty()) {
result.error = "Failed to parse " + Common::FS::PathToUTF8String(path);
}
return result;
}
result.module = backend->module();
std::set<std::string> wanted;
for (const auto& technique : result.module.techniques) {
for (const auto& pass : technique.passes) {
if (!pass.vs_entry_point.empty()) {
wanted.insert(pass.vs_entry_point);
}
if (!pass.ps_entry_point.empty()) {
wanted.insert(pass.ps_entry_point);
}
}
}
for (const auto& name : wanted) {
std::string binary;
std::string assembly;
std::string errors;
if (!backend->assemble_code_for_entry_point(name, binary, assembly, errors)) {
result.error = "Failed to assemble entry point '" + name + "': " + errors;
return result;
}
if (binary.size() % sizeof(u32) != 0) {
result.error = "Entry point '" + name + "' produced a malformed SPIR-V module";
return result;
}
std::vector<u32> words(binary.size() / sizeof(u32));
std::memcpy(words.data(), binary.data(), binary.size());
result.entry_points.emplace(name, std::move(words));
}
if (result.entry_points.empty()) {
result.error = "Effect declares no usable entry points";
}
return result;
}
} // namespace VideoCore
@@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <filesystem>
#include <map>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "effect_module.hpp"
namespace VideoCore {
struct FxCompileResult {
reshadefx::effect_module module;
std::map<std::string, std::vector<u32>> entry_points;
std::string error;
bool Succeeded() const {
return error.empty() && !entry_points.empty();
}
};
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
u32 color_bit_depth);
} // namespace VideoCore
@@ -1,283 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "video_core/post_processing/fx_compile.h"
#include "video_core/post_processing/fx_effect.h"
namespace VideoCore {
namespace {
constexpr u32 CATALOG_PROBE_WIDTH = 1280;
constexpr u32 CATALOG_PROBE_HEIGHT = 720;
constexpr u32 CATALOG_PROBE_DEPTH = 8;
std::vector<FxEffectDesc> catalog;
const reshadefx::annotation* FindAnnotation(const std::vector<reshadefx::annotation>& annotations,
std::string_view name) {
const auto it = std::find_if(annotations.begin(), annotations.end(),
[&](const reshadefx::annotation& a) { return a.name == name; });
if (it == annotations.end()) {
return nullptr;
}
return &*it;
}
std::string AnnotationString(const std::vector<reshadefx::annotation>& annotations,
std::string_view name) {
const reshadefx::annotation* a = FindAnnotation(annotations, name);
if (a == nullptr) {
return std::string();
}
return a->value.string_data;
}
bool AnnotationFloat(const std::vector<reshadefx::annotation>& annotations, std::string_view name,
f32& out) {
const reshadefx::annotation* a = FindAnnotation(annotations, name);
if (a == nullptr) {
return false;
}
if (a->type.is_floating_point()) {
out = a->value.as_float[0];
return true;
}
if (a->type.is_integral()) {
out = static_cast<f32>(a->value.as_int[0]);
return true;
}
return false;
}
FxUiType ParseUiType(std::string_view value) {
if (value == "slider") {
return FxUiType::Slider;
}
if (value == "drag") {
return FxUiType::Drag;
}
if (value == "combo") {
return FxUiType::Combo;
}
if (value == "radio") {
return FxUiType::Radio;
}
if (value == "check" || value == "checkbox") {
return FxUiType::CheckBox;
}
if (value == "color") {
return FxUiType::Color;
}
if (value == "input") {
return FxUiType::InputBox;
}
return FxUiType::Hidden;
}
std::vector<std::string> SplitItems(const std::string& items) {
std::vector<std::string> out;
std::string current;
for (const char c : items) {
if (c == '\0') {
out.push_back(current);
current.clear();
continue;
}
current += c;
}
if (!current.empty()) {
out.push_back(current);
}
return out;
}
FxUniformDesc DescribeUniform(const reshadefx::uniform& info) {
FxUniformDesc desc;
desc.name = info.name;
desc.components = std::min<u32>(info.type.components(), 4);
if (info.type.is_boolean()) {
desc.kind = FxUniformKind::Boolean;
} else if (info.type.is_integral()) {
desc.kind = FxUniformKind::Integer;
} else {
desc.kind = FxUniformKind::Floating;
}
desc.label = AnnotationString(info.annotations, "ui_label");
if (desc.label.empty()) {
desc.label = info.name;
}
desc.tooltip = AnnotationString(info.annotations, "ui_tooltip");
desc.category = AnnotationString(info.annotations, "ui_category");
desc.ui_type = ParseUiType(AnnotationString(info.annotations, "ui_type"));
desc.items = SplitItems(AnnotationString(info.annotations, "ui_items"));
if (desc.kind == FxUniformKind::Boolean) {
desc.ui_min = 0.0f;
desc.ui_max = 1.0f;
desc.ui_step = 1.0f;
} else if (desc.kind == FxUniformKind::Integer) {
desc.ui_min = 0.0f;
desc.ui_max = 100.0f;
desc.ui_step = 1.0f;
}
void(AnnotationFloat(info.annotations, "ui_min", desc.ui_min));
void(AnnotationFloat(info.annotations, "ui_max", desc.ui_max));
void(AnnotationFloat(info.annotations, "ui_step", desc.ui_step));
if (desc.ui_step <= 0.0f) {
desc.ui_step = 0.01f;
if (desc.kind != FxUniformKind::Floating) {
desc.ui_step = 1.0f;
}
}
if (desc.ui_max < desc.ui_min) {
std::swap(desc.ui_min, desc.ui_max);
}
if (info.has_initializer_value) {
for (u32 i = 0; i < desc.components; ++i) {
if (desc.kind == FxUniformKind::Floating) {
desc.default_value[i] = info.initializer_value.as_float[i];
} else {
desc.default_value[i] = static_cast<f32>(info.initializer_value.as_int[i]);
}
}
}
return desc;
}
FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesystem::path& root) {
FxEffectDesc desc;
desc.file = Common::FS::PathToUTF8String(std::filesystem::relative(path, root));
desc.name = Common::FS::PathToUTF8String(path.stem());
const auto compiled =
CompileFxEffect(path, CATALOG_PROBE_WIDTH, CATALOG_PROBE_HEIGHT, CATALOG_PROBE_DEPTH);
if (!compiled.Succeeded()) {
desc.error = compiled.error;
return desc;
}
for (const auto& technique : compiled.module.techniques) {
desc.techniques.push_back(technique.name);
}
for (const auto& uniform : compiled.module.uniforms) {
FxUniformDesc uniform_desc = DescribeUniform(uniform);
if (uniform_desc.ui_type == FxUiType::Hidden) {
continue;
}
desc.uniforms.push_back(std::move(uniform_desc));
}
return desc;
}
} // Anonymous namespace
std::filesystem::path GetFxRootDirectory() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::PostShaderDir);
}
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path) {
const auto root = GetFxRootDirectory();
std::vector<std::filesystem::path> paths;
paths.push_back(effect_path.parent_path());
paths.push_back(root);
paths.push_back(root / "Shaders");
const auto last = std::unique(paths.begin(), paths.end());
paths.erase(last, paths.end());
return paths;
}
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
std::string_view source) {
const auto root = GetFxRootDirectory();
const std::filesystem::path name{source};
const std::array candidates{
effect_path.parent_path() / name,
root / "Textures" / name,
root / name,
};
for (const auto& candidate : candidates) {
if (Common::FS::Exists(candidate)) {
return candidate;
}
}
return std::filesystem::path();
}
void ReloadFxCatalog() {
catalog.clear();
const auto root = GetFxRootDirectory();
if (!Common::FS::Exists(root)) {
void(Common::FS::CreateDirs(root));
return;
}
std::vector<std::filesystem::path> effect_files;
Common::FS::IterateDirEntriesRecursively(
root,
[&](const std::filesystem::directory_entry& entry) {
if (entry.path().extension() == ".fx") {
effect_files.push_back(entry.path());
}
return true;
},
Common::FS::DirEntryFilter::File);
std::sort(effect_files.begin(), effect_files.end());
for (const auto& file : effect_files) {
FxEffectDesc desc = DescribeEffect(file, root);
if (!desc.error.empty()) {
LOG_WARNING(Render, "Post-processing effect '{}' failed to compile:\n{}", desc.file,
desc.error);
}
catalog.push_back(std::move(desc));
}
const size_t usable = std::count_if(catalog.begin(), catalog.end(),
[](const FxEffectDesc& d) { return d.Valid(); });
LOG_INFO(Render, "Loaded {} post-processing effects ({} usable)", catalog.size(), usable);
}
const std::vector<FxEffectDesc>& GetFxCatalog() {
return catalog;
}
const FxEffectDesc* FindFxEffect(std::string_view file) {
const auto it = std::find_if(catalog.begin(), catalog.end(),
[&](const FxEffectDesc& d) { return d.file == file; });
if (it == catalog.end()) {
return nullptr;
}
return &*it;
}
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name) {
const auto it = std::find_if(effect.uniforms.begin(), effect.uniforms.end(),
[&](const FxUniformDesc& u) { return u.name == name; });
if (it == effect.uniforms.end()) {
return nullptr;
}
return &*it;
}
} // namespace VideoCore
@@ -1,75 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
namespace VideoCore {
enum class FxUniformKind {
Boolean,
Integer,
Floating,
};
enum class FxUiType {
Hidden,
Slider,
Drag,
Combo,
Radio,
CheckBox,
Color,
InputBox,
};
struct FxUniformDesc {
std::string name;
std::string label;
std::string tooltip;
std::string category;
FxUniformKind kind{FxUniformKind::Floating};
u32 components{1};
FxUiType ui_type{FxUiType::Hidden};
f32 ui_min{0.0f};
f32 ui_max{1.0f};
f32 ui_step{0.01f};
std::vector<std::string> items;
std::array<f32, 4> default_value{};
};
struct FxEffectDesc {
std::string file;
std::string name;
std::vector<std::string> techniques;
std::vector<FxUniformDesc> uniforms;
std::string error;
bool Valid() const {
return error.empty() && !techniques.empty();
}
};
std::filesystem::path GetFxRootDirectory();
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path);
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
std::string_view source);
void ReloadFxCatalog();
const std::vector<FxEffectDesc>& GetFxCatalog();
const FxEffectDesc* FindFxEffect(std::string_view file);
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name);
} // namespace VideoCore
@@ -18,10 +18,6 @@
#include "video_core/renderer_vulkan/present/sgsr.h" #include "video_core/renderer_vulkan/present/sgsr.h"
#include "video_core/renderer_vulkan/present/fxaa.h" #include "video_core/renderer_vulkan/present/fxaa.h"
#include "video_core/renderer_vulkan/present/layer.h" #include "video_core/renderer_vulkan/present/layer.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#include "video_core/renderer_vulkan/present/post_process.h"
#endif
#include "video_core/renderer_vulkan/present/present_push_constants.h" #include "video_core/renderer_vulkan/present/present_push_constants.h"
#include "video_core/renderer_vulkan/present/smaa.h" #include "video_core/renderer_vulkan/present/smaa.h"
#include "video_core/renderer_vulkan/present/util.h" #include "video_core/renderer_vulkan/present/util.h"
@@ -97,9 +93,6 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
RefreshResources(device, framebuffer); RefreshResources(device, framebuffer);
SetAntiAliasPass(device); SetAntiAliasPass(device);
#ifdef HAS_RESHADE
SetPostProcessPass(device);
#endif
// Finish any pending renderpass // Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext(); scheduler.RequestOutsideRenderPassOperationContext();
@@ -122,12 +115,6 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view); smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
} }
#ifdef HAS_RESHADE
if (post_process.has_value()) {
post_process->Draw(device, scheduler, image_index, &source_image, &source_image_view);
}
#endif
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height); auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
const VkExtent2D render_extent{ const VkExtent2D render_extent{
.width = scaled_width, .width = scaled_width,
@@ -227,40 +214,6 @@ void Layer::SetAntiAliasPass(const Device& device) {
} }
} }
#ifdef HAS_RESHADE
void Layer::SetPostProcessPass(const Device& device) {
const VkExtent2D render_area{
.width = Settings::values.resolution_info.ScaleUp(raw_width),
.height = Settings::values.resolution_info.ScaleUp(raw_height),
};
const u64 generation = VideoCore::FxChain::Instance().Snapshot().generation;
if (post_process_generation == generation && post_process_extent.width == render_area.width &&
post_process_extent.height == render_area.height) {
return;
}
for (const u64 tick : resource_ticks) {
scheduler.Wait(tick);
}
post_process_generation = generation;
post_process_extent = render_area;
post_process.reset();
if (VideoCore::FxChain::Instance().Size() == 0) {
return;
}
post_process.emplace(device, memory_allocator, scheduler, image_count, render_area);
if (post_process->Empty()) {
post_process.reset();
}
}
#endif
void Layer::ReleaseRawImages() { void Layer::ReleaseRawImages() {
for (const u64 tick : resource_ticks) { for (const u64 tick : resource_ticks) {
scheduler.Wait(tick); scheduler.Wait(tick);
@@ -15,9 +15,6 @@
#include "video_core/renderer_vulkan/present/fsr.h" #include "video_core/renderer_vulkan/present/fsr.h"
#include "video_core/renderer_vulkan/present/sgsr.h" #include "video_core/renderer_vulkan/present/sgsr.h"
#include "video_core/renderer_vulkan/present/fxaa.h" #include "video_core/renderer_vulkan/present/fxaa.h"
#ifdef HAS_RESHADE
#include "video_core/renderer_vulkan/present/post_process.h"
#endif
#include "video_core/renderer_vulkan/present/smaa.h" #include "video_core/renderer_vulkan/present/smaa.h"
namespace Layout { namespace Layout {
@@ -69,9 +66,6 @@ private:
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer); void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
void SetAntiAliasPass(const Device& device); void SetAntiAliasPass(const Device& device);
#ifdef HAS_RESHADE
void SetPostProcessPass(const Device& device);
#endif
void ReleaseRawImages(); void ReleaseRawImages();
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
@@ -101,11 +95,6 @@ private:
Settings::AntiAliasing anti_alias_setting{}; Settings::AntiAliasing anti_alias_setting{};
std::variant<std::monostate, FXAA, SMAA> anti_alias{}; std::variant<std::monostate, FXAA, SMAA> anti_alias{};
std::variant<std::monostate, SGSR, FSR> sr_filter{}; std::variant<std::monostate, SGSR, FSR> sr_filter{};
#ifdef HAS_RESHADE
std::optional<PostProcessChain> post_process{};
u64 post_process_generation{};
VkExtent2D post_process_extent{};
#endif
std::vector<u64> resource_ticks{}; std::vector<u64> resource_ticks{};
}; };
@@ -1,899 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <random>
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "common/logging.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_compile.h"
#include "video_core/post_processing/fx_effect.h"
#include "video_core/renderer_vulkan/present/post_process.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr VkFormat BACKBUFFER_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
constexpr size_t NO_TEXTURE = ~size_t{0};
VkFormat ToVkFormat(reshadefx::texture_format format) {
switch (format) {
case reshadefx::texture_format::r8:
return VK_FORMAT_R8_UNORM;
case reshadefx::texture_format::r16f:
return VK_FORMAT_R16_SFLOAT;
case reshadefx::texture_format::r32f:
return VK_FORMAT_R32_SFLOAT;
case reshadefx::texture_format::rg8:
return VK_FORMAT_R8G8_UNORM;
case reshadefx::texture_format::rg16:
return VK_FORMAT_R16G16_UNORM;
case reshadefx::texture_format::rg16f:
return VK_FORMAT_R16G16_SFLOAT;
case reshadefx::texture_format::rg32f:
return VK_FORMAT_R32G32_SFLOAT;
case reshadefx::texture_format::rgba8:
return VK_FORMAT_R8G8B8A8_UNORM;
case reshadefx::texture_format::rgba16:
return VK_FORMAT_R16G16B16A16_UNORM;
case reshadefx::texture_format::rgba16f:
return VK_FORMAT_R16G16B16A16_SFLOAT;
case reshadefx::texture_format::rgba32f:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case reshadefx::texture_format::rgb10a2:
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
default:
return VK_FORMAT_R8G8B8A8_UNORM;
}
}
VkSamplerAddressMode ToAddressMode(reshadefx::texture_address_mode mode) {
switch (mode) {
case reshadefx::texture_address_mode::wrap:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case reshadefx::texture_address_mode::mirror:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case reshadefx::texture_address_mode::border:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
case reshadefx::texture_address_mode::clamp:
default:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
}
}
VkBlendFactor ToBlendFactor(reshadefx::blend_factor func) {
switch (func) {
case reshadefx::blend_factor::zero:
return VK_BLEND_FACTOR_ZERO;
case reshadefx::blend_factor::source_color:
return VK_BLEND_FACTOR_SRC_COLOR;
case reshadefx::blend_factor::source_alpha:
return VK_BLEND_FACTOR_SRC_ALPHA;
case reshadefx::blend_factor::one_minus_source_color:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
case reshadefx::blend_factor::one_minus_source_alpha:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
case reshadefx::blend_factor::dest_color:
return VK_BLEND_FACTOR_DST_COLOR;
case reshadefx::blend_factor::dest_alpha:
return VK_BLEND_FACTOR_DST_ALPHA;
case reshadefx::blend_factor::one_minus_dest_color:
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
case reshadefx::blend_factor::one_minus_dest_alpha:
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
case reshadefx::blend_factor::one:
default:
return VK_BLEND_FACTOR_ONE;
}
}
VkBlendOp ToBlendOp(reshadefx::blend_op op) {
switch (op) {
case reshadefx::blend_op::subtract:
return VK_BLEND_OP_SUBTRACT;
case reshadefx::blend_op::reverse_subtract:
return VK_BLEND_OP_REVERSE_SUBTRACT;
case reshadefx::blend_op::min:
return VK_BLEND_OP_MIN;
case reshadefx::blend_op::max:
return VK_BLEND_OP_MAX;
case reshadefx::blend_op::add:
default:
return VK_BLEND_OP_ADD;
}
}
VkPrimitiveTopology ToTopology(reshadefx::primitive_topology topology) {
switch (topology) {
case reshadefx::primitive_topology::point_list:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case reshadefx::primitive_topology::line_list:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case reshadefx::primitive_topology::line_strip:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case reshadefx::primitive_topology::triangle_strip:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case reshadefx::primitive_topology::triangle_list:
default:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
}
}
vk::RenderPass CreateFxRenderPass(const Device& device, VkFormat format, bool clear) {
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL;
if (clear) {
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
}
const VkAttachmentDescription attachment{
.flags = 0,
.format = format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = load_op,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = initial_layout,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkAttachmentReference reference{
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkSubpassDescription subpass{
.flags = 0,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = 1,
.pColorAttachments = &reference,
.pResolveAttachments = nullptr,
.pDepthStencilAttachment = nullptr,
.preserveAttachmentCount = 0,
.pPreserveAttachments = nullptr,
};
return device.GetLogical().CreateRenderPass(VkRenderPassCreateInfo{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.attachmentCount = 1,
.pAttachments = &attachment,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 0,
.pDependencies = nullptr,
});
}
vk::Pipeline CreateFxPipeline(const Device& device, vk::RenderPass& renderpass,
vk::PipelineLayout& layout, VkShaderModule vertex_shader,
VkShaderModule fragment_shader,
const reshadefx::pass& pass) {
const std::array<VkPipelineShaderStageCreateInfo, 2> stages{{
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertex_shader,
.pName = pass.vs_entry_point.c_str(),
.pSpecializationInfo = nullptr,
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragment_shader,
.pName = pass.ps_entry_point.c_str(),
.pSpecializationInfo = nullptr,
},
}};
constexpr VkPipelineVertexInputStateCreateInfo vertex_input{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.vertexBindingDescriptionCount = 0,
.pVertexBindingDescriptions = nullptr,
.vertexAttributeDescriptionCount = 0,
.pVertexAttributeDescriptions = nullptr,
};
const VkPipelineInputAssemblyStateCreateInfo input_assembly{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.topology = ToTopology(pass.topology),
.primitiveRestartEnable = VK_FALSE,
};
constexpr VkPipelineViewportStateCreateInfo viewport_state{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.viewportCount = 1,
.pViewports = nullptr,
.scissorCount = 1,
.pScissors = nullptr,
};
constexpr VkPipelineRasterizationStateCreateInfo rasterization{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f,
.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f,
};
constexpr VkPipelineMultisampleStateCreateInfo multisampling{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 0.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
VkBool32 blend_enable = VK_FALSE;
if (pass.blend_enable[0]) {
blend_enable = VK_TRUE;
}
const VkPipelineColorBlendAttachmentState blending{
.blendEnable = blend_enable,
.srcColorBlendFactor = ToBlendFactor(pass.source_color_blend_factor[0]),
.dstColorBlendFactor = ToBlendFactor(pass.dest_color_blend_factor[0]),
.colorBlendOp = ToBlendOp(pass.color_blend_op[0]),
.srcAlphaBlendFactor = ToBlendFactor(pass.source_alpha_blend_factor[0]),
.dstAlphaBlendFactor = ToBlendFactor(pass.dest_alpha_blend_factor[0]),
.alphaBlendOp = ToBlendOp(pass.alpha_blend_op[0]),
.colorWriteMask = static_cast<VkColorComponentFlags>(pass.render_target_write_mask[0] & 0xF),
};
const VkPipelineColorBlendStateCreateInfo color_blend{
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
.pAttachments = &blending,
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
};
constexpr std::array dynamic_states{
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
const VkPipelineDynamicStateCreateInfo dynamic_state{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
.pDynamicStates = dynamic_states.data(),
};
return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &vertex_input,
.pInputAssemblyState = &input_assembly,
.pTessellationState = nullptr,
.pViewportState = &viewport_state,
.pRasterizationState = &rasterization,
.pMultisampleState = &multisampling,
.pDepthStencilState = nullptr,
.pColorBlendState = &color_blend,
.pDynamicState = &dynamic_state,
.layout = *layout,
.renderPass = *renderpass,
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
});
}
} // Anonymous namespace
PostProcessChain::PostProcessChain(const Device& device, MemoryAllocator& allocator,
Scheduler& scheduler, size_t image_count, VkExtent2D extent)
: m_extent(extent)
, m_image_count(u32(image_count))
{
m_start = std::chrono::steady_clock::now();
m_previous = m_start;
CreatePingPongImages(device, allocator);
m_fallback_sampler = CreateWrappedSampler(device);
m_fallback_image = CreateWrappedImage(allocator, VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM);
m_fallback_view = CreateWrappedImageView(device, m_fallback_image, VK_FORMAT_R8G8B8A8_UNORM);
if (!BuildEffects(device, allocator, scheduler)) {
m_effects.clear();
}
}
PostProcessChain::~PostProcessChain() = default;
bool PostProcessChain::Empty() const {
return m_effects.empty();
}
void PostProcessChain::CreatePingPongImages(const Device& device, MemoryAllocator& allocator) {
m_frames.resize(m_image_count);
for (auto& frame : m_frames) {
for (size_t i = 0; i < frame.images.size(); ++i) {
frame.images[i] = CreateWrappedImage(allocator, m_extent, BACKBUFFER_FORMAT);
frame.views[i] = CreateWrappedImageView(device, frame.images[i], BACKBUFFER_FORMAT);
}
}
}
bool PostProcessChain::BuildEffects(const Device& device, MemoryAllocator& allocator,
Scheduler& scheduler) {
const auto snapshot = VideoCore::FxChain::Instance().Snapshot();
if (snapshot.entries.empty()) {
return true;
}
const auto root = VideoCore::GetFxRootDirectory();
for (size_t entry_index = 0; entry_index < snapshot.entries.size(); ++entry_index) {
const auto& entry = snapshot.entries[entry_index];
const auto path = root / entry.file;
const auto compiled = VideoCore::CompileFxEffect(path, m_extent.width, m_extent.height, 8);
if (!compiled.Succeeded()) {
LOG_ERROR(Render_Vulkan, "Post-processing effect '{}' failed to compile:\n{}",
entry.file, compiled.error);
continue;
}
const auto& module = compiled.module;
const auto technique = std::find_if(
module.techniques.begin(), module.techniques.end(),
[&](const reshadefx::technique& t) { return t.name == entry.technique; });
if (technique == module.techniques.end()) {
LOG_ERROR(Render_Vulkan, "Effect '{}' has no technique '{}'", entry.file,
entry.technique);
continue;
}
Effect effect;
effect.entry_index = entry_index;
effect.file = entry.file;
effect.uniform_size = module.total_uniform_size;
for (const auto& [name, words] : compiled.entry_points) {
effect.shaders.emplace(name, CreateWrappedShaderModule(device, words));
}
for (const auto& texture : module.textures) {
Texture out;
out.name = texture.unique_name;
out.extent = VkExtent2D{texture.width, texture.height};
out.format = ToVkFormat(texture.format);
if (texture.semantic == "COLOR") {
out.is_backbuffer = true;
effect.textures.push_back(std::move(out));
continue;
}
if (texture.semantic == "DEPTH") {
effect.textures.push_back(std::move(out));
continue;
}
out.image = CreateWrappedImage(allocator, out.extent, out.format);
out.view = CreateWrappedImageView(device, out.image, out.format);
effect.textures.push_back(std::move(out));
}
for (const auto& sampler : module.samplers) {
Sampler out;
out.texture_index = NO_TEXTURE;
for (size_t i = 0; i < effect.textures.size(); ++i) {
if (effect.textures[i].name == sampler.texture_name) {
out.texture_index = i;
break;
}
}
VkFilter mag_filter = VK_FILTER_LINEAR;
VkFilter min_filter = VK_FILTER_LINEAR;
VkSamplerMipmapMode mip_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
const u32 filter = static_cast<u32>(sampler.filter);
if ((filter & 0x10) == 0) {
min_filter = VK_FILTER_NEAREST;
}
if ((filter & 0x04) == 0) {
mag_filter = VK_FILTER_NEAREST;
}
if ((filter & 0x01) == 0) {
mip_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
out.sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.magFilter = mag_filter,
.minFilter = min_filter,
.mipmapMode = mip_mode,
.addressModeU = ToAddressMode(sampler.address_u),
.addressModeV = ToAddressMode(sampler.address_v),
.addressModeW = ToAddressMode(sampler.address_w),
.mipLodBias = sampler.lod_bias,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 1.0f,
.compareEnable = VK_FALSE,
.compareOp = VK_COMPARE_OP_NEVER,
.minLod = sampler.min_lod,
.maxLod = sampler.max_lod,
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
.unnormalizedCoordinates = VK_FALSE,
});
effect.samplers.push_back(std::move(out));
}
for (const auto& uniform : module.uniforms) {
UniformWrite write;
write.name = uniform.name;
write.offset = uniform.offset;
write.components = std::min<u32>(uniform.type.components(), 4);
write.kind = UniformKind::Floating;
if (uniform.type.is_boolean()) {
write.kind = UniformKind::Boolean;
} else if (uniform.type.is_integral()) {
write.kind = UniformKind::Integer;
}
for (const auto& annotation : uniform.annotations) {
if (annotation.name != "source") {
continue;
}
const std::string& source = annotation.value.string_data;
if (source == "frametime") {
write.source = UniformSource::FrameTime;
} else if (source == "framecount") {
write.source = UniformSource::FrameCount;
} else if (source == "timer") {
write.source = UniformSource::Timer;
} else if (source == "random") {
write.source = UniformSource::Random;
} else if (source == "pingpong") {
write.source = UniformSource::PingPong;
}
}
if (uniform.has_initializer_value) {
for (u32 i = 0; i < write.components; ++i) {
if (write.kind == UniformKind::Floating) {
write.fallback[i] = uniform.initializer_value.as_float[i];
} else {
write.fallback[i] = static_cast<f32>(uniform.initializer_value.as_int[i]);
}
}
}
write.args = {0.0f, 1.0f, 1.0f, 0.0f};
for (const auto& annotation : uniform.annotations) {
if (annotation.name == "min" && annotation.type.is_floating_point()) {
write.args[0] = annotation.value.as_float[0];
}
if (annotation.name == "max" && annotation.type.is_floating_point()) {
write.args[1] = annotation.value.as_float[0];
}
if (annotation.name == "step" && annotation.type.is_floating_point()) {
write.args[2] = annotation.value.as_float[0];
}
}
write.state = write.args[0];
effect.uniforms.push_back(std::move(write));
}
effect.uniform_layout = CreateWrappedDescriptorSetLayout(
device, std::array{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
for (const auto& pass : technique->passes) {
Pass out;
out.num_vertices = pass.num_vertices;
out.clear = pass.clear_render_targets != 0;
out.target_texture = NO_TEXTURE;
out.extent = m_extent;
const std::string& target = pass.render_target_names[0];
if (target.empty()) {
out.writes_backbuffer = true;
} else {
for (size_t i = 0; i < effect.textures.size(); ++i) {
if (effect.textures[i].name == target) {
out.target_texture = i;
out.extent = effect.textures[i].extent;
break;
}
}
if (out.target_texture == NO_TEXTURE) {
LOG_WARNING(Render_Vulkan, "Effect '{}' pass targets unknown texture '{}'",
entry.file, target);
out.writes_backbuffer = true;
}
}
if (pass.viewport_width != 0 && pass.viewport_height != 0) {
out.extent = VkExtent2D{pass.viewport_width, pass.viewport_height};
}
VkFormat target_format = BACKBUFFER_FORMAT;
if (!out.writes_backbuffer) {
target_format = effect.textures[out.target_texture].format;
}
u32 binding_count = 0;
for (const auto& binding : pass.sampler_bindings) {
SamplerBinding entry_binding;
entry_binding.binding = binding.entry_point_binding;
entry_binding.sampler_index = binding.index;
out.sampler_bindings.push_back(entry_binding);
binding_count = std::max(binding_count, binding.entry_point_binding + 1);
}
const std::vector<VkDescriptorType> sampler_types(
std::max<size_t>(binding_count, 1), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
out.sampler_layout = CreateWrappedDescriptorSetLayout(
device, sampler_types, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
const std::array set_layouts{*effect.uniform_layout, *out.sampler_layout};
out.pipeline_layout =
device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.setLayoutCount = static_cast<u32>(set_layouts.size()),
.pSetLayouts = set_layouts.data(),
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
});
const auto vertex_shader = effect.shaders.find(pass.vs_entry_point);
const auto fragment_shader = effect.shaders.find(pass.ps_entry_point);
if (vertex_shader == effect.shaders.end() ||
fragment_shader == effect.shaders.end()) {
LOG_WARNING(Render_Vulkan, "Effect '{}' pass references a missing entry point",
entry.file);
continue;
}
out.renderpass = CreateFxRenderPass(device, target_format, out.clear);
out.pipeline = CreateFxPipeline(device, out.renderpass, out.pipeline_layout,
*vertex_shader->second, *fragment_shader->second, pass);
if (out.writes_backbuffer) {
out.backbuffer_slot = static_cast<u32>(effect.backbuffer_pass_count % 2);
++effect.backbuffer_pass_count;
for (u32 image = 0; image < m_image_count; ++image) {
for (size_t slot = 0; slot < 2; ++slot) {
out.framebuffers.push_back(CreateWrappedFramebuffer(
device, out.renderpass, m_frames[image].views[slot], out.extent));
}
}
} else {
out.framebuffers.push_back(
CreateWrappedFramebuffer(device, out.renderpass,
effect.textures[out.target_texture].view, out.extent));
}
effect.passes.push_back(std::move(out));
}
if (effect.passes.empty()) {
LOG_WARNING(Render_Vulkan, "Effect '{}' technique '{}' has no passes", entry.file,
entry.technique);
continue;
}
const u32 buffer_size = std::max<u32>(effect.uniform_size, 4);
for (u32 i = 0; i < m_image_count; ++i) {
effect.uniform_buffers.push_back(
CreateWrappedBuffer(allocator, buffer_size, MemoryUsage::Upload));
}
size_t sampler_descriptor_count = 0;
size_t sampler_set_count = 0;
for (const auto& pass : effect.passes) {
sampler_descriptor_count +=
m_image_count * std::max<size_t>(pass.sampler_bindings.size(), 1);
sampler_set_count += m_image_count;
}
effect.descriptor_pool = CreateWrappedDescriptorPool(
device, m_image_count + sampler_descriptor_count, m_image_count + sampler_set_count,
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
const std::vector<VkDescriptorSetLayout> uniform_layouts(m_image_count,
*effect.uniform_layout);
effect.uniform_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, uniform_layouts);
for (auto& pass : effect.passes) {
const std::vector<VkDescriptorSetLayout> layouts(m_image_count, *pass.sampler_layout);
pass.sampler_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, layouts);
}
m_effects.push_back(std::move(effect));
}
return true;
}
void PostProcessChain::PrepareImages(const Device& device, Scheduler& scheduler) {
if (m_images_ready) {
return;
}
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
ClearColorImage(cmdbuf, *m_fallback_image);
for (auto& frame : m_frames) {
for (auto& image : frame.images) {
ClearColorImage(cmdbuf, *image);
}
}
for (auto& effect : m_effects) {
for (auto& texture : effect.textures) {
if (texture.image) {
ClearColorImage(cmdbuf, *texture.image);
}
}
}
});
scheduler.Finish();
m_images_ready = true;
}
void PostProcessChain::UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds) {
if (effect.uniform_size == 0) {
return;
}
static thread_local std::mt19937 rng{std::random_device{}()};
std::vector<u8> staging(effect.uniform_size, 0);
const f32 elapsed =
std::chrono::duration<f32>(std::chrono::steady_clock::now() - m_start).count();
const auto overrides = VideoCore::FxChain::Instance().EntryValues(effect.entry_index);
for (auto& uniform : effect.uniforms) {
std::array<f32, 4> value = uniform.fallback;
const auto override = overrides.find(uniform.name);
if (override != overrides.end()) {
value = override->second;
}
switch (uniform.source) {
case UniformSource::FrameTime:
value[0] = delta_seconds * 1000.0f;
break;
case UniformSource::FrameCount:
value[0] = static_cast<f32>(m_frame_count);
break;
case UniformSource::Timer:
value[0] = elapsed * 1000.0f;
break;
case UniformSource::Random: {
const int low = static_cast<int>(uniform.args[0]);
int high = static_cast<int>(uniform.args[1]);
if (high <= low) {
high = low + 1;
}
std::uniform_int_distribution<int> dist(low, high);
value[0] = static_cast<f32>(dist(rng));
break;
}
case UniformSource::PingPong: {
const f32 min_value = uniform.args[0];
f32 max_value = uniform.args[1];
if (max_value <= min_value) {
max_value = min_value + 1.0f;
}
f32 step = uniform.args[2];
if (step == 0.0f) {
step = 1.0f;
}
uniform.state += uniform.direction * step * delta_seconds;
if (uniform.state >= max_value) {
uniform.state = max_value;
uniform.direction = -1.0f;
}
if (uniform.state <= min_value) {
uniform.state = min_value;
uniform.direction = 1.0f;
}
value[0] = uniform.state;
value[1] = uniform.direction;
break;
}
case UniformSource::Value:
default:
break;
}
for (u32 i = 0; i < uniform.components; ++i) {
const size_t offset = uniform.offset + i * sizeof(u32);
if (offset + sizeof(u32) > staging.size()) {
break;
}
if (uniform.kind == UniformKind::Floating) {
const f32 element = value[i];
std::memcpy(staging.data() + offset, &element, sizeof(f32));
} else {
const s32 element = static_cast<s32>(value[i]);
std::memcpy(staging.data() + offset, &element, sizeof(s32));
}
}
}
const std::span<u8> mapped = effect.uniform_buffers[image_index].Mapped();
if (mapped.size() >= staging.size()) {
std::memcpy(mapped.data(), staging.data(), staging.size());
effect.uniform_buffers[image_index].Flush();
}
}
void PostProcessChain::UpdateDescriptors(const Device& device, Effect& effect, Pass& pass,
size_t image_index, VkImageView backbuffer_view) {
std::vector<VkDescriptorImageInfo> image_infos;
std::vector<VkWriteDescriptorSet> writes;
image_infos.reserve(pass.sampler_bindings.size() + 1);
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
for (const auto& binding : pass.sampler_bindings) {
VkImageView view = *m_fallback_view;
VkSampler handle = *m_fallback_sampler;
if (binding.sampler_index < effect.samplers.size()) {
const Sampler& sampler = effect.samplers[binding.sampler_index];
if (sampler.sampler) {
handle = *sampler.sampler;
}
if (sampler.texture_index != NO_TEXTURE) {
const Texture& texture = effect.textures[sampler.texture_index];
if (texture.is_backbuffer) {
view = backbuffer_view;
} else if (texture.view) {
view = *texture.view;
}
}
}
writes.push_back(
CreateWriteDescriptorSet(image_infos, handle, view, sampler_set, binding.binding));
}
const VkDescriptorBufferInfo buffer_info{
.buffer = *effect.uniform_buffers[image_index],
.offset = 0,
.range = VK_WHOLE_SIZE,
};
writes.push_back(VkWriteDescriptorSet{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = effect.uniform_sets[image_index],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pImageInfo = nullptr,
.pBufferInfo = &buffer_info,
.pTexelBufferView = nullptr,
});
device.GetLogical().UpdateDescriptorSets(writes, {});
}
void PostProcessChain::Draw(const Device& device, Scheduler& scheduler, size_t image_index,
VkImage* inout_image, VkImageView* inout_image_view) {
if (m_effects.empty()) {
return;
}
PrepareImages(device, scheduler);
const auto now = std::chrono::steady_clock::now();
const f32 delta_seconds = std::chrono::duration<f32>(now - m_previous).count();
m_previous = now;
++m_frame_count;
FrameImages& frame = m_frames[image_index];
VkImage current_image = *inout_image;
VkImageView current_view = *inout_image_view;
u32 slot = 0;
for (auto& effect : m_effects) {
UpdateUniforms(effect, image_index, delta_seconds);
for (size_t pass_index = 0; pass_index < effect.passes.size(); ++pass_index) {
Pass& pass = effect.passes[pass_index];
UpdateDescriptors(device, effect, pass, image_index, current_view);
VkFramebuffer framebuffer{};
VkImage target_image{};
if (pass.writes_backbuffer) {
const u32 target_slot = (slot + 1) % 2;
framebuffer = *pass.framebuffers[image_index * 2 + target_slot];
target_image = *frame.images[target_slot];
} else {
framebuffer = *pass.framebuffers[0];
target_image = *effect.textures[pass.target_texture].image;
}
const VkImage source_image = current_image;
const VkRenderPass renderpass = *pass.renderpass;
const VkPipeline pipeline = *pass.pipeline;
const VkPipelineLayout layout = *pass.pipeline_layout;
const VkDescriptorSet uniform_set = effect.uniform_sets[image_index];
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
const VkExtent2D extent = pass.extent;
const u32 vertices = pass.num_vertices;
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([=](vk::CommandBuffer cmdbuf) {
TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
BeginRenderPass(cmdbuf, renderpass, framebuffer, extent);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0,
std::array{uniform_set, sampler_set}, {});
cmdbuf.Draw(vertices, 1, 0, 0);
cmdbuf.EndRenderPass();
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
});
if (pass.writes_backbuffer) {
slot = (slot + 1) % 2;
current_image = *frame.images[slot];
current_view = *frame.views[slot];
}
}
}
*inout_image = current_image;
*inout_image_view = current_view;
}
} // namespace Vulkan
@@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <map>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
class Device;
class Scheduler;
class PostProcessChain {
public:
explicit PostProcessChain(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler,
size_t image_count, VkExtent2D extent);
~PostProcessChain();
void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image,
VkImageView* inout_image_view);
bool Empty() const;
private:
enum class UniformKind : u32 {
Boolean,
Integer,
Floating,
};
enum class UniformSource : u32 {
Value,
FrameTime,
FrameCount,
Timer,
Random,
PingPong,
};
struct UniformWrite {
std::string name;
u32 offset{};
u32 components{};
UniformKind kind{UniformKind::Floating};
UniformSource source{UniformSource::Value};
std::array<f32, 4> fallback{};
std::array<f32, 4> args{};
f32 state{};
f32 direction{1.0f};
};
struct Texture {
std::string name;
vk::Image image{};
vk::ImageView view{};
VkExtent2D extent{};
VkFormat format{};
bool is_backbuffer{};
};
struct Sampler {
vk::Sampler sampler{};
size_t texture_index{};
};
struct SamplerBinding {
u32 binding{};
size_t sampler_index{};
};
struct Pass {
vk::RenderPass renderpass{};
vk::Pipeline pipeline{};
vk::DescriptorSetLayout sampler_layout{};
vk::PipelineLayout pipeline_layout{};
vk::DescriptorSets sampler_sets{};
std::vector<SamplerBinding> sampler_bindings{};
std::vector<vk::Framebuffer> framebuffers{};
size_t target_texture{};
VkExtent2D extent{};
u32 num_vertices{3};
bool clear{};
bool writes_backbuffer{};
u32 backbuffer_slot{};
};
struct Effect {
size_t entry_index{};
std::string file{};
std::map<std::string, vk::ShaderModule> shaders{};
std::vector<Texture> textures{};
std::vector<Sampler> samplers{};
std::vector<Pass> passes{};
std::vector<UniformWrite> uniforms{};
u32 uniform_size{};
std::vector<vk::Buffer> uniform_buffers{};
vk::DescriptorSetLayout uniform_layout{};
vk::DescriptorPool descriptor_pool{};
vk::DescriptorSets uniform_sets{};
size_t backbuffer_pass_count{};
u32 backbuffer_slots{1};
};
struct FrameImages {
std::array<vk::Image, 2> images{};
std::array<vk::ImageView, 2> views{};
};
bool BuildEffects(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler);
void CreatePingPongImages(const Device& device, MemoryAllocator& allocator);
void PrepareImages(const Device& device, Scheduler& scheduler);
void UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds);
void UpdateDescriptors(const Device& device, Effect& effect, Pass& pass, size_t image_index,
VkImageView backbuffer_view);
const VkExtent2D m_extent;
const u32 m_image_count;
std::vector<Effect> m_effects{};
std::vector<FrameImages> m_frames{};
vk::Sampler m_fallback_sampler{};
vk::Image m_fallback_image{};
vk::ImageView m_fallback_view{};
std::chrono::steady_clock::time_point m_start{};
std::chrono::steady_clock::time_point m_previous{};
u64 m_frame_count{};
bool m_images_ready{};
};
} // namespace Vulkan
@@ -24,10 +24,6 @@
#include "video_core/gpu.h" #include "video_core/gpu.h"
#include "video_core/present.h" #include "video_core/present.h"
#include "video_core/renderer_vulkan/present/util.h" #include "video_core/renderer_vulkan/present/util.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
#endif
#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h" #include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h" #include "video_core/renderer_vulkan/vk_rasterizer.h"
@@ -187,12 +183,6 @@ try
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); }); scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
} }
#ifdef HAS_RESHADE
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().LoadFromSettings();
VideoCore::FxChain::Instance().DropUnknownEntries();
#endif
Report(); Report();
} catch (const vk::Exception& exception) { } catch (const vk::Exception& exception) {
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what()); LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
-6
View File
@@ -250,12 +250,6 @@ if (YUZU_CRASH_DUMPS)
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS) target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
endif() endif()
if (ENABLE_RESHADE)
target_sources(yuzu PRIVATE
configuration/configure_post_processing.cpp
configuration/configure_post_processing.h)
endif()
if (CXX_CLANG) if (CXX_CLANG)
target_compile_definitions(yuzu PRIVATE target_compile_definitions(yuzu PRIVATE
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>) $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
@@ -1,378 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <cmath>
#include <string>
#include <QCheckBox>
#include <QComboBox>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QSlider>
#include <QToolButton>
#include <QUrl>
#include <QVBoxLayout>
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
#include "yuzu/configuration/configure_post_processing.h"
namespace {
std::array<float, 4> CurrentValue(int index, const VideoCore::FxUniformDesc& uniform) {
auto& chain = VideoCore::FxChain::Instance();
if (chain.HasValue(static_cast<size_t>(index), uniform.name)) {
return chain.GetValue(static_cast<size_t>(index), uniform.name);
}
return uniform.default_value;
}
int SliderSteps(const VideoCore::FxUniformDesc& uniform) {
const float span = uniform.ui_max - uniform.ui_min;
const int steps = static_cast<int>(std::lround(span / uniform.ui_step));
if (steps < 1) {
return 1;
}
return steps;
}
QString FormatValue(const VideoCore::FxUniformDesc& uniform, float value) {
if (uniform.kind == VideoCore::FxUniformKind::Floating) {
return QString::number(value, 'f', 3);
}
return QString::number(static_cast<int>(std::lround(value)));
}
QString SlotLabel(const VideoCore::FxEffectDesc& effect, const std::string& technique) {
const QString name = QString::fromStdString(effect.name);
if (effect.techniques.size() == 1) {
return name;
}
return name + QStringLiteral(" · ") + QString::fromStdString(technique);
}
} // Anonymous namespace
ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(parent) {
setWindowTitle(tr("Post-Processing Effects"));
setMinimumWidth(560);
setMinimumHeight(460);
auto* root = new QVBoxLayout(this);
auto* description = new QLabel(
tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
"directory. Changes apply immediately while a game is running."),
this);
description->setWordWrap(true);
root->addWidget(description);
auto* scroll = new QScrollArea(this);
scroll->setWidgetResizable(true);
slots_container = new QWidget(scroll);
slots_layout = new QVBoxLayout(slots_container);
slots_layout->setAlignment(Qt::AlignTop);
scroll->setWidget(slots_container);
root->addWidget(scroll, 1);
auto* actions = new QHBoxLayout();
auto* add_button = new QPushButton(tr("Add Effect"), this);
connect(add_button, &QPushButton::clicked, this, [this]() {
for (const auto& effect : VideoCore::GetFxCatalog()) {
if (!effect.Valid()) {
continue;
}
VideoCore::FxChain::Instance().Append(effect.file, effect.techniques.front());
ApplyStructuralChange();
return;
}
});
actions->addWidget(add_button);
auto* reload_button = new QPushButton(tr("Reload From Disk"), this);
connect(reload_button, &QPushButton::clicked, this, [this]() {
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
ApplyStructuralChange();
});
actions->addWidget(reload_button);
auto* open_button = new QPushButton(tr("Open Folder"), this);
connect(open_button, &QPushButton::clicked, this, []() {
const auto path = VideoCore::GetFxRootDirectory();
void(Common::FS::CreateDirs(path));
QDesktopServices::openUrl(
QUrl::fromLocalFile(QString::fromStdString(Common::FS::PathToUTF8String(path))));
});
actions->addWidget(open_button);
actions->addStretch();
root->addLayout(actions);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
root->addWidget(buttons);
RebuildRows();
}
ConfigurePostProcessing::~ConfigurePostProcessing() = default;
void ConfigurePostProcessing::ApplyStructuralChange() {
VideoCore::FxChain::Instance().StoreToSettings();
RebuildRows();
}
void ConfigurePostProcessing::PopulateEffectCombo(QComboBox* combo,
const VideoCore::FxChainEntry& entry) const {
combo->clear();
int selected = -1;
for (const auto& effect : VideoCore::GetFxCatalog()) {
if (!effect.Valid()) {
continue;
}
for (const auto& technique : effect.techniques) {
const QString key = QString::fromStdString(effect.file + "|" + technique);
combo->addItem(SlotLabel(effect, technique), key);
if (effect.file == entry.file && technique == entry.technique) {
selected = combo->count() - 1;
}
}
}
if (selected >= 0) {
combo->setCurrentIndex(selected);
}
}
void ConfigurePostProcessing::BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
const VideoCore::FxUniformDesc& uniform) {
const auto value = CurrentValue(index, uniform);
const QString label = QString::fromStdString(uniform.label);
if (uniform.ui_type == VideoCore::FxUiType::CheckBox) {
auto* box = new QCheckBox(label, parent);
box->setChecked(value[0] != 0.0f);
if (!uniform.tooltip.empty()) {
box->setToolTip(QString::fromStdString(uniform.tooltip));
}
const std::string name = uniform.name;
connect(box, &QCheckBox::toggled, this, [index, name](bool checked) {
std::array<float, 4> next{};
if (checked) {
next[0] = 1.0f;
}
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
VideoCore::FxChain::Instance().StoreToSettings();
});
layout->addWidget(box);
return;
}
if (uniform.ui_type == VideoCore::FxUiType::Combo ||
uniform.ui_type == VideoCore::FxUiType::Radio) {
auto* row = new QHBoxLayout();
row->addWidget(new QLabel(label, parent));
auto* combo = new QComboBox(parent);
for (size_t i = 0; i < uniform.items.size(); ++i) {
combo->addItem(QString::fromStdString(uniform.items[i]), static_cast<int>(i));
}
if (combo->count() == 0) {
combo->addItem(tr("Enabled"), 1);
combo->addItem(tr("Disabled"), 0);
}
const int current = static_cast<int>(std::lround(value[0]));
if (current >= 0 && current < combo->count()) {
combo->setCurrentIndex(current);
}
if (!uniform.tooltip.empty()) {
combo->setToolTip(QString::fromStdString(uniform.tooltip));
}
const std::string name = uniform.name;
connect(combo, &QComboBox::currentIndexChanged, this, [index, name](int selected) {
std::array<float, 4> next{};
next[0] = static_cast<float>(selected);
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
VideoCore::FxChain::Instance().StoreToSettings();
});
row->addWidget(combo, 1);
layout->addLayout(row);
return;
}
auto* grid = new QGridLayout();
for (unsigned component = 0; component < uniform.components; ++component) {
QString component_label = label;
if (uniform.components > 1) {
component_label = label + QStringLiteral(" [%1]").arg(component);
}
auto* name_label = new QLabel(component_label, parent);
auto* value_label = new QLabel(parent);
value_label->setMinimumWidth(64);
value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
value_label->setText(FormatValue(uniform, value[component]));
auto* slider = new QSlider(Qt::Horizontal, parent);
slider->setMinimum(0);
slider->setMaximum(SliderSteps(uniform));
slider->setValue(
static_cast<int>(std::lround((value[component] - uniform.ui_min) / uniform.ui_step)));
if (!uniform.tooltip.empty()) {
slider->setToolTip(QString::fromStdString(uniform.tooltip));
}
const std::string name = uniform.name;
const auto desc = uniform;
connect(slider, &QSlider::valueChanged, this,
[index, name, desc, component, value_label](int steps) {
auto next = CurrentValue(index, desc);
next[component] = desc.ui_min + static_cast<float>(steps) * desc.ui_step;
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
value_label->setText(FormatValue(desc, next[component]));
});
connect(slider, &QSlider::sliderReleased, this,
[]() { VideoCore::FxChain::Instance().StoreToSettings(); });
grid->addWidget(name_label, static_cast<int>(component), 0);
grid->addWidget(slider, static_cast<int>(component), 1);
grid->addWidget(value_label, static_cast<int>(component), 2);
}
layout->addLayout(grid);
}
QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainEntry& entry) {
auto* group = new QGroupBox(slots_container);
auto* layout = new QVBoxLayout(group);
auto* header = new QHBoxLayout();
auto* combo = new QComboBox(group);
PopulateEffectCombo(combo, entry);
connect(combo, &QComboBox::currentIndexChanged, this, [this, index, combo](int) {
const QString key = combo->currentData().toString();
const qsizetype separator = key.indexOf(QLatin1Char('|'));
if (separator < 0) {
return;
}
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
key.left(separator).toStdString(),
key.mid(separator + 1).toStdString());
ApplyStructuralChange();
});
header->addWidget(combo, 1);
auto* up_button = new QToolButton(group);
up_button->setText(QStringLiteral(""));
up_button->setEnabled(index > 0);
connect(up_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), -1);
ApplyStructuralChange();
});
header->addWidget(up_button);
auto* down_button = new QToolButton(group);
down_button->setText(QStringLiteral(""));
down_button->setEnabled(static_cast<size_t>(index) + 1 < VideoCore::FxChain::Instance().Size());
connect(down_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), 1);
ApplyStructuralChange();
});
header->addWidget(down_button);
auto* reset_button = new QToolButton(group);
reset_button->setText(QStringLiteral(""));
reset_button->setToolTip(tr("Reset to defaults"));
connect(reset_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
ApplyStructuralChange();
});
header->addWidget(reset_button);
auto* remove_button = new QToolButton(group);
remove_button->setText(QStringLiteral(""));
connect(remove_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
ApplyStructuralChange();
});
header->addWidget(remove_button);
layout->addLayout(header);
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entry.file);
if (effect == nullptr) {
auto* missing =
new QLabel(tr("Effect '%1' was not found.").arg(QString::fromStdString(entry.file)),
group);
missing->setWordWrap(true);
layout->addWidget(missing);
return group;
}
if (!effect->error.empty()) {
auto* failed = new QLabel(
tr("Effect failed to compile:\n%1").arg(QString::fromStdString(effect->error)), group);
failed->setWordWrap(true);
layout->addWidget(failed);
return group;
}
std::string current_category;
for (const auto& uniform : effect->uniforms) {
if (uniform.category != current_category) {
current_category = uniform.category;
if (!current_category.empty()) {
auto* category = new QLabel(QString::fromStdString(current_category), group);
category->setStyleSheet(QStringLiteral("font-weight: bold;"));
layout->addWidget(category);
}
}
BuildUniformWidget(group, layout, index, uniform);
}
return group;
}
void ConfigurePostProcessing::RebuildRows() {
QLayoutItem* item = nullptr;
while ((item = slots_layout->takeAt(0)) != nullptr) {
if (item->widget() != nullptr) {
item->widget()->deleteLater();
}
delete item;
}
bool has_usable = false;
for (const auto& effect : VideoCore::GetFxCatalog()) {
if (effect.Valid()) {
has_usable = true;
break;
}
}
if (!has_usable) {
auto* empty = new QLabel(
tr("No usable ReShade FX effects were found. Place .fx files in the post_shaders "
"folder."),
slots_container);
empty->setWordWrap(true);
slots_layout->addWidget(empty);
return;
}
const auto entries = VideoCore::FxChain::Instance().Entries();
for (size_t i = 0; i < entries.size(); ++i) {
slots_layout->addWidget(BuildSlot(static_cast<int>(i), entries[i]));
}
}
@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QDialog>
class QComboBox;
class QVBoxLayout;
class QWidget;
namespace VideoCore {
struct FxChainEntry;
struct FxEffectDesc;
struct FxUniformDesc;
}
class ConfigurePostProcessing : public QDialog {
Q_OBJECT
public:
explicit ConfigurePostProcessing(QWidget* parent = nullptr);
~ConfigurePostProcessing() override;
private:
void RebuildRows();
QWidget* BuildSlot(int index, const VideoCore::FxChainEntry& entry);
void BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
const VideoCore::FxUniformDesc& uniform);
void PopulateEffectCombo(QComboBox* combo, const VideoCore::FxChainEntry& entry) const;
void ApplyStructuralChange();
QVBoxLayout* slots_layout{};
QWidget* slots_container{};
};
-6
View File
@@ -148,7 +148,6 @@
<addaction name="action_Show_Filter_Bar"/> <addaction name="action_Show_Filter_Bar"/>
<addaction name="action_Show_Status_Bar"/> <addaction name="action_Show_Status_Bar"/>
<addaction name="action_Show_Performance_Overlay"/> <addaction name="action_Show_Performance_Overlay"/>
<addaction name="action_Post_Processing_Shaders"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menu_Reset_Window_Size"/> <addaction name="menu_Reset_Window_Size"/>
<addaction name="menu_View_Debugging"/> <addaction name="menu_View_Debugging"/>
@@ -611,11 +610,6 @@
<string>Show &amp;Performance Overlay</string> <string>Show &amp;Performance Overlay</string>
</property> </property>
</action> </action>
<action name="action_Post_Processing_Shaders">
<property name="text">
<string>Post-Processing &amp;Shaders...</string>
</property>
</action>
<action name="action_Carousel_View"> <action name="action_Carousel_View">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
-24
View File
@@ -13,9 +13,6 @@
#include "common/settings_enums.h" #include "common/settings_enums.h"
#include "frontend_common/settings_generator.h" #include "frontend_common/settings_generator.h"
#include "render/performance_overlay.h" #include "render/performance_overlay.h"
#ifdef HAS_RESHADE
#include "configuration/configure_post_processing.h"
#endif
#include "updater/update_dialog.h" #include "updater/update_dialog.h"
#include "common/fs/ryujinx_compat.h" #include "common/fs/ryujinx_compat.h"
@@ -1521,11 +1518,6 @@ void MainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar); connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar); connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay); connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
#ifdef HAS_RESHADE
connect_menu(ui->action_Post_Processing_Shaders, &MainWindow::OnPostProcessingShaders);
#else
ui->action_Post_Processing_Shaders->setVisible(false);
#endif
connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720); connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720);
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900); connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
@@ -3908,22 +3900,6 @@ void MainWindow::OnTogglePerfOverlay() {
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked()); perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
} }
#ifdef HAS_RESHADE
void MainWindow::OnPostProcessingShaders() {
if (post_processing_dialog == nullptr) {
post_processing_dialog = new ConfigurePostProcessing(this);
connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() {
post_processing_dialog->deleteLater();
post_processing_dialog = nullptr;
});
}
post_processing_dialog->show();
post_processing_dialog->raise();
post_processing_dialog->activateWindow();
}
#endif
void MainWindow::OnGameListRefresh() { void MainWindow::OnGameListRefresh() {
// Resets metadata cache and reloads // Resets metadata cache and reloads
QtCommon::Game::ResetMetadata(false); QtCommon::Game::ResetMetadata(false);
-9
View File
@@ -56,9 +56,6 @@ class QSlider;
class QHBoxLayout; class QHBoxLayout;
class WaitTreeWidget; class WaitTreeWidget;
class PerformanceOverlay; class PerformanceOverlay;
#ifdef HAS_RESHADE
class ConfigurePostProcessing;
#endif
enum class GameListOpenTarget; enum class GameListOpenTarget;
enum class DumpRomFSTarget; enum class DumpRomFSTarget;
class GameListPlaceholder; class GameListPlaceholder;
@@ -395,9 +392,6 @@ private slots:
void OnToggleFilterBar(); void OnToggleFilterBar();
void OnToggleStatusBar(); void OnToggleStatusBar();
void OnTogglePerfOverlay(); void OnTogglePerfOverlay();
#ifdef HAS_RESHADE
void OnPostProcessingShaders();
#endif
void OnGameListRefresh(); void OnGameListRefresh();
void InitializeHotkeys(); void InitializeHotkeys();
void ToggleFullscreen(); void ToggleFullscreen();
@@ -502,9 +496,6 @@ private:
QTimer shutdown_timer; QTimer shutdown_timer;
OverlayDialog* shutdown_dialog{}; OverlayDialog* shutdown_dialog{};
PerformanceOverlay* perf_overlay = nullptr; PerformanceOverlay* perf_overlay = nullptr;
#ifdef HAS_RESHADE
ConfigurePostProcessing* post_processing_dialog = nullptr;
#endif
GameListPlaceholder* game_list_placeholder = nullptr; GameListPlaceholder* game_list_placeholder = nullptr;