The game's own Windows client runs inside one PS5 app, on Wine, with its DirectX 12 translated to the console's GPU.
No streaming. No Linux. No PC in the loop once it's copied.
Install · Play · Launch settings · Troubleshooting · How it works · Build
![]() |
![]() |
WoW Forever, captured from a PS5 over Remote Play. The green text in the corner was a debug label used for the recording; it is not part of the release.
Important
A hobby project, not affiliated with or endorsed by Blizzard Entertainment or Sony. The game is not included: you copy your own installation and sign in with your own account on the console. Use it at your own risk.
One app, two clients. Copy either or both; the app starts the one you pick.
| WoW Forever | WoW Classic Era | |
|---|---|---|
| Folder | _classic_beta_ |
_classic_era_ |
| Program | WowB.exe |
WowClassic.exe |
| Client build tested | 1.60.1.70205, 70235, 70374 | 1.15.9.70003 |
| Status | ✅ Sign-in, character select, playing in the world, sound, controller | 🧪 New: starts on the console and plays its intro; sign-in and the world are being tested |
| Graphics | DirectX 12, as on PC | DirectX 12, forced (-d3d12 and GxApi D3D12, set up by the installer) |
Both share the Data folder and .build.info, exactly as Battle.net lays them out on a PC.
- Classic Era support. The app knows both clients and starts the first one it finds (Forever, then Classic Era), or the one named by
WOWPS5_GAMEin the launch settings. install.py --flavorcopies only the game you want (_classic_beta_,_classic_era_, or both).install.py --startswitches the game the app starts, without copying anything again.- No more black screen on a first start. A client with no
WTF/Config.wtfasks for your region and language in a window the console cannot show. The installer now writes a shortConfig.wtf(region, language,GxApi D3D12) for each game that has none, taking your PC's own values when it has them. - Faster runtime (from the
perf/runtimework): thread priorities on by default (Trade Quarter: about 43 → 57–62 frames a second), no kernel call for memory protection that is already right, and a cache forRtlLookupFunctionEntry. Details in docs/technical-notes.md.
About 15 minutes of your time, plus the copy (50 to 65 GiB, depending on what you have installed).
- A jailbroken PS5 with a homebrew enabler running (tested: etaHEN with kstuff), and:
- its FTP server on (port 2121 is the default of the common payloads),
- ShadowMount (or another way to register an app folder from
/data/homebrew), - about 70 GB free on the internal drive (more with both games).
- A PC on the same network with Python 3 (Linux, macOS or Windows).
- Your own World of Warcraft installation, kept up to date by Battle.net, with
_classic_beta_(WoW Forever) and/or_classic_era_(WoW Classic Era) in it. - The console's address on your network (PS5: Settings → Network → View Connection Status).
-
Download the bundle from the latest release and unpack it.
-
Turn the console on and run your jailbreak, so that FTP and ShadowMount are up.
-
Run the installer from the unpacked folder. It copies the app, Wine and your game, and can be re-run if it is interrupted:
# Everything you have installed; the app starts Forever if it is there python3 install.py --host 192.168.1.50 --wow "/path/to/World of Warcraft" # Only Classic Era, and start it python3 install.py --host 192.168.1.50 --wow "/path/to/World of Warcraft" --flavor _classic_era_ --start _classic_era_
--hostis the console's address;--wowis your game folder, the one that holdsDataand the game folders. Nothing needs editing. Your installation on the PC is only read. -
On the console, the app appears on the home screen within a minute (its tile is still called WoW Forever). Start it.
-
Sign in. Press L3 + R3 (click both sticks) to type with the on-screen keyboard.
The first start, and the first visit to each place, are slower than later ones: the graphics driver compiles the game's shaders once and keeps them.
python3 install.py --host 192.168.1.50 --skip-app --start _classic_era_ # Classic Era
python3 install.py --host 192.168.1.50 --skip-app --start _classic_beta_ # WoW Forever
python3 install.py --host 192.168.1.50 --skip-app --start auto # the first installedThis only rewrites the WOWPS5_GAME line of /data/wowps5/launch.txt; you can also edit that file over FTP yourself.
Installing by hand instead (FileZilla, WinSCP or any FTP program on port 2121)
| Copy this | To this place on the PS5 |
|---|---|
the folder title/PPSA99220 |
/data/homebrew/PPSA99220 |
the folder runtime/wowps5/wine |
/data/wowps5/wine |
the file runtime/wowps5/ca-certificates.crt |
/data/wowps5/ca-certificates.crt |
the three .reg files in runtime/wowps5/prefix |
/data/wowps5/prefix/ |
everything inside runtime/wowps5/prefix/drive_c |
/data/wowps5/prefix/dosdevices/c:/ (the last folder is named c:, with the colon) |
your game's Data folder, .build.info, and _classic_beta_ and/or _classic_era_ |
/data/wowps5/prefix/dosdevices/c:/Games/World of Warcraft/ |
From the game folders, leave out WTF, Interface, Cache, Logs and Errors: PC settings do not suit the console, and the game makes these again. Then give each game a WTF/Config.wtf on the console with at least:
SET portal "US"
SET textLocale "enUS"
SET audioLocale "enUS"
SET GxApi "D3D12"
(portal "test" for WoW Forever; use your own region and language.) For Classic Era, also add the two lines from Launch settings.
The console cannot update the game itself. Let Battle.net update it on the PC, close the game on the console, then run the installer again with --skip-app:
python3 install.py --host 192.168.1.50 --wow "/path/to/World of Warcraft" --skip-appIt sends only the files the update changed (a few GiB, not the whole game). If your first copy was made by hand or with release 0.1.0, add --since "YYYY-MM-DD HH:MM" with the time of that copy, once: an update changes files without changing their size, and the installer has to know which are newer.
A new version of this port is installed the same way as the first: unpack the new release and run the installer. Your game, settings and shaders on the console stay.
| Input | What it does |
|---|---|
| Controller | The game's own controller support and console UI |
| Touchpad | Moves the mouse pointer; pressing it is a left click |
| L3 + R3 (click both sticks together) | Opens the PS5 on-screen keyboard. What you type goes into the text box that has focus (login, search, chat), when you confirm |
| L3 + R3 again, before pressing anything else | Opens the keyboard with what you just typed, to fix a mistake: confirming replaces the old text. Confirming an empty keyboard just deletes it |
| USB keyboard and mouse | Work as on a PC (and Esc skips Classic Era's intro cinematic) |
By default a PS5 app gets exactly as many frames as the TV shows. This port adds a second way of presenting to the graphics driver, where the game draws as fast as it can and the screen shows the newest finished frame at each refresh, with no tearing.
To use it, in the game: System → Graphics → turn Vertical Sync off, and set Max Foreground FPS to what you like (or off). The game's frame counter then goes above 60 and input feels more direct; the TV still shows its own refresh rate.
- Leaving the game: use the game's own Exit Game. It takes about ten seconds to close.
- Settings and add-on data are saved on the console when you leave the game normally.
- A "system software" message from the PS5 while the game runs can be dismissed.
- No voice chat.
/data/wowps5/launch.txt on the console, one setting a line, read at every start. Delete the file to return to the defaults.
| Line | What it does |
|---|---|
NAME=value |
An environment setting for Wine and the game |
+argument |
An argument for the game's program (for example +-d3d12) |
# ... |
A comment |
| Setting | Default | |
|---|---|---|
WOWPS5_GAME=<path of the .exe on the console> |
first installed | Which game starts. Classic Era: /data/wowps5/prefix/dosdevices/c:/Games/World of Warcraft/_classic_era_/WowClassic.exe |
+-d3d12 |
— | Keeps the client on DirectX 12, the only graphics the port translates. Recommended for Classic Era |
PS5_VIDEOOUT_MAILBOX=0 |
on | Removes the uncapped present mode |
VKD3D_CONFIG= |
single_queue |
Empty turns single-queue mode off |
WOWPS5_PRIORITIES=0 |
on | Turns the console thread priorities off |
WOWPS5_OSK_CHAT=OPTIONS+L3 |
off | A second keyboard shortcut for chat (Enter, text, Enter) |
WINEDEBUG=+loaddll,warn+seh |
— | Wine's debug channels, to the console's log (see below) |
A Classic Era setup, as the installer writes it:
# install.py --start: the game the app starts
WOWPS5_GAME=/data/wowps5/prefix/dosdevices/c:/Games/World of Warcraft/_classic_era_/WowClassic.exe
+-d3d12| What you see | What to do |
|---|---|
| The installer says there is no FTP server | Check the console's address, that the jailbreak ran, and that its FTP server is on port 2121 (--port to change it) |
| No app on the home screen | ShadowMount must be loaded; it looks in /data/homebrew. Removing the tile and letting it be added again also refreshes its name and pictures |
| A black screen that never ends (Classic Era especially) | The client is waiting in its region/language window, which the console cannot show. Close the app and give the game a WTF/Config.wtf with portal, textLocale and audioLocale: run the installer again with --wow (it writes one if there is none), or copy the four lines from Installing by hand |
| A black screen for a while on the very first start | Wait: the first start compiles shaders. Later starts are much faster |
| Classic Era's intro cinematic cannot be skipped | The controller does not skip it. Press Esc on a USB keyboard, or let it finish; it plays only the first time |
| The game closes on start | Make sure the copy finished (run the installer again; it only sends what is missing) and that the client matches the build on your PC |
| Sign-in fails with BLZ51914400 | The client signs in directly, without the Battle.net app (which cannot run on the console), and Blizzard sometimes refuses that. Starting the client directly on a PC then shows the same error: it is on Blizzard's side, not something the port changes |
| The picture freezes in the world (sound may carry on) | Close the game from the PS button and start it again. A crash shows as a frozen picture because the game's crash window is not visible on the console |
| Lua warnings about SavedVariables, or settings that do not stick | Update to the latest release: this was a limit of 250 open files that the port now stays under |
The app writes everything Wine and the game print to the console's kernel log. To read it on your PC, load a klog server payload (for example klogsrv, sent to your ELF loader on port 9021) and follow it on port 3232, or let tools/wine-ps5/klog-record.py keep it in files. Add WINEDEBUG= channels to launch.txt for more: +win,+dialog shows windows the console cannot draw (that is how the hidden region window was found).
A PS5 app cannot start a second program, has 16 KiB memory pages, may not read its own code, and gets about 250 open files. So the whole of Windows has to live inside one app:
flowchart LR
subgraph APP["PS5 app PPSA99220 · one process"]
GAME["WowB.exe<br/>WowClassic.exe<br/><i>your own copy</i>"]
PE["Wine 11.19<br/>Windows side"]
UNIX["Wine Unix side<br/>wineserver on a thread<br/>pad · keyboard · sound"]
MEM["Memory layer<br/>4 KiB pages<br/>executable memory"]
D3D["vkd3d-proton<br/>D3D12 → Vulkan"]
RADV["RADV<br/>Mesa, PS5 port"]
GAME --> PE --> UNIX --> MEM
GAME -- "D3D12" --> D3D -- "Vulkan" --> RADV
end
MEM --> KERNEL[("PS5 kernel")]
RADV --> GPU[("PS5 GPU")]
- One process. Wine's server runs on a thread and its Unix side is linked into the app's executable.
- Memory. A memory layer gives Windows programs the 4 KiB pages and executable memory they expect, on top of the console's direct memory.
- Graphics. vkd3d-proton turns the game's DirectX 12 into Vulkan, and RADV (Mesa's driver, in Mihawk's PS5 port) drives the console's GPU, with an added present mode for frame rates above the TV's.
- Choosing a game. The app looks for
_classic_beta_/WowB.exe, then_classic_era_/WowClassic.exe, unlessWOWPS5_GAMEnames one (app/runtime/wine_title.c).
Every change to Wine is one patch against a pinned revision, and each exists for something measured on the console: see docs/technical-notes.md.
| Here | What |
|---|---|
app/ |
The PS5 app: the platform side (app/ps5), and what it does around Wine (app/runtime: start-up and game choice, files, sound, controller, on-screen keyboard) |
runtime/wine-ps5/ |
Every change to Wine as one patch, and the memory layer |
runtime/radv-ps5/ |
The uncapped present mode, as a patch to the graphics driver's display code |
tools/ |
Build, install, run and test scripts; the Windows test programs |
docs/ |
Technical notes: what was measured on the console and why each change exists |
Not here: the game, anything of an account, and the third-party source (vendor/, fetched at the revisions pinned in dependencies.json).
The release bundle is the supported way to install. A fresh build has been reproduced (Ubuntu 24.04 under WSL, clang 18, Meson 1.9, LLVM 19 for Mesa's host tool) with tools/build-all.sh, which runs the build steps below in order and fixes what a fresh tree needed; see its header for MESON and LLVM_CONFIG. The original steps:
python3 tools/bootstrap.py # pinned sources and host packages into vendor/ and .deps/
cp .env.example .env # then set WOW_INSTALL; console settings go in vendor/PS5_Vulkan/.env
bash tools/wine-ps5/build-radv.sh # the graphics driver with this project's patch
bash tools/wine-ps5/build-console.sh # Wine's Unix side, the app, and its deployment to the console
python3 tools/wine-ps5/stage-console.py --all --system32 # Wine's Windows side to the console
python3 tools/wine-ps5/stage-game.py # your game to the console (WOW_FLAVOR=_classic_era_ for only that one)The host needs Linux with LLVM 23, mingw-w64, meson, ninja and cmake. runtime/wine-ps5/README.md describes the Wine patch; tools/make-bundle.py makes the release bundle from a console where everything works.
- BeLikeBrett/WoWPS5 — the original port this fork builds on: Wine in one PS5 app, the memory layer, the uncapped mode, the installer. All the hard parts.
- Wine (LGPL-2.1-or-later); the patch here is under the same licence.
- vkd3d-proton (LGPL-2.1).
- Mesa RADV (MIT) in Mihawk's PS5 port, and his PS5_Vulkan.
- The PS5 payload SDK and klogsrv.
- The licence texts of everything the app contains are in the bundle under
title/PPSA99220/licenses/. This project's own code is MIT.
World of Warcraft and Blizzard Entertainment are trademarks of Blizzard Entertainment, Inc. PlayStation and PS5 are trademarks of Sony Interactive Entertainment Inc. The pictures used for the app's tile and background are fan-made from publicly available artwork.


