Compare commits
274 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d8de65dd0 | |||
| 33ef130098 | |||
| 3370d9c940 | |||
| 2c24cce740 | |||
| 26685ab32c | |||
| 1a5e56c0b2 | |||
| 8e2a58ccaa | |||
| 5192b5f615 | |||
| 9e55cd228c | |||
| 65417749b2 | |||
| 9a0ce6c131 | |||
| d86b41daab | |||
| 0a522e6ac4 | |||
| f2c3d256f3 | |||
| dfb1249efc | |||
| 7feb11b6a7 | |||
| 20b66a7a58 | |||
| 9b6a023d38 | |||
| ea83935dc4 | |||
| 654132b5d2 | |||
| e484ad72e0 | |||
| 64ac199730 | |||
| f4e9b4c60a | |||
| a4039fa9cd | |||
| 6af0527498 | |||
| 1cd0092a41 | |||
| 62d4c65a7d | |||
| 220259b093 | |||
| 0f8d88bb58 | |||
| 4a3716191b | |||
| 5c58c500c1 | |||
| a3d677316a | |||
| 0f45ced3e9 | |||
| ccfdbf2faf | |||
| 9c403e9107 | |||
| 46c3a44b41 | |||
| bc82594a26 | |||
| e3ce4edede | |||
| 61c37d86c1 | |||
| ff12a9aaca | |||
| b5dbf25254 | |||
| 10e5dc4708 | |||
| 1b499ab953 | |||
| 011eedb855 | |||
| 897cca83cf | |||
| fe6e957c92 | |||
| de4cb26c34 | |||
| 9097abf307 | |||
| 14a86610ed | |||
| 9e34ce6f49 | |||
| 7f1846013d | |||
| 05d6d495a5 | |||
| 5991a5a894 | |||
| b0c481ed62 | |||
| 3712574784 | |||
| e738f7f089 | |||
| 8eba207a6b | |||
| 67ed70082c | |||
| 35aa6fba01 | |||
| 267c2ca00b | |||
| 1d8ae0b4d9 | |||
| 30ad83fa67 | |||
| 11880ecc05 | |||
| a62b7dad26 | |||
| f8280e5f97 | |||
| c1bf5ebb43 | |||
| d1866c9bbf | |||
| 42e927caa9 | |||
| cd4c06231f | |||
| 4588a71e5a | |||
| 9476a3fc27 | |||
| b03ccc7e3e | |||
| bc4092fb75 | |||
| 261cefc8b1 | |||
| 34666ee220 | |||
| c918596850 | |||
| 976610705a | |||
| 5758f8dba1 | |||
| 8d83b941d0 | |||
| 7ab98f41da | |||
| 5499d29cc2 | |||
| dd5873266d | |||
| c1e225847e | |||
| 99d1a3272a | |||
| d316d2e260 | |||
| 2f99a3760a | |||
| 6b1a3e615b | |||
| 132395a53a | |||
| 9cbcd09be0 | |||
| 35f48cbd8f | |||
| db6578d57c | |||
| 4a5ea63f4a | |||
| 27608d9c11 | |||
| c84d4b134f | |||
| 722010c0f7 | |||
| 82c651a3ad | |||
| 5834a41cec | |||
| f48b5ac8b2 | |||
| 1b29f20541 | |||
| b492a2fac6 | |||
| c896c11f57 | |||
| 6aef245eab | |||
| 8ae6500fb5 | |||
| 768bdb5b05 | |||
| f997376819 | |||
| 3d7b6837ec | |||
| 533ee04443 | |||
| 6e1691d023 | |||
| be6fd25190 | |||
| ac57fb16a4 | |||
| 687a4515a2 | |||
| 0705b4d155 | |||
| 2818b4ac2f | |||
| ab5536d554 | |||
| fbb053883b | |||
| d1161dd8e4 | |||
| 91b54b635d | |||
| 2e89cfa8d6 | |||
| 62d484472f | |||
| ba3016f89c | |||
| c5605c8685 | |||
| 126e5944f4 | |||
| 1531f4dad2 | |||
| 82d0539c31 | |||
| 5c47385bee | |||
| e3956a0a09 | |||
| 6568ba7e32 | |||
| 4fe238a01a | |||
| 4b4627d3e5 | |||
| a1b457a5e6 | |||
| 1ca0e928a4 | |||
| 8d4c9ca93a | |||
| 83b8f1a87c | |||
| 33a6e307e9 | |||
| 08fbbd50ad | |||
| c5f047dc0d | |||
| d976452e00 | |||
| cbe69c6ada | |||
| 201676be5c | |||
| 5e6a5355f5 | |||
| 18ddfa4ef4 | |||
| f5bfd4a3c6 | |||
| 9c821b2988 | |||
| afba337575 | |||
| 53451c2d45 | |||
| 884653d41c | |||
| 5304fff790 | |||
| 4316afc137 | |||
| 616f8ad827 | |||
| f0f9171acd | |||
| 049135bd2d | |||
| 4e68e6a48c | |||
| 272e757315 | |||
| 768c07b99a | |||
| 4de1495c31 | |||
| 7872fea6ab | |||
| d33709f4a2 | |||
| 43d7815165 | |||
| 3057c31d01 | |||
| cce42c4165 | |||
| 3e735fcea4 | |||
| 779d32d20f | |||
| 1fa3d3a56d | |||
| b2a22522d2 | |||
| 4e93b2fc8b | |||
| 90b7f0d0c0 | |||
| a0f0230700 | |||
| a0505176ec | |||
| 51ba23faa6 | |||
| d8e35b021f | |||
| a966696d9d | |||
| 72d0047634 | |||
| cabd03a82e | |||
| 3ea65ccc75 | |||
| 38c0d975cc | |||
| 77799d1a37 | |||
| 7d0438f149 | |||
| c9a61afa62 | |||
| 2da235caf7 | |||
| aa577b4b63 | |||
| af43756899 | |||
| 696fb8377e | |||
| cb0e9ba0b2 | |||
| 01f3ed7bc6 | |||
| d46e6de4f0 | |||
| 71cfced5ee | |||
| 4b1f5420f2 | |||
| 0de169e474 | |||
| bc8010d704 | |||
| 05b5998a62 | |||
| 7a5a3deb4d | |||
| f2e84dbf78 | |||
| f0cbb08742 | |||
| 1deb5e0708 | |||
| 5326c1d888 | |||
| 519565760f | |||
| d0dbffb364 | |||
| 5957fd62e3 | |||
| e8d605afe3 | |||
| 25c0aa21dc | |||
| b06bfc13b4 | |||
| a7e478780e | |||
| f63b4bd88b | |||
| cbfcbe16f9 | |||
| 6b812520bc | |||
| f1b00d3ee0 | |||
| 008e59b892 | |||
| 482bc5fbad | |||
| 983c6e1ebc | |||
| 1888ab61d4 | |||
| ef5350f69b | |||
| 5a518c9980 | |||
| d97db987cd | |||
| 8c85868e08 | |||
| 9e682e7c13 | |||
| 96e478192a | |||
| 724186a8c0 | |||
| 4158f4a42a | |||
| 14e6111448 | |||
| add46095c3 | |||
| 5e2e38f1b5 | |||
| 47198779b7 | |||
| 5ff2694ded | |||
| 138c563a40 | |||
| 5ec969ece3 | |||
| 4e2e423a4b | |||
| 632af03a3f | |||
| 025fc154d4 | |||
| 1ca3d3b27f | |||
| 1b839520c9 | |||
| 293df2b73a | |||
| c496abc2bf | |||
| 45c2b792f7 | |||
| 50a594ec60 | |||
| b5739fff68 | |||
| 2c6df68af3 | |||
| 8a1c4f5f8b | |||
| 87cae99326 | |||
| ef939075ef | |||
| a6a1647801 | |||
| 3ebe7dd3e2 | |||
| cc1ce099e5 | |||
| 62dde991df | |||
| 75c11aeba5 | |||
| ed05a5f6ec | |||
| 1c2786ef43 | |||
| 7651922dc9 | |||
| 3a21cb29fd | |||
| 005eeb3a3f | |||
| 582fd84f5e | |||
| 067c28dee5 | |||
| 12fbdbbcdd | |||
| 700b1497d6 | |||
| 20d709686a | |||
| 5c2564336e | |||
| ee4dade7ad | |||
| 7476eb62d3 | |||
| 6466eec106 | |||
| 5f0a6f3d8e | |||
| 5d251361b0 | |||
| 554edf6edf | |||
| 0c53a0dc41 | |||
| 6b97c628ef | |||
| 85125a5569 | |||
| 1a2be3ab11 | |||
| c0740fd21f | |||
| d2d96d9c0a | |||
| ba7a3fc4c6 | |||
| 8f2f7670b2 | |||
| c579e47354 | |||
| c4790068ef | |||
| 7b035d311f | |||
| 713e8ff7c1 | |||
| 53ebd42612 |
@@ -19,9 +19,11 @@ system/ep3/battle-records/*.mzrd
|
||||
system/ep3/tournament-state.json
|
||||
system/licenses.nsi
|
||||
system/licenses/*.json
|
||||
system/players/player_*
|
||||
system/players/account_*
|
||||
system/players/bank_*
|
||||
system/players/*.psochar
|
||||
system/players/*.psosys
|
||||
system/players/*.psocard
|
||||
system/players/*.nsc
|
||||
system/players/*.nsa
|
||||
system/patch-pc/.metadata-cache.json
|
||||
system/patch-bb/.metadata-cache.json
|
||||
|
||||
|
||||
@@ -33,13 +33,14 @@ set (LIBEVENT_LIBRARIES
|
||||
${LIBEVENT_CORE})
|
||||
|
||||
find_package(phosg REQUIRED)
|
||||
find_package(Iconv REQUIRED)
|
||||
find_package(resource_file QUIET)
|
||||
|
||||
|
||||
|
||||
# Executable definition
|
||||
|
||||
add_executable(newserv
|
||||
set(SOURCES
|
||||
src/AFSArchive.cc
|
||||
src/BattleParamsIndex.cc
|
||||
src/BMLArchive.cc
|
||||
@@ -72,6 +73,7 @@ add_executable(newserv
|
||||
src/IPStackSimulator.cc
|
||||
src/ItemCreator.cc
|
||||
src/ItemData.cc
|
||||
src/ItemNameIndex.cc
|
||||
src/ItemParameterTable.cc
|
||||
src/Items.cc
|
||||
src/LevelTable.cc
|
||||
@@ -103,13 +105,21 @@ add_executable(newserv
|
||||
src/ServerState.cc
|
||||
src/Shell.cc
|
||||
src/StaticGameData.cc
|
||||
src/TeamIndex.cc
|
||||
src/Text.cc
|
||||
src/TextArchive.cc
|
||||
src/UnicodeTextSet.cc
|
||||
src/Version.cc
|
||||
src/WordSelectTable.cc
|
||||
)
|
||||
target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR})
|
||||
target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} pthread)
|
||||
|
||||
if(resource_file_FOUND)
|
||||
set(SOURCES ${SOURCES} src/ARCodeTranslator.cc)
|
||||
endif()
|
||||
|
||||
add_executable(newserv ${SOURCES})
|
||||
target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR} ${Iconv_INCLUDE_DIRS})
|
||||
target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} ${Iconv_LIBRARIES} pthread)
|
||||
|
||||
if(resource_file_FOUND)
|
||||
target_compile_definitions(newserv PUBLIC HAVE_RESOURCE_FILE)
|
||||
@@ -131,7 +141,7 @@ foreach(LogTestCase IN ITEMS ${LogTestCases})
|
||||
add_test(
|
||||
NAME ${LogTestCase}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMAND ${CMAKE_BINARY_DIR}/newserv replay-log ${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json --require-basic-credentials)
|
||||
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
|
||||
endforeach()
|
||||
|
||||
file(GLOB ScriptTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
|
||||
|
||||
@@ -57,25 +57,27 @@ See TODO.md for a list of known issues and future work.
|
||||
newserv supports several versions of PSO. Specifically:
|
||||
| Version | Login | Lobbies | Games | Proxy |
|
||||
|----------------|--------------|--------------|--------------|--------------|
|
||||
| DC Trial | Yes (3) | Yes (3) | Yes (3) | No |
|
||||
| DC Prototype | Yes (3) | Yes (3) | Yes (3) | No |
|
||||
| DC Trial | Yes | Yes | Yes | No |
|
||||
| DC 11/2000 | Yes | Yes | Yes | No |
|
||||
| DC 12/2000 | Yes | Yes | Yes | Yes |
|
||||
| DC 01/2001 | Yes | Yes | Yes | Yes |
|
||||
| DC V1 | Yes | Yes | Yes | Yes |
|
||||
| DC 08/2001 | Untested (1) | Untested (1) | Untested (1) | Untested (1) |
|
||||
| DC V2 | Yes | Yes | Yes | Yes |
|
||||
| PC | Yes | Yes | Yes | Yes |
|
||||
| GC Ep1&2 Trial | Untested (1) | Untested (1) | Untested (1) | Untested (1) |
|
||||
| GC Ep1&2 | Yes | Yes | Yes | Yes |
|
||||
| GC Ep1&2 Plus | Yes | Yes | Yes | Yes |
|
||||
| GC Ep3 Trial | Yes | Yes | Partial (4) | Yes |
|
||||
| GC Ep3 Trial | Yes | Yes | Partial (3) | Yes |
|
||||
| GC Ep3 | Yes | Yes | Yes | Yes |
|
||||
| XBOX Ep1&2 | Untested (1) | Untested (1) | Untested (1) | Untested (1) |
|
||||
| Xbox Ep1&2 | Yes | Yes | Yes | Yes |
|
||||
| BB (vanilla) | Yes | Yes | Yes (2) | Yes |
|
||||
| BB (Tethealla) | Yes | Yes | Yes (2) | Yes |
|
||||
|
||||
*Notes:*
|
||||
1. *newserv's implementations of these versions are based on disassembly of the client executables and have never been tested.*
|
||||
2. *BB games are mostly playable, but there are still some unimplemented features (for example, some quests that use rare commands may not work). Please submit a GitHub issue if you find something that doesn't work.*
|
||||
3. *Support for PSO Dreamcast Trial Edition and the December 2000 prototype is somewhat incomplete and probably never will be complete. These versions are rather unstable and seem to crash often, but it's not obvious whether it's because they're prototypes or because newserv sends data they can't handle.*
|
||||
4. *Creating a game works and battle setup behaves mostly normally, but starting a battle doesn't work.*
|
||||
3. *Creating a game works and battle setup behaves mostly normally, but starting a battle doesn't work.*
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -88,8 +90,8 @@ There is a fairly recent macOS ARM64 release on the newserv GitHub repository. Y
|
||||
There is a fairly recent Windows release on the newserv GitHub repository also. It's built with Cygwin, and all the necessary DLL files should be included. That said, I've only tested it on my own machine and there is no CI for Windows builds like there is for macOS and Linux, so if it doesn't work for you, please open a GitHub issue to let me know.
|
||||
|
||||
If you're not using a release from the GitHub repository, do this to build newserv:
|
||||
1. If you're on Windows, install Cygwin. While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell).
|
||||
2. Make sure you have CMake and libevent installed. (On macOS, `brew install cmake libevent`; on most Linuxes, `sudo apt-get install cmake libevent-dev`; on Windows, you already did this in step 1.)
|
||||
1. If you're on Windows, install Cygwin. While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, `libiconv`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell).
|
||||
2. Make sure you have CMake, libevent, and libiconv installed. (On macOS, `brew install cmake libevent libiconv`; on most Linuxes, `sudo apt-get install cmake libevent-dev`; on Windows, you already did this in step 1.)
|
||||
3. Build and install phosg (https://github.com/fuzziqersoftware/phosg).
|
||||
4. Optionally, install resource_dasm (https://github.com/fuzziqersoftware/resource_dasm). This will enable newserv to send memory patches and load DOL files on PSO GC clients. PSO GC clients can play PSO normally on newserv without this.
|
||||
5. Run `cmake . && make` in the newserv directory.
|
||||
@@ -105,20 +107,23 @@ To use newserv in other ways (e.g. for translating data), see the end of this do
|
||||
|
||||
### Installing quests
|
||||
|
||||
newserv automatically finds quests in the system/quests/ directory. To install your own quests, or to use quests you've saved using the proxy's "save files" option, just put them in that directory and name them appropriately.
|
||||
newserv automatically finds quests in the subdirectories of the system/quests/ directory. To install your own quests, or to use quests you've saved using the proxy's Save Files option, just put them in one of the subdirectories there and name them appropriately. The subdirectories and their behaviors (e.g. in which game modes they should appear and for which PSO versions) is defined in the QuestCategories field in config.json.
|
||||
|
||||
Standard quest files should be named like `q###-CATEGORY-VERSION-LANGUAGE.EXT`, battle quests should be named like `b###-VERSION-LANGUAGE.EXT`, challenge quests should be named like `c###-VERSION-LANGUAGE.EXT` for Episode 1 or `d###-VERSION-LANGUAGE.EXT` for Episode 2. The fields in each filename are:
|
||||
Within the category directories, quest files should be named like `q###-VERSION-LANGUAGE.EXT` (although the `q` is ignored, and can be any letter). The fields in each filename are:
|
||||
- `###`: quest number (this doesn't really matter; it should just be unique across the PSO version)
|
||||
- `CATEGORY`: ret = Retrieval, ext = Extermination, evt = Events, shp = Shops, vr = VR, twr = Tower, gv1/gv2/gv4 = Government (BB only), dl = Download (these don't appear during online play), 1p = Solo (BB only)
|
||||
- `VERSION`: dn = Dreamcast NTE, d1 = Dreamcast v1, dc = Dreamcast v2, pc = PC, gcn = GameCube Trial Edition, gc = GameCube Episodes 1 & 2, gc3 = Episode 3 (see below), xb = Xbox, bb = Blue Burst
|
||||
- `VERSION`: dn = Dreamcast NTE, dp = Dreamcast 11/2000 prototype, d1 = Dreamcast v1, dc = Dreamcast v2, pc = PC, gcn = GameCube Trial Edition, gc = GameCube Episodes 1 & 2, gc3 = Episode 3 (see below), xb = Xbox, bb = Blue Burst
|
||||
- `LANGUAGE`: j = Japanese, e = English, g = German, f = French, s = Spanish
|
||||
- `EXT`: file extension (see table below)
|
||||
|
||||
On .dat files, the `LANGUAGE` token may be omitted. If it's present, then that .dat file will only be used for that version of the quest; if omitted, then that .dat file will be used for all versions of the quest.
|
||||
For .dat files, the `LANGUAGE` token may be omitted. If it's present, then that .dat file will only be used for that language of the quest; if omitted, then that .dat file will be used for all languages of the quest.
|
||||
|
||||
For example, the GameCube version of Lost HEAT SWORD is in two files named `q058-ret-gc-e.bin` and `q058-ret-gc.dat`. newserv knows these files are quests because they're in the system/quests/ directory, it knows they're for PSO GC because the filenames contain `-gc`, it knows this is the English version of the quest because the .bin filename ends with `-e` (even though the .dat filename does not), and it puts them in the Retrieval category because the filenames contain `-ret`.
|
||||
Some quests (mostly battle and challenge mode quests) have additional JSON metadata files that describe how the server should handle them. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all versions of the quest on all PSO versions.
|
||||
|
||||
The type identifiers (`b`, `c`, `d`, `e`, or `q`) and categories are configurable. See QuestCategories in config.example.json for more information on how to make new categories or edit the existing categories.
|
||||
Some quests may also include a .pvr file, which contains an image used in the quest. These files are named similarly to their .bin and .dat counterparts.
|
||||
|
||||
For example, the GameCube version of Lost HEAT SWORD is in two files named `q058-gc-e.bin` and `q058-gc.dat`. newserv knows these files are quests because they're in the system/quests/ directory, it knows they're for PSO GC because the filenames contain `-gc`, it knows this is the English version of the quest because the .bin filename ends with `-e` (even though the .dat filename does not), and it puts them in the Retrieval category because the files are within the retrieval/ directory within system/quests/.
|
||||
|
||||
The GameCube and Xbox quest formats are very similar, but newserv treats them as different. If you want to use the same quest file for GameCube and Xbox clients, you can make one a symbolic link to the other.
|
||||
|
||||
There are multiple PSO quest formats out there; newserv supports all of them. It can also decode any known format to standard .bin/.dat format. Specifically:
|
||||
|
||||
@@ -186,7 +191,7 @@ Episode 3 state and game data is stored in the system/ep3 directory. The files i
|
||||
* card-text.mnrd: Decompressed card text archive; same format as TextCardE.bin. Generally only used for debugging.
|
||||
* com-decks.json: COM decks used in tournaments. The default decks in this file come from logs from Sega's servers, so the file doesn't include every COM deck Sega ever made - the rest are probably lost to time.
|
||||
* maps/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). newserv comes with all the original online and offline maps, including Story Mode quests. If you don't want the offline maps and quests to be playable online, delete the .bind files system/ep3/maps.
|
||||
* maps-download/: Download maps and quests (.mnm/.bin/.mnmd/.bind files). Files in this directory have the same format as those in the maps/ directory, but should be named like `e###-gc3-LANGUAGE.EXT` (similar to how non-Episode 3 quests are named in the system/quests/ directory). If you want a map to be available for online play and for downloading, the file must exist in both maps/ and maps-download/ (a symbolic link is acceptable).
|
||||
* maps-download/: Download maps and quests (.mnm/.bin/.mnmd/.bind files). There are two subcategories by default (download maps and Trial Edition download maps), but you can add more by editing QuestCategories in config.json. Categories that have flag 0x40 (Ep3 download) set are indexed from this directory; all others are indexed from system/quests/. Files in maps-download/ subdirectories have the same format as those in the maps/ directory, but should be named like `e###-gc3-LANGUAGE.EXT` (similar to how non-Episode 3 quests are named in the system/quests/ directory). If you want a map to be available for online play and for downloading, the file must exist in both maps/ and in a maps-download/ subdirectory (a symbolic link is acceptable).
|
||||
* tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved).
|
||||
|
||||
There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndexes.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available.
|
||||
@@ -269,12 +274,16 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
|
||||
* Information commands
|
||||
* `$li`: Shows basic information about the lobby or game you're in. If you're on the proxy server, shows information about your connection instead (remote Guild Card number, client ID, etc.).
|
||||
* `$ping` (game server only): Shows round-trip ping time from the server to you.
|
||||
* `$what` (game server only): Shows the type, name, and stats of the nearest item on the ground.
|
||||
* `$matcount` (game server only): Shows how many of each type of material you've used.
|
||||
|
||||
* Debugging commands
|
||||
* `$debug` (game server only): Enable or disable debug. You need the DEBUG permission in your user license to use this command. When debug is enabled, you'll see in-game messages from the server when you take certain actions. You'll also be placed into the highest available slot in lobbies and games instead of the lowest, which is useful for finding commands for which newserv doesn't handle client IDs properly. This setting also disables certain safeguards and allows you to do some things that might crash your client.
|
||||
* `$call <function-id>`: Call a quest function on your client.
|
||||
* `$quest <number>`: Load a quest by quest number. Can be used to load battle or challenge quests with only one player present.
|
||||
* `$qcall <function-id>`: Call a quest function on your client.
|
||||
* `$qset <flag-num>` or `$qclear <flag-num>`: Set or clear a global quest flag for everyone in the game.
|
||||
* `$qsync <reg-num> <value>`: Set a quest register's value on your client. `<reg-num>` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `<value>` is parsed as a floating-point value instead of as an integer.
|
||||
* `$gc` (game server only): Send your own Guild Card to yourself.
|
||||
* `$persist` (game server only): Enable or disable persistence for the current lobby or game. This determines whether the lobby/game is deleted when the last player leaves. You need the DEBUG permission in your user license to use this command because there are no game state checks when you do this. For example, if you make a game persistent, start a quest, then leave the game, the game can't be joined by anyone but also can't be deleted.
|
||||
* `$sc <data>`: Send a command to yourself.
|
||||
@@ -282,21 +291,23 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
|
||||
* Personal state commands
|
||||
* `$arrow <color-id>`: Changes your lobby arrow color.
|
||||
* `$secid <section-id>`: Sets your override section ID. After running this command, any games you create will use your override section ID for rare drops instead of your character's actual section ID. To revert to your actual section id, run `$secid` with no name after it. On the proxy server, this will not work if the remote server controls item drops (e.g. on BB, or on Schtserv with server drops enabled).
|
||||
* `$rand <seed>`: Sets your override random seed (specified as a 32-bit hex value). This will make any games you create use the given seed for rare enemies. This also makes item drops deterministic in Blue Burst games hosted by newserv. On the proxy server, this command can cause desyncs with other players in the same game, since they will not see the overridden random seed. To remove the override, run `$rand` with no arguments.
|
||||
* `$secid <section-id>`: Sets your override section ID. After running this command, any games you create will use your override section ID for rare drops instead of your character's actual section ID. To revert to your actual section id, run `$secid` with no name after it. On the proxy server, this will not work if the remote server controls item drops (e.g. on BB, or on Schtserv with server drops enabled). If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing.
|
||||
* `$rand <seed>`: Sets your override random seed (specified as a 32-bit hex value). This will make any games you create use the given seed for rare enemies. This also makes item drops deterministic in Blue Burst games hosted by newserv. On the proxy server, this command can cause desyncs with other players in the same game, since they will not see the overridden random seed. To remove the override, run `$rand` with no arguments. If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing.
|
||||
* `$ln [name-or-type]`: Sets the lobby number. Visible only to you. This command exists because some non-lobby maps can be loaded as lobbies with invalid lobby numbers. See the "GC lobby types" and "Ep3 lobby types" entries in the information menu for acceptable values here. Note that non-lobby maps do not have a lobby counter, so there's no way to exit the lobby without using either `$ln` again or `$exit`. On the game server, `$ln` reloads the lobby immediately; on the proxy server, it doesn't take effect until you load another lobby yourself (which means you'll like have to use `$exit` to escape). Run this command with no argument to return to the default lobby.
|
||||
* `$exit`: If you're in a lobby, sends you to the main menu (which ends your proxy session, if you're in one). If you're in a game or spectator team, sends you to the lobby (but does not end your proxy session if you're in one). Does nothing if you're in a non-Episode 3 game and no quest is in progress.
|
||||
* `$patch <name>`: Run a patch on your client. `<name>` must exactly match the name of a patch on the server.
|
||||
|
||||
* Blue Burst player commands (game server only)
|
||||
* `$bbchar <username> <password> <1-4>`: Use this command when playing on a non-BB version of PSO. If the username and password are correct, this command converts your current character to BB format and saves it on the server in the given slot. Any character already in that slot is overwritten.
|
||||
* `$edit <stat> <value>`: Modifies your character data.
|
||||
* `$edit <stat> <value>`: Modifies your character data. If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing.
|
||||
* `$save`: Saves your character, system, and Guild Card data immediately. (By default, your character is saved every 60 seconds while online, and your account and Guild Card data are saved whenever they change.)
|
||||
|
||||
* Game state commands (game server only)
|
||||
* `$maxlevel <level>`: Sets the maximum level for players to join the current game. (This only applies when joining; if a player joins and then levels up past this level during the game, they are not kicked out, but won't be able to rejoin if they leave.)
|
||||
* `$minlevel <level>`: Sets the minimum level for players to join the current game.
|
||||
* `$password <password>`: Sets the game's join password. To unlock the game, run `$password` with nothing after it.
|
||||
* `$raretable`: Switches between using the client's or the server's drop table. No effect on BB (the server's drop table is always used). The server's rare tables are defined in JSON files in the system/rare-tables directory.
|
||||
* `$itemtable`: Switches between using the client's or the server's drop table. No effect on BB (the server's drop table is always used). The server's rare tables are defined in JSON files in the system/item-tables directory.
|
||||
* `$drop`: Enables or disables all item drops from boxes and enemies in the current game.
|
||||
|
||||
* Episode 3 commands (game server only)
|
||||
* `$spec`: Toggles the allow spectators flag for Episode 3 games. If any players are spectating when this flag is disabled, they will be sent back to the lobby.
|
||||
@@ -310,9 +321,9 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* Cheat mode commands
|
||||
* `$cheat`: Enables or disables cheat mode for the current game. All other cheat mode commands do nothing if cheat mode is disabled. By default, cheat mode is off in new games but can be enabled; there is an option in config.json that allows you to disable cheat mode entirely, or set it to on by default in new games.
|
||||
* `$infhp` / `$inftp`: Enables or disables infinite HP or TP mode. Applies to only you. In infinite HP mode, one-hit KO attacks will still kill you.
|
||||
* `$warpme <area-id>`: Warps yourself to the given area.
|
||||
* `$warpall <area-id>`: Warps everyone in the game to the given area. You must be the leader to use this command, unless you're on the proxy server.
|
||||
* `$next`: Warps yourself to the next area.
|
||||
* `$warpme <floor-id>`: Warps yourself to the given floor.
|
||||
* `$warpall <floor-id>`: Warps everyone in the game to the given floor. You must be the leader to use this command, unless you're on the proxy server.
|
||||
* `$next`: Warps yourself to the next floor.
|
||||
* `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player doors in solo games if you step on both switches sequentially.
|
||||
* `$item <desc>` (or `$i <desc>`): Create an item. `desc` may be a description of the item (e.g. "Hell Saber +5 0/10/25/0/10") or a string of hex data specifying the item code. Item codes are 16 hex bytes; at least 2 bytes must be specified, and all unspecified bytes are zeroes. If you are on the proxy server, you must not be using Blue Burst for this command to work. On the game server, this command works for all versions.
|
||||
* `$unset <index>`: In an Episode 3 battle, removes one of your set cards from the field. `<index>` is the index of the set card as it appears on your screen - 1 is the card next to your SC's icon, 2 is the card to the right of 1, etc. This does not cause a Hunters-side SC to lose HP, as they normally do when their items are destroyed.
|
||||
@@ -341,10 +352,12 @@ If you're emulating PSO DC or have a disc image, you can patch the appropriate f
|
||||
|
||||
If you're emulating PSO DC, all versions will connect to newserv by setting the following options in Flycast's `emu.cfg` file under `[network]`:
|
||||
- DNS = Your newserv's server address (newserv's DNS server must be running on port 53)
|
||||
- EmulateBBA = no (while some versions support the BBA, some do not, and all versions support the modem)
|
||||
- EmulateBBA = yes
|
||||
- Enable = yes
|
||||
|
||||
Once set up, the EU and US versions will work without any extra set up (other than the HL Check Disable code for USv2), while the JP versions require HL Check Disable codes to be running, and an e-mail account set up. The easiest way to set up an e-mail account is through PlanetWeb's Internet Browser for Dreamcast.
|
||||
It is also necessary to save any DNS information to the flash memory of the Dreamcast to use the BBA - the easiest way to do this is to use the website option in USv2 and then choose the save to flash option.
|
||||
|
||||
Once set up, the EU and US versions will work without any extra set up (other than the HL Check Disable code for USv2), while the JP versions require HL Check Disable codes to be running.
|
||||
|
||||
If the server is running on the same machine as Flycast, this might not work, even if you point Flycast's DNS queries at your local IP address (instead of 127.0.0.1). In this case, you can modify the loaded executable in memory to make it connect anywhere you want. There is a script included with newserv that can do this on macOS; a similar technique could be done manually using scanmem on Linux or Cheat Engine on Windows. To use the script, do this:
|
||||
1. Build and install memwatch (https://github.com/fuzziqersoftware/memwatch).
|
||||
@@ -395,7 +408,6 @@ newserv has many CLI options, which can be used to access functionality other th
|
||||
* Convert a PSO GC or Episode 3 snapshot file to a BMP image (`decode-gci-snapshot`)
|
||||
* Find the likely round1 or round2 seed for a corrupt save file (`salvage-gci`)
|
||||
* Run a brute-force search for a decryption seed (`find-decryption-seed`)
|
||||
* Decode Shift-JIS text to UTF-16 (`decode-sjis`)
|
||||
* Convert quests in .gci, .vms, .dlq, or .qst format to .bin/.dat format (`decode-gci`, `decode-vms`, `decode-dlq`, `decode-qst`)
|
||||
* Convert quests in .bin/.dat to .qst format (`encode-qst`)
|
||||
* Convert text archives (e.g. TextEnglish.pr2) to JSON and vice versa (`decode-text-archive`, `encode-text-archive`)
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
## General
|
||||
|
||||
- Test PSOX (blocked on Insignia private server support)
|
||||
- Implement server-side drops on non-BB game versions
|
||||
- Find a way to silence audio in RunDOL.s
|
||||
- Encapsulate BB server-side random state and make replays deterministic
|
||||
- Implement choice search
|
||||
- Write a simple status API
|
||||
- Implement per-game logging
|
||||
- Add default values in all command structures (like we use for Episode 3 battle commands)
|
||||
- Check for RCE potential in 6x6B-6x6E commands
|
||||
- Build an exception-handling abstraction in ChatCommands that shows formatted error messages in all cases
|
||||
- Make reloading happen on separate threads so compression doesn't block active clients
|
||||
- Implement decrypt/encrypt actions for VMS files
|
||||
- Make UI strings localizable (e.g. entries in menus, welcome message, etc.)
|
||||
- Figure out what causes the corruption message on PC proxy sessions and fix it
|
||||
- Enable item tracking in battle/challenge games (everything should already be set up for it to work)
|
||||
- Rewrite REL-based parsers so they don't assume any fixed offsets
|
||||
- Make $edit for DC/PC
|
||||
- Add an idle connection timeout for proxy sessions
|
||||
|
||||
## Episode 3
|
||||
|
||||
@@ -27,20 +23,19 @@
|
||||
- Make `reload licenses` not vulnerable to online players' licenses overwriting licenses on disk somehow
|
||||
- Implement ranks (based on total Meseta earned)
|
||||
|
||||
## PSO XBOX
|
||||
|
||||
- Fix receiving Guild Cards from non-Xbox players
|
||||
- Make the Guild Card description field in SavedPlayerDataBB longer to accommodate XB descriptions (0x200 bytes)
|
||||
|
||||
## PSOBB
|
||||
|
||||
- Find any remaining mismatches in enemy IDs / experience
|
||||
- Support EXP multipliers
|
||||
- Sale prices for non-rare weapons with specials are computed incorrectly when buying/selling at shops
|
||||
- Replace enemy list, game episode, etc. with quest data when loading a quest
|
||||
- Implement trade window
|
||||
- Find any remaining mismatches in enemy indexes / experience
|
||||
- Fix some edge cases on the BB proxy server (e.g. Change Ship)
|
||||
- Implement less-common subcommands
|
||||
- 6xAC: Sort inventory
|
||||
- 6xC1, 6xC2, 6xCD, 6xCE
|
||||
- 6xCC: Exchange item for team points
|
||||
- 6xD8: Add S-rank weapon special
|
||||
- 6xDE: Good Luck quest
|
||||
- 6xE0
|
||||
- 6xE1: Gallon's Plan quest
|
||||
- Implement team commands
|
||||
- Test team commands
|
||||
- Test all EA subcommands (a few are still not implemented)
|
||||
- 6xC1, 6xC2, 6xCD, 6xCE: Team invites/administration (not implemented)
|
||||
- Fix invite member menu
|
||||
- Implement story progress flags for unlocking quests
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
DC NTE: pso02.dricas.ne.jp
|
||||
Nov 2000 proto: test1.st-pso.games.sega.net
|
||||
Dec 2000 proto: sg107634.csrd.sega.co.jp
|
||||
Dec 2000 proto: sg107634.csrd.sega.co.jp OR master.pso.dream-key.com
|
||||
Jan 2001 proto: master.pso.dream-key.com
|
||||
Aug 2001 proto (v2): ???
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,3 @@
|
||||
.\data\help2-0-ja.png
|
||||
.\data\help0-4-ja.png
|
||||
.\data\help0-5-ja.png
|
||||
@@ -0,0 +1,6 @@
|
||||
.\data\help2-0-ja.png
|
||||
.\data\help0-0-ja.png
|
||||
.\data\help0-1-ja.png
|
||||
.\data\help0-2-ja.png
|
||||
.\data\help0-3-ja.png
|
||||
.\data\help0-7-ja.png
|
||||
@@ -0,0 +1 @@
|
||||
.\data\help1-1-ja.png
|
||||
@@ -0,0 +1 @@
|
||||
.\data\help1-0-ja.png
|
||||
@@ -0,0 +1,39 @@
|
||||
PSOBB SUPPORT FILES, NOTES & RESOURCES
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
CLIENT LOCALIZATION
|
||||
|
||||
By default PSOBB loads everything in Japanese so it requires some extra files
|
||||
to properly implement the English localization from SOA, these files are offered
|
||||
here inside the usbb-resources folder for your convenience they are the same ones
|
||||
from the old official USBB client
|
||||
|
||||
To use them, you just need to drag and drop all its contents into your client's
|
||||
data folder. Then if the client's internal lang flag is set correctly to English
|
||||
will load all the correct texts from these files.
|
||||
|
||||
In case you want to play in Japanese, just use the default Tethealla client and
|
||||
delete all the files including _e or _eng in the names and then the game will
|
||||
default everything to its original Japanese language.
|
||||
|
||||
Just in case, there's the jpbb-resources folder with the latest localization
|
||||
changes made on the official JPBB for an extra backup.
|
||||
|
||||
---------------------------------------------------------------------------------
|
||||
PSOBB EP1,2,4 ORIGINAL VANILLA DROP TABLES/RATES
|
||||
|
||||
Included in the vanilla-tables folder I placed the original files I created for the
|
||||
Schtserv vanilla for backup purposes as they are already implemented into the main
|
||||
newserv logic.
|
||||
|
||||
These tables will offer you the experience as close as possible to the original SEGA
|
||||
servers for PSOBB JP up to the latest patch before the servers shutdown, so besides a
|
||||
fully functional Episode IV experience, the tables also include the latest special items
|
||||
which where added to some Episode 1 and Episode 2 in Ultimate for certain section ID's
|
||||
|
||||
Vanilla Tables and rates are the same ones as the Schtserv Wiki for reference:
|
||||
https://bbwiki.schtserv.com/index.php/Drops-ep1
|
||||
https://bbwiki.schtserv.com/index.php/Drops-ep2
|
||||
https://bbwiki.schtserv.com/index.php/Drops-ep4
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,3 @@
|
||||
.\data\help2-0-en.png
|
||||
.\data\help0-4-en.png
|
||||
.\data\help0-5-en.png
|
||||
@@ -0,0 +1,6 @@
|
||||
.\data\help2-0-en.png
|
||||
.\data\help0-0-en.png
|
||||
.\data\help0-1-en.png
|
||||
.\data\help0-2-en.png
|
||||
.\data\help0-3-en.png
|
||||
.\data\help0-7-en.png
|
||||
@@ -0,0 +1 @@
|
||||
.\data\help1-1-en.png
|
||||
@@ -0,0 +1 @@
|
||||
.\data\help1-0-en.png
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
AFSArchive::AFSArchive(std::shared_ptr<const std::string> data)
|
||||
AFSArchive::AFSArchive(shared_ptr<const string> data)
|
||||
: data(data) {
|
||||
struct FileHeader {
|
||||
be_uint32_t magic;
|
||||
@@ -23,7 +23,7 @@ AFSArchive::AFSArchive(std::shared_ptr<const std::string> data)
|
||||
|
||||
StringReader r(*this->data);
|
||||
const auto& header = r.get<FileHeader>();
|
||||
if (header.magic != 0x41465300) {
|
||||
if (header.magic != 0x41465300) { // 'AFS\0'
|
||||
throw runtime_error("file is not an AFS archive");
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ AFSArchive::AFSArchive(std::shared_ptr<const std::string> data)
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||
pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||
const auto& entry = this->entries.at(index);
|
||||
if (entry.offset > this->data->size()) {
|
||||
throw out_of_range("entry begins beyond end of archive");
|
||||
@@ -45,7 +45,7 @@ std::pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
||||
}
|
||||
|
||||
std::string AFSArchive::get_copy(size_t index) const {
|
||||
string AFSArchive::get_copy(size_t index) const {
|
||||
auto ret = this->get(index);
|
||||
return string(reinterpret_cast<const char*>(ret.first), ret.second);
|
||||
}
|
||||
@@ -54,3 +54,33 @@ StringReader AFSArchive::get_reader(size_t index) const {
|
||||
auto ret = this->get(index);
|
||||
return StringReader(ret.first, ret.second);
|
||||
}
|
||||
|
||||
string AFSArchive::generate(const vector<string>& files, bool big_endian) {
|
||||
return big_endian ? AFSArchive::generate_t<true>(files) : AFSArchive::generate_t<false>(files);
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
string AFSArchive::generate_t(const vector<string>& files) {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
StringWriter w;
|
||||
w.put_u32b(0x41465300); // 'AFS\0'
|
||||
w.put<U32T>(files.size());
|
||||
|
||||
// It seems entries are aligned to 0x800-byte boundaries, and the file's
|
||||
// header is always 0x80000 (!) bytes, most of which is unused
|
||||
uint32_t data_offset = 0x80000;
|
||||
for (const auto& file : files) {
|
||||
w.put<U32T>(data_offset);
|
||||
w.put<U32T>(file.size());
|
||||
data_offset = (data_offset + file.size() + 0x7FF) & (~0x7FF);
|
||||
}
|
||||
|
||||
w.extend_to(0x80000);
|
||||
for (const auto& file : files) {
|
||||
w.write(file);
|
||||
w.extend_to((w.size() + 0x7FF) & (~0x7FF));
|
||||
}
|
||||
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
@@ -25,7 +25,12 @@ public:
|
||||
std::string get_copy(size_t index) const;
|
||||
StringReader get_reader(size_t index) const;
|
||||
|
||||
static std::string generate(const std::vector<std::string>& files, bool big_endian);
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
static std::string generate_t(const std::vector<std::string>& files);
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
std::vector<Entry> entries;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
inline void run_ar_code_translator(const std::string&, const std::string&, const std::string&) {
|
||||
throw std::runtime_error("resource_file is not available; install it and rebuild newserv");
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "ARCodeTranslator.hh"
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <resource_file/ExecutableFormats/DOLFile.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command) {
|
||||
string directory = initial_directory;
|
||||
while (ends_with(directory, "/")) {
|
||||
directory.resize(directory.size() - 1);
|
||||
}
|
||||
PrefixedLogger log("[ar-trans] ");
|
||||
|
||||
unordered_map<string, shared_ptr<DOLFile>> files;
|
||||
for (const auto& filename : list_directory(directory)) {
|
||||
if (ends_with(filename, ".dol")) {
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
string path = directory + "/" + filename;
|
||||
files.emplace(name, new DOLFile(path.c_str()));
|
||||
log.info("Loaded %s", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
string source_filename;
|
||||
shared_ptr<DOLFile> source_file;
|
||||
auto find_match = [&](std::shared_ptr<DOLFile> target_file, uint32_t source_address) -> uint32_t {
|
||||
const DOLFile::Section* source_section = nullptr;
|
||||
for (const auto& sec : source_file->sections) {
|
||||
if (source_address >= sec.address && source_address < sec.address + sec.data.size()) {
|
||||
source_section = &sec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!source_section) {
|
||||
throw runtime_error("source address not within any section");
|
||||
}
|
||||
size_t source_offset = source_address - source_section->address;
|
||||
size_t source_bytes_available_after = source_section->data.size() - source_offset;
|
||||
log.info("(find_match) Source offset = %08zX with %08zX bytes available after", source_offset, source_bytes_available_after);
|
||||
|
||||
for (size_t match_length = 4;
|
||||
match_length < min<size_t>(source_bytes_available_after, 0x100);
|
||||
match_length += 4) {
|
||||
size_t num_matches = 0;
|
||||
size_t last_match_address = 0;
|
||||
StringReader source_r(source_section->data.data() + source_offset, match_length);
|
||||
for (const auto& target_section : target_file->sections) {
|
||||
for (size_t target_section_offset = 0;
|
||||
target_section_offset + match_length <= target_section.data.size();
|
||||
target_section_offset += 4) {
|
||||
source_r.go(0);
|
||||
StringReader target_r(target_section.data.data() + target_section_offset, match_length);
|
||||
size_t z;
|
||||
for (z = 0; z < match_length; z += 4) {
|
||||
if (source_section->is_text) {
|
||||
uint32_t source_opcode = source_r.get_u32b();
|
||||
uint32_t target_opcode = target_r.get_u32b();
|
||||
uint32_t source_class = source_opcode & 0xFC000000;
|
||||
if (source_class != (target_opcode & 0xFC000000)) {
|
||||
break;
|
||||
}
|
||||
if (source_class == 0x48000000) {
|
||||
source_opcode &= 0xFC000003;
|
||||
target_opcode &= 0xFC000003;
|
||||
} else if (source_class == 0x40000000) {
|
||||
source_opcode &= 0xFFFF0003;
|
||||
target_opcode &= 0xFFFF0003;
|
||||
}
|
||||
if (source_opcode != target_opcode) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (source_r.get_u32l() != target_r.get_u32l()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (z == match_length) {
|
||||
num_matches++;
|
||||
last_match_address = target_section.address + target_section_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("(find_match) For match length %zX, %zu matches found", match_length, num_matches);
|
||||
if (num_matches == 1) {
|
||||
return last_match_address;
|
||||
} else if (num_matches == 0) {
|
||||
throw runtime_error("did not find exactly one match");
|
||||
}
|
||||
}
|
||||
throw runtime_error("scan field too long; too many matches");
|
||||
};
|
||||
|
||||
auto handle_command = [&](const string& command) -> void {
|
||||
auto tokens = split(command, ' ');
|
||||
if (tokens.empty()) {
|
||||
throw runtime_error("no command given");
|
||||
}
|
||||
strip_trailing_whitespace(tokens[tokens.size() - 1]);
|
||||
|
||||
if (tokens[0] == "use") {
|
||||
source_filename = tokens.at(1);
|
||||
source_file = files.at(source_filename);
|
||||
} else if (tokens[0] == "match") {
|
||||
if (!source_file) {
|
||||
throw runtime_error("no source file selected");
|
||||
}
|
||||
|
||||
uint32_t source_addr = stoul(tokens.at(1), nullptr, 16);
|
||||
for (const auto& it : files) {
|
||||
if (it.second == source_file) {
|
||||
log.info("(%s) %08" PRIX32, it.first.c_str(), source_addr);
|
||||
} else {
|
||||
try {
|
||||
uint32_t match_addr = find_match(it.second, source_addr);
|
||||
log.info("(%s) %08" PRIX32, it.first.c_str(), match_addr);
|
||||
} catch (const exception& e) {
|
||||
log.error("(%s) failed: %s", it.first.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!tokens[0].empty()) {
|
||||
throw runtime_error("unknown command");
|
||||
}
|
||||
};
|
||||
|
||||
if (!use_file.empty()) {
|
||||
source_filename = use_file;
|
||||
source_file = files.at(source_filename);
|
||||
}
|
||||
|
||||
if (!command.empty()) {
|
||||
handle_command(command);
|
||||
} else {
|
||||
while (!feof(stdin)) {
|
||||
if (!source_filename.empty()) {
|
||||
fprintf(stdout, "ar-trans:%s/%s> ", directory.c_str(), source_filename.c_str());
|
||||
} else {
|
||||
fprintf(stdout, "ar-trans:%s> ", directory.c_str());
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
string command = fgets(stdin);
|
||||
try {
|
||||
handle_command(command);
|
||||
} catch (const exception& e) {
|
||||
log.error("Failed: %s", e.what());
|
||||
}
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command);
|
||||
@@ -21,7 +21,7 @@ template <bool IsBigEndian>
|
||||
struct BMLHeaderEntry {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
ptext<char, 0x20> filename;
|
||||
pstring<TextEncoding::ASCII, 0x20> filename;
|
||||
U32T compressed_size;
|
||||
parray<uint8_t, 0x04> unknown_a1;
|
||||
U32T decompressed_size;
|
||||
@@ -52,7 +52,7 @@ void BMLArchive::load_t() {
|
||||
size_t gvm_offset = offset;
|
||||
offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F);
|
||||
|
||||
this->entries.emplace(entry.filename, Entry{data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size});
|
||||
this->entries.emplace(entry.filename.decode(), Entry{data_offset, entry.compressed_size, gvm_offset, entry.compressed_gvm_size});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,47 +9,28 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
string BattleParamsIndex::Entry::str() const {
|
||||
string a1str = format_data_string(this->unknown_a1.data(), this->unknown_a1.bytes());
|
||||
return string_printf(
|
||||
"BattleParamsEntry[ATP=%hu PSV=%hu EVP=%hu HP=%hu DFP=%hu ATA=%hu LCK=%hu ESP=%hu a1=%s EXP=%" PRIu32 " diff=%" PRIu32 "]",
|
||||
this->atp.load(),
|
||||
this->psv.load(),
|
||||
this->evp.load(),
|
||||
this->hp.load(),
|
||||
this->dfp.load(),
|
||||
this->ata.load(),
|
||||
this->lck.load(),
|
||||
this->esp.load(),
|
||||
a1str.c_str(),
|
||||
this->experience.load(),
|
||||
this->difficulty.load());
|
||||
}
|
||||
|
||||
void BattleParamsIndex::Table::print(FILE* stream) const {
|
||||
auto print_entry = +[](FILE* stream, const Entry& e) {
|
||||
string a1str = format_data_string(e.unknown_a1.data(), e.unknown_a1.bytes());
|
||||
auto print_entry = +[](FILE* stream, const PlayerStats& e) {
|
||||
fprintf(stream,
|
||||
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %s %5" PRIu32 " %5" PRIu32,
|
||||
e.atp.load(),
|
||||
e.psv.load(),
|
||||
e.evp.load(),
|
||||
e.hp.load(),
|
||||
e.dfp.load(),
|
||||
e.ata.load(),
|
||||
e.lck.load(),
|
||||
e.esp.load(),
|
||||
a1str.c_str(),
|
||||
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
|
||||
e.char_stats.atp.load(),
|
||||
e.char_stats.mst.load(),
|
||||
e.char_stats.evp.load(),
|
||||
e.char_stats.hp.load(),
|
||||
e.char_stats.dfp.load(),
|
||||
e.char_stats.ata.load(),
|
||||
e.char_stats.lck.load(),
|
||||
e.unknown_a1.load(),
|
||||
e.experience.load(),
|
||||
e.difficulty.load());
|
||||
e.meseta.load());
|
||||
};
|
||||
|
||||
for (size_t diff = 0; diff < 4; diff++) {
|
||||
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP A1 EXP DIFF\n",
|
||||
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
|
||||
abbreviation_for_difficulty(diff));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
fprintf(stream, " %02zX ", z);
|
||||
print_entry(stream, this->difficulty[diff][z]);
|
||||
print_entry(stream, this->stats[diff][z]);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
@@ -82,20 +63,7 @@ BattleParamsIndex::BattleParamsIndex(
|
||||
}
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Entry& BattleParamsIndex::get(
|
||||
bool solo, Episode episode, uint8_t difficulty, EnemyType type) const {
|
||||
return this->get(solo, episode, difficulty, battle_param_index_for_enemy_type(episode, type));
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Entry& BattleParamsIndex::get(
|
||||
bool solo, Episode episode, uint8_t difficulty, size_t index) const {
|
||||
if (difficulty > 4) {
|
||||
throw invalid_argument("incorrect difficulty");
|
||||
}
|
||||
if (index >= 0x60) {
|
||||
throw invalid_argument("incorrect monster type");
|
||||
}
|
||||
|
||||
const BattleParamsIndex::Table& BattleParamsIndex::get_table(bool solo, Episode episode) const {
|
||||
uint8_t ep_index;
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
@@ -111,5 +79,5 @@ const BattleParamsIndex::Entry& BattleParamsIndex::get(
|
||||
throw invalid_argument("invalid episode");
|
||||
}
|
||||
|
||||
return this->files[!!solo][ep_index].table->difficulty[difficulty][index];
|
||||
return *this->files[!!solo][ep_index].table;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <random>
|
||||
@@ -9,29 +10,71 @@
|
||||
#include <vector>
|
||||
|
||||
#include "EnemyType.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class BattleParamsIndex {
|
||||
public:
|
||||
struct Entry {
|
||||
le_uint16_t atp; // attack power
|
||||
le_uint16_t psv; // perseverance (intelligence?)
|
||||
le_uint16_t evp; // evasion
|
||||
le_uint16_t hp; // hit points
|
||||
le_uint16_t dfp; // defense
|
||||
le_uint16_t ata; // accuracy
|
||||
le_uint16_t lck; // luck
|
||||
le_uint16_t esp; // ???
|
||||
parray<uint8_t, 0x0C> unknown_a1;
|
||||
le_uint32_t experience;
|
||||
le_uint32_t difficulty;
|
||||
// These files are little-endian, even on PSO GC.
|
||||
|
||||
std::string str() const;
|
||||
struct AttackData {
|
||||
/* 00 */ le_int16_t unknown_a1;
|
||||
/* 02 */ le_int16_t atp;
|
||||
/* 04 */ le_int16_t ata_bonus;
|
||||
/* 06 */ le_uint16_t unknown_a4;
|
||||
/* 08 */ le_float distance_x;
|
||||
/* 0C */ le_float angle_x;
|
||||
/* 10 */ le_float distance_y;
|
||||
/* 14 */ le_uint16_t unknown_a8;
|
||||
/* 16 */ le_uint16_t unknown_a9;
|
||||
/* 18 */ le_uint16_t unknown_a10;
|
||||
/* 1A */ le_uint16_t unknown_a11;
|
||||
/* 1C */ le_uint32_t unknown_a12;
|
||||
/* 20 */ le_uint32_t unknown_a13;
|
||||
/* 24 */ le_uint32_t unknown_a14;
|
||||
/* 28 */ le_uint32_t unknown_a15;
|
||||
/* 2C */ le_uint32_t unknown_a16;
|
||||
/* 30 */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ResistData {
|
||||
/* 00 */ le_int16_t evp_bonus;
|
||||
/* 02 */ le_uint16_t efr;
|
||||
/* 04 */ le_uint16_t eic;
|
||||
/* 06 */ le_uint16_t eth;
|
||||
/* 08 */ le_uint16_t elt;
|
||||
/* 0A */ le_uint16_t edk;
|
||||
/* 0C */ le_uint32_t unknown_a6;
|
||||
/* 10 */ le_uint32_t unknown_a7;
|
||||
/* 14 */ le_uint32_t unknown_a8;
|
||||
/* 18 */ le_uint32_t unknown_a9;
|
||||
/* 1C */ le_int32_t dfp_bonus;
|
||||
/* 20 */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MovementData {
|
||||
/* 00 */ le_float idle_move_speed;
|
||||
/* 04 */ le_float idle_animation_speed;
|
||||
/* 08 */ le_float move_speed;
|
||||
/* 0C */ le_float animation_speed;
|
||||
/* 10 */ le_float unknown_a1;
|
||||
/* 14 */ le_float unknown_a2;
|
||||
/* 18 */ le_uint32_t unknown_a3;
|
||||
/* 1C */ le_uint32_t unknown_a4;
|
||||
/* 20 */ le_uint32_t unknown_a5;
|
||||
/* 24 */ le_uint32_t unknown_a6;
|
||||
/* 28 */ le_uint32_t unknown_a7;
|
||||
/* 2C */ le_uint32_t unknown_a8;
|
||||
/* 30 */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Table {
|
||||
parray<parray<Entry, 0x60>, 4> difficulty;
|
||||
/* 0000 */ parray<parray<PlayerStats, 0x60>, 4> stats;
|
||||
/* 3600 */ parray<parray<AttackData, 0x60>, 4> attack_data;
|
||||
/* 7E00 */ parray<parray<ResistData, 0x60>, 4> resist_data;
|
||||
/* AE00 */ parray<parray<MovementData, 0x60>, 4> movement_data;
|
||||
/* F600 */
|
||||
|
||||
void print(FILE* stream) const;
|
||||
} __attribute__((packed));
|
||||
@@ -44,17 +87,14 @@ public:
|
||||
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
|
||||
std::shared_ptr<const std::string> data_off_ep4); // BattleParamEntry_ep4.dat
|
||||
|
||||
const Entry& get(
|
||||
bool solo, Episode episode, uint8_t difficulty, EnemyType type) const;
|
||||
const Entry& get(
|
||||
bool solo, Episode episode, uint8_t difficulty, size_t entry_index) const;
|
||||
const Table& get_table(bool solo, Episode episode) const;
|
||||
|
||||
private:
|
||||
struct LoadedFile {
|
||||
struct File {
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* table;
|
||||
};
|
||||
|
||||
// online/offline, episode
|
||||
LoadedFile files[2][3];
|
||||
// Indexed as [online/offline][episode]
|
||||
std::array<std::array<File, 3>, 2> files;
|
||||
};
|
||||
|
||||
@@ -34,12 +34,13 @@ using namespace std;
|
||||
CatSession::CatSession(
|
||||
shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
GameVersion version,
|
||||
Version version,
|
||||
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
|
||||
: Shell(base),
|
||||
log("[CatSession] ", proxy_server_log.min_level),
|
||||
log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
|
||||
channel(
|
||||
version,
|
||||
1,
|
||||
CatSession::dispatch_on_channel_input,
|
||||
CatSession::dispatch_on_channel_error,
|
||||
this,
|
||||
@@ -73,11 +74,10 @@ void CatSession::dispatch_on_channel_input(
|
||||
|
||||
void CatSession::on_channel_input(
|
||||
uint16_t command, uint32_t flag, std::string& data) {
|
||||
if (this->channel.version != GameVersion::BB) {
|
||||
if (!uses_v4_encryption(this->channel.version)) {
|
||||
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
|
||||
if ((this->channel.version == GameVersion::GC) ||
|
||||
(this->channel.version == GameVersion::XB)) {
|
||||
if (uses_v3_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
|
||||
this->channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
|
||||
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
CatSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
GameVersion version,
|
||||
Version version,
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
|
||||
virtual ~CatSession() = default;
|
||||
|
||||
|
||||