Skip to content

About

Replacement for the EA Sports Tiger Woods PGA Tour 2004 online server

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Tiger Woods PGA Tour 2004 — online master server

A replacement for EA's long-dead online service for Tiger Woods PGA Tour 2004 on the PlayStation 2, for playing online through the PCSX2 emulator.

My server is online at jeddyh.fyi/TW04Online.

Create an account on the website and use it to log into the game's Online feature; the in-game account creation does not work.

It brings back the game's whole online menu:

  • Lobby: accounts and personas, game rooms, chat, and challenges.
  • Head-to-head play: match play and stroke play, with results recorded.
  • Online tournaments: a daily event with its own leaderboards.
  • Leaderboards and stats: your rank and statistics on the in-game screens.
  • EA Messenger: buddy lists, blocking, who's online, and messages between players.
  • In-game news: your own text, plus a digest the server writes from recent results.
  • Report abuse: reports are stored with the chat that led up to them.

Alongside the lobby runs a web site. Players create their account and download the game patch there. It also shows live server status, leaderboards, the tournament calendar with a page for every event, monthly seasons and a Hall of Fame, player profiles with handicaps and achievements, head-to-head records, side-by-side comparisons, tour stats with a 30-day activity chart, records, and course pages with what each tournament setting costs in strokes. A private admin page covers password resets, bans, renames and the in-game news.

Both are plain Python with no dependencies: no framework, no database server, no build step.

How the game's online protocol was worked out -- every message, field and screen, read from the game's executable and a live console -- is written up in docs/PROTOCOL.md, and the scripts used to read the executable are in analysis/.

This is a fan project. It is not affiliated with, endorsed by or connected to Electronic Arts or Sony. No game files, BIOS or disc images are included, and none are needed to run the server.


Contents


What you need

  • Python 3.8 or newer. There's nothing to pip install. The SQLite built into Python must be 3.24 or newer, which any Python from the last several years has (python3 -c "import sqlite3; print(sqlite3.sqlite_version)").
  • A machine that players can reach on:
Port Protocol What uses it
10200 TCP the lobby (lobbyd.py); consoles connect straight to it
10202 TCP EA Messenger (buddy lists, presence, messages), inside lobbyd.py
8080 TCP the web site (webui.py), or whatever you put in front of it

The game itself (a match between two players) is peer-to-peer over UDP 3658 directly between the players. It never goes through this server; see Known limits.

Quick start

git clone https://github.com/jeddyhhh/TW04OnlineServer.git
cd TW04OnlineServer

python3 webui.py --port 8080      # the web site
python3 lobbyd.py                 # the lobby, on port 10200

Run each in its own terminal. Then open http://localhost:8080/, create an account, and follow the Connect from PCSX2 steps on that page to point a game at the server.

The site marks its login cookie Secure, which browsers accept over plain HTTP only at localhost. To try it from another machine before you've set up HTTPS, add --no-secure-cookie, or you'll be signed straight back out.

Both programs open the same database, data/tw04.db, next to the scripts. It's created the first time either one starts. Each prints the full path it opened. If those two paths ever differ, accounts made on the web site won't exist at the game's login screen.

How it fits together

 PCSX2 + the game ──TCP 10200──▶  lobbyd.py  ──┐
          │         ──TCP 10202──▶  (Messenger) │
          │                                    ├──▶  data/tw04.db  (SQLite)
          │                                    │
          └── UDP 3658 to the other player     │
                                               │
 web browser ─────────HTTP──────▶  webui.py  ──┘
  • lobbyd.py talks to the consoles. It speaks EA's lobby protocol, introduces two players to each other when a challenge is accepted, and records results. It also writes the live picture (who's online, matches in play) into the database for the web site.
  • webui.py is the web site. It only ever reads what the lobby has written, apart from account sign-up and account management.
  • The game patch. The lobby's address is an IP written into the game disc, so every player's game has to be patched to reach your server. The web site builds that patch (a PCSX2 .pnach file) for your server on demand, so there's nothing to prepare by hand.

The supporting modules, for anyone reading the code:

File What it is
twdb.py the database: accounts, personas, results, tournaments, buddy lists, reports
twrecords.py player pages, stat leaders, records, course pages, and the news digest
twtourney.py the online tournament calendar and its wire format
twstats.py the packed statistics record, and the course list
tagfield.py, eacrypt.py EA's message encoding and the login password cipher
make_pnach.py builds the game patch
twlog.py the size-capped log both programs write
tw04.sh runs and supervises both on Linux (see below)
docs/PROTOCOL.md the protocol research notes: how everything above was worked out
analysis/ the ELF analysis helpers those notes were worked out with (they need capstone and pyelftools; see analysis/README.md)

