diff --git a/libraries/libstratosphere/source/htclow/ctrl/htclow_json.cpp b/libraries/libstratosphere/source/htclow/ctrl/htclow_json.cpp
new file mode 100644
index 000000000..df248060c
--- /dev/null
+++ b/libraries/libstratosphere/source/htclow/ctrl/htclow_json.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018-2020 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 .
+ */
+#include
+#include "htclow_json.hpp"
+
+namespace ams::htclow::ctrl {
+
+ namespace {
+
+ constexpr const char ChannelKey[] = "Chan";
+ constexpr const char ProtocolVersionKey[] = "Prot";
+
+ }
+
+ bool JsonHandler::Key(const Ch *str, rapidjson::SizeType len, bool copy) {
+ if (m_state == State::ParseObject) {
+ if (!util::Strncmp(str, ChannelKey, sizeof(ChannelKey))) {
+ m_state = State::ParseServiceChannels;
+ }
+ if (!util::Strncmp(str, ProtocolVersionKey, sizeof(ProtocolVersionKey))) {
+ m_state = State::ParseProtocolVersion;
+ }
+ }
+ return true;
+ }
+
+ bool JsonHandler::Uint(unsigned val) {
+ if (m_state == State::ParseProtocolVersion) {
+ *m_version = val;
+ }
+ return true;
+ }
+
+ bool JsonHandler::String(const Ch *str, rapidjson::SizeType len, bool copy) {
+ if (m_state == State::ParseServiceChannelsArray && *m_num_strings < m_max_strings) {
+ m_strings[(*m_num_strings)++] = str;
+ }
+ return true;
+ }
+
+ bool JsonHandler::StartObject() {
+ if (m_state == State::Begin) {
+ m_state = State::ParseObject;
+ }
+ return true;
+ }
+
+ bool JsonHandler::EndObject(rapidjson::SizeType) {
+ m_state = State::End;
+ return true;
+ }
+
+ bool JsonHandler::StartArray() {
+ if (m_state == State::ParseServiceChannels) {
+ m_state = State::ParseServiceChannelsArray;
+ }
+ return true;
+ }
+
+ bool JsonHandler::EndArray(rapidjson::SizeType len) {
+ if (m_state == State::ParseServiceChannelsArray && len) {
+ m_state = State::ParseObject;
+ }
+ return true;
+ }
+
+}
diff --git a/libraries/libstratosphere/source/htclow/ctrl/htclow_json.hpp b/libraries/libstratosphere/source/htclow/ctrl/htclow_json.hpp
new file mode 100644
index 000000000..12dc23c4a
--- /dev/null
+++ b/libraries/libstratosphere/source/htclow/ctrl/htclow_json.hpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-2020 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 .
+ */
+#pragma once
+#include
+
+namespace ams::htclow::ctrl {
+
+ class JsonHandler : public rapidjson::BaseReaderHandler, JsonHandler>{
+ private:
+ enum class State {
+ Begin = 0,
+ ParseObject = 1,
+ ParseServiceChannels = 2,
+ ParseServiceChannelsArray = 3,
+ ParseProtocolVersion = 4,
+ End = 5,
+ };
+ private:
+ State m_state;
+ s16 *m_version;
+ const char **m_strings;
+ int *m_num_strings;
+ int m_max_strings;
+ public:
+ JsonHandler(s16 *vers, const char **str, int *ns, int max) : m_state(State::Begin), m_version(vers), m_strings(str), m_num_strings(ns), m_max_strings(max) { /* ... */ }
+
+ bool Key(const Ch *str, rapidjson::SizeType len, bool copy);
+ bool Uint(unsigned);
+ bool String(const Ch *, rapidjson::SizeType, bool);
+
+ bool StartObject();
+ bool EndObject(rapidjson::SizeType);
+ bool StartArray();
+ bool EndArray(rapidjson::SizeType);
+ };
+
+}
diff --git a/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.cpp b/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.cpp
new file mode 100644
index 000000000..2a64382ce
--- /dev/null
+++ b/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018-2020 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 .
+ */
+#include
+#include "htclow_json.hpp"
+#include "htclow_service_channel_parser.hpp"
+
+namespace ams::htclow::ctrl {
+
+ namespace {
+
+ void ParseBody(s16 *out_version, const char **out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size) {
+ /* Create JSON handler. */
+ JsonHandler json_handler(out_version, out_channels, out_num_channels, max_channels);
+
+ /* Create reader. */
+ rapidjson::Reader json_reader;
+
+ /* Create stream. */
+ rapidjson::InsituStringStream json_stream(static_cast(str));
+
+ /* Parse the json. */
+ json_reader.Parse(json_stream, json_handler);
+ }
+
+ constexpr bool IsNumericCharacter(char c) {
+ return '0' <= c && c <= '9';
+ }
+
+ constexpr bool IsValidCharacter(char c) {
+ return IsNumericCharacter(c) || c == ':';
+ }
+
+ bool IntegerToModuleId(ModuleId *out, int id) {
+ switch (id) {
+ case 0:
+ case 1:
+ case 3:
+ case 4:
+ *out = static_cast(id);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool StringToChannel(impl::ChannelInternalType *out, char *str, size_t size) {
+ enum class State {
+ Begin,
+ ModuleId,
+ Sep1,
+ Reserved,
+ Sep2,
+ ChannelId
+ };
+
+ State state = State::Begin;
+
+ const char * cur = nullptr;
+
+ const char * const str_end = str + size;
+ while (str < str_end && IsValidCharacter(*str)) {
+ const char c = *str;
+
+ switch (state) {
+ case State::Begin:
+ if (IsNumericCharacter(c)) {
+ cur = str;
+ state = State::ModuleId;
+ }
+ break;
+ case State::ModuleId:
+ if (c == ':') {
+ *str = 0;
+ if (!IntegerToModuleId(std::addressof(out->module_id), atoi(cur))) {
+ return false;
+ }
+ state = State::Sep1;
+ } else if (!IsNumericCharacter(c)) {
+ return false;
+ }
+ break;
+ case State::Sep1:
+ if (IsNumericCharacter(c)) {
+ cur = str;
+ state = State::Reserved;
+ }
+ break;
+ case State::Reserved:
+ if (c == ':') {
+ *str = 0;
+ out->reserved = 0;
+ state = State::Sep2;
+ } else if (!IsNumericCharacter(c)) {
+ return false;
+ }
+ break;
+ case State::Sep2:
+ if (IsNumericCharacter(c)) {
+ cur = str;
+ state = State::ChannelId;
+ }
+ break;
+ case State::ChannelId:
+ if (!IsNumericCharacter(c)) {
+ return false;
+ }
+ break;
+ }
+
+ ++str;
+ }
+
+ if (str != str_end) {
+ return false;
+ }
+
+ out->channel_id = atoi(cur);
+
+ return true;
+ }
+
+ }
+
+ void ParseServiceChannel(s16 *out_version, impl::ChannelInternalType *out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size) {
+ /* Parse the JSON. */
+ const char *channel_strs[0x20];
+ int num_channels;
+ ParseBody(out_version, channel_strs, std::addressof(num_channels), util::size(channel_strs), str, str_size);
+
+ /* Parse the channel strings. */
+ char * const str_end = static_cast(str) + str_size;
+ int parsed_channels = 0;
+ for (auto i = 0; i < num_channels && i < max_channels; ++i) {
+ impl::ChannelInternalType channel;
+ if (StringToChannel(std::addressof(channel), const_cast(channel_strs[i]), util::Strnlen(channel_strs[i], str_end - channel_strs[i]))) {
+ out_channels[parsed_channels++] = channel;
+ }
+ }
+
+ *out_num_channels = parsed_channels;
+ }
+
+}
diff --git a/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.hpp b/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.hpp
new file mode 100644
index 000000000..1b0c1ec8e
--- /dev/null
+++ b/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018-2020 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 .
+ */
+#pragma once
+#include
+
+namespace ams::htclow::ctrl {
+
+ void ParseServiceChannel(s16 *out_version, impl::ChannelInternalType *out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size);
+
+}
diff --git a/libraries/libvapours/include/vapours/util/util_string_util.hpp b/libraries/libvapours/include/vapours/util/util_string_util.hpp
index 9abe8aaab..a70f0cae4 100644
--- a/libraries/libvapours/include/vapours/util/util_string_util.hpp
+++ b/libraries/libvapours/include/vapours/util/util_string_util.hpp
@@ -88,4 +88,17 @@ namespace ams::util {
return static_cast(cur - src);
}
+ template
+ constexpr int Strnlen(const T *str, int count) {
+ AMS_ASSERT(str != nullptr);
+ AMS_ASSERT(count >= 0);
+
+ int length = 0;
+ while (count-- && *str++) {
+ ++length;
+ }
+
+ return length;
+ }
+
}