Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit UsbSession g_usb_session;
|
||||
|
||||
}
|
||||
|
||||
Result AsyncUsbServer::Initialize(const UsbCommsInterfaceInfo *interface_info, u16 id_vendor, u16 id_product, EventReactor *reactor) {
|
||||
m_reactor = reactor;
|
||||
|
||||
/* Set up a new USB session. */
|
||||
R_TRY(g_usb_session.Initialize(interface_info, id_vendor, id_product));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void AsyncUsbServer::Finalize() {
|
||||
g_usb_session.Finalize();
|
||||
}
|
||||
|
||||
Result AsyncUsbServer::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) const {
|
||||
u32 urb_id;
|
||||
s32 waiter_idx;
|
||||
|
||||
/* If we're not configured yet, wait to become configured first. */
|
||||
if (!g_usb_session.GetConfigured()) {
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(usbDsGetStateChangeEvent())));
|
||||
R_TRY(eventClear(usbDsGetStateChangeEvent()));
|
||||
|
||||
R_THROW(haze::ResultNotConfigured());
|
||||
}
|
||||
|
||||
/* Select the appropriate endpoint and begin a transfer. */
|
||||
UsbSessionEndpoint ep = read ? UsbSessionEndpoint_Read : UsbSessionEndpoint_Write;
|
||||
R_TRY(g_usb_session.TransferAsync(ep, page, size, std::addressof(urb_id)));
|
||||
|
||||
/* Try to wait for the event. */
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(g_usb_session.GetCompletionEvent(ep))));
|
||||
|
||||
/* Return what we transferred. */
|
||||
R_RETURN(g_usb_session.GetTransferResult(ep, urb_id, out_size_transferred));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) noperspective in vec3 inTexCoord;
|
||||
layout (location = 1) flat in vec4 inFrontPal;
|
||||
layout (location = 2) flat in vec4 inBackPal;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
layout (binding = 0) uniform sampler2DArray tileset;
|
||||
|
||||
void main()
|
||||
{
|
||||
float value = texture(tileset, inTexCoord).r;
|
||||
outColor = mix(inBackPal, inFrontPal, value);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in float inTileId;
|
||||
layout (location = 1) in uvec2 inColorId;
|
||||
|
||||
layout (location = 0) out vec3 outTexCoord;
|
||||
layout (location = 1) out vec4 outFrontPal;
|
||||
layout (location = 2) out vec4 outBackPal;
|
||||
|
||||
layout (std140, binding = 0) uniform Config
|
||||
{
|
||||
vec4 dimensions;
|
||||
vec4 vertices[3];
|
||||
vec4 palettes[24];
|
||||
} u;
|
||||
|
||||
void main()
|
||||
{
|
||||
float id = float(gl_InstanceID);
|
||||
float tileRow = floor(id / u.dimensions.z);
|
||||
float tileCol = id - tileRow * u.dimensions.z;
|
||||
|
||||
vec2 basePos;
|
||||
basePos.x = 2.0 * (tileCol + 0.5) / u.dimensions.z - 1.0;
|
||||
basePos.y = 2.0 * (1.0 - (tileRow + 0.5) / u.dimensions.w) - 1.0;
|
||||
|
||||
vec2 vtxData = u.vertices[gl_VertexID].xy;
|
||||
vec2 scale = vec2(1.0) / u.dimensions.zw;
|
||||
gl_Position.xy = vtxData * scale + basePos;
|
||||
gl_Position.zw = vec2(0.5, 1.0);
|
||||
|
||||
outTexCoord = vec3(u.vertices[gl_VertexID].zw, inTileId);
|
||||
outFrontPal = u.palettes[inColorId.x];
|
||||
outBackPal = u.palettes[inColorId.y];
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit SetSysSerialNumber g_serial_number = {};
|
||||
constinit SetSysFirmwareVersion g_firmware_version = {};
|
||||
|
||||
}
|
||||
|
||||
Result LoadDeviceProperties() {
|
||||
/* Initialize set:sys. */
|
||||
R_TRY(setsysInitialize());
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { setsysExit(); };
|
||||
|
||||
/* Get the serial number and firmware version. */
|
||||
R_TRY(setsysGetSerialNumber(std::addressof(g_serial_number)));
|
||||
R_TRY(setsysGetFirmwareVersion(std::addressof(g_firmware_version)));
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
const char *GetSerialNumber() {
|
||||
return g_serial_number.number;
|
||||
}
|
||||
|
||||
const char *GetFirmwareVersion() {
|
||||
return g_firmware_version.display_version;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
bool EventReactor::AddConsumer(EventConsumer *consumer, Waiter waiter) {
|
||||
HAZE_ASSERT(m_num_wait_objects + 1 <= svc::ArgumentHandleCountMax);
|
||||
|
||||
/* Add to the end of the list. */
|
||||
m_consumers[m_num_wait_objects] = consumer;
|
||||
m_waiters[m_num_wait_objects] = waiter;
|
||||
m_num_wait_objects++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventReactor::RemoveConsumer(EventConsumer *consumer) {
|
||||
s32 output_index = 0;
|
||||
|
||||
/* Remove the consumer. */
|
||||
for (s32 input_index = 0; input_index < m_num_wait_objects; input_index++) {
|
||||
if (m_consumers[input_index] == consumer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output_index != input_index) {
|
||||
m_consumers[output_index] = m_consumers[input_index];
|
||||
m_waiters[output_index] = m_waiters[input_index];
|
||||
}
|
||||
|
||||
output_index++;
|
||||
}
|
||||
|
||||
m_num_wait_objects = output_index;
|
||||
}
|
||||
|
||||
Result EventReactor::WaitForImpl(s32 *out_arg_waiter, const Waiter *arg_waiters, s32 num_arg_waiters) {
|
||||
HAZE_ASSERT(0 < num_arg_waiters && num_arg_waiters <= svc::ArgumentHandleCountMax);
|
||||
HAZE_ASSERT(m_num_wait_objects + num_arg_waiters <= svc::ArgumentHandleCountMax);
|
||||
|
||||
while (true) {
|
||||
/* Check if we should wait for an event. */
|
||||
R_TRY(m_result);
|
||||
|
||||
/* Insert waiters from argument list. */
|
||||
for (s32 i = 0; i < num_arg_waiters; i++) {
|
||||
m_waiters[i + m_num_wait_objects] = arg_waiters[i];
|
||||
}
|
||||
|
||||
s32 idx;
|
||||
HAZE_R_ABORT_UNLESS(waitObjects(std::addressof(idx), m_waiters, m_num_wait_objects + num_arg_waiters, svc::WaitInfinite));
|
||||
|
||||
/* If a waiter in the argument list was signaled, return it. */
|
||||
if (idx >= m_num_wait_objects) {
|
||||
*out_arg_waiter = idx - m_num_wait_objects;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Otherwise, process the event as normal. */
|
||||
m_consumers[idx]->ProcessEvent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/iosupport.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <deko3d.h>
|
||||
|
||||
// Define the desired number of framebuffers
|
||||
#define FB_NUM 2
|
||||
|
||||
// Define the size of the memory block that will hold code
|
||||
#define CODEMEMSIZE (64*1024)
|
||||
|
||||
// Define the size of the memory block that will hold command lists
|
||||
#define CMDMEMSIZE (64*1024)
|
||||
|
||||
#define NUM_IMAGE_SLOTS 1
|
||||
#define NUM_SAMPLER_SLOTS 1
|
||||
|
||||
typedef struct {
|
||||
float pos[2];
|
||||
float tex[2];
|
||||
} VertexDef;
|
||||
|
||||
typedef struct {
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
float alpha;
|
||||
} PaletteColor;
|
||||
|
||||
typedef struct {
|
||||
float dimensions[4];
|
||||
VertexDef vertices[3];
|
||||
PaletteColor palettes[24];
|
||||
} ConsoleConfig;
|
||||
|
||||
static const VertexDef g_vertexData[3] = {
|
||||
{ { 0.0f, +1.0f }, { 0.5f, 0.0f, } },
|
||||
{ { -1.0f, -1.0f }, { 0.0f, 1.0f, } },
|
||||
{ { +1.0f, -1.0f }, { 1.0f, 1.0f, } },
|
||||
};
|
||||
|
||||
static const PaletteColor g_paletteData[24] = {
|
||||
{ 0.0f, 0.0f, 0.0f, 0.0f }, // black
|
||||
{ 0.5f, 0.0f, 0.0f, 1.0f }, // red
|
||||
{ 0.0f, 0.5f, 0.0f, 1.0f }, // green
|
||||
{ 0.5f, 0.5f, 0.0f, 1.0f }, // yellow
|
||||
{ 0.0f, 0.0f, 0.5f, 1.0f }, // blue
|
||||
{ 0.5f, 0.0f, 0.5f, 1.0f }, // magenta
|
||||
{ 0.0f, 0.5f, 0.5f, 1.0f }, // cyan
|
||||
{ 0.75f, 0.75f, 0.75f, 1.0f }, // white
|
||||
|
||||
{ 0.5f, 0.5f, 0.5f, 1.0f }, // bright black
|
||||
{ 1.0f, 0.0f, 0.0f, 1.0f }, // bright red
|
||||
{ 0.0f, 1.0f, 0.0f, 1.0f }, // bright green
|
||||
{ 1.0f, 1.0f, 0.0f, 1.0f }, // bright yellow
|
||||
{ 0.0f, 0.0f, 1.0f, 1.0f }, // bright blue
|
||||
{ 1.0f, 0.0f, 1.0f, 1.0f }, // bright magenta
|
||||
{ 0.0f, 1.0f, 1.0f, 1.0f }, // bright cyan
|
||||
{ 1.0f, 1.0f, 1.0f, 1.0f }, // bright white
|
||||
|
||||
{ 0.0f, 0.0f, 0.0f, 0.0f }, // faint black
|
||||
{ 0.25f, 0.0f, 0.0f, 1.0f }, // faint red
|
||||
{ 0.0f, 0.25f, 0.0f, 1.0f }, // faint green
|
||||
{ 0.25f, 0.25f, 0.0f, 1.0f }, // faint yellow
|
||||
{ 0.0f, 0.0f, 0.25f, 1.0f }, // faint blue
|
||||
{ 0.25f, 0.0f, 0.25f, 1.0f }, // faint magenta
|
||||
{ 0.0f, 0.25f, 0.25f, 1.0f }, // faint cyan
|
||||
{ 0.375f, 0.375f, 0.375f, 1.0f }, // faint white
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t tileId;
|
||||
uint8_t frontPal;
|
||||
uint8_t backPal;
|
||||
} ConsoleChar;
|
||||
|
||||
static const DkVtxAttribState g_attribState[] = {
|
||||
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,tileId), .size=DkVtxAttribSize_1x16, .type=DkVtxAttribType_Uscaled, .isBgra=0 },
|
||||
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,frontPal), .size=DkVtxAttribSize_2x8, .type=DkVtxAttribType_Uint, .isBgra=0 },
|
||||
};
|
||||
|
||||
static const DkVtxBufferState g_vtxbufState[] = {
|
||||
{ .stride=sizeof(ConsoleChar), .divisor=1 },
|
||||
};
|
||||
|
||||
struct GpuRenderer {
|
||||
ConsoleRenderer base;
|
||||
|
||||
bool initialized;
|
||||
|
||||
DkDevice device;
|
||||
DkQueue queue;
|
||||
|
||||
DkMemBlock imageMemBlock;
|
||||
DkMemBlock codeMemBlock;
|
||||
DkMemBlock dataMemBlock;
|
||||
|
||||
DkSwapchain swapchain;
|
||||
DkImage framebuffers[FB_NUM];
|
||||
DkImage tileset;
|
||||
ConsoleChar* charBuf;
|
||||
|
||||
uint32_t codeMemOffset;
|
||||
DkShader vertexShader;
|
||||
DkShader fragmentShader;
|
||||
|
||||
DkCmdBuf cmdbuf;
|
||||
DkCmdList cmdsBindFramebuffer[FB_NUM];
|
||||
DkCmdList cmdsRender;
|
||||
|
||||
DkFence lastRenderFence;
|
||||
};
|
||||
|
||||
static struct GpuRenderer* GpuRenderer(PrintConsole* con)
|
||||
{
|
||||
return (struct GpuRenderer*)con->renderer;
|
||||
}
|
||||
|
||||
static void GpuRenderer_destroy(struct GpuRenderer* r)
|
||||
{
|
||||
// Make sure the queue is idle before destroying anything
|
||||
dkQueueWaitIdle(r->queue);
|
||||
|
||||
// Destroy all the resources we've created
|
||||
dkQueueDestroy(r->queue);
|
||||
dkCmdBufDestroy(r->cmdbuf);
|
||||
dkSwapchainDestroy(r->swapchain);
|
||||
dkMemBlockDestroy(r->dataMemBlock);
|
||||
dkMemBlockDestroy(r->codeMemBlock);
|
||||
dkMemBlockDestroy(r->imageMemBlock);
|
||||
dkDeviceDestroy(r->device);
|
||||
|
||||
// Clear out all state
|
||||
memset(&r->initialized, 0, sizeof(*r) - offsetof(struct GpuRenderer, initialized));
|
||||
}
|
||||
|
||||
// Simple function for loading a shader from the filesystem
|
||||
static void GpuRenderer_loadShader(struct GpuRenderer* r, DkShader* pShader, const char* path)
|
||||
{
|
||||
// Open the file, and retrieve its size
|
||||
FILE* f = fopen(path, "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
uint32_t size = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
// Look for a spot in the code memory block for loading this shader. Note that
|
||||
// we are just using a simple incremental offset; this isn't a general purpose
|
||||
// allocation algorithm.
|
||||
uint32_t codeOffset = r->codeMemOffset;
|
||||
r->codeMemOffset += (size + DK_SHADER_CODE_ALIGNMENT - 1) &~ (DK_SHADER_CODE_ALIGNMENT - 1);
|
||||
|
||||
// Read the file into memory, and close the file
|
||||
fread((uint8_t*)dkMemBlockGetCpuAddr(r->codeMemBlock) + codeOffset, size, 1, f);
|
||||
fclose(f);
|
||||
|
||||
// Initialize the user provided shader object with the code we've just loaded
|
||||
DkShaderMaker shaderMaker;
|
||||
dkShaderMakerDefaults(&shaderMaker, r->codeMemBlock, codeOffset);
|
||||
dkShaderInitialize(pShader, &shaderMaker);
|
||||
}
|
||||
|
||||
static bool GpuRenderer_init(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
if (r->initialized) {
|
||||
// We're already initialized
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create the deko3d device, which is the root object
|
||||
DkDeviceMaker deviceMaker;
|
||||
dkDeviceMakerDefaults(&deviceMaker);
|
||||
r->device = dkDeviceCreate(&deviceMaker);
|
||||
|
||||
// Create the queue
|
||||
DkQueueMaker queueMaker;
|
||||
dkQueueMakerDefaults(&queueMaker, r->device);
|
||||
queueMaker.flags = DkQueueFlags_Graphics;
|
||||
r->queue = dkQueueCreate(&queueMaker);
|
||||
|
||||
// Calculate required width/height for the framebuffers
|
||||
u32 width = con->font.tileWidth * con->consoleWidth;
|
||||
u32 height = con->font.tileHeight * con->consoleHeight;
|
||||
u32 totalConSize = con->consoleWidth * con->consoleHeight;
|
||||
|
||||
// Calculate layout for the framebuffers
|
||||
DkImageLayoutMaker imageLayoutMaker;
|
||||
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
|
||||
imageLayoutMaker.flags = DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression;
|
||||
imageLayoutMaker.format = DkImageFormat_RGBA8_Unorm;
|
||||
imageLayoutMaker.dimensions[0] = width;
|
||||
imageLayoutMaker.dimensions[1] = height;
|
||||
|
||||
// Calculate layout for the framebuffers
|
||||
DkImageLayout framebufferLayout;
|
||||
dkImageLayoutInitialize(&framebufferLayout, &imageLayoutMaker);
|
||||
|
||||
// Calculate layout for the tileset
|
||||
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
|
||||
imageLayoutMaker.type = DkImageType_2DArray;
|
||||
imageLayoutMaker.format = DkImageFormat_R32_Float;
|
||||
imageLayoutMaker.dimensions[0] = con->font.tileWidth;
|
||||
imageLayoutMaker.dimensions[1] = con->font.tileHeight;
|
||||
imageLayoutMaker.dimensions[2] = con->font.numChars;
|
||||
|
||||
// Calculate layout for the tileset
|
||||
DkImageLayout tilesetLayout;
|
||||
dkImageLayoutInitialize(&tilesetLayout, &imageLayoutMaker);
|
||||
|
||||
// Retrieve necessary size and alignment for the framebuffers
|
||||
uint32_t framebufferSize = dkImageLayoutGetSize(&framebufferLayout);
|
||||
uint32_t framebufferAlign = dkImageLayoutGetAlignment(&framebufferLayout);
|
||||
framebufferSize = (framebufferSize + framebufferAlign - 1) &~ (framebufferAlign - 1);
|
||||
|
||||
// Retrieve necessary size and alignment for the tileset
|
||||
uint32_t tilesetSize = dkImageLayoutGetSize(&tilesetLayout);
|
||||
uint32_t tilesetAlign = dkImageLayoutGetAlignment(&tilesetLayout);
|
||||
tilesetSize = (tilesetSize + tilesetAlign - 1) &~ (tilesetAlign - 1);
|
||||
|
||||
// Create a memory block that will host the framebuffers and the tileset
|
||||
DkMemBlockMaker memBlockMaker;
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device, FB_NUM*framebufferSize + tilesetSize);
|
||||
memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image;
|
||||
r->imageMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Initialize the framebuffers with the layout and backing memory we've just created
|
||||
DkImage const* swapchainImages[FB_NUM];
|
||||
for (unsigned i = 0; i < FB_NUM; i ++) {
|
||||
swapchainImages[i] = &r->framebuffers[i];
|
||||
dkImageInitialize(&r->framebuffers[i], &framebufferLayout, r->imageMemBlock, i*framebufferSize);
|
||||
}
|
||||
|
||||
// Create a swapchain out of the framebuffers we've just initialized
|
||||
DkSwapchainMaker swapchainMaker;
|
||||
dkSwapchainMakerDefaults(&swapchainMaker, r->device, nwindowGetDefault(), swapchainImages, FB_NUM);
|
||||
r->swapchain = dkSwapchainCreate(&swapchainMaker);
|
||||
|
||||
// Initialize the tileset
|
||||
dkImageInitialize(&r->tileset, &tilesetLayout, r->imageMemBlock, FB_NUM*framebufferSize);
|
||||
|
||||
// Create a memory block onto which we will load shader code
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device, CODEMEMSIZE);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code;
|
||||
r->codeMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
r->codeMemOffset = 0;
|
||||
|
||||
// Load our shaders (both vertex and fragment)
|
||||
romfsInit();
|
||||
GpuRenderer_loadShader(r, &r->vertexShader, "romfs:/shaders/console_vsh.dksh");
|
||||
GpuRenderer_loadShader(r, &r->fragmentShader, "romfs:/shaders/console_fsh.dksh");
|
||||
|
||||
// Generate the descriptors
|
||||
struct {
|
||||
DkImageDescriptor images[NUM_IMAGE_SLOTS];
|
||||
DkSamplerDescriptor samplers[NUM_SAMPLER_SLOTS];
|
||||
} descriptors;
|
||||
|
||||
// Generate a image descriptor for the tileset
|
||||
DkImageView tilesetView;
|
||||
dkImageViewDefaults(&tilesetView, &r->tileset);
|
||||
dkImageDescriptorInitialize(&descriptors.images[0], &tilesetView, false, false);
|
||||
|
||||
// Generate a sampler descriptor for the tileset
|
||||
DkSampler sampler;
|
||||
dkSamplerDefaults(&sampler);
|
||||
sampler.wrapMode[0] = DkWrapMode_ClampToEdge;
|
||||
sampler.wrapMode[1] = DkWrapMode_ClampToEdge;
|
||||
sampler.minFilter = DkFilter_Nearest;
|
||||
sampler.magFilter = DkFilter_Nearest;
|
||||
dkSamplerDescriptorInitialize(&descriptors.samplers[0], &sampler);
|
||||
|
||||
uint32_t descriptorsOffset = CMDMEMSIZE;
|
||||
uint32_t configOffset = (descriptorsOffset + sizeof(descriptors) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
|
||||
uint32_t configSize = (sizeof(ConsoleConfig) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
|
||||
|
||||
uint32_t charBufOffset = configOffset + configSize;
|
||||
uint32_t charBufSize = totalConSize * sizeof(ConsoleChar);
|
||||
|
||||
// Create a memory block which will be used for recording command lists using a command buffer
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
|
||||
(charBufOffset + charBufSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
|
||||
);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
|
||||
r->dataMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Create a command buffer object
|
||||
DkCmdBufMaker cmdbufMaker;
|
||||
dkCmdBufMakerDefaults(&cmdbufMaker, r->device);
|
||||
r->cmdbuf = dkCmdBufCreate(&cmdbufMaker);
|
||||
|
||||
// Feed our memory to the command buffer so that we can start recording commands
|
||||
dkCmdBufAddMemory(r->cmdbuf, r->dataMemBlock, 0, CMDMEMSIZE);
|
||||
|
||||
// Create a temporary buffer that will hold the tileset
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
|
||||
(sizeof(float)*con->font.tileWidth*con->font.tileHeight*con->font.numChars + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
|
||||
);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
|
||||
DkMemBlock scratchMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
float* scratchMem = (float*)dkMemBlockGetCpuAddr(scratchMemBlock);
|
||||
|
||||
// Unpack 1bpp tileset into a texture image the GPU can read
|
||||
unsigned packedTileWidth = (con->font.tileWidth+7)/8;
|
||||
for (unsigned tile = 0; tile < con->font.numChars; tile ++) {
|
||||
const uint8_t* data = (const uint8_t*)con->font.gfx + con->font.tileHeight*packedTileWidth*tile;
|
||||
for (unsigned y = 0; y < con->font.tileHeight; y ++) {
|
||||
const uint8_t* row = &data[packedTileWidth*(y+1)];
|
||||
uint8_t c = 0;
|
||||
for (unsigned x = 0; x < con->font.tileWidth; x ++) {
|
||||
if (!(x & 7))
|
||||
c = *--row;
|
||||
*scratchMem++ = (c & 0x80) ? 1.0f : 0.0f;
|
||||
c <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up configuration
|
||||
DkGpuAddr configAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + configOffset;
|
||||
ConsoleConfig consoleConfig = {};
|
||||
consoleConfig.dimensions[0] = width;
|
||||
consoleConfig.dimensions[1] = height;
|
||||
consoleConfig.dimensions[2] = con->consoleWidth;
|
||||
consoleConfig.dimensions[3] = con->consoleHeight;
|
||||
memcpy(consoleConfig.vertices, g_vertexData, sizeof(g_vertexData));
|
||||
memcpy(consoleConfig.palettes, g_paletteData, sizeof(g_paletteData));
|
||||
|
||||
// Generate a temporary command list for uploading stuff and run it
|
||||
DkGpuAddr descriptorSet = dkMemBlockGetGpuAddr(r->dataMemBlock) + descriptorsOffset;
|
||||
DkCopyBuf copySrc = { dkMemBlockGetGpuAddr(scratchMemBlock), 0, 0 };
|
||||
DkImageRect copyDst = { 0, 0, 0, con->font.tileWidth, con->font.tileHeight, con->font.numChars };
|
||||
dkCmdBufPushData(r->cmdbuf, descriptorSet, &descriptors, sizeof(descriptors));
|
||||
dkCmdBufPushConstants(r->cmdbuf, configAddr, configSize, 0, sizeof(consoleConfig), &consoleConfig);
|
||||
dkCmdBufBindImageDescriptorSet(r->cmdbuf, descriptorSet, NUM_IMAGE_SLOTS);
|
||||
dkCmdBufBindSamplerDescriptorSet(r->cmdbuf, descriptorSet + NUM_IMAGE_SLOTS*sizeof(DkImageDescriptor), NUM_SAMPLER_SLOTS);
|
||||
dkCmdBufCopyBufferToImage(r->cmdbuf, ©Src, &tilesetView, ©Dst, 0);
|
||||
dkQueueSubmitCommands(r->queue, dkCmdBufFinishList(r->cmdbuf));
|
||||
dkQueueFlush(r->queue);
|
||||
dkQueueWaitIdle(r->queue);
|
||||
dkCmdBufClear(r->cmdbuf);
|
||||
|
||||
// Destroy the scratch memory block since we don't need it anymore
|
||||
dkMemBlockDestroy(scratchMemBlock);
|
||||
|
||||
// Retrieve the address of the character buffer
|
||||
DkGpuAddr charBufAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + charBufOffset;
|
||||
r->charBuf = (ConsoleChar*)((uint8_t*)dkMemBlockGetCpuAddr(r->dataMemBlock) + charBufOffset);
|
||||
memset(r->charBuf, 0, charBufSize);
|
||||
|
||||
// Generate a command list for each framebuffer, which will bind each of them as a render target
|
||||
for (unsigned i = 0; i < FB_NUM; i ++) {
|
||||
DkImageView imageView;
|
||||
dkImageViewDefaults(&imageView, &r->framebuffers[i]);
|
||||
dkCmdBufBindRenderTarget(r->cmdbuf, &imageView, NULL);
|
||||
r->cmdsBindFramebuffer[i] = dkCmdBufFinishList(r->cmdbuf);
|
||||
}
|
||||
|
||||
// Declare structs that will be used for binding state
|
||||
DkViewport viewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };
|
||||
DkScissor scissor = { 0, 0, width, height };
|
||||
DkShader const* shaders[] = { &r->vertexShader, &r->fragmentShader };
|
||||
DkRasterizerState rasterizerState;
|
||||
DkColorState colorState;
|
||||
DkColorWriteState colorWriteState;
|
||||
|
||||
// Initialize state structs with the deko3d defaults
|
||||
dkRasterizerStateDefaults(&rasterizerState);
|
||||
dkColorStateDefaults(&colorState);
|
||||
dkColorWriteStateDefaults(&colorWriteState);
|
||||
|
||||
rasterizerState.fillRectangleEnable = true;
|
||||
colorState.alphaCompareOp = DkCompareOp_Greater;
|
||||
|
||||
// Generate the main rendering command list
|
||||
dkCmdBufSetViewports(r->cmdbuf, 0, &viewport, 1);
|
||||
dkCmdBufSetScissors(r->cmdbuf, 0, &scissor, 1);
|
||||
//dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 0.0f);
|
||||
dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
dkCmdBufBindShaders(r->cmdbuf, DkStageFlag_GraphicsMask, shaders, sizeof(shaders)/sizeof(shaders[0]));
|
||||
dkCmdBufBindRasterizerState(r->cmdbuf, &rasterizerState);
|
||||
dkCmdBufBindColorState(r->cmdbuf, &colorState);
|
||||
dkCmdBufBindColorWriteState(r->cmdbuf, &colorWriteState);
|
||||
dkCmdBufBindUniformBuffer(r->cmdbuf, DkStage_Vertex, 0, configAddr, configSize);
|
||||
dkCmdBufBindTexture(r->cmdbuf, DkStage_Fragment, 0, dkMakeTextureHandle(0, 0));
|
||||
dkCmdBufBindVtxAttribState(r->cmdbuf, g_attribState, sizeof(g_attribState)/sizeof(g_attribState[0]));
|
||||
dkCmdBufBindVtxBufferState(r->cmdbuf, g_vtxbufState, sizeof(g_vtxbufState)/sizeof(g_vtxbufState[0]));
|
||||
dkCmdBufBindVtxBuffer(r->cmdbuf, 0, charBufAddr, charBufSize);
|
||||
dkCmdBufSetAlphaRef(r->cmdbuf, 0.0f);
|
||||
dkCmdBufDraw(r->cmdbuf, DkPrimitive_Triangles, 3, totalConSize, 0, 0);
|
||||
r->cmdsRender = dkCmdBufFinishList(r->cmdbuf);
|
||||
|
||||
r->initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void GpuRenderer_deinit(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
if (r->initialized) {
|
||||
GpuRenderer_destroy(r);
|
||||
}
|
||||
}
|
||||
|
||||
static void GpuRenderer_drawChar(PrintConsole* con, int x, int y, int c)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
int writingColor = con->fg;
|
||||
int screenColor = con->bg;
|
||||
|
||||
if (con->flags & CONSOLE_COLOR_BOLD) {
|
||||
writingColor += 8;
|
||||
} else if (con->flags & CONSOLE_COLOR_FAINT) {
|
||||
writingColor += 16;
|
||||
}
|
||||
|
||||
if (con->flags & CONSOLE_COLOR_REVERSE) {
|
||||
int tmp = writingColor;
|
||||
writingColor = screenColor;
|
||||
screenColor = tmp;
|
||||
}
|
||||
|
||||
// Wait for the fence
|
||||
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
|
||||
|
||||
ConsoleChar* pos = &r->charBuf[y*con->consoleWidth+x];
|
||||
pos->tileId = c;
|
||||
pos->frontPal = writingColor;
|
||||
pos->backPal = screenColor;
|
||||
}
|
||||
|
||||
static void GpuRenderer_scrollWindow(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
// Wait for the fence
|
||||
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
|
||||
|
||||
// Perform the scrolling
|
||||
for (int y = 0; y < con->windowHeight-1; y ++) {
|
||||
memcpy(
|
||||
&r->charBuf[(con->windowY+y+0)*con->consoleWidth + con->windowX],
|
||||
&r->charBuf[(con->windowY+y+1)*con->consoleWidth + con->windowX],
|
||||
sizeof(ConsoleChar)*con->windowWidth);
|
||||
}
|
||||
}
|
||||
|
||||
static void GpuRenderer_flushAndSwap(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
// Acquire a framebuffer from the swapchain (and wait for it to be available)
|
||||
int slot = dkQueueAcquireImage(r->queue, r->swapchain);
|
||||
|
||||
// Run the command list that binds said framebuffer as a render target
|
||||
dkQueueSubmitCommands(r->queue, r->cmdsBindFramebuffer[slot]);
|
||||
|
||||
// Run the main rendering command list
|
||||
dkQueueSubmitCommands(r->queue, r->cmdsRender);
|
||||
|
||||
// Signal the fence
|
||||
dkQueueSignalFence(r->queue, &r->lastRenderFence, false);
|
||||
|
||||
// Now that we are done rendering, present it to the screen
|
||||
dkQueuePresentImage(r->queue, r->swapchain, slot);
|
||||
}
|
||||
|
||||
static struct GpuRenderer s_gpuRenderer =
|
||||
{
|
||||
{
|
||||
GpuRenderer_init,
|
||||
GpuRenderer_deinit,
|
||||
GpuRenderer_drawChar,
|
||||
GpuRenderer_scrollWindow,
|
||||
GpuRenderer_flushAndSwap,
|
||||
}
|
||||
};
|
||||
|
||||
ConsoleRenderer* getDefaultConsoleRenderer(void)
|
||||
{
|
||||
return &s_gpuRenderer.base;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/console_main_loop.hpp>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* Load device firmware version and serial number. */
|
||||
HAZE_R_ABORT_UNLESS(haze::LoadDeviceProperties());
|
||||
|
||||
/* Run the application. */
|
||||
haze::ConsoleMainLoop::RunApplication();
|
||||
|
||||
/* Return to the loader. */
|
||||
return 0;
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
void PtpObjectDatabase::Initialize(PtpObjectHeap *object_heap) {
|
||||
m_object_heap = object_heap;
|
||||
m_object_heap->Initialize();
|
||||
|
||||
std::construct_at(std::addressof(m_name_tree));
|
||||
std::construct_at(std::addressof(m_object_id_tree));
|
||||
|
||||
m_next_object_id = 1;
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::Finalize() {
|
||||
std::destroy_at(std::addressof(m_object_id_tree));
|
||||
std::destroy_at(std::addressof(m_name_tree));
|
||||
|
||||
m_next_object_id = 0;
|
||||
|
||||
m_object_heap->Finalize();
|
||||
m_object_heap = nullptr;
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object) {
|
||||
constexpr auto separator = "/";
|
||||
|
||||
/* Calculate length of the new name with null terminator. */
|
||||
const size_t parent_name_len = util::Strlen(parent_name);
|
||||
const size_t separator_len = util::Strlen(separator);
|
||||
const size_t name_len = util::Strlen(name);
|
||||
const size_t terminator_len = 1;
|
||||
const size_t alloc_len = sizeof(PtpObject) + parent_name_len + separator_len + name_len + terminator_len;
|
||||
|
||||
/* Allocate memory for the object. */
|
||||
PtpObject * const object = m_object_heap->Allocate<PtpObject>(alloc_len);
|
||||
R_UNLESS(object != nullptr, haze::ResultOutOfMemory());
|
||||
|
||||
/* Build the object name. */
|
||||
std::strncpy(object->m_name, parent_name, parent_name_len + terminator_len);
|
||||
std::strncpy(object->m_name + parent_name_len, separator, separator_len + terminator_len);
|
||||
std::strncpy(object->m_name + parent_name_len + separator_len, name, name_len + terminator_len);
|
||||
|
||||
{
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
auto guard = SCOPE_GUARD { m_object_heap->Deallocate(object, alloc_len); };
|
||||
|
||||
/* Check if an object with this name already exists. If it does, we can just return it here. */
|
||||
if (auto * const existing = this->GetObjectByName(object->GetName()); existing != nullptr) {
|
||||
*out_object = existing;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Persist the reference to the object. */
|
||||
guard.Cancel();
|
||||
}
|
||||
|
||||
/* Set object properties. */
|
||||
object->m_parent_id = parent_id;
|
||||
object->m_object_id = 0;
|
||||
|
||||
/* Set output. */
|
||||
*out_object = object;
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::RegisterObject(PtpObject *object, u32 desired_id) {
|
||||
/* If the object is already registered, skip registration. */
|
||||
if (object->GetIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set desired object ID. */
|
||||
if (desired_id == 0) {
|
||||
desired_id = m_next_object_id++;
|
||||
}
|
||||
|
||||
/* Insert object into trees. */
|
||||
object->Register(desired_id);
|
||||
m_object_id_tree.insert(*object);
|
||||
m_name_tree.insert(*object);
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::UnregisterObject(PtpObject *object) {
|
||||
/* If the object is not registered, skip trying to unregister. */
|
||||
if (!object->GetIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove object from trees. */
|
||||
m_object_id_tree.erase(m_object_id_tree.iterator_to(*object));
|
||||
m_name_tree.erase(m_name_tree.iterator_to(*object));
|
||||
object->Unregister();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::DeleteObject(PtpObject *object) {
|
||||
/* Unregister the object as required. */
|
||||
this->UnregisterObject(object);
|
||||
|
||||
/* Free the object. */
|
||||
m_object_heap->Deallocate(object, sizeof(PtpObject) + std::strlen(object->GetName()) + 1);
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id) {
|
||||
/* Try to create the object. */
|
||||
PtpObject *object;
|
||||
R_TRY(this->CreateOrFindObject(parent_name, name, parent_id, std::addressof(object)));
|
||||
|
||||
/* We succeeded, so register it. */
|
||||
this->RegisterObject(object);
|
||||
|
||||
/* Set the output ID. */
|
||||
*out_object_id = object->GetObjectId();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
PtpObject *PtpObjectDatabase::GetObjectById(u32 object_id) {
|
||||
/* Find in ID mapping. */
|
||||
if (auto it = m_object_id_tree.find_key(object_id); it != m_object_id_tree.end()) {
|
||||
return std::addressof(*it);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PtpObject *PtpObjectDatabase::GetObjectByName(const char *name) {
|
||||
/* Find in name mapping. */
|
||||
if (auto it = m_name_tree.find_key(name); it != m_name_tree.end()) {
|
||||
return std::addressof(*it);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Allow 30MiB for use by libnx. */
|
||||
static constexpr size_t LibnxReservedMemorySize = 30_MB;
|
||||
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Initialize() {
|
||||
/* If we're already initialized, skip re-initialization. */
|
||||
if (m_heap_block_size != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Estimate how much memory we can reserve. */
|
||||
size_t mem_used = 0;
|
||||
HAZE_R_ABORT_UNLESS(svcGetInfo(std::addressof(mem_used), InfoType_UsedMemorySize, svc::CurrentProcess, 0));
|
||||
HAZE_ASSERT(mem_used > LibnxReservedMemorySize);
|
||||
mem_used -= LibnxReservedMemorySize;
|
||||
|
||||
/* Calculate size of blocks. */
|
||||
m_heap_block_size = mem_used / NumHeapBlocks;
|
||||
HAZE_ASSERT(m_heap_block_size > 0);
|
||||
|
||||
/* Allocate the memory. */
|
||||
for (size_t i = 0; i < NumHeapBlocks; i++) {
|
||||
m_heap_blocks[i] = std::malloc(m_heap_block_size);
|
||||
HAZE_ASSERT(m_heap_blocks[i] != nullptr);
|
||||
}
|
||||
|
||||
/* Set the address to allocate from. */
|
||||
m_next_address = m_heap_blocks[0];
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Finalize() {
|
||||
if (m_heap_block_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Tear down the heap, allowing a subsequent call to Initialize() if desired. */
|
||||
for (size_t i = 0; i < NumHeapBlocks; i++) {
|
||||
std::free(m_heap_blocks[i]);
|
||||
m_heap_blocks[i] = nullptr;
|
||||
}
|
||||
|
||||
m_next_address = nullptr;
|
||||
m_heap_block_size = 0;
|
||||
m_current_heap_block = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
PtpBuffers *GetBuffers() {
|
||||
static constinit PtpBuffers buffers = {};
|
||||
return std::addressof(buffers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
|
||||
m_object_heap = object_heap;
|
||||
m_buffers = GetBuffers();
|
||||
|
||||
/* Configure fs proxy. */
|
||||
m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc"));
|
||||
|
||||
R_RETURN(m_usb_server.Initialize(std::addressof(MtpInterfaceInfo), SwitchMtpIdVendor, SwitchMtpIdProduct, reactor));
|
||||
}
|
||||
|
||||
void PtpResponder::Finalize() {
|
||||
m_usb_server.Finalize();
|
||||
m_fs.Finalize();
|
||||
}
|
||||
|
||||
Result PtpResponder::LoopProcess() {
|
||||
while (true) {
|
||||
/* Try to handle a request. */
|
||||
R_TRY_CATCH(this->HandleRequest()) {
|
||||
R_CATCH(haze::ResultStopRequested, haze::ResultFocusLost) {
|
||||
/* If we encountered a stop condition, we're done.*/
|
||||
R_THROW(R_CURRENT_RESULT);
|
||||
}
|
||||
R_CATCH_ALL() {
|
||||
/* On other failures, try to handle another request. */
|
||||
continue;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Otherwise, handle the next request. */
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleRequest() {
|
||||
ON_RESULT_FAILURE {
|
||||
/* For general failure modes, the failure is unrecoverable. Close the session. */
|
||||
this->ForceCloseSession();
|
||||
};
|
||||
|
||||
R_TRY_CATCH(this->HandleRequestImpl()) {
|
||||
R_CATCH(haze::ResultUnknownRequestType) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
R_CATCH(haze::ResultSessionNotOpen) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_SessionNotOpen));
|
||||
}
|
||||
R_CATCH(haze::ResultOperationNotSupported) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_OperationNotSupported));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidStorageId) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_InvalidStorageId));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidObjectId) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_InvalidObjectHandle));
|
||||
}
|
||||
R_CATCH(haze::ResultUnknownPropertyCode) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpObjectPropNotSupported));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidPropertyValue) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue));
|
||||
}
|
||||
R_CATCH(haze::ResultGroupSpecified) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByGroupUnsupported));
|
||||
}
|
||||
R_CATCH(haze::ResultDepthSpecified) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByDepthUnsupported));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidArgument) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
R_CATCH_MODULE(fs) {
|
||||
/* Errors from fs are typically recoverable. */
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleRequestImpl() {
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(dp.Read(std::addressof(m_request_header)));
|
||||
|
||||
switch (m_request_header.type) {
|
||||
case PtpUsbBulkContainerType_Command: R_RETURN(this->HandleCommandRequest(dp));
|
||||
default: R_THROW(haze::ResultUnknownRequestType());
|
||||
}
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleCommandRequest(PtpDataParser &dp) {
|
||||
if (!m_session_open && m_request_header.code != PtpOperationCode_OpenSession && m_request_header.code != PtpOperationCode_GetDeviceInfo) {
|
||||
R_THROW(haze::ResultSessionNotOpen());
|
||||
}
|
||||
|
||||
switch (m_request_header.code) {
|
||||
case PtpOperationCode_GetDeviceInfo: R_RETURN(this->GetDeviceInfo(dp)); break;
|
||||
case PtpOperationCode_OpenSession: R_RETURN(this->OpenSession(dp)); break;
|
||||
case PtpOperationCode_CloseSession: R_RETURN(this->CloseSession(dp)); break;
|
||||
case PtpOperationCode_GetStorageIds: R_RETURN(this->GetStorageIds(dp)); break;
|
||||
case PtpOperationCode_GetStorageInfo: R_RETURN(this->GetStorageInfo(dp)); break;
|
||||
case PtpOperationCode_GetObjectHandles: R_RETURN(this->GetObjectHandles(dp)); break;
|
||||
case PtpOperationCode_GetObjectInfo: R_RETURN(this->GetObjectInfo(dp)); break;
|
||||
case PtpOperationCode_GetObject: R_RETURN(this->GetObject(dp)); break;
|
||||
case PtpOperationCode_SendObjectInfo: R_RETURN(this->SendObjectInfo(dp)); break;
|
||||
case PtpOperationCode_SendObject: R_RETURN(this->SendObject(dp)); break;
|
||||
case PtpOperationCode_DeleteObject: R_RETURN(this->DeleteObject(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjectPropsSupported: R_RETURN(this->GetObjectPropsSupported(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break;
|
||||
case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjPropList: R_RETURN(this->GetObjectPropList(dp)); break;
|
||||
case PtpOperationCode_AndroidGetPartialObject64: R_RETURN(this->GetPartialObject64(dp)); break;
|
||||
case PtpOperationCode_AndroidSendPartialObject: R_RETURN(this->SendPartialObject(dp)); break;
|
||||
case PtpOperationCode_AndroidTruncateObject: R_RETURN(this->TruncateObject(dp)); break;
|
||||
case PtpOperationCode_AndroidBeginEditObject: R_RETURN(this->BeginEditObject(dp)); break;
|
||||
case PtpOperationCode_AndroidEndEditObject: R_RETURN(this->EndEditObject(dp)); break;
|
||||
default: R_THROW(haze::ResultOperationNotSupported());
|
||||
}
|
||||
}
|
||||
|
||||
void PtpResponder::ForceCloseSession() {
|
||||
if (m_session_open) {
|
||||
m_session_open = false;
|
||||
m_object_database.Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
Result PtpResponder::WriteResponse(PtpResponseCode code, const void* data, size_t size) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(db.AddResponseHeader(m_request_header, code, size));
|
||||
R_TRY(db.AddBuffer(reinterpret_cast<const u8*>(data), size));
|
||||
R_RETURN(db.Commit());
|
||||
}
|
||||
|
||||
Result PtpResponder::WriteResponse(PtpResponseCode code) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(db.AddResponseHeader(m_request_header, code, 0));
|
||||
R_RETURN(db.Commit());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result PtpResponder::GetPartialObject64(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID, offset, and size for the file we want to read. */
|
||||
u32 object_id, size;
|
||||
u64 offset;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(offset)));
|
||||
R_TRY(dp.Read(std::addressof(size)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 file_size = 0;
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
|
||||
|
||||
/* Ensure the requested offset and size are within range. */
|
||||
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
|
||||
R_UNLESS(static_cast<u64>(file_size) <= offset + size, haze::ResultInvalidArgument());
|
||||
|
||||
/* Send the header and data size. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, size));
|
||||
|
||||
/* Begin reading the file, writing data to the builder as we progress. */
|
||||
s64 size_remaining = size;
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
u64 bytes_to_read = std::min<s64>(FsBufferSize, size_remaining);
|
||||
u64 bytes_read;
|
||||
|
||||
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_read, FsReadOption_None, std::addressof(bytes_read)));
|
||||
|
||||
size_remaining -= bytes_read;
|
||||
offset += bytes_read;
|
||||
|
||||
/* Write to output. */
|
||||
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
|
||||
|
||||
/* If we read fewer bytes than the batch size, or have read enough data, we're done. */
|
||||
if (bytes_read < FsBufferSize || size_remaining == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the data response. */
|
||||
R_TRY(db.Commit());
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendPartialObject(PtpDataParser &rdp) {
|
||||
/* Get the object ID, offset, and size for the file we want to write. */
|
||||
u32 object_id, size;
|
||||
u64 offset;
|
||||
R_TRY(rdp.Read(std::addressof(object_id)));
|
||||
R_TRY(rdp.Read(std::addressof(size)));
|
||||
R_TRY(rdp.Read(std::addressof(offset)));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 file_size = 0;
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
|
||||
|
||||
/* Ensure the requested offset and size are within range. */
|
||||
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
|
||||
R_UNLESS(static_cast<u64>(file_size) <= offset, haze::ResultInvalidArgument());
|
||||
|
||||
/* Prepare a data parser for the data we are about to receive. */
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Begin writing to the filesystem. */
|
||||
s64 size_remaining = size;
|
||||
while (true) {
|
||||
/* Read as many bytes as we can. */
|
||||
u32 bytes_received;
|
||||
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
|
||||
|
||||
/* Write to the file. */
|
||||
u32 bytes_to_write = std::min<s64>(size_remaining, bytes_received);
|
||||
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_write, 0));
|
||||
|
||||
size_remaining -= bytes_to_write;
|
||||
offset += bytes_to_write;
|
||||
|
||||
/* If we received fewer bytes than the batch size, or have written enough data, we're done. */
|
||||
if (haze::ResultEndOfTransmission::Includes(read_res) || size_remaining == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
R_TRY(read_res);
|
||||
}
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::TruncateObject(PtpDataParser &dp) {
|
||||
/* Get the object ID and size for the file we want to truncate. */
|
||||
u32 object_id;
|
||||
u64 size;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(size)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Truncate the file. */
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), size));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::BeginEditObject(PtpDataParser &dp) {
|
||||
/* Get the object ID we are going to begin editing. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* We don't implement transactions, so write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::EndEditObject(PtpDataParser &dp) {
|
||||
/* Get the object ID we are going to finish editing. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* We don't implement transactions, so write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,418 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Write information about all object properties we can support. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
|
||||
PtpObjectPropertyCode property_code;
|
||||
u16 object_format;
|
||||
|
||||
R_TRY(dp.Read(std::addressof(property_code)));
|
||||
R_TRY(dp.Read(std::addressof(object_format)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure we have a valid property code before continuing. */
|
||||
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Begin writing information about the property code. */
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_TRY(db.Add(property_code));
|
||||
|
||||
/* Each property code corresponds to a different pattern, which contains the data type, */
|
||||
/* whether the property can be set for an object, and the default value of the property. */
|
||||
switch (property_code) {
|
||||
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U128));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add<u128>(0));
|
||||
}
|
||||
case PtpObjectPropertyCode_ObjectSize:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U64));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add<u64>(0));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_StorageId:
|
||||
case PtpObjectPropertyCode_ParentObject:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U32));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add(StorageId_SdmcFs));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFormat:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U16));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFileName:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_String));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
|
||||
R_TRY(db.AddString(""));
|
||||
}
|
||||
break;
|
||||
HAZE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
|
||||
R_TRY(db.Add(PtpPropertyGroupCode_Default));
|
||||
|
||||
/* We don't use the form flag. */
|
||||
R_TRY(db.Add(PtpPropertyFormFlag_None));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
|
||||
u32 object_id;
|
||||
PtpObjectPropertyCode property_code;
|
||||
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(property_code)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure we have a valid property code before continuing. */
|
||||
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Define helper for getting the object type. */
|
||||
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
|
||||
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
|
||||
};
|
||||
|
||||
/* Define helper for getting the object size. */
|
||||
const auto GetObjectSize = [&] (s64 *out_size) {
|
||||
*out_size = 0;
|
||||
|
||||
/* Check if this is a directory. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
|
||||
/* If it is, we're done. */
|
||||
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
|
||||
|
||||
/* Otherwise, open as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
|
||||
};
|
||||
|
||||
/* Begin writing the requested object property. */
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
switch (property_code) {
|
||||
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
||||
{
|
||||
R_TRY(db.Add<u128>(object_id));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectSize:
|
||||
{
|
||||
s64 size;
|
||||
R_TRY(GetObjectSize(std::addressof(size)));
|
||||
R_TRY(db.Add<u64>(size));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_StorageId:
|
||||
{
|
||||
R_TRY(db.Add(StorageId_SdmcFs));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ParentObject:
|
||||
{
|
||||
R_TRY(db.Add(obj->GetParentId()));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFormat:
|
||||
{
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFileName:
|
||||
{
|
||||
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
|
||||
}
|
||||
break;
|
||||
HAZE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectPropList(PtpDataParser &dp) {
|
||||
u32 object_id;
|
||||
u32 object_format;
|
||||
s32 property_code;
|
||||
s32 group_code;
|
||||
s32 depth;
|
||||
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(object_format)));
|
||||
R_TRY(dp.Read(std::addressof(property_code)));
|
||||
R_TRY(dp.Read(std::addressof(group_code)));
|
||||
R_TRY(dp.Read(std::addressof(depth)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure format is unspecified. */
|
||||
R_UNLESS(object_format == 0, haze::ResultInvalidArgument());
|
||||
|
||||
/* Ensure we have a valid property code. */
|
||||
R_UNLESS(property_code == -1 || IsSupportedObjectPropertyCode(PtpObjectPropertyCode(property_code)), haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Ensure group code is the default. */
|
||||
R_UNLESS(group_code == PtpPropertyGroupCode_Default, haze::ResultGroupSpecified());
|
||||
|
||||
/* Ensure depth is 0. */
|
||||
R_UNLESS(depth == 0, haze::ResultDepthSpecified());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Define helper for getting the object type. */
|
||||
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
|
||||
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
|
||||
};
|
||||
|
||||
/* Define helper for getting the object size. */
|
||||
const auto GetObjectSize = [&] (s64 *out_size) {
|
||||
*out_size = 0;
|
||||
|
||||
/* Check if this is a directory. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
|
||||
/* If it is, we're done. */
|
||||
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
|
||||
|
||||
/* Otherwise, open as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
|
||||
};
|
||||
|
||||
/* Define helper for determining if the property should be included. */
|
||||
const auto ShouldIncludeProperty = [&] (PtpObjectPropertyCode code) {
|
||||
/* If all properties were requested, or it was the requested property, we should include the property. */
|
||||
return property_code == -1 || code == property_code;
|
||||
};
|
||||
|
||||
/* Determine how many output elements we will report. */
|
||||
u32 num_output_elements = 0;
|
||||
for (const auto obj_property : SupportedObjectProperties) {
|
||||
if (ShouldIncludeProperty(obj_property)) {
|
||||
num_output_elements++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Begin writing the requested object properties. */
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
/* Report the number of elements. */
|
||||
R_TRY(db.Add(num_output_elements));
|
||||
|
||||
for (const auto obj_property : SupportedObjectProperties) {
|
||||
if (!ShouldIncludeProperty(obj_property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Write the object handle. */
|
||||
R_TRY(db.Add<u32>(object_id));
|
||||
|
||||
/* Write the property code. */
|
||||
R_TRY(db.Add<u16>(obj_property));
|
||||
|
||||
/* Write the property value. */
|
||||
switch (obj_property) {
|
||||
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U128));
|
||||
R_TRY(db.Add<u128>(object_id));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectSize:
|
||||
{
|
||||
s64 size;
|
||||
R_TRY(GetObjectSize(std::addressof(size)));
|
||||
R_TRY(db.Add(PtpDataTypeCode_U64));
|
||||
R_TRY(db.Add<u64>(size));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_StorageId:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U32));
|
||||
R_TRY(db.Add(StorageId_SdmcFs));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ParentObject:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U32));
|
||||
R_TRY(db.Add(obj->GetParentId()));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFormat:
|
||||
{
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
R_TRY(db.Add(PtpDataTypeCode_U16));
|
||||
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFileName:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_String));
|
||||
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
|
||||
}
|
||||
break;
|
||||
HAZE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) {
|
||||
u32 object_id;
|
||||
PtpObjectPropertyCode property_code;
|
||||
|
||||
R_TRY(rdp.Read(std::addressof(object_id)));
|
||||
R_TRY(rdp.Read(std::addressof(property_code)));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Ensure we have a valid property code before continuing. */
|
||||
R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* We are reading a file name. */
|
||||
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure we can actually process the new name. */
|
||||
const bool is_empty = m_buffers->filename_string_buffer[0] == '\x00';
|
||||
const bool contains_slashes = std::strchr(m_buffers->filename_string_buffer, '/') != nullptr;
|
||||
R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue());
|
||||
|
||||
/* Add a new object in the database with the new name. */
|
||||
PtpObject *newobj;
|
||||
{
|
||||
/* Find the last path separator in the existing object name. */
|
||||
char *pathsep = std::strrchr(obj->m_name, '/');
|
||||
HAZE_ASSERT(pathsep != nullptr);
|
||||
|
||||
/* Temporarily mark the path separator as null to facilitate processing. */
|
||||
*pathsep = '\x00';
|
||||
ON_SCOPE_EXIT { *pathsep = '/'; };
|
||||
|
||||
R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), m_buffers->filename_string_buffer, obj->GetParentId(), std::addressof(newobj)));
|
||||
}
|
||||
|
||||
{
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
ON_RESULT_FAILURE {
|
||||
if (!newobj->GetIsRegistered()) {
|
||||
/* Only delete if the object was not registered. */
|
||||
/* Otherwise, we would remove an object that still exists. */
|
||||
m_object_database.DeleteObject(newobj);
|
||||
}
|
||||
};
|
||||
|
||||
/* Get the old object type. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Attempt to rename the object on the filesystem. */
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName()));
|
||||
} else {
|
||||
R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Unregister and free the old object. */
|
||||
m_object_database.DeleteObject(obj);
|
||||
|
||||
/* Register the new object. */
|
||||
m_object_database.RegisterObject(newobj, object_id);
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Write the device info data. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
|
||||
R_TRY(db.Add(MtpStandardVersion));
|
||||
R_TRY(db.Add(MtpVendorExtensionId));
|
||||
R_TRY(db.Add(MtpStandardVersion));
|
||||
R_TRY(db.AddString(MtpVendorExtensionDesc));
|
||||
R_TRY(db.Add(MtpFunctionalModeDefault));
|
||||
R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes)));
|
||||
R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes)));
|
||||
R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties)));
|
||||
R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats)));
|
||||
R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats)));
|
||||
R_TRY(db.AddString(MtpDeviceManufacturer));
|
||||
R_TRY(db.AddString(MtpDeviceModel));
|
||||
R_TRY(db.AddString(GetFirmwareVersion()));
|
||||
R_TRY(db.AddString(GetSerialNumber()));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::OpenSession(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Close, if we're already open. */
|
||||
this->ForceCloseSession();
|
||||
|
||||
/* Initialize the database. */
|
||||
m_session_open = true;
|
||||
m_object_database.Initialize(m_object_heap);
|
||||
|
||||
/* Create the root storages. */
|
||||
PtpObject *object;
|
||||
R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object)));
|
||||
|
||||
/* Register the root storages. */
|
||||
m_object_database.RegisterObject(object, StorageId_SdmcFs);
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::CloseSession(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
this->ForceCloseSession();
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Write the storage ID array. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds)));
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
PtpStorageInfo storage_info(DefaultStorageInfo);
|
||||
|
||||
/* Get the storage ID the client requested information for. */
|
||||
u32 storage_id;
|
||||
R_TRY(dp.Read(std::addressof(storage_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Get the info from fs. */
|
||||
switch (storage_id) {
|
||||
case StorageId_SdmcFs:
|
||||
{
|
||||
s64 total_space, free_space;
|
||||
R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space)));
|
||||
R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space)));
|
||||
|
||||
storage_info.max_capacity = total_space;
|
||||
storage_info.free_space_in_bytes = free_space;
|
||||
storage_info.free_space_in_images = 0;
|
||||
storage_info.storage_description = "SD Card";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
R_THROW(haze::ResultInvalidStorageId());
|
||||
}
|
||||
|
||||
/* Write the storage info data. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
|
||||
R_TRY(db.Add(storage_info.storage_type));
|
||||
R_TRY(db.Add(storage_info.filesystem_type));
|
||||
R_TRY(db.Add(storage_info.access_capability));
|
||||
R_TRY(db.Add(storage_info.max_capacity));
|
||||
R_TRY(db.Add(storage_info.free_space_in_bytes));
|
||||
R_TRY(db.Add(storage_info.free_space_in_images));
|
||||
R_TRY(db.AddString(storage_info.storage_description));
|
||||
R_TRY(db.AddString(storage_info.volume_label));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested enumeration for. */
|
||||
u32 storage_id, object_format_code, association_object_handle;
|
||||
R_TRY(dp.Read(std::addressof(storage_id)));
|
||||
R_TRY(dp.Read(std::addressof(object_format_code)));
|
||||
R_TRY(dp.Read(std::addressof(association_object_handle)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Handle top-level requests. */
|
||||
if (storage_id == PtpGetObjectHandles_AllStorage) {
|
||||
storage_id = StorageId_SdmcFs;
|
||||
}
|
||||
|
||||
/* Rewrite requests for enumerating storage directories. */
|
||||
if (association_object_handle == PtpGetObjectHandles_RootParent) {
|
||||
association_object_handle = storage_id;
|
||||
}
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(association_object_handle);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Try to read the object as a directory. */
|
||||
FsDir dir;
|
||||
R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); };
|
||||
|
||||
/* Count how many entries are in the directory. */
|
||||
s64 entry_count = 0;
|
||||
R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count)));
|
||||
|
||||
/* Begin writing. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32))));
|
||||
R_TRY(db.Add(static_cast<u32>(entry_count)));
|
||||
|
||||
/* Enumerate the directory, writing results to the data builder as we progress. */
|
||||
/* TODO: How should we handle the directory contents changing during enumeration? */
|
||||
/* Is this even feasible to handle? */
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
s64 read_count = 0;
|
||||
R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, m_buffers->file_system_entry_buffer));
|
||||
|
||||
/* Write to output. */
|
||||
for (s64 i = 0; i < read_count; i++) {
|
||||
const char *name = m_buffers->file_system_entry_buffer[i].name;
|
||||
u32 handle;
|
||||
|
||||
R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), name, obj->GetObjectId(), std::addressof(handle)));
|
||||
R_TRY(db.Add(handle));
|
||||
}
|
||||
|
||||
/* If we read fewer than the batch size, we're done. */
|
||||
if (read_count < DirectoryReadSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the data response. */
|
||||
R_TRY(db.Commit());
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested info for. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Build info about the object. */
|
||||
PtpObjectInfo object_info(DefaultObjectInfo);
|
||||
|
||||
if (object_id == StorageId_SdmcFs) {
|
||||
/* The SD Card directory has some special properties. */
|
||||
object_info.object_format = PtpObjectFormatCode_Association;
|
||||
object_info.association_type = PtpAssociationType_GenericFolder;
|
||||
object_info.filename = "SD Card";
|
||||
} else {
|
||||
/* Figure out what type of object this is. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Get the size, if we are requesting info about a file. */
|
||||
s64 size = 0;
|
||||
if (entry_type == FsDirEntryType_File) {
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
|
||||
}
|
||||
|
||||
object_info.filename = std::strrchr(obj->GetName(), '/') + 1;
|
||||
object_info.object_compressed_size = size;
|
||||
object_info.parent_object = obj->GetParentId();
|
||||
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
object_info.object_format = PtpObjectFormatCode_Association;
|
||||
object_info.association_type = PtpAssociationType_GenericFolder;
|
||||
} else {
|
||||
object_info.object_format = PtpObjectFormatCode_Undefined;
|
||||
object_info.association_type = PtpAssociationType_Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the object info data. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
|
||||
R_TRY(db.Add(object_info.storage_id));
|
||||
R_TRY(db.Add(object_info.object_format));
|
||||
R_TRY(db.Add(object_info.protection_status));
|
||||
R_TRY(db.Add(object_info.object_compressed_size));
|
||||
R_TRY(db.Add(object_info.thumb_format));
|
||||
R_TRY(db.Add(object_info.thumb_compressed_size));
|
||||
R_TRY(db.Add(object_info.thumb_width));
|
||||
R_TRY(db.Add(object_info.thumb_height));
|
||||
R_TRY(db.Add(object_info.image_width));
|
||||
R_TRY(db.Add(object_info.image_height));
|
||||
R_TRY(db.Add(object_info.image_depth));
|
||||
R_TRY(db.Add(object_info.parent_object));
|
||||
R_TRY(db.Add(object_info.association_type));
|
||||
R_TRY(db.Add(object_info.association_desc));
|
||||
R_TRY(db.Add(object_info.sequence_number));
|
||||
R_TRY(db.AddString(object_info.filename));
|
||||
R_TRY(db.AddString(object_info.capture_date));
|
||||
R_TRY(db.AddString(object_info.modification_date));
|
||||
R_TRY(db.AddString(object_info.keywords));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObject(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 size = 0;
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
|
||||
|
||||
/* Send the header and file size. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, size));
|
||||
|
||||
/* Begin reading the file, writing data to the builder as we progress. */
|
||||
s64 offset = 0;
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
u64 bytes_read;
|
||||
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
|
||||
|
||||
offset += bytes_read;
|
||||
|
||||
/* Write to output. */
|
||||
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
|
||||
|
||||
/* If we read fewer bytes than the batch size, we're done. */
|
||||
if (bytes_read < FsBufferSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the data response. */
|
||||
R_TRY(db.Commit());
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
|
||||
/* Get the storage ID and parent object and flush the request packet. */
|
||||
u32 storage_id, parent_object;
|
||||
R_TRY(rdp.Read(std::addressof(storage_id)));
|
||||
R_TRY(rdp.Read(std::addressof(parent_object)));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
PtpObjectInfo info(DefaultObjectInfo);
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Read in the object info. */
|
||||
R_TRY(dp.Read(std::addressof(info.storage_id)));
|
||||
R_TRY(dp.Read(std::addressof(info.object_format)));
|
||||
R_TRY(dp.Read(std::addressof(info.protection_status)));
|
||||
R_TRY(dp.Read(std::addressof(info.object_compressed_size)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_format)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_compressed_size)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_width)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_height)));
|
||||
R_TRY(dp.Read(std::addressof(info.image_width)));
|
||||
R_TRY(dp.Read(std::addressof(info.image_height)));
|
||||
R_TRY(dp.Read(std::addressof(info.image_depth)));
|
||||
R_TRY(dp.Read(std::addressof(info.parent_object)));
|
||||
R_TRY(dp.Read(std::addressof(info.association_type)));
|
||||
R_TRY(dp.Read(std::addressof(info.association_desc)));
|
||||
R_TRY(dp.Read(std::addressof(info.sequence_number)));
|
||||
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
|
||||
R_TRY(dp.ReadString(m_buffers->capture_date_string_buffer));
|
||||
R_TRY(dp.ReadString(m_buffers->modification_date_string_buffer));
|
||||
R_TRY(dp.ReadString(m_buffers->keywords_string_buffer));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Rewrite requests for creating in storage directories. */
|
||||
if (parent_object == PtpGetObjectHandles_RootParent) {
|
||||
parent_object = storage_id;
|
||||
}
|
||||
|
||||
/* Check if we know about the parent object. If we don't, it's an error. */
|
||||
auto * const parentobj = m_object_database.GetObjectById(parent_object);
|
||||
R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Make a new object with the intended name. */
|
||||
PtpNewObjectInfo new_object_info;
|
||||
new_object_info.storage_id = StorageId_SdmcFs;
|
||||
new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object;
|
||||
|
||||
/* Create the object in the database. */
|
||||
PtpObject *obj;
|
||||
R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), m_buffers->filename_string_buffer, parentobj->GetObjectId(), std::addressof(obj)));
|
||||
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); };
|
||||
|
||||
/* Register the object with a new ID. */
|
||||
m_object_database.RegisterObject(obj);
|
||||
new_object_info.object_id = obj->GetObjectId();
|
||||
|
||||
/* Create the object on the filesystem. */
|
||||
if (info.object_format == PtpObjectFormatCode_Association) {
|
||||
R_TRY(m_fs.CreateDirectory(obj->GetName()));
|
||||
m_send_object_id = 0;
|
||||
} else {
|
||||
R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0));
|
||||
m_send_object_id = new_object_info.object_id;
|
||||
}
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendObject(PtpDataParser &rdp) {
|
||||
/* Reset SendObject object ID on exit. */
|
||||
ON_SCOPE_EXIT { m_send_object_id = 0; };
|
||||
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Truncate the file after locking for write. */
|
||||
s64 offset = 0;
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), 0));
|
||||
|
||||
/* Expand to the needed size. */
|
||||
if (data_header.length > sizeof(PtpUsbBulkContainer)) {
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer)));
|
||||
}
|
||||
|
||||
/* Begin writing to the filesystem. */
|
||||
while (true) {
|
||||
/* Read as many bytes as we can. */
|
||||
u32 bytes_received;
|
||||
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
|
||||
|
||||
/* Write to the file. */
|
||||
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_received, 0));
|
||||
|
||||
offset += bytes_received;
|
||||
|
||||
/* If we received fewer bytes than the batch size, we're done. */
|
||||
if (haze::ResultEndOfTransmission::Includes(read_res)) {
|
||||
break;
|
||||
}
|
||||
|
||||
R_TRY(read_res);
|
||||
}
|
||||
|
||||
/* Truncate the file to the received size. */
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), offset));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
|
||||
/* Get the object ID and flush the request packet. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Disallow deleting the storage root. */
|
||||
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Figure out what type of object this is. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Remove the object from the filesystem. */
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName()));
|
||||
} else {
|
||||
R_TRY(m_fs.DeleteFile(obj->GetName()));
|
||||
}
|
||||
|
||||
/* Remove the object from the database. */
|
||||
m_object_database.DeleteObject(obj);
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const u32 DefaultInterfaceNumber = 0;
|
||||
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize1x(const UsbCommsInterfaceInfo *info) {
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = DefaultInterfaceNumber,
|
||||
.bInterfaceClass = info->bInterfaceClass,
|
||||
.bInterfaceSubClass = info->bInterfaceSubClass,
|
||||
.bInterfaceProtocol = info->bInterfaceProtocol,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_interrupt = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_INTERRUPT,
|
||||
.wMaxPacketSize = 0x18,
|
||||
.bInterval = 0x4,
|
||||
};
|
||||
|
||||
/* Set up interface. */
|
||||
R_TRY(usbDsGetDsInterface(std::addressof(m_interface), std::addressof(interface_descriptor), "usb"));
|
||||
|
||||
/* Set up endpoints. */
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), std::addressof(endpoint_descriptor_in)));
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), std::addressof(endpoint_descriptor_out)));
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), std::addressof(endpoint_descriptor_interrupt)));
|
||||
|
||||
R_RETURN(usbDsInterface_EnableInterface(m_interface));
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize5x(const UsbCommsInterfaceInfo *info) {
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = DefaultInterfaceNumber,
|
||||
.bNumEndpoints = 3,
|
||||
.bInterfaceClass = info->bInterfaceClass,
|
||||
.bInterfaceSubClass = info->bInterfaceSubClass,
|
||||
.bInterfaceProtocol = info->bInterfaceProtocol,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_interrupt = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_INTERRUPT,
|
||||
.wMaxPacketSize = 0x18,
|
||||
.bInterval = 0x6,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x0f,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion_interrupt = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x00,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
R_TRY(usbDsRegisterInterface(std::addressof(m_interface)));
|
||||
|
||||
u8 iInterface;
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iInterface), "MTP"));
|
||||
|
||||
interface_descriptor.bInterfaceNumber = m_interface->interface_index;
|
||||
interface_descriptor.iInterface = iInterface;
|
||||
endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_interrupt.bEndpointAddress += interface_descriptor.bInterfaceNumber + 2;
|
||||
|
||||
/* High speed config. */
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE));
|
||||
|
||||
/* Super speed config. */
|
||||
endpoint_descriptor_in.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength;
|
||||
endpoint_descriptor_out.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength;
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion_interrupt), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
|
||||
/* Set up endpoints. */
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), endpoint_descriptor_in.bEndpointAddress));
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), endpoint_descriptor_out.bEndpointAddress));
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), endpoint_descriptor_interrupt.bEndpointAddress));
|
||||
|
||||
R_RETURN(usbDsInterface_EnableInterface(m_interface));
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize(const UsbCommsInterfaceInfo *info, u16 id_vendor, u16 id_product) {
|
||||
R_TRY(usbDsInitialize());
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0)) {
|
||||
/* Report language as US English. */
|
||||
static const u16 supported_langs[1] = { 0x0409 };
|
||||
R_TRY(usbDsAddUsbLanguageStringDescriptor(nullptr, supported_langs, util::size(supported_langs)));
|
||||
|
||||
/* Report strings. */
|
||||
u8 iManufacturer, iProduct, iSerialNumber;
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iManufacturer), "Nintendo"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iProduct), "Nintendo Switch"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iSerialNumber), GetSerialNumber()));
|
||||
|
||||
/* Send device descriptors */
|
||||
struct usb_device_descriptor device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = id_vendor,
|
||||
.idProduct = id_product,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = iManufacturer,
|
||||
.iProduct = iProduct,
|
||||
.iSerialNumber = iSerialNumber,
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, std::addressof(device_descriptor)));
|
||||
|
||||
device_descriptor.bcdUSB = 0x0300;
|
||||
device_descriptor.bMaxPacketSize0 = 0x09;
|
||||
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, std::addressof(device_descriptor)));
|
||||
|
||||
/* Binary Object Store */
|
||||
u8 bos[0x16] = {
|
||||
0x05, /* .bLength */
|
||||
USB_DT_BOS, /* .bDescriptorType */
|
||||
0x16, 0x00, /* .wTotalLength */
|
||||
0x02, /* .bNumDeviceCaps */
|
||||
|
||||
/* USB 2.0 */
|
||||
0x07, /* .bLength */
|
||||
USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */
|
||||
0x02, /* .bDevCapabilityType */
|
||||
0x02, 0x00, 0x00, 0x00, /* .bmAttributes */
|
||||
|
||||
/* USB 3.0 */
|
||||
0x0a, /* .bLength */
|
||||
USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */
|
||||
0x03, /* .bDevCapabilityType */
|
||||
0x00, /* .bmAttributes */
|
||||
0x0c, 0x00, /* .wSpeedSupported */
|
||||
0x03, /* .bFunctionalitySupport */
|
||||
0x00, /* .bU1DevExitLat */
|
||||
0x00, 0x00 /* .bU2DevExitLat */
|
||||
};
|
||||
R_TRY(usbDsSetBinaryObjectStore(bos, sizeof(bos)));
|
||||
}
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0)) {
|
||||
R_TRY(this->Initialize5x(info));
|
||||
R_TRY(usbDsEnable());
|
||||
} else {
|
||||
R_TRY(this->Initialize1x(info));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UsbSession::Finalize() {
|
||||
usbDsExit();
|
||||
}
|
||||
|
||||
bool UsbSession::GetConfigured() const {
|
||||
UsbState usb_state;
|
||||
|
||||
HAZE_R_ABORT_UNLESS(usbDsGetState(std::addressof(usb_state)));
|
||||
|
||||
return usb_state == UsbState_Configured;
|
||||
}
|
||||
|
||||
Event *UsbSession::GetCompletionEvent(UsbSessionEndpoint ep) const {
|
||||
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
||||
}
|
||||
|
||||
Result UsbSession::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) {
|
||||
R_RETURN(usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id));
|
||||
}
|
||||
|
||||
Result UsbSession::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size) {
|
||||
UsbDsReportData report_data;
|
||||
|
||||
R_TRY(eventClear(std::addressof(m_endpoints[ep]->CompletionEvent)));
|
||||
R_TRY(usbDsEndpoint_GetReportData(m_endpoints[ep], std::addressof(report_data)));
|
||||
R_TRY(usbDsParseReportData(std::addressof(report_data), urb_id, nullptr, out_transferred_size));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user