Running it for real

As a service (Linux)

tw04.sh starts both programs and restarts either one a few seconds after it exits, so a crash doesn't need anyone to notice:

chmod +x tw04.sh
./tw04.sh start            # start both; safe to run again, won't double-start
./tw04.sh start webui      # just one of them
./tw04.sh status
./tw04.sh restart [lobbyd|webui]
./tw04.sh stop [lobbyd|webui]
./tw04.sh log lobbyd       # follow a log

Settings are at the top of the script, or can be given in the environment:

Setting Default Meaning
LOBBY_PORT 10200 the lobby's TCP port
BUDDY_PORT 10202 EA Messenger's TCP port; empty to run without it
BUDDY_ADDR (empty) host:port to give consoles for Messenger, if this machine's own address isn't the public one (behind NAT)
WEB_HOST 127.0.0.1 where the web site listens; keep it local behind a reverse proxy
WEB_PORT 8080 the web site's port
WEB_BASE /TW04Online the path the site is served under
LOG_MAX_MB / LOG_KEEP 10 / 3 each log rolls over at this size, keeping this many old copies
PYTHON python3 the interpreter to use

To start everything at boot, add this to crontab -e, as the user that owns the files rather than root:

@reboot /path/to/repository/tw04.sh start

Optionally, add a watchdog that restarts anything that isn't running:

* * * * * /path/to/repository/tw04.sh start >/dev/null 2>&1

Behind a web server, with HTTPS

The web site is plain HTTP, so put it behind a reverse proxy that handles TLS. With Apache, serving it at https://example.com/TW04Online:

# a2enmod proxy proxy_http
ProxyPreserveHost On
ProxyPass        /TW04Online  http://127.0.0.1:8080/TW04Online
ProxyPassReverse /TW04Online  http://127.0.0.1:8080/TW04Online

and run the site on localhost with the matching path, which tw04.sh does by default:

python3 webui.py --host 127.0.0.1 --port 8080 --base-path /TW04Online --trust-proxy
  • --trust-proxy matters here. Without it, every visitor appears to come from 127.0.0.1, so the site's lockout after repeated wrong passwords becomes one shared counter for everybody. Only use it with a proxy actually in front, because anyone who can reach the port directly can fake the header it reads.
  • The session cookie is marked Secure. --no-secure-cookie exists only for testing over plain HTTP.
  • If the downloaded patch points players at the wrong address, start the site with --advertise <your public hostname or IPv4>. It works out the address automatically otherwise. It's also needed if the lobby runs on a different machine from the site.

Only the web site goes behind the proxy. Consoles connect straight to TCP 10200 and 10202, so open both on your firewall.

Before it faces the internet

  • Put HTTPS in front of the web site (above).
  • Don't run lobbyd.py with --open. It creates an account for anyone who types a new name at the console, which is handy on a LAN and wrong on the internet.
  • Open TCP 10200 and 10202 on the firewall.

How players connect

The web site's front page walks players through all of this and gives them the patch file. In short, in PCSX2:

  1. Install the patch. Download the .pnach file from the site and put it in PCSX2's cheats folder, keeping its name exactly: PCSX2 matches it to the disc by name. Then right-click the game → Properties → Patches and tick Enable Cheats.
  2. Turn on networking. Settings → Network & HDD:
    • tick Enable Network (DEV9);
    • set the Ethernet device type to Sockets;
    • set Ethernet Device to the network adapter you use on Windows, and to Auto on Linux and the Steam Deck; naming the adapter there gets you into the lobby, but matches never start;
    • set DNS1 to a real resolver, such as your router or 1.1.1.1. Leaving it on Auto makes the game's DNAS screen slow, especially on Linux.
  3. Save a network configuration (first time only). The game needs a PlayStation 2 network configuration on the memory card. If there isn't one, the game says so when you choose ONLINE and offers to create one with the network configurator on the game disc. Don't change anything: just save the default settings to the memory card.
  4. Sign in. Create an account on the web site, then sign in on the console's ONLINE menu with the same account name and password. Choose a persona on the SELECT ACCOUNT screen.

The patch never modifies the disc image. Deleting the .pnach file undoes it.

On a real PS2 (untested)

The front page also offers the same patch as cheat codes for a real console's cheat engine: SLUS_207.57.cht for Open PS2 Loader, and a Cheat Device list. make_pnach.py <ip> --real-ps2 writes both too. They need a PS2 that can run homebrew, and a game-specific master code hooks the game (it's built in, taken from the game's CodeBreaker master code). Nobody has tried them on a real console yet. If you do, whether they work or not, please open an issue with what happened. It works with Tiger Woods PGA Tour 2004, USA (NTSC-U), serial SLUS-20757, CRC 64F9781E, and no other release.

