# Bananapass Integration Guide (v1)

## What Bananapass is, and why integrate it

Bananapass is a universal player card, meant to work the same way no
matter what software launches the game. It's built to be adopted broadly,
not tied to any one frontend or loader: any launcher, any standalone tool,
any future piece of software can add support for it using nothing more
than the one API call on this page.

The goal is that a player's Bananapass identity works identically whether
they're using your software or anyone else's. Every integration reaching
the same endpoint, the same way, is what makes that possible. There's no
approval process and no partnership required: add the call, and your
software supports Bananapass.

Typical use case: your frontend adds a screen where a player enters their
Bananapass card number once. You call the endpoint below, and on success,
record locally that the card is active. From then on, your software can
recognize that player as a Bananapass holder, fully offline, forever.

## Activation API

### Endpoint

```
POST https://ultraloader.org/bananapass-activate-api.php
Content-Type: application/json
```

### Request

```json
{
  "action": "activate",
  "card_number": "1234567890123456",
  "pin_hash": "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3",
  "client": "your-integration-name/1.0"
}
```

| field         | required | notes                                                        |
|---------------|----------|---------------------------------------------------------------|
| `action`      | yes      | always `"activate"` for v1                                   |
| `card_number` | yes      | whatever the player typed/configured, sent as-is (trim whitespace, don't reformat) |
| `pin_hash`    | yes      | SHA-256 hex digest of the player's PIN. Obtained from the player's card data. Never send the raw PIN. |
| `client`      | no       | a short string identifying your integration, for the operator's own diagnostics. Not validated. |

No signing, no API key, nothing else required.

### Computing the PIN hash

Hash the PIN concatenated with the card number (PIN first, then card number), as a plain UTF-8 string with no separator. The card number is always exactly 16 digits, so the concatenation is unambiguous.

```js
// JavaScript (Web Crypto API)
async function computePinHash(pin, cardNumber) {
  const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(pin + cardNumber));
  return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
}
```

```python
# Python
import hashlib
pin_hash = hashlib.sha256((pin + card_number).encode()).hexdigest()
```

```csharp
// C#
using System.Security.Cryptography;
using System.Text;
var pinHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(pin + cardNumber))).ToLower();
```

The user types their PIN as normal — the hashing is done by your software before the request is sent. The raw PIN never leaves the user's machine.

### Example

```bash
curl -X POST https://ultraloader.org/bananapass-activate-api.php \
  -H "Content-Type: application/json" \
  -d '{"action":"activate","card_number":"1234567890123456","pin_hash":"<sha256-of-pin>","client":"my-launcher/2.1"}'
```

### Response

HTTP 200 with a JSON body, except `rate_limited` which is HTTP 429:

```json
{"ok": true, "status": "active"}
```

```json
{"ok": false, "error": "invalid_card"}
```

`error` is one of:

| error            | meaning                                                          |
|------------------|------------------------------------------------------------------|
| `invalid_card`   | card number not found, or PIN did not match                      |
| `invalid_format` | not even well-formed (wrong length, non-numeric, etc.)           |
| `pin_required`   | `pin_hash` field was missing or not a valid SHA-256 hex string   |
| `revoked`        | was valid, has since been revoked. Hard block, do not retry      |
| `rate_limited`   | too many attempts from this IP right now. Back off and try later |
| `forbidden`      | the server refused the request for another reason                |

Treat `invalid_card`, `invalid_format`, and `pin_required` the same in your UI, with
something like "That card number or PIN wasn't recognized."

### The offline model: activate once, trust forever

Call this endpoint exactly once, when the player first enters their card
number. On `{"ok":true,"status":"active"}`, record that result locally (a
config file, a database row, whatever fits your software) and never call
this endpoint again for that card unless you're specifically re-checking
it. Your software then runs fully offline, forever.

The only thing that should ever downgrade a locally-recorded "active" card
is separately learning it was `revoked`, which today means calling this
same endpoint again and getting that answer back. There's no periodic
re-check required.

### Swapping cards

To let a player switch to a different Bananapass card, ask them for the new card number and PIN, call this endpoint with the new details, and on success replace the locally stored card with the new one. The old card remains valid and unaffected — there is no server-side "deactivation." The swap is entirely local to your software.

## What data this stores

If you're running the reference Bananapass Service (the background daemon
that watches for known games and backs up their saves), here is exactly
what it tracks locally on the player's own machine, for full transparency:

**One line per session played, added to a growing log:**
- game name
- date
- time
- how long the session lasted
- which program launched it (e.g. a specific frontend, or "unknown" if that
  couldn't be determined)
- how many screenshots were kept for it

The session log is append-only: every finished session adds a new line,
and nothing already written is ever edited or removed by the service.

**One running total per game:**
- how many times it's been played
- total time played, added up across every session
- when it was last played, and by which program
- the most recently kept screenshot

**One summary per card:**
- total sessions across every game
- which game was played most recently, and when

None of this ever leaves the player's machine. Nothing here is uploaded,
synced, or sent anywhere. No score is recorded (there is currently no way
to read one out of an arbitrary game). This is the complete list; nothing
else is tracked.

### How often it looks

While a recognized game is running, the service checks in a few times a
second (about every half of a second). That's how it notices a game
starting or closing promptly, and how it has more than one chance to catch
a screen that's only shown briefly when deciding what to keep. It does
nothing at all when no recognized game is running.

### Network behavior

The activation call described above is the only network request this
service ever makes. It does not check for updates, does not send
telemetry, and does not contact any other server for any reason.

### What it looks at, and what it doesn't

To notice a game starting or closing, the service has to check the names
of currently running processes. It does not record, log, or send anything
about a process that isn't a recognized game. Nothing happens for software
it doesn't know about, not even a note that it was seen running.

For a recognized game only, it also notes which program launched it and
where its executable file is located on disk. This exists purely to tell
two different games apart on the rare occasion they happen to share an
identical filename; it is never checked, recorded, or used for anything
else, and none of it applies to software the service doesn't already
recognize as a specific game.

Screenshot capture only ever targets the recognized game's own window,
only while that specific game is the one being tracked. It never captures
the desktop, never captures another application's window, and never
captures anything while no recognized game is running, period.

The service never reads or writes a game's memory, never injects anything
into a running game, and never intercepts keyboard or mouse input. All it
ever does is check whether a process exists and, if so, take an ordinary
screenshot of its window, the same thing any screenshot tool could see
from outside the process.

It also never requires elevated privileges. On Windows it runs as an
ordinary program in the user's own session, no administrator rights
needed. On Linux it runs as an ordinary user service, no root needed.

### Starting automatically

The service registers itself to launch when the player logs in (on
Windows, a registry entry under the current user; on Linux, a systemd user
service), openly, using the same mechanism any ordinary application would.
It can be removed like any other startup entry: on Windows, from Task
Manager's Startup tab or by deleting the `BananapassService` value under
`HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`; on
Linux, with `systemctl --user disable bananapass-service`.

### Where the data lives

Everything described above is stored in one folder on the player's own
machine (`%LOCALAPPDATA%\BananapassService\` on Windows, and
`~/.local/share/bananapass-service/` on Linux), as plain, ordinary files:
readable JSON text and standard BMP images, nothing hidden, encrypted, or
in a proprietary format. Anyone can open, copy, examine or delete any of it
at any time using nothing more than a text editor and an image viewer.

There is currently no automatic cleanup: the session log keeps every line
ever written, and save-file backups aren't pruned. Deleting old data is a
manual, ordinary file-deletion task for now.