Atmosphere/stratosphere/sm/source/sm_registration.cpp

633 lines
20 KiB
C++
Raw Normal View History

/*
2019-04-08 02:00:49 +00:00
* Copyright (c) 2018-2019 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/>.
*/
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
#include <switch.h>
#include <algorithm>
#include <stratosphere.hpp>
2018-04-22 06:11:57 +00:00
#include "sm_registration.hpp"
#include "meta_tools.hpp"
2018-04-22 06:11:57 +00:00
static std::array<Registration::Process, REGISTRATION_LIST_MAX_PROCESS> g_process_list = {0};
static std::array<Registration::Service, REGISTRATION_LIST_MAX_SERVICE> g_service_list = {0};
2018-04-22 06:11:57 +00:00
static u64 g_initial_process_id_low = 0;
static u64 g_initial_process_id_high = 0;
static bool g_determined_initial_process_ids = false;
static bool g_end_init_defers = false;
u64 GetServiceNameLength(u64 service) {
u64 service_name_len = 0;
while (service & 0xFF) {
service_name_len++;
service >>= 8;
}
return service_name_len;
}
/* Atmosphere extension utilities. */
void Registration::EndInitDefers() {
g_end_init_defers = true;
}
constexpr u64 EncodeNameConstant(const char *name) {
u64 service = 0;
for (unsigned int i = 0; i < sizeof(service); i++) {
if (name[i] == '\x00') {
break;
}
service |= ((u64)name[i]) << (8 * i);
}
return service;
}
bool Registration::ShouldInitDefer(u64 service) {
/* Only enable if compile-time generated. */
#ifndef SM_ENABLE_INIT_DEFERS
return false;
#endif
if (g_end_init_defers) {
return false;
}
2019-06-17 16:17:53 +00:00
/* This is a mechanism by which certain services will always be deferred until sm:m receives a special command. */
/* This can be extended with more services as needed at a later date. */
constexpr u64 FSP_SRV = EncodeNameConstant("fsp-srv");
return service == FSP_SRV;
}
2018-04-22 06:11:57 +00:00
/* Utilities. */
Registration::Process *Registration::GetProcessForPid(u64 pid) {
auto process_it = std::find_if(g_process_list.begin(), g_process_list.end(), member_equals_fn(&Process::pid, pid));
if (process_it == g_process_list.end()) {
return nullptr;
2018-04-22 06:11:57 +00:00
}
return &*process_it;
2018-04-22 06:11:57 +00:00
}
Registration::Process *Registration::GetFreeProcess() {
return GetProcessForPid(0);
}
Registration::Service *Registration::GetService(u64 service_name) {
auto service_it = std::find_if(g_service_list.begin(), g_service_list.end(), member_equals_fn(&Service::service_name, service_name));
if (service_it == g_service_list.end()) {
return nullptr;
2018-04-22 06:11:57 +00:00
}
return &*service_it;
2018-04-22 06:11:57 +00:00
}
Registration::Service *Registration::GetFreeService() {
return GetService(0);
}
bool Registration::IsValidForSac(u8 *sac, size_t sac_size, u64 service, bool is_host) {
u8 cur_ctrl;
u64 cur_service;
u64 service_for_compare;
2018-04-22 06:11:57 +00:00
bool cur_is_host;
size_t remaining = sac_size;
while (remaining) {
cur_ctrl = *sac++;
remaining--;
size_t cur_size = (cur_ctrl & 7) + 1;
if (cur_size > remaining) {
break;
}
cur_is_host = (cur_ctrl & 0x80) != 0;
cur_service = 0;
for (unsigned int i = 0; i < cur_size; i++) {
cur_service |= ((u64)sac[i]) << (8 * i);
}
/* Check if the last byte is a wildcard ('*') */
service_for_compare = service;
2018-04-22 06:11:57 +00:00
if (sac[cur_size - 1] == '*') {
u64 mask = U64_MAX;
for (unsigned int i = 0; i < 8 - (cur_size - 1); i++) {
mask >>= 8;
}
2018-04-22 06:11:57 +00:00
/* Mask cur_service, service with 0xFF.. up until the wildcard. */
cur_service &= mask;
service_for_compare &= mask;
2018-04-22 06:11:57 +00:00
}
if (cur_service == service_for_compare && (is_host == cur_is_host)) {
2018-04-22 06:11:57 +00:00
return true;
}
sac += cur_size;
remaining -= cur_size;
}
return false;
}
bool Registration::ValidateSacAgainstRestriction(u8 *r_sac, size_t r_sac_size, u8 *sac, size_t sac_size) {
u8 cur_ctrl;
u64 cur_service;
bool cur_is_host;
size_t remaining = sac_size;
while (remaining) {
cur_ctrl = *sac++;
remaining--;
size_t cur_size = (cur_ctrl & 7) + 1;
if (cur_size > remaining) {
break;
}
cur_is_host = (cur_ctrl & 0x80) != 0;
cur_service = 0;
for (unsigned int i = 0; i < cur_size; i++) {
cur_service |= ((u64)sac[i]) << (8 * i);
}
if (!IsValidForSac(r_sac, r_sac_size, cur_service, cur_is_host)) {
return false;
}
sac += cur_size;
remaining -= cur_size;
}
return true;
}
void Registration::CacheInitialProcessIdLimits() {
if (g_determined_initial_process_ids) {
return;
}
2019-05-10 10:25:07 +00:00
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
svcGetSystemInfo(&g_initial_process_id_low, 2, 0, 0);
svcGetSystemInfo(&g_initial_process_id_high, 2, 0, 1);
} else {
g_initial_process_id_low = 0;
g_initial_process_id_high = REGISTRATION_INITIAL_PID_MAX;
}
g_determined_initial_process_ids = true;
}
bool Registration::IsInitialProcess(u64 pid) {
CacheInitialProcessIdLimits();
return g_initial_process_id_low <= pid && pid <= g_initial_process_id_high;
}
u64 Registration::GetInitialProcessId() {
CacheInitialProcessIdLimits();
if (IsInitialProcess(1)) {
return 1;
}
return g_initial_process_id_low;
}
2018-04-22 06:11:57 +00:00
/* Process management. */
Result Registration::RegisterProcess(u64 pid, u8 *acid_sac, size_t acid_sac_size, u8 *aci0_sac, size_t aci0_sac_size) {
if (aci0_sac_size > REGISTRATION_MAX_SAC_SIZE) {
return ResultSmTooLargeAccessControl;
2018-04-22 06:11:57 +00:00
}
Registration::Process *proc = GetFreeProcess();
2018-04-22 06:11:57 +00:00
if (proc == NULL) {
return ResultSmInsufficientProcesses;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
if (aci0_sac_size && !ValidateSacAgainstRestriction(acid_sac, acid_sac_size, aci0_sac, aci0_sac_size)) {
return ResultSmNotAllowed;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
proc->pid = pid;
proc->sac_size = aci0_sac_size;
std::copy(aci0_sac, aci0_sac + aci0_sac_size, proc->sac);
2019-03-29 05:39:39 +00:00
return ResultSuccess;
2018-04-22 06:11:57 +00:00
}
Result Registration::UnregisterProcess(u64 pid) {
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
proc->pid = 0;
2019-03-29 05:39:39 +00:00
return ResultSuccess;
2018-04-22 06:11:57 +00:00
}
/* Service management. */
bool Registration::HasService(u64 service) {
return std::any_of(g_service_list.begin(), g_service_list.end(), member_equals_fn(&Service::service_name, service));
2018-04-22 06:11:57 +00:00
}
bool Registration::HasMitm(u64 service) {
Registration::Service *target_service = GetService(service);
return target_service != NULL && target_service->mitm_pid != 0;
}
Result Registration::GetMitmServiceHandleImpl(Registration::Service *target_service, u64 pid, Handle *out) {
/* Send command to query if we should mitm. */
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 pid;
} *info = ((decltype(info))ipcPrepareHeader(&c, sizeof(*info)));
info->magic = SFCI_MAGIC;
info->cmd_id = 65000;
info->pid = pid;
R_TRY(ipcDispatch(target_service->mitm_query_h));
/* Parse response to see if we should mitm. */
bool should_mitm;
{
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
bool should_mitm;
} *resp = ((decltype(resp))r.Raw);
R_TRY(resp->result);
should_mitm = resp->should_mitm;
}
/* If we shouldn't mitm, give normal session. */
if (!should_mitm) {
return svcConnectToPort(out, target_service->port_h);
}
/* Create both handles. If the second fails, close the first to prevent leak. */
R_TRY(svcConnectToPort(&target_service->mitm_fwd_sess_h, target_service->port_h));
R_TRY_CLEANUP(svcConnectToPort(out, target_service->mitm_port_h), {
svcCloseHandle(target_service->mitm_fwd_sess_h);
target_service->mitm_fwd_sess_h = 0;
});
target_service->mitm_waiting_ack_pid = pid;
target_service->mitm_waiting_ack = true;
return ResultSuccess;
}
Result Registration::GetServiceHandleImpl(Registration::Service *target_service, u64 pid, Handle *out) {
/* Clear handle output. */
*out = 0;
/* If not mitm'd or mitm service is requesting, get normal session. */
if (target_service->mitm_pid == 0 || target_service->mitm_pid == pid) {
return svcConnectToPort(out, target_service->port_h);
}
/* We're mitm'd. */
if (R_FAILED(GetMitmServiceHandleImpl(target_service, pid, out))) {
/* If the Mitm service is dead, just give a normal session. */
return svcConnectToPort(out, target_service->port_h);
}
return ResultSuccess;
}
Result Registration::GetServiceHandle(u64 pid, u64 service, Handle *out) {
Registration::Service *target_service = GetService(service);
if (target_service == NULL || ShouldInitDefer(service) || target_service->mitm_waiting_ack) {
/* Note: This defers the result until later. */
return ResultServiceFrameworkRequestDeferredByUser;
}
2019-06-17 16:17:53 +00:00
R_TRY_CATCH(GetServiceHandleImpl(target_service, pid, out)) {
/* Convert Kernel result to SM result. */
2019-06-17 16:17:53 +00:00
R_CATCH(ResultKernelOutOfSessions) {
return ResultSmInsufficientSessions;
}
2019-06-17 16:17:53 +00:00
} R_END_TRY_CATCH;
2019-06-17 16:17:53 +00:00
return ResultSuccess;
}
2018-04-22 06:11:57 +00:00
Result Registration::GetServiceForPid(u64 pid, u64 service, Handle *out) {
if (!service) {
return ResultSmInvalidServiceName;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
2018-04-22 06:11:57 +00:00
}
/* In 8.0.0, Nintendo removed the service apm:p -- however, all homebrew attempts to get */
/* a handle to this when calling appletInitialize(). Because hbl has access to all services, */
/* This would return true, and homebrew would *wait forever* trying to get a handle to a service */
/* that will never register. Thus, in the interest of not breaking every single piece of homebrew */
/* we will provide a little first class help. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_800 && service == EncodeNameConstant("apm:p")) {
return ResultSmNotAllowed;
}
2019-06-17 16:17:53 +00:00
if (!IsInitialProcess(pid)) {
2018-04-22 06:11:57 +00:00
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
if (!IsValidForSac(proc->sac, proc->sac_size, service, false)) {
return ResultSmNotAllowed;
2018-04-22 06:11:57 +00:00
}
}
2019-06-17 16:17:53 +00:00
return GetServiceHandle(pid, service, out);
2018-04-22 06:11:57 +00:00
}
Result Registration::RegisterServiceForPid(u64 pid, u64 service, u64 max_sessions, bool is_light, Handle *out) {
if (!service) {
return ResultSmInvalidServiceName;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
if (!IsInitialProcess(pid)) {
2018-04-22 06:11:57 +00:00
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
if (!IsValidForSac(proc->sac, proc->sac_size, service, true)) {
return ResultSmNotAllowed;
2018-04-22 06:11:57 +00:00
}
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
if (HasService(service)) {
return ResultSmAlreadyRegistered;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
#ifdef SM_MINIMUM_SESSION_LIMIT
if (max_sessions < SM_MINIMUM_SESSION_LIMIT) {
max_sessions = SM_MINIMUM_SESSION_LIMIT;
}
#endif
2018-04-22 06:11:57 +00:00
Registration::Service *free_service = GetFreeService();
if (free_service == NULL) {
return ResultSmInsufficientServices;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
*out = 0;
*free_service = (const Registration::Service){0};
2019-06-17 16:17:53 +00:00
R_TRY(svcCreatePort(out, &free_service->port_h, max_sessions, is_light, (char *)&free_service->service_name));
free_service->service_name = service;
free_service->owner_pid = pid;
free_service->max_sessions = max_sessions;
free_service->is_light = is_light;
return ResultSuccess;
2018-04-22 06:11:57 +00:00
}
Result Registration::RegisterServiceForSelf(u64 service, u64 max_sessions, bool is_light, Handle *out) {
u64 pid;
2019-06-17 16:17:53 +00:00
R_TRY(svcGetProcessId(&pid, CUR_PROCESS_HANDLE));
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
if (HasService(service)) {
return ResultSmAlreadyRegistered;
}
#ifdef SM_MINIMUM_SESSION_LIMIT
if (max_sessions < SM_MINIMUM_SESSION_LIMIT) {
max_sessions = SM_MINIMUM_SESSION_LIMIT;
}
#endif
2019-06-17 16:17:53 +00:00
Registration::Service *free_service = GetFreeService();
if (free_service == NULL) {
return ResultSmInsufficientServices;
}
2019-06-17 16:17:53 +00:00
*out = 0;
*free_service = (const Registration::Service){0};
2019-06-17 16:17:53 +00:00
R_TRY(svcCreatePort(out, &free_service->port_h, max_sessions, is_light, (char *)&free_service->service_name));
free_service->service_name = service;
free_service->owner_pid = pid;
free_service->max_sessions = max_sessions;
free_service->is_light = is_light;
return ResultSuccess;
}
2018-04-22 06:11:57 +00:00
Result Registration::UnregisterServiceForPid(u64 pid, u64 service) {
if (!service) {
return ResultSmInvalidServiceName;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
2018-04-22 06:11:57 +00:00
}
if (!IsInitialProcess(pid) && target_service->owner_pid != pid) {
return ResultSmNotAllowed;
2018-04-22 06:11:57 +00:00
}
2019-06-17 16:17:53 +00:00
2018-04-22 06:11:57 +00:00
svcCloseHandle(target_service->port_h);
svcCloseHandle(target_service->mitm_port_h);
svcCloseHandle(target_service->mitm_query_h);
2018-04-22 06:11:57 +00:00
*target_service = (const Registration::Service){0};
2019-03-29 05:39:39 +00:00
return ResultSuccess;
2018-04-22 06:11:57 +00:00
}
Result Registration::InstallMitmForPid(u64 pid, u64 service, Handle *out, Handle *query_out) {
if (!service) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
/* Verify we're allowed to mitm the service. */
if (!IsInitialProcess(pid)) {
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
}
2019-06-17 16:17:53 +00:00
if (!IsValidForSac(proc->sac, proc->sac_size, service, true)) {
return ResultSmNotAllowed;
}
}
2019-06-17 16:17:53 +00:00
/* Verify the service exists. */
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultServiceFrameworkRequestDeferredByUser;
}
2019-06-17 16:17:53 +00:00
/* Verify the service isn't already being mitm'd. */
if (target_service->mitm_pid != 0) {
return ResultSmAlreadyRegistered;
}
2019-06-17 16:17:53 +00:00
*out = 0;
u64 x = 0;
2019-06-17 16:17:53 +00:00
R_TRY(svcCreatePort(out, &target_service->mitm_port_h, target_service->max_sessions, target_service->is_light, (char *)&x));
R_TRY(svcCreateSession(query_out, &target_service->mitm_query_h, 0, 0));
target_service->mitm_pid = pid;
return ResultSuccess;
}
Result Registration::UninstallMitmForPid(u64 pid, u64 service) {
if (!service) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
}
if (!IsInitialProcess(pid) && target_service->mitm_pid != pid) {
return ResultSmNotAllowed;
}
2019-06-17 16:17:53 +00:00
svcCloseHandle(target_service->mitm_port_h);
svcCloseHandle(target_service->mitm_query_h);
target_service->mitm_pid = 0;
2019-03-29 05:39:39 +00:00
return ResultSuccess;
}
Result Registration::AcknowledgeMitmSessionForPid(u64 pid, u64 service, Handle *out, u64 *out_pid) {
if (!service) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
2019-06-17 16:17:53 +00:00
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
}
if ((!IsInitialProcess(pid) && target_service->mitm_pid != pid) || !target_service->mitm_waiting_ack) {
return ResultSmNotAllowed;
}
2019-06-17 16:17:53 +00:00
*out = target_service->mitm_fwd_sess_h;
*out_pid = target_service->mitm_waiting_ack_pid;
target_service->mitm_fwd_sess_h = 0;
target_service->mitm_waiting_ack_pid = 0;
target_service->mitm_waiting_ack = false;
2019-03-29 05:39:39 +00:00
return ResultSuccess;
}
Result Registration::AssociatePidTidForMitm(u64 pid, u64 tid) {
for (auto &service : g_service_list) {
if (service.mitm_pid) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 pid;
u64 tid;
} *info = ((decltype(info))ipcPrepareHeader(&c, sizeof(*info)));
info->magic = SFCI_MAGIC;
info->cmd_id = 65001;
info->pid = pid;
info->tid = tid;
ipcDispatch(service.mitm_query_h);
}
}
2019-03-29 05:39:39 +00:00
return ResultSuccess;
}
2019-01-20 23:59:09 +00:00
void Registration::ConvertServiceToRecord(Registration::Service *service, SmServiceRecord *record) {
record->service_name = service->service_name;
record->owner_pid = service->owner_pid;
record->max_sessions = service->max_sessions;
record->mitm_pid = service->mitm_pid;
record->mitm_waiting_ack_pid = service->mitm_waiting_ack_pid;
record->is_light = service->is_light;
record->mitm_waiting_ack = service->mitm_waiting_ack;
}
Result Registration::GetServiceRecord(u64 service, SmServiceRecord *out) {
if (!service) {
return ResultSmInvalidServiceName;
2019-01-20 23:59:09 +00:00
}
2019-06-17 16:17:53 +00:00
2019-01-20 23:59:09 +00:00
u64 service_name_len = GetServiceNameLength(service);
2019-06-17 16:17:53 +00:00
2019-01-20 23:59:09 +00:00
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
2019-01-20 23:59:09 +00:00
}
2019-06-17 16:17:53 +00:00
2019-01-20 23:59:09 +00:00
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
2019-01-20 23:59:09 +00:00
}
2019-06-17 16:17:53 +00:00
2019-01-20 23:59:09 +00:00
ConvertServiceToRecord(target_service, out);
2019-03-29 05:39:39 +00:00
return ResultSuccess;
2019-01-20 23:59:09 +00:00
}
void Registration::ListServiceRecords(u64 offset, u64 max_out, SmServiceRecord *out, u64 *out_count) {
u64 count = 0;
2019-06-17 16:17:53 +00:00
2019-01-20 23:59:09 +00:00
for (auto it = g_service_list.begin(); it != g_service_list.end() && count < max_out; it++) {
if (it->service_name != 0) {
if (offset > 0) {
offset--;
} else {
ConvertServiceToRecord(it, out++);
count++;
}
}
}
2019-06-17 16:17:53 +00:00
2019-01-20 23:59:09 +00:00
*out_count = count;
}