Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Build
run-name: ${{ github.event.inputs.version }} Build

on:
workflow_dispatch:
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/tools.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Tools

on:
workflow_dispatch:

jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- uses: ilammy/msvc-dev-cmd@v1

# Configure the src tree (same as the product build); the tools live there
# via add_subdirectory and land in build/tools/<config>/.
- name: Configure
run: cmake -S src -B build -G "Ninja Multi-Config"

# Build only the host tool targets, not the OpenSteamTool DLL.
- name: Build Release
run: cmake --build build --config Release --target ipc_codegen extract_tickets

- name: Build Debug
run: cmake --build build --config Debug --target ipc_codegen extract_tickets

- name: Upload Release artifacts
uses: actions/upload-artifact@v4
with:
name: OpenSteamTool-Tools-Release
path: build/tools/Release/

- name: Upload Debug artifacts
uses: actions/upload-artifact@v4
with:
name: OpenSteamTool-Tools-Debug
path: build/tools/Debug/
75 changes: 40 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,41 @@ OpenSteamTool is a Windows DLL project built with CMake.
- Adding, modifying, deleting, or overwriting `.lua` files in any watched directory automatically triggers a reload. No restart, no offline/online toggle needed.

### Family Sharing and Remote Play
- Bypass Steam Family Sharing restrictions, allowing shared games to be played without limitations.
- Bypass Steam Family Sharing restrictions for games that have been added to the library with `addappid` in Lua. All accounts in the Steam Family that participate in sharing must use OpenSteamTool for this to work.

### Compatible with games protected by Denuvo and SteamStub
- For AppTicket and ETicket: in `HKEY_CURRENT_USER\Software\Valve\Steam\Apps\{AppId}`, both `AppTicket` and `ETicket` are `REG_BINARY` values.
- SteamStub-only games do not require configuring `AppTicket`. OpenSteamTool can reuse Steam's local ConfigStore ticket and forge the requested AppId through a SteamDRMP off-by-four ticket parsing vulnerability, without injecting into the game process.
- Denuvo-protected games still require explicit ticket data. In `HKEY_CURRENT_USER\Software\Valve\Steam\Apps\{AppId}`, both `AppTicket` and `ETicket` are `REG_BINARY` values.
- Use `setAppTicket(appid, "hex")` and `setETicket(appid, "hex")` in Lua config to write these values to the registry automatically.
- SteamID priority: read `SteamID` as `REG_SZ` (numeric-only) first; if missing, parse from `AppTicket`.
- AppTicket priority: explicit tickets have the highest priority, including tickets configured by `setAppTicket` and existing `AppTicket` registry values. If no explicit AppTicket is available, OpenSteamTool falls back to the forged local ConfigStore ticket path.
- SteamID priority: read `SteamID` as `REG_SZ` (numeric-only) first; if missing, parse from explicit `AppTicket`.

#### Extracting tickets with `extract_tickets`

The `extract_tickets` tool dumps the `AppTicket` and `ETicket` hex strings you need for `setAppTicket` / `setETicket`. Run it on a machine where Steam is running and logged into an account that **owns** the target game.

1. Build the tools (see [Build](#build)); the binary lands in `build/tools/Release/extract_tickets.exe`.
2. Run it with the target AppId (or run it with no argument and type the AppId when prompted):
```powershell
extract_tickets.exe 1361510
```
3. It reads the Steam install path from the registry, loads `steamclient64.dll`, and writes everything into an `<appid>/` folder next to the executable:
- `appticket.bin` — raw app ownership ticket (binary)
- `eticket.bin` — raw encrypted app ticket (binary)
- `tickets.txt` — plain-text summary with the hex strings:
```
appid:1361510
appticket(184 bytes):14000000...
eticket(143 bytes):...
```
A ticket that could not be obtained is reported as `appticket:null` / `eticket:null`.
4. Paste the hex strings from `tickets.txt` into your Lua config:
```lua
setAppTicket(1361510, "14000000...")
setETicket(1361510, "...")
```

> **Note:** Tickets are only valid when extracted from an account that **genuinely owns** the game.

### Stats and Achievements
- Enable stats and achievements for unowned games.
Expand Down Expand Up @@ -96,10 +125,9 @@ timeout_recv_ms = 10000
[lua]
paths = []

# Optional signature-file mirror. See "Steam version compatibility" below.
# Leave commented out for the built-in default (raw.githubusercontent.com).
[pattern]
# mirror = "https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern"
# Optional metadata mirror. See "Steam version compatibility" below.
[remote]
# url_template = "https://your.server/{channel}/{component}/{sha256}.toml"
```

### Manifest via Lua
Expand Down Expand Up @@ -143,39 +171,16 @@ You can also drop a pattern TOML into the cache directory manually if you know t

#### Using a different mirror

For most users, the built-in **GitHub jsDelivr** automatic fallback is enough; you do not need to touch `opensteamtool.toml` at all.
For most users, the built-in **GitHub -> jsDelivr** fallback is enough. To use a private mirror or intranet server, configure a full URL template. A custom mirror replaces the built-in remote sources; local cache fallback remains available.

If you want to force a specific source (private mirror, intranet server, or a CDN that's faster on your network than the defaults), set it explicitly in `opensteamtool.toml`. **Setting `mirror` disables the automatic GitHub→jsDelivr fallback** — only the URL you specify is tried, on the principle that an explicit user choice should win.
The template must include `{channel}`, `{component}`, and `{sha256}`. Channels currently used are `pattern` and `ipc`.

```toml
[pattern]
# Default if unset:
# https://raw.githubusercontent.com/OpenSteam001/steam-monitor/pattern
# Examples:
mirror = "https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern"
# mirror = "https://ghproxy.com/https://raw.githubusercontent.com/OpenSteam001/steam-monitor/pattern"
# mirror = "https://your.server.com/opensteamtool-patterns"
[remote]
url_template = "https://your.server/{channel}/{component}/{sha256}.toml"
# url_template = "https://fast.jsdelivr.net/gh/OpenSteam001/steam-monitor@{channel}/{component}/{sha256}.toml"
```

The full URL fetched at runtime is `<mirror>/steamclient/<sha256>.toml` and `<mirror>/steamui/<sha256>.toml`. Any HTTPS server that serves the same directory layout works. A trailing `/` is allowed but optional.

Resolved URL by config (example, for the `steamui` lookup):

| Config | Resulting URL |
|---|---|
| `[pattern]` omitted, or `mirror = ""` | `https://raw.githubusercontent.com/OpenSteam001/steam-monitor/pattern/steamui/<sha>.toml` |
| `mirror = "https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern"` | `https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern/steamui/<sha>.toml` |
| `mirror = "https://your.server.com/p/"` (trailing slash) | `https://your.server.com/p/steamui/<sha>.toml` (slash stripped at parse) |

**Verifying a mirror in your browser:** paste a complete URL — base + subdir + a real SHA-256 + `.toml`. The base URL alone (without the file path) will return `Invalid URL` from most CDNs, which is expected behavior, not a sign the mirror is broken. Example URLs you can paste directly:

```
https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern/steamui/7a72275b5efc6781a964f6a8e5414ea2226c4a0a64a82e79b9e7d501dfcc3b57.toml
https://raw.githubusercontent.com/OpenSteam001/steam-monitor/pattern/steamui/7a72275b5efc6781a964f6a8e5414ea2226c4a0a64a82e79b9e7d501dfcc3b57.toml
```

Replace the hash with a real one from [the upstream `pattern` branch](https://github.com/OpenSteam001/steam-monitor/tree/pattern/steamui). If the browser returns `200` you're good; `404` means upstream hasn't published a file for that DLL yet (open an issue), and connect/timeout errors mean the mirror itself isn't reachable from your network — pick another.

### Debug logging

Debug builds write per-module log files under `<Steam>/opensteamtool/`:
Expand Down
6 changes: 6 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ for %%C in (%CONFIGS%) do (
echo [INFO] Building: %%C
cmake --build build --config %%C
if errorlevel 1 goto :fail

REM extract_tickets is EXCLUDE_FROM_ALL, so build it explicitly. It lands in
REM build\tools\%%C\ rather than the shipped output directory.
echo [INFO] Building tool extract_tickets for %%C
cmake --build build --config %%C --target extract_tickets
if errorlevel 1 goto :fail
)

echo [OK] Build completed successfully.
Expand Down
50 changes: 6 additions & 44 deletions opensteamtool.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,48 +65,10 @@ timeout_recv_ms = 10000
[lua]
# paths = []

[pattern]
# Mirror base URL for the per-DLL signature TOML files.
# At runtime, OpenSteamTool appends "/steamclient/<sha256>.toml" and
# "/steamui/<sha256>.toml" to whatever you set here.
[remote]
# Optional metadata mirror. Leave unset to use GitHub with jsDelivr fallback.
# A custom mirror replaces the built-in remote sources and must include all
# three placeholders: {channel}, {component}, and {sha256}.
#
# Default behaviour (this key commented out / empty) — every launch:
# 1. Try https://raw.githubusercontent.com/OpenSteam001/steam-monitor/pattern
# 2. On connection failure, automatically fall back to
# https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern
# 3. On total remote failure, fall back to <Steam>/opensteamtool/pattern/
# (the local cache, written after each successful remote fetch).
#
# Remote is consulted every launch so the upstream bot can re-publish a TOML
# (adding new signatures or fixing existing ones) and users pick it up
# automatically without clearing any cache.
#
# Most users — including users behind GFW where raw.githubusercontent.com
# is blocked but jsDelivr is reachable — do NOT need to set anything here.
#
# Setting `mirror` below DISABLES the automatic GitHub→jsDelivr fallback:
# only the URL you specify is tried (an explicit user choice wins). Local
# cache fallback still applies. Use this if you have a private mirror, an
# intranet server, or want a CDN that's faster than both defaults on your
# network. Verify your chosen mirror by
# opening the FULL URL (mirror + subdir + a real sha256 + ".toml") in a
# browser — pasting just the mirror base will usually show "Invalid URL"
# from CDNs, that's expected.
#
# Example full URL to verify in a browser (replace <sha> with one from
# https://github.com/OpenSteam001/steam-monitor/tree/pattern/steamui):
# https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern/steamui/<sha>.toml
#
# Common mirrors:
#
# # jsDelivr (global CDN, generally reachable from mainland China)
# mirror = "https://cdn.jsdelivr.net/gh/OpenSteam001/steam-monitor@pattern"
#
# # ghproxy (community proxy — availability varies, use at your own risk)
# mirror = "https://ghproxy.com/https://raw.githubusercontent.com/OpenSteam001/steam-monitor/pattern"
#
# # Self-hosted: any HTTPS server that serves the same directory layout
# mirror = "https://your.server.com/opensteamtool-patterns"
#
# A trailing "/" is allowed but optional; OpenSteamTool strips it.
# mirror = ""
# url_template = "https://your.server/{channel}/{component}/{sha256}.toml"
# url_template = "https://fast.jsdelivr.net/gh/OpenSteam001/steam-monitor@{channel}/{component}/{sha256}.toml"
65 changes: 37 additions & 28 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,50 +22,54 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
# Dependency recipes (FetchContent-backed, cached at <repo>/.deps).
# ---------------------------------------------------------------------------
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(OPENSTEAMTOOL_TOOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../tools")
include(Lua)
include(Detours)
include(Spdlog)
include(Protobuf)
include(Tomlplusplus)
include(LogMacros)

# Build the host code generators (ipc_codegen) in their own binary subtree so
# their executables never land in the shipped output directory. EXCLUDE_FROM_ALL
# means only the tools actually depended on (ipc_codegen) get built here.
add_subdirectory("${OPENSTEAMTOOL_TOOLS_DIR}" "${CMAKE_BINARY_DIR}/tools" EXCLUDE_FROM_ALL)
include("${OPENSTEAMTOOL_TOOLS_DIR}/cmake/IPCCodegen.cmake")

set(IPC_IDL "${CMAKE_CURRENT_SOURCE_DIR}/Steam/IPCMessages.steamd")
# Per-config output dir, mirroring the protobuf recipe below: a Multi-Config
# build invokes the generator once per config, and a single shared output file
# would ping-pong between the Release/Debug graphs and force needless rebuilds.
opensteamtool_add_ipc_codegen(IPC_GEN
IDL "${IPC_IDL}"
CPP_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated/$<CONFIG>"
)

# ---------------------------------------------------------------------------
# Protobuf code generation — two variants from the same .proto:
# Protobuf code generation — one variant per config, from the same .proto:
#
# Debug → full Message (protoc --cpp_out) → links libprotobuf
# Release → lite MessageLite (protoc --cpp_out=lite) → links libprotobuf-lite
# Debug → full Message (protoc --cpp_out) → links libprotobuf
# Release → lite MessageLite (protoc --cpp_out=lite:) → links libprotobuf-lite
#
# Both land in separate subdirectories of the build tree so the source
# directory stays clean and the right set is picked per configuration.
# Output lands in the per-config generated/ dir (same root as the IPC header),
# so each config owns its own files. A shared output path would ping-pong
# between the Release/Debug graphs (the command embeds the config-specific
# protoc path) and force a regen + recompile on every config switch.
# ---------------------------------------------------------------------------
set(PROTO_SRC "${CMAKE_CURRENT_SOURCE_DIR}/proto/steam_messages.proto")
set(PROTO_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/proto")
set(PROTO_GEN_LITE_DIR "${CMAKE_CURRENT_BINARY_DIR}/proto_lite")
set(PROTO_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/$<CONFIG>")

# Full Message (Debug)
add_custom_command(
OUTPUT "${PROTO_GEN_DIR}/steam_messages.pb.cc"
"${PROTO_GEN_DIR}/steam_messages.pb.h"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROTO_GEN_DIR}"
COMMAND $<TARGET_FILE:protoc>
"--cpp_out=${PROTO_GEN_DIR}"
"-I${CMAKE_CURRENT_SOURCE_DIR}/proto"
"${PROTO_SRC}"
DEPENDS "${PROTO_SRC}" protoc
COMMENT "Generating protobuf full-Message sources (Debug)"
)

# Lite MessageLite (Release)
add_custom_command(
OUTPUT "${PROTO_GEN_LITE_DIR}/steam_messages.pb.cc"
"${PROTO_GEN_LITE_DIR}/steam_messages.pb.h"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROTO_GEN_LITE_DIR}"
COMMAND $<TARGET_FILE:protoc>
"--cpp_out=lite:${PROTO_GEN_LITE_DIR}"
"--cpp_out=$<$<CONFIG:Release>:lite:>${PROTO_GEN_DIR}"
"-I${CMAKE_CURRENT_SOURCE_DIR}/proto"
"${PROTO_SRC}"
DEPENDS "${PROTO_SRC}" protoc
COMMENT "Generating protobuf lite-MessageLite sources (Release)"
COMMENT "Generating protobuf C++ sources"
VERBATIM
)

# ---------------------------------------------------------------------------
Expand All @@ -85,6 +89,12 @@ add_library(OpenSteamTool SHARED
Utils/WinHttp.cpp
Utils/FileWatcher.cpp
Utils/ManifestClient.cpp
Utils/RemoteToml.cpp
Utils/IPCLoader.cpp
Utils/SteamDiagnostics.cpp

# Generated IPC message structs (depends on IPCMessages.steamd via add_custom_command)
"${IPC_GEN}"

# Per-category hook modules
Hook/HookManager.cpp
Expand All @@ -97,20 +107,19 @@ add_library(OpenSteamTool SHARED
Hook/Hooks_Manifest.cpp
Hook/Hooks_Misc.cpp
Hook/Hooks_NetPacket.cpp
Hook/PendingAPICalls.cpp
Hook/Hooks_SteamUI.cpp
Hook/Hooks_Package.cpp

# protobuf generated sources — per-config variant
$<$<CONFIG:Debug>:${PROTO_GEN_DIR}/steam_messages.pb.cc>
$<$<CONFIG:Release>:${PROTO_GEN_LITE_DIR}/steam_messages.pb.cc>
# protobuf generated sources (full in Debug, lite in Release)
"${PROTO_GEN_DIR}/steam_messages.pb.cc"
)

# Header search path — per-config include directory
target_include_directories(OpenSteamTool PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}/generated
$<$<CONFIG:Debug>:${PROTO_GEN_DIR}>
$<$<CONFIG:Release>:${PROTO_GEN_LITE_DIR}>
${CMAKE_CURRENT_BINARY_DIR}/generated/$<CONFIG>
)

target_link_libraries(OpenSteamTool PRIVATE
Expand Down
Loading
Loading