mirror of
https://github.com/cranebeaker/coop-anything.git
synced 2026-11-07 10:26:02 -10:00
Merge #262
BLACK SOULS II: write-up, directory entry, RPG Maker playbook notes
This commit is contained in:
5 files changed
+184
-2
No files matched your search
+2
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
<!-- Generated by tools/build_directory.py from data/directory.yaml. Edit the YAML, not this file. -->
|
||||
|
||||
355 co-op projects and 44 open gaps across 251 games. Status counts: playable 76, official 13, partial 59, alpha 90, prototype 78, abandoned 39.
|
||||
356 co-op projects and 44 open gaps across 252 games. Status counts: playable 76, official 13, partial 59, alpha 91, prototype 78, abandoned 39.
|
||||
|
||||
**QA** is the column that matters: it says how the claims were checked. See [data/SCHEMA.md](data/SCHEMA.md). Found something wrong or new? Edit `data/directory.yaml` and open a pull request, or let your agent do it (see [AGENTS.md](AGENTS.md)).
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
| BattleTech | 2018 | Unity | [BattleTech Co-op (Co-op Anything Discord)](https://discord.com/channels/1561411702082510879/1562162768403234959) | mod | prototype | ? | unknown | unknown | untested | 2026-10 |
|
||||
| BioShock Remastered | 2016 | Unreal Engine 2.5/3 | [BioShock Together](https://www.nexusmods.com/bioshock/mods/178) | mod | alpha | players visible: yes, story: yes, enemies: yes, world state: partial, items: partial | 2 | direct-ip | untested | 2026-10 |
|
||||
| Black Myth: Wukong | 2024 | Unreal Engine 5 | [WukongMP](https://www.nexusmods.com/blackmythwukong/mods/1205) | mod | alpha | story: unknown, enemies: unknown, world state: unknown, items: unknown | up to 10 co-op | hosted-relay | untested | 2026-08 |
|
||||
| BLACK SOULS II | 2018 | RPG Maker VX Ace | BS2 Coop | mod | alpha | players visible: yes, story: yes, enemies: partial, world state: yes, items: per-player | 2-4 | vpn-needed | automated tests | 2026-10 |
|
||||
| Call of Duty: Black Ops III | 2015 | IW engine (Call of Duty, Treyarch fork) | [BO3 4-Player Local Splitscreen on PC](https://www.nexusmods.com/callofdutyblackops3/mods/53) | mod | partial | players visible: yes, story: unknown, enemies: yes, world state: yes, items: per-player | 2-4 | ? | player reports | 2026-10 |
|
||||
| Call of Duty: Modern Warfare 2 | 2009 | IW 4.0 (Call of Duty) | [mw2_coop](https://github.com/stxlvn/mw2_coop) | mod | partial | players visible: yes, story: partial, enemies: yes, world state: yes, items: per-player | 2 | direct-ip | automated tests | 2026-10 |
|
||||
| Call of Duty: Modern Warfare 2 | 2009 | IW 4.0 (Call of Duty) | [COD-MOD campaign co-op pack (iw4sp_codmod)](https://steamcommunity.com/app/10180/discussions/0/3087772148451672664) | mod | abandoned | players visible: yes, story: partial, enemies: unknown, world state: unknown, items: unknown | 2 | direct-ip | player reports | 2021-10 |
|
||||
|
||||
@@ -10728,3 +10728,34 @@
|
||||
- https://github.com/carlgoodtoseeyu/superflightmp/releases/tag/v0.2.0
|
||||
checked: '2026-10-30'
|
||||
made_with_coop_anything: true
|
||||
- game: BLACK SOULS II
|
||||
year: 2018
|
||||
engine: RPG Maker VX Ace
|
||||
project: BS2 Coop
|
||||
url: ''
|
||||
other_urls: []
|
||||
kind: mod
|
||||
status: alpha
|
||||
coop_scope:
|
||||
players_visible: 'yes'
|
||||
story: 'yes'
|
||||
enemies: partial
|
||||
world_state: 'yes'
|
||||
items: per-player
|
||||
players: "2-4"
|
||||
networking: vpn-needed
|
||||
open_source: 'no'
|
||||
last_activity: 2026-10
|
||||
qa: automated
|
||||
notes: Private build; the code is not public. Each player runs their own copy (Steam 1.0, or the 1.4 patch plus
|
||||
the Spanish fan translation) through a loader that adds the mod to the game's RGSS3 scripts. Shared switches
|
||||
and variables chosen by a census of the event data, joinable battles with shared enemy damage and deaths,
|
||||
players drawn on the map. Direct TCP over a VPN or a forwarded port. The co-op claims come from real-client
|
||||
bot suites (two clients on one PC over localhost); scenario list and PASS counts per build are in
|
||||
games/black-souls-ii/README.md. The sources back the game, year, engine and the two builds tested.
|
||||
sources:
|
||||
- https://en.wikipedia.org/wiki/Black_Souls_(video_game_series)
|
||||
- https://store.steampowered.com/app/3855540/
|
||||
- https://steamcommunity.com/sharedfiles/filedetails/?id=3614515422
|
||||
checked: '2026-10-30'
|
||||
made_with_coop_anything: true
|
||||
@@ -0,0 +1,147 @@
|
||||
# BLACK SOULS II (2018)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Engine | RPG Maker VX Ace (RGSS3, Ruby 1.9.2) |
|
||||
| Versions tested | Steam 1.0 ([app 3855540](https://store.steampowered.com/app/3855540/), build 25767755; censored release, 2025, 18 maps); Steam + official full-version patch 1.4 + the Spanish fan translation (411 maps): translated by the Hispachan community, adapted to the Steam version by Yotichi ([Steam guide](https://steamcommunity.com/sharedfiles/filedetails/?id=3614515422)) |
|
||||
| Projects | BS2 Coop: private build, code not public (directory entry "BS2 Coop") |
|
||||
| QA level | automated |
|
||||
| Last updated | 2026-10-30 |
|
||||
|
||||
## Where things stand
|
||||
|
||||
A private mod lets 2-4 players play together over direct TCP (a gaming VPN or a forwarded port):
|
||||
everyone sees the others walking on the same map, story and world flags are shared, and any player
|
||||
can join another's battle, where enemy damage and deaths are shared. Real-client bot suites pass on
|
||||
both the Steam 1.0 build and the patched, translated build. Coverage is the scripted scenarios listed
|
||||
under QA, run with two clients on one PC over localhost.
|
||||
The code is not public; this write-up records how it works so the next agent can rebuild it.
|
||||
|
||||
## Existing work
|
||||
|
||||
None found for this game (web search for BLACK SOULS II co-op / multiplayer, 2026-10-30; every
|
||||
result was about Dark Souls II). The closest relative is Fear & Hunger Together (RPG Maker MV), but
|
||||
MV runs JavaScript in a browser engine, so nothing carries over directly except ideas (census of
|
||||
switches from game data, separate characters, bots driving real clients).
|
||||
|
||||
## How the game works (for co-op)
|
||||
|
||||
- **Data and scripts.** Everything is in `Game.rgss3a`: an XOR archive (`RGSSAD\0\3`, key
|
||||
`k*9+3`, per-file key `k*7+3` per 4 bytes) holding `Data/*.rvdata2` (Ruby Marshal) and
|
||||
`Data/Scripts.rvdata2` (an array of `[id, name, zlib(code)]`). A 60-line C# reader unpacks it;
|
||||
Windows ships `csc.exe` (.NET Framework), so no SDK is needed.
|
||||
- **The archive wins.** While `Game.rgss3a` exists, RGSS3 loads scripts and `load_data` from the
|
||||
archive only; a `Scripts=` path on disk fails with "Failed to load script". To inject a mod without
|
||||
repacking: extract the archive next to `Game.exe`, rename it, and point `Game.ini` `Scripts=` at a
|
||||
one-section file that evals `Data/Scripts.rvdata2` section by section and the mod's own `.rb` files
|
||||
just before `Main`. That works for any build or translation of the game.
|
||||
- **Unfocused windows freeze.** RGSS3 stops inside `Graphics.update` after `WM_ACTIVATEAPP(FALSE)`
|
||||
(posting that message to a running copy froze it; posting `TRUE` resumed it). A session would stall
|
||||
whenever anyone alt-tabs. Fix from Ruby alone: write a 60-byte x86 window procedure into
|
||||
`VirtualAlloc`'d memory that drops `WM_ACTIVATEAPP(FALSE)` and `WM_ACTIVATE(WA_INACTIVE)` and forwards
|
||||
everything else with `CallWindowProc`, install it with `SetWindowLong(GWL_WNDPROC)`, then post
|
||||
`WM_ACTIVATEAPP(TRUE)` to yourself in case another window took focus before the shim existed. Ignore
|
||||
keyboard input while `GetForegroundWindow` is not the game.
|
||||
- **Networking.** No netcode at all. `Win32API` against `ws2_32` gives non-blocking TCP (`ioctlsocket
|
||||
FIONBIO`, `select` for connect completion) with no extra DLL. Receive buffers must be binary
|
||||
(`force_encoding("ASCII-8BIT")`): a UTF-8 buffer sliced by characters corrupted frames as soon as a
|
||||
Japanese sprite file name was sent.
|
||||
- **Switches and variables.** Names are stripped in the shipped data, so the mod classifies them by
|
||||
how event lists use them: operands that are random, game data or script, transfer and picture
|
||||
coordinates, writes inside battle (troop) events and parallel events, flags set ON and OFF in one
|
||||
event, and anything the game's Ruby scripts reference stay per-player; everything else written by
|
||||
events is world state, plus all self switches. On the full game this takes 0.74 s for 410 maps.
|
||||
Examples from this game: variable 4 is the random roll that picks a symbol enemy's troop; variables
|
||||
21-23 hold the soul-drop map and position and 29-31 the last bonfire.
|
||||
- **Battles.** Symbol encounters on the map run `Battle Processing` with "can lose"; the lose branch
|
||||
calls common event 11 (soul drop, "YOU DIED", respawn at the bonfire). The battle system is a custom
|
||||
ATB: between actions `Scene_Battle#start_party_command_selection` spins a wait loop calling
|
||||
`battlers_frame_update`, which is the safe point to apply another client's damage. If a remote kill
|
||||
ends the fight inside that loop, `BattleManager.judge_win_loss` runs there and a private exception
|
||||
unwinds to `Scene_Battle#update` / `post_start`.
|
||||
- **Translated builds.** The Spanish fan translation adds Fullscreen++ (Zeus81; F5/F6, writes a
|
||||
`[Fullscreen++]` section into `Game.ini`, grabs the first "RGSS Player" window on the PC) and Steam
|
||||
achievements via `steam_api.dll`. An installer must change only the `Scripts=` line of `Game.ini`.
|
||||
- **Test traps.** Starting a new game without the intro leaves placeholder actor 1 with ~27x stats
|
||||
(270k HP, 16k ATK): fights one-shot and enemies "aren't affected". The intro (map 7) swaps in one
|
||||
of the origin characters (actors 2-6). The player also starts transparent until the intro shows them.
|
||||
|
||||
## What we changed
|
||||
|
||||
Built from zero:
|
||||
|
||||
- Loader as above, and an installer that unpacks whatever archive the player has, backs up every file
|
||||
it overwrites, and uninstalls cleanly.
|
||||
- Host/client sessions over TCP (star topology, host relays), handshake with mod version and a CRC of
|
||||
`Data/*.rvdata2` so mismatched game data refuses to connect, safe custom serializer (no `Marshal`
|
||||
over the network).
|
||||
- Shared world: census, switch/variable/self-switch hooks, host snapshot on join, remote changes held
|
||||
until the local map interpreter is idle.
|
||||
- Presence: other players drawn as interpolated characters with name tags; gesture balloons; travel to
|
||||
a partner.
|
||||
- Joinable battles: the joiner starts the same troop with their own party; enemy HP loss and deaths are
|
||||
broadcast; a joiner who loses runs the same death event the starting player's battle event would.
|
||||
- UI in Spanish with keyboard text entry (`ToUnicode`, live modifier state, Ctrl+V paste) and a list of
|
||||
the host's IPv4 addresses (LAN or VPN) to give to the other players.
|
||||
|
||||
Bugs the bot suites found and fixed: the unfocused freeze; the focus race at startup; the UTF-8 receive
|
||||
buffer; `Graphics.frame_count` being rewritten on load (breaking timeouts; the mod keeps its own
|
||||
counter); Shift lost because `GetKeyboardState` lags behind injected or fast key presses.
|
||||
|
||||
## QA
|
||||
|
||||
- A bot runs inside each real game client (only when `COOP_BOT` is set), drives a scenario through the
|
||||
game's own objects and writes PASS/FAIL lines; a PowerShell runner starts 2 clients side by side
|
||||
(visible, so a tester can watch them) and captures the game windows with `PrintWindow`.
|
||||
- Scenarios: handshake and names; unicode round trip; world snapshot on join, switch/variable/self-switch
|
||||
sync both ways, a per-player variable staying local, presence and interpolation; Knight + Deprived
|
||||
(level 5) vs Headless Soldier with the F7 join path; three Star Spirits with kills from both clients;
|
||||
a joiner's defeat running the death event and respawning at the hub; menus and travel; both players
|
||||
choosing Attack through the real actor command window; real keyboard entry (only when the window has
|
||||
focus, otherwise SKIP so keys never go to another app).
|
||||
- Command (in the private mod's folder, against a game copy with the mod installed):
|
||||
`powershell -ExecutionPolicy Bypass -File tools\run-all.ps1 -Game <game copy>`
|
||||
- Suite summary, 2026-10-30 (one run per build, two clients on one PC over localhost;
|
||||
counts are checks passed / checks run, per client):
|
||||
|
||||
| Scenario | What it asserts | Steam 1.0 (host / guest) | 1.4 + Spanish (host / guest) |
|
||||
|---|---|---|---|
|
||||
| net | handshake (mod version, data CRC), names, host IP list | 4/4 / 3/3 | 4/4 / 3/3 |
|
||||
| unicode | Japanese and Spanish text, 70 KB frame round trip | 3/3 / 3/3 | 3/3 / 3/3 |
|
||||
| world | snapshot on join, switch/variable/self-switch sync both ways, per-player variable stays local, presence | 7/7 / 7/7 | 7/7 / 7/7 |
|
||||
| battle | Knight + Deprived (level 5) vs Headless Soldier, join path, shared damage, win, troop dead | 8/8 / 8/8 | 8/8 / 8/8 |
|
||||
| battle3 | three Star Spirits at level 30, kills from both clients | 8/8 / 8/8 | 8/8 / 8/8 |
|
||||
| death | level 5 vs three Star Spirits; the guest's defeat runs the death event and respawns at the hub | 4/4 / 5/5 | 4/4 / 5/5 |
|
||||
| ui | main menu entry, co-op menu, address box, travel to the partner | 3/3 / 3/3 | 3/3 / 3/3 |
|
||||
| manual2 | both players pick Attack through the real battle command window (no auto-battle) | 7/7 / 7/7 | 7/7 / 7/7 |
|
||||
| typing | real keyboard entry with Shift, Backspace, Enter (one client) | 2/2 / - | 2/2 / - |
|
||||
| **Total** | | **46/46 / 44/44** | **46/46 / 44/44** |
|
||||
|
||||
In an earlier run the guest of the death scenario reported no result on Steam 1.0: the host's client
|
||||
closed while the guest was still fighting, which ended the session before the guest's battle finished.
|
||||
The mod now records the battle result even when the session ends mid-fight, and each bot waits for its
|
||||
partner to leave battle before closing; the scenario then passed twice in a row on each build before
|
||||
the run above.
|
||||
|
||||
## How to play
|
||||
|
||||
Private build, not distributed. Its installer finds the Steam folder, unpacks the archive and adds the
|
||||
mod; in game: menu > Cooperativo > host or join, F7 joins a partner's battle, F8 shows a gesture.
|
||||
Internet play needs Radmin VPN, ZeroTier or Tailscale, or TCP port 47800 forwarded to the host. All
|
||||
players must use byte-identical `Game.rgss3a` files (same patch and translation).
|
||||
|
||||
## Known issues
|
||||
|
||||
- Wandering symbol enemies move independently on each screen; only their defeated state is shared.
|
||||
- In shared battles only enemy HP loss and death are synced, not enemy heals or states.
|
||||
- A cutscene might fire for a partner when a shared flag flips. The suites cover flag sync and the death
|
||||
event, not story cutscenes.
|
||||
- Joining adopts the host's world progress; players should use a separate save slot for co-op.
|
||||
- The host leaving ends the session.
|
||||
|
||||
## Next steps
|
||||
|
||||
- A co-op playthrough on the 1.4 build to confirm the census choices and cutscene behaviour.
|
||||
- Host-authoritative positions for symbol enemies per map.
|
||||
- Enemy states and heals in shared battles; partner actions in the battle log.
|
||||
- Run the suites across two PCs and through a VPN (all runs so far used one PC over localhost).
|
||||
@@ -31,6 +31,7 @@ sources; this page is about what carries over.
|
||||
| **Java (JVM)** | The Doors of Trithius (two-player co-op mod, [games/the-doors-of-trithius](../games/the-doors-of-trithius/README.md)) | A `-javaagent` (`java.lang.instrument`) loads the mod into the unmodified game JVM, so no game file changes. The game's public classes and its event bus are the hook points; `javap` from the bundled JDK gives every API fact, and a decompiler (CFR, Vineflower) reads the logic. For a single-player game with no netcode, an impersonation window reuses every rule: the host swaps the game's current-player fields (entity, bag, skills, abilities, field of view) to the guest's, runs the vanilla code for the guest's command, then restores them. Turn-based games also need a clock rule for two players far apart. |
|
||||
| **Santa Monica Studio Kinetica** | God of War 2018 ([games/god-of-war-2018](../games/god-of-war-2018/README.md)); God of War Ragnarök | Despite `.bind` (SteamStub v3.1), `GoW.exe`'s `.text`, `.rdata` and `.data` are unencrypted on disk (flags `0x06`), and jumping `.bind` (`0x14519f310`) to `OriginalEntryPoint` (`0x1403ec680`) lets a second instance run on one PC. Gameplay, quests and Atreus AI run on 1,423 Lua 5.2 scripts (32-bit `float` numbers, unstripped debug info) stored as uncompressed `IFFLuaScriptChunkHeader` chunks inside `.wad` files (`exec/wad/pc_le/`; `IFFHeader.m_Length` is measured from the `ScriptChunk` start). Each character (`heroa00`, `son00`) has its own `lua_State*` with `socket.core` (UDP) and `cmsgpack` built in, but `io`/`os` are omitted and any unhandled Lua error hits `int 3` (`0x14063d629`), so inject config in C++ when compiling overrides in `IFFProcessLuaScriptChunk` (`0x140466de0`) and run the co-op loop inside `son00`'s VM (`crt == game.AI.FindSon()`). |
|
||||
| **Haxe / HashLink** (Heaps) | Dead Cells: local co-op mod on DCCM ([games/dead-cells](../games/dead-cells/README.md)); online mod DeadCellsMultiplayerMod | The game logic is HashLink bytecode (often embedded in the exe as `hlboot`). [DCCM](https://github.com/dead-cells-core-modding/core) loads C# (.NET) mods that hook any HashLink function and generates a proxy class for every game class; crashlink (bundled) disassembles and decompiles. Dump every function to text once and grep it: decompiling one function at a time is slow and loses call names. Trap: argument-less Haxe enum values are singletons compared by pointer in game code, so an enum value created from C# matches nothing; take the value from the game instead. Strings and objects you hand to the game should be fresh, not cached on the C# side. |
|
||||
| **RPG Maker** (VX Ace = RGSS3 Ruby; MV/MZ = JavaScript) | Fear & Hunger (MV: Fear & Hunger Together); BLACK SOULS II (VX Ace: private mod, [games/black-souls-ii](../games/black-souls-ii/README.md)) | All game logic is scripts plus event command lists, so co-op is a script mod; the two generations share no code. Both games stripped switch names, and both projects decide what is world state with a census of how the event lists use each switch and variable. VX Ace: `Game.rgss3a` is a simple XOR archive, and while it exists RGSS3 loads scripts only from it, so extract it and point `Game.ini` `Scripts=` at a one-section loader that evals the game's scripts and then the mod before `Main`. RGSS3 freezes in `Graphics.update` when the window loses focus (`WM_ACTIVATEAPP`); a tiny x86 window-procedure shim written from Ruby via `Win32API` keeps it running. `Win32API` on `ws2_32` gives TCP without extra DLLs. Battle systems are usually custom (ATB scripts): look for the wait loop between actions as the point to apply remote damage. |
|
||||
|
||||
## Rebuilt engines and decompilations
|
||||
|
||||
|
||||
+3
-1
@@ -24,7 +24,7 @@ Drawn from six co-op projects (STALKER, Fallout New Vegas, Dungeon Keeper 1, Oca
|
||||
|
||||
11. **Never touch the owner's install, config or saves.** Copy to a test folder, use instance-local INIs and saves, back up before editing, and never set attributes on the owner's files. One experiment set an owner INI read-only and had to be repaired. Also never use reserved save names and never use an old save that is known to crash. MW2 (2009): a test launch without the Steam app id variables made Steam start the owner's real install, which rewrote its config and left a lock file behind; both were put back from a backup taken before the first run. Back up first, then guard every launch.
|
||||
|
||||
12. **Never take desktop screenshots.** A screen recorder captures whatever else is on the screen, including a browser or private apps. Use the game's own screenshot command, in-engine capture or logs, tell the owner if anything else was captured, and delete the frames. MW2 (2009): with no console or in-game screenshot reachable, copying the screen rectangle of the game window captured another app lying on top of it; even a topmost capture of the game window shows overlays (Discord) drawn into the game. Delete such shots, keep the rest local, and prefer logs. `PrintWindow` with `PW_RENDERFULLCONTENT` on the game's own window (Shadow of War, D3D11) returned the game's frame without the windows on top of it, but it would still include overlays drawn into the game, so the same rules apply.
|
||||
12. **Never take desktop screenshots.** A screen recorder captures whatever else is on the screen, including a browser or private apps. Use the game's own screenshot command, in-engine capture or logs, tell the owner if anything else was captured, and delete the frames. MW2 (2009): with no console or in-game screenshot reachable, copying the screen rectangle of the game window captured another app lying on top of it; even a topmost capture of the game window shows overlays (Discord) drawn into the game. Delete such shots, keep the rest local, and prefer logs. `PrintWindow` with `PW_RENDERFULLCONTENT` on the game's own window (Shadow of War, D3D11) returned the game's frame without the windows on top of it, but it would still include overlays drawn into the game, so the same rules apply. BLACK SOULS II (RGSS3): early bot screenshots copied the window's screen rectangle and caught Discord and another app on top; switching the runner to `PrintWindow` (flag 2) captured the game window cleanly.
|
||||
|
||||
13. **Do not make the owner's PC unusable.** Full-screen launches by test agents were the loudest complaint. Always windowed, minimized without activation, "always active" set so the game does not pause, and a game-slot lease so agents do not launch more than one at a time. Memory pressure from many launches caused the host to kill a suite mid-run; run fewer at a time.
|
||||
|
||||
@@ -195,3 +195,5 @@ none of their checks needed a hook. The first test that did (a portal flow) fail
|
||||
Use `PatchAll(typeof(Plugin).Assembly)` (or `PatchAll()`), log `harmony.GetPatchedMethods()` with each method's name,
|
||||
error when the count is 0, and give every patch at least one test that fails without it. Example: Superflight
|
||||
(`Plugin.cs` in superflight-coop, `games/superflight/NOTES.md`).
|
||||
|
||||
89. **Battle bots need real player stats, and a person watching catches what asserts miss.** BLACK SOULS II: a bot that skipped the intro played the placeholder actor (about 27 times normal HP and attack), so every fight one-shot, enemy hits showed "not affected", and a battle suite "passed" without exercising multi-turn sync. A tester watching the visible test windows spotted it, and also noticed that auto-battling bots never open the command menu. Load the real starting characters at a realistic level (read the class curves and the damage formula from the data), keep bot runs visible when someone wants to watch, and add one scenario that uses the real input menus instead of auto-battle.
|
||||
Reference in new issue
Block a user