mirror of
https://github.com/jmelovich/Steam-Link-PS5.git
synced 2026-10-19 02:10:53 -05:00
M1: ihslib, protobuf-c, mbedtls, opus and libc++ cross-built; the title discovers Steam
tools/build-deps.sh builds the streaming stack once into build/deps from explicit file lists (ihslib's CMake wants pkg-config and SDL2). platform/ps5 holds the pthread backend for ihslib, ProsperoLight's sceNet socket adapter and entropy source, and an __assert shim. The app links the SDK's libc++/libc++abi/libunwind (the linker script now brackets .eh_frame for libunwind; -L satisfies libc++'s pthread dependent-library note with the SDK's empty libpthread.a). src/main.cpp drives discovery, PIN pairing and the streaming request from a flow thread and counts delivered video/audio frames; app_storage keeps the identity and log under /data/steamlink-ps5. Tested on the console: discovers the PC in 3 s, unauthorized stream request leads to the pairing prompt with a PIN on screen.
This commit is contained in:
1 parent
ae52676cfc
commit
d3049b4bc6
11 files changed
+870
-58
No files matched your search
@@ -8,8 +8,11 @@ SHELL := /bin/bash
|
||||
-include .env
|
||||
|
||||
APP_DEFINITIONS ?=
|
||||
APP_INCLUDE_PATHS ?=
|
||||
APP_STATIC_ARCHIVES ?=
|
||||
# The streaming stack from tools/build-deps.sh (archive order: users before providers).
|
||||
APP_INCLUDE_PATHS ?= third_party/ihslib/include platform/ps5 third_party/opus/include third_party/mbedtls/include
|
||||
APP_STATIC_ARCHIVES ?= build/deps/libihslib.a build/deps/libprotobuf-c.a build/deps/libopus.a \
|
||||
build/deps/libmbedtls.a build/deps/libmbedx509.a build/deps/libmbedcrypto.a \
|
||||
build/deps/libcxx.a build/deps/libcxxabi.a build/deps/libunwind.a
|
||||
APP_RUNTIME_MODULES ?=
|
||||
APP_WRAP_SYMBOLS ?=
|
||||
APP_SOURCE_DIR ?=
|
||||
@@ -140,7 +143,12 @@ $(RUNTIME): $(RUNTIME_INPUTS)
|
||||
@printf '%s\n' '==> [libc] Generating the missing or outdated runtime'
|
||||
@bash tools/rebuild-libc.sh
|
||||
|
||||
app: $(RUNTIME)
|
||||
# The streaming stack (ihslib, protobuf-c, mbedtls, opus, platform/ps5) is built once
|
||||
# into build/deps by tools/build-deps.sh and relinked into the app from there.
|
||||
stream-deps:
|
||||
@bash tools/build-deps.sh
|
||||
|
||||
app: $(RUNTIME) stream-deps
|
||||
@printf '%s\n' '==> [app] Compiling, linking, signing, and assembling the app folder'
|
||||
@bash tools/build.sh Folder
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* steamlink-ps5 - pthread backend for ihslib's thread abstraction.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* ihslib ships an SDL2 backend; the PS5 has no SDL. Mutexes are recursive to
|
||||
* match SDL's semantics, which ihslib relies on. Threads get an explicit stack:
|
||||
* a title's default thread stack is not what a desktop library expects.
|
||||
*/
|
||||
|
||||
#include "ihs_thread.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define IHS_PS5_THREAD_STACK_SIZE (512 * 1024)
|
||||
|
||||
struct IHS_Thread {
|
||||
pthread_t tid;
|
||||
IHS_ThreadFunction *function;
|
||||
void *context;
|
||||
};
|
||||
|
||||
struct IHS_Mutex {
|
||||
pthread_mutex_t mutex;
|
||||
};
|
||||
|
||||
struct IHS_Cond {
|
||||
pthread_cond_t cond;
|
||||
};
|
||||
|
||||
static void *ThreadEntry(void *arg) {
|
||||
IHS_Thread *thread = arg;
|
||||
thread->function(thread->context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IHS_Thread *IHS_ThreadCreate(IHS_ThreadFunction *function, const char *name, void *context) {
|
||||
(void) name;
|
||||
IHS_Thread *thread = calloc(1, sizeof(IHS_Thread));
|
||||
if (thread == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
thread->function = function;
|
||||
thread->context = context;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setstacksize(&attr, IHS_PS5_THREAD_STACK_SIZE);
|
||||
int result = pthread_create(&thread->tid, &attr, ThreadEntry, thread);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (result != 0) {
|
||||
free(thread);
|
||||
return NULL;
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
void IHS_ThreadJoin(IHS_Thread *thread) {
|
||||
if (thread == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_join(thread->tid, NULL);
|
||||
free(thread);
|
||||
}
|
||||
|
||||
IHS_Mutex *IHS_MutexCreate() {
|
||||
IHS_Mutex *mutex = calloc(1, sizeof(IHS_Mutex));
|
||||
if (mutex == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&mutex->mutex, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
return mutex;
|
||||
}
|
||||
|
||||
void IHS_MutexDestroy(IHS_Mutex *mutex) {
|
||||
if (mutex == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_destroy(&mutex->mutex);
|
||||
free(mutex);
|
||||
}
|
||||
|
||||
bool IHS_MutexLock(IHS_Mutex *mutex) {
|
||||
return pthread_mutex_lock(&mutex->mutex) == 0;
|
||||
}
|
||||
|
||||
bool IHS_MutexUnlock(IHS_Mutex *mutex) {
|
||||
return pthread_mutex_unlock(&mutex->mutex) == 0;
|
||||
}
|
||||
|
||||
IHS_Cond *IHS_CondCreate() {
|
||||
IHS_Cond *cond = calloc(1, sizeof(IHS_Cond));
|
||||
if (cond == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
pthread_cond_init(&cond->cond, NULL);
|
||||
return cond;
|
||||
}
|
||||
|
||||
void IHS_CondDestroy(IHS_Cond *cond) {
|
||||
if (cond == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_cond_destroy(&cond->cond);
|
||||
free(cond);
|
||||
}
|
||||
|
||||
void IHS_CondSignal(IHS_Cond *cond) {
|
||||
pthread_cond_signal(&cond->cond);
|
||||
}
|
||||
|
||||
bool IHS_CondWait(IHS_Cond *cond, IHS_Mutex *mutex) {
|
||||
return pthread_cond_wait(&cond->cond, &mutex->mutex) == 0;
|
||||
}
|
||||
+13
-21
@@ -1,33 +1,22 @@
|
||||
/*
|
||||
* ps5-native-app-boilerplate - ProsperoLight component.
|
||||
* ps5-native-app-boilerplate - ProsperoLight component, trimmed for steamlink-ps5.
|
||||
* Copyright (C) 2026 BlackBearReloaded
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Force-included (-include) when compiling third-party networking code: a PS5
|
||||
* socket is a libSceNet handle, so the descriptor calls below must go through
|
||||
* the adapter in ps5_sockets.c rather than libc.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <poll.h>
|
||||
#include <sys/select.h>
|
||||
/* The SDK's <arpa/inet.h> does not pull these in; ihslib's POSIX backends expect it to. */
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "ps5_thread_placement.h"
|
||||
|
||||
/*
|
||||
* The generic FreeBSD path in moonlight-common-c renames every new thread.
|
||||
* Calling the PS5 pthread_rename_np export from a newly started thread hangs
|
||||
* before its entry point runs, so the name is never applied. The hook runs on
|
||||
* the new thread itself, so it places that thread by name instead.
|
||||
*/
|
||||
static inline int ps5_pthread_setname_place(pthread_t thread, const char *name) {
|
||||
(void)thread;
|
||||
ps5_thread_placement_apply_name(name);
|
||||
return 0;
|
||||
}
|
||||
#define pthread_setname_np ps5_pthread_setname_place
|
||||
|
||||
/* Portable by default; optional CPUID + XCR0 checked nanors dispatch. */
|
||||
#include "ps5_fec_cpu.h"
|
||||
|
||||
/* PS5 network handles must be closed/configured through libSceNet. */
|
||||
#ifndef PS5_SOCKET_ADAPTER_IMPLEMENTATION
|
||||
#define close ps5_socket_close
|
||||
#define fcntl ps5_socket_fcntl
|
||||
@@ -37,4 +26,7 @@ static inline int ps5_pthread_setname_place(pthread_t thread, const char *name)
|
||||
int ps5_socket_close(int socket);
|
||||
int ps5_socket_fcntl(int socket, int command, ...);
|
||||
int ps5_socket_ioctl(int socket, unsigned long request, ...);
|
||||
int ps5_socket_poll(struct pollfd *descriptors, nfds_t count, int timeout_ms);
|
||||
int ps5_socket_select(int descriptor_count, fd_set *read_set, fd_set *write_set,
|
||||
fd_set *error_set, struct timeval *timeout);
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* steamlink-ps5 - libc entry points the title's runtime does not provide.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Third-party C code (ihslib, mbedtls) reaches these through libc macros; the
|
||||
* clean-room runtime shim resolves them to nothing, so they are defined here.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int sceKernelDebugOutText(int channel, const char *text);
|
||||
|
||||
/* assert(): report to the kernel log, then stop. */
|
||||
__attribute__((noreturn)) void __assert(const char *function, const char *file, int line,
|
||||
const char *expression)
|
||||
{
|
||||
char text[512];
|
||||
snprintf(text, sizeof text, "[SLP5] assertion failed: %s (%s:%d, %s)\n", expression, file, line,
|
||||
function ? function : "?");
|
||||
sceKernelDebugOutText(0, text);
|
||||
abort();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* steamlink-ps5 - Logging: a file under the data directory plus the kernel log.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace slp
|
||||
{
|
||||
// printf-style; one line per call, timestamped. Thread-safe.
|
||||
void log(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
||||
// The most recent line, for the status screen.
|
||||
std::string log_last_line();
|
||||
} // namespace slp
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* steamlink-ps5 - Data directory, log file and client identity.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "app_storage.hpp"
|
||||
#include "app_log.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fcntl.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int sceKernelDebugOutText(int channel, const char *text);
|
||||
int ps5_get_random(void *output, size_t length); // platform/ps5/ps5_entropy.c
|
||||
}
|
||||
|
||||
namespace slp
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string g_data_dir;
|
||||
Identity g_identity{};
|
||||
int g_log_fd = -1;
|
||||
std::mutex g_log_mutex;
|
||||
std::string g_last_line;
|
||||
|
||||
bool writable_directory(const std::string &path)
|
||||
{
|
||||
mkdir(path.c_str(), 0777);
|
||||
const std::string probe = path + "/.probe";
|
||||
const int fd = open(probe.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
close(fd);
|
||||
unlink(probe.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_or_create_identity()
|
||||
{
|
||||
const std::string path = g_data_dir + "/identity.bin";
|
||||
int fd = open(path.c_str(), O_RDONLY);
|
||||
if (fd >= 0)
|
||||
{
|
||||
const ssize_t got = read(fd, &g_identity, sizeof g_identity);
|
||||
close(fd);
|
||||
if (got == static_cast<ssize_t>(sizeof g_identity) && g_identity.device_id != 0)
|
||||
{
|
||||
log("identity loaded, device id %llu", static_cast<unsigned long long>(g_identity.device_id));
|
||||
return;
|
||||
}
|
||||
}
|
||||
random_bytes(&g_identity, sizeof g_identity);
|
||||
g_identity.device_id |= 1; // never zero
|
||||
fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd >= 0)
|
||||
{
|
||||
write(fd, &g_identity, sizeof g_identity);
|
||||
close(fd);
|
||||
log("identity created, device id %llu", static_cast<unsigned long long>(g_identity.device_id));
|
||||
}
|
||||
else
|
||||
{
|
||||
log("identity could not be saved (%s); pairing will not persist", std::strerror(errno));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void storage_init()
|
||||
{
|
||||
// ShadowMount+ 1.7beta4 and newer mount /data into the sandbox; /download0 is
|
||||
// always there when param.json declares downloadDataSize.
|
||||
for (const char *candidate : {"/data/steamlink-ps5", "/download0/steamlink-ps5", "/app0/steamlink-ps5"})
|
||||
{
|
||||
if (writable_directory(candidate))
|
||||
{
|
||||
g_data_dir = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!g_data_dir.empty())
|
||||
g_log_fd = open((g_data_dir + "/log.txt").c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
log("data directory: %s", g_data_dir.empty() ? "(none writable)" : g_data_dir.c_str());
|
||||
if (!g_data_dir.empty())
|
||||
load_or_create_identity();
|
||||
else
|
||||
random_bytes(&g_identity, sizeof g_identity);
|
||||
}
|
||||
|
||||
const char *data_dir()
|
||||
{
|
||||
return g_data_dir.c_str();
|
||||
}
|
||||
|
||||
const Identity &identity()
|
||||
{
|
||||
return g_identity;
|
||||
}
|
||||
|
||||
void random_bytes(void *destination, std::size_t length)
|
||||
{
|
||||
if (ps5_get_random(destination, length) != 0)
|
||||
{
|
||||
// Last resort: never leave the buffer untouched.
|
||||
auto *bytes = static_cast<unsigned char *>(destination);
|
||||
std::uint64_t seed = static_cast<std::uint64_t>(time(nullptr)) ^ reinterpret_cast<std::uintptr_t>(destination);
|
||||
for (std::size_t i = 0; i < length; i++)
|
||||
{
|
||||
seed = seed * 6364136223846793005ull + 1442695040888963407ull;
|
||||
bytes[i] = static_cast<unsigned char>(seed >> 56);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void log(const char *format, ...)
|
||||
{
|
||||
char text[1024];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
const int length = std::vsnprintf(text, sizeof text, format, args);
|
||||
va_end(args);
|
||||
if (length < 0)
|
||||
return;
|
||||
|
||||
// Seconds since the title started: the console's clock is not worth a localtime_r.
|
||||
char line[1100];
|
||||
static const std::time_t started = time(nullptr);
|
||||
const long elapsed = static_cast<long>(time(nullptr) - started);
|
||||
const int line_length = std::snprintf(line, sizeof line, "[SLP5] %5lds %s\n", elapsed, text);
|
||||
|
||||
std::lock_guard lock{g_log_mutex};
|
||||
sceKernelDebugOutText(0, line);
|
||||
if (g_log_fd >= 0)
|
||||
write(g_log_fd, line, static_cast<size_t>(line_length));
|
||||
g_last_line = text;
|
||||
}
|
||||
|
||||
std::string log_last_line()
|
||||
{
|
||||
std::lock_guard lock{g_log_mutex};
|
||||
return g_last_line;
|
||||
}
|
||||
} // namespace slp
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* steamlink-ps5 - Where the app keeps its files, and the client identity.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace slp
|
||||
{
|
||||
// The pairing identity Steam remembers: a device id and the 32-byte secret the
|
||||
// device token is derived from. Generated once, then persisted.
|
||||
struct Identity
|
||||
{
|
||||
std::uint64_t device_id;
|
||||
std::uint8_t secret_key[32];
|
||||
};
|
||||
|
||||
// Picks the data directory (/data/steamlink-ps5 when ShadowMount+ mounts /data into
|
||||
// the sandbox, otherwise /download0), opens the log, loads or creates the identity.
|
||||
// Call first thing in main, on the main thread.
|
||||
void storage_init();
|
||||
const char *data_dir();
|
||||
const Identity &identity();
|
||||
// Console entropy (sysctl kern.rng_pseudo). Not for keys that leave the console.
|
||||
void random_bytes(void *destination, std::size_t length);
|
||||
} // namespace slp
|
||||
+354
-33
@@ -1,59 +1,380 @@
|
||||
/*
|
||||
* ps5-native-app-boilerplate - Minimal native application example.
|
||||
* Copyright (C) 2026 BlackBearReloaded
|
||||
* steamlink-ps5 - Steam Remote Play client for the PS5: M1 protocol bring-up.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Draws a small CPU-rendered scene and keeps the process alive for normal
|
||||
* shell-mediated closure.
|
||||
* No controller input yet. A flow thread drives ihslib: discover the PC running
|
||||
* Steam, pair with the PIN shown on screen when Steam asks for one, request a
|
||||
* stream, then count the video and audio frames the session delivers. The main
|
||||
* thread draws the state on the CPU renderer.
|
||||
*/
|
||||
|
||||
#include "app_log.hpp"
|
||||
#include "app_storage.hpp"
|
||||
#include "demo_renderer.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "ihslib.h"
|
||||
}
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::array<char, 48> banner{};
|
||||
std::array<char, 48> build_label{};
|
||||
constexpr char device_name[] = "PS5";
|
||||
|
||||
struct Status
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::string state = "STARTING";
|
||||
std::string host;
|
||||
std::string pin;
|
||||
std::string video;
|
||||
std::string audio;
|
||||
std::atomic<std::uint64_t> video_frames{0};
|
||||
std::atomic<std::uint64_t> video_bytes{0};
|
||||
std::atomic<std::uint64_t> audio_frames{0};
|
||||
std::atomic<std::uint64_t> audio_bytes{0};
|
||||
};
|
||||
Status status;
|
||||
|
||||
void set_state(const char *text)
|
||||
{
|
||||
std::lock_guard lock{status.mutex};
|
||||
if (status.state == text)
|
||||
return;
|
||||
status.state = text;
|
||||
slp::log("state: %s", text);
|
||||
}
|
||||
|
||||
// The demo font has capitals only; everything shown goes through here.
|
||||
std::string upper(std::string text)
|
||||
{
|
||||
for (char &c : text)
|
||||
if (c >= 'a' && c <= 'z')
|
||||
c = static_cast<char>(c - 'a' + 'A');
|
||||
return text;
|
||||
}
|
||||
|
||||
void draw_scene(ps5::demo::Canvas &canvas) noexcept
|
||||
{
|
||||
using ps5::demo::Color;
|
||||
|
||||
canvas.clear(Color::background);
|
||||
canvas.rectangle(120, 430, 500, 470, Color::panel);
|
||||
canvas.rectangle(710, 430, 500, 470, Color::panel);
|
||||
canvas.rectangle(1300, 430, 500, 470, Color::panel);
|
||||
canvas.rectangle(120, 375, 1680, 8, Color::white);
|
||||
canvas.text(120, 80, "STEAM REMOTE PLAY", 10, Color::white);
|
||||
canvas.rectangle(120, 200, 1680, 6, Color::white);
|
||||
|
||||
canvas.text(120, 90, "HELLO WORLD", 14, Color::white);
|
||||
canvas.text(120, 245, "MODERN CXX20 RAII", 6, Color::white);
|
||||
canvas.text(120, 315, banner.data(), 5, Color::cyan);
|
||||
std::array<char, 96> line{};
|
||||
{
|
||||
std::lock_guard lock{status.mutex};
|
||||
canvas.text(120, 260, upper(status.state), 5, Color::cyan);
|
||||
if (!status.host.empty())
|
||||
canvas.text(120, 340, upper("HOST " + status.host), 4, Color::white);
|
||||
if (!status.pin.empty())
|
||||
{
|
||||
canvas.text(120, 440, "ENTER THIS PIN IN STEAM ON YOUR PC", 4, Color::yellow);
|
||||
canvas.text(120, 500, status.pin, 12, Color::yellow);
|
||||
}
|
||||
if (!status.video.empty())
|
||||
canvas.text(120, 700, upper(status.video), 4, Color::white);
|
||||
if (!status.audio.empty())
|
||||
canvas.text(120, 760, upper(status.audio), 4, Color::white);
|
||||
}
|
||||
std::snprintf(line.data(), line.size(), "VIDEO FRAMES %llu BYTES %llu",
|
||||
static_cast<unsigned long long>(status.video_frames.load()),
|
||||
static_cast<unsigned long long>(status.video_bytes.load()));
|
||||
canvas.text(120, 840, line.data(), 4, Color::white);
|
||||
std::snprintf(line.data(), line.size(), "AUDIO FRAMES %llu BYTES %llu",
|
||||
static_cast<unsigned long long>(status.audio_frames.load()),
|
||||
static_cast<unsigned long long>(status.audio_bytes.load()));
|
||||
canvas.text(120, 900, line.data(), 4, Color::white);
|
||||
canvas.text(120, 1000, upper(slp::log_last_line()), 3, Color::magenta);
|
||||
}
|
||||
|
||||
canvas.circle(370, 665, 130, Color::cyan);
|
||||
canvas.rectangle(840, 535, 240, 240, Color::yellow);
|
||||
canvas.triangle(1550, 520, 170, 285, Color::magenta);
|
||||
void ihs_log(IHS_LogLevel level, const char *tag, const char *message)
|
||||
{
|
||||
if (level > IHS_LogLevelInfo)
|
||||
return;
|
||||
slp::log("[IHS.%s %s] %s", tag, IHS_LogLevelName(level), message);
|
||||
}
|
||||
|
||||
canvas.text(250, 830, "CIRCLE", 5, Color::white);
|
||||
canvas.text(870, 830, "SQUARE", 5, Color::white);
|
||||
canvas.text(1420, 830, "TRIANGLE", 5, Color::white);
|
||||
// ---- client phase: discovery, authorization, streaming request -------------
|
||||
|
||||
// A build that is not a release says which one it is (docs/PULL_REQUEST_BUILDS.md).
|
||||
if (build_label[0] != '\0')
|
||||
canvas.text(120, 960, build_label.data(), 4, Color::yellow);
|
||||
struct ClientFlow
|
||||
{
|
||||
IHS_ClientConfig config{};
|
||||
IHS_HostInfo host{};
|
||||
bool host_chosen = false;
|
||||
bool authorization_tried = false;
|
||||
bool have_session = false;
|
||||
IHS_SessionInfo session{};
|
||||
char pin[8] = {};
|
||||
};
|
||||
|
||||
void request_stream(IHS_Client *client, ClientFlow &flow)
|
||||
{
|
||||
IHS_StreamingRequest request{};
|
||||
request.streamingEnable.video = true;
|
||||
request.streamingEnable.audio = true;
|
||||
request.streamingEnable.input = true;
|
||||
request.maxResolution.x = 1920;
|
||||
request.maxResolution.y = 1080;
|
||||
request.audioChannelCount = 2;
|
||||
request.streamingInterface = IHS_StreamInterfaceBigPicture;
|
||||
set_state("REQUESTING STREAM");
|
||||
if (!IHS_ClientStreamingRequest(client, &flow.host, &request))
|
||||
slp::log("IHS_ClientStreamingRequest failed: %s", IHS_ClientError(client));
|
||||
}
|
||||
|
||||
void on_discovered(IHS_Client *client, const IHS_HostInfo *host, void *context)
|
||||
{
|
||||
auto &flow = *static_cast<ClientFlow *>(context);
|
||||
char *address = IHS_IPAddressToString(&host->address.ip);
|
||||
slp::log("discovered %s at %s:%u (games running: %d)", host->hostname, address, host->address.port,
|
||||
host->gamesRunning);
|
||||
if (flow.host_chosen)
|
||||
{
|
||||
free(address);
|
||||
return;
|
||||
}
|
||||
flow.host_chosen = true;
|
||||
flow.host = *host;
|
||||
{
|
||||
std::lock_guard lock{status.mutex};
|
||||
status.host = std::string{host->hostname} + " " + address;
|
||||
}
|
||||
free(address);
|
||||
request_stream(client, flow);
|
||||
}
|
||||
|
||||
void on_authorization_progress(IHS_Client *, const IHS_HostInfo *, void *)
|
||||
{
|
||||
set_state("WAITING FOR PIN ENTRY ON THE PC");
|
||||
}
|
||||
|
||||
void on_authorization_success(IHS_Client *client, const IHS_HostInfo *, std::uint64_t steam_id, void *context)
|
||||
{
|
||||
auto &flow = *static_cast<ClientFlow *>(context);
|
||||
slp::log("authorized, steam id %llu", static_cast<unsigned long long>(steam_id));
|
||||
{
|
||||
std::lock_guard lock{status.mutex};
|
||||
status.pin.clear();
|
||||
}
|
||||
request_stream(client, flow);
|
||||
}
|
||||
|
||||
void on_authorization_failed(IHS_Client *client, const IHS_HostInfo *, IHS_AuthorizationResult result, void *)
|
||||
{
|
||||
slp::log("authorization failed: %d", static_cast<int>(result));
|
||||
set_state("PAIRING FAILED");
|
||||
{
|
||||
std::lock_guard lock{status.mutex};
|
||||
status.pin.clear();
|
||||
}
|
||||
IHS_ClientStop(client);
|
||||
}
|
||||
|
||||
void on_streaming_progress(IHS_Client *, const IHS_HostInfo *, void *)
|
||||
{
|
||||
set_state("STEAM IS STARTING THE STREAM");
|
||||
}
|
||||
|
||||
void on_streaming_success(IHS_Client *client, const IHS_HostInfo *, const IHS_SocketAddress *address,
|
||||
const std::uint8_t *session_key, std::size_t session_key_length, void *context)
|
||||
{
|
||||
auto &flow = *static_cast<ClientFlow *>(context);
|
||||
flow.session.address = *address;
|
||||
flow.session.sessionKeyLen = session_key_length;
|
||||
std::memcpy(flow.session.sessionKey, session_key, session_key_length);
|
||||
flow.session.steamId = 0;
|
||||
flow.have_session = true;
|
||||
char *text = IHS_IPAddressToString(&address->ip);
|
||||
slp::log("stream granted at %s:%u, session key %zu bytes", text, address->port, session_key_length);
|
||||
free(text);
|
||||
IHS_ClientStop(client);
|
||||
}
|
||||
|
||||
void on_streaming_failed(IHS_Client *client, const IHS_HostInfo *, IHS_StreamingResult result, void *context)
|
||||
{
|
||||
auto &flow = *static_cast<ClientFlow *>(context);
|
||||
slp::log("streaming request failed: %d", static_cast<int>(result));
|
||||
if ((result == IHS_StreamingUnauthorized || result == IHS_StreamingPINRequired) && !flow.authorization_tried)
|
||||
{
|
||||
flow.authorization_tried = true;
|
||||
std::uint8_t random[2];
|
||||
slp::random_bytes(random, sizeof random);
|
||||
std::snprintf(flow.pin, sizeof flow.pin, "%04u", (random[0] | (random[1] << 8)) % 10000u);
|
||||
{
|
||||
std::lock_guard lock{status.mutex};
|
||||
status.pin = flow.pin;
|
||||
}
|
||||
set_state("PAIRING: STEAM > SETTINGS > REMOTE PLAY > PAIR STEAM LINK");
|
||||
if (!IHS_ClientAuthorizationRequest(client, &flow.host, flow.pin))
|
||||
{
|
||||
slp::log("IHS_ClientAuthorizationRequest failed: %s", IHS_ClientError(client));
|
||||
IHS_ClientStop(client);
|
||||
}
|
||||
return;
|
||||
}
|
||||
set_state("STREAM REQUEST FAILED");
|
||||
IHS_ClientStop(client);
|
||||
}
|
||||
|
||||
// ---- session phase: count what the host sends ------------------------------
|
||||
|
||||
int on_video_start(IHS_Session *, const IHS_StreamVideoConfig *config, void *)
|
||||
{
|
||||
slp::log("video start: %ux%u codec %d, %zu bytes codec data", config->width, config->height,
|
||||
static_cast<int>(config->codec), config->codecDataLen);
|
||||
std::lock_guard lock{status.mutex};
|
||||
status.video = "video " + std::to_string(config->width) + "x" + std::to_string(config->height) + " codec " +
|
||||
std::to_string(static_cast<int>(config->codec));
|
||||
return 0;
|
||||
}
|
||||
|
||||
IHS_StreamVideoSubmitResult on_video_submit(IHS_Session *, IHS_Buffer *data, IHS_StreamVideoFrameFlag, void *)
|
||||
{
|
||||
status.video_frames.fetch_add(1);
|
||||
status.video_bytes.fetch_add(data->size);
|
||||
return IHS_StreamVideoSubmitOK;
|
||||
}
|
||||
|
||||
void on_video_stop(IHS_Session *, void *)
|
||||
{
|
||||
slp::log("video stop");
|
||||
}
|
||||
|
||||
int on_audio_start(IHS_Session *, const IHS_StreamAudioConfig *config, void *)
|
||||
{
|
||||
slp::log("audio start: %u Hz, %u channels, codec %d", config->frequency, config->channels,
|
||||
static_cast<int>(config->codec));
|
||||
std::lock_guard lock{status.mutex};
|
||||
status.audio = "audio " + std::to_string(config->frequency) + " hz " + std::to_string(config->channels) +
|
||||
" ch codec " + std::to_string(static_cast<int>(config->codec));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int on_audio_submit(IHS_Session *, IHS_Buffer *data, void *)
|
||||
{
|
||||
status.audio_frames.fetch_add(1);
|
||||
status.audio_bytes.fetch_add(data->size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void on_audio_stop(IHS_Session *, void *)
|
||||
{
|
||||
slp::log("audio stop");
|
||||
}
|
||||
|
||||
void on_session_configuring(IHS_Session *, IHS_SessionConfig *config, void *)
|
||||
{
|
||||
config->enableAudio = true;
|
||||
config->enableHevc = false;
|
||||
}
|
||||
|
||||
void on_session_connected(IHS_Session *, void *)
|
||||
{
|
||||
set_state("STREAMING (COUNTING FRAMES)");
|
||||
}
|
||||
|
||||
void on_session_disconnected(IHS_Session *, void *)
|
||||
{
|
||||
set_state("SESSION ENDED");
|
||||
}
|
||||
|
||||
bool set_cursor(IHS_Session *, std::uint64_t, void *)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void *flow_thread(void *)
|
||||
{
|
||||
ClientFlow flow;
|
||||
const slp::Identity &identity = slp::identity();
|
||||
flow.config.deviceId = identity.device_id;
|
||||
flow.config.secretKey = identity.secret_key;
|
||||
flow.config.deviceName = device_name;
|
||||
|
||||
IHS_Init();
|
||||
for (;;)
|
||||
{
|
||||
flow.host_chosen = false;
|
||||
flow.have_session = false;
|
||||
set_state("LOOKING FOR A PC RUNNING STEAM");
|
||||
IHS_Client *client = IHS_ClientCreate(&flow.config);
|
||||
IHS_ClientSetLogFunction(client, ihs_log);
|
||||
IHS_ClientDiscoveryCallbacks discovery{};
|
||||
discovery.discovered = on_discovered;
|
||||
IHS_ClientAuthorizationCallbacks authorization{};
|
||||
authorization.progress = on_authorization_progress;
|
||||
authorization.success = on_authorization_success;
|
||||
authorization.failed = on_authorization_failed;
|
||||
IHS_ClientStreamingCallbacks streaming{};
|
||||
streaming.progress = on_streaming_progress;
|
||||
streaming.success = on_streaming_success;
|
||||
streaming.failed = on_streaming_failed;
|
||||
IHS_ClientSetDiscoveryCallbacks(client, &discovery, &flow);
|
||||
IHS_ClientSetAuthorizationCallbacks(client, &authorization, &flow);
|
||||
IHS_ClientSetStreamingCallbacks(client, &streaming, &flow);
|
||||
if (!IHS_ClientStartDiscovery(client, 0))
|
||||
slp::log("discovery failed to start: %s", IHS_ClientError(client));
|
||||
IHS_ClientThreadedJoin(client);
|
||||
IHS_ClientDestroy(client);
|
||||
|
||||
if (flow.have_session)
|
||||
{
|
||||
IHS_Session *session = IHS_SessionCreate(&flow.config, &flow.session);
|
||||
IHS_SessionSetLogFunction(session, ihs_log);
|
||||
IHS_StreamSessionCallbacks session_callbacks{};
|
||||
session_callbacks.configuring = on_session_configuring;
|
||||
session_callbacks.connected = on_session_connected;
|
||||
session_callbacks.disconnected = on_session_disconnected;
|
||||
IHS_StreamVideoCallbacks video_callbacks{};
|
||||
video_callbacks.start = on_video_start;
|
||||
video_callbacks.submit = on_video_submit;
|
||||
video_callbacks.stop = on_video_stop;
|
||||
IHS_StreamAudioCallbacks audio_callbacks{};
|
||||
audio_callbacks.start = on_audio_start;
|
||||
audio_callbacks.submit = on_audio_submit;
|
||||
audio_callbacks.stop = on_audio_stop;
|
||||
IHS_StreamInputCallbacks input_callbacks{};
|
||||
input_callbacks.setCursor = set_cursor;
|
||||
IHS_SessionSetSessionCallbacks(session, &session_callbacks, nullptr);
|
||||
IHS_SessionSetVideoCallbacks(session, &video_callbacks, nullptr);
|
||||
IHS_SessionSetAudioCallbacks(session, &audio_callbacks, nullptr);
|
||||
IHS_SessionSetInputCallbacks(session, &input_callbacks, nullptr);
|
||||
set_state("CONNECTING SESSION");
|
||||
if (IHS_SessionConnect(session))
|
||||
IHS_SessionThreadedJoin(session);
|
||||
else
|
||||
slp::log("IHS_SessionConnect failed");
|
||||
IHS_SessionDestroy(session);
|
||||
}
|
||||
sleep(5);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
ps5::demo::read_asset_text("/app0/assets/banner.txt", std::span{banner}, "APP0 ASSET FAILED");
|
||||
// Written by the build when BUILD_LABEL is set; the demo font has capitals only.
|
||||
ps5::demo::read_asset_text("/app0/build-label.txt", std::span{build_label}, "");
|
||||
for (char &character : build_label)
|
||||
{
|
||||
if (character >= 'a' && character <= 'z')
|
||||
character = static_cast<char>(character - 'a' + 'A');
|
||||
}
|
||||
ps5::demo::run(draw_scene, banner.data());
|
||||
slp::storage_init();
|
||||
slp::log("Steam Remote Play for PS5 starting; data in %s", slp::data_dir());
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setstacksize(&attr, 1024 * 1024);
|
||||
pthread_t thread;
|
||||
if (pthread_create(&thread, &attr, flow_thread, nullptr) != 0)
|
||||
slp::log("could not start the flow thread");
|
||||
pthread_attr_destroy(&attr);
|
||||
|
||||
ps5::demo::run_frames(draw_scene, "Steam Remote Play");
|
||||
}
|
||||
@@ -26,10 +26,17 @@ SECTIONS {
|
||||
*(.plt .plt.*)
|
||||
} : text
|
||||
|
||||
/* The bracket symbols are what libunwind (pulled in by libc++abi) looks up. */
|
||||
.eh_frame_hdr : ALIGN(CONSTANT(MAXPAGESIZE)) {
|
||||
PROVIDE_HIDDEN(__eh_frame_hdr_start = .);
|
||||
KEEP(*(.eh_frame_hdr))
|
||||
PROVIDE_HIDDEN(__eh_frame_hdr_end = .);
|
||||
} : ro
|
||||
.eh_frame : {
|
||||
PROVIDE_HIDDEN(__eh_frame_start = .);
|
||||
KEEP(*(.eh_frame))
|
||||
PROVIDE_HIDDEN(__eh_frame_end = .);
|
||||
} : ro
|
||||
.eh_frame : { KEEP(*(.eh_frame)) } : ro
|
||||
.gcc_except_table : { *(.gcc_except_table .gcc_except_table.*) } : ro
|
||||
.rodata : { *(.rodata .rodata.*) } : ro
|
||||
|
||||
|
||||
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
# steamlink-ps5 - Cross-build the streaming dependencies once, into build/deps.
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Everything the app links besides its own sources: protobuf-c's runtime, mbedtls
|
||||
# (the crypto ihslib's authorization and session need), opus (the audio decoder),
|
||||
# ihslib itself (built from an explicit file list: its CMake wants pkg-config and
|
||||
# SDL2, neither of which exists for the console) and the PS5 platform layer under
|
||||
# platform/ps5 (sceNet socket adapter, entropy, pthread backend). Each archive is
|
||||
# rebuilt only when its inputs change.
|
||||
#
|
||||
# tools/build-deps.sh build what is missing or stale
|
||||
# tools/build-deps.sh --clean remove build/deps first
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bash "$root/tools/setup-native-dependencies.sh" >/dev/null
|
||||
sdk=${PS5_PAYLOAD_SDK:-$root/.deps/native/ps5-payload-sdk}
|
||||
cc="$sdk/bin/prospero-clang"
|
||||
ar="$sdk/bin/llvm-ar"
|
||||
ranlib="$sdk/bin/llvm-ranlib"
|
||||
out="$root/build/deps"
|
||||
jobs=${BUILD_JOBS:-$(nproc 2>/dev/null || echo 2)}
|
||||
|
||||
ihslib="$root/third_party/ihslib"
|
||||
protobuf_c="$root/third_party/protobuf-c"
|
||||
mbedtls="$root/third_party/mbedtls"
|
||||
opus="$root/third_party/opus"
|
||||
platform="$root/platform/ps5"
|
||||
|
||||
[[ ${1:-} == --clean ]] && rm -rf -- "$out"
|
||||
[[ -x $cc && -x $ar ]] || { echo "build-deps.sh: PS5 payload SDK not found at $sdk" >&2; exit 2; }
|
||||
[[ -f $ihslib/include/ihslib.h && -f $protobuf_c/protobuf-c/protobuf-c.c && -f $mbedtls/Makefile && -f $opus/CMakeLists.txt ]] || {
|
||||
echo "build-deps.sh: submodules missing; run: git submodule update --init --recursive" >&2
|
||||
exit 2
|
||||
}
|
||||
mkdir -p "$out/obj"
|
||||
|
||||
# An archive is up to date when the hash of its inputs matches the one recorded
|
||||
# beside it. Hash file contents, not timestamps: git checkouts reset the latter.
|
||||
stamp_of() { sha256sum "$@" | sha256sum | cut -d' ' -f1; }
|
||||
fresh() {
|
||||
local archive=$1 stamp=$2
|
||||
[[ -f $archive && -f $archive.stamp && $(<"$archive.stamp") == "$stamp" ]]
|
||||
}
|
||||
cflags=(-O2 -ffunction-sections -fdata-sections -fPIC)
|
||||
mbedtls_config=(-DMBEDTLS_CONFIG_FILE='"mbedtls_ps5_config.h"' "-I$platform")
|
||||
|
||||
# ---- protobuf-c runtime ----------------------------------------------------
|
||||
archive="$out/libprotobuf-c.a"
|
||||
stamp=$(stamp_of "$protobuf_c/protobuf-c/protobuf-c.c" "$protobuf_c/protobuf-c/protobuf-c.h" "$0")
|
||||
if ! fresh "$archive" "$stamp"; then
|
||||
echo "==> [deps] protobuf-c"
|
||||
"$cc" "${cflags[@]}" -std=c11 -w -I"$protobuf_c" -c "$protobuf_c/protobuf-c/protobuf-c.c" -o "$out/obj/protobuf-c.o"
|
||||
rm -f "$archive" && "$ar" rcs "$archive" "$out/obj/protobuf-c.o" && echo "$stamp" > "$archive.stamp"
|
||||
fi
|
||||
|
||||
# ---- mbedtls ---------------------------------------------------------------
|
||||
archive="$out/libmbedcrypto.a"
|
||||
stamp=$(stamp_of "$platform/mbedtls_ps5_config.h" "$platform/psa_crypto_ps5_config.h" "$mbedtls/include/mbedtls/build_info.h" "$0" \
|
||||
$(find "$mbedtls/library" -type f -name '*.c' | sort))
|
||||
if ! fresh "$archive" "$stamp"; then
|
||||
echo "==> [deps] mbedtls (library only)"
|
||||
work="$out/mbedtls-build"
|
||||
rm -rf "$work" && cp -a "$mbedtls" "$work"
|
||||
# The checkout already holds its generated sources; the generators would need perl
|
||||
# and python jsonschema on the host. A git checkout gives the templates arbitrary
|
||||
# mtimes, so mark the generated files newer than them and turn generation off.
|
||||
touch "$work/library/psa_crypto_driver_wrappers.h" "$work/library/psa_crypto_driver_wrappers_no_static.c" \
|
||||
"$work/library/error.c" "$work/library/version_features.c" "$work/library/ssl_debug_helpers_generated.c"
|
||||
make -C "$work" lib -j "$jobs" CC="$cc" AR="$ar" GEN_FILES= \
|
||||
CFLAGS="${cflags[*]} -DMBEDTLS_CONFIG_FILE=\\\"mbedtls_ps5_config.h\\\" -I$platform" \
|
||||
WARNING_CFLAGS='-w' >"$out/mbedtls.log" 2>&1 || { tail -30 "$out/mbedtls.log" >&2; exit 1; }
|
||||
cp "$work/library/libmbedcrypto.a" "$work/library/libmbedx509.a" "$work/library/libmbedtls.a" "$out/"
|
||||
rm -rf "$work"
|
||||
echo "$stamp" > "$archive.stamp"
|
||||
fi
|
||||
|
||||
# ---- opus (decoder only is not an option upstream; the whole library is small) ----
|
||||
archive="$out/libopus.a"
|
||||
stamp=$(stamp_of "$opus/CMakeLists.txt" "$0" $(find "$opus/src" "$opus/celt" "$opus/silk" -maxdepth 1 -type f -name '*.c' | sort))
|
||||
if ! fresh "$archive" "$stamp"; then
|
||||
echo "==> [deps] opus"
|
||||
work="$out/opus-build"
|
||||
cmake -S "$opus" -B "$work" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER="$cc" -DCMAKE_AR="$ar" -DCMAKE_RANLIB="$ranlib" \
|
||||
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
|
||||
-DCMAKE_C_FLAGS_RELEASE="${cflags[*]} -DNDEBUG" \
|
||||
-DOPUS_BUILD_SHARED_LIBRARY=OFF -DOPUS_BUILD_TESTING=OFF -DOPUS_BUILD_PROGRAMS=OFF \
|
||||
-DOPUS_DISABLE_INTRINSICS=ON -DOPUS_X86_MAY_HAVE_AVX2=OFF -DOPUS_X86_PRESUME_AVX2=OFF \
|
||||
-DOPUS_X86_PRESUME_SSE4_1=OFF -DOPUS_STACK_PROTECTOR=OFF -DOPUS_FORTIFY_SOURCE=OFF \
|
||||
>"$out/opus.log" 2>&1 || { tail -30 "$out/opus.log" >&2; exit 1; }
|
||||
cmake --build "$work" --target opus --parallel "$jobs" >>"$out/opus.log" 2>&1 || { tail -30 "$out/opus.log" >&2; exit 1; }
|
||||
cp "$work/libopus.a" "$archive"
|
||||
rm -rf "$work"
|
||||
echo "$stamp" > "$archive.stamp"
|
||||
fi
|
||||
|
||||
# ---- ihslib + PS5 platform layer ------------------------------------------
|
||||
# Sources: everything under src/ except the SDL backends (threads, UDP, HID) and the
|
||||
# OpenSSL crypto; the console gets the pthread backend from platform/ps5.
|
||||
mapfile -t ihslib_sources < <(find "$ihslib/src" -type f -name '*.c' \
|
||||
! -path '*/hid/sdl/*' ! -name 'ihs_thread_sdl.c' ! -name 'ihs_udp_sdl.c' ! -name 'impl_openssl.c' | sort)
|
||||
platform_sources=("$platform/ihs_thread_posix.c" "$platform/ps5_sockets.c" "$platform/ps5_entropy.c" "$platform/ps5_runtime_shims.c")
|
||||
archive="$out/libihslib.a"
|
||||
stamp=$(stamp_of "${ihslib_sources[@]}" "${platform_sources[@]}" "$platform/ps5_compat.h" "$platform/mbedtls_ps5_config.h" "$0" \
|
||||
$(find "$ihslib/include" "$ihslib/src" -type f -name '*.h' | sort))
|
||||
if ! fresh "$archive" "$stamp"; then
|
||||
echo "==> [deps] ihslib + platform (${#ihslib_sources[@]} + ${#platform_sources[@]} files)"
|
||||
objects=()
|
||||
# Socket descriptor calls inside ihslib route through the sceNet adapter (ps5_compat.h);
|
||||
# the adapter itself and the rest of the platform layer see the real libc names.
|
||||
common=("${cflags[@]}" -std=gnu11 -w "-I$ihslib/include" "-I$ihslib/src" "-I$protobuf_c" "-I$mbedtls/include" "-I$platform" "${mbedtls_config[@]}")
|
||||
i=0
|
||||
for source in "${ihslib_sources[@]}"; do
|
||||
object="$out/obj/ihs-$(printf '%03d' "$i")-$(basename "${source%.c}").o"
|
||||
"$cc" "${common[@]}" -include "$platform/ps5_compat.h" -c "$source" -o "$object" &
|
||||
objects+=("$object"); i=$((i + 1))
|
||||
(( i % jobs == 0 )) && wait
|
||||
done
|
||||
wait
|
||||
# set -e does not see a failed background compile: check the objects exist.
|
||||
for object in "${objects[@]}"; do
|
||||
[[ -f $object ]] || { echo "build-deps.sh: compile failed: $object" >&2; exit 1; }
|
||||
done
|
||||
for source in "${platform_sources[@]}"; do
|
||||
object="$out/obj/platform-$(basename "${source%.c}").o"
|
||||
extra=()
|
||||
[[ $(basename "$source") == ps5_sockets.c ]] && extra=(-DPS5_SOCKET_ADAPTER_IMPLEMENTATION)
|
||||
"$cc" "${common[@]}" "${extra[@]}" -c "$source" -o "$object"
|
||||
objects+=("$object")
|
||||
done
|
||||
rm -f "$archive" && "$ar" rcs "$archive" "${objects[@]}" && echo "$stamp" > "$archive.stamp"
|
||||
fi
|
||||
|
||||
# ---- libc++ ---------------------------------------------------------------
|
||||
# The app uses std::string, std::mutex and friends, whose non-inline parts live in
|
||||
# the SDK's libc++. Copied under names the app build's path check accepts.
|
||||
cp "$sdk/target/lib/libc++.a" "$out/libcxx.a"
|
||||
cp "$sdk/target/lib/libc++abi.a" "$out/libcxxabi.a"
|
||||
cp "$sdk/target/lib/libunwind.a" "$out/libunwind.a"
|
||||
|
||||
echo "==> [deps] ready: $(cd "$out" && ls *.a | tr '\n' ' ')"
|
||||
@@ -257,7 +257,10 @@ if [[ -n ${pacbrew_root:-} ]]; then
|
||||
ninja_inputs+=("$input")
|
||||
done < <(find "$pacbrew_root" -type f \( -name '*.a' -o -name '*.so' \) -print0 | sort -z)
|
||||
fi
|
||||
# -L: libc++'s objects carry a dependent-library note for pthread; the SDK's
|
||||
# libpthread.a is empty (threads come from libkernel) and satisfies it.
|
||||
ninja_edge LINK "$build/llvm-pie.elf" "$sdk_root/bin/prospero-lld" -T "$native/ps5-pie.ld" --eh-frame-hdr \
|
||||
-L "$sdk_root/target/lib" \
|
||||
"${wrap_options[@]}" --version-script "$native/app-symbols.map" \
|
||||
-e _start -o "$build/llvm-pie.elf" "${link_inputs[@]}" \
|
||||
--as-needed "$sdk_root"/target/lib/*.so
|
||||
|
||||
Reference in new issue
Block a user