mirror of
https://github.com/SirHumza/orbisRPC.git
synced 2026-10-24 18:02:04 +02:00
18 files changed
+9194
-9035
No files matched your search
+6
-2
@@ -11,8 +11,12 @@ __pycache__/
|
||||
config/config.ps4.json
|
||||
plugin/build/
|
||||
tests/test_utils
|
||||
config/icons/*
|
||||
!config/icons/logo.png
|
||||
# Retro cover art. Fetched at runtime from retro-games.cybermask.dpdns.org
|
||||
# (see the *_art URLs in cfg.c) and cached here locally; nothing under
|
||||
# orbisrpc/ reads these. 3.7 GB across ~23k files, so kept out of history.
|
||||
config/images/PS1/
|
||||
config/images/PS2/
|
||||
config/images/PSP/
|
||||
|
||||
# installer artifacts
|
||||
*.pkg
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="config/icons/logo.png" width="420" alt="orbisRPC">
|
||||
<img src="config/images/icons/logo.png" width="420" alt="orbisRPC">
|
||||
</p>
|
||||
|
||||
# orbisRPC — Discord Rich Presence for PS4 (GoldHEN RPC)
|
||||
@@ -43,7 +43,7 @@ playing to Discord: name, cover art, timer. No PC at runtime.</p>
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 🎮 **Any game, no lists** | Names resolve from your console's metadata (SFO, app.xml) plus Sony's TMDB — CUSA, PPSA, indies, zero per-game setup. |
|
||||
| 🎮 **Any games** | Names resolve on your console via Sony's TMDB — CUSA, zero per-game setup. |
|
||||
| 🕹️ **Homebrew + retro** | Homebrew resolves via pkg-zone; PS1/PS2/PSP classics via a custom list. |
|
||||
| 🖼️ **Real cover art** | Game art served per title, PlayStation logo when idle. |
|
||||
| ⏱️ **True timers** | Survive reconnects and restarts, resume across quick game switches. |
|
||||
@@ -78,9 +78,9 @@ Details: [`docs/DAEMON.md`](docs/DAEMON.md) · full index at [`docs/`](docs/)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Jailbroken PS4 on firmware **9.00 through 13.52**, GoldHEN running.
|
||||
- Jailbroken PS4 on firmware **from 5.05 upto 13.52**, GoldHEN running.
|
||||
- A way to send the payload (elfldr on port 9021, or GoldHEN BinLoader
|
||||
on 9020).
|
||||
on 9090).
|
||||
- A Discord account + your user token (see Install).
|
||||
- Internet on the PS4 that can reach Discord (see Troubleshooting if not).
|
||||
|
||||
@@ -117,16 +117,13 @@ test builds change fast — stick to manual runs for now.
|
||||
**Official release:** will ship a PKG installer that sets everything up
|
||||
for you — including autorun and the nanoDNS exception fix.
|
||||
|
||||
✅ Firmware: confirmed working on 9.00 through 13.52.
|
||||
✅ Firmware: confirmed working on 9.00 through 13.52 but older fimwares should work.
|
||||
|
||||
### What's in the latest test build (0.60)
|
||||
### What's in the latest test build (0.70)
|
||||
|
||||
- Fixed firmware string parsing — older firmware numbers like 9.00 now
|
||||
show properly.
|
||||
- Homebrew apps resolve via pkg-zone.
|
||||
- Retro games (PS1/PS2/PSP) resolve via a custom list.
|
||||
- Optional presence flags, all `true` by default:
|
||||
`show_firmware`, `show_idle`, `show_media`, `show_homebrew`.
|
||||
- Increased ws gateway frame buffer cap to 32MB - should fix oversized gateway frame skipping issue for some users (hopefully🤞).
|
||||
- fixed minor issue with frame draining.
|
||||
- added notification for invalid token.
|
||||
|
||||
## What you'll see
|
||||
|
||||
@@ -273,9 +270,10 @@ Build/test/CI details: [`docs/BUILDING.md`](docs/BUILDING.md).
|
||||
## Credits
|
||||
|
||||
- **SirHumza** — project founder, daemon core.
|
||||
- **CyberMask367** — firmware coverage to 13.52, pkg-zone homebrew,
|
||||
retro lists, presence flags, tester wrangling.
|
||||
- Testers in `testers-chat` — every log file that made a fix possible.
|
||||
- **CyberMask367** — firmware coverage to 13.52, homebrew and retro games resolving, presence flags, tester wrangling.
|
||||
- Testers in `testers-chat` on DIscord — every log file that made a fix possible.
|
||||
- [*PKG-ZONE*](https://pkg-zone.com) — metadata on ps4 hombrew apps used for resolving
|
||||
- [*PSX DATA CENTER*](https://psxdatacenter.com) — source used to make custom json list in [`config`](config/) for resolving retro games
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+1
-8
@@ -1,8 +1 @@
|
||||
{
|
||||
"token": "SET_ME",
|
||||
"application_id": "1536977374795538532",
|
||||
"art_base_url": "https://raw.githubusercontent.com/SirHumza/orbisrpc-host/main/icons/",
|
||||
"enabled": 1,
|
||||
"poll_interval_s": 12,
|
||||
"presence_state": "On PS4"
|
||||
}
|
||||
{"schema_version":1,"token":"SET_ME","application_id":"1536977374795538532","art_base_url":"https://raw.githubusercontent.com/SirHumza/orbisrpc-host/main/icons/","enabled":true,"pkgzone_enabled":true,"retro_enabled":true,"show_firmware":true,"show_idle":true,"show_media":true,"show_homebrew":true,"auto_update":true,"debug":false,"poll_interval_s":12,"presence_state":"On PS4","presence_details_game":"Playing on PlayStation 4","presence_details_home":"Idling on Home Menu","presence_settings_text":"In Settings","asset_idle":"","asset_playing":"","large_art":"https://retro-games.cybermask.dpdns.org/images/ps-logo-full.png","small_art":"https://retro-games.cybermask.dpdns.org/images/ps-logo-blue.png","browser_art":"https://retro-games.cybermask.dpdns.org/images/web_browser.png","home_art":"https://retro-games.cybermask.dpdns.org/images/ps-logo-full.png"}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
File renamed without changes.
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""build_art_table.py - resolve Sony CDN artwork for a title list via TMDB.
|
||||
|
||||
RETIRED: per-title tables were replaced by live TMDB + self-learning map.
|
||||
Kept for history.
|
||||
|
||||
Runs on the BUILD host (console network blocks TMDB port 80) and emits
|
||||
orbisrpc/art_table.h: {titleId, name, icon_url} for every resolvable title.
|
||||
Also downloads each icon into icons/ for the art_base_url pack.
|
||||
|
||||
Usage: python3 docs/research/retired-tables/build_art_table.py titles.txt [--icons icons/]
|
||||
Title list: one TITLEID per line (CUSAxxxxx etc.); unknown IDs are skipped.
|
||||
"""
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
KEY = bytes([
|
||||
0xF5, 0xDE, 0x66, 0xD2, 0x68, 0x0E, 0x25, 0x5B, 0x2D, 0xF7, 0x9E, 0x74,
|
||||
0xF8, 0x90, 0xEB, 0xF3, 0x49, 0x26, 0x2F, 0x61, 0x8B, 0xCA, 0xE2, 0xA9,
|
||||
0xAC, 0xCD, 0xEE, 0x51, 0x56, 0xCE, 0x8D, 0xF2, 0xCD, 0xF2, 0xD4, 0x8C,
|
||||
0x71, 0x17, 0x3C, 0xDC, 0x25, 0x94, 0x46, 0x5B, 0x87, 0x40, 0x5D, 0x19,
|
||||
0x7C, 0xF1, 0xAE, 0xD3, 0xB7, 0xE9, 0x67, 0x1E, 0xEB, 0x56, 0xCA, 0x67,
|
||||
0x53, 0xC2, 0xE6, 0xB0,
|
||||
])
|
||||
TMDB = "http://tmdb.np.dl.playstation.net"
|
||||
|
||||
|
||||
def c_escape(s):
|
||||
return s.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
|
||||
def resolve(tid):
|
||||
mac = hmac.new(KEY, (tid + "_00\x00").encode("latin1")[:12],
|
||||
hashlib.sha1).hexdigest().upper()
|
||||
url = f"{TMDB}/tmdb2/{tid}_00_{mac}/{tid}_00.json"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "orbisRPC-arttbl"})
|
||||
d = json.loads(urllib.request.urlopen(req, timeout=20).read())
|
||||
name = d["names"][0]["name"]
|
||||
icon = d["icons"][0]["icon"].replace("http://", "https://")
|
||||
return name, icon
|
||||
|
||||
|
||||
def main():
|
||||
titles = [l.strip() for l in open(sys.argv[1]) if l.strip()]
|
||||
icons = sys.argv[3] if len(sys.argv) > 3 and sys.argv[2] == "--icons" else None
|
||||
if icons:
|
||||
os.makedirs(icons, exist_ok=True)
|
||||
rows = []
|
||||
for tid in titles:
|
||||
try:
|
||||
name, icon = resolve(tid)
|
||||
except Exception as e:
|
||||
print(f"MISS {tid}: {str(e)[:60]}", flush=True)
|
||||
continue
|
||||
print(f"OK {tid} :: {name}", flush=True)
|
||||
rows.append((tid, name, icon))
|
||||
if icons:
|
||||
try:
|
||||
img = urllib.request.urlopen(icon, timeout=25).read()
|
||||
open(os.path.join(icons, tid.lower() + ".png"), "wb").write(img)
|
||||
except Exception as e:
|
||||
print(f" icon fetch failed: {str(e)[:60]}", flush=True)
|
||||
with open("orbisrpc/art_table.h", "w") as f:
|
||||
f.write("/* art_table.h - GENERATED by docs/research/retired-tables/build_art_table.py.\n")
|
||||
f.write(" * Do not edit: Sony CDN artwork baked at build time so the\n")
|
||||
f.write(" * console needs no TMDB network access for art.\n")
|
||||
f.write(f" * Titles: {len(rows)}. */\n")
|
||||
f.write("#ifndef ORBISRPC_ART_TABLE_H\n#define ORBISRPC_ART_TABLE_H\n")
|
||||
f.write("#include <stddef.h>\n")
|
||||
f.write("typedef struct { const char *id; const char *name;"
|
||||
" const char *icon; } art_entry_t;\n")
|
||||
f.write("static const art_entry_t ORBISRPC_ART_TABLE[] = {\n")
|
||||
for tid, name, icon in rows:
|
||||
f.write(f' {{ "{tid}", "{c_escape(name)}", "{icon}" }},\n')
|
||||
f.write("};\n#define ORBISRPC_ART_TABLE_N "
|
||||
f"(sizeof ORBISRPC_ART_TABLE / sizeof ORBISRPC_ART_TABLE[0])\n")
|
||||
f.write("#endif\n")
|
||||
print(f"wrote art_table.h with {len(rows)} entries")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_nametable.py - build orbisrpc/nametable.h from config/titles.json.
|
||||
|
||||
RETIRED: baked nametables were replaced by the self-learning titles map.
|
||||
Kept for history.
|
||||
|
||||
titles.json maps TITLEID -> display name (extracted from the PS4's
|
||||
/system_data/priv/mms/app.db tbl_appbrowse_* tables, or extended by hand).
|
||||
The generated table is compiled in, so name lookup needs no filesystem
|
||||
access and works inside any console sandbox.
|
||||
|
||||
Usage: python3 docs/research/retired-tables/gen_nametable.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SRC = os.path.join(ROOT, "config", "titles.json")
|
||||
DST = os.path.join(ROOT, "orbisrpc", "nametable.h")
|
||||
|
||||
|
||||
def c_escape(s):
|
||||
out = []
|
||||
for ch in s:
|
||||
o = ord(ch)
|
||||
if ch == '"':
|
||||
out.append('\\"')
|
||||
elif ch == "\\":
|
||||
out.append("\\\\")
|
||||
elif 32 <= o < 127:
|
||||
out.append(ch)
|
||||
else:
|
||||
# octal: unlike \x, always stops after 3 digits, so a hex
|
||||
# digit following the escape can never merge into it.
|
||||
for b in ch.encode("utf-8"):
|
||||
out.append("\\%03o" % b)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def main():
|
||||
with open(SRC, encoding="utf-8") as fh:
|
||||
titles = json.load(fh)
|
||||
items = sorted(titles.items())
|
||||
with open(DST, "w", encoding="utf-8") as fh:
|
||||
fh.write("/* nametable.h - GENERATED by docs/research/retired-tables/gen_nametable.py. Do not edit. */\n")
|
||||
fh.write("#ifndef NAMETABLE_H\n#define NAMETABLE_H\n");
|
||||
fh.write("#include <stddef.h>\n#include <string.h>\n")
|
||||
fh.write("typedef struct { const char *id; const char *name; } nt_entry_t;\n")
|
||||
fh.write("static const nt_entry_t g_nametable[] = {\n")
|
||||
for tid, name in items:
|
||||
fh.write(' { "%s", "%s" },\n' % (c_escape(tid), c_escape(name)))
|
||||
fh.write("};\n")
|
||||
fh.write("static const size_t g_nametable_count = sizeof g_nametable / sizeof g_nametable[0];\n")
|
||||
fh.write("static int nametable_lookup(const char *tid, char *out, size_t cap){\n")
|
||||
fh.write(" if(!tid || !out || cap == 0) return -1;\n")
|
||||
fh.write(" for(size_t i = 0; i < g_nametable_count; i++){\n")
|
||||
fh.write(" if(!strcmp(tid, g_nametable[i].id)){\n")
|
||||
fh.write(" strncpy(out, g_nametable[i].name, cap - 1);\n")
|
||||
fh.write(" out[cap - 1] = 0;\n")
|
||||
fh.write(" return out[0] ? 0 : -1;\n")
|
||||
fh.write(" }\n")
|
||||
fh.write(" }\n")
|
||||
fh.write(" return -1;\n")
|
||||
fh.write("}\n#endif\n")
|
||||
print("wrote %s with %d entries" % (DST, len(items)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,37 +0,0 @@
|
||||
APOL00004
|
||||
CHTM00777
|
||||
CUSA00021
|
||||
CUSA00127
|
||||
CUSA00135
|
||||
CUSA00411
|
||||
CUSA00740
|
||||
CUSA01004
|
||||
CUSA01015
|
||||
CUSA01623
|
||||
CUSA01839
|
||||
CUSA03245
|
||||
CUSA04294
|
||||
CUSA04602
|
||||
CUSA06014
|
||||
CUSA06545
|
||||
CUSA06712
|
||||
CUSA08992
|
||||
CUSA09311
|
||||
CUSA11995
|
||||
CUSA13323
|
||||
CUSA14909
|
||||
CUSA18795
|
||||
CUSA20177
|
||||
CUSA20499
|
||||
CUSA24899
|
||||
CUSA26618
|
||||
CUSA33151
|
||||
FLTZ00003
|
||||
GBTX00001
|
||||
LAPY20006
|
||||
LAPY20009
|
||||
LAPY20015
|
||||
MANU90003
|
||||
NPXS39041
|
||||
PKGI13337
|
||||
CUSA09303
|
||||
@@ -68,6 +68,12 @@ static int s_settings_posted = 0;
|
||||
static int s_in_browser = -1;
|
||||
static int s_browser_posted = 0;
|
||||
|
||||
/* Shown when the gateway closes with 4004 (token rejected). Same text at
|
||||
* connect time and mid-session, so one constant covers both. Throttled
|
||||
* there rather than once-per-boot because a rejected token persists and
|
||||
* the daemon keeps retrying on purpose -- it never exits on 4004. */
|
||||
#define TOKEN_REJECTED_MSG "Discord token rejected - make sure Token is valid"
|
||||
|
||||
/* "Firmware 13.52" for the Settings presence. Reads through the same helper the
|
||||
* rest of the daemon uses, so an unavailable firmware string degrades to
|
||||
* "unknown" instead of stalling the post. */
|
||||
@@ -349,6 +355,7 @@ int daemon_run(void){
|
||||
* reload picks up a fixed token on its own. */
|
||||
log_msg("WARN: token rejected by gateway (close 4004). "
|
||||
"Fix \"token\" in %s; retrying", CFG_PATH);
|
||||
notify_throttled("token4004", TOKEN_REJECTED_MSG, 300);
|
||||
int wait = reconnect_delay(&conn_fails, base_poll, &jctr);
|
||||
if(sleep_stop(wait)) break;
|
||||
continue;
|
||||
@@ -746,6 +753,7 @@ int daemon_run(void){
|
||||
* 4004 (never strand the daemon). Drop to the outer loop:
|
||||
* backoff + config reload picks up a fixed token alone. */
|
||||
log_msg("WARN: token rejected (close 4004). Fix %s; retrying", CFG_PATH);
|
||||
notify_throttled("token4004", TOKEN_REJECTED_MSG, 300);
|
||||
ws_close(&dc.ws);
|
||||
pres_set(&pres, PS_NONE);
|
||||
if(sleep_stop(reconnect_delay(&conn_fails, base_poll, &jctr))) break;
|
||||
|
||||
+9042
-8814
File diff suppressed because it is too large.
Load diff
+28
-2
@@ -339,6 +339,10 @@ static int valid_frame_header(const unsigned char *b, uint64_t plen){
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ws_skip_plan() lives in ws_skip.c so the host tests can compile it without
|
||||
* the socket/TLS half of this file. */
|
||||
#include "ws_skip.c"
|
||||
|
||||
int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out){
|
||||
if(!w || !w->connected || !buf || cap==0) return -1;
|
||||
for(;;){
|
||||
@@ -376,7 +380,7 @@ int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out)
|
||||
size_t h = (hdr < avail) ? hdr : avail;
|
||||
w->rpos += h; /* eat the header */
|
||||
size_t payload_here = avail - h;
|
||||
w->skip_left = plen - (uint64_t)payload_here;
|
||||
ws_skip_plan(plen, payload_here, &w->skip_left);
|
||||
w->skip_op = wire_op;
|
||||
if(w->rpos >= w->rlen){ w->rpos=0; w->rlen=0; }
|
||||
continue;
|
||||
@@ -401,7 +405,29 @@ int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out)
|
||||
return (int)copy;
|
||||
}
|
||||
}
|
||||
if(w->rlen == w->rcap){ /* full with no parseable frame: give up cleanly */
|
||||
if(w->rlen == w->rcap){
|
||||
/* A full buffer during a drain is NORMAL, not a fault: an
|
||||
* oversized frame is by definition larger than the buffer, so
|
||||
* the drain can only make progress by discarding what is
|
||||
* buffered. Bailing out here wiped the buffer and returned -2
|
||||
* with skip_left outstanding, which left the socket mid-frame
|
||||
* and made the next parse read JSON payload as a frame header
|
||||
* (observed: op=2 fin=0 plen=125 b1=0x22 -- a quote byte).
|
||||
* Consume against skip_left instead; the loop below refills
|
||||
* from TLS, so no headroom is actually needed. */
|
||||
if(w->skip_left > 0){
|
||||
size_t take = (size_t)w->skip_left;
|
||||
if(take > w->rlen) take = w->rlen;
|
||||
w->skip_left -= take;
|
||||
w->rpos += take;
|
||||
if(w->rpos >= w->rlen){ w->rpos=0; w->rlen=0; }
|
||||
if(w->skip_left == 0){
|
||||
if(opcode_out)*opcode_out = w->skip_op;
|
||||
return -3;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* full with no parseable frame and no drain: give up cleanly */
|
||||
log_msg("ws: buffer full with no frame; dropping %zuB and reconnecting", w->rlen);
|
||||
w->rpos=0; w->rlen=0; return -2;
|
||||
}
|
||||
|
||||
+16
-1
@@ -7,7 +7,16 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#define WS_RBUF_MIN 65536 /* initial raw socket buffer (READY is big) */
|
||||
#define WS_RBUF_MAX (8*1024*1024) /* grow limit; bigger frames are skipped */
|
||||
/* Grow limit; anything larger is drained and skipped.
|
||||
*
|
||||
* 32 MB, raised from 8 MB on 2026-10-06: a real account's READY measured
|
||||
* 11.7 MB across four attempts (11695449 / 11694320 / 11694256 / 11706895),
|
||||
* well past the old cap, so identify never completed and the gateway
|
||||
* reported a connect failure. The value is not stable frame-to-frame, so
|
||||
* this is sized with real headroom rather than matched to one observation.
|
||||
* rbuf doubles from 64 KB, so a full READY holds ~32 MB of PS4 heap for
|
||||
* the life of the session. */
|
||||
#define WS_RBUF_MAX (32*1024*1024)
|
||||
|
||||
typedef struct {
|
||||
int32_t sock; int32_t connected; int32_t fd;
|
||||
@@ -28,4 +37,10 @@ int ws_send_text(ws_t *w, const char *msg, size_t len);
|
||||
int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out);
|
||||
int ws_pong(ws_t *w); /* answer a server PING (call when recv gives opcode 9) */
|
||||
int ws_close(ws_t *w);
|
||||
/* How many payload bytes remain to discard after an oversized frame's header
|
||||
* has been consumed. Saturates at 0 rather than wrapping when payload_here
|
||||
* already covers plen. Pure, so the host tests can pin it: the drain state
|
||||
* lives in the ws_t and a bad value is only visible on the console as a
|
||||
* desynced frame stream. */
|
||||
void ws_skip_plan(uint64_t plen, size_t payload_here, uint64_t *skip_left_out);
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
/* ws_skip.c - oversized-frame drain arithmetic, split out of ws.c.
|
||||
*
|
||||
* This is the only part of ws.c that can be tested on the host: it has no
|
||||
* socket, no tls_ctx_t and no PS4 headers. The rest of ws_recv_frame() is
|
||||
* wired to those, so a bug here used to be invisible until a real gateway
|
||||
* handshake on a real console.
|
||||
*
|
||||
* Why it is worth isolating: drain state lives in the ws_t, so a wrong
|
||||
* value here does not fail loudly. On console 2026-10-06 an 11.7 MB READY
|
||||
* overflowed the buffer, the drain miscounted, and the next parse read JSON
|
||||
* payload as a frame header (op=2 fin=0 plen=125 b1=0x22 -- a quote byte),
|
||||
* which surfaced only as "cannot connect to discord".
|
||||
*
|
||||
* Included by ws.c; compiled directly by tests/test_utils.c. Both paths get
|
||||
* the same code, so the tests cannot drift from the payload.
|
||||
*/
|
||||
#include "ws.h"
|
||||
|
||||
void ws_skip_plan(uint64_t plen, size_t payload_here, uint64_t *skip_left_out){
|
||||
if(!skip_left_out) return;
|
||||
*skip_left_out = ((uint64_t)payload_here >= plen)
|
||||
? 0 : plen - (uint64_t)payload_here;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ to prepare a source icon.
|
||||
"""
|
||||
import sys, os, struct
|
||||
|
||||
SRC = sys.argv[1] if len(sys.argv) > 1 else 'config/icons/logo.notify.png'
|
||||
SRC = sys.argv[1] if len(sys.argv) > 1 else 'config/images/icons/logo.notify.png'
|
||||
OUT = sys.argv[2] if len(sys.argv) > 2 else 'orbisrpc/icon_embedded.h'
|
||||
|
||||
MAX_ICON = 512
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../orbisrpc/pkgzone.h"
|
||||
#include "../orbisrpc/gamecache.h"
|
||||
#include "../orbisrpc/fw.h"
|
||||
#include "../orbisrpc/ws.h"
|
||||
#include "../orbisrpc/gamecache.h"
|
||||
#include "../orbisrpc/pkgzone.h"
|
||||
#include "../installer/icfg.h"
|
||||
@@ -38,6 +39,10 @@ int ws_recv_frame(ws_t *w, char *b, size_t c, int *o, int *f){
|
||||
}
|
||||
int ws_pong(ws_t *w){ (void)w; return -1; }
|
||||
int ws_close(ws_t *w){ (void)w; return -1; }
|
||||
/* ws.c itself is not linked on the host (it owns a tls_ctx_t), so the pure
|
||||
* drain arithmetic comes in directly. Only ws_skip_plan is needed, and it is
|
||||
* real code compiled from the real source, not a copy. */
|
||||
#include "../orbisrpc/ws_skip.c"
|
||||
#include <mbedtls/ecdsa.h>
|
||||
#include <mbedtls/ecp.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
@@ -93,6 +98,56 @@ static void test_gateway_op_spoof(void) {
|
||||
jl_free(r2);
|
||||
}
|
||||
|
||||
/* --- oversized-frame drain arithmetic ---------------------------------
|
||||
* A real account's READY measured 11.7 MB (four attempts, 11695449 /
|
||||
* 11694320 / 11694256 / 11706895), past the old 8 MB cap, so identify never
|
||||
* completed. The drain then left the socket mid-frame and the next parse
|
||||
* read JSON payload as a header (op=2 fin=0 plen=125 b1=0x22 -- a quote).
|
||||
* ws_skip_plan is the pure piece, so the silent failure modes are pinned
|
||||
* here instead of on the console. */
|
||||
static void test_ws_skip_plan(void) {
|
||||
uint64_t left = 0xdeadbeef;
|
||||
|
||||
/* The real READY, header just eaten, nothing of the body buffered. */
|
||||
ws_skip_plan(11695449, 0, &left);
|
||||
assert(left == 11695449);
|
||||
|
||||
/* Part of the body already buffered: drain only the remainder. */
|
||||
ws_skip_plan(11695449, 65536, &left);
|
||||
assert(left == 11695449 - 65536);
|
||||
|
||||
/* Body fully buffered already -> nothing left to discard. This is the
|
||||
* saturation case; the old bare subtraction underflowed to ~1.8e19 here
|
||||
* and the drain never terminated. */
|
||||
ws_skip_plan(1000, 1000, &left);
|
||||
assert(left == 0);
|
||||
/* More buffered than the header claimed: must still saturate, never wrap. */
|
||||
ws_skip_plan(1000, 5000, &left);
|
||||
assert(left == 0);
|
||||
ws_skip_plan(100, (size_t)-1, &left);
|
||||
assert(left == 0);
|
||||
|
||||
/* Draining to exactly zero must land on zero, not one byte over. */
|
||||
ws_skip_plan(2048, 2047, &left);
|
||||
assert(left == 1);
|
||||
ws_skip_plan(2048, 2048, &left);
|
||||
assert(left == 0);
|
||||
|
||||
/* The cap the console actually hits: 11.7 MB frame, 32 MB buffer. */
|
||||
ws_skip_plan(11695449, 32768, &left);
|
||||
assert(left == 11695449 - 32768);
|
||||
/* A frame larger than even the raised cap still drains sanely. */
|
||||
ws_skip_plan((uint64_t)64 * 1024 * 1024, 0, &left);
|
||||
assert(left == (uint64_t)64 * 1024 * 1024);
|
||||
|
||||
/* NULL out-pointer must not crash. */
|
||||
ws_skip_plan(1000, 10, NULL);
|
||||
|
||||
/* The cap must actually be above the measured READY, or every one of
|
||||
* these scenarios returns to the console as a failed connect. */
|
||||
assert(WS_RBUF_MAX > 11706895);
|
||||
}
|
||||
|
||||
static void test_json_oom_safe(void) {
|
||||
jl_val_t *t = jl_parse("true", 4); assert(t != NULL); jl_free(t);
|
||||
jl_val_t *f = jl_parse("false", 5); assert(f != NULL); jl_free(f);
|
||||
@@ -1438,6 +1493,7 @@ int main(void) {
|
||||
test_procwalk_find_and_pid();
|
||||
test_json();
|
||||
test_gateway_op_spoof();
|
||||
test_ws_skip_plan();
|
||||
test_json_oom_safe();
|
||||
test_json_hostile();
|
||||
test_tmdb();
|
||||
|
||||
Reference in new issue
Block a user