# origin-sdk
This crate handles the low-level networking, cryptography, and request/response mapping of the LSX protocol, providing an asynchronous API for connecting, authenticating, and interacting with the Origin (EA Desktop) backend.
## Usage
```rs
use origin_sdk::{protocol::game::GetAllGameInfo, sdk::OriginSdk};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to the Origin SDK server
let (client, _) = OriginSdk::connect("127.0.0.1:3216").await.unwrap();
let game_info = client.send_request(GetAllGameInfo {}).await.unwrap();
println!("{:#?}", game_info);
// GetAllGameInfoResponse {
// up_to_date: true,
// languages: "de_DE,en_US,es_ES,es_MX,fr_FR,it_IT,ja_JP,ko_KR,pl_PL,pt_BR,ru_RU,zh_CN,zh_TW",
// free_trial: false,
// full_game_purchased: true,
// full_game_released: true,
// full_game_release_date: "0000-00-00T00:00:48",
// expiration: "0000-00-00T00:00:08",
// system_time: "2025-09-03T12:15:54",
// has_expiration: false,
// installed_version: "",
// installed_language: "en_US",
// available_version: "0.0.0.0",
// display_name: "Knockout City™",
// max_group_size: 16,
// entitlement_source: "EPIC",
// }
Ok(())
}
```
## LSX models
All request types are defined in [`src/protocol`](src/protocol/) and are organized by domain:
- `achievements.rs`
- `auth.rs`
- `broadcast.rs`
- `chat.rs`
- etc.
Each request implements the `RequestResponse` trait, which is generated by the `request_response!` macro.
This ensures requests are automatically mapped to their corresponding `ResponseBody` variant:
```rs
pub enum RequestBody {
// ...
GetProfile(GetProfile)
// ...
}
pub enum ResponseBody {
// ...
GetProfileResponse(GetProfileResponse),
// ...
}
request_response! {
// ...
GetProfile => GetProfileResponse,
// ...
}
```
```rs
// This call will be automatically deserialized into a `GetProfileResponse`
let profile = client.send_request(GetProfile { /* ... */ }).await?;
```
## Cryptography
The LSX protocol uses a custom AES-128-ECB scheme with deterministic key derivation.
The session key is derived through a challenge handshake with the server.
### Key Notes
- AES-128-ECB + PKCS#7 padding is used for encryption and decryption
- Session keys are generated from integer seeds and are expanded into 16-byte arrays
- Pseudo-random number generator is used to produce bytes from the seed
### Challenge Flow
1. The server sends a `Challenge` event with a random hex string key
2. The client encrypts this ASCII challenge string using AES-128-ECB with the default key
3. The encrypted data is hex-encoded into a string
4. The first two bytes of the hex string as ASCII bytes are combined into a u16 seed
```js
seed = (byte[0] << 8) | byte[1]
```
5. The derived seed is used to generate a new AES key and it becomes the session key for all subsequent encryption/decryption operations
6. The client sends a `ChallengeResponse` request back to the server, which includes:
- The original challenge key
- The encrypted + hex-encoded response key
- SDK metadata (protocol version, SDK version, etc.)
7. The server sends a `ChallengeAccepted` response, both sides now share the same AES key
## Acknowledgements
- `Warranty Voider` for releasing [LSX-Dumper](https://github.com/zeroKilo/LSX-Dumper) and publishing information about the protocol on various forums
- `Bergmann89` for releasing [xsd-parser](https://github.com/Bergmann89/xsd-parser), which helped with LSX model generation
## Disclaimer
This project is a **third-party reimplementation** of the Origin SDK, based on reverse-engineering and observations of Origin SDK versions 9.12.1.7 and 10.6.1.8, intended for educational and research purposes only.
It is not affiliated with, endorsed by, or supported by Electronic Arts.