Initial port configuration (needs cleanup)

This commit is contained in:
Phillip Stephens 2022-05-08 01:50:21 -07:00
parent f8d9207aaa
commit 10d4bbf297
Signed by: Antidote
GPG Key ID: F8BEE4C83DACA60D
8 changed files with 334 additions and 91 deletions

View File

@ -89,6 +89,7 @@ set(RUNTIME_SOURCES_B
Streams/ContainerReaders.hpp
Streams/CTextInStream.hpp Streams/CTextInStream.cpp
Streams/CTextOutStream.hpp Streams/CTextOutStream.cpp
Streams/CFileOutStream.hpp Streams/CFileOutStream.cpp
CGameAllocator.hpp CGameAllocator.cpp
CMemoryCardSys.hpp CMemoryCardSys.cpp
CScannableObjectInfo.hpp CScannableObjectInfo.cpp

View File

@ -1,15 +1,64 @@
#include "Runtime/ImGuiControllerConfig.hpp"
#include "Runtime/Streams/CFileOutStream.hpp"
#include "Runtime/Streams/ContainerReaders.hpp"
#include "Runtime/Streams/ContainerWriters.hpp"
#include "aurora/pad.hpp"
#include "aurora/aurora.hpp"
#include <imgui.h>
namespace metaforce {
ImGuiControllerConfig::Button::Button(CInputStream& in)
: button(in.Get<s32>())
, uvX(in.Get<u32>())
, uvY(in.Get<u32>())
, width(in.Get<u32>())
, height(in.Get<u32>())
, offX(in.Get<float>())
, offY(in.Get<float>()) {}
void ImGuiControllerConfig::Button::PutTo(COutputStream& out) const {
out.Put(button);
out.Put(uvX);
out.Put(uvY);
out.Put(width);
out.Put(height);
out.Put(offX);
out.Put(offY);
}
ImGuiControllerConfig::ControllerAtlas::ControllerAtlas(CInputStream& in) : name(in.Get<std::string>()) {
u32 vidPidCount = in.Get<u32>();
vidPids.reserve(vidPidCount);
for (u32 i = 0; i < vidPidCount; ++i) {
u16 vid = static_cast<u16>(in.Get<s16>());
u16 pid = static_cast<u16>(in.Get<s16>());
vidPids.emplace_back(vid, pid);
}
atlasFile = in.Get<std::string>();
read_vector(buttons, in);
};
void ImGuiControllerConfig::ControllerAtlas::PutTo(COutputStream& out) const {
out.Put(name);
out.Put(static_cast<u32>(vidPids.size()));
for (const auto& vidPid : vidPids) {
out.Put(vidPid.first);
out.Put(vidPid.second);
}
write_vector(buttons, out);
}
void ImGuiControllerConfig::show(bool& visible) {
/** TODO:
* - Implement multiple controllers
* - Implement setting controller ports
* - Implement setting controller ports (except for the GameCube adapter, which is hard coded)
* - Implement fancy graphical UI
*/
@ -18,56 +67,127 @@ void ImGuiControllerConfig::show(bool& visible) {
}
if (m_pendingMapping != nullptr) {
s32 nativeButton = PADGetNativeButtonPressed(0);
s32 nativeButton = PADGetNativeButtonPressed(m_pendingPort);
if (nativeButton != -1) {
m_pendingMapping->nativeButton = nativeButton;
m_pendingMapping = nullptr;
m_pendingPort = -1;
PADBlockInput(false);
}
}
std::vector<std::string> controllers;
controllers.push_back("None");
for (u32 i = 0; i < PADCount(); ++i) {
controllers.push_back(fmt::format(FMT_STRING("{}-{}"), PADGetNameForControllerIndex(i), i));
}
m_pendingValid = false;
if (ImGui::Begin("Controller Config", &visible)) {
ImGui::Text("%s", PADGetName(0));
u32 buttonCount = 0;
PADButtonMapping* mapping = PADGetButtonMappings(0, &buttonCount);
if (mapping != nullptr) {
for (u32 i = 0; i < buttonCount; ++i) {
bool pressed = ImGui::Button(PADGetButtonName(mapping[i].padButton));
ImGui::SameLine();
ImGui::Text("%s", PADGetNativeButtonName(mapping[i].nativeButton));
if (pressed && m_pendingMapping == nullptr) {
m_pendingMapping = &mapping[i];
PADBlockInput(true);
if (ImGui::CollapsingHeader("Ports")) {
for (u32 i = 0; i < 4; ++i) {
ImGui::PushID(fmt::format(FMT_STRING("PortConf-{}"), i).c_str());
s32 index = PADGetIndexForPort(i);
int sel = 0;
std::string name = "None";
const char* tmpName = PADGetName(i);
bool changed = false;
if (tmpName != nullptr) {
name = fmt::format(FMT_STRING("{}-{}"), tmpName, index);
}
if (ImGui::BeginCombo(fmt::format(FMT_STRING("Port {}"), i + 1).c_str(), name.c_str())) {
for (u32 j = 0; const auto& s : controllers) {
if (ImGui::Selectable(s.c_str(), name == s)) {
sel = j;
changed = true;
}
++j;
}
ImGui::EndCombo();
}
if (m_pendingMapping == &mapping[i]) {
ImGui::SameLine();
ImGui::Text(" - Waiting for button...");
if (changed) {
if (sel > 0) {
PADSetPortForIndex(sel - 1, i);
} else if (sel == 0) {
PADClearPort(i);
}
}
ImGui::PopID();
}
}
if (ImGui::BeginTabBar("Controllers")) {
for (u32 i = 0; i < 4; ++i) {
if (ImGui::BeginTabItem(fmt::format(FMT_STRING("Port {}"), i + 1).c_str())) {
ImGui::PushID(fmt::format(FMT_STRING("Port_{}"), i + 1).c_str());
/* If the tab is changed while pending for input, cancel the pending port */
if (m_pendingMapping != nullptr && m_pendingPort != i) {
m_pendingMapping = nullptr;
m_pendingValid = false;
m_pendingPort = -1;
}
u32 vid, pid;
PADGetVidPid(i, &vid, &pid);
if (vid == 0 && pid == 0) {
ImGui::EndTabItem();
ImGui::PopID();
continue;
}
ImGui::Text("%s", PADGetName(i));
u32 buttonCount = 0;
PADButtonMapping* mapping = PADGetButtonMappings(i, &buttonCount);
if (mapping != nullptr) {
for (u32 m = 0; m < buttonCount; ++m) {
const char* padName = PADGetButtonName(mapping[m].padButton);
if (padName == nullptr) {
continue;
}
ImGui::PushID(padName);
bool pressed = ImGui::Button(padName);
ImGui::SameLine();
ImGui::Text("%s", PADGetNativeButtonName(mapping[m].nativeButton));
if (ImGui::CollapsingHeader("Dead-zones")) {
PADDeadZones* deadZones = PADGetDeadZones(0);
ImGui::Checkbox("Use Dead-zones", &deadZones->useDeadzones);
s32 tmp = deadZones->stickDeadZone;
if (ImGui::DragInt("Left Stick", &tmp)) {
deadZones->stickDeadZone = tmp;
}
tmp = deadZones->substickDeadZone;
if (ImGui::DragInt("Right Stick", &tmp)) {
deadZones->substickDeadZone = tmp;
}
ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers);
tmp = deadZones->leftTriggerActivationZone;
if (ImGui::DragInt("Left Trigger Activation", &tmp)) {
deadZones->leftTriggerActivationZone = tmp;
}
tmp = deadZones->rightTriggerActivationZone;
if (ImGui::DragInt("Right Trigger Activation", &tmp)) {
deadZones->rightTriggerActivationZone = tmp;
if (pressed && m_pendingMapping == nullptr) {
m_pendingMapping = &mapping[m];
m_pendingPort = i;
PADBlockInput(true);
}
if (m_pendingMapping == &mapping[m]) {
m_pendingValid = true;
ImGui::SameLine();
ImGui::Text(" - Waiting for button...");
}
ImGui::PopID();
}
}
if (ImGui::CollapsingHeader("Dead-zones")) {
PADDeadZones* deadZones = PADGetDeadZones(i);
ImGui::Checkbox("Use Dead-zones", &deadZones->useDeadzones);
float tmp = static_cast<float>(deadZones->stickDeadZone * 100.f) / 32767.f;
if (ImGui::DragFloat("Left Stick", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->stickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);
}
tmp = static_cast<float>(deadZones->substickDeadZone * 100.f) / 32767.f;
if (ImGui::DragFloat("Right Stick", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->substickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);
}
ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers);
tmp = static_cast<float>(deadZones->leftTriggerActivationZone * 100.f) / 32767.f;
if (ImGui::DragFloat("Left Trigger Activation", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->leftTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);
}
tmp = static_cast<float>(deadZones->rightTriggerActivationZone * 100.f) / 32767.f;
if (ImGui::DragFloat("Right Trigger Activation", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
deadZones->rightTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);
}
}
ImGui::PopID();
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
ImGui::Separator();
@ -96,6 +216,23 @@ void ImGuiControllerConfig::showEditor(bool& visible) {
}
if (ImGui::Begin("Controller Atlas Editor", &visible)) {
/* TODO: Atlas editor */
ImGui::Separator();
if (ImGui::Button("Save Controller Database")) {
CFileOutStream out("ControllerAtlases.ctrdb");
out.WriteUint32(SLITTLE('CTDB'));
out.WriteUint32(1); // Version
write_vector(m_controllerAtlases, out);
}
ImGui::SameLine();
if (ImGui::Button("Export") && m_currentAtlas != nullptr) {
CFileOutStream out("test.ctratlas");
out.Put(SLITTLE('CTRA'));
out.Put(1); // Version
out.Put(*m_currentAtlas);
}
/* TODO: Import logic */
}
ImGui::End();
}

View File

@ -1,6 +1,8 @@
#pragma once
#include "Runtime/GCNTypes.hpp"
#include "Runtime/Streams/CInputStream.hpp"
#include "Runtime/Streams/COutputStream.hpp"
#include "aurora/pad.hpp"
#include <array>
@ -11,20 +13,28 @@
namespace metaforce {
class ImGuiControllerConfig {
struct Button {
u32 button; // the SDL button this entry corresponds to
u32 uvX; // Offset if icon image in atlas from left (in pixels)
u32 uvY; // Offset if icon image in atlas from top (in pixels)
u32 width; // Width of button image (in pixels)
u32 height; // Height of button image (in pixels)
float offX; // Offset from left of config window
float offY; // Offset from top of config window
s32 button = -1; // the SDL button this entry corresponds to
u32 uvX = 0; // Offset if icon image in atlas from left (in pixels)
u32 uvY = 0; // Offset if icon image in atlas from top (in pixels)
u32 width = 32; // Width of button image (in pixels)
u32 height = 32; // Height of button image (in pixels)
float offX = 0.f; // Offset from left of config window
float offY = 0.f; // Offset from top of config window
Button() = default;
explicit Button(CInputStream& in);
void PutTo(COutputStream& in) const;
};
struct ControllerMapping {
struct ControllerAtlas {
std::string name;
std::pair<u32, u32> vidPid;
std::vector<std::pair<u16, u16>> vidPids;
std::string atlasFile; // Path to atlas relative to controller definition
std::vector<Button> buttons;
ControllerAtlas() = default;
explicit ControllerAtlas(CInputStream& in);
void PutTo(COutputStream& out) const;
};
public:
@ -34,9 +44,12 @@ private:
void showEditor(bool& visible);
PADButtonMapping* m_pendingMapping = nullptr;
s32 m_pendingPort = 0;
bool m_pendingValid = false;
bool m_editorVisible = false;
std::array<ControllerMapping*, 4> m_controllers;
std::vector<ControllerMapping> m_mappings;
ControllerAtlas* m_currentAtlas = nullptr;
std::vector<ControllerAtlas> m_controllerAtlases;
};
} // namespace metaforce

View File

@ -30,8 +30,11 @@ void CInputGenerator::Update(float dt, CArchitectureQueue& queue) {
if (i == 0) {
firstController = true;
}
m_lastInput = CFinalInput(i, dt, cont, xc_leftDiv, x10_rightDiv);
queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, m_lastInput));
auto tmp = CFinalInput(i, dt, cont, xc_leftDiv, x10_rightDiv);
if (i == 0) {
m_lastInput = tmp;
}
queue.Push(MakeMsg::CreateUserInput(EArchMsgTarget::Game, tmp));
++availSlot;
}

View File

@ -0,0 +1,21 @@
#include "Runtime/Streams/CFileOutStream.hpp"
namespace metaforce {
CFileOutStream::CFileOutStream(std::string_view name, u32 blockLen) : COutputStream(blockLen) {
m_file = fopen(name.data(), "wbe");
}
CFileOutStream::~CFileOutStream() {
Flush();
if (m_file) {
fclose(m_file);
}
}
void CFileOutStream::Write(const u8* ptr, u32 len) {
if (!m_file) {
return;
}
fwrite(ptr, 1, len, m_file);
}
} // namespace metaforce

View File

@ -0,0 +1,16 @@
#pragma once
#include "Runtime/Streams/COutputStream.hpp"
#include <cstdio>
namespace metaforce {
class CFileOutStream final : public COutputStream {
FILE* m_file;
public:
explicit CFileOutStream(std::string_view name, u32 blockLen = 4096);
virtual ~CFileOutStream();
protected:
void Write(const u8* ptr, u32 len);
};
} // namespace metaforce

View File

@ -97,9 +97,15 @@ struct PADButtonMapping {
PAD::BUTTON padButton;
};
s32 PADGetCount();
void PADGetVidPid(u32 idx, u32* vid, u32* pid);
const char* PADGetName(u32 idx);
/* Returns the total number of controllers */
u32 PADCount();
/* Returns the controller name for the given index into the controller map */
const char* PADGetNameForControllerIndex(u32 idx);
void PADSetPortForIndex(u32 index, s32 port);
s32 PADGetIndexForPort(u32 port);
void PADGetVidPid(u32 port, u32* vid, u32* pid);
void PADClearPort(u32 port);
const char* PADGetName(u32 port);
void PADSetButtonMapping(u32 port, PADButtonMapping mapping);
void PADSetAllButtonMappings(u32 port, PADButtonMapping buttons[12]);
PADButtonMapping* PADGetButtonMappings(u32 port, u32* buttonCount);

View File

@ -22,7 +22,7 @@ struct GameController {
PADDeadZones m_deadZones;
u16 m_vid = 0;
u16 m_pid = 0;
std::array<PADButtonMapping, 12> m_mapping;
std::array<PADButtonMapping, 12> m_mapping{};
bool m_mappingLoaded = false;
constexpr bool operator==(const GameController& other) const {
return m_controller == other.m_controller && m_index == other.m_index;
@ -37,20 +37,24 @@ GameController* get_controller_for_player(u32 player) noexcept {
}
}
#if 0
/* If we don't have a controller assigned to this port use the first unassigned controller */
if (!g_GameControllers.empty()) {
int32_t availIndex = -1;
for (const auto& controller : g_GameControllers) {
if (player_index(controller.second.m_index) == -1) {
availIndex = controller.second.m_index;
GameController* ct = nullptr;
for (auto& controller : g_GameControllers) {
if (player_index(controller.first) == -1) {
availIndex = controller.first;
ct = &controller.second;
break;
}
}
if (availIndex != -1) {
set_player_index(g_GameControllers.begin()->second.m_index, player);
return get_controller_for_player(player);
set_player_index(availIndex, player);
return ct;
}
}
#endif
return nullptr;
}
@ -389,6 +393,71 @@ static const std::array<PADButtonMapping, 12> mDefaultButtons{{
void PADSetSpec(s32 spec) {}
void PADInit() {}
aurora::input::GameController* __PADGetControllerForIndex(u32 idx) {
if (idx >= aurora::input::g_GameControllers.size()) {
return nullptr;
}
u32 tmp = 0;
auto iter = aurora::input::g_GameControllers.begin();
while (tmp < idx) {
++iter;
++tmp;
}
if (iter == aurora::input::g_GameControllers.end()) {
return nullptr;
}
return &iter->second;
}
u32 PADCount() { return aurora::input::g_GameControllers.size(); }
const char* PADGetNameForControllerIndex(u32 idx) {
auto* ctrl = __PADGetControllerForIndex(idx);
if (ctrl == nullptr) {
return nullptr;
}
return SDL_GameControllerName(ctrl->m_controller);
}
void PADSetPortForIndex(u32 idx, s32 port) {
auto* ctrl = __PADGetControllerForIndex(idx);
auto* dest = aurora::input::get_controller_for_player(port);
if (ctrl == nullptr) {
return;
}
if (dest != nullptr) {
SDL_GameControllerSetPlayerIndex(dest->m_controller, -1);
}
SDL_GameControllerSetPlayerIndex(ctrl->m_controller, port);
}
s32 PADGetIndexForPort(u32 port) {
auto* ctrl = aurora::input::get_controller_for_player(port);
if (ctrl == nullptr) {
return -1;
}
s32 index = 0;
for (auto iter = aurora::input::g_GameControllers.begin(); iter != aurora::input::g_GameControllers.end();
++iter, ++index) {
if (&iter->second == ctrl) {
break;
}
}
return index;
}
void PADClearPort(u32 port) {
auto* ctrl = aurora::input::get_controller_for_player(port);
if (ctrl == nullptr) {
return;
}
SDL_GameControllerSetPlayerIndex(ctrl->m_controller, -1);
}
void __PADLoadMapping(aurora::input::GameController* controller) {
s32 playerIndex = SDL_GameControllerGetPlayerIndex(controller->m_controller);
if (playerIndex == -1) {
@ -402,11 +471,9 @@ void __PADLoadMapping(aurora::input::GameController* controller) {
controller->m_mappingLoaded = true;
FILE* file =
fopen(fmt::format(FMT_STRING("{}/{}_{:04X}_{:04X}.controller"), basePath,
aurora::input::controller_name(controller->m_index), controller->m_vid, controller->m_pid)
.c_str(),
"rbe");
auto path = fmt::format(FMT_STRING("{}/{}_{:04X}_{:04X}.controller"), basePath, PADGetName(playerIndex),
controller->m_vid, controller->m_pid);
FILE* file = fopen(path.c_str(), "rbe");
if (file == nullptr) {
return;
}
@ -434,7 +501,7 @@ void __PADLoadMapping(aurora::input::GameController* controller) {
}
fread(&controller->m_deadZones, 1, sizeof(PADDeadZones), file);
fread(&controller->m_mapping, 1, sizeof(PADButtonMapping), file);
fread(&controller->m_mapping, 1, sizeof(PADButtonMapping) * controller->m_mapping.size(), file);
fclose(file);
}
@ -455,9 +522,6 @@ u32 PADRead(PAD::Status* status) {
if (!controller->m_mappingLoaded) {
__PADLoadMapping(controller);
#ifndef NDEBUG
PADSerializeMappings();
#endif
}
status[i].xa_err = PAD::ERR_NONE;
std::for_each(controller->m_mapping.begin(), controller->m_mapping.end(),
@ -563,7 +627,7 @@ void PADControlAllMotors(const u32* commands) {
}
u32 SIProbe(s32 chan) {
auto *const controller = aurora::input::get_controller_for_player(chan);
auto* const controller = aurora::input::get_controller_for_player(chan);
if (controller == nullptr) {
return SI::ERROR_NO_RESPONSE;
}
@ -735,26 +799,10 @@ void PADClampCircle(PAD::Status* status) {
}
}
s32 PADGetCount() { return aurora::input::g_GameControllers.size(); }
aurora::input::GameController* __PADGetController(s32 idx) {
auto iter = aurora::input::g_GameControllers.begin();
s32 i = 0;
for (; aurora::input::g_GameControllers.begin() != aurora::input::g_GameControllers.end() && i < idx; ++iter, ++i) {}
if (iter == aurora::input::g_GameControllers.end()) {
return nullptr;
}
return &iter->second;
}
void PADGetVidPid(u32 idx, u32* vid, u32* pid) {
void PADGetVidPid(u32 port, u32* vid, u32* pid) {
*vid = 0;
*pid = 0;
if (idx < 0 || idx >= aurora::input::g_GameControllers.size()) {
return;
}
auto* controller = __PADGetController(idx);
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return;
}
@ -763,8 +811,8 @@ void PADGetVidPid(u32 idx, u32* vid, u32* pid) {
*pid = controller->m_pid;
}
const char* PADGetName(u32 idx) {
auto* controller = __PADGetController(idx);
const char* PADGetName(u32 port) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return nullptr;
}
@ -916,6 +964,4 @@ void PADRestoreDefaultMapping(u32 port) {
controller->m_mapping = mDefaultButtons;
}
void PADBlockInput(bool block) {
gBlockPAD = block;
}
void PADBlockInput(bool block) { gBlockPAD = block; }