Looking after it

The admin page

Most day-to-day jobs are on a page that nothing links to:

<your site>/admin/<key>

Find an account by its name or any of its personas, then:

  • Reset its password. Type one, or leave the box empty and the page makes up an 8-character one that's easy to type on the console keyboard. The new password is shown once, on the page. Players can't recover a password by themselves, so this is how a forgotten one gets fixed.
  • Ban the account, or lift a ban. A ban blocks signing in, on the console and on the web site, for all of the account's personas. Anyone already in the lobby stays until they leave.
  • Rename a persona. Its results, tournament rounds, buddies and reports move with it. The page refuses while that persona is online.
  • Delete the account. It removes the account, its personas, and every round and match they played, for good. Type the account name to confirm. Abuse reports about them are kept as the moderation record.

The same page edits the in-game news (see below).

The key is created the first time the web site starts, kept in data/admin.key, and printed at every start. Anyone with the address can do all of the above, so keep it private. To change it, delete the file and restart the site. webui.py --admin-key off turns the page off.

Accounts from the shell

python3 twdb.py --list
python3 twdb.py --create-account NAME --password PASS [--persona PERSONA]
python3 twdb.py --add-persona ACCOUNT PERSONA
python3 twdb.py --set-password ACCOUNT PASS
python3 twdb.py --disable ACCOUNT        # a ban; --enable undoes it

Starting afresh. To wipe the server's play but keep everyone's account and personas, stop the server and run:

python3 twdb.py --reset-stats

It keeps accounts, personas, their uploaded golfers, buddy lists and the abuse reports, and wipes matches, tournament rounds, the live board's history. The tournament calendar goes too, and a new one is drawn when the lobby next starts. It refuses to run while the lobby is up, saves a full copy of the database first (before-reset-DATE.db beside it), and asks you to type RESET (--yes skips that).

Just a new calendar. python3 twdb.py --reset-calendar (server stopped) clears the tournament events from tomorrow on and picks a random calendar generation, so the lobby deals a new schedule when it starts. Today, past events and any day somebody has already played stay, so prize money and leaderboards are unchanged, and nothing else is touched. Same safeguards: a copy first (before-calendar-DATE.db) and RESET to confirm.

Players normally manage their own accounts and personas on the web site. An account can hold up to four personas.

News

The game's news screen shows the text of data/news.txt, if it exists. Edit it on the admin page, or by hand. It's re-read on every request, so a change shows up for the next player who opens the screen. The PS2 can only show plain ASCII, and the admin page won't save anything else. Below it, the server adds a digest it writes itself: today's event and leader, yesterday's winner, the week's new records, and the busiest player. --no-auto-news turns the digest off.

Backups

The lobby copies the database once a day into data/backups/, as tw04-YYYY-MM-DD.db, and keeps the newest 7. It uses SQLite's own backup, so the copy is consistent even while players are on. --backup-keep N changes how many are kept (0 turns backups off), and --backup-dir puts them somewhere else, such as another disk.

To restore one, stop both programs, copy the backup over data/tw04.db, delete data/tw04.db-wal and data/tw04.db-shm if they exist, and start them again.

Abuse reports

When a player uses REPORT ABUSE in the game, the report is stored with the chat the server relayed in the hour before. Reports are listed on a page that nothing links to:

<your site>/reports/<key>

The key is created the first time the web site starts, kept in data/reports.key, and printed at every start. Anyone with the address can read the reports, so keep it private. To change it, delete the file and restart the site. webui.py --reports-key off turns the page off. To ban someone, use the admin page or twdb.py --disable.

Options

The most useful ones; --help on either program lists them all.

lobbyd.py

Option Default Meaning
--port N 10200 the lobby's TCP port; it must match the port in the players' patch
--host ADDR 0.0.0.0 where to listen; :: takes IPv6 and IPv4 together
--buddy-port N 10202 EA Messenger's port; 0 turns it off
--buddy-addr HOST:PORT the Messenger address to give consoles, when this machine's own address isn't the public one
--db PATH data/tw04.db the database; must be the same file the web site uses
--news FILE data/news.txt the news screen text
--no-auto-news the news file only, without the generated digest
--backup-keep N 7 daily database copies to keep; 0 turns backups off
--backup-dir DIR data/backups where the daily copies go
--open create an account on first login instead of refusing it (LAN only)
--logfile FILE logs/lobbyd.log the log; '' for none
--quiet log to the file only, not the terminal too
--log-max-mb / --log-keep 10 / 3 log rollover size and copies kept
-v, --verbose hex dump of every message, for debugging

webui.py

Option Default Meaning
--port N 8080 the site's port
--host ADDR 0.0.0.0 where to listen; use 127.0.0.1 behind a proxy
--base-path PATH serve under a path, e.g. /TW04Online
--trust-proxy read the visitor's address from X-Forwarded-For (proxy only)
--advertise HOST the address the downloadable patch points players at
--lobby-port N 10200 the lobby port put in the patch, if the lobby hasn't published one
--no-secure-cookie for testing over plain HTTP only
--reports-key KEY (from data/reports.key) the abuse-reports page's secret; off turns the page off
--admin-key KEY (from data/admin.key) the admin page's secret; off turns the page off
--news FILE data/news.txt the news file the admin page edits; must be the lobby's --news
--db PATH data/tw04.db the database; must be the same file the lobby uses
--logfile FILE logs/webui.log the request log; '' for none

Files it creates

Everything the servers write goes in two folders next to the scripts. Both are in .gitignore.

Path What it is
data/tw04.db the database: every account and password hash, results, tournaments, buddy lists and reports
data/backups/ a copy of the database for each of the last 7 days (see Backups)
data/reports.key the secret in the abuse-reports page's address
data/admin.key the secret in the admin page's address
data/news.txt your news text, if you create it
logs/lobbyd.log, logs/webui.log the logs, each capped at LOG_MAX_MB with LOG_KEEP old copies

Passwords are stored as salted PBKDF2 hashes, and they're kept out of the logs unless you start the lobby with --log-passwords, a debugging switch that says so loudly when it's on.

Tests

Each test starts a real server on a spare port with a throwaway database, and drives it the way the game does:

python3 tests/lobbyd_selftest.py      # message format and the password cipher
python3 tests/lobbyd_authtest.py      # only real credentials get in
python3 tests/lobbyd_roomtest.py      # rooms, challenges, rematches
python3 tests/lobbyd_addresstest.py   # which address each console is given for peer to peer
python3 tests/twdb_resettest.py       # --reset-stats keeps the accounts and wipes the rest
python3 tests/lobbyd_livetest.py      # the live picture the web site reads
python3 tests/lobbyd_buddytest.py     # EA Messenger
python3 tests/lobbyd_reporttest.py    # abuse reports and their private page
python3 tests/twdb_statstest.py       # scoring real captured rounds
python3 tests/twdb_tourneytest.py     # ties, open days, and every match counted
python3 tests/webui_pagestest.py      # player, stats, records and course pages; the news
python3 tests/webui_featurestest.py   # event, h2h, compare, Hall of Fame and admin pages; handicaps

Most modules also test themselves: python3 twrecords.py, twlog.py, twtourney.py, twstats.py, tagfield.py and eacrypt.py.

Troubleshooting

  • Nobody can sign in, but the web site works. The two programs are using different databases. Compare the path each one prints at startup.
  • The console says the server can't be reached. Check the lobby is running and TCP 10200 is open. Also check the port in the players' patch matches --port: a port mismatch looks exactly like a server that's down.
  • Players get into the lobby, but matches never start. The two players have to reach each other on UDP 3658, as the lobby only introduces them. Check each player can receive on that port (a port forward, behind a home router). On Linux or a Steam Deck, check the Ethernet Device is set to Auto. Wi-Fi networks that isolate devices from each other also break this.
  • The DNAS screen takes 30 seconds or more. Set DNS1 in PCSX2 to a real resolver (see How players connect).
  • What happened to that connection? logs/lobbyd.log records every message both ways, with passwords redacted (unless the lobby was started with --log-passwords). Add -v for a hex dump of each message too.

Known limits

  • Matches are peer-to-peer. The two consoles play each other directly over UDP 3658, and there's no relay. Each player needs that port reachable (a port forward behind a home router). Two players behind the same router can play each other through an internet-hosted lobby only if their router supports "hairpin" NAT (NAT loopback); many do.
  • IPv4 only, for players. The game can only express IPv4 addresses. The lobby can listen on IPv6 (--host ::), but a player connecting over IPv6 can't be introduced to an opponent.
  • Two emulators on one PC can't play each other, because both need UDP 3658.
  • DNAS is passed, not removed. The patch makes the game's dead Sony DNAS check succeed, but the game still tries to reach Sony first, hence the DNS1 advice above.
  • Only the one disc. The patch is a list of addresses in one build of the game (SLUS-20757). Other regions and Tiger Woods PGA Tour 2005 aren't supported.

License

The server code is released under the MIT License.

Tiger Woods PGA Tour 2004 is a trademark of Electronic Arts; PlayStation and DNAS are trademarks of Sony Interactive Entertainment. This project contains no code, data or assets from the game, and you need your own legally obtained copy to play.

About

Replacement for the EA Sports Tiger Woods PGA Tour 2004 online server

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages