Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More camera emulation #376

Merged
merged 3 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions include/services/cam.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,39 @@
class Kernel;

class CAMService {
using Event = std::optional<Handle>;

struct Port {
Event bufferErrorInterruptevent = std::nullopt;
u16 transferBytes;

void reset() {
bufferErrorInterruptevent = std::nullopt;
transferBytes = 256;
}
};

Handle handle = KernelHandles::CAM;
Memory& mem;
Kernel& kernel;
MAKE_LOG_FUNCTION(log, camLogger)

using Event = std::optional<Handle>;
static constexpr size_t portCount = 4; // PORT_NONE, PORT_CAM1, PORT_CAM2, PORT_BOTH
std::array<Event, portCount> bufferErrorInterruptEvents;
static constexpr size_t portCount = 2;
std::array<Port, portCount> ports;

// Service commands
void driverInitialize(u32 messagePointer);
void driverFinalize(u32 messagePointer);
void getMaxLines(u32 messagePointer);
void getBufferErrorInterruptEvent(u32 messagePointer);
void getSuitableY2RCoefficients(u32 messagePointer);
void getTransferBytes(u32 messagePointer);
void setContrast(u32 messagePointer);
void setFrameRate(u32 messagePointer);
void setSize(u32 messagePointer);
void setTransferLines(u32 messagePointer);
void setTrimming(u32 messagePointer);
void setTrimmingParamsCenter(u32 messagePointer);

public:
CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
Expand Down
1 change: 1 addition & 0 deletions include/services/fs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class FSService {
void getFreeBytes(u32 messagePointer);
void getFormatInfo(u32 messagePointer);
void getPriority(u32 messagePointer);
void getSdmcArchiveResource(u32 messagePointer);
void getThisSaveDataSecureValue(u32 messagePointer);
void theGameboyVCFunction(u32 messagePointer);
void initialize(u32 messagePointer);
Expand Down
156 changes: 144 additions & 12 deletions src/core/services/cam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,78 @@ namespace CAMCommands {
enum : u32 {
GetBufferErrorInterruptEvent = 0x00060040,
DriverInitialize = 0x00390000,
DriverFinalize = 0x003A0000,
SetTransferLines = 0x00090100,
GetMaxLines = 0x000A0080,
GetTransferBytes = 0x000C0040,
SetTrimming = 0x000E0080,
SetTrimmingParamsCenter = 0x00120140,
SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module
SetFrameRate = 0x00200080,
SetContrast = 0x00230080,
GetSuitableY2rStandardCoefficient = 0x00360000,
};
}

void CAMService::reset() { bufferErrorInterruptEvents.fill(std::nullopt); }
// Helper struct for working with camera ports
class PortSelect {
u32 value;

public:
PortSelect(u32 val) : value(val) {}
bool isValid() const { return value < 4; }

bool isSinglePort() const {
// 1 corresponds to the first camera port and 2 corresponds to the second port
return value == 1 || value == 2;
}

bool isBothPorts() const {
// 3 corresponds to both ports
return value == 3;
}

// Returns the index of the camera port, assuming that it's only a single port
int getSingleIndex() const {
if (!isSinglePort()) [[unlikely]] {
Helpers::panic("Camera: getSingleIndex called for port with invalid value");
}

return value - 1;
}

std::vector<int> getPortIndices() const {
switch (value) {
case 1: return {0}; // Only port 1
case 2: return {1}; // Only port 2
case 3: return {0, 1}; // Both port 1 and port 2
default: return {}; // No ports or invalid ports
}
}
};

void CAMService::reset() {
for (auto& port : ports) {
port.reset();
}
}

void CAMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
case CAMCommands::DriverFinalize: driverFinalize(messagePointer); break;
case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break;
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
case CAMCommands::GetSuitableY2rStandardCoefficient: getSuitableY2RCoefficients(messagePointer); break;
case CAMCommands::GetTransferBytes: getTransferBytes(messagePointer); break;
case CAMCommands::SetContrast: setContrast(messagePointer); break;
case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break;
case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break;
case CAMCommands::SetTrimming: setTrimming(messagePointer); break;
case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break;
case CAMCommands::SetSize: setSize(messagePointer); break;

default:
Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command);
break;
Expand All @@ -36,6 +90,12 @@ void CAMService::driverInitialize(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}

void CAMService::driverFinalize(u32 messagePointer) {
log("CAM::DriverFinalize\n");
mem.write32(messagePointer, IPC::responseHeader(0x3A, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

void CAMService::setContrast(u32 messagePointer) {
const u32 cameraSelect = mem.read32(messagePointer + 4);
const u32 contrast = mem.read32(messagePointer + 8);
Expand All @@ -47,12 +107,23 @@ void CAMService::setContrast(u32 messagePointer) {
}

void CAMService::setTransferLines(u32 messagePointer) {
const u32 port = mem.read32(messagePointer + 4);
const s16 lines = mem.read16(messagePointer + 8);
const s16 width = mem.read16(messagePointer + 12);
const s16 height = mem.read16(messagePointer + 16);
const u32 portIndex = mem.read32(messagePointer + 4);
const u16 lines = mem.read16(messagePointer + 8);
const u16 width = mem.read16(messagePointer + 12);
const u16 height = mem.read16(messagePointer + 16);
const PortSelect port(portIndex);

if (port.isValid()) {
const u32 transferBytes = lines * width * 2;

for (int i : port.getPortIndices()) {
ports[i].transferBytes = transferBytes;
}
} else {
Helpers::warn("CAM::SetTransferLines: Invalid port\n");
}

log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", port, lines, width, height);
log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", portIndex, lines, width, height);

mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
Expand All @@ -68,6 +139,41 @@ void CAMService::setFrameRate(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}

void CAMService::setSize(u32 messagePointer) {
const u32 cameraSelect = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const u32 context = mem.read32(messagePointer + 12);

log("CAM::SetSize (camera select = %d, size = %d, context = %d)\n", cameraSelect, size, context);

mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

void CAMService::setTrimming(u32 messagePointer) {
const u32 port = mem.read32(messagePointer + 4);
const bool trim = mem.read8(messagePointer + 8) != 0;

log("CAM::SetTrimming (port = %d, trimming = %s)\n", port, trim ? "enabled" : "disabled");

mem.write32(messagePointer, IPC::responseHeader(0x0E, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

void CAMService::setTrimmingParamsCenter(u32 messagePointer) {
const u32 port = mem.read32(messagePointer + 4);
const s16 trimWidth = s16(mem.read16(messagePointer + 8));
const s16 trimHeight = s16(mem.read16(messagePointer + 12));
const s16 cameraWidth = s16(mem.read16(messagePointer + 16));
const s16 cameraHeight = s16(mem.read16(messagePointer + 20));

log("CAM::SetTrimmingParamsCenter (port = %d), trim size = (%d, %d), camera size = (%d, %d)\n", port, trimWidth, trimHeight, cameraWidth,
cameraHeight);

mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

// Algorithm taken from Citra
// https://github.com/citra-emu/citra/blob/master/src/core/hle/service/cam/cam.cpp#L465
void CAMService::getMaxLines(u32 messagePointer) {
Expand Down Expand Up @@ -100,22 +206,48 @@ void CAMService::getMaxLines(u32 messagePointer) {
}
}

void CAMService::getSuitableY2RCoefficients(u32 messagePointer) {
log("CAM::GetSuitableY2RCoefficients\n");
mem.write32(messagePointer, IPC::responseHeader(0x36, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
// Y2R standard coefficient value
mem.write32(messagePointer + 8, 0);
}

void CAMService::getTransferBytes(u32 messagePointer) {
const u32 portIndex = mem.read32(messagePointer + 4);
const PortSelect port(portIndex);
log("CAM::GetTransferBytes (port = %d)\n", portIndex);

mem.write32(messagePointer, IPC::responseHeader(0x0C, 2, 0));
mem.write32(messagePointer + 4, Result::Success);

if (port.isSinglePort()) {
mem.write32(messagePointer + 8, ports[port.getSingleIndex()].transferBytes);
} else {
// TODO: This should return the proper error code
Helpers::warn("CAM::GetTransferBytes: Invalid port index");
mem.write32(messagePointer + 8, 0);
}
}

void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
const u32 port = mem.read32(messagePointer + 4);
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", port);
const u32 portIndex = mem.read32(messagePointer + 4);
const PortSelect port(portIndex);
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", portIndex);

mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2));

if (port >= portCount) {
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
} else {
auto& event = bufferErrorInterruptEvents[port];
if (port.isSinglePort()) {
auto& event = ports[port.getSingleIndex()].bufferErrorInterruptevent;
if (!event.has_value()) {
event = kernel.makeEvent(ResetType::OneShot);
}

mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0);
mem.write32(messagePointer + 12, event.value());
} else {
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
}
}
21 changes: 21 additions & 0 deletions src/core/services/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace FSCommands {
CloseArchive = 0x080E0080,
FormatThisUserSaveData = 0x080F0180,
GetFreeBytes = 0x08120080,
GetSdmcArchiveResource = 0x08140000,
IsSdmcDetected = 0x08170000,
IsSdmcWritable = 0x08180000,
CardSlotIsInserted = 0x08210000,
Expand Down Expand Up @@ -179,6 +180,7 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
case FSCommands::GetPriority: getPriority(messagePointer); break;
case FSCommands::GetSdmcArchiveResource: getSdmcArchiveResource(messagePointer); break;
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
case FSCommands::Initialize: initialize(messagePointer); break;
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
Expand Down Expand Up @@ -764,3 +766,22 @@ void FSService::renameFile(u32 messagePointer) {
const HorizonResult res = sourceArchive->archive->renameFile(sourcePath, destPath);
mem.write32(messagePointer + 4, static_cast<u32>(res));
}

void FSService::getSdmcArchiveResource(u32 messagePointer) {
log("FS::GetSdmcArchiveResource"); // For the time being, return the same stubbed archive resource for every media type

static constexpr ArchiveResource resource = {
.sectorSize = 512,
.clusterSize = 16_KB,
.partitionCapacityInClusters = 0x80000, // 0x80000 * 16 KB = 8GB
.freeSpaceInClusters = 0x80000, // Same here
};

mem.write32(messagePointer, IPC::responseHeader(0x814, 5, 0));
mem.write32(messagePointer + 4, Result::Success);

mem.write32(messagePointer + 8, resource.sectorSize);
mem.write32(messagePointer + 12, resource.clusterSize);
mem.write32(messagePointer + 16, resource.partitionCapacityInClusters);
mem.write32(messagePointer + 20, resource.freeSpaceInClusters);
}
Loading