Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e9003b061 | |||
| a59a2d7cd3 | |||
| 8cb7b465da | |||
| 0279b20bb7 | |||
| a140cdbedb | |||
| e7db8f2404 | |||
| 70dfeeba91 | |||
| a860d29636 | |||
| a7811429a8 | |||
| 75be38c38b | |||
| 75de6f259d | |||
| e6a6e862db | |||
| 2d1544edf4 | |||
| 0522b539c4 | |||
| ac20d0c7d4 | |||
| 263622cef8 | |||
| 461bd3d488 | |||
| 7baf5ce327 | |||
| 67c43e803b | |||
| fb9bd077a8 | |||
| 6e808b8340 | |||
| 996509531c | |||
| 4e7d6800cd | |||
| 0c9d4bf338 | |||
| 48641d46a0 | |||
| 84159821e9 | |||
| 823199be2e | |||
| 9eb5601349 | |||
| a7604353c3 | |||
| cfd264e4dc | |||
| e7d0739c8b | |||
| e5afc1d937 | |||
| a9a15600b2 | |||
| 086b2d411a | |||
| c61a13f62e | |||
| 0f25af1ab7 | |||
| 21f1c40408 | |||
| f8e479b4f9 | |||
| 775842dfc5 | |||
| a7d436a894 | |||
| 47bc37e806 | |||
| 080a9ebac4 | |||
| cac9589b81 | |||
| 34bd2cd6a7 | |||
| 8cc8d804bc | |||
| 59124678bf | |||
| b9fd52c6c1 | |||
| 458f5b2d0f | |||
| 7139df0265 | |||
| c6490cb3fb | |||
| b7d37eb169 | |||
| 1d26d1a529 | |||
| 5294a53e1b | |||
| 40d8227504 | |||
| a734bcf483 | |||
| 23e37b8eb7 | |||
| 627c0d949c | |||
| 096f9e46f4 | |||
| 7910556ace | |||
| 2bfcc32b6b | |||
| 0af0f8bc53 | |||
| 46c212f4a1 | |||
| 1e61415c9e | |||
| aa4a773095 | |||
| c8b8bf43f7 | |||
| e50848b52e | |||
| 9e8f7a1cc5 | |||
| 39f3a4afa7 | |||
| 4831f3649a | |||
| a9a524d04a | |||
| b773813f10 | |||
| 00bfae3b62 | |||
| 4dcb49bb34 | |||
| fd25eaadfd | |||
| 2d5b70c734 | |||
| 1ee3caf640 | |||
| e6e11794b8 | |||
| 79eabe5ed2 | |||
| b13e67d491 | |||
| 16a8f91822 | |||
| 82f036f66f | |||
| 3d2b5ebb79 | |||
| 302de15c75 | |||
| 18ce96c84b | |||
| e017279423 | |||
| dbc252a5d6 | |||
| cb0a9dad32 | |||
| 1f6f01a37f | |||
| eaa982aae9 | |||
| 07308b192c | |||
| 27105a3222 | |||
| d915b5e688 | |||
| 089980a6ab | |||
| 49992be60a | |||
| 7414b6ce8e | |||
| 591f3c7b36 | |||
| de2df5f6cf | |||
| 4a40dfd361 | |||
| b760bf5066 | |||
| 8e85167cb6 | |||
| af27ea080f | |||
| 65de5d0060 | |||
| a9b816c548 | |||
| 075c576116 | |||
| f9986f5ac5 | |||
| a9a28aa71b | |||
| c6bbd5daa3 | |||
| c89c3c27ad | |||
| 3205afbcdb | |||
| 61003b509a | |||
| ce3f25be7b | |||
| a8fd1bdada | |||
| 4426476a15 | |||
| 7d775a38d1 | |||
| a7d3720050 | |||
| 596ea40bc0 | |||
| f8f194e19b | |||
| 170111422b | |||
| 81969fc91b | |||
| f0366a3550 | |||
| d676e9bb38 | |||
| 188aac48eb | |||
| 24be0d8195 | |||
| fbc5cd5967 | |||
| d11329b2c9 | |||
| 3a74dbf04e | |||
| 299e187380 | |||
| 0f29b1801d | |||
| f8162d442a | |||
| cd09bfa7e8 | |||
| 1bfbf09891 | |||
| 5523388ad4 | |||
| a3cc0bd13f | |||
| 70ada6669d | |||
| 4d76229527 | |||
| 5ea3d0ad4b | |||
| 90efde7aa9 | |||
| 55f1869125 | |||
| b4efd90fdc | |||
| 87dd554592 | |||
| 58974ae1be | |||
| 21c8bab91c | |||
| c58b37be23 | |||
| d3d98c44b8 | |||
| dc2e73d198 | |||
| 774f9649da | |||
| 093287af75 | |||
| 0126189cbd | |||
| c250a2dbc4 | |||
| 2ff9df19c8 | |||
| 528593651b | |||
| 9f073d07cd | |||
| 4bd6ef12a9 | |||
| 52644695a3 | |||
| 45e619718c | |||
| 43fd979763 | |||
| 082bc49a4d | |||
| 4adcaa7bee | |||
| 630ae0beb4 | |||
| 246dfd9fe0 | |||
| 6f056cb1bd | |||
| 9322c023da | |||
| fd4719f8ec | |||
| 3a22a5c489 | |||
| 862b3d27da | |||
| 998664d2fb | |||
| 0bf2d950ac | |||
| 3ae5e875a1 | |||
| a88795d8b9 | |||
| 9ca1b79409 | |||
| ce8277b96a | |||
| 25731eb71f | |||
| e55963b82b | |||
| b9d9b38351 | |||
| 782babf3ae | |||
| 9869fa03c2 | |||
| 0ae02b0191 | |||
| c0ea976fdc | |||
| c4bf9e7d5b | |||
| 2e5d95d612 | |||
| 75b2827da9 |
@@ -27,14 +27,23 @@ jobs:
|
||||
|
||||
- name: Install libraries (macOS)
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: brew install libevent
|
||||
run: |
|
||||
brew install libevent
|
||||
|
||||
cat << EOF > nproc
|
||||
#!/bin/sh
|
||||
sysctl -n hw.logicalcpu
|
||||
EOF
|
||||
chmod a+x nproc
|
||||
sudo cp nproc /usr/local/bin/nproc
|
||||
rm -f nproc
|
||||
|
||||
- name: Install phosg
|
||||
run: |
|
||||
git clone https://github.com/fuzziqersoftware/phosg.git
|
||||
cd phosg
|
||||
cmake .
|
||||
make
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
- name: Install resource_file
|
||||
@@ -43,14 +52,14 @@ jobs:
|
||||
git clone https://github.com/fuzziqersoftware/resource_dasm.git
|
||||
cd resource_dasm
|
||||
cmake .
|
||||
make
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j $(nproc)
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/build
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
# After build passes with tests
|
||||
workflow_run:
|
||||
workflows: [CMake]
|
||||
types: [completed]
|
||||
branches:
|
||||
- master
|
||||
|
||||
push:
|
||||
tags:
|
||||
- 'v**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=sha
|
||||
type=ref,event=tag
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+23
-15
@@ -1,4 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0110 NEW)
|
||||
|
||||
|
||||
|
||||
@@ -14,12 +15,6 @@ else()
|
||||
add_compile_options(-Wall -Wextra -Werror -Wno-address-of-packed-member)
|
||||
endif()
|
||||
|
||||
set(LOCAL_INCLUDE_DIR "/usr/local/include")
|
||||
set(LOCAL_LIB_DIR "/usr/local/lib")
|
||||
list(APPEND CMAKE_PREFIX_PATH ${LOCAL_LIB_DIR})
|
||||
include_directories(${LOCAL_INCLUDE_DIR})
|
||||
link_directories(${LOCAL_LIB_DIR})
|
||||
|
||||
|
||||
|
||||
# Library search
|
||||
@@ -71,6 +66,7 @@ set(SOURCES
|
||||
src/Compression.cc
|
||||
src/DCSerialNumbers.cc
|
||||
src/DNSServer.cc
|
||||
src/DownloadSession.cc
|
||||
src/EnemyType.cc
|
||||
src/Episode3/AssistServer.cc
|
||||
src/Episode3/BattleRecord.cc
|
||||
@@ -127,6 +123,7 @@ set(SOURCES
|
||||
src/Server.cc
|
||||
src/ServerShell.cc
|
||||
src/ServerState.cc
|
||||
src/SignalWatcher.cc
|
||||
src/StaticGameData.cc
|
||||
src/TeamIndex.cc
|
||||
src/Text.cc
|
||||
@@ -141,20 +138,19 @@ 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)
|
||||
target_link_libraries(newserv phosg::phosg ${LIBEVENT_LIBRARIES} ${Iconv_LIBRARIES} pthread)
|
||||
if(resource_file_FOUND)
|
||||
target_compile_definitions(newserv PUBLIC HAVE_RESOURCE_FILE)
|
||||
target_link_libraries(newserv resource_file::resource_file)
|
||||
message(STATUS "resource_file found; enabling patch support")
|
||||
else()
|
||||
message(WARNING "resource_file not found; disabling patch support")
|
||||
endif()
|
||||
add_dependencies(newserv newserv-Revision-cc)
|
||||
|
||||
# target_compile_options(newserv PRIVATE -fsanitize=address)
|
||||
# target_link_options(newserv PRIVATE -fsanitize=address)
|
||||
|
||||
if(resource_file_FOUND)
|
||||
target_compile_definitions(newserv PUBLIC HAVE_RESOURCE_FILE)
|
||||
target_link_libraries(newserv resource_file)
|
||||
message(STATUS "libresource_file found; enabling patch support")
|
||||
else()
|
||||
message(WARNING "libresource_file not found; disabling patch support")
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
# Test configuration
|
||||
@@ -162,6 +158,7 @@ endif()
|
||||
enable_testing()
|
||||
|
||||
file(GLOB LogTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.txt)
|
||||
file(GLOB LogRDTestCases ${CMAKE_SOURCE_DIR}/tests/*.rdtest.txt)
|
||||
|
||||
foreach(LogTestCase IN ITEMS ${LogTestCases})
|
||||
add_test(
|
||||
@@ -170,6 +167,15 @@ foreach(LogTestCase IN ITEMS ${LogTestCases})
|
||||
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
|
||||
endforeach()
|
||||
|
||||
if(resource_file_FOUND)
|
||||
foreach(LogRDTestCase IN ITEMS ${LogRDTestCases})
|
||||
add_test(
|
||||
NAME ${LogRDTestCase}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LogRDTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
file(GLOB ScriptTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
|
||||
|
||||
foreach(ScriptTestCase IN ITEMS ${ScriptTestCases})
|
||||
@@ -179,6 +185,8 @@ foreach(ScriptTestCase IN ITEMS ${ScriptTestCases})
|
||||
COMMAND ${ScriptTestCase} ${CMAKE_BINARY_DIR}/newserv)
|
||||
endforeach()
|
||||
|
||||
|
||||
|
||||
# Installation configuration
|
||||
|
||||
install(TARGETS newserv DESTINATION bin)
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG BASE_IMAGE=ubuntu:24.04
|
||||
FROM ${BASE_IMAGE} AS builder
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends \
|
||||
python3 \
|
||||
git \
|
||||
ca-certificates \
|
||||
sudo \
|
||||
make \
|
||||
cmake \
|
||||
g++ \
|
||||
libevent-dev \
|
||||
zlib1g-dev
|
||||
|
||||
# ---
|
||||
|
||||
FROM builder AS deps
|
||||
|
||||
ARG PHOSG_TARGET=master
|
||||
ARG RESOURCE_DASM_TARGET=master
|
||||
ARG BUILD_RESOURCE_DASM=true
|
||||
|
||||
RUN git clone --depth 1 -b ${PHOSG_TARGET} https://github.com/fuzziqersoftware/phosg.git && \
|
||||
cd phosg && \
|
||||
cmake . && \
|
||||
make -j$(nproc) && \
|
||||
sudo make install
|
||||
|
||||
RUN \
|
||||
if [ "$BUILD_RESOURCE_DASM" = "true" ] ; then \
|
||||
git clone --depth 1 -b ${RESOURCE_DASM_TARGET} https://github.com/fuzziqersoftware/resource_dasm.git && \
|
||||
cd resource_dasm && \
|
||||
cmake . && \
|
||||
make -j$(nproc) && \
|
||||
sudo make install \
|
||||
; fi
|
||||
|
||||
# ---
|
||||
|
||||
FROM builder AS newserv
|
||||
|
||||
ARG BUILD_TYPE=Release
|
||||
ARG BUILD_STRIP=true
|
||||
|
||||
WORKDIR /usr/src/newserv
|
||||
COPY . .
|
||||
COPY --from=deps /usr/local /usr/local
|
||||
|
||||
RUN cmake -B $PWD/build -DCMAKE_BUILD_TYPE=${BUILD_TYPE} && \
|
||||
cmake --build $PWD/build --config ${BUILD_TYPE} -j $(nproc) && \
|
||||
sudo make -C build install
|
||||
|
||||
RUN \
|
||||
if [ "$BUILD_STRIP" = "true" ] ; then \
|
||||
strip /usr/local/lib/*.a && \
|
||||
strip /usr/local/bin/* \
|
||||
; fi
|
||||
|
||||
# ---
|
||||
|
||||
FROM ${BASE_IMAGE} AS data
|
||||
|
||||
WORKDIR /newserv
|
||||
COPY system/ ./system
|
||||
RUN cp -f system/config.example.json system/config.json && \
|
||||
sed -i 's/"ExternalAddress": "[^"]*"/"ExternalAddress": "0.0.0.0"/' system/config.json
|
||||
|
||||
# ---
|
||||
|
||||
FROM ${BASE_IMAGE} AS final
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends \
|
||||
libevent-dev \
|
||||
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
WORKDIR /newserv
|
||||
COPY --from=data /newserv .
|
||||
COPY --from=newserv /usr/local /usr/local
|
||||
|
||||
USER root
|
||||
VOLUME /newserv/system
|
||||
|
||||
# does not allow receiving any signal at the moment, so force kill the app
|
||||
STOPSIGNAL SIGKILL
|
||||
CMD ["newserv"]
|
||||
@@ -1,6 +1,6 @@
|
||||
# newserv <img align="right" src="static/s-newserv.png" />
|
||||
|
||||
newserv is a game server, proxy, and reverse-engineering tool for Phantasy Star Online (PSO).
|
||||
newserv is a game server, proxy, and reverse-engineering tool for Phantasy Star Online (PSO). **To quickly get started using newserv, just read the [server setup](#server-setup) and [how to connect](#how-to-connect) sections.**
|
||||
|
||||
This project includes code that was reverse-engineered by the community in ages long past, and has been included in many projects since then. It also includes some game data from Phantasy Star Online itself, which was originally created by Sega.
|
||||
|
||||
@@ -13,6 +13,7 @@ See TODO.md for a list of known issues and future work I've curated, or go to th
|
||||
* [History](#history)
|
||||
* [Other server projects](#other-server-projects)
|
||||
* [Using newserv in other projects](#using-newserv-in-other-projects)
|
||||
* [Developer information](#developer-information)
|
||||
* [Compatibility](#compatibility)
|
||||
* Setup
|
||||
* [Server setup](#server-setup)
|
||||
@@ -23,6 +24,7 @@ See TODO.md for a list of known issues and future work I've curated, or go to th
|
||||
* [Installing quests](#installing-quests)
|
||||
* [Item tables and drop modes](#item-tables-and-drop-modes)
|
||||
* [Cross-version play](#cross-version-play)
|
||||
* [Server-side saves](#server-side-saves)
|
||||
* [Episode 3 features](#episode-3-features)
|
||||
* [Memory patches, client functions, and DOL files](#memory-patches-client-functions-and-dol-files)
|
||||
* [Using newserv as a proxy](#using-newserv-as-a-proxy)
|
||||
@@ -64,6 +66,20 @@ Independently of this project, there are many other PSO servers out there. Those
|
||||
* (2021) **[Phantasmal World](https://github.com/DaanVandenBosch/phantasmal-world)**: A set of PSO tools, including a web-based model viewer and quest builder, and a PSO server, written by Daan Vanden Bosch.
|
||||
* (2021) **[Elseware](http://git.sharnoth.com/jake/elseware)**: A PSOBB server written in Rust by Jake.
|
||||
|
||||
## Developer information
|
||||
|
||||
There is a lot of code in this project that could be useful as a reference. Some of the more notable files are:
|
||||
* **src/CommandFormats.hh**: Complete listing of all network commands used in all known versions of the game, and their formats
|
||||
* **src/DCSerialNumbers.hh/cc**: PSO DC serial number validation algorithm and serial number generator
|
||||
* **src/ItemData.hh**: Item format reference
|
||||
* **src/ItemCreator.hh/cc**: Reverse-engineered item generator from Episodes 1&2 (used for all versions)
|
||||
* **src/ItemParameterTable.hh**: Format of many structures in ItemPMT.prs
|
||||
* **src/Map.hh/cc**: Map file (.dat) structure and reverse-engineered Challenge Mode random enemy generation algorithm
|
||||
* **src/QuestScript.cc**: Complete listing of all quest opcodes on all versions, along with their arguments and behavior
|
||||
* **src/SaveFileFormats.hh**: Definitions of save file structures for all versions
|
||||
* **src/Episode3/DataIndexes.hh**: Episode 3 file structures, including card definition format and map/quest format
|
||||
* **system/item-tables/names-v4.json**: Names of all items, indexed by the first 3 bytes of data1
|
||||
|
||||
## Using newserv in other projects
|
||||
|
||||
There is a fair amount of code in this project that could potentially be useful to other projects. You are free to use code from newserv in your own open-source projects; the only condition is that the contents of the LICENSE file must be included in your project if you use code from newserv. Your project does not also have to use the MIT license; you can use any license you want.
|
||||
@@ -74,7 +90,8 @@ If you want to use parts of newserv in your project, there are two easy ways to
|
||||
|
||||
# Compatibility
|
||||
|
||||
newserv supports all known versions of PSO, including development prototypes. Specifically:
|
||||
newserv supports all known versions of PSO, including development prototypes. This table lists all versions that newserv supports. (NTE stands for Network Trial Edition; the GameCube beta versions were called Trial Edition instead, but we use the NTE abbreviation anyway for consistency.)
|
||||
|
||||
| Version | Lobbies | Games | Proxy |
|
||||
|-----------------|----------|----------|----------|
|
||||
| DC NTE | Yes | Yes | No |
|
||||
@@ -97,7 +114,7 @@ newserv supports all known versions of PSO, including development prototypes. Sp
|
||||
| BB (Tethealla) | Yes | Yes (2) | Yes |
|
||||
|
||||
*Notes:*
|
||||
1. *Ep3 NTE battles are not well-tested; some things may not work. See notes/ep3-nte-differences.txt for a list of known differences between NTE and the final version. NTE and non-NTE players cannot battle each other.*
|
||||
1. *Episode 3 NTE battles are not well-tested; some things may not work. See notes/ep3-nte-differences.txt for a list of known differences between NTE and the final version. NTE and non-NTE players cannot battle each other.*
|
||||
2. *Some BB-specific features are not well-tested (for example, some quests that use rare commands may not work properly). Please submit a GitHub issue if you find something that doesn't work.*
|
||||
3. *This is the only version of PSO that doesn't have any way to identify the player's account - there is no serial number or username. For this reason, AllowUnregisteredUsers must be enabled in config.json to support PC NTE, and PC NTE players receive a random Guild Card number every time they connect. To prevent abuse, PC NTE support can be disabled in config.json.*
|
||||
|
||||
@@ -110,26 +127,30 @@ Currently newserv works on macOS, Windows, and Ubuntu Linux. It will likely work
|
||||
### Windows/macOS
|
||||
|
||||
1. Download the latest release-windows-amd64.zip or release-macos-arm64.zip file from the [releases page](https://github.com/fuzziqersoftware/newserv/releases).
|
||||
2. Extract the contents of the release folder to a location on your computer.
|
||||
3. Edit the config.example.json file in the system folder as needed, then rename it to config.json.
|
||||
4. If you plan to play Blue Burst on newserv, set up the patch directory. See [client patch directories](#client-patch-directories) for more information.
|
||||
2. Extract the contents of the archive to some location on your computer.
|
||||
3. (Optional) If you want to change any config options, go into the system/ folder, open config.json in a text editor, and edit it to your liking. There are comments in the file that describe what all the options do.
|
||||
4. (Optional) If you plan to play Blue Burst on newserv, set up the patch directory. See [client patch directories](#client-patch-directories) for details.
|
||||
5. Run the newserv executable.
|
||||
|
||||
### Linux
|
||||
|
||||
There are currently no precompiled releases for Linux. To run newserv on Linux, see the "Building from source" section below.
|
||||
There are currently no precompiled releases for Linux. To run newserv on Linux, you'll have to build it from source - see the "Building from source" section below.
|
||||
|
||||
### Building from source
|
||||
|
||||
1. Install the packages newserv depends on.
|
||||
* If you're on Windows, install [Cygwin](https://www.cygwin.com/). While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `make`, `libiconv-devel`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell).
|
||||
* If you're on Windows, install [Cygwin](https://www.cygwin.com/). While doing so, install the `cmake`, `gcc-core`, `gcc-g++`, `git`, `libevent2.1_7`, `libevent-devel`, `make`, `libiconv-devel`, and `zlib` packages. Do the rest of these steps inside a Cygwin shell (not a Windows cmd shell or PowerShell).
|
||||
* If you're on macOS, run `brew install cmake libevent libiconv`.
|
||||
* If you're on Linux, run `sudo apt-get install cmake libevent-dev` (or use your Linux distribution's package manager).
|
||||
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.
|
||||
|
||||
After building newserv, edit system/config.example.json as needed and rename it to system/config.json, set up [client patch directories](#client-patch-directories) if you're planning to play Blue Burst, then run `./newserv` in newserv's directory.
|
||||
After building newserv, edit system/config.example.json as needed **and rename it to system/config.json** (note that this step is not necessary for the precompiled releases!), set up [client patch directories](#client-patch-directories) if you're planning to play Blue Burst, then run `./newserv` in newserv's directory.
|
||||
|
||||
The server has an interactive shell which can be used to make changes, such as managing user accounts, updating the server's configuration, managing Episode 3 tournaments, and more. Type `help` and press Enter to see all the commands.
|
||||
|
||||
On Linux and macOS, the server also responds to SIGUSR1 and SIGUSR2. SIGUSR1 does the equivalent of the shell's `reload config` command, which reloads config.json but not any dependent files (so quests, Episode 3 maps, etc. will not be reloaded). SIGUSR2 does the equivalent of the shell's `reload all` command, which reloads everything.
|
||||
|
||||
To use newserv in other ways (e.g. for translating data), see the end of this document.
|
||||
|
||||
@@ -140,10 +161,10 @@ newserv implements a patch server for PSO PC and PSO BB game data. Any file or d
|
||||
For Blue Burst set up, the below is mandatory for a smooth experience:
|
||||
|
||||
1. Browse to your chosen client's data directory.
|
||||
2. Copy all the map_*.dat files, unitxt_* files and the data.gsl file and place them in `system/patch-bb/data`.
|
||||
3. If you're using game files from the Tethealla client, make a copy of unitxt_j.prs inside system/patch-bb/data and name it unitxt_e.prs. (If unitxt_e.prs already exists, replace it with the copied file.)
|
||||
2. Copy all the `map_*.dat` files, `unitxt_*` files and the `data.gsl` file and place them in `system/patch-bb/data`.
|
||||
3. If you're using game files from the Tethealla client, make a copy of `unitxt_j.prs` inside system/patch-bb/data and name it `unitxt_e.prs`. (If `unitxt_e.prs` already exists, replace it with the copied file.)
|
||||
|
||||
If you do not have a BB client, or using a Tethealla client from another source, Tethealla clients that are compatible with newserv can be found here: [English](https://web.archive.org/web/20240402011115/https://ragol.org/files/bb/TethVer12513_English.zip) / [Japanese](https://web.archive.org/web/20240402013127/https://ragol.org/files/bb/TethVer12513_Japanese.zip). These clients connect to 127.0.0.1 (localhost) automatically.
|
||||
If you don't have a BB client, or if you're using a Tethealla client from another source, Tethealla clients that are compatible with newserv can be found here: [English](https://web.archive.org/web/20240402011115/https://ragol.org/files/bb/TethVer12513_English.zip) / [Japanese](https://web.archive.org/web/20240402013127/https://ragol.org/files/bb/TethVer12513_Japanese.zip). These clients connect to 127.0.0.1 (localhost) automatically.
|
||||
|
||||
For BB clients, newserv reads some files out of the patch data to implement game logic, so it's important that certain game files are synchronized between the server and the client. newserv contains defaults for these files in the system/maps/bb-v4 directory, but if these don't match the client's copies of the files, odd behavior will occur in games.
|
||||
|
||||
@@ -357,10 +378,14 @@ Exactly which data is saved and loaded depends on the game version:
|
||||
| PSO PC (v2) | Yes | Yes | No | No | No | Save only |
|
||||
| PSO GC NTE | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| PSO GC (not Plus) | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| PSO GC Plus | Save only | Save only | No | No | No | Save only |
|
||||
| PSO GC Plus (1) | Save only | Save only | No | No | No | Save only |
|
||||
| PSO GC Ep3 (1) | No | Save only | No | No | No | Save only |
|
||||
| PSO Xbox | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| PSO BB | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
|
||||
*Notes*:
|
||||
1. *If EnableSendFunctionCallQuestNumber is enabled in config.json, then $savechar and $loadchar can save and restore all character data on these versions, just like on GC non-Plus. Episode 3 characters exist in a separate namespace; that is, you can't use $savechar and $loadchar to convert an Ep3 character to non-Ep3, or vice versa.*
|
||||
|
||||
## Episode 3 features
|
||||
|
||||
newserv supports many features unique to Episode 3:
|
||||
@@ -407,7 +432,9 @@ Like quests, Episode 3 card definitions, maps, and quests are cached in memory.
|
||||
|
||||
You can put assembly files in the system/client-functions directory with filenames like PatchName.VERS.patch.s and they will appear in the Patches menu for clients that support client functions. Client functions are written in SH-4, PowerPC, or x86 assembly and are compiled when newserv is started. The assembly system's features are documented in the comments in system/client-functions/System/WriteMemory.ppc.s.
|
||||
|
||||
The VERS token in client function filenames refers to the specific version of the game that the client function applies to. Some versions do not support receiving client functions at all. The specific versions are:
|
||||
The VERS token in client function filenames refers to the specific version of the game that the client function applies to. Some versions do not support receiving client functions at all. *Note: newserv uses the shorter GameCube versioning convention, where discs labeled DOL-XXXX-0-0Y are version 1.Y. The PSO community seems to use the convention 1.0Y in some places instead, but these are the same version. For example, the version that newserv calls v1.4 is the same as v1.04, and is labeled DOL-GPOJ-0-04 on the underside of the disc.*
|
||||
|
||||
The specific versions are:
|
||||
|
||||
| Game | VERS | Architecture |
|
||||
|-------------------|------|---------------|
|
||||
@@ -427,15 +454,15 @@ The VERS token in client function filenames refers to the specific version of th
|
||||
| PSO GC v1.2 JP | 3OJ2 | PowerPC |
|
||||
| PSO GC v1.3 JP | 3OJ3 | PowerPC |
|
||||
| PSO GC v1.4 JP | 3OJ4 | PowerPC |
|
||||
| PSO GC v1.5 JP | 3OJ5 | Not supported |
|
||||
| PSO GC v1.5 JP | 3OJ5 | PowerPC (1) |
|
||||
| PSO GC v1.0 US | 3OE0 | PowerPC |
|
||||
| PSO GC v1.1 US | 3OE1 | PowerPC |
|
||||
| PSO GC v1.2 US | 3OE2 | Not supported |
|
||||
| PSO GC v1.2 US | 3OE2 | PowerPC (1) |
|
||||
| PSO GC v1.0 EU | 3OP0 | PowerPC |
|
||||
| PSO GC Ep3 NTE | 3SJT | PowerPC |
|
||||
| PSO GC Ep3 JP | 3SJ0 | PowerPC |
|
||||
| PSO GC Ep3 US | 3SE0 | Not supported |
|
||||
| PSO GC Ep3 EU | 3SP0 | Not supported |
|
||||
| PSO GC Ep3 US | 3SE0 | PowerPC (1) |
|
||||
| PSO GC Ep3 EU | 3SP0 | PowerPC (1) |
|
||||
| PSO Xbox Beta | 4OJB | x86 |
|
||||
| PSO Xbox JP Disc | 4OJD | x86 |
|
||||
| PSO Xbox JP TU | 4OJU | x86 |
|
||||
@@ -443,12 +470,13 @@ The VERS token in client function filenames refers to the specific version of th
|
||||
| PSO Xbox US TU | 4OEU | x86 |
|
||||
| PSO Xbox EU Disc | 4OPD | x86 |
|
||||
| PSO Xbox EU TU | 4OPU | x86 |
|
||||
| PSO BB JP 1.25.13 | 51OC | x86 |
|
||||
| PSO BB Tethealla | 51OC | x86 |
|
||||
| PSO BB JP 1.25.13 | 59NL | x86 |
|
||||
| PSO BB Tethealla | 59NL | x86 |
|
||||
|
||||
*Note: newserv uses the shorter GameCube versioning convention, where discs labeled DOL-XXXX-0-0Y are version 1.Y. The PSO community seems to use the convention 1.0Y in some places instead, but these are the same version. For example, the version that newserv calls v1.4 is the same as v1.04, and is labeled DOL-GPOJ-0-04 on the underside of the disc.*
|
||||
*Notes:*
|
||||
1. *Client functions are only supported on these versions if EnableSendFunctionCallQuestNumbers is set in config.json. See the comments there for more information.*
|
||||
|
||||
newserv comes with a set of patches for some of the above versions, based on AR codes originally made by Ralf at GC-Forever and Aleron Ives. Many of them were originally posted in [this thread](https://www.gc-forever.com/forums/viewtopic.php?f=38&t=2050).
|
||||
newserv comes with a set of patches for many of the above versions, based on AR codes originally made by Ralf at GC-Forever and Aleron Ives. Many of them were originally posted in [this thread](https://www.gc-forever.com/forums/viewtopic.php?f=38&t=2050).
|
||||
|
||||
You can also put DOL files in the system/dol directory, and they will appear in the Programs menu for GC clients. Selecting a DOL file there will load the file into the GameCube's memory and run it, just like the old homebrew loaders (PSUL and PSOload) did. For this to work, ReadMemoryWord.ppc.s, WriteMemory.ppc.s, and RunDOL.ppc.s must be present in the system/client-functions/System directory. This has been tested on Dolphin but not on a real GameCube, so results may vary.
|
||||
|
||||
@@ -470,10 +498,10 @@ There are many options available when starting a proxy session. All options are
|
||||
* **Chat commands**: enables chat commands in the proxy session (on by default).
|
||||
* **Chat filter**: enables escape sequences in chat messages and info board (on by default).
|
||||
* **Player notifications**: shows a message when any player joins or leaves the game or lobby you're in.
|
||||
* **Block pings**: blocks automatic pings sent by the client, and responds to ping commands from the server automatically. This works around a bug in Sylverant's login server.
|
||||
* **Block pings**: blocks automatic pings sent by the client, and responds to ping commands from the server automatically.
|
||||
* **Infinite HP**: automatically heals you whenever you get hit. An attack that kills you in one hit will still kill you, however.
|
||||
* **Infinite TP**: automatically restores your TP whenever you use any technique.
|
||||
* **Switch assist**: attempts to unlock doors that require two or four players in a one-player game.
|
||||
* **Switch assist**: unlocks doors that require two or four players in a one-player game, when you step on one of the switches.
|
||||
* **Infinite Meseta** (Episode 3 only): gives you 1,000,000 Meseta, regardless of the value sent by the remote server.
|
||||
* **Block events**: disables holiday events sent by the remote server.
|
||||
* **Block patches**: prevents any B2 (patch) commands from reaching the client.
|
||||
@@ -502,11 +530,13 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$si` (game server only): Show basic information about the server.
|
||||
* `$ping`: Show round-trip ping time from the server to you. On the proxy server, show the ping time from you to the proxy and from the proxy to the server.
|
||||
* `$matcount` (game server only): Show how many of each type of material you've used.
|
||||
* `$killcount` (game server only): Show the kill count on your currently-equipped weapon. If you're in a game and not on BB, the value is only accurate at the time the item enters the game.
|
||||
* `$itemnotifs <mode>`: Enable item drop notification messages. If the game has private drops enabled, you will only see a notification if the dropped item is visible to you; you won't be notified of other players' drops. The modes are:
|
||||
* `off`: No notifications are shown.
|
||||
* `rare`: You are notified when a rare item drops.
|
||||
* `on`: You are notified when any item drops, except Meseta.
|
||||
* `every`: You are notified when any item drops, including Meseta.
|
||||
* `$announcerares`: Enable or disable announcements for your rare item finds. This determines whether rare items you find will be announced to the game and server, not whether you will see announcements for others finding rare items.
|
||||
* `$what` (game server only): Show the type, name, and stats of the nearest item on the ground.
|
||||
* `$where` (game server only): Show your current floor number and coordinates. Mainly useful for debugging.
|
||||
* `$qfread <field-name>` (game server only): Show the value of a quest counter in your player data. The field names are defined in config.json.
|
||||
@@ -539,9 +569,10 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* Personal state commands
|
||||
* `$arrow <color-id>`: Change your lobby arrow color.
|
||||
* `$secid <section-id>`: Set 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. If you're in a game and you are the leader of the game, this also immediately changes the item tables used by the server when creating items. 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.
|
||||
* `$battle` (game server only; DC v1 only): After using this command, the next game you create will be in battle mode. (A chat command is required for this because DCv1 doesn't allow this natively.) On DCv1, the battle quests are not available, but free-roam is.
|
||||
* `$rand <seed>`: Set 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]`: Set 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.
|
||||
* `$swa`: Enable or disable switch assist. When enabled, the server will attempt to automatically unlock two-player and four-player doors in non-quest games if you step on all the required switches sequentially.
|
||||
* `$swa`: Enable or disable switch assist. When enabled, the server will unlock two-player and four-player doors in non-quest games when you step on any of the required switches.
|
||||
* `$exit`: If you're in a lobby, send you to the main menu (which ends your proxy session, if you're in one). If you're in a game or spectator team, send 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.
|
||||
|
||||
@@ -549,7 +580,7 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$savechar <slot>`: Save your current character data on the server in the specified slot. See the "Server-side saves" section for more details.
|
||||
* `$loadchar <slot>`: Save your current character data on the server in the specified slot. See the "Server-side saves" section for more details.
|
||||
* `$bbchar <username> <password> <slot>`: Save your current character data on the server in a different account's BB character slots. See the "Server-side saves" section for more details.
|
||||
* `$edit <stat> <value>`: Modify your character data. If you are on V3 (GameCube/Xbox), this command does nothing. If you are on V1 or V2 (DC or PC, not BB), your changes will be undone if you join a game - to save your changes, disconnect from the lobby. If cheats are allowed on the server, `<stat>` can be any of `atp`, `mst`, `evp`, `hp`, `dfp`, `ata`, `lck`, `meseta`, `exp`, `level`, `namecolor`, `secid`, `name`, `language`, `npc`, or `tech`. If cheats are not allowed, only `namecolor`, `name`, `language`, and `npc` can be used. Changing your character's language is only useful on BB; to do so, use a single-character language code (e.g. to switch your character to English, use `$edit language E`; for Japanese, use `$edit language J`).
|
||||
* `$edit <stat> <value>`: Modify your character data. See "Using $edit" below for details.
|
||||
|
||||
* Blue Burst player commands (game server only)
|
||||
* `$bank [number]`: Switch your current bank, so you can access your other character's banks (if `number` is 1-4) or your shared account bank (if `number` is 0). If `number` is not given, switch back to your current character's bank.
|
||||
@@ -560,14 +591,14 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$minlevel <level>`: Set the minimum level for players to join the current game.
|
||||
* `$password <password>`: Set the game's join password. To unlock the game, run `$password` with nothing after it.
|
||||
* `$dropmode [mode]`: Change the way item drops behave in the current game. `mode` can be `none`, `client`, `shared`, `private`, or `duplicate`. If `mode` is not given, tells you the current drop mode without changing it. See the "Item tables and drop modes" section for more information.
|
||||
* `$persist`: Enable or disable persistence for the current game. When persistence is on, the game will not be deleted when the last player leaves. The states of enemies, objects, and switches will be saved, and items left on the floor will not be deleted (except items that were only visible to the leaving player, which are deleted). If the game is empty for too long (15 minutes by default), it is then deleted. There is an edge case with persistence: if the player defeats a boss, leaves the room, joins again, and returns to the boss arena, neither the boss nor the exit warp will appear, so they will be stuck there and have to use $warp or Quit Game to get out. For this reason, `$warp 0` is allowed in boss arenas once the boss is defeated, even if cheat mode is disabled.
|
||||
* `$persist`: Enable or disable persistence for the current game. When persistence is on, the game will not be deleted when the last player leaves. The states of enemies, objects, and switches will be saved, and items left on the floor will not be deleted. (But if you're in the private or duplicate drop mode, items dropped by enemies are deleted - to make sure a certain item won't be deleted, you can pick it up and drop it again.) If the game is empty for too long (15 minutes by default), it is then deleted.
|
||||
|
||||
* Episode 3 commands (game server only)
|
||||
* `$spec`: Toggle the allow spectators flag for Episode 3 games. If any players are spectating when this flag is disabled, they are sent back to the lobby.
|
||||
* `$inftime`: Toggle infinite-time mode. Must be used before starting a battle. If infinite-time mode is on, the overall and per-phase time limits will be disabled regardless of the values chosen during battle rules setup. After completing a battle, infinite-time mode is reset to the server's default value (which can be set in Episode3BehaviorFlags in config.json).
|
||||
* `$dicerange [d:L-H] [1:L-H] [a1:L-H] [d1:L-H]`: Set override dice ranges for the next battle. The min and max dice values from the rules setup menu always apply to the ATK dice, but you can specify a different range for the DEF dice with `d:2-4` (for example). The `1:` override applies to the 1-player team in a 2v1 game (so you would set the 2-player team's desired dice range in the rules menu). You can also specify the 1-player team's ATK and DEF ranges separately with the `a1:` and `d1:` overrides. Note that these ranges will only be used if the chosen map or quest does not override them.
|
||||
* `$stat <what>`: Show a statistic about your player or team in the current battle. `<what>` can be `duration`, `fcs-destroyed`, `cards-destroyed`, `damage-given`, `damage-taken`, `opp-cards-destroyed`, `own-cards-destroyed`, `move-distance`, `cards-set`, `fcs-set`, `attack-actions-set`, `techs-set`, `assists-set`, `defenses-self`, `defenses-ally`, `cards-drawn`, `max-attack-damage`, `max-combo`, `attacks-given`, `attacks-taken`, `sc-damage`, `damage-defended`, or `rank`.
|
||||
* `$surrender`: Cause your team to immediately lose the current battle.
|
||||
* `$surrender`: Cause your team to immediately lose the current battle. If your story character is already defeated, you can't surrender - only your teammate can.
|
||||
* `$saverec <name>`: Save the recording of the last battle.
|
||||
* `$playrec <name>`: Play a battle recording. This command creates a spectator team immediately but the replay does not start automatically, to give other players a chance to join. To start the battle replay within the spectator team, run `$playrec` again (with no name). There is a bug in Dolphin that makes this command unstable in emulation (see the "Battle records" section above).
|
||||
|
||||
@@ -588,13 +619,49 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$song <song-id>` (Episode 3 only): Play a specific song in the current lobby.
|
||||
|
||||
* Administration commands (game server only)
|
||||
* `$ann <message>`: Send an announcement message. The message is sent as temporary on-screen text to all players in all games and lobbies.
|
||||
* `$ann! <message>`: Send an announcement message. The message is sent as a Simple Mail message to all players in all games and lobbies.
|
||||
* `$ann <message>`: Send an announcement message. The message is sent as temporary on-screen text to all players in all games and lobbies. On BB, the message appears in the scrolling top bar.
|
||||
* `$ann!`, `$ann?`, `$ann?!`: Same as `$ann`, but with `?`, omits the sender's name, and with `!`, sends the message as a Simple Mail message instead of on-screen text.
|
||||
* `$ax <message>`: Send a message to the server's terminal. This cannot be used to run server shell commands; it only prints text to stderr.
|
||||
* `$silence <identifier>`: Silence a player (remove their ability to chat) or unsilence a player. The identifier may be the player's name or Guild Card number.
|
||||
* `$kick <identifier>`: Disconnect a player. The identifier may be the player's name or Guild Card number.
|
||||
* `$ban <duration> <identifier>`: Ban a player. The duration should be of the form `10m` (minutes), `10h` (hours), `10d` (days), `10w` (weeks), `10M` (months), or `10y` (years). (Numbers other than 10 may be used, of course.) As with `$kick`, the identifier may be the player's name or Guild Card number.
|
||||
|
||||
### Using $edit
|
||||
|
||||
The $edit command modifies your character data. This command doesn't work on V3 (GameCube/Xbox). If you are on V1 or V2 (DC or PC, not BB), your changes will be undone if you join a game - to save your changes, disconnect from the lobby.
|
||||
|
||||
Some subcommands are always available. They are:
|
||||
* `$edit mat reset power`: Clear your usage of power materials (BB only)
|
||||
* `$edit mat reset mind`: Clear your usage of mind materials (BB only)
|
||||
* `$edit mat reset evade`: Clear your usage of evade materials (BB only)
|
||||
* `$edit mat reset def`: Clear your usage of def materials (BB only)
|
||||
* `$edit mat reset luck`: Clear your usage of luck materials (BB only)
|
||||
* `$edit mat reset hp`: Clear your usage of HP materials (BB only)
|
||||
* `$edit mat reset tp`: Clear your usage of TP materials (BB only)
|
||||
* `$edit mat reset all`: Clear your usage of all materials except HP and TP (BB only)
|
||||
* `$edit mat reset every`: Clear your usage of all materials including HP and TP (BB only)
|
||||
* `$edit namecolor AARRGGBB`: Set your name color (AARRGGBB specified in hex)
|
||||
* `$edit language L`: Set your language (Generally only useful on BB; values for L: J = Japanese, E = English, G = German, F = French, S = Spanish, B = Simplified Chinese, T = Traditional Chinese, K = Korean)
|
||||
* `$edit name NAME`: Set your character name
|
||||
* `$edit npc NPC-NAME`: Set or remove an NPC skin on your character (use `none` to remove a skin). The NPC names are:
|
||||
* On all versions except DCv1 and early prototypes: `ninja`, `rico`, `sonic`, `knuckles`, `tails`
|
||||
* On GC, Xbox, and BB: `flowen`, `elly`
|
||||
* On BB only: `momoka`, `irene`, `guild`, `nurse`
|
||||
* `$edit secid SECID-NAME`: Set your section ID (cheat mode is required for this unless your character is Level 1)
|
||||
|
||||
The remaining subcommands are only available if cheat mode is enabled on the server. They are:
|
||||
* `$edit atp N`: Set your ATP to N until stats are updated (e.g. by leveling up)
|
||||
* `$edit mst N`: Set your MST to N until stats are updated
|
||||
* `$edit evp N`: Set your EVP to N until stats are updated
|
||||
* `$edit dfp N`: Set your DFP to N until stats are updated
|
||||
* `$edit ata N`: Set your ATA to N until stats are updated
|
||||
* `$edit lck N`: Set your LCK to N until stats are updated
|
||||
* `$edit hp N`: Set your MST to N until stats are updated
|
||||
* `$edit meseta N`: Set the amount of Meseta in your inventory
|
||||
* `$edit exp N`: Set your total amount of EXP (does not affect level)
|
||||
* `$edit level N`: Set your current level (recomputes stats, but does not affect EXP)
|
||||
* `$edit tech TECH-NAME LEVEL`: Set the level of one of your techniques
|
||||
|
||||
# Non-server features
|
||||
|
||||
newserv has many CLI options, which can be used to access functionality other than the game and proxy server. Run `newserv help` to see a full list of the options and how to use each one.
|
||||
@@ -613,6 +680,7 @@ The data formats that newserv can convert to/from are:
|
||||
| PSO GC quest file (.gci) | None | `decode-gci` |
|
||||
| Download quest file (.dlq) | None | `decode-dlq` |
|
||||
| Server quest file (.qst) | `encode-qst` | `decode-qst` |
|
||||
| PSO DC save file (.vms) | `encrypt-vms-save` | `decrypt-vms-save` |
|
||||
| PSO PC save file | `encrypt-pc-save` | `decrypt-pc-save` |
|
||||
| PSO GC save file (.gci) | `encrypt-gci-save` | `decrypt-gci-save` |
|
||||
| PSO GC snapshot file | None | `decode-gci-snapshot` |
|
||||
@@ -639,3 +707,108 @@ There are several actions that don't fit well into the table above, which let yo
|
||||
* Convert item data to a human-readable description, or vice versa (`describe-item`)
|
||||
* Connect to another PSO server and pretend to be a client (`cat-client`)
|
||||
* Generate or describe DC serial numbers (`generate-dc-serial-number`, `inspect-dc-serial-number`)
|
||||
|
||||
# Docker
|
||||
Docker is new and mostly unsupported at this time. However, here are some best-effort steps to build and run in a docker container on Ubuntu Linux.
|
||||
Tested on Ubuntu 22.04.4 LTS.
|
||||
Note: You cannot have anything except this docker container using port 53 (DNS) on your server.
|
||||
|
||||
Install prerequisites
|
||||
```
|
||||
sudo apt install -y git
|
||||
sudo apt install -y cmake. ## minimum version is 3.10. Check installed version with "cmake --version"
|
||||
```
|
||||
|
||||
Clone repository
|
||||
```
|
||||
cd ~
|
||||
git clone https://github.com/fuzziqersoftware/newserv/
|
||||
cd ~/newserv
|
||||
```
|
||||
|
||||
Build newserv. This will take a while. Don't forget the period at the end!
|
||||
```
|
||||
sudo docker build -t newserv .
|
||||
```
|
||||
|
||||
Create persistent directories. Assuming you want to store the persistent data in your home directory
|
||||
```
|
||||
mkdir ~/newservPersist
|
||||
mkdir ~/newservPersist/players
|
||||
mkdir ~/newservPersist/teams
|
||||
mkdir ~/newservPersist/licenses
|
||||
```
|
||||
|
||||
Copy config file to config dir
|
||||
```
|
||||
cp ~/newserv/system/config.example.json ~/newservPersist/config.json
|
||||
```
|
||||
|
||||
Edit config.json
|
||||
```
|
||||
nano ~/newservPersist/config.json
|
||||
```
|
||||
Pro tip:
|
||||
Set "LocalAddress" to the static, LAN IP address of your server. If your server LAN IP is "192.168.0.10":
|
||||
"LocalAddress": "192.168.0.10",
|
||||
|
||||
Set "ExternalAddress" to the WAN IP address of your network. If your WAN IP is "8.8.8.8":
|
||||
"ExternalAddress": "8.8.8.8",
|
||||
|
||||
For Dolphin > Settings. Set SP1 to "Broadband Adapter (HLE)" Click [...] next to this, and set the DNS to the IP address of your server. Then start the game. Changes will not take affect if the game is running.
|
||||
|
||||
Docker run. Remember to change /home/changeme/newservPersist to your persistent directory. Do not use aliases such as '~'
|
||||
```
|
||||
docker run --name newserv -p 53:53/udp -p 5100:5100 -p 5110:5110 -p 5111:5111 -p 5112:5112 -p 9064:9064 -p 9100:9100 -p 9103:9103 -p 9300:9300 -p 11000:11000 -p 12000:12000 -p 12004:12004 -p 12005:12005 -v /etc/localtime:/etc/localtime:ro -v /home/changeme/newservPersist/config.json:/newserv/system/config.json -v /home/changeme/newservPersist/players:/newserv/system/players -v /home/changeme/newservPersist/teams:/newserv/system/teams -v /home/changeme/newservPersist/licenses:/newserv/system/licenses --restart no newserv:latest
|
||||
```
|
||||
|
||||
Docker run host network mode. Remember to change /home/changeme/newservPersist to your persistent directory. Do not use aliases such as '~'
|
||||
```
|
||||
docker run --net host --name newserv -v /etc/localtime:/etc/localtime:ro -v /home/changeme/newservPersist/config.json:/newserv/system/config.json -v /home/changeme/newservPersist/players:/newserv/system/players -v /home/changeme/newservPersist/teams:/newserv/system/teams -v /home/changeme/newservPersist/licenses:/newserv/system/licenses --restart no newserv:latest
|
||||
```
|
||||
|
||||
Docker compose. Remember to change /home/changeme/newservPersist to your persistent directory. Do not use aliases such as '~'
|
||||
```
|
||||
name: psonewserv
|
||||
services:
|
||||
newserv:
|
||||
container_name: newserv
|
||||
ports:
|
||||
- 53:53/udp
|
||||
- 5100:5100
|
||||
- 5110:5110
|
||||
- 5111:5111
|
||||
- 5112:5112
|
||||
- 9064:9064
|
||||
- 9100:9100
|
||||
- 9103:9103
|
||||
- 9300:9300
|
||||
- 11000:11000
|
||||
- 12000:12000
|
||||
- 12004:12004
|
||||
- 12005:12005
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /home/changeme/newservPersist/config.json:/newserv/system/config.json
|
||||
- /home/changeme/newservPersist/players:/newserv/system/players
|
||||
- /home/changeme/newservPersist/teams:/newserv/system/teams
|
||||
- /home/changeme/newservPersist/licenses:/newserv/system/licenses
|
||||
restart: no ## Set to whatever you want.
|
||||
image: newserv:latest
|
||||
```
|
||||
Docker compose host network mode. Remember to change /home/changeme/newservPersist to your persistent directory. Do not use aliases such as '~'
|
||||
```
|
||||
name: psonewserv
|
||||
services:
|
||||
newserv:
|
||||
container_name: newserv
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /home/changeme/newservPersist/config.json:/newserv/system/config.json
|
||||
- /home/changeme/newservPersist/players:/newserv/system/players
|
||||
- /home/changeme/newservPersist/teams:/newserv/system/teams
|
||||
- /home/changeme/newservPersist/licenses:/newserv/system/licenses
|
||||
restart: no ## Set to whatever you want.
|
||||
network_mode: host
|
||||
image: newserv:latest
|
||||
```
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
## General
|
||||
|
||||
- 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.)
|
||||
- Add an idle connection timeout for proxy sessions
|
||||
- Clean up ItemParameterTable implementation (see comment at the top of the class definition)
|
||||
- Handle MeetUserExtensions properly in 41 and C4 commands on the proxy (rewrite the embedded 19 command and store a map of )
|
||||
- Handle MeetUserExtensions properly in 41 and C4 commands on the proxy (rewrite the embedded 19 command and store a map of received destinations)
|
||||
|
||||
## PSO DC
|
||||
|
||||
@@ -26,7 +24,6 @@
|
||||
|
||||
## PSOBB
|
||||
|
||||
- Test all quest item subcommands
|
||||
- Figure out why Pouilly Slime EXP doesn't work
|
||||
- Make server-specified rare enemies work with maps loaded by the proxy
|
||||
- Implement serialization for various table types (ItemPMT, ItemPT, etc.)
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
This file contains client patches I've made for various versions of PSO.
|
||||
|
||||
All BB patches are for the JP 1.25.13 version (Tethealla client).
|
||||
See also https://github.com/Solybum/Blue-Burst-Patch-Project
|
||||
|
||||
(DCv2-US) Disable serial number validation (untested)
|
||||
8C1E743E 01E0
|
||||
8C2670B6 01E0
|
||||
|
||||
(BB) Disable item equip restrictions ("God of equip")
|
||||
Memory: 005C9F31 E9A7000000
|
||||
File: 001C9331 E9A7000000
|
||||
|
||||
All rareable enemies are rare (GC US v1.1)
|
||||
040AC944 60000000 // Hildeblue
|
||||
040C1B70 60000000 // Rappies
|
||||
040C3FC8 60000000 // Nar Lily
|
||||
040EB050 48000010 // Pouilly Slime
|
||||
|
||||
Unlock all songs in BGM test
|
||||
(Note: sadly, there are no secret/unused ones)
|
||||
Ep12-JP12 => 04367A68 38600001
|
||||
@@ -79,6 +98,11 @@ Ep3-US => 042F9AC0 60000000
|
||||
Ep3-NTE => 040C2C48 60000000
|
||||
Ep3-JP => 042F8B74 60000000
|
||||
|
||||
(Ep1&2 USA v1.1) Change type of all loading screens
|
||||
0401CA04 3BE0000X
|
||||
0401CA08 48000038
|
||||
Values for X: 0 = lobby/game join, 1 = quest load, 3 = pipe up, 4 = pipe down, anything else = silent black screen
|
||||
|
||||
(Ep3 USA) Replace loading screen A button sounds with random sounds
|
||||
042F9B18 4804BB19
|
||||
042F9B1C 5463063E
|
||||
@@ -259,6 +283,7 @@ Ep3-EU => 041A1C84 38600001
|
||||
Ep3-US => 041A16FC 38600001
|
||||
|
||||
(Ep3 USA) Full dressing room v1
|
||||
Original Ep1&2 code by Ralf @ GC-Forever
|
||||
Can't change your class, but you start with your existing appearance
|
||||
Go online with this code on after using the dressing room to fully save changes
|
||||
0418EB5C 60000000
|
||||
@@ -266,6 +291,7 @@ Go online with this code on after using the dressing room to fully save changes
|
||||
042A0188 387E2120
|
||||
|
||||
(Ep3 USA) Full dressing room v2
|
||||
Original Ep1&2 code by Ralf @ GC-Forever
|
||||
Can change your class, but you start with the default appearance
|
||||
Go online with this code on after using the dressing room to fully save changes
|
||||
04186ECC 4BFFFFD8
|
||||
@@ -350,3 +376,8 @@ Note: Without a TextEnglish.pr2/pr3 patch, the menu items for these sounds will
|
||||
0408E448 38000001
|
||||
0408E44C 900DA62C
|
||||
0408E450 4E800020
|
||||
|
||||
(v1.1 USA) Replace all sound effects with specified sound effect
|
||||
042256E4 3F40XXXX
|
||||
042256E8 635AYYYY
|
||||
042256EC 4800000C
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
000F04 LOGiN
|
||||
006E00 GAME MAGAZNE
|
||||
00AD00 RAGE DE FEU
|
||||
00AD01 RAGE DE FEU
|
||||
00AD02 RAGE DE FEU
|
||||
00D000 UNKNOWN3
|
||||
00D100 UNKNOWN4
|
||||
01013D KROE'S SWEATER
|
||||
01013F SONICTEAM ARMOR
|
||||
010230 HUNTER'S SHELL
|
||||
010233 HUNTER'S SHELL
|
||||
010234 HUNTER'S SHELL
|
||||
010236 Barrier
|
||||
010237 Barrier
|
||||
010238 Barrier
|
||||
010239 Barrier
|
||||
010253 BLUE RING
|
||||
010254 BLUE RING
|
||||
010255 BLUE RING
|
||||
010256 BLUE RING
|
||||
010257 BLUE RING
|
||||
010258 BLUE RING
|
||||
01025A BLUE RING
|
||||
01025B GREEN RING
|
||||
01025C GREEN RING
|
||||
01025D GREEN RING
|
||||
01025E GREEN RING
|
||||
010260 GREEN RING
|
||||
010261 GREEN RING
|
||||
010262 GREEN RING
|
||||
010263 YELLOW RING
|
||||
010264 YELLOW RING
|
||||
010265 YELLOW RING
|
||||
010267 YELLOW RING
|
||||
010268 YELLOW RING
|
||||
010269 YELLOW RING
|
||||
01026A YELLOW RING
|
||||
01026B PURPLE RING
|
||||
01026D PURPLE RING
|
||||
01026E PURPLE RING
|
||||
01026F PURPLE RING
|
||||
010270 PURPLE RING
|
||||
010271 PURPLE RING
|
||||
010272 PURPLE RING
|
||||
010274 WHITE RING
|
||||
010276 WHITE RING
|
||||
010277 WHITE RING
|
||||
010278 WHITE RING
|
||||
010279 WHITE RING
|
||||
01027A WHITE RING
|
||||
01027C BLACK RING
|
||||
01027D BLACK RING
|
||||
01027E BLACK RING
|
||||
01027F BLACK RING
|
||||
010281 BLACK RING
|
||||
01029A UNKNOWN_B
|
||||
024300 \n
|
||||
024A00 Yahoo!
|
||||
024D00 Cell of MAG 0503
|
||||
024E00 Cell of MAG 0504
|
||||
024F00 Cell of MAG 0505
|
||||
025000 Cell of MAG 0506
|
||||
025100 Cell of MAG 0507
|
||||
03120B New Year's Card
|
||||
03120C Christmas Card
|
||||
03120D Birthday Card
|
||||
03120E Proof of Sonic Team
|
||||
03120F Special Event Ticket
|
||||
03140A Bouquet
|
||||
03140B Decoction
|
||||
031603 DISK Vol.4 "Open Your Heart"
|
||||
031604 DISK Vol.5 "Live & Learn"
|
||||
031801 UNKNOWN2
|
||||
031808 Yahoo!'s engine
|
||||
03180B Cell of MAG 0503
|
||||
03180C Cell of MAG 0504
|
||||
03180D Cell of MAG 0505
|
||||
03180E Cell of MAG 0506
|
||||
03180F Cell of MAG 0507
|
||||
200000 (invalid item code)
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
entry counter flags
|
||||
|
||||
01 = rules have any non-default values
|
||||
02 = map number is set
|
||||
04 = UNKNOWN (something to do with deck selection/verification)
|
||||
08 = tournament mode (set by 6xB4x3D; shows timer in battle select menu and skips map select and rule select)
|
||||
10 = UNKNOWN (used by 6xB5x43)
|
||||
20 = command DC received
|
||||
40 = tournament result available (6xB4x51 received)
|
||||
@@ -0,0 +1,18 @@
|
||||
Ep1 Ep2
|
||||
1 Forest 1 Temple
|
||||
2 Forest 2 Temple
|
||||
3 Cave 1 Spaceship
|
||||
4 Cave 2 Spaceship
|
||||
5 Cave 3 CCA
|
||||
6 Mine 1 Jungle
|
||||
7 Mine 2 Jungle
|
||||
8 Ruins 1 (broken) Mountain
|
||||
9 Ruins 2 (broken) Seaside
|
||||
10 Ruins 3 (broken) Void (Seabed doors + Mine music)
|
||||
11 Dragon Void (doors + Dolmolm + Mine music)
|
||||
12 De Rol Le Gal Gryphon
|
||||
13 Vol Opt Olga Flow (unfinished, Flow does no damage)
|
||||
14 void (Falz music) Barba Ray (unfinished)
|
||||
15 Lobby Gol Dragon (unfinished)
|
||||
16 Versus1 crash
|
||||
17 Versus2 crash
|
||||
+925
-912
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
patch required in TethVer12513 to get this to work: 0048210D EB
|
||||
|
||||
is_hangame callsites:
|
||||
0040457C - ??? (something in TDataProtocol?)
|
||||
004820F4 - client version check (use patch above to bypass)
|
||||
00708318 - patch server domain name
|
||||
00708348 - patch server port
|
||||
0070852C - ep4 unlocked setting (always true for hangame)
|
||||
007085F4 - data server domain name
|
||||
00708670 - data server port
|
||||
007618E3 - whether to save user/pass to registry
|
||||
00761C4C - create title screen menu (only shows Start Game and Exit Game in Hangame mode)
|
||||
007623B0 - input password length limit?? (does nothing, since both branches of if statement lead to same result)
|
||||
00762530 - registry account data access
|
||||
00762708 - input password length limit?? (does nothing, since both branches of if statement lead to same result)
|
||||
0076296F - input username length limit?? (limits to 12 instead of 16)
|
||||
00762C30 - input username length limit?? (limits to 12 instead of 16)
|
||||
00762D00 - password length limit again??
|
||||
00762D2C - username length limit again??
|
||||
+41
-11
@@ -73,9 +73,9 @@ ItemLossPrevention
|
||||
*** desc=Don't lose items if\nyou don't log off\nnormally
|
||||
JP12------------- JP13------------- JP14------------- JP15------------- US10------------- US11------------- US12------------- EU--------------- DISASSEMBLY (US10)
|
||||
801D33E4 4800004C 801D38EC 4800004C 801D3CC4 4800004C 801D39B8 4800004C 801D381C 4800004C 801D381C 4800004C 801D3A1C 4800004C 801D3ED8 4800004C b +0x0000004C /* 801D3868 */
|
||||
8020010C 60000000 801FF710 60000000 801FF0FC 60000000 801FF0FC 60000000 801FFA44 60000000 801FF9E0 60000000 nop
|
||||
802016CC 60000000 80200C9C 60000000 80200658 60000000 80200658 60000000 80200FD0 60000000 80200F3C 60000000 nop
|
||||
801FD944 38000000 80202860 38000000 802021C4 38000000 802021C4 38000000 80202B94 38000000 80202AA8 38000000 li r0, 0x0000
|
||||
801FE900 60000000 801FF174 60000000 8020010C 60000000 801FF710 60000000 801FF0FC 60000000 801FF0FC 60000000 801FFA44 60000000 801FF9E0 60000000 nop
|
||||
801FFE5C 60000000 802006D0 60000000 802016CC 60000000 80200C9C 60000000 80200658 60000000 80200658 60000000 80200FD0 60000000 80200F3C 60000000 nop
|
||||
802019C8 38000000 8020223C 38000000 801FD944 38000000 80202860 38000000 802021C4 38000000 802021C4 38000000 80202B94 38000000 80202AA8 38000000 li r0, 0x0000
|
||||
802C2060 4800004C 802C2F98 4800004C 802C42E4 4800004C 802C3E78 4800004C 802C2A40 4800004C 802C2A84 4800004C 802C402C 4800004C 802C37C0 4800004C b +0x0000004C /* 802C2A8C */
|
||||
802D0AA0 48000020 802D1A58 48000020 802D2C10 48000020 802D2938 48000020 802D1480 48000020 802D14C4 48000020 802D2AEC 48000020 802D2280 48000020 b +0x00000020 /* 802D14A0 */
|
||||
|
||||
@@ -754,15 +754,45 @@ Show Enemy HP Bars
|
||||
EnemyHPBars
|
||||
*** name=Enemy HP bars
|
||||
*** desc=Show HP bars in\nenemy info windows
|
||||
JP12------------- JP13------------- JP14------------- JP15------------- US10------------- US11------------- US12------------- EU--------------- DISASSEMBLY (US10)
|
||||
JP12------------- JP13------------- JP14------------- JP15------------- US10------------- US11------------- US12------------- EU--------------- DISASSEMBLY (US12)
|
||||
802612C4 4BFE1541 80261E9C 4BFE1349 80262EE4 4BFE0665 80262C98 4BFE1241 80261B9C 4BFE1545 80261B9C 4BFE1545 80262F5C 4BFE12B1 802627A4 4BFE12B1 bl -0x0001EABC /* 802430E0 */
|
||||
804CAF00 42300000 804CE650 42300000 804D0BA0 42300000 804D0940 42300000 804CB6D0 42300000 804CBBB0 42300000 804D0218 42300000 804D0608 42300000 bdnz cr4, +0x00000000 /* 804CB6D0 */
|
||||
804CAF1C FF00FF15 804CE66C FF00FF15 804D0BBC FF00FF15 804D095C FF00FF15 804CB6EC FF00FF15 804CBBCC FF00FF15 804D0234 FF00FF15 804D0624 FF00FF15 .invalid FC, 0
|
||||
805CBFBC 42A00000 805D65BC 42A00000 805DDA5C 42A00000 805DD7FC 42A00000 805CC8C4 42A00000 805D38E4 42A00000 805DD104 42A00000 805D9344 42A00000 b +0x00000000 /* 805CC8C4 */
|
||||
804CAE40 42640000 804CE590 42640000 804D0AE0 42640000 804D0880 42640000 804CB610 42640000 804CBAF0 42640000 804D0158 42640000 804D0548 42640000 bc 19, 4, +0x00000000 /* 804CB610 */
|
||||
804CAE4C 42640000 804CE59C 42640000 804D0AEC 42640000 804D088C 42640000 804CB61C 42640000 804CBAFC 42640000 804D0164 42640000 804D0554 42640000 bc 19, 4, +0x00000000 /* 804CB61C */
|
||||
804CAE58 42640000 804CE5A8 42640000 804D0AF8 42640000 804D0898 42640000 804CB628 42640000 804CBB08 42640000 804D0170 42640000 804D0560 42640000 bc 19, 4, +0x00000000 /* 804CB628 */
|
||||
804CAE64 42640000 804CE5B4 42640000 804D0B04 42640000 804D08A4 42640000 804CB634 42640000 804CBB14 42640000 804D017C 42640000 804D056C 42640000 bc 19, 4, +0x00000000 /* 804CB634 */
|
||||
804CAF00 42780000 804CE650 42780000 804D0BA0 42780000 804D0940 42780000 804CB6D0 42780000 804CBBB0 42780000 804D0218 42780000 804D0608 42780000
|
||||
804CAF1C FF00FF15 804CE66C FF00FF15 804D0BBC FF00FF15 804D095C FF00FF15 804CB6EC FF00FF15 804CBBCC FF00FF15 804D0234 FF00FF15 804D0624 FF00FF15
|
||||
805CBFBC 42C00000 805D65BC 42C00000 805DDA5C 42C00000 805DD7FC 42C00000 805CC8C4 42C00000 805D38E4 42C00000 805DD104 42C00000 805D9344 42C00000
|
||||
804CAE40 42960000 804CE590 42960000 804D0AE0 42960000 804D0880 42960000 804CB610 42960000 804CBAF0 42960000 804D0158 42960000 804D0548 42960000
|
||||
804CAE4C 42960000 804CE59C 42960000 804D0AEC 42960000 804D088C 42960000 804CB61C 42960000 804CBAFC 42960000 804D0164 42960000 804D0554 42960000
|
||||
804CAE58 42960000 804CE5A8 42960000 804D0AF8 42960000 804D0898 42960000 804CB628 42960000 804CBB08 42960000 804D0170 42960000 804D0560 42960000
|
||||
804CAE64 42960000 804CE5B4 42960000 804D0B04 42960000 804D08A4 42960000 804CB634 42960000 804CBB14 42960000 804D017C 42960000 804D056C 42960000
|
||||
804CAE70 42960000 804CE5C0 42960000 804D0B10 42960000 804D08B0 42960000 804CB640 42960000 804CBB20 42960000 804D0188 42960000 804D0578 42960000
|
||||
80261260 4BDAA3F1 80261E38 4BDA9819 80262E80 4BDA87D1 80262C34 4BDA8A1D 80261B38 4BDA9B19 80261B38 4BDA9B19 80262EF8 4BDA8759 80262740 4BDA8F11 bl -0x002578A8 /* 8000B650 */
|
||||
80261420 4BDAA245 80261FF8 4BDA966D 80263040 4BDA8625 80262DF4 4BDA8871 80261CF8 4BDA996D 80261CF8 4BDA996D 802630B8 4BDA85AD 80262900 4BDA8D65 bl -0x00257A54 /* 8000B664 */
|
||||
8000B650 3CA08001 8000B650 3CA08001 8000B650 3CA08001 8000B650 3CA08001 8000B650 3CA08001 8000B650 3CA08001 8000B650 3CA08001 8000B650 3CA08001 lis r5, 0x8001
|
||||
8000B654 8065B6BC 8000B654 8065B6BC 8000B654 8065B6BC 8000B654 8065B6BC 8000B654 8065B6BC 8000B654 8065B6BC 8000B654 8065B6BC 8000B654 8065B6BC lwz r3, [r5 - 0x4944]
|
||||
8000B658 7FFEFB78 8000B658 7FFEFB78 8000B658 7FFEFB78 8000B658 7FFEFB78 8000B658 7FFEFB78 8000B658 7FFEFB78 8000B658 7FFEFB78 8000B658 7FFEFB78 mr r30, r31
|
||||
8000B65C A8DE032C 8000B65C A8DE032C 8000B65C A8DE032C 8000B65C A8DE032C 8000B65C A8DE032C 8000B65C A8DE032C 8000B65C A8DE032C 8000B65C A8DE032C lha r6, [r30 + 0x032C]
|
||||
8000B660 48000010 8000B660 48000010 8000B660 48000010 8000B660 48000010 8000B660 48000010 8000B660 48000010 8000B660 48000010 8000B660 48000010 b +0x00000010 /* 8000B670 */
|
||||
8000B664 A8DE02B8 8000B664 A8DE02B8 8000B664 A8DE02B8 8000B664 A8DE02B8 8000B664 A8DE02B8 8000B664 A8DE02B8 8000B664 A8DE02B8 8000B664 A8DE02B8 lha r6, [r30 + 0x02B8]
|
||||
8000B668 3CA08001 8000B668 3CA08001 8000B668 3CA08001 8000B668 3CA08001 8000B668 3CA08001 8000B668 3CA08001 8000B668 3CA08001 8000B668 3CA08001 lis r5, 0x8001
|
||||
8000B66C 9065B6BC 8000B66C 9065B6BC 8000B66C 9065B6BC 8000B66C 9065B6BC 8000B66C 9065B6BC 8000B66C 9065B6BC 8000B66C 9065B6BC 8000B66C 9065B6BC stw [r5 - 0x4944], r3
|
||||
8000B670 7C0802A6 8000B670 7C0802A6 8000B670 7C0802A6 8000B670 7C0802A6 8000B670 7C0802A6 8000B670 7C0802A6 8000B670 7C0802A6 8000B670 7C0802A6 mflr r0
|
||||
8000B674 9005B6C0 8000B674 9005B6C0 8000B674 9005B6C0 8000B674 9005B6C0 8000B674 9005B6C0 8000B674 9005B6C0 8000B674 9005B6C0 8000B674 9005B6C0 stw [r5 - 0x4940], r0
|
||||
8000B678 7C651B78 8000B678 7C651B78 8000B678 7C651B78 8000B678 7C651B78 8000B678 7C651B78 8000B678 7C651B78 8000B678 7C651B78 8000B678 7C651B78 mr r5, r3
|
||||
8000B67C A8FE02B8 8000B67C A8FE02B8 8000B67C A8FE02B8 8000B67C A8FE02B8 8000B67C A8FE02B8 8000B67C A8FE02B8 8000B67C A8FE02B8 8000B67C A8FE02B8 lha r7, [r30 + 0x02B8]
|
||||
8000B680 3C808000 8000B680 3C808000 8000B680 3C808000 8000B680 3C808000 8000B680 3C808000 8000B680 3C808000 8000B680 3C808000 8000B680 3C808000 lis r4, 0x8000
|
||||
8000B684 6084B6AC 8000B684 6084B6AC 8000B684 6084B6AC 8000B684 6084B6AC 8000B684 6084B6AC 8000B684 6084B6AC 8000B684 6084B6AC 8000B684 6084B6AC ori r4, r4, 0xB6AC
|
||||
8000B688 38640018 8000B688 38640018 8000B688 38640018 8000B688 38640018 8000B688 38640018 8000B688 38640018 8000B688 38640018 8000B688 38640018 addi r3, r4, 0x0018
|
||||
8000B68C 4CC63182 8000B68C 4CC63182 8000B68C 4CC63182 8000B68C 4CC63182 8000B68C 4CC63182 8000B68C 4CC63182 8000B68C 4CC63182 8000B68C 4CC63182 crxor crb6, crb6, crb6
|
||||
8000B690 4838A86D 8000B690 4838D275 8000B690 4838F115 8000B690 4838EEC5 8000B690 4838BB3D 8000B690 4838BB95 8000B690 4838F295 8000B690 4838DD85 bl sprintf /* 8039A924 */
|
||||
8000B694 3C808000 8000B694 3C808000 8000B694 3C808000 8000B694 3C808000 8000B694 3C808000 8000B694 3C808000 8000B694 3C808000 8000B694 3C808000 lis r4, 0x8000
|
||||
8000B698 6084B6C4 8000B698 6084B6C4 8000B698 6084B6C4 8000B698 6084B6C4 8000B698 6084B6C4 8000B698 6084B6C4 8000B698 6084B6C4 8000B698 6084B6C4 ori r4, r4, 0xB6C4
|
||||
8000B69C 7F83E378 8000B69C 7F83E378 8000B69C 7F83E378 8000B69C 7F83E378 8000B69C 7F83E378 8000B69C 7F83E378 8000B69C 7F83E378 8000B69C 7F83E378 mr r3, r28
|
||||
8000B6A0 8004FFFC 8000B6A0 8004FFFC 8000B6A0 8004FFFC 8000B6A0 8004FFFC 8000B6A0 8004FFFC 8000B6A0 8004FFFC 8000B6A0 8004FFFC 8000B6A0 8004FFFC lwz r0, [r4 - 0x0004]
|
||||
8000B6A4 7C0803A6 8000B6A4 7C0803A6 8000B6A4 7C0803A6 8000B6A4 7C0803A6 8000B6A4 7C0803A6 8000B6A4 7C0803A6 8000B6A4 7C0803A6 8000B6A4 7C0803A6 mtlr r0
|
||||
8000B6A8 4E800020 8000B6A8 4E800020 8000B6A8 4E800020 8000B6A8 4E800020 8000B6A8 4E800020 8000B6A8 4E800020 8000B6A8 4E800020 8000B6A8 4E800020 blr
|
||||
8000B6AC 25730A0A 8000B6AC 25730A0A 8000B6AC 25730A0A 8000B6AC 25730A0A 8000B6AC 25730A0A 8000B6AC 25730A0A 8000B6AC 25730A0A 8000B6AC 25730A0A .invalid
|
||||
8000B6B0 48503A25 8000B6B0 48503A25 8000B6B0 48503A25 8000B6B0 48503A25 8000B6B0 48503A25 8000B6B0 48503A25 8000B6B0 48503A25 8000B6B0 48503A25 bl +0x00503A24 /* 8050F0D4 */
|
||||
8000B6B4 642F2564 8000B6B4 642F2564 8000B6B4 642F2564 8000B6B4 642F2564 8000B6B4 642F2564 8000B6B4 642F2564 8000B6B4 642F2564 8000B6B4 642F2564 oris r15, r1, 0x2564
|
||||
8000B6B8 00000000 8000B6B8 00000000 8000B6B8 00000000 8000B6B8 00000000 8000B6B8 00000000 8000B6B8 00000000 8000B6B8 00000000 8000B6B8 00000000 .invalid
|
||||
|
||||
PSO DC Reticle Colours
|
||||
DCReticleColors
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable → Regular
Binary file not shown.
Executable → Regular
@@ -0,0 +1,165 @@
|
||||
$qfread
|
||||
|
||||
00 000003FF Garon points
|
||||
00 0003FC00 Garon button-mashing game score
|
||||
00 03FC0000 Garon timing game score
|
||||
00 04000000 Garon Tier 1 (10 cards) of Guild Card counter
|
||||
00 08000000 Garon Tier 2 (30 cards) of Guild Card counter
|
||||
00 10000000 Garon Tier 3 (50 cards) of Guild Card counter
|
||||
00 20000000 Garon Tier 4 (100 cards) of Guild Card counter
|
||||
00 C0000000 __UNUSED__
|
||||
|
||||
01 00000001 Dream Messenger: NiGHTS
|
||||
01 00000002 Pioneer Warehouse: ???
|
||||
01 00000004 Garon's Shop: Puyo Pop
|
||||
01 00000008 Pioneer Warehouse: Chu Chu Challenge
|
||||
01 00000010 Reach for the Dream: Chu Chu Puzzle
|
||||
01 00000020 Seat of the Heart: Checkpoint (Normal)
|
||||
01 00000040 Seat of the Heart: Checkpoint (Sue path)
|
||||
01 00000080 Seat of the Heart: Checkpoint (Elly is mad)
|
||||
01 00000100 Seat of the Heart: Quest complete (no Sue path)
|
||||
01 00000200 Seat of the Heart: Quest complete (Sue path)
|
||||
01 00000400 Seat of the Heart: Got Ragol Ring (Normal)
|
||||
01 00000800 Seat of the Heart: Checkpoint (Hard)
|
||||
01 00000800 White Day: ???
|
||||
01 00001000 Blue Star Memories: Future Forecast
|
||||
01 00001000 Seat of the Heart: Checkpoint (Sue path)
|
||||
01 00002000 Blue Star Memories: Future Bullet
|
||||
01 00002000 Seat of the Heart: Checkpoint (Elly is mad)
|
||||
01 00004000 Seat of the Heart: Quest complete (no Sue path)
|
||||
01 00008000 Seat of the Heart: Quest complete (Sue path)
|
||||
01 00010000 Seat of the Heart: Got Ragol Ring (Hard)
|
||||
01 00020000 Seat of the Heart: Checkpoint (Very Hard)
|
||||
01 00040000 Seat of the Heart: Checkpoint (Sue path)
|
||||
01 00080000 Seat of the Heart: Checkpoint (Elly is mad)
|
||||
01 00100000 Seat of the Heart: Quest complete (no Sue path)
|
||||
01 00200000 Seat of the Heart: Quest complete (Sue path)
|
||||
01 003F8000 Beta Lucky Coins
|
||||
01 00400000 Seat of the Heart: Got Ragol Ring (Very Hard)
|
||||
01 00800000 Seat of the Heart: Checkpoint (Ultimate)
|
||||
01 01000000 Seat of the Heart: Checkpoint (Sue path)
|
||||
01 02000000 Seat of the Heart: Checkpoint (Elly is mad)
|
||||
01 04000000 Seat of the Heart: Quest complete (no Sue path)
|
||||
01 08000000 Seat of the Heart: Quest complete (Sue path)
|
||||
01 10000000 Seat of the Heart: Got Ragol Ring (Ultimate)
|
||||
01 E0000000 __UNUSED__
|
||||
|
||||
02 00000001 Pioneer Halloween: Got Jack-O'-Lantern
|
||||
02 00000002 Pioneer Halloween: Got cake
|
||||
02 00000020 East Tower
|
||||
02 00000020 The East Tower: Paganini side quest
|
||||
02 00000040 The West Tower: Paganini side quest
|
||||
02 00000040 West Tower
|
||||
02 00000080 Labyrinthine Trial: White Ring
|
||||
02 00000100 Garon's Treachery: Rakonia Stone
|
||||
02 00000200 Garon's Treachery: Fragment of Friendship
|
||||
02 00000400 Towards the Future: Purple Ring
|
||||
02 00000800 Towards the Future: Flower Bouquet
|
||||
02 00002000 Heart Of Poumn
|
||||
02 00002000 Rappy's Holiday: Heart of Poumn
|
||||
02 003FC000 Rappy's Holiday points
|
||||
02 07000000 Respective Tomorrow: WIS
|
||||
02 08000000 Respective Tomorrow: S/SS Rank
|
||||
02 10000000 Towards the Future: Black Ring
|
||||
02 20000000 Green Ring
|
||||
02 C0C0101C __UNUSED__
|
||||
|
||||
03 000000FF Lucky Tickets
|
||||
03 003FFF00 Kill count
|
||||
03 07C00000 Song count
|
||||
03 08000000 Couple flag
|
||||
03 7FFFFFFF MA4 kills (Central Dome)
|
||||
03 80000000 __UNUSED__
|
||||
|
||||
04 7FFFFFFF MA4 kills (Gal Da Val)
|
||||
04 80000000 __UNUSED__
|
||||
|
||||
05 00007FFF Principal's Gift: Random Candy ID
|
||||
05 00008000 Candy ID init flag
|
||||
05 00200000 Racket
|
||||
05 00400000 Tree Clippers
|
||||
05 00800000 Synthesizer
|
||||
05 01000000 Shichishito
|
||||
05 02000000 Dirty Life Jacket
|
||||
05 04000000 Lost Hell Pallasch: Gush Raygun
|
||||
05 F81F0000 __UNUSED__
|
||||
|
||||
06 0FF00000 Lucky Tickets
|
||||
06 F00FFFFF __UNUSED__
|
||||
|
||||
07 00000001 Government 4-5: Normal cleared
|
||||
07 00000002 Government 4-5: Hard cleared
|
||||
07 00000004 Government 4-5: Very Hard cleared
|
||||
07 00000008 Government 4-5: Ultimate cleared
|
||||
07 00000010 Government 8-3: Normal cleared
|
||||
07 00000020 Government 8-3: Hard cleared
|
||||
07 00000040 Government 8-3: Very Hard cleared
|
||||
07 00000080 Government 8-3: Ultimate cleared
|
||||
07 FFFFFF00 __UNUSED__
|
||||
|
||||
08 7FFFFFFF MA4 kills (Crater)
|
||||
08 80000000 __UNUSED__
|
||||
|
||||
09 00003FFF MA1v2 points
|
||||
09 00003FFF Maximum Attack 1 Ver.2: points
|
||||
09 0FFFC000 MA2v2 points
|
||||
09 0FFFC000 Maximum Attack 2 Ver.2: points
|
||||
09 10000000 AOL CUP -Sunset Base- (Mag Cell)
|
||||
09 10000000 Maximum Attack 1 Ver.2: Class Master flag
|
||||
09 20000000 AOL CUP -Sunset Base- (Ruins)
|
||||
09 20000000 Maximum Attack 2 Ver.2: ID Master flag
|
||||
09 40000000 Beach Laughter: Got 5 Photon Spheres & Black Ring
|
||||
09 40000000 Blue Ring
|
||||
09 80000000 __UNUSED__
|
||||
|
||||
0A 00000001 Heart of HUmar
|
||||
0A 00000002 Heart of HUnewearl
|
||||
0A 00000004 Heart of HUcast
|
||||
0A 00000008 Heart of HUcaseal
|
||||
0A 00000010 Heart of RAmar
|
||||
0A 00000020 Heart of RAmarl
|
||||
0A 00000040 Heart of RAcast
|
||||
0A 00000080 Heart of RAcaseal
|
||||
0A 00000100 Heart of FOmar
|
||||
0A 00000200 Heart of FOmarl
|
||||
0A 00000400 Heart of FOnewm
|
||||
0A 00000800 Heart of FOnewearl
|
||||
0A 00001000 Heart of Viridia
|
||||
0A 00002000 Heart of Greenill
|
||||
0A 00004000 Heart of Skyly
|
||||
0A 00008000 Heart of Bluefull
|
||||
0A 00010000 Heart of Purplenum
|
||||
0A 00020000 Heart of Pinkal
|
||||
0A 00040000 Heart of Redria
|
||||
0A 00080000 Heart of Oran
|
||||
0A 00100000 Heart of Yellowboze
|
||||
0A 00200000 Heart of Whitill
|
||||
0A 7FC00000 Lucky Tickets
|
||||
0A 80000000 __UNUSED__
|
||||
|
||||
0B 00000001 Garon's Shop: Black Gear
|
||||
0B 00000001 Roulette (SEIRYU)
|
||||
0B 00000002 Beta -> Final Lucky Coins init flag
|
||||
0B 00000002 Roulette (GENBU)
|
||||
0B 000001FC Lucky Coins
|
||||
0B 0007FC00 Pioneer Christmas ???
|
||||
0B 00080000 Cleared 4th Pioneer Christmas tier?
|
||||
0B 1FF00000 Wrapping Papers
|
||||
0B 20000000 Pioneer Christmas Present
|
||||
0B 40000000 Wall
|
||||
0B 40000000 White Day: Flower Bouquet or Heart Key
|
||||
0B 80000200 __UNUSED__
|
||||
|
||||
0C FFFFFFFF __UNUSED__
|
||||
|
||||
0D FFFFFFFF __UNUSED__
|
||||
|
||||
0E 7FFFFFFF MA4 kills (Total)
|
||||
0E 80000000 __UNUSED__
|
||||
|
||||
0F 000000FF MA4 Tickets
|
||||
0F 00000100 MA4 PHOTON CRYSTAL
|
||||
0F 00000200 MA4 FRIEND RING
|
||||
0F 00000400 MA4 GIRASOLE
|
||||
0F 00000800 MA4 SAMURAI ARMOR
|
||||
0F FFFFF000 __UNUSED__
|
||||
@@ -0,0 +1,188 @@
|
||||
0007 = Set by rico capsule in caves
|
||||
000B = P2 Tyrell Start
|
||||
000C = P2 Irene Start
|
||||
000D = P2 Scientist 1 Start
|
||||
000E = P2 Scientist 2 Start
|
||||
000F = P2 More Scientist stuff.
|
||||
0010 = P2 Irene after talking to Tyrell
|
||||
0011 = Read a rico capsule (any)
|
||||
0012 = P2 Scientist after talking to Irene.
|
||||
0013 = P2 Menu 6, quest counter / Tekker talked to
|
||||
0014 = Entered Forest 1
|
||||
0015 = Entered Forest 2
|
||||
0016 = Entered Dragon Area
|
||||
0017 = Dragon defeated
|
||||
0018 = Caves unlocked
|
||||
0018 = P2 Principle after defeating dragon
|
||||
0019 = P2 Scientist after defeating dragon
|
||||
001E = Entered Caves 1 (Gov 2-1)
|
||||
001F = Entered De Rol Le in 2-4
|
||||
0020 = De Ro lee defeated
|
||||
0021 = Mines unlocked (P2 Tyrell after defeating De Rol Le)
|
||||
0028 = Entered Mines 1
|
||||
0029 = Entered Vol Opt Area
|
||||
002A = Defeated Vol Opt
|
||||
002B = Set by rico capsule about the 3 seals (after vol opt).
|
||||
002C = Activated Forest monument
|
||||
002D = Activated Caves monument (Gov 2-2)
|
||||
002E = Activated Mines monument
|
||||
002F = Activated all monuments
|
||||
0030 = Entered Ruins 1
|
||||
0032 = Entered Falz 1
|
||||
0035 = Hard mode unlocked
|
||||
0036 = Entered Falz 3 // Very Hard mode unlocked (?)
|
||||
0037 = Ultimate unlocked
|
||||
0046 = One CCA door lock unlocked
|
||||
0047 = One CCA door lock unlocked
|
||||
0048 = One CCA door lock unlocked
|
||||
0049 = Entered Laboratory
|
||||
004A = Lab Assistant Start
|
||||
004B = Entered Temple Beta
|
||||
004C = Defeated Barba Ray
|
||||
004D = Lab Assistant after defeating barba ray
|
||||
004E = Entered Spaceship Beta
|
||||
004F = Defeated Gol Dragon
|
||||
0051 = Entered CCA
|
||||
0052 = Defeated Gal Gyrphon // Defeated Gol dragon in seat of heart (?)
|
||||
0054 = Entered Seabed Upper
|
||||
0057 = Defeated Olga Flow
|
||||
005B = Lab Natasha Start
|
||||
005C = Lab Natasha after VR temple
|
||||
005D = Lab Natasha after VR Spaceship
|
||||
005E = Lab Assistant after defeating Gal gryphon
|
||||
005F = After reading the last capsule from flowen
|
||||
0060 = Lab Natasha after CCA
|
||||
0065 = Cleared Magnitude of Metal
|
||||
0067 = Cleared Claiming a Stake
|
||||
0069 = Cleared Value of Money
|
||||
006B = Cleared Battle Training
|
||||
006D = Cleared Journalistic Pursuit
|
||||
006F = Cleared The Fake in Yellow
|
||||
0071 = Cleared Native Research
|
||||
0073 = Cleared Forest of Sorrow
|
||||
0075 = Cleared Gran Squall
|
||||
0077 = Cleared Addicting Food
|
||||
0079 = Cleared The Lost Bride
|
||||
007B = Cleared Waterfall Tears
|
||||
007D = Cleared Black Paper
|
||||
007F = Cleared Secret Delivery
|
||||
0081 = Cleared Soul of a Blacksmith
|
||||
0083 = Cleared Letter from Lionel
|
||||
0085 = Cleared The Grave's Butler
|
||||
0087 = Cleared Knowing One's Heart
|
||||
0089 = Cleared The Retired Hunter
|
||||
008B = Cleared Dr. Osto's Research
|
||||
008D = Cleared Unsealed Door
|
||||
008F = Cleared Soul of Steel
|
||||
0091 = Cleared Doc's Secret Plan (able to make enemy part weapons)
|
||||
0093 = Cleared Seek my Master
|
||||
0095 = Cleared From the Depths
|
||||
0096 = Unknown (set in the fake in yellow)
|
||||
0097 = Seat of heart unknown
|
||||
009B = Cleared Central Dome Fire Swirl
|
||||
00A1 = Cleared Seat of the Heart
|
||||
00C9 = Got an enemy weapon converted
|
||||
00CA = unknown Fake In Yellow
|
||||
00CE = unknown Fake In Yellow
|
||||
00D3 = Dr.Osto's research black paper subplot. Told Sue your name
|
||||
00D4 = Dr.Osto's research black paper subplot. Didn't tell Sue your name from before.
|
||||
00D5 = Dr.Osto's research black paper subplot. Did tell Sue your name from before.
|
||||
00D6 = Unsealed door. black paper subplot Talked to Sue. Refused to tell her your name
|
||||
00D7 = Unsealed door. black paper subplot. bernie tells you Sue is part of black paper.
|
||||
00D8 = Black paper subplot in waterfall of tears talking to Sue
|
||||
00D9 = Black paper subplot in Black paper talking to Sue (used option 2)
|
||||
00DB = Black paper subplot in Black paper talking to Sue (used any option)
|
||||
00DE = Black paper subplot in Black paper talked to Sue at the end of quest?
|
||||
00DF = Knowing ones heart talked to Bernie?
|
||||
00E0 = Seek my master. Zoke ,Donoph subplot?
|
||||
00E2 = Bernie Gran Squall
|
||||
00E7 = Defeated Kireek in waterfall of tears
|
||||
00E8 = Black paper subplot in black paper. defeated Kireek...
|
||||
00EB = Black paper subplot in from the depths. Defeated Kireek and got soul eater!
|
||||
00F1 = Secret delivery. Started the Weapons subplot //is cleared if quest is left
|
||||
00F3 = Weapon badge approval for claiming the snake //is cleared if quest is left
|
||||
00F4 = Weapon badge approval for the lost bride //is cleared if quest is left
|
||||
00F5 = Weapon badge approval for gran squall //is cleared if quest is left
|
||||
00F6 = Secret delivery. Got AKIKO's FRYING PAN!
|
||||
00FB = Got Orochi-agito
|
||||
00FB = Received OROCHI-AGITO!
|
||||
00FD = Unknown addicting food
|
||||
0105 = Central dome fire swirl. Got Glory of the past!
|
||||
0106 = Central dome fire swirl. Got Mark3.
|
||||
0107 = Central dome fire swirl. got Sonic knuckles
|
||||
0108 = Central dome fire swirl. got mail from BOGARDE
|
||||
0109 = Central dome fire swirl. got mail from ANNA
|
||||
010A = Central dome fire swirl. got mail from NADJA
|
||||
010B = Central dome fire swirl. got mail from Lionel
|
||||
010C = Soul of the blacksmith. Got one of the 3 special weapons!
|
||||
010D = Donoph Baz dies The Retired Hunter
|
||||
010E = Seat of heart unknown
|
||||
010F = Seat of heart unknown
|
||||
0110 = Seat of heart unknown
|
||||
0111 = Seat of heart unknown
|
||||
0112 = Seat of heart unknown
|
||||
0113 = Seat of heart unknown
|
||||
0187 = Soul of steel. Got Marina's bag! //dreamcast
|
||||
0188 = Soul of steel. Unknown.
|
||||
0191 = Capsule Elly VR
|
||||
0197 = Cleared VR Temple
|
||||
01AD = Capsule elly CCA
|
||||
01AE = Capsule elly CCA
|
||||
01B3 = After reading a capsule from flowen
|
||||
01D6 = Set after unlocking vr spaceship
|
||||
01F5 = Episode1: Cleared government 1-1
|
||||
01F7 = Episode1: Cleared government 1-2
|
||||
01F9 = Episode1: Cleared government 1-3
|
||||
01FB = Episode1: Cleared government 2-1
|
||||
01FD = Episode1: Cleared government 2-2
|
||||
01FF = Episode1: Cleared government 2-3
|
||||
0201 = Episode1: Cleared government 2-4
|
||||
0203 = Episode1: Cleared government 3-1
|
||||
0205 = Episode1: Cleared government 3-2
|
||||
0207 = Episode1: Cleared government 3-3
|
||||
0209 = Episode1: Cleared government 4-1
|
||||
020B = Episode1: Cleared government 4-2
|
||||
020D = Episode1: Cleared government 4-3
|
||||
020F = Episode1: Cleared government 4-4
|
||||
0211 = Episode1: Cleared government 4-5
|
||||
0213 = Episode2: Cleared government 5-1 // Talked to Tekker (?)
|
||||
0214 = Entered Forest 1
|
||||
0215 = Episode2: Cleared government 5-2
|
||||
0217 = Episode2: Cleared government 5-3 // Defeated Dragon (?)
|
||||
0219 = Episode2: Cleared government 5-4
|
||||
021B = Episode2: Cleared government 5-5
|
||||
021D = Episode2: Cleared government 6-1
|
||||
021F = Episode2: Cleared government 6-2
|
||||
0220 = Defeated De Rol Le
|
||||
0221 = Episode2: Cleared government 6-3
|
||||
0223 = Episode2: Cleared government 6-4
|
||||
0225 = Episode2: Cleared government 6-5
|
||||
0227 = Episode2: Cleared government 7-1
|
||||
0229 = Episode2: Cleared government 7-2
|
||||
022A = Defeated Vol Opt (002A and 022A together on hard mode)
|
||||
022B = Episode2: Cleared government 7-3 // Rico capsule after Vol Opt, at Ruins door (?)
|
||||
022D = Episode2: Cleared government 7-4 // Entered Caves 2 (?)
|
||||
022F = Episode2: Cleared government 7-5
|
||||
0230 = Entered Ruins 1
|
||||
0231 = Episode2: Cleared government 8-1
|
||||
0233 = Episode2: Cleared government 8-2
|
||||
0234 = Entered Falz 2
|
||||
0235 = Episode2: Cleared government 8-3
|
||||
0246 = Activated Jungle East big door switch
|
||||
0248 = Activated Seaside big door switch
|
||||
024F = Defeated Gol Dragon
|
||||
0252 = Defeated Gal Gryphon
|
||||
02BD = Episode4: Cleared government 9-1
|
||||
02BE = Episode4: Cleared government 9-2
|
||||
02BF = Episode4: Cleared government 9-3
|
||||
02C0 = Episode4: Cleared government 9-4
|
||||
02C1 = Episode4: Cleared government 9-5
|
||||
02C2 = Episode4: Cleared government 9-6
|
||||
02C3 = Episode4: Cleared government 9-7
|
||||
02C4 = Episode4: Cleared government 9-8
|
||||
0314 = Entered Forest 1
|
||||
0330 = Entered Ruins 1
|
||||
03FA = P2 Menu 7, G-Counter // Talked to Momoka
|
||||
03FB = Nol start
|
||||
03FC = Cleared Ep2 government on ultimate
|
||||
03FE = Cleared Ep2 government on normal-vh
|
||||
@@ -0,0 +1,21 @@
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
|
||||
async def main():
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.ws_connect("ws://localhost:5050/y/rare-drops/stream") as ws:
|
||||
async for msg in ws:
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
data = msg.json()
|
||||
print(f"Received message: {data}")
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
print(f"Received binary data: {msg.data}")
|
||||
elif msg.type == aiohttp.WSMsgType.CLOSE:
|
||||
break
|
||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,27 @@
|
||||
### T FLAG NAME REQUIREMENTS AVAILABLE_IF ENABLED_IF
|
||||
001 1 0065 Magnitude of Metal !F_0065 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
002 1 0067 Claiming A Stake !F_0067 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
003 3 0069 The Value of Money T1, Caves F_0065 && F_0067 && F_006B && F_01F9 !F_0069 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
004 1 006B Battle Training !F_006B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
005 2 006D Journalistic Pursuit T1 F_0065 && F_0067 && F_006B !F_006D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
006 2 006F The Fake in yellow T1 F_0065 && F_0067 && F_006B !F_006F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
007 2 0071 Native Research T1 F_0065 && F_0067 && F_006B !F_0071 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
008 2 0073 Forest of Sorrow 007 F_0071 !F_0073 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
009 2 0075 Gran Squall T1 F_0065 && F_0067 && F_006B !F_0075 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
010 3 0077 Addicting Food T1 F_0065 && F_0067 && F_006B !F_0077 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
011 3 0079 The Lost Bride T1 F_0065 && F_0067 && F_006B !F_0079 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
012 3 007B Waterfall tears 010, 011, 014, 017 F_0077 && F_0079 && F_007F && F_0085 !F_007B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
013 3 007D Black Paper 012 F_007B !F_007D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
014 3 007F Secret Delivery T1 F_0065 && F_0067 && F_006B !F_007F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
015 3 0081 Soul of a Blacksmith 010, 011, 014, 017 F_0077 && F_0079 && F_007F && F_0085 !F_0081 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
016 3 0083 Letter from Lionel T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_0083 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
017 3 0085 The Grave's Butler T1 F_0065 && F_0067 && F_006B !F_0085 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
018 4 0087 Knowing One's Heart T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_0087 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
019 5 0089 Retired Hunter T1, Ruins F_0065 && F_0067 && F_006B && F_0207 !F_0089 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
020 4 008B Dr. Osto's Research T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_008B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
021 4 008D The Unsealed Door 020, 014 F_008B && F_007F !F_008D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
022 5 008F Soul of Steel 023 F_0091 !F_008F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
023 5 0091 Doc's Secret Plan 014, Ruins F_007F && F_0207 !F_0091 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
024 5 0093 Seek My Master T1, Ruins F_0065 && F_0067 && F_006B && F_0207 !F_0093 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
025 5 0095 From the Depths 023 F_0091 !F_0095 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||
026 2 009B Central Dome Fire Swirl 008 F_0073
|
||||
+8
-10
@@ -23,7 +23,7 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
|
||||
le_uint32_t size;
|
||||
} __packed_ws__(FileEntry, 8);
|
||||
|
||||
StringReader r(*this->data);
|
||||
phosg::StringReader r(*this->data);
|
||||
const auto& header = r.get<FileHeader>();
|
||||
if (header.magic != 0x41465300) { // 'AFS\0'
|
||||
throw runtime_error("file is not an AFS archive");
|
||||
@@ -52,29 +52,27 @@ string AFSArchive::get_copy(size_t index) const {
|
||||
return string(reinterpret_cast<const char*>(ret.first), ret.second);
|
||||
}
|
||||
|
||||
StringReader AFSArchive::get_reader(size_t index) const {
|
||||
phosg::StringReader AFSArchive::get_reader(size_t index) const {
|
||||
auto ret = this->get(index);
|
||||
return StringReader(ret.first, ret.second);
|
||||
return phosg::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>
|
||||
template <bool BE>
|
||||
string AFSArchive::generate_t(const vector<string>& files) {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(0x41465300); // 'AFS\0'
|
||||
w.put<U32T>(files.size());
|
||||
w.put<U32T<BE>>(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());
|
||||
w.put<U32T<BE>>(data_offset);
|
||||
w.put<U32T<BE>>(file.size());
|
||||
data_offset = (data_offset + file.size() + 0x7FF) & (~0x7FF);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -8,6 +8,8 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Types.hh"
|
||||
|
||||
class AFSArchive {
|
||||
public:
|
||||
AFSArchive(std::shared_ptr<const std::string> data);
|
||||
@@ -23,12 +25,12 @@ public:
|
||||
|
||||
std::pair<const void*, size_t> get(size_t index) const;
|
||||
std::string get_copy(size_t index) const;
|
||||
StringReader get_reader(size_t index) const;
|
||||
phosg::StringReader get_reader(size_t index) const;
|
||||
|
||||
static std::string generate(const std::vector<std::string>& files, bool big_endian);
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
static std::string generate_t(const std::vector<std::string>& files);
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
|
||||
+80
-64
@@ -11,7 +11,7 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
shared_ptr<DCNTELicense> DCNTELicense::from_json(const JSON& json) {
|
||||
shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<DCNTELicense>();
|
||||
ret->serial_number = json.get_string("SerialNumber");
|
||||
ret->access_key = json.get_string("AccessKey");
|
||||
@@ -30,14 +30,14 @@ shared_ptr<DCNTELicense> DCNTELicense::from_json(const JSON& json) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON DCNTELicense::json() const {
|
||||
return JSON::dict({
|
||||
phosg::JSON DCNTELicense::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"SerialNumber", this->serial_number},
|
||||
{"AccessKey", this->access_key},
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<V1V2License> V1V2License::from_json(const JSON& json) {
|
||||
shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<V1V2License>();
|
||||
ret->serial_number = json.get_int("SerialNumber");
|
||||
ret->access_key = json.get_string("AccessKey");
|
||||
@@ -50,14 +50,14 @@ shared_ptr<V1V2License> V1V2License::from_json(const JSON& json) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON V1V2License::json() const {
|
||||
return JSON::dict({
|
||||
phosg::JSON V1V2License::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"SerialNumber", this->serial_number},
|
||||
{"AccessKey", this->access_key},
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<GCLicense> GCLicense::from_json(const JSON& json) {
|
||||
shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<GCLicense>();
|
||||
ret->serial_number = json.get_int("SerialNumber");
|
||||
ret->access_key = json.get_string("AccessKey");
|
||||
@@ -74,15 +74,15 @@ shared_ptr<GCLicense> GCLicense::from_json(const JSON& json) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON GCLicense::json() const {
|
||||
return JSON::dict({
|
||||
phosg::JSON GCLicense::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"SerialNumber", this->serial_number},
|
||||
{"AccessKey", this->access_key},
|
||||
{"Password", this->password},
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<XBLicense> XBLicense::from_json(const JSON& json) {
|
||||
shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<XBLicense>();
|
||||
ret->gamertag = json.get_string("GamerTag");
|
||||
ret->user_id = json.get_int("UserID");
|
||||
@@ -99,15 +99,15 @@ shared_ptr<XBLicense> XBLicense::from_json(const JSON& json) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON XBLicense::json() const {
|
||||
return JSON::dict({
|
||||
phosg::JSON XBLicense::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"GamerTag", this->gamertag},
|
||||
{"UserID", this->user_id},
|
||||
{"AccountID", this->account_id},
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<BBLicense> BBLicense::from_json(const JSON& json) {
|
||||
shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<BBLicense>();
|
||||
ret->username = json.get_string("UserName");
|
||||
ret->password = json.get_string("Password");
|
||||
@@ -126,16 +126,17 @@ shared_ptr<BBLicense> BBLicense::from_json(const JSON& json) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON BBLicense::json() const {
|
||||
return JSON::dict({
|
||||
phosg::JSON BBLicense::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"UserName", this->username},
|
||||
{"Password", this->password},
|
||||
});
|
||||
}
|
||||
|
||||
Account::Account(const JSON& json)
|
||||
Account::Account(const phosg::JSON& json)
|
||||
: account_id(0),
|
||||
flags(0),
|
||||
user_flags(0),
|
||||
ban_end_time(0),
|
||||
ep3_current_meseta(0),
|
||||
ep3_total_meseta_earned(0),
|
||||
@@ -222,6 +223,7 @@ Account::Account(const JSON& json)
|
||||
}
|
||||
|
||||
this->flags = json.get_int("Flags", 0);
|
||||
this->user_flags = json.get_int("UserFlags", 0);
|
||||
this->ban_end_time = json.get_int("BanEndTime", 0);
|
||||
this->last_player_name = json.get_string("LastPlayerName", "");
|
||||
this->auto_reply_message = json.get_string("AutoReplyMessage", "");
|
||||
@@ -237,38 +239,38 @@ Account::Account(const JSON& json)
|
||||
}
|
||||
}
|
||||
|
||||
JSON Account::json() const {
|
||||
JSON dc_nte_json = JSON::list();
|
||||
phosg::JSON Account::json() const {
|
||||
phosg::JSON dc_nte_json = phosg::JSON::list();
|
||||
for (const auto& it : this->dc_nte_licenses) {
|
||||
dc_nte_json.emplace_back(it.second->json());
|
||||
}
|
||||
JSON dc_json = JSON::list();
|
||||
phosg::JSON dc_json = phosg::JSON::list();
|
||||
for (const auto& it : this->dc_licenses) {
|
||||
dc_json.emplace_back(it.second->json());
|
||||
}
|
||||
JSON pc_json = JSON::list();
|
||||
phosg::JSON pc_json = phosg::JSON::list();
|
||||
for (const auto& it : this->pc_licenses) {
|
||||
pc_json.emplace_back(it.second->json());
|
||||
}
|
||||
JSON gc_json = JSON::list();
|
||||
phosg::JSON gc_json = phosg::JSON::list();
|
||||
for (const auto& it : this->gc_licenses) {
|
||||
gc_json.emplace_back(it.second->json());
|
||||
}
|
||||
JSON xb_json = JSON::list();
|
||||
phosg::JSON xb_json = phosg::JSON::list();
|
||||
for (const auto& it : this->xb_licenses) {
|
||||
xb_json.emplace_back(it.second->json());
|
||||
}
|
||||
JSON bb_json = JSON::list();
|
||||
phosg::JSON bb_json = phosg::JSON::list();
|
||||
for (const auto& it : this->bb_licenses) {
|
||||
bb_json.emplace_back(it.second->json());
|
||||
}
|
||||
|
||||
JSON auto_patches_json = JSON::list();
|
||||
phosg::JSON auto_patches_json = phosg::JSON::list();
|
||||
for (const auto& it : this->auto_patches_enabled) {
|
||||
auto_patches_json.emplace_back(it);
|
||||
}
|
||||
|
||||
return JSON::dict({
|
||||
return phosg::JSON::dict({
|
||||
{"FormatVersion", 1},
|
||||
{"AccountID", this->account_id},
|
||||
{"DCNTELicenses", std::move(dc_nte_json)},
|
||||
@@ -278,6 +280,7 @@ JSON Account::json() const {
|
||||
{"XBLicenses", std::move(xb_json)},
|
||||
{"BBLicenses", std::move(bb_json)},
|
||||
{"Flags", this->flags},
|
||||
{"UserFlags", this->user_flags},
|
||||
{"BanEndTime", this->ban_end_time},
|
||||
{"LastPlayerName", this->last_player_name},
|
||||
{"AutoReplyMessage", this->auto_reply_message},
|
||||
@@ -300,47 +303,60 @@ void Account::print(FILE* stream) const {
|
||||
} else if (this->flags == static_cast<uint32_t>(Flag::MODERATOR)) {
|
||||
flags_str = "MODERATOR";
|
||||
} else {
|
||||
if (this->flags & static_cast<uint32_t>(Flag::KICK_USER)) {
|
||||
if (this->check_flag(Flag::KICK_USER)) {
|
||||
flags_str += "KICK_USER,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::BAN_USER)) {
|
||||
if (this->check_flag(Flag::BAN_USER)) {
|
||||
flags_str += "BAN_USER,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::SILENCE_USER)) {
|
||||
if (this->check_flag(Flag::SILENCE_USER)) {
|
||||
flags_str += "SILENCE_USER,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::CHANGE_EVENT)) {
|
||||
if (this->check_flag(Flag::CHANGE_EVENT)) {
|
||||
flags_str += "CHANGE_EVENT,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::ANNOUNCE)) {
|
||||
if (this->check_flag(Flag::ANNOUNCE)) {
|
||||
flags_str += "ANNOUNCE,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::FREE_JOIN_GAMES)) {
|
||||
if (this->check_flag(Flag::FREE_JOIN_GAMES)) {
|
||||
flags_str += "FREE_JOIN_GAMES,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::DEBUG)) {
|
||||
if (this->check_flag(Flag::DEBUG)) {
|
||||
flags_str += "DEBUG,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::CHEAT_ANYWHERE)) {
|
||||
if (this->check_flag(Flag::CHEAT_ANYWHERE)) {
|
||||
flags_str += "CHEAT_ANYWHERE,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||
if (this->check_flag(Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||
flags_str += "ALWAYS_ENABLE_CHAT_COMMANDS,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::IS_SHARED_ACCOUNT)) {
|
||||
if (this->check_flag(Flag::IS_SHARED_ACCOUNT)) {
|
||||
flags_str += "IS_SHARED_ACCOUNT,";
|
||||
}
|
||||
}
|
||||
if (flags_str.empty()) {
|
||||
flags_str = "none";
|
||||
} else if (ends_with(flags_str, ",")) {
|
||||
} else if (phosg::ends_with(flags_str, ",")) {
|
||||
flags_str.pop_back();
|
||||
}
|
||||
fprintf(stream, " Flags: %08" PRIX32 " (%s)\n", this->flags, flags_str.c_str());
|
||||
}
|
||||
|
||||
if (this->user_flags) {
|
||||
string user_flags_str = "";
|
||||
if (this->check_user_flag(UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST)) {
|
||||
user_flags_str += "DISABLE_DROP_NOTIFICATION_BROADCAST,";
|
||||
}
|
||||
if (user_flags_str.empty()) {
|
||||
user_flags_str = "none";
|
||||
} else if (phosg::ends_with(user_flags_str, ",")) {
|
||||
user_flags_str.pop_back();
|
||||
}
|
||||
fprintf(stream, " User flags: %08" PRIX32 " (%s)\n", this->user_flags, user_flags_str.c_str());
|
||||
}
|
||||
|
||||
if (this->ban_end_time) {
|
||||
string time_str = format_time(this->ban_end_time);
|
||||
string time_str = phosg::format_time(this->ban_end_time);
|
||||
fprintf(stream, " Banned until: %" PRIu64 " (%s)\n", this->ban_end_time, time_str.c_str());
|
||||
}
|
||||
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
||||
@@ -388,14 +404,14 @@ void Account::print(FILE* stream) const {
|
||||
void Account::save() const {
|
||||
if (!this->is_temporary) {
|
||||
auto json = this->json();
|
||||
string json_data = json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS);
|
||||
string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->account_id);
|
||||
save_file(filename, json_data);
|
||||
string json_data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS);
|
||||
string filename = phosg::string_printf("system/licenses/%010" PRIu32 ".json", this->account_id);
|
||||
phosg::save_file(filename, json_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Account::delete_file() const {
|
||||
string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->account_id);
|
||||
string filename = phosg::string_printf("system/licenses/%010" PRIu32 ".json", this->account_id);
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
@@ -420,8 +436,8 @@ shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const string& ser
|
||||
if (login->dc_nte_license->access_key != access_key) {
|
||||
throw incorrect_access_key();
|
||||
}
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= now())) {
|
||||
throw invalid_argument("user is banned");
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
throw account_banned();
|
||||
}
|
||||
return login;
|
||||
}
|
||||
@@ -448,7 +464,7 @@ shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
||||
auto login = make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = fnv1a32(serial_number) & 0x7FFFFFFF;
|
||||
login->account->account_id = phosg::fnv1a32(serial_number) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<DCNTELicense>();
|
||||
lic->serial_number = serial_number;
|
||||
lic->access_key = access_key;
|
||||
@@ -470,8 +486,8 @@ shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
||||
if (!is_shared && (login->dc_license->access_key != access_key)) {
|
||||
throw incorrect_access_key();
|
||||
}
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= now())) {
|
||||
throw invalid_argument("user is banned");
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
throw account_banned();
|
||||
}
|
||||
if (is_shared) {
|
||||
login->account = this->create_temporary_account_for_shared_account(login->account, access_key + ":" + character_name);
|
||||
@@ -519,7 +535,7 @@ shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_numb
|
||||
throw missing_account();
|
||||
}
|
||||
if (guild_card_number == 0xFFFFFFFF) {
|
||||
guild_card_number = random_object<uint32_t>() & 0x7FFFFFFF;
|
||||
guild_card_number = phosg::random_object<uint32_t>() & 0x7FFFFFFF;
|
||||
}
|
||||
auto login = make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
@@ -543,8 +559,8 @@ shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
||||
if (!is_shared && (login->pc_license->access_key != access_key)) {
|
||||
throw incorrect_access_key();
|
||||
}
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= now())) {
|
||||
throw invalid_argument("user is banned");
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
throw account_banned();
|
||||
}
|
||||
if (is_shared) {
|
||||
login->account = this->create_temporary_account_for_shared_account(login->account, access_key + ":" + character_name);
|
||||
@@ -599,8 +615,8 @@ shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
||||
if (password && (login->gc_license->password != *password)) {
|
||||
throw incorrect_password();
|
||||
}
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= now())) {
|
||||
throw invalid_argument("user is banned");
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
throw account_banned();
|
||||
}
|
||||
if (is_shared) {
|
||||
login->account = this->create_temporary_account_for_shared_account(login->account, access_key + ":" + character_name);
|
||||
@@ -652,8 +668,8 @@ shared_ptr<Login> AccountIndex::from_xb_credentials_locked(const string& gamerta
|
||||
(login->xb_license->account_id && (login->xb_license->account_id != account_id))) {
|
||||
throw incorrect_access_key();
|
||||
}
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= now())) {
|
||||
throw invalid_argument("user is banned");
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
throw account_banned();
|
||||
}
|
||||
return login;
|
||||
}
|
||||
@@ -680,7 +696,7 @@ shared_ptr<Login> AccountIndex::from_xb_credentials(
|
||||
auto login = make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = fnv1a32(gamertag) & 0x7FFFFFFF;
|
||||
login->account->account_id = phosg::fnv1a32(gamertag) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<XBLicense>();
|
||||
lic->gamertag = gamertag;
|
||||
lic->user_id = user_id;
|
||||
@@ -701,8 +717,8 @@ shared_ptr<Login> AccountIndex::from_bb_credentials_locked(const string& usernam
|
||||
if (password && (login->bb_license->password != *password)) {
|
||||
throw incorrect_password();
|
||||
}
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= now())) {
|
||||
throw invalid_argument("user is banned");
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
throw account_banned();
|
||||
}
|
||||
return login;
|
||||
}
|
||||
@@ -728,7 +744,7 @@ shared_ptr<Login> AccountIndex::from_bb_credentials(const string& username, cons
|
||||
auto login = make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = fnv1a32(username) & 0x7FFFFFFF;
|
||||
login->account->account_id = phosg::fnv1a32(username) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<BBLicense>();
|
||||
lic->username = username;
|
||||
lic->password = *password;
|
||||
@@ -976,24 +992,24 @@ shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
|
||||
shared_ptr<const Account> src_a, const string& variation_data) const {
|
||||
auto ret = make_shared<Account>(*src_a);
|
||||
ret->is_temporary = true;
|
||||
ret->account_id = fnv1a32(&src_a->account_id, sizeof(src_a->account_id));
|
||||
ret->account_id = fnv1a32(variation_data, ret->account_id);
|
||||
ret->account_id = phosg::fnv1a32(&src_a->account_id, sizeof(src_a->account_id));
|
||||
ret->account_id = phosg::fnv1a32(variation_data, ret->account_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
AccountIndex::AccountIndex(bool force_all_temporary)
|
||||
: force_all_temporary(force_all_temporary) {
|
||||
if (!this->force_all_temporary) {
|
||||
if (!isdir("system/licenses")) {
|
||||
if (!phosg::isdir("system/licenses")) {
|
||||
mkdir("system/licenses", 0755);
|
||||
} else {
|
||||
for (const auto& item : list_directory("system/licenses")) {
|
||||
if (ends_with(item, ".json")) {
|
||||
for (const auto& item : phosg::list_directory("system/licenses")) {
|
||||
if (phosg::ends_with(item, ".json")) {
|
||||
try {
|
||||
JSON json = JSON::parse(load_file("system/licenses/" + item));
|
||||
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + item));
|
||||
this->add(make_shared<Account>(json));
|
||||
} catch (const exception& e) {
|
||||
log_error("Failed to index account %s", item.c_str());
|
||||
phosg::log_error("Failed to index account %s", item.c_str());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-12
@@ -16,16 +16,16 @@ struct DCNTELicense {
|
||||
std::string serial_number;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
static std::shared_ptr<DCNTELicense> from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
|
||||
struct V1V2License {
|
||||
uint32_t serial_number = 0;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<V1V2License> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
static std::shared_ptr<V1V2License> from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
|
||||
struct GCLicense {
|
||||
@@ -33,8 +33,8 @@ struct GCLicense {
|
||||
std::string access_key;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<GCLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
static std::shared_ptr<GCLicense> from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
|
||||
struct XBLicense {
|
||||
@@ -42,16 +42,16 @@ struct XBLicense {
|
||||
uint64_t user_id = 0;
|
||||
uint64_t account_id = 0;
|
||||
|
||||
static std::shared_ptr<XBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
static std::shared_ptr<XBLicense> from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
|
||||
struct BBLicense {
|
||||
std::string username;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<BBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
static std::shared_ptr<BBLicense> from_json(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
|
||||
struct Account {
|
||||
@@ -76,11 +76,15 @@ struct Account {
|
||||
UNUSED_BITS = 0x70FFFF00,
|
||||
// clang-format on
|
||||
};
|
||||
enum class UserFlag : uint32_t {
|
||||
DISABLE_DROP_NOTIFICATION_BROADCAST = 0x00000001,
|
||||
};
|
||||
|
||||
// account_id is also the account's guild card number
|
||||
uint32_t account_id = 0;
|
||||
|
||||
uint32_t flags = 0;
|
||||
uint32_t user_flags = 0;
|
||||
uint64_t ban_end_time = 0; // 0 = not banned
|
||||
std::string last_player_name;
|
||||
std::string auto_reply_message;
|
||||
@@ -101,10 +105,10 @@ struct Account {
|
||||
std::unordered_map<std::string, std::shared_ptr<BBLicense>> bb_licenses;
|
||||
|
||||
Account() = default;
|
||||
explicit Account(const JSON& json);
|
||||
explicit Account(const phosg::JSON& json);
|
||||
virtual ~Account() = default;
|
||||
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
virtual void save() const;
|
||||
virtual void delete_file() const;
|
||||
|
||||
@@ -124,6 +128,19 @@ struct Account {
|
||||
this->flags = static_cast<uint32_t>(mask);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool check_user_flag(UserFlag flag) const {
|
||||
return !!(this->user_flags & static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void set_user_flag(UserFlag flag) {
|
||||
this->user_flags |= static_cast<uint32_t>(flag);
|
||||
}
|
||||
inline void clear_user_flag(UserFlag flag) {
|
||||
this->user_flags &= (~static_cast<uint32_t>(flag));
|
||||
}
|
||||
inline void toggle_user_flag(UserFlag flag) {
|
||||
this->user_flags ^= static_cast<uint32_t>(flag);
|
||||
}
|
||||
|
||||
void print(FILE* stream) const;
|
||||
};
|
||||
|
||||
@@ -159,6 +176,10 @@ public:
|
||||
public:
|
||||
missing_account() : invalid_argument("missing account") {}
|
||||
};
|
||||
class account_banned : public std::invalid_argument {
|
||||
public:
|
||||
account_banned() : invalid_argument("account is banned") {}
|
||||
};
|
||||
|
||||
explicit AccountIndex(bool force_all_temporary);
|
||||
virtual ~AccountIndex() = default;
|
||||
|
||||
+25
-25
@@ -108,32 +108,32 @@ public:
|
||||
: log("[addr-trans] "),
|
||||
directory(directory),
|
||||
enable_ppc(false) {
|
||||
while (ends_with(this->directory, "/")) {
|
||||
while (phosg::ends_with(this->directory, "/")) {
|
||||
this->directory.pop_back();
|
||||
}
|
||||
for (const auto& filename : list_directory(this->directory)) {
|
||||
if (ends_with(filename, ".dol")) {
|
||||
for (const auto& filename : phosg::list_directory(this->directory)) {
|
||||
if (phosg::ends_with(filename, ".dol")) {
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
string path = directory + "/" + filename;
|
||||
DOLFile dol(path.c_str());
|
||||
auto mem = make_shared<MemoryContext>();
|
||||
ResourceDASM::DOLFile dol(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
dol.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->enable_ppc = true;
|
||||
this->log.info("Loaded %s", name.c_str());
|
||||
} else if (ends_with(filename, ".xbe")) {
|
||||
} else if (phosg::ends_with(filename, ".xbe")) {
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
string path = directory + "/" + filename;
|
||||
XBEFile xbe(path.c_str());
|
||||
auto mem = make_shared<MemoryContext>();
|
||||
ResourceDASM::XBEFile xbe(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
xbe.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->log.info("Loaded %s", name.c_str());
|
||||
} else if (ends_with(filename, ".bin")) {
|
||||
} else if (phosg::ends_with(filename, ".bin")) {
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
string path = directory + "/" + filename;
|
||||
string data = load_file(path);
|
||||
auto mem = make_shared<MemoryContext>();
|
||||
string data = phosg::load_file(path);
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
mem->allocate_at(0x8C010000, data.size());
|
||||
mem->memcpy(0x8C010000, data.data(), data.size());
|
||||
this->mems.emplace(name, mem);
|
||||
@@ -160,7 +160,7 @@ public:
|
||||
uint32_t r2 = 0;
|
||||
uint32_t r13 = 0;
|
||||
for (const auto& block : it.second->allocated_blocks()) {
|
||||
StringReader r = it.second->reader(block.first, block.second);
|
||||
phosg::StringReader r = it.second->reader(block.first, block.second);
|
||||
while (!r.eof() && r.where()) {
|
||||
uint32_t opcode = r.get_u32b();
|
||||
if ((opcode & 0xFFFF0000) == 0x3DA00000) {
|
||||
@@ -204,7 +204,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t find_match(
|
||||
shared_ptr<const MemoryContext> dest_mem,
|
||||
shared_ptr<const ResourceDASM::MemoryContext> dest_mem,
|
||||
uint32_t src_addr,
|
||||
uint32_t src_size,
|
||||
ExpandMethod expand_method) const {
|
||||
@@ -243,13 +243,13 @@ public:
|
||||
size_t num_matches = 0;
|
||||
size_t last_match_address = 0;
|
||||
size_t match_length = match_bytes_before + match_bytes_after + 4;
|
||||
StringReader src_r = this->src_mem->reader(src_section.first + src_offset - match_bytes_before, match_length);
|
||||
phosg::StringReader src_r = this->src_mem->reader(src_section.first + src_offset - match_bytes_before, match_length);
|
||||
for (const auto& dest_section : dest_mem->allocated_blocks()) {
|
||||
for (size_t dest_match_offset = 0;
|
||||
dest_match_offset + match_length < dest_section.second;
|
||||
dest_match_offset += (is_ppc ? 4 : 1)) {
|
||||
src_r.go(0);
|
||||
StringReader dest_r = dest_mem->reader(dest_section.first + dest_match_offset, match_length);
|
||||
phosg::StringReader dest_r = dest_mem->reader(dest_section.first + dest_match_offset, match_length);
|
||||
size_t z;
|
||||
if (is_ppc) {
|
||||
for (z = 0; z < match_length; z += 4) {
|
||||
@@ -429,11 +429,11 @@ public:
|
||||
}
|
||||
|
||||
void handle_command(const string& command) {
|
||||
auto tokens = split(command, ' ');
|
||||
auto tokens = phosg::split(command, ' ');
|
||||
if (tokens.empty()) {
|
||||
throw runtime_error("no command given");
|
||||
}
|
||||
strip_trailing_whitespace(tokens[tokens.size() - 1]);
|
||||
phosg::strip_trailing_whitespace(tokens[tokens.size() - 1]);
|
||||
|
||||
if (tokens[0] == "use") {
|
||||
this->set_source_file(tokens.at(1));
|
||||
@@ -457,7 +457,7 @@ public:
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
string command = fgets(stdin);
|
||||
string command = phosg::fgets(stdin);
|
||||
try {
|
||||
this->handle_command(command);
|
||||
} catch (const exception& e) {
|
||||
@@ -468,11 +468,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
PrefixedLogger log;
|
||||
phosg::PrefixedLogger log;
|
||||
string directory;
|
||||
unordered_map<string, shared_ptr<const MemoryContext>> mems;
|
||||
unordered_map<string, shared_ptr<const ResourceDASM::MemoryContext>> mems;
|
||||
string src_filename;
|
||||
shared_ptr<const MemoryContext> src_mem;
|
||||
shared_ptr<const ResourceDASM::MemoryContext> src_mem;
|
||||
bool enable_ppc;
|
||||
};
|
||||
|
||||
@@ -490,10 +490,10 @@ void run_address_translator(const std::string& directory, const std::string& use
|
||||
}
|
||||
|
||||
vector<pair<uint32_t, string>> diff_dol_files(const string& a_filename, const string& b_filename) {
|
||||
DOLFile a(a_filename.c_str());
|
||||
DOLFile b(b_filename.c_str());
|
||||
auto a_mem = make_shared<MemoryContext>();
|
||||
auto b_mem = make_shared<MemoryContext>();
|
||||
ResourceDASM::DOLFile a(a_filename.c_str());
|
||||
ResourceDASM::DOLFile b(b_filename.c_str());
|
||||
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
a.load_into(a_mem);
|
||||
b.load_into(b_mem);
|
||||
|
||||
|
||||
+14
-17
@@ -5,15 +5,14 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
struct BMLHeaderT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
parray<uint8_t, 0x04> unknown_a1;
|
||||
U32T num_entries;
|
||||
U32T<BE> num_entries;
|
||||
parray<uint8_t, 0x38> unknown_a2;
|
||||
} __packed__;
|
||||
|
||||
@@ -22,16 +21,14 @@ using BMLHeaderBE = BMLHeaderT<true>;
|
||||
check_struct_size(BMLHeader, 0x40);
|
||||
check_struct_size(BMLHeaderBE, 0x40);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
struct BMLHeaderEntryT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
pstring<TextEncoding::ASCII, 0x20> filename;
|
||||
U32T compressed_size;
|
||||
U32T<BE> compressed_size;
|
||||
parray<uint8_t, 0x04> unknown_a1;
|
||||
U32T decompressed_size;
|
||||
U32T compressed_gvm_size;
|
||||
U32T decompressed_gvm_size;
|
||||
U32T<BE> decompressed_size;
|
||||
U32T<BE> compressed_gvm_size;
|
||||
U32T<BE> decompressed_gvm_size;
|
||||
parray<uint8_t, 0x0C> unknown_a2;
|
||||
} __packed__;
|
||||
|
||||
@@ -40,15 +37,15 @@ using BMLHeaderEntryBE = BMLHeaderEntryT<true>;
|
||||
check_struct_size(BMLHeaderEntry, 0x40);
|
||||
check_struct_size(BMLHeaderEntryBE, 0x40);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
void BMLArchive::load_t() {
|
||||
StringReader r(*this->data);
|
||||
phosg::StringReader r(*this->data);
|
||||
|
||||
const auto& header = r.get<BMLHeaderT<IsBigEndian>>();
|
||||
const auto& header = r.get<BMLHeaderT<BE>>();
|
||||
|
||||
size_t offset = 0x800;
|
||||
while (this->entries.size() < header.num_entries) {
|
||||
const auto& entry = r.get<BMLHeaderEntryT<IsBigEndian>>();
|
||||
const auto& entry = r.get<BMLHeaderEntryT<BE>>();
|
||||
|
||||
if (offset + entry.compressed_size > this->data->size()) {
|
||||
throw runtime_error("BML data entry extends beyond end of data");
|
||||
@@ -106,10 +103,10 @@ string BMLArchive::get_copy(const string& name) const {
|
||||
}
|
||||
}
|
||||
|
||||
StringReader BMLArchive::get_reader(const string& name) const {
|
||||
phosg::StringReader BMLArchive::get_reader(const string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return StringReader(this->data->data() + entry.offset, entry.size);
|
||||
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,10 +24,10 @@ public:
|
||||
std::pair<const void*, size_t> get(const std::string& name) const;
|
||||
std::pair<const void*, size_t> get_gvm(const std::string& name) const;
|
||||
std::string get_copy(const std::string& name) const;
|
||||
StringReader get_reader(const std::string& name) const;
|
||||
phosg::StringReader get_reader(const std::string& name) const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
void load_t();
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
|
||||
@@ -54,7 +54,7 @@ BattleParamsIndex::BattleParamsIndex(
|
||||
for (uint8_t episode = 0; episode < 3; episode++) {
|
||||
auto& file = this->files[is_solo][episode];
|
||||
if (file.data->size() < sizeof(Table)) {
|
||||
throw runtime_error(string_printf(
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)",
|
||||
sizeof(Table), file.data->size(), is_solo, episode));
|
||||
}
|
||||
|
||||
+7
-8
@@ -38,7 +38,7 @@ CatSession::CatSession(
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
|
||||
: log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
|
||||
: log(phosg::string_printf("[CatSession:%s] ", phosg::name_for_enum(version)), proxy_server_log.min_level),
|
||||
base(base),
|
||||
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free),
|
||||
channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
|
||||
@@ -48,19 +48,19 @@ CatSession::CatSession(
|
||||
throw runtime_error("remote is not AF_INET");
|
||||
}
|
||||
|
||||
string netloc_str = render_sockaddr_storage(remote);
|
||||
string netloc_str = phosg::render_sockaddr_storage(remote);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(
|
||||
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
throw runtime_error(phosg::string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
throw runtime_error(phosg::string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
|
||||
event_add(this->read_event.get(), nullptr);
|
||||
@@ -68,7 +68,7 @@ CatSession::CatSession(
|
||||
}
|
||||
|
||||
void CatSession::execute_command(const std::string& command) {
|
||||
string full_cmd = parse_data_string(command, nullptr, ParseDataFlags::ALLOW_FILES);
|
||||
string full_cmd = phosg::parse_data_string(command, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
|
||||
}
|
||||
|
||||
@@ -109,9 +109,8 @@ void CatSession::on_channel_input(
|
||||
|
||||
// TODO: Use the iovec form of print_data here instead of
|
||||
// prepend_command_header (which copies the string)
|
||||
string full_cmd = prepend_command_header(
|
||||
this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS);
|
||||
string full_cmd = prepend_command_header(this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
phosg::print_data(stdout, full_cmd, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||
|
||||
+2
-2
@@ -29,10 +29,10 @@ public:
|
||||
virtual ~CatSession() = default;
|
||||
|
||||
protected:
|
||||
PrefixedLogger log;
|
||||
phosg::PrefixedLogger log;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
|
||||
Poll poll;
|
||||
phosg::Poll poll;
|
||||
|
||||
Channel channel;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
|
||||
|
||||
+19
-19
@@ -29,8 +29,8 @@ Channel::Channel(
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
TerminalFormat terminal_send_color,
|
||||
TerminalFormat terminal_recv_color)
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
virtual_network_id(0),
|
||||
version(version),
|
||||
@@ -52,8 +52,8 @@ Channel::Channel(
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
TerminalFormat terminal_send_color,
|
||||
TerminalFormat terminal_recv_color)
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
version(version),
|
||||
language(language),
|
||||
@@ -98,7 +98,7 @@ void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
} else {
|
||||
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
|
||||
phosg::get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
|
||||
}
|
||||
|
||||
bufferevent_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
|
||||
@@ -206,9 +206,9 @@ Channel::Message Channel::recv() {
|
||||
}
|
||||
command_data.resize(command_logical_size - header_size);
|
||||
|
||||
if (command_data_log.should_log(LogLevel::INFO) && (this->terminal_recv_color != TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
|
||||
if (command_data_log.should_log(phosg::LogLevel::INFO) && (this->terminal_recv_color != phosg::TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, this->terminal_recv_color, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
|
||||
if (version == Version::BB_V4) {
|
||||
@@ -221,7 +221,7 @@ Channel::Message Channel::recv() {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
name_for_enum(this->version),
|
||||
phosg::name_for_enum(this->version),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
}
|
||||
@@ -229,10 +229,10 @@ Channel::Message Channel::recv() {
|
||||
vector<struct iovec> iovs;
|
||||
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
|
||||
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
|
||||
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stderr, iovs, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
|
||||
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
||||
if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) {
|
||||
phosg::print_color_escape(stderr, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,20 +342,20 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
|
||||
}
|
||||
send_data.resize(send_data_size, '\0');
|
||||
|
||||
if (!silent && (command_data_log.should_log(LogLevel::INFO)) && (this->terminal_send_color != TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
|
||||
if (!silent && (command_data_log.should_log(phosg::LogLevel::INFO)) && (this->terminal_send_color != phosg::TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_send_color != phosg::TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(), cmd, flag);
|
||||
} else {
|
||||
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(), name_for_enum(version), cmd, flag);
|
||||
this->name.c_str(), phosg::name_for_enum(version), cmd, flag);
|
||||
}
|
||||
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
|
||||
phosg::print_data(stderr, send_data.data(), logical_size, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
if (use_terminal_colors && this->terminal_send_color != phosg::TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -21,8 +21,8 @@ struct Channel {
|
||||
std::shared_ptr<PSOEncryption> crypt_out;
|
||||
|
||||
std::string name;
|
||||
TerminalFormat terminal_send_color;
|
||||
TerminalFormat terminal_recv_color;
|
||||
phosg::TerminalFormat terminal_send_color;
|
||||
phosg::TerminalFormat terminal_recv_color;
|
||||
|
||||
struct Message {
|
||||
uint16_t command;
|
||||
@@ -45,8 +45,8 @@ struct Channel {
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name,
|
||||
TerminalFormat terminal_send_color = TerminalFormat::END,
|
||||
TerminalFormat terminal_recv_color = TerminalFormat::END);
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
// Creates a connected channel
|
||||
Channel(
|
||||
struct bufferevent* bev,
|
||||
@@ -57,8 +57,8 @@ struct Channel {
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "",
|
||||
TerminalFormat terminal_send_color = TerminalFormat::END,
|
||||
TerminalFormat terminal_recv_color = TerminalFormat::END);
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
Channel(const Channel& other) = delete;
|
||||
Channel(Channel&& other) = delete;
|
||||
Channel& operator=(const Channel& other) = delete;
|
||||
|
||||
+317
-140
@@ -71,22 +71,25 @@ static void check_debug_enabled(shared_ptr<Client> c) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_cheats_enabled(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
|
||||
static void check_cheats_enabled(shared_ptr<Lobby> l, shared_ptr<Client> c, bool behavior_is_cheating) {
|
||||
if (behavior_is_cheating &&
|
||||
!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
|
||||
!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) {
|
||||
throw precondition_failed("$C6This command can\nonly be used in\ncheat mode.");
|
||||
}
|
||||
}
|
||||
|
||||
static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) &&
|
||||
static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<Client> c, bool behavior_is_cheating) {
|
||||
if (behavior_is_cheating &&
|
||||
(s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) &&
|
||||
!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) {
|
||||
throw precondition_failed("$C6Cheats are disabled\non this server.");
|
||||
}
|
||||
}
|
||||
|
||||
static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<ProxyServer::LinkedSession> ses) {
|
||||
if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) &&
|
||||
static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<ProxyServer::LinkedSession> ses, bool behavior_is_cheating) {
|
||||
if (behavior_is_cheating &&
|
||||
(s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) &&
|
||||
(!ses->login || !ses->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))) {
|
||||
throw precondition_failed("$C6Cheats are disabled\non this proxy.");
|
||||
}
|
||||
@@ -103,13 +106,21 @@ static void check_is_leader(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
|
||||
static void server_command_server_info(shared_ptr<Client> c, const std::string&) {
|
||||
auto s = c->require_server_state();
|
||||
string uptime_str = format_duration(now() - s->creation_time);
|
||||
send_text_message_printf(c,
|
||||
"Uptime: $C6%s$C7\nLobbies: $C6%zu$C7\nClients: $C6%zu$C7(g) $C6%zu$C7(p)",
|
||||
uptime_str.c_str(),
|
||||
s->id_to_lobby.size(),
|
||||
s->channel_to_client.size(),
|
||||
s->proxy_server->num_sessions());
|
||||
string uptime_str = phosg::format_duration(phosg::now() - s->creation_time);
|
||||
if (s->proxy_server) {
|
||||
send_text_message_printf(c,
|
||||
"Uptime: $C6%s$C7\nLobbies: $C6%zu$C7\nClients: $C6%zu$C7(g) $C6%zu$C7(p)",
|
||||
uptime_str.c_str(),
|
||||
s->id_to_lobby.size(),
|
||||
s->channel_to_client.size(),
|
||||
s->proxy_server->num_sessions());
|
||||
} else {
|
||||
send_text_message_printf(c,
|
||||
"Uptime: $C6%s$C7\nLobbies: $C6%zu$C7\nClients: $C6%zu",
|
||||
uptime_str.c_str(),
|
||||
s->id_to_lobby.size(),
|
||||
s->channel_to_client.size());
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_lobby_info(shared_ptr<Client> c, const std::string&) {
|
||||
@@ -123,11 +134,11 @@ static void server_command_lobby_info(shared_ptr<Client> c, const std::string&)
|
||||
if (l->is_game()) {
|
||||
if (!l->is_ep3()) {
|
||||
if (l->max_level == 0xFFFFFFFF) {
|
||||
lines.emplace_back(string_printf("$C6%08X$C7 L$C6%d+$C7", l->lobby_id, l->min_level + 1));
|
||||
lines.emplace_back(phosg::string_printf("$C6%08X$C7 L$C6%d+$C7", l->lobby_id, l->min_level + 1));
|
||||
} else {
|
||||
lines.emplace_back(string_printf("$C6%08X$C7 L$C6%d-%d$C7", l->lobby_id, l->min_level + 1, l->max_level + 1));
|
||||
lines.emplace_back(phosg::string_printf("$C6%08X$C7 L$C6%d-%d$C7", l->lobby_id, l->min_level + 1, l->max_level + 1));
|
||||
}
|
||||
lines.emplace_back(string_printf("$C7Section ID: $C6%s$C7", name_for_section_id(l->effective_section_id())));
|
||||
lines.emplace_back(phosg::string_printf("$C7Section ID: $C6%s$C7", name_for_section_id(l->effective_section_id())));
|
||||
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
@@ -153,45 +164,45 @@ static void server_command_lobby_info(shared_ptr<Client> c, const std::string&)
|
||||
}
|
||||
|
||||
} else {
|
||||
lines.emplace_back(string_printf("$C7State seed: $C6%08X$C7", l->random_seed));
|
||||
lines.emplace_back(phosg::string_printf("$C7State seed: $C6%08X$C7", l->random_seed));
|
||||
}
|
||||
|
||||
} else {
|
||||
lines.emplace_back(string_printf("$C7Lobby ID: $C6%08X$C7", l->lobby_id));
|
||||
lines.emplace_back(phosg::string_printf("$C7Lobby ID: $C6%08X$C7", l->lobby_id));
|
||||
}
|
||||
|
||||
string slots_str = "Slots: ";
|
||||
for (size_t z = 0; z < l->clients.size(); z++) {
|
||||
if (!l->clients[z]) {
|
||||
slots_str += string_printf("$C0%zX$C7", z);
|
||||
slots_str += phosg::string_printf("$C0%zX$C7", z);
|
||||
} else {
|
||||
bool is_self = l->clients[z] == c;
|
||||
bool is_leader = z == l->leader_id;
|
||||
if (is_self && is_leader) {
|
||||
slots_str += string_printf("$C6%zX$C7", z);
|
||||
slots_str += phosg::string_printf("$C6%zX$C7", z);
|
||||
} else if (is_self) {
|
||||
slots_str += string_printf("$C2%zX$C7", z);
|
||||
slots_str += phosg::string_printf("$C2%zX$C7", z);
|
||||
} else if (is_leader) {
|
||||
slots_str += string_printf("$C4%zX$C7", z);
|
||||
slots_str += phosg::string_printf("$C4%zX$C7", z);
|
||||
} else {
|
||||
slots_str += string_printf("%zX", z);
|
||||
slots_str += phosg::string_printf("%zX", z);
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.emplace_back(std::move(slots_str));
|
||||
}
|
||||
|
||||
send_text_message(c, join(lines, "\n"));
|
||||
send_text_message(c, phosg::join(lines, "\n"));
|
||||
}
|
||||
|
||||
static void server_command_ping(shared_ptr<Client> c, const std::string&) {
|
||||
c->ping_start_time = now();
|
||||
c->ping_start_time = phosg::now();
|
||||
send_command(c, 0x1D, 0x00);
|
||||
}
|
||||
|
||||
static void proxy_command_ping(shared_ptr<ProxyServer::LinkedSession> ses, const std::string&) {
|
||||
ses->client_ping_start_time = now();
|
||||
ses->server_ping_start_time = now();
|
||||
ses->client_ping_start_time = phosg::now();
|
||||
ses->server_ping_start_time = ses->client_ping_start_time;
|
||||
|
||||
C_GuildCardSearch_40 cmd = {0x00010000, ses->remote_guild_card_number, ses->remote_guild_card_number};
|
||||
ses->client_channel.send(0x1D, 0x00);
|
||||
@@ -204,7 +215,7 @@ static void proxy_command_lobby_info(shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
// don't show it. (The user can see it in the pause menu, unlike in masked-GC
|
||||
// sessions like GC.)
|
||||
if (ses->remote_guild_card_number >= 0) {
|
||||
msg = string_printf("$C7GC: $C6%" PRId64 "$C7\n", ses->remote_guild_card_number);
|
||||
msg = phosg::string_printf("$C7GC: $C6%" PRId64 "$C7\n", ses->remote_guild_card_number);
|
||||
}
|
||||
msg += "Slots: ";
|
||||
|
||||
@@ -212,15 +223,15 @@ static void proxy_command_lobby_info(shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
bool is_self = z == ses->lobby_client_id;
|
||||
bool is_leader = z == ses->leader_client_id;
|
||||
if (ses->lobby_players[z].guild_card_number == 0) {
|
||||
msg += string_printf("$C0%zX$C7", z);
|
||||
msg += phosg::string_printf("$C0%zX$C7", z);
|
||||
} else if (is_self && is_leader) {
|
||||
msg += string_printf("$C6%zX$C7", z);
|
||||
msg += phosg::string_printf("$C6%zX$C7", z);
|
||||
} else if (is_self) {
|
||||
msg += string_printf("$C2%zX$C7", z);
|
||||
msg += phosg::string_printf("$C2%zX$C7", z);
|
||||
} else if (is_leader) {
|
||||
msg += string_printf("$C4%zX$C7", z);
|
||||
msg += phosg::string_printf("$C4%zX$C7", z);
|
||||
} else {
|
||||
msg += string_printf("%zX", z);
|
||||
msg += phosg::string_printf("%zX", z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +244,7 @@ static void proxy_command_lobby_info(shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
}
|
||||
if (!cheats_tokens.empty()) {
|
||||
msg += "\n$C7Cheats: $C6";
|
||||
msg += join(cheats_tokens, ",");
|
||||
msg += phosg::join(cheats_tokens, ",");
|
||||
}
|
||||
|
||||
vector<const char*> behaviors_tokens;
|
||||
@@ -251,7 +262,7 @@ static void proxy_command_lobby_info(shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
}
|
||||
if (!behaviors_tokens.empty()) {
|
||||
msg += "\n$C7Flags: $C6";
|
||||
msg += join(behaviors_tokens, ",");
|
||||
msg += phosg::join(behaviors_tokens, ",");
|
||||
}
|
||||
|
||||
if (ses->config.override_section_id != 0xFF) {
|
||||
@@ -267,16 +278,37 @@ static void server_command_ax(shared_ptr<Client> c, const std::string& args) {
|
||||
ax_messages_log.info("%s", args.c_str());
|
||||
}
|
||||
|
||||
static void server_command_announce(shared_ptr<Client> c, const std::string& args) {
|
||||
static void server_command_announce_inner(shared_ptr<Client> c, const std::string& args, bool mail, bool anonymous) {
|
||||
auto s = c->require_server_state();
|
||||
check_account_flag(c, Account::Flag::ANNOUNCE);
|
||||
send_text_message(s, args);
|
||||
if (anonymous) {
|
||||
if (mail) {
|
||||
send_simple_mail(s, 0, s->name, args);
|
||||
} else {
|
||||
send_text_or_scrolling_message(s, args, args);
|
||||
}
|
||||
} else {
|
||||
auto from_name = c->character()->disp.name.decode(c->language());
|
||||
if (mail) {
|
||||
send_simple_mail(s, 0, from_name, args);
|
||||
} else {
|
||||
auto message = from_name + ": " + args;
|
||||
send_text_or_scrolling_message(s, message, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_announce_mail(shared_ptr<Client> c, const std::string& args) {
|
||||
auto s = c->require_server_state();
|
||||
check_account_flag(c, Account::Flag::ANNOUNCE);
|
||||
send_simple_mail(s, 0, s->name, args);
|
||||
static void server_command_announce_named(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_announce_inner(c, args, false, false);
|
||||
}
|
||||
static void server_command_announce_anonymous(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_announce_inner(c, args, false, true);
|
||||
}
|
||||
static void server_command_announce_mail_named(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_announce_inner(c, args, true, false);
|
||||
}
|
||||
static void server_command_announce_mail_anonymous(shared_ptr<Client> c, const std::string& args) {
|
||||
server_command_announce_inner(c, args, true, true);
|
||||
}
|
||||
|
||||
static void server_command_arrow(shared_ptr<Client> c, const std::string& args) {
|
||||
@@ -353,7 +385,7 @@ static void server_command_swset_swclear(shared_ptr<Client> c, const std::string
|
||||
return;
|
||||
}
|
||||
|
||||
auto tokens = split(args, ' ');
|
||||
auto tokens = phosg::split(args, ' ');
|
||||
uint8_t floor, flag_num;
|
||||
if (tokens.size() == 1) {
|
||||
floor = c->floor;
|
||||
@@ -391,7 +423,7 @@ static void proxy_command_swset_swclear(shared_ptr<ProxyServer::LinkedSession> s
|
||||
return;
|
||||
}
|
||||
|
||||
auto tokens = split(args, ' ');
|
||||
auto tokens = phosg::split(args, ' ');
|
||||
uint8_t floor, flag_num;
|
||||
if (tokens.size() == 1) {
|
||||
floor = ses->floor;
|
||||
@@ -589,7 +621,7 @@ static void server_command_qgwrite(shared_ptr<Client> c, const std::string& args
|
||||
return;
|
||||
}
|
||||
|
||||
auto tokens = split(args, ' ');
|
||||
auto tokens = phosg::split(args, ' ');
|
||||
if (tokens.size() != 2) {
|
||||
send_text_message(c, "$C6Incorrect number\nof arguments");
|
||||
return;
|
||||
@@ -616,7 +648,7 @@ static void server_command_qsync_qsyncall(shared_ptr<Client> c, const std::strin
|
||||
return;
|
||||
}
|
||||
|
||||
auto tokens = split(args, ' ');
|
||||
auto tokens = phosg::split(args, ' ');
|
||||
if (tokens.size() != 2) {
|
||||
send_text_message(c, "$C6Incorrect number of\narguments");
|
||||
return;
|
||||
@@ -655,7 +687,7 @@ static void proxy_command_qsync_qsyncall(shared_ptr<ProxyServer::LinkedSession>
|
||||
return;
|
||||
}
|
||||
|
||||
auto tokens = split(args, ' ');
|
||||
auto tokens = phosg::split(args, ' ');
|
||||
if (tokens.size() != 2) {
|
||||
send_text_message(ses->client_channel, "$C6Incorrect number of\narguments");
|
||||
return;
|
||||
@@ -724,6 +756,35 @@ static void server_command_show_material_counts(shared_ptr<Client> c, const std:
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_show_kill_count(shared_ptr<Client> c, const std::string&) {
|
||||
auto p = c->character();
|
||||
size_t item_index;
|
||||
try {
|
||||
item_index = p->inventory.find_equipped_item(EquipSlot::WEAPON);
|
||||
} catch (const out_of_range&) {
|
||||
send_text_message(c, "No weapon equipped");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& item = p->inventory.items.at(item_index);
|
||||
if (!item.data.has_kill_count()) {
|
||||
send_text_message(c, "Weapon does not\nhave a kill count");
|
||||
return;
|
||||
}
|
||||
|
||||
// Kill counts are only accurate on the server side at all times on BB. On
|
||||
// other versions, we update the server's view of the client's inventory
|
||||
// during games, but we can't track kills because the client doesn't inform
|
||||
// the server whether it counted a kill for any individual enemy. So, on
|
||||
// non-BB versions, the kill count is accurate at all times in the lobby
|
||||
// (since kills can't occur there), or at the beginning of a game.
|
||||
if ((c->version() == Version::BB_V4) || !c->require_lobby()->is_game()) {
|
||||
send_text_message_printf(c, "%hu kills", item.data.get_kill_count());
|
||||
} else {
|
||||
send_text_message_printf(c, "%hu kills as of\ngame join", item.data.get_kill_count());
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_auction(shared_ptr<Client> c, const std::string&) {
|
||||
check_account_flag(c, Account::Flag::DEBUG);
|
||||
auto l = c->require_lobby();
|
||||
@@ -835,7 +896,6 @@ static void server_command_exit(shared_ptr<Client> c, const std::string&) {
|
||||
G_UnusedHeader cmd = {0x73, 0x01, 0x0000};
|
||||
c->channel.send(0x60, 0x00, cmd);
|
||||
c->floor = 0;
|
||||
c->recent_switch_flags.clear();
|
||||
} else if (is_ep3(c->version())) {
|
||||
c->channel.send(0xED, 0x00);
|
||||
} else {
|
||||
@@ -896,26 +956,26 @@ static void proxy_command_get_player_card(shared_ptr<ProxyServer::LinkedSession>
|
||||
|
||||
static void server_command_send_client(shared_ptr<Client> c, const std::string& args) {
|
||||
check_debug_enabled(c);
|
||||
string data = parse_data_string(args);
|
||||
string data = phosg::parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
c->channel.send(data);
|
||||
}
|
||||
|
||||
static void server_command_send_server(shared_ptr<Client> c, const std::string& args) {
|
||||
check_debug_enabled(c);
|
||||
string data = parse_data_string(args);
|
||||
string data = phosg::parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
on_command_with_header(c, data);
|
||||
}
|
||||
|
||||
static void proxy_command_send_client(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
string data = parse_data_string(args);
|
||||
string data = phosg::parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
ses->client_channel.send(data);
|
||||
}
|
||||
|
||||
static void proxy_command_send_server(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
string data = parse_data_string(args);
|
||||
string data = phosg::parse_data_string(args);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
ses->server_channel.send(data);
|
||||
}
|
||||
@@ -944,7 +1004,9 @@ static void server_command_cheat(shared_ptr<Client> c, const std::string&) {
|
||||
l->toggle_flag(Lobby::Flag::CHEATS_ENABLED);
|
||||
send_text_message_printf(l, "Cheat mode %s", l->check_flag(Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled");
|
||||
|
||||
if (!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) {
|
||||
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
|
||||
!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE) &&
|
||||
s->cheat_flags.insufficient_minimum_level) {
|
||||
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty);
|
||||
if (l->min_level < default_min_level) {
|
||||
l->min_level = default_min_level;
|
||||
@@ -1044,7 +1106,7 @@ static string file_path_for_recording(const std::string& args, uint32_t account_
|
||||
throw runtime_error("invalid recording name");
|
||||
}
|
||||
}
|
||||
return string_printf("system/ep3/battle-records/%010" PRIu32 "_%s.mzrd", account_id, args.c_str());
|
||||
return phosg::string_printf("system/ep3/battle-records/%010" PRIu32 "_%s.mzrd", account_id, args.c_str());
|
||||
}
|
||||
|
||||
static void server_command_saverec(shared_ptr<Client> c, const std::string& args) {
|
||||
@@ -1054,7 +1116,7 @@ static void server_command_saverec(shared_ptr<Client> c, const std::string& args
|
||||
}
|
||||
string file_path = file_path_for_recording(args, c->login->account->account_id);
|
||||
string data = c->ep3_prev_battle_record->serialize();
|
||||
save_file(file_path, data);
|
||||
phosg::save_file(file_path, data);
|
||||
send_text_message(c, "$C7Recording saved");
|
||||
c->ep3_prev_battle_record.reset();
|
||||
}
|
||||
@@ -1080,8 +1142,8 @@ static void server_command_playrec(shared_ptr<Client> c, const std::string& args
|
||||
|
||||
string data;
|
||||
try {
|
||||
data = load_file(file_path);
|
||||
} catch (const cannot_open_file&) {
|
||||
data = phosg::load_file(file_path);
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
send_text_message(c, "$C4The recording does\nnot exist");
|
||||
return;
|
||||
}
|
||||
@@ -1119,7 +1181,8 @@ static void server_command_meseta(shared_ptr<Client> c, const std::string& args)
|
||||
|
||||
static void server_command_secid(shared_ptr<Client> c, const std::string& args) {
|
||||
auto l = c->require_lobby();
|
||||
check_cheats_allowed(c->require_server_state(), c);
|
||||
auto s = c->require_server_state();
|
||||
check_cheats_allowed(s, c, s->cheat_flags.override_section_id);
|
||||
|
||||
uint8_t new_override_section_id;
|
||||
|
||||
@@ -1144,7 +1207,9 @@ static void server_command_secid(shared_ptr<Client> c, const std::string& args)
|
||||
}
|
||||
|
||||
static void proxy_command_secid(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
check_cheats_allowed(ses->require_server_state(), ses);
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.override_section_id);
|
||||
|
||||
if (!args[0]) {
|
||||
ses->config.override_section_id = 0xFF;
|
||||
send_text_message(ses->client_channel, "$C6Override section ID\nremoved");
|
||||
@@ -1166,7 +1231,7 @@ static void server_command_variations(shared_ptr<Client> c, const std::string& a
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, false);
|
||||
check_cheats_allowed(s, c);
|
||||
check_cheats_allowed(s, c, s->cheat_flags.override_variations);
|
||||
|
||||
c->override_variations = make_unique<parray<le_uint32_t, 0x20>>();
|
||||
c->override_variations->clear(0);
|
||||
@@ -1179,7 +1244,7 @@ static void server_command_rand(shared_ptr<Client> c, const std::string& args) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, false);
|
||||
check_cheats_allowed(s, c);
|
||||
check_cheats_allowed(s, c, s->cheat_flags.override_random_seed);
|
||||
|
||||
if (!args[0]) {
|
||||
c->config.clear_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED);
|
||||
@@ -1193,7 +1258,8 @@ static void server_command_rand(shared_ptr<Client> c, const std::string& args) {
|
||||
}
|
||||
|
||||
static void proxy_command_rand(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
check_cheats_allowed(ses->require_server_state(), ses);
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.override_random_seed);
|
||||
if (!args[0]) {
|
||||
ses->config.clear_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED);
|
||||
ses->config.override_random_seed = 0;
|
||||
@@ -1264,7 +1330,7 @@ static void server_command_min_level(shared_ptr<Client> c, const std::string& ar
|
||||
auto s = c->require_server_state();
|
||||
bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) ||
|
||||
c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
|
||||
if (!cheats_allowed) {
|
||||
if (!cheats_allowed && s->cheat_flags.insufficient_minimum_level) {
|
||||
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty);
|
||||
if (new_min_level < default_min_level) {
|
||||
send_text_message_printf(c, "$C6Cannot set minimum\nlevel below %zu", default_min_level + 1);
|
||||
@@ -1307,34 +1373,74 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
||||
bool cheats_allowed = ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) ||
|
||||
c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
|
||||
|
||||
string encoded_args = tolower(args);
|
||||
vector<string> tokens = split(encoded_args, ' ');
|
||||
string encoded_args = phosg::tolower(args);
|
||||
vector<string> tokens = phosg::split(encoded_args, ' ');
|
||||
|
||||
using MatType = PSOBBCharacterFile::MaterialType;
|
||||
|
||||
try {
|
||||
auto p = c->character();
|
||||
if (tokens.at(0) == "atp" && cheats_allowed) {
|
||||
if (tokens.at(0) == "atp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.atp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "mst" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "mst" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.mst = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "evp" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "evp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.evp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "hp" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "hp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.hp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "dfp" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "dfp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.dfp = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "ata" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "ata" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.ata = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "lck" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "lck" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.lck = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "meseta" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.meseta = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "exp" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.experience = stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "level" && cheats_allowed) {
|
||||
uint32_t level = stoul(tokens.at(1)) - 1;
|
||||
auto level_table = s->level_table(c->version());
|
||||
level_table->reset_to_base(p->disp.stats, p->disp.visual.char_class);
|
||||
level_table->advance_to_level(p->disp.stats, level, p->disp.visual.char_class);
|
||||
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.level = stoul(tokens.at(1)) - 1;
|
||||
p->recompute_stats(s->level_table(c->version()));
|
||||
} else if (((tokens.at(0) == "material") || (tokens.at(0) == "mat")) && !is_v1_or_v2(c->version()) && (cheats_allowed || !s->cheat_flags.reset_materials)) {
|
||||
if (tokens.at(1) == "reset") {
|
||||
const auto& which = tokens.at(2);
|
||||
if (which == "power") {
|
||||
p->set_material_usage(MatType::POWER, 0);
|
||||
} else if (which == "mind") {
|
||||
p->set_material_usage(MatType::MIND, 0);
|
||||
} else if (which == "evade") {
|
||||
p->set_material_usage(MatType::EVADE, 0);
|
||||
} else if (which == "def") {
|
||||
p->set_material_usage(MatType::DEF, 0);
|
||||
} else if (which == "luck") {
|
||||
p->set_material_usage(MatType::LUCK, 0);
|
||||
} else if (which == "hp") {
|
||||
p->set_material_usage(MatType::HP, 0);
|
||||
} else if (which == "tp") {
|
||||
p->set_material_usage(MatType::TP, 0);
|
||||
} else if (which == "all") {
|
||||
p->set_material_usage(MatType::POWER, 0);
|
||||
p->set_material_usage(MatType::MIND, 0);
|
||||
p->set_material_usage(MatType::EVADE, 0);
|
||||
p->set_material_usage(MatType::DEF, 0);
|
||||
p->set_material_usage(MatType::LUCK, 0);
|
||||
} else if (which == "every") {
|
||||
p->set_material_usage(MatType::POWER, 0);
|
||||
p->set_material_usage(MatType::MIND, 0);
|
||||
p->set_material_usage(MatType::EVADE, 0);
|
||||
p->set_material_usage(MatType::DEF, 0);
|
||||
p->set_material_usage(MatType::LUCK, 0);
|
||||
p->set_material_usage(MatType::HP, 0);
|
||||
p->set_material_usage(MatType::TP, 0);
|
||||
} else {
|
||||
send_text_message(c, "$C6Invalid subcommand");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
send_text_message(c, "$C6Invalid subcommand");
|
||||
return;
|
||||
}
|
||||
p->recompute_stats(s->level_table(c->version()));
|
||||
} else if (tokens.at(0) == "namecolor") {
|
||||
uint32_t new_color;
|
||||
sscanf(tokens.at(1).c_str(), "%8X", &new_color);
|
||||
@@ -1349,9 +1455,13 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
||||
p->guild_card.language = new_language;
|
||||
auto sys = c->system_file(false);
|
||||
if (sys) {
|
||||
sys->base.language = new_language;
|
||||
sys->language = new_language;
|
||||
}
|
||||
} else if (tokens.at(0) == "secid") {
|
||||
if (!cheats_allowed && (p->disp.stats.level > 0) && s->cheat_flags.edit_section_id) {
|
||||
send_text_message(c, "$C6You cannot change\nyour Section ID\nafter level 1");
|
||||
return;
|
||||
}
|
||||
} else if (tokens.at(0) == "secid" && cheats_allowed) {
|
||||
uint8_t secid = section_id_for_name(tokens.at(1));
|
||||
if (secid == 0xFF) {
|
||||
send_text_message(c, "$C6No such section ID");
|
||||
@@ -1360,22 +1470,57 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
||||
p->disp.visual.section_id = secid;
|
||||
}
|
||||
} else if (tokens.at(0) == "name") {
|
||||
vector<string> orig_tokens = split(args, ' ');
|
||||
vector<string> orig_tokens = phosg::split(args, ' ');
|
||||
p->disp.name.encode(orig_tokens.at(1), p->inventory.language);
|
||||
} else if (tokens.at(0) == "npc") {
|
||||
if (tokens.at(1) == "none") {
|
||||
p->disp.visual.extra_model = 0;
|
||||
p->disp.visual.validation_flags &= 0xFD;
|
||||
// Restore saved fields, if any
|
||||
if (p->disp.visual.unused[0] == 0x8D) {
|
||||
p->disp.visual.char_class = p->disp.visual.unused[1];
|
||||
p->disp.visual.head = p->disp.visual.unused[2];
|
||||
p->disp.visual.hair = p->disp.visual.unused[3];
|
||||
p->disp.visual.unused.clear(0);
|
||||
}
|
||||
} else {
|
||||
uint8_t npc = npc_for_name(tokens.at(1));
|
||||
uint8_t npc = npc_for_name(tokens.at(1), c->version());
|
||||
if (npc == 0xFF) {
|
||||
send_text_message(c, "$C6No such NPC");
|
||||
return;
|
||||
}
|
||||
|
||||
// Some NPCs can crash the client if the character's class is
|
||||
// incorrect. To handle this, we save the affected fields in the unused
|
||||
// bytes after extra_model.
|
||||
int8_t replacement_class = -1;
|
||||
switch (npc) {
|
||||
case 1: // Rico (replace with HUnewearl)
|
||||
case 6: // Elly (replace with HUnewearl)
|
||||
replacement_class = 0x01;
|
||||
break;
|
||||
case 0: // Ninja (replace with HUmar)
|
||||
case 2: // Sonic (replace with HUmar)
|
||||
case 5: // Flowen (replace with HUmar)
|
||||
replacement_class = 0x00;
|
||||
break;
|
||||
}
|
||||
if (replacement_class >= 0) {
|
||||
if (p->disp.visual.unused[0] != 0x8D) {
|
||||
p->disp.visual.unused[0] = 0x8D;
|
||||
p->disp.visual.unused[1] = p->disp.visual.char_class;
|
||||
p->disp.visual.unused[2] = p->disp.visual.head;
|
||||
p->disp.visual.unused[3] = p->disp.visual.hair;
|
||||
}
|
||||
p->disp.visual.char_class = replacement_class;
|
||||
p->disp.visual.head = 0x00;
|
||||
p->disp.visual.hair = 0x00;
|
||||
}
|
||||
|
||||
p->disp.visual.extra_model = npc;
|
||||
p->disp.visual.validation_flags |= 0x02;
|
||||
}
|
||||
} else if (tokens.at(0) == "tech" && cheats_allowed) {
|
||||
} else if (tokens.at(0) == "tech" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
uint8_t level = stoul(tokens.at(2)) - 1;
|
||||
if (tokens.at(1) == "all") {
|
||||
for (size_t x = 0; x < 0x14; x++) {
|
||||
@@ -1440,7 +1585,11 @@ static void server_command_change_bank(shared_ptr<Client> c, const std::string&
|
||||
throw runtime_error("invalid bank number");
|
||||
}
|
||||
|
||||
const auto& bank = c->current_bank();
|
||||
auto& bank = c->current_bank();
|
||||
bank.assign_ids(0x99000000 + (c->lobby_client_id << 20));
|
||||
c->log.info("Assigned bank item IDs");
|
||||
c->print_bank(stderr);
|
||||
|
||||
send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load());
|
||||
}
|
||||
|
||||
@@ -1449,10 +1598,15 @@ static void server_command_bbchar_savechar(shared_ptr<Client> c, const std::stri
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, false);
|
||||
|
||||
if (is_bb_conversion && is_ep3(c->version())) {
|
||||
send_text_message(c, "$C6Episode 3 players\ncannot be converted\nto BB format");
|
||||
return;
|
||||
}
|
||||
|
||||
auto pending_export = make_unique<Client::PendingCharacterExport>();
|
||||
|
||||
if (is_bb_conversion) {
|
||||
vector<string> tokens = split(args, ' ');
|
||||
vector<string> tokens = phosg::split(args, ' ');
|
||||
if (tokens.size() != 3) {
|
||||
send_text_message(c, "$C6Incorrect argument count");
|
||||
return;
|
||||
@@ -1503,14 +1657,6 @@ static void server_command_savechar(shared_ptr<Client> c, const std::string& arg
|
||||
}
|
||||
|
||||
static void server_command_loadchar(shared_ptr<Client> c, const std::string& args) {
|
||||
if (!is_v1_or_v2(c->version()) &&
|
||||
(c->version() != Version::GC_V3) &&
|
||||
(c->version() != Version::GC_NTE) &&
|
||||
(c->version() != Version::XB_V3) &&
|
||||
(c->version() != Version::BB_V4)) {
|
||||
send_text_message(c, "$C7This command cannot\nbe used on your\ngame version");
|
||||
return;
|
||||
}
|
||||
if (c->login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT)) {
|
||||
send_text_message(c, "$C7This command cannot\nbe used on a shared\naccount");
|
||||
return;
|
||||
@@ -1523,7 +1669,13 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
||||
send_text_message(c, "$C6Player index must\nbe in range 1-16");
|
||||
return;
|
||||
}
|
||||
c->load_backup_character(c->login->account->account_id, index);
|
||||
|
||||
shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
|
||||
if (is_ep3(c->version())) {
|
||||
ep3_char = c->load_ep3_backup_character(c->login->account->account_id, index);
|
||||
} else {
|
||||
c->load_backup_character(c->login->account->account_id, index);
|
||||
}
|
||||
|
||||
if (c->version() == Version::BB_V4) {
|
||||
// On BB, it suffices to simply send the character file again
|
||||
@@ -1535,10 +1687,12 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
||||
} else if ((c->version() == Version::DC_V2) ||
|
||||
(c->version() == Version::GC_NTE) ||
|
||||
(c->version() == Version::GC_V3) ||
|
||||
(c->version() == Version::GC_EP3_NTE) ||
|
||||
(c->version() == Version::GC_EP3) ||
|
||||
(c->version() == Version::XB_V3)) {
|
||||
// TODO: Support extended player info on other versions
|
||||
auto s = c->require_server_state();
|
||||
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) ||
|
||||
if (!c->config.check_flag(Client::Flag::HAS_SEND_FUNCTION_CALL) ||
|
||||
c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) {
|
||||
send_text_message_printf(c, "Can\'t load character\ndata on this game\nversion");
|
||||
return;
|
||||
@@ -1574,14 +1728,19 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
||||
};
|
||||
|
||||
if (c->version() == Version::DC_V2) {
|
||||
auto dc_char = make_shared<PSODCV2CharacterFile>(c->character()->to_dc_v2());
|
||||
send_set_extended_player_info.operator()<PSODCV2CharacterFile>(c, dc_char);
|
||||
auto dc_char = make_shared<PSODCV2CharacterFile::Character>(c->character()->to_dc_v2());
|
||||
send_set_extended_player_info.operator()<PSODCV2CharacterFile::Character>(c, dc_char);
|
||||
} else if (c->version() == Version::GC_NTE) {
|
||||
auto gc_char = make_shared<PSOGCNTECharacterFileCharacter>(c->character()->to_gc_nte());
|
||||
send_set_extended_player_info.operator()<PSOGCNTECharacterFileCharacter>(c, gc_char);
|
||||
} else if (c->version() == Version::GC_V3) {
|
||||
auto gc_char = make_shared<PSOGCCharacterFile::Character>(c->character()->to_gc());
|
||||
send_set_extended_player_info.operator()<PSOGCCharacterFile::Character>(c, gc_char);
|
||||
} else if (c->version() == Version::GC_EP3_NTE) {
|
||||
auto nte_char = make_shared<PSOGCEp3NTECharacter>(*ep3_char);
|
||||
send_set_extended_player_info.operator()<PSOGCEp3NTECharacter>(c, nte_char);
|
||||
} else if (c->version() == Version::GC_EP3) {
|
||||
send_set_extended_player_info.operator()<PSOGCEp3CharacterFile::Character>(c, ep3_char);
|
||||
} else if (c->version() == Version::XB_V3) {
|
||||
if (!c->login || !c->login->xb_license) {
|
||||
throw runtime_error("XB client is not logged in");
|
||||
@@ -1622,7 +1781,7 @@ static string name_for_client(shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
if (c->login) {
|
||||
return string_printf("SN:%" PRIu32, c->login->account->account_id);
|
||||
return phosg::string_printf("SN:%" PRIu32, c->login->account->account_id);
|
||||
}
|
||||
|
||||
return "Player";
|
||||
@@ -1717,7 +1876,7 @@ static void server_command_ban(shared_ptr<Client> c, const std::string& args) {
|
||||
usecs *= 60 * 60 * 24 * 365;
|
||||
}
|
||||
|
||||
target->login->account->ban_end_time = now() + usecs;
|
||||
target->login->account->ban_end_time = phosg::now() + usecs;
|
||||
target->login->account->save();
|
||||
send_message_box(target, "$C6You have been banned.");
|
||||
target->should_disconnect = true;
|
||||
@@ -1732,29 +1891,13 @@ static void server_command_warp(shared_ptr<Client> c, const std::string& args, b
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.warp);
|
||||
|
||||
uint32_t floor = stoul(args, nullptr, 0);
|
||||
if (c->floor == floor) {
|
||||
if (!is_warpall && (c->floor == floor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: $warp[me] 0 is allowed in boss arenas if the boss is already
|
||||
// defeated, even if cheats are disabled. This is because if a player returns
|
||||
// to a boss arena after a persistence gap in the game, the exit warp won't
|
||||
// exist, so they need a way to get out.
|
||||
bool should_check_cheats = is_warpall || (floor != 0) || !floor_is_boss_arena(l->episode, c->floor);
|
||||
if (!should_check_cheats) {
|
||||
for (const auto* event : l->map->get_events(c->floor)) {
|
||||
if (!(event->flags & 0x18)) {
|
||||
should_check_cheats = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should_check_cheats) {
|
||||
check_cheats_enabled(l, c);
|
||||
}
|
||||
|
||||
size_t limit = floor_limit_for_episode(l->episode);
|
||||
if (limit == 0) {
|
||||
return;
|
||||
@@ -1780,7 +1923,7 @@ static void server_command_warpall(shared_ptr<Client> c, const std::string& args
|
||||
|
||||
static void proxy_command_warp(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args, bool is_warpall) {
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses);
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.warp);
|
||||
if (!ses->is_in_game) {
|
||||
send_text_message(ses->client_channel, "$C6You must be in a\ngame to use this\ncommand");
|
||||
return;
|
||||
@@ -1805,7 +1948,7 @@ static void server_command_next(shared_ptr<Client> c, const std::string&) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.warp);
|
||||
|
||||
size_t limit = floor_limit_for_episode(l->episode);
|
||||
if (limit == 0) {
|
||||
@@ -1816,7 +1959,7 @@ static void server_command_next(shared_ptr<Client> c, const std::string&) {
|
||||
|
||||
static void proxy_command_next(shared_ptr<ProxyServer::LinkedSession> ses, const std::string&) {
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses);
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.warp);
|
||||
if (!ses->is_in_game) {
|
||||
send_text_message(ses->client_channel, "$C6You must be in a\ngame to use this\ncommand");
|
||||
return;
|
||||
@@ -1917,25 +2060,25 @@ static void server_command_infinite_hp(shared_ptr<Client> c, const std::string&)
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.infinite_hp_tp);
|
||||
|
||||
c->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
bool enabled = c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
send_text_message_printf(c, "$C6Infinite HP %s", enabled ? "enabled" : "disabled");
|
||||
if (enabled && l->is_game()) {
|
||||
send_remove_conditions(c);
|
||||
send_remove_negative_conditions(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_command_infinite_hp(shared_ptr<ProxyServer::LinkedSession> ses, const std::string&) {
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses);
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.infinite_hp_tp);
|
||||
ses->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
bool enabled = ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED);
|
||||
send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", enabled ? "enabled" : "disabled");
|
||||
if (enabled && ses->is_in_game) {
|
||||
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
|
||||
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
|
||||
send_remove_negative_conditions(ses->client_channel, ses->lobby_client_id);
|
||||
send_remove_negative_conditions(ses->server_channel, ses->lobby_client_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1943,7 +2086,7 @@ static void server_command_infinite_tp(shared_ptr<Client> c, const std::string&)
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.infinite_hp_tp);
|
||||
|
||||
c->config.toggle_flag(Client::Flag::INFINITE_TP_ENABLED);
|
||||
send_text_message_printf(c, "$C6Infinite TP %s", c->config.check_flag(Client::Flag::INFINITE_TP_ENABLED) ? "enabled" : "disabled");
|
||||
@@ -1951,7 +2094,7 @@ static void server_command_infinite_tp(shared_ptr<Client> c, const std::string&)
|
||||
|
||||
static void proxy_command_infinite_tp(shared_ptr<ProxyServer::LinkedSession> ses, const std::string&) {
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses);
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.infinite_hp_tp);
|
||||
ses->config.toggle_flag(Client::Flag::INFINITE_TP_ENABLED);
|
||||
send_text_message_printf(ses->client_channel, "$C6Infinite TP %s",
|
||||
ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED) ? "enabled" : "disabled");
|
||||
@@ -1974,6 +2117,13 @@ static void proxy_command_switch_assist(shared_ptr<ProxyServer::LinkedSession> s
|
||||
ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
static void server_command_toggle_rare_announce(shared_ptr<Client> c, const std::string&) {
|
||||
c->login->account->toggle_user_flag(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST);
|
||||
c->login->account->save();
|
||||
send_text_message_printf(c, "$C6Rare announcements\n%s for your\nitems",
|
||||
c->login->account->check_user_flag(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST) ? "disabled" : "enabled");
|
||||
}
|
||||
|
||||
static void server_command_dropmode(shared_ptr<Client> c, const std::string& args) {
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
@@ -2041,7 +2191,8 @@ static void server_command_dropmode(shared_ptr<Client> c, const std::string& arg
|
||||
}
|
||||
|
||||
static void proxy_command_dropmode(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
check_cheats_allowed(ses->require_server_state(), ses);
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(ses->require_server_state(), ses, s->cheat_flags.proxy_override_drops);
|
||||
|
||||
using DropMode = ProxyServer::LinkedSession::DropMode;
|
||||
if (args.empty()) {
|
||||
@@ -2089,7 +2240,7 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_cheats_enabled(l, c);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.create_items);
|
||||
|
||||
ItemData item = s->parse_item_description(c->version(), args);
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
@@ -2108,7 +2259,7 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
|
||||
|
||||
static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
|
||||
auto s = ses->require_server_state();
|
||||
check_cheats_allowed(s, ses);
|
||||
check_cheats_allowed(s, ses, s->cheat_flags.create_items);
|
||||
if (ses->version() == Version::BB_V4) {
|
||||
send_text_message(ses->client_channel, "$C6This command cannot\nbe used on the proxy\nserver in BB games");
|
||||
return;
|
||||
@@ -2125,7 +2276,7 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
bool set_drop = (!args.empty() && (args[0] == '!'));
|
||||
|
||||
ItemData item = s->parse_item_description(ses->version(), (set_drop ? args.substr(1) : args));
|
||||
item.id = random_object<uint32_t>() | 0x80000000;
|
||||
item.id = phosg::random_object<uint32_t>() | 0x80000000;
|
||||
|
||||
if (set_drop) {
|
||||
ses->next_drop_item = item;
|
||||
@@ -2142,6 +2293,21 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_enable_battle_mode_v1(shared_ptr<Client> c, const std::string&) {
|
||||
check_is_game(c->require_lobby(), false);
|
||||
if (!is_v1(c->version())) {
|
||||
send_text_message(c, "$C6This command can\nonly be used on\nDC v1 and earlier");
|
||||
return;
|
||||
}
|
||||
|
||||
c->config.toggle_flag(Client::Flag::FORCE_BATTLE_MODE_GAME);
|
||||
if (c->config.check_flag(Client::Flag::FORCE_BATTLE_MODE_GAME)) {
|
||||
send_text_message(c, "$C6Battle mode enabled\nfor next game");
|
||||
} else {
|
||||
send_text_message(c, "$C6Battle mode disabled\nfor next game");
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_enable_ep3_battle_debug_menu(shared_ptr<Client> c, const std::string& args) {
|
||||
check_debug_enabled(c);
|
||||
|
||||
@@ -2214,7 +2380,7 @@ static void server_command_ep3_set_dice_range(shared_ptr<Client> c, const std::s
|
||||
}
|
||||
|
||||
auto parse_dice_range = +[](const string& spec) -> uint8_t {
|
||||
auto tokens = split(spec, '-');
|
||||
auto tokens = phosg::split(spec, '-');
|
||||
if (tokens.size() == 1) {
|
||||
uint8_t v = stoull(spec);
|
||||
return (v << 4) | (v & 0x0F);
|
||||
@@ -2228,8 +2394,8 @@ static void server_command_ep3_set_dice_range(shared_ptr<Client> c, const std::s
|
||||
uint8_t def_dice_range = 0;
|
||||
uint8_t atk_dice_range_2v1 = 0;
|
||||
uint8_t def_dice_range_2v1 = 0;
|
||||
for (const auto& spec : split(args, ' ')) {
|
||||
auto tokens = split(spec, ':');
|
||||
for (const auto& spec : phosg::split(args, ' ')) {
|
||||
auto tokens = phosg::split(spec, ':');
|
||||
if (tokens.size() != 2) {
|
||||
send_text_message(c, "$C6Invalid dice spec\nformat");
|
||||
return;
|
||||
@@ -2274,7 +2440,7 @@ static void server_command_ep3_replace_assist_card(shared_ptr<Client> c, const s
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_is_ep3(c, true);
|
||||
check_cheats_enabled(l, c);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.ep3_replace_assist);
|
||||
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
@@ -2295,7 +2461,7 @@ static void server_command_ep3_replace_assist_card(shared_ptr<Client> c, const s
|
||||
size_t client_id;
|
||||
string card_name;
|
||||
if (isdigit(args[0])) {
|
||||
auto tokens = split(args, ' ', 1);
|
||||
auto tokens = phosg::split(args, ' ', 1);
|
||||
client_id = stoul(tokens.at(0), nullptr, 0) - 1;
|
||||
card_name = tokens.at(1);
|
||||
} else {
|
||||
@@ -2326,7 +2492,7 @@ static void server_command_ep3_unset_field_character(shared_ptr<Client> c, const
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_is_ep3(c, true);
|
||||
check_cheats_enabled(l, c);
|
||||
check_cheats_enabled(l, c, s->cheat_flags.ep3_unset_field_character);
|
||||
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
@@ -2359,6 +2525,11 @@ static void server_command_surrender(shared_ptr<Client> c, const std::string&) {
|
||||
send_text_message(c, "$C6Battle has not\nyet started");
|
||||
return;
|
||||
}
|
||||
auto ps = l->ep3_server->get_player_state(c->lobby_client_id);
|
||||
if (!ps || !ps->is_alive()) {
|
||||
send_text_message(c, "$C6Defeated players\ncannot surrender");
|
||||
return;
|
||||
}
|
||||
string name = remove_color(c->character()->disp.name.decode(c->language()));
|
||||
send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str());
|
||||
for (const auto& watcher_l : l->watcher_lobbies) {
|
||||
@@ -2398,7 +2569,7 @@ static void server_command_get_ep3_battle_stat(shared_ptr<Client> c, const std::
|
||||
const char* rank_name = ps->stats.name_for_rank(rank);
|
||||
send_text_message_printf(c, "$C7Score: %g\nRank: %hhu (%s)", score, rank, rank_name);
|
||||
} else if (args == "duration") {
|
||||
string s = format_duration(now() - l->ep3_server->battle_start_usecs);
|
||||
string s = phosg::format_duration(phosg::now() - l->ep3_server->battle_start_usecs);
|
||||
send_text_message_printf(c, "$C7Duration: %s", s.c_str());
|
||||
} else if (args == "fcs-destroyed") {
|
||||
send_text_message_printf(c, "$C7Team FCs destroyed:\n%" PRIu32, l->ep3_server->team_num_ally_fcs_destroyed[team_id]);
|
||||
@@ -2458,13 +2629,18 @@ struct ChatCommandDefinition {
|
||||
|
||||
static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$allevent", {server_command_lobby_event_all, nullptr}},
|
||||
{"$ann", {server_command_announce, nullptr}},
|
||||
{"$ann!", {server_command_announce_mail, nullptr}},
|
||||
{"$ann", {server_command_announce_named, nullptr}},
|
||||
{"$ann?", {server_command_announce_anonymous, nullptr}},
|
||||
{"$ann!", {server_command_announce_mail_named, nullptr}},
|
||||
{"$ann?!", {server_command_announce_mail_anonymous, nullptr}},
|
||||
{"$ann!?", {server_command_announce_mail_anonymous, nullptr}},
|
||||
{"$announcerares", {server_command_toggle_rare_announce, nullptr}},
|
||||
{"$arrow", {server_command_arrow, proxy_command_arrow}},
|
||||
{"$auction", {server_command_auction, proxy_command_auction}},
|
||||
{"$ax", {server_command_ax, nullptr}},
|
||||
{"$ban", {server_command_ban, nullptr}},
|
||||
{"$bank", {server_command_change_bank, nullptr}},
|
||||
{"$battle", {server_command_enable_battle_mode_v1, nullptr}},
|
||||
{"$bbchar", {server_command_bbchar, nullptr}},
|
||||
{"$cheat", {server_command_cheat, nullptr}},
|
||||
{"$debug", {server_command_debug, nullptr}},
|
||||
@@ -2482,6 +2658,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
|
||||
{"$itemnotifs", {server_command_item_notifs, proxy_command_item_notifs}},
|
||||
{"$i", {server_command_item, proxy_command_item}},
|
||||
{"$kick", {server_command_kick, nullptr}},
|
||||
{"$killcount", {server_command_show_kill_count, nullptr}},
|
||||
{"$li", {server_command_lobby_info, proxy_command_lobby_info}},
|
||||
{"$ln", {server_command_lobby_type, proxy_command_lobby_type}},
|
||||
{"$loadchar", {server_command_loadchar, nullptr}},
|
||||
|
||||
+7
-9
@@ -7,18 +7,16 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
class Client;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
struct ChoiceSearchConfigT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
|
||||
U32T<BE> disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
|
||||
struct Entry {
|
||||
U16T parent_choice_id = 0;
|
||||
U16T choice_id = 0;
|
||||
U16T<BE> parent_choice_id = 0;
|
||||
U16T<BE> choice_id = 0;
|
||||
} __packed_ws__(Entry, 4);
|
||||
parray<Entry, 5> entries;
|
||||
|
||||
@@ -31,8 +29,8 @@ struct ChoiceSearchConfigT {
|
||||
return -1;
|
||||
}
|
||||
|
||||
operator ChoiceSearchConfigT<!IsBigEndian>() const {
|
||||
ChoiceSearchConfigT<!IsBigEndian> ret;
|
||||
operator ChoiceSearchConfigT<!BE>() const {
|
||||
ChoiceSearchConfigT<!BE> ret;
|
||||
ret.disabled = this->disabled.load();
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
auto& ret_e = ret.entries[z];
|
||||
|
||||
+111
-121
@@ -31,6 +31,7 @@ void Client::Config::set_flags_for_version(Version version, int64_t sub_version)
|
||||
if (version == Version::BB_V4) {
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::SAVE_ENABLED);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
return;
|
||||
}
|
||||
@@ -41,32 +42,40 @@ void Client::Config::set_flags_for_version(Version version, int64_t sub_version)
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case Version::DC_V2:
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
case Version::GC_EP3:
|
||||
// Some of these versions have send_function_call and some don't; we
|
||||
// have to set these flags later when we get sub_version
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
// TODO: Do all versions of XB need this flag? US does, at least.
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
default:
|
||||
@@ -74,68 +83,66 @@ void Client::Config::set_flags_for_version(Version version, int64_t sub_version)
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x20: // DCNTE, possibly also DCv1 JP
|
||||
case 0x20: // DC NTE, 11/2000, possibly also DCv1 JP
|
||||
case 0x21: // DCv1 US
|
||||
case 0x22: // DCv1 EU, 12/2000, and 01/2001, at 50Hz (presumably)
|
||||
case 0x23: // DCv1 EU, 12/2000, and 01/2001, at 60Hz (presumably)
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case 0x22: // DCv1 EU 50Hz (presumably)
|
||||
case 0x23: // DCv1 EU 60Hz (presumably)
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case 0x25: // DCv2 JP
|
||||
case 0x26: // DCv2 US
|
||||
case 0x26: // DCv2 US and 08/2001
|
||||
case 0x27: // DCv2 EU 50Hz (presumably)
|
||||
case 0x28: // DCv2 EU 60Hz (presumably)
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
case 0x29: // PC
|
||||
this->set_flag(Flag::NO_D6);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, at least one version of XB
|
||||
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.2, XB JP
|
||||
case 0x31: // GC Ep1&2 US v1.0, GC US v1.1, XB US
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case 0x32: // GC Ep1&2 EU 50Hz
|
||||
case 0x33: // GC Ep1&2 EU 60Hz
|
||||
case 0x34: // GC Ep1&2 JP v1.3
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
break;
|
||||
case 0x35: // GC Ep1&2 JP v1.4 (Plus)
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
case 0x3A: // GC Ep1&2 US v1.2 (Plus) GMK edition
|
||||
case 0x3A: // GC Ep1&2 US v1.2 (Plus) Return to Ragol
|
||||
this->set_flag(Flag::IS_CLIENT_CUSTOMIZATION);
|
||||
[[fallthrough]];
|
||||
case 0x36: // GC Ep1&2 US v1.2 (Plus)
|
||||
case 0x39: // GC Ep1&2 JP v1.5 (Plus)
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::CAN_RECEIVE_ENABLE_B2_QUEST);
|
||||
break;
|
||||
case 0x40: // GC Ep3 JP and Trial Edition (and BB)
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::HAS_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
// sub_version can't be used to tell JP final and Trial Edition apart; we
|
||||
// instead look at header.flag in the 61 command and set the version then.
|
||||
break;
|
||||
case 0x41: // GC Ep3 US (and BB)
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
case 0x41: // GC Ep3 US (and BB, but BB is handled above)
|
||||
case 0x42: // GC Ep3 EU 50Hz
|
||||
case 0x43: // GC Ep3 EU 60Hz
|
||||
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
|
||||
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
|
||||
this->set_flag(Flag::CAN_RECEIVE_ENABLE_B2_QUEST);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error(string_printf("unknown sub_version %" PRIX64, sub_version));
|
||||
throw runtime_error(phosg::string_printf("unknown sub_version %" PRIX64, sub_version));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,8 +186,8 @@ Client::Client(
|
||||
ServerBehavior server_behavior)
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
channel(bev, virtual_network_id, version, 1, nullptr, nullptr, this, "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
|
||||
log(phosg::string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
channel(bev, virtual_network_id, version, 1, nullptr, nullptr, this, "", phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::FG_GREEN),
|
||||
server_behavior(server_behavior),
|
||||
should_disconnect(false),
|
||||
should_send_to_lobby_server(false),
|
||||
@@ -239,8 +246,8 @@ Client::Client(
|
||||
// more annoying than helpful at this point.
|
||||
if ((s->hide_download_commands) &&
|
||||
((this->channel.version == Version::PC_PATCH) || (this->channel.version == Version::BB_PATCH))) {
|
||||
this->channel.terminal_recv_color = TerminalFormat::END;
|
||||
this->channel.terminal_send_color = TerminalFormat::END;
|
||||
this->channel.terminal_recv_color = phosg::TerminalFormat::END;
|
||||
this->channel.terminal_send_color = phosg::TerminalFormat::END;
|
||||
}
|
||||
|
||||
this->log.info("Created");
|
||||
@@ -267,24 +274,24 @@ void Client::update_channel_name() {
|
||||
auto player = this->character(false, false);
|
||||
if (player) {
|
||||
string name_str = player->disp.name.decode(this->language());
|
||||
this->channel.name = string_printf("C-%" PRIX64 " (%s) @ %s", this->id, name_str.c_str(), ip_str.c_str());
|
||||
this->channel.name = phosg::string_printf("C-%" PRIX64 " (%s) @ %s", this->id, name_str.c_str(), ip_str.c_str());
|
||||
} else {
|
||||
this->channel.name = string_printf("C-%" PRIX64 " @ %s", this->id, ip_str.c_str());
|
||||
this->channel.name = phosg::string_printf("C-%" PRIX64 " @ %s", this->id, ip_str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Client::reschedule_save_game_data_event() {
|
||||
if (this->version() == Version::BB_V4) {
|
||||
struct timeval tv = usecs_to_timeval(60000000); // 1 minute
|
||||
struct timeval tv = phosg::usecs_to_timeval(60000000); // 1 minute
|
||||
event_add(this->save_game_data_event.get(), &tv);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::reschedule_ping_and_timeout_events() {
|
||||
auto s = this->require_server_state();
|
||||
struct timeval ping_tv = usecs_to_timeval(s->client_ping_interval_usecs);
|
||||
struct timeval ping_tv = phosg::usecs_to_timeval(s->client_ping_interval_usecs);
|
||||
event_add(this->send_ping_event.get(), &ping_tv);
|
||||
struct timeval idle_tv = usecs_to_timeval(s->client_idle_timeout_usecs);
|
||||
struct timeval idle_tv = phosg::usecs_to_timeval(s->client_idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &idle_tv);
|
||||
}
|
||||
|
||||
@@ -384,7 +391,7 @@ bool Client::evaluate_quest_availability_expression(
|
||||
.v1_present = v1_present,
|
||||
};
|
||||
int64_t ret = expr->evaluate(env);
|
||||
if (this->log.should_log(LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
string expr_str = expr->str();
|
||||
this->log.info("Evaluated integral expression %s => %s", expr_str.c_str(), ret ? "TRUE" : "FALSE");
|
||||
}
|
||||
@@ -442,7 +449,7 @@ void Client::send_ping() {
|
||||
if (!is_patch(this->version())) {
|
||||
this->log.info("Sending ping command");
|
||||
// The game doesn't use this timestamp; we only use it for debugging purposes
|
||||
be_uint64_t timestamp = now();
|
||||
be_uint64_t timestamp = phosg::now();
|
||||
try {
|
||||
this->channel.send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t));
|
||||
} catch (const exception& e) {
|
||||
@@ -634,7 +641,7 @@ string Client::system_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return string_printf("system/players/system_%s.psosys", this->login->bb_license->username.c_str());
|
||||
return phosg::string_printf("system/players/system_%s.psosys", this->login->bb_license->username.c_str());
|
||||
}
|
||||
|
||||
string Client::character_filename(const std::string& bb_username, int8_t index) {
|
||||
@@ -644,11 +651,12 @@ string Client::character_filename(const std::string& bb_username, int8_t index)
|
||||
if (index < 0) {
|
||||
throw logic_error("character index is not set");
|
||||
}
|
||||
return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index);
|
||||
return phosg::string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index);
|
||||
}
|
||||
|
||||
string Client::backup_character_filename(uint32_t account_id, size_t index) {
|
||||
return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", account_id, index);
|
||||
string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
|
||||
return phosg::string_printf("system/players/backup_player_%" PRIu32 "_%zu.%s",
|
||||
account_id, index, is_ep3 ? "pso3char" : "psochar");
|
||||
}
|
||||
|
||||
string Client::character_filename(int8_t index) const {
|
||||
@@ -668,7 +676,7 @@ string Client::guild_card_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return string_printf("system/players/guild_cards_%s.psocard", this->login->bb_license->username.c_str());
|
||||
return phosg::string_printf("system/players/guild_cards_%s.psocard", this->login->bb_license->username.c_str());
|
||||
}
|
||||
|
||||
string Client::shared_bank_filename() const {
|
||||
@@ -678,7 +686,7 @@ string Client::shared_bank_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return string_printf("system/players/shared_bank_%s.psobank", this->login->bb_license->username.c_str());
|
||||
return phosg::string_printf("system/players/shared_bank_%s.psobank", this->login->bb_license->username.c_str());
|
||||
}
|
||||
|
||||
string Client::legacy_account_filename() const {
|
||||
@@ -688,7 +696,7 @@ string Client::legacy_account_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return string_printf("system/players/account_%s.nsa", this->login->bb_license->username.c_str());
|
||||
return phosg::string_printf("system/players/account_%s.nsa", this->login->bb_license->username.c_str());
|
||||
}
|
||||
|
||||
string Client::legacy_player_filename() const {
|
||||
@@ -701,7 +709,7 @@ string Client::legacy_player_filename() const {
|
||||
if (this->bb_character_index < 0) {
|
||||
throw logic_error("character index is not set");
|
||||
}
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"system/players/player_%s_%hhd.nsc",
|
||||
this->login->bb_license->username.c_str(),
|
||||
static_cast<int8_t>(this->bb_character_index + 1));
|
||||
@@ -737,8 +745,8 @@ void Client::load_all_files() {
|
||||
this->system_data = files_manager->get_system(sys_filename);
|
||||
if (this->system_data) {
|
||||
player_data_log.info("Using loaded system file %s", sys_filename.c_str());
|
||||
} else if (isfile(sys_filename)) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||
} else if (phosg::isfile(sys_filename)) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Loaded system data from %s", sys_filename.c_str());
|
||||
} else {
|
||||
@@ -750,33 +758,25 @@ void Client::load_all_files() {
|
||||
this->character_data = files_manager->get_character(char_filename);
|
||||
if (this->character_data) {
|
||||
player_data_log.info("Using loaded character file %s", char_filename.c_str());
|
||||
} else if (isfile(char_filename)) {
|
||||
auto f = fopen_unique(char_filename, "rb");
|
||||
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
||||
if (header.size != 0x399C) {
|
||||
throw runtime_error("incorrect size in character file header");
|
||||
}
|
||||
if (header.command != 0x00E7) {
|
||||
throw runtime_error("incorrect command in character file header");
|
||||
}
|
||||
if (header.flag != 0x00000000) {
|
||||
throw runtime_error("incorrect flag in character file header");
|
||||
}
|
||||
static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBFullSystemFile) == 0x3994, ".psochar size is incorrect");
|
||||
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||
} else if (phosg::isfile(char_filename)) {
|
||||
auto psochar = load_psochar(char_filename, !this->system_data);
|
||||
this->character_data = psochar.character_file;
|
||||
files_manager->set_character(char_filename, this->character_data);
|
||||
player_data_log.info("Loaded character data from %s", char_filename.c_str());
|
||||
|
||||
// If there was no .psosys file, load the system file from the .psochar
|
||||
// If there was no .psosys file, use the system file from the .psochar
|
||||
// file instead
|
||||
if (!this->system_data) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(freadx<PSOBBBaseSystemFile>(f.get()));
|
||||
if (!psochar.system_file) {
|
||||
throw logic_error("account system data not present, and also not loaded from psochar file");
|
||||
}
|
||||
this->system_data = psochar.system_file;
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Loaded system data from %s", char_filename.c_str());
|
||||
}
|
||||
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->system_data->base.language = this->language();
|
||||
this->system_data->language = this->language();
|
||||
|
||||
} else {
|
||||
player_data_log.info("Character file is missing: %s", char_filename.c_str());
|
||||
@@ -787,8 +787,8 @@ void Client::load_all_files() {
|
||||
this->guild_card_data = files_manager->get_guild_card(card_filename);
|
||||
if (this->guild_card_data) {
|
||||
player_data_log.info("Using loaded Guild Card file %s", card_filename.c_str());
|
||||
} else if (isfile(card_filename)) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||
} else if (phosg::isfile(card_filename)) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||
files_manager->set_guild_card(card_filename, this->guild_card_data);
|
||||
player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str());
|
||||
} else {
|
||||
@@ -799,13 +799,13 @@ void Client::load_all_files() {
|
||||
if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
|
||||
string nsa_filename = this->legacy_account_filename();
|
||||
shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
||||
if (isfile(nsa_filename)) {
|
||||
nsa_data = make_shared<LegacySavedAccountDataBB>(load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
||||
if (phosg::isfile(nsa_filename)) {
|
||||
nsa_data = make_shared<LegacySavedAccountDataBB>(phosg::load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
||||
if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) {
|
||||
throw runtime_error("account data header is incorrect");
|
||||
}
|
||||
if (!this->system_data) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file.base);
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str());
|
||||
}
|
||||
@@ -818,6 +818,13 @@ void Client::load_all_files() {
|
||||
|
||||
if (!this->system_data) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
||||
auto s = this->require_server_state();
|
||||
if (s->bb_default_keyboard_config) {
|
||||
this->system_data->key_config = *s->bb_default_keyboard_config;
|
||||
}
|
||||
if (s->bb_default_joystick_config) {
|
||||
this->system_data->joystick_config = *s->bb_default_joystick_config;
|
||||
}
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Created new system data");
|
||||
}
|
||||
@@ -829,7 +836,7 @@ void Client::load_all_files() {
|
||||
|
||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||
string nsc_filename = this->legacy_player_filename();
|
||||
auto nsc_data = load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
||||
auto nsc_data = phosg::load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
||||
if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) {
|
||||
nsc_data.signature = LegacySavedPlayerDataBB::SIGNATURE_V0;
|
||||
nsc_data.unused.clear();
|
||||
@@ -885,7 +892,7 @@ void Client::load_all_files() {
|
||||
this->character_data->disp.name.clear_after_bytes(0x18);
|
||||
this->login->account->auto_reply_message = this->character_data->auto_reply.decode();
|
||||
this->login->account->save();
|
||||
this->last_play_time_update = now();
|
||||
this->last_play_time_update = phosg::now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,7 +917,7 @@ void Client::save_all() {
|
||||
}
|
||||
if (this->external_bank) {
|
||||
string filename = this->shared_bank_filename();
|
||||
save_object_file<PlayerBank200>(filename, *this->external_bank);
|
||||
phosg::save_object_file<PlayerBank200>(filename, *this->external_bank);
|
||||
player_data_log.info("Saved shared bank file %s", filename.c_str());
|
||||
}
|
||||
if (this->external_bank_character) {
|
||||
@@ -926,7 +933,7 @@ void Client::save_system_file() const {
|
||||
throw logic_error("no system file loaded");
|
||||
}
|
||||
string filename = this->system_filename();
|
||||
save_object_file(filename, *this->system_data);
|
||||
phosg::save_object_file(filename, *this->system_data);
|
||||
player_data_log.info("Saved system file %s", filename.c_str());
|
||||
}
|
||||
|
||||
@@ -934,26 +941,17 @@ void Client::save_character_file(
|
||||
const string& filename,
|
||||
shared_ptr<const PSOBBBaseSystemFile> system,
|
||||
shared_ptr<const PSOBBCharacterFile> character) {
|
||||
auto f = fopen_unique(filename, "wb");
|
||||
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
|
||||
fwritex(f.get(), header);
|
||||
fwritex(f.get(), *character);
|
||||
fwritex(f.get(), *system);
|
||||
// TODO: Technically, we should write the actual team membership struct to the
|
||||
// file here, but that would cause Client to depend on Account, which
|
||||
// it currently does not. This data doesn't matter at all for correctness
|
||||
// within newserv, since it ignores this data entirely and instead generates
|
||||
// the membership struct from the team ID in the Account and the team's state.
|
||||
// So, writing correct data here would mostly be for compatibility with other
|
||||
// PSO servers. But if the other server is newserv, then this data would be
|
||||
// used anyway, and if it's not, then it would presumably have a different set
|
||||
// of teams with a different set of team IDs anyway, so the membership struct
|
||||
// here would be useless either way.
|
||||
static const PSOBBTeamMembership empty_membership;
|
||||
fwritex(f.get(), empty_membership);
|
||||
save_psochar(filename, system, character);
|
||||
player_data_log.info("Saved character file %s", filename.c_str());
|
||||
}
|
||||
|
||||
void Client::save_ep3_character_file(
|
||||
const string& filename,
|
||||
const PSOGCEp3CharacterFile::Character& character) {
|
||||
phosg::save_file(filename, &character, sizeof(character));
|
||||
player_data_log.info("Saved Episode 3 character file %s", filename.c_str());
|
||||
}
|
||||
|
||||
void Client::save_character_file() {
|
||||
if (!this->system_data.get()) {
|
||||
throw logic_error("no system file loaded");
|
||||
@@ -964,7 +962,7 @@ void Client::save_character_file() {
|
||||
if (this->should_update_play_time) {
|
||||
// This is slightly inaccurate, since fractions of a second are truncated
|
||||
// off each time we save. I'm lazy, so insert shrug emoji here.
|
||||
uint64_t t = now();
|
||||
uint64_t t = phosg::now();
|
||||
uint64_t seconds = (t - this->last_play_time_update) / 1000000;
|
||||
this->character_data->play_time_seconds += seconds;
|
||||
player_data_log.info("Added %" PRIu64 " seconds to play time", seconds);
|
||||
@@ -979,28 +977,27 @@ void Client::save_guild_card_file() const {
|
||||
throw logic_error("no Guild Card file loaded");
|
||||
}
|
||||
string filename = this->guild_card_filename();
|
||||
save_object_file(filename, *this->guild_card_data);
|
||||
phosg::save_object_file(filename, *this->guild_card_data);
|
||||
player_data_log.info("Saved Guild Card file %s", filename.c_str());
|
||||
}
|
||||
|
||||
void Client::load_backup_character(uint32_t account_id, size_t index) {
|
||||
string filename = this->backup_character_filename(account_id, index);
|
||||
auto f = fopen_unique(filename, "rb");
|
||||
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
||||
if (header.size != 0x399C) {
|
||||
throw runtime_error("incorrect size in character file header");
|
||||
}
|
||||
if (header.command != 0x00E7) {
|
||||
throw runtime_error("incorrect command in character file header");
|
||||
}
|
||||
if (header.flag != 0x00000000) {
|
||||
throw runtime_error("incorrect flag in character file header");
|
||||
}
|
||||
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||
string filename = this->backup_character_filename(account_id, index, false);
|
||||
this->character_data = load_psochar(filename, false).character_file;
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->v1_v2_last_reported_disp.reset();
|
||||
}
|
||||
|
||||
shared_ptr<PSOGCEp3CharacterFile::Character> Client::load_ep3_backup_character(uint32_t account_id, size_t index) {
|
||||
string filename = this->backup_character_filename(account_id, index, true);
|
||||
auto ch = make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
|
||||
this->character_data = PSOBBCharacterFile::create_from_ep3(*ch);
|
||||
this->ep3_config = make_shared<Episode3::PlayerConfig>(ch->ep3_config);
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->v1_v2_last_reported_disp.reset();
|
||||
return ch;
|
||||
}
|
||||
|
||||
void Client::save_and_unload_character() {
|
||||
if (this->character_data) {
|
||||
this->save_character_file();
|
||||
@@ -1018,6 +1015,10 @@ PlayerBank200& Client::current_bank() {
|
||||
return this->character()->bank;
|
||||
}
|
||||
|
||||
const PlayerBank200& Client::current_bank() const {
|
||||
return const_cast<Client*>(this)->current_bank();
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBCharacterFile> Client::current_bank_character() {
|
||||
return this->external_bank_character ? this->external_bank_character : this->character();
|
||||
}
|
||||
@@ -1025,7 +1026,7 @@ std::shared_ptr<PSOBBCharacterFile> Client::current_bank_character() {
|
||||
void Client::use_default_bank() {
|
||||
if (this->external_bank) {
|
||||
string filename = this->shared_bank_filename();
|
||||
save_object_file<PlayerBank200>(filename, *this->external_bank);
|
||||
phosg::save_object_file<PlayerBank200>(filename, *this->external_bank);
|
||||
this->external_bank.reset();
|
||||
player_data_log.info("Detached shared bank %s", filename.c_str());
|
||||
}
|
||||
@@ -1046,8 +1047,8 @@ bool Client::use_shared_bank() {
|
||||
if (this->external_bank) {
|
||||
player_data_log.info("Using loaded shared bank %s", filename.c_str());
|
||||
return true;
|
||||
} else if (isfile(filename)) {
|
||||
this->external_bank = make_shared<PlayerBank200>(load_object_file<PlayerBank200>(filename));
|
||||
} else if (phosg::isfile(filename)) {
|
||||
this->external_bank = make_shared<PlayerBank200>(phosg::load_object_file<PlayerBank200>(filename));
|
||||
files_manager->set_bank(filename, this->external_bank);
|
||||
player_data_log.info("Loaded shared bank %s", filename.c_str());
|
||||
return true;
|
||||
@@ -1069,19 +1070,8 @@ void Client::use_character_bank(int8_t index) {
|
||||
if (this->external_bank_character) {
|
||||
this->external_bank_character_index = index;
|
||||
player_data_log.info("Using loaded character file %s for external bank", filename.c_str());
|
||||
} else if (isfile(filename)) {
|
||||
auto f = fopen_unique(filename, "rb");
|
||||
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
||||
if (header.size != 0x399C) {
|
||||
throw runtime_error("incorrect size in character file header");
|
||||
}
|
||||
if (header.command != 0x00E7) {
|
||||
throw runtime_error("incorrect command in character file header");
|
||||
}
|
||||
if (header.flag != 0x00000000) {
|
||||
throw runtime_error("incorrect flag in character file header");
|
||||
}
|
||||
this->external_bank_character = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||
} else if (phosg::isfile(filename)) {
|
||||
this->external_bank_character = load_psochar(filename, false).character_file;
|
||||
this->update_character_data_after_load(this->external_bank_character);
|
||||
this->external_bank_character_index = index;
|
||||
files_manager->set_character(filename, this->external_bank_character);
|
||||
@@ -1107,11 +1097,11 @@ void Client::print_inventory(FILE* stream) const {
|
||||
|
||||
void Client::print_bank(FILE* stream) const {
|
||||
auto s = this->require_server_state();
|
||||
auto p = this->character();
|
||||
fprintf(stream, "[PlayerBank] Meseta: %" PRIu32 "\n", p->bank.meseta.load());
|
||||
fprintf(stream, "[PlayerBank] %" PRIu32 " items\n", p->bank.num_items.load());
|
||||
for (size_t x = 0; x < p->bank.num_items; x++) {
|
||||
const auto& item = p->bank.items[x];
|
||||
auto bank = this->current_bank();
|
||||
fprintf(stream, "[PlayerBank] Meseta: %" PRIu32 "\n", bank.meseta.load());
|
||||
fprintf(stream, "[PlayerBank] %" PRIu32 " items\n", bank.num_items.load());
|
||||
for (size_t x = 0; x < bank.num_items; x++) {
|
||||
const auto& item = bank.items[x];
|
||||
const char* present_token = item.present ? "" : " (missing present flag)";
|
||||
auto hex = item.data.hex();
|
||||
auto name = s->describe_item(this->version(), item.data, false);
|
||||
|
||||
+16
-8
@@ -35,7 +35,7 @@ public:
|
||||
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
|
||||
// in the high bits) but that would require re-recording or manually
|
||||
// rewriting all the tests
|
||||
CLIENT_SIDE_MASK = 0xFF3CFFFF7C0FFFFB,
|
||||
CLIENT_SIDE_MASK = 0xE73CFFFF7C0BFFFB,
|
||||
|
||||
// Version-related flags
|
||||
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
|
||||
@@ -44,11 +44,12 @@ public:
|
||||
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
|
||||
|
||||
// Flags describing the behavior for send_function_call
|
||||
NO_SEND_FUNCTION_CALL = 0x0000000000001000,
|
||||
HAS_SEND_FUNCTION_CALL = 0x0000000000001000,
|
||||
ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000,
|
||||
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000,
|
||||
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000,
|
||||
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000,
|
||||
CAN_RECEIVE_ENABLE_B2_QUEST = 0x0000000000020000,
|
||||
AWAITING_ENABLE_B2_QUEST = 0x0000000000040000, // Server-side only
|
||||
|
||||
// State flags
|
||||
LOADING = 0x0000000000100000, // Server-side only
|
||||
@@ -72,6 +73,7 @@ public:
|
||||
SHOULD_SEND_ENABLE_SAVE = 0x0004000000000000,
|
||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||
IS_CLIENT_CUSTOMIZATION = 0x0100000000000000,
|
||||
EP3_ALLOW_6xBC = 0x1000000000000000, // Server-side only
|
||||
|
||||
// Cheat mode and option flags
|
||||
INFINITE_HP_ENABLED = 0x0000000200000000,
|
||||
@@ -79,6 +81,7 @@ public:
|
||||
DEBUG_ENABLED = 0x0000000800000000,
|
||||
ITEM_DROP_NOTIFICATIONS_1 = 0x0010000000000000,
|
||||
ITEM_DROP_NOTIFICATIONS_2 = 0x0020000000000000,
|
||||
FORCE_BATTLE_MODE_GAME = 0x0800000000000000, // Server-side only
|
||||
|
||||
// Proxy option flags
|
||||
PROXY_SAVE_FILES = 0x0000001000000000,
|
||||
@@ -93,6 +96,7 @@ public:
|
||||
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
|
||||
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
|
||||
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
|
||||
PROXY_VIRTUAL_CLIENT = 0x0400000000000000,
|
||||
// clang-format on
|
||||
};
|
||||
enum class ItemDropNotificationMode {
|
||||
@@ -145,7 +149,7 @@ public:
|
||||
|
||||
template <size_t Bytes>
|
||||
void parse_from(const parray<uint8_t, Bytes>& data) {
|
||||
StringReader r(data.data(), data.size());
|
||||
phosg::StringReader r(data.data(), data.size());
|
||||
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
|
||||
throw std::invalid_argument("config signature is incorrect");
|
||||
}
|
||||
@@ -161,7 +165,7 @@ public:
|
||||
|
||||
template <size_t Bytes>
|
||||
void serialize_into(parray<uint8_t, Bytes>& data) const {
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put_u32l(CLIENT_CONFIG_MAGIC);
|
||||
w.put_u32l(this->specific_version);
|
||||
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
|
||||
@@ -182,7 +186,7 @@ public:
|
||||
|
||||
std::weak_ptr<Server> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
phosg::PrefixedLogger log;
|
||||
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
@@ -256,7 +260,6 @@ public:
|
||||
|
||||
// Miscellaneous (used by chat commands)
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
RecentSwitchFlags recent_switch_flags; // used for switch assist
|
||||
bool can_chat;
|
||||
struct PendingCharacterExport {
|
||||
std::shared_ptr<const Account> dest_account;
|
||||
@@ -362,7 +365,7 @@ public:
|
||||
|
||||
std::string system_filename() const;
|
||||
static std::string character_filename(const std::string& bb_username, int8_t index);
|
||||
static std::string backup_character_filename(uint32_t account_id, size_t index);
|
||||
static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3);
|
||||
std::string character_filename(int8_t index = -1) const;
|
||||
std::string guild_card_filename() const;
|
||||
std::string shared_bank_filename() const;
|
||||
@@ -376,14 +379,19 @@ public:
|
||||
const std::string& filename,
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> sys,
|
||||
std::shared_ptr<const PSOBBCharacterFile> character);
|
||||
static void save_ep3_character_file(
|
||||
const std::string& filename,
|
||||
const PSOGCEp3CharacterFile::Character& character);
|
||||
// Note: This function is not const because it updates the player's play time.
|
||||
void save_character_file();
|
||||
void save_guild_card_file() const;
|
||||
|
||||
void load_backup_character(uint32_t account_id, size_t index);
|
||||
std::shared_ptr<PSOGCEp3CharacterFile::Character> load_ep3_backup_character(uint32_t account_id, size_t index);
|
||||
void save_and_unload_character();
|
||||
|
||||
PlayerBank200& current_bank();
|
||||
const PlayerBank200& current_bank() const;
|
||||
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
|
||||
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
|
||||
void use_character_bank(int8_t bb_character_index);
|
||||
|
||||
+537
-356
File diff suppressed because it is too large
Load Diff
+43
-45
@@ -4,12 +4,13 @@
|
||||
#include "EnemyType.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <typename IntT, size_t Count>
|
||||
JSON to_json(const parray<IntT, Count>& v) {
|
||||
auto ret = JSON::list();
|
||||
phosg::JSON to_json(const parray<IntT, Count>& v) {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (size_t z = 0; z < Count; z++) {
|
||||
ret.emplace_back(v[z]);
|
||||
}
|
||||
@@ -17,7 +18,7 @@ JSON to_json(const parray<IntT, Count>& v) {
|
||||
}
|
||||
|
||||
template <typename IntT, size_t Count>
|
||||
void from_json_into(const JSON& json, parray<IntT, Count>& ret) {
|
||||
void from_json_into(const phosg::JSON& json, parray<IntT, Count>& ret) {
|
||||
if (json.size() != Count) {
|
||||
throw runtime_error("incorrect array length");
|
||||
}
|
||||
@@ -27,8 +28,8 @@ void from_json_into(const JSON& json, parray<IntT, Count>& ret) {
|
||||
}
|
||||
|
||||
template <typename IntT, size_t Count>
|
||||
JSON to_json(const parray<CommonItemSet::Table::Range<IntT>, Count>& v) {
|
||||
auto ret = JSON::list();
|
||||
phosg::JSON to_json(const parray<CommonItemSet::Table::Range<IntT>, Count>& v) {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (size_t z = 0; z < Count; z++) {
|
||||
ret.emplace_back(to_json(v[z]));
|
||||
}
|
||||
@@ -36,7 +37,7 @@ JSON to_json(const parray<CommonItemSet::Table::Range<IntT>, Count>& v) {
|
||||
}
|
||||
|
||||
template <typename IntT, size_t Count>
|
||||
void from_json_into(const JSON& json, parray<CommonItemSet::Table::Range<IntT>, Count>& ret) {
|
||||
void from_json_into(const phosg::JSON& json, parray<CommonItemSet::Table::Range<IntT>, Count>& ret) {
|
||||
if (json.size() != Count) {
|
||||
throw runtime_error("incorrect array length");
|
||||
}
|
||||
@@ -46,16 +47,16 @@ void from_json_into(const JSON& json, parray<CommonItemSet::Table::Range<IntT>,
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
JSON to_json(const CommonItemSet::Table::Range<IntT>& v) {
|
||||
phosg::JSON to_json(const CommonItemSet::Table::Range<IntT>& v) {
|
||||
if (v.min == v.max) {
|
||||
return JSON(v.min);
|
||||
return phosg::JSON(v.min);
|
||||
} else {
|
||||
return JSON::list({v.min, v.max});
|
||||
return phosg::JSON::list({v.min, v.max});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void from_json_into(const JSON& json, CommonItemSet::Table::Range<IntT>& ret) {
|
||||
void from_json_into(const phosg::JSON& json, CommonItemSet::Table::Range<IntT>& ret) {
|
||||
if (json.is_int()) {
|
||||
IntT v = json.as_int();
|
||||
ret.min = v;
|
||||
@@ -71,8 +72,8 @@ void from_json_into(const JSON& json, CommonItemSet::Table::Range<IntT>& ret) {
|
||||
}
|
||||
|
||||
template <typename IntT, size_t Count1, size_t Count2>
|
||||
JSON to_json(const parray<parray<IntT, Count2>, Count1>& v) {
|
||||
auto ret = JSON::list();
|
||||
phosg::JSON to_json(const parray<parray<IntT, Count2>, Count1>& v) {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (size_t z = 0; z < Count1; z++) {
|
||||
ret.emplace_back(to_json(v[z]));
|
||||
}
|
||||
@@ -80,7 +81,7 @@ JSON to_json(const parray<parray<IntT, Count2>, Count1>& v) {
|
||||
}
|
||||
|
||||
template <typename IntT, size_t Count1, size_t Count2>
|
||||
void from_json_into(const JSON& json, parray<parray<IntT, Count2>, Count1>& ret) {
|
||||
void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1>& ret) {
|
||||
if (json.size() != Count1) {
|
||||
throw runtime_error("incorrect array length");
|
||||
}
|
||||
@@ -90,7 +91,7 @@ void from_json_into(const JSON& json, parray<parray<IntT, Count2>, Count1>& ret)
|
||||
}
|
||||
|
||||
template <typename IntT, size_t Count1, size_t Count2>
|
||||
void from_json_into(const JSON& json, parray<parray<CommonItemSet::Table::Range<IntT>, Count2>, Count1>& ret) {
|
||||
void from_json_into(const phosg::JSON& json, parray<parray<CommonItemSet::Table::Range<IntT>, Count2>, Count1>& ret) {
|
||||
if (json.size() != Count1) {
|
||||
throw runtime_error("incorrect array length");
|
||||
}
|
||||
@@ -99,7 +100,7 @@ void from_json_into(const JSON& json, parray<parray<CommonItemSet::Table::Range<
|
||||
}
|
||||
}
|
||||
|
||||
CommonItemSet::Table::Table(const JSON& json, Episode episode)
|
||||
CommonItemSet::Table::Table(const phosg::JSON& json, Episode episode)
|
||||
: episode(episode) {
|
||||
from_json_into(json.at("BaseWeaponTypeProbTable"), this->base_weapon_type_prob_table);
|
||||
from_json_into(json.at("SubtypeBaseTable"), this->subtype_base_table);
|
||||
@@ -128,7 +129,7 @@ CommonItemSet::Table::Table(const JSON& json, Episode episode)
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string name = string_printf("%s:%s", abbreviation_for_episode(episode), name_for_enum(type));
|
||||
string name = phosg::string_printf("%s:%s", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
from_json_into(*enemy_meseta_ranges_json.at(name), this->enemy_meseta_ranges[z]);
|
||||
this->enemy_type_drop_probs[z] = enemy_type_drop_probs_json.at(name)->as_int();
|
||||
this->enemy_item_classes[z] = enemy_item_classes_json.at(name)->as_int();
|
||||
@@ -170,7 +171,7 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
if (!enemies_str.empty()) {
|
||||
enemies_str += ", ";
|
||||
}
|
||||
enemies_str += name_for_enum(enemy_type);
|
||||
enemies_str += phosg::name_for_enum(enemy_type);
|
||||
}
|
||||
if (drop_probs[z]) {
|
||||
fprintf(stream, " %02zX %5hu %5hu %3hhu%% %02hX:%s %s\n",
|
||||
@@ -335,22 +336,22 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
}
|
||||
}
|
||||
|
||||
JSON CommonItemSet::Table::json() const {
|
||||
JSON enemy_meseta_ranges_json = JSON::dict();
|
||||
JSON enemy_type_drop_probs_json = JSON::dict();
|
||||
JSON enemy_item_classes_json = JSON::dict();
|
||||
phosg::JSON CommonItemSet::Table::json() const {
|
||||
phosg::JSON enemy_meseta_ranges_json = phosg::JSON::dict();
|
||||
phosg::JSON enemy_type_drop_probs_json = phosg::JSON::dict();
|
||||
phosg::JSON enemy_item_classes_json = phosg::JSON::dict();
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string name = string_printf("%s:%s", abbreviation_for_episode(episode), name_for_enum(type));
|
||||
string name = phosg::string_printf("%s:%s", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z]));
|
||||
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]);
|
||||
enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return JSON::dict({
|
||||
return phosg::JSON::dict({
|
||||
{"BaseWeaponTypeProbTable", to_json(this->base_weapon_type_prob_table)},
|
||||
{"SubtypeBaseTable", to_json(this->subtype_base_table)},
|
||||
{"SubtypeAreaLengthTable", to_json(this->subtype_area_length_table)},
|
||||
@@ -376,16 +377,16 @@ JSON CommonItemSet::Table::json() const {
|
||||
});
|
||||
}
|
||||
|
||||
JSON CommonItemSet::json() const {
|
||||
auto modes_dict = JSON::dict();
|
||||
phosg::JSON CommonItemSet::json() const {
|
||||
auto modes_dict = phosg::JSON::dict();
|
||||
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (const auto& mode : modes) {
|
||||
auto episodes_dict = JSON::dict();
|
||||
auto episodes_dict = phosg::JSON::dict();
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (const auto& episode : episodes) {
|
||||
auto difficulty_dict = JSON::dict();
|
||||
auto difficulty_dict = phosg::JSON::dict();
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
auto section_id_dict = JSON::dict();
|
||||
auto section_id_dict = phosg::JSON::dict();
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
auto table = this->get_table(episode, mode, difficulty, section_id);
|
||||
@@ -423,7 +424,7 @@ void CommonItemSet::print(FILE* stream) const {
|
||||
}
|
||||
}
|
||||
|
||||
CommonItemSet::Table::Table(const StringReader& r, bool is_big_endian, bool is_v3, Episode episode)
|
||||
CommonItemSet::Table::Table(const phosg::StringReader& r, bool is_big_endian, bool is_v3, Episode episode)
|
||||
: episode(episode) {
|
||||
if (is_big_endian) {
|
||||
this->parse_itempt_t<true>(r, is_v3);
|
||||
@@ -432,12 +433,9 @@ CommonItemSet::Table::Table(const StringReader& r, bool is_big_endian, bool is_v
|
||||
}
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
const auto& offsets = r.pget<OffsetsT<IsBigEndian>>(r.pget<U32T>(r.size() - 0x10));
|
||||
template <bool BE>
|
||||
void CommonItemSet::Table::parse_itempt_t(const phosg::StringReader& r, bool is_v3) {
|
||||
const auto& offsets = r.pget<OffsetsT<BE>>(r.pget<U32T<BE>>(r.size() - 0x10));
|
||||
|
||||
this->base_weapon_type_prob_table = r.pget<parray<uint8_t, 0x0C>>(offsets.base_weapon_type_prob_table_offset);
|
||||
this->subtype_base_table = r.pget<parray<int8_t, 0x0C>>(offsets.subtype_base_table_offset);
|
||||
@@ -445,14 +443,14 @@ void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) {
|
||||
this->grind_prob_table = r.pget<parray<parray<uint8_t, 4>, 9>>(offsets.grind_prob_table_offset);
|
||||
this->armor_shield_type_index_prob_table = r.pget<parray<uint8_t, 0x05>>(offsets.armor_shield_type_index_prob_table_offset);
|
||||
this->armor_slot_count_prob_table = r.pget<parray<uint8_t, 0x05>>(offsets.armor_slot_count_prob_table_offset);
|
||||
const auto& data = r.pget<parray<Range<U16T>, 0x64>>(offsets.enemy_meseta_ranges_offset);
|
||||
const auto& data = r.pget<parray<Range<U16T<BE>>, 0x64>>(offsets.enemy_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->enemy_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
this->enemy_type_drop_probs = r.pget<parray<uint8_t, 0x64>>(offsets.enemy_type_drop_probs_offset);
|
||||
this->enemy_item_classes = r.pget<parray<uint8_t, 0x64>>(offsets.enemy_item_classes_offset);
|
||||
{
|
||||
const auto& data = r.pget<parray<Range<U16T>, 0x0A>>(offsets.box_meseta_ranges_offset);
|
||||
const auto& data = r.pget<parray<Range<U16T<BE>>, 0x0A>>(offsets.box_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->box_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
@@ -466,7 +464,7 @@ void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) {
|
||||
}
|
||||
}
|
||||
} else { // V3
|
||||
const auto& data = r.pget<parray<parray<U16T, 6>, 0x17>>(offsets.bonus_value_prob_table_offset);
|
||||
const auto& data = r.pget<parray<parray<U16T<BE>, 6>, 0x17>>(offsets.bonus_value_prob_table_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
for (size_t x = 0; x < data[z].size(); x++) {
|
||||
this->bonus_value_prob_table[z][x] = data[z][x];
|
||||
@@ -478,7 +476,7 @@ void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) {
|
||||
this->special_mult = r.pget<parray<uint8_t, 0x0A>>(offsets.special_mult_offset);
|
||||
this->special_percent = r.pget<parray<uint8_t, 0x0A>>(offsets.special_percent_offset);
|
||||
{
|
||||
const auto& data = r.pget<parray<parray<U16T, 0x0A>, 0x1C>>(offsets.tool_class_prob_table_offset);
|
||||
const auto& data = r.pget<parray<parray<U16T<BE>, 0x0A>, 0x1C>>(offsets.tool_class_prob_table_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
for (size_t x = 0; x < data[z].size(); x++) {
|
||||
this->tool_class_prob_table[z][x] = data[z][x];
|
||||
@@ -505,7 +503,7 @@ shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
||||
try {
|
||||
return this->tables.at(this->key_for_table(episode, mode, difficulty, secid));
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(string_printf("common item table not available for episode=%s, mode=%s, difficulty=%hu, secid=%hu",
|
||||
throw runtime_error(phosg::string_printf("common item table not available for episode=%s, mode=%s, difficulty=%hu, secid=%hu",
|
||||
name_for_episode(episode), name_for_mode(mode), difficulty, secid));
|
||||
}
|
||||
}
|
||||
@@ -518,7 +516,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
||||
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto entry = pt_afs.get(difficulty * 10 + section_id);
|
||||
StringReader r(entry.first, entry.second);
|
||||
phosg::StringReader r(entry.first, entry.second);
|
||||
auto table = make_shared<Table>(r, false, false, Episode::EP1);
|
||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table);
|
||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table);
|
||||
@@ -556,7 +554,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
default:
|
||||
throw runtime_error("invalid episode");
|
||||
}
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"ItemPT%s%s%c%1hhu.rel",
|
||||
is_challenge ? "c" : "",
|
||||
episode_token,
|
||||
@@ -568,7 +566,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
for (Episode episode : episodes) {
|
||||
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
StringReader r;
|
||||
phosg::StringReader r;
|
||||
try {
|
||||
r = gsl.get_reader(filename_for_table(episode, difficulty, section_id, false));
|
||||
} catch (const exception&) {
|
||||
@@ -602,7 +600,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
}
|
||||
}
|
||||
|
||||
JSONCommonItemSet::JSONCommonItemSet(const JSON& json) {
|
||||
JSONCommonItemSet::JSONCommonItemSet(const phosg::JSON& json) {
|
||||
for (const auto& mode_it : json.as_dict()) {
|
||||
static const unordered_map<string, GameMode> mode_keys(
|
||||
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
||||
@@ -791,7 +789,7 @@ const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_bonus_delta_prob_
|
||||
}
|
||||
|
||||
int8_t TekkerAdjustmentSet::get_luck(uint32_t start_offset, uint8_t delta_index) const {
|
||||
StringReader sub_r = r.sub(start_offset);
|
||||
phosg::StringReader sub_r = r.sub(start_offset);
|
||||
while (!sub_r.eof()) {
|
||||
const auto& entry = sub_r.get<LuckTableEntry>();
|
||||
if (entry.delta_index == 0xFF) {
|
||||
|
||||
+31
-33
@@ -8,14 +8,15 @@
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
class CommonItemSet {
|
||||
public:
|
||||
class Table {
|
||||
public:
|
||||
Table() = delete;
|
||||
Table(const JSON& json, Episode episode);
|
||||
Table(const StringReader& r, bool big_endian, bool is_v3, Episode episode);
|
||||
Table(const phosg::JSON& json, Episode episode);
|
||||
Table(const phosg::StringReader& r, bool big_endian, bool is_v3, Episode episode);
|
||||
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
@@ -47,18 +48,15 @@ public:
|
||||
parray<uint8_t, 0x0A> unit_max_stars_table;
|
||||
parray<parray<uint8_t, 10>, 7> box_item_class_prob_table;
|
||||
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
void print(FILE* stream) const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
void parse_itempt_t(const StringReader& r, bool is_v3);
|
||||
template <bool BE>
|
||||
void parse_itempt_t(const phosg::StringReader& r, bool is_v3);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
struct OffsetsT {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
// This data structure uses index probability tables in multiple places. An
|
||||
// index probability table is a table where each entry holds the probability
|
||||
// that that entry's index is used. For example, if the armor slot count
|
||||
@@ -80,7 +78,7 @@ public:
|
||||
// The indexes in this table correspond to the non-rare weapon types 01
|
||||
// through 0C (Saber through Wand).
|
||||
// V2/V3: -> parray<uint8_t, 0x0C>
|
||||
/* 00 */ U32T base_weapon_type_prob_table_offset;
|
||||
/* 00 */ U32T<BE> base_weapon_type_prob_table_offset;
|
||||
|
||||
// This table specifies the base subtype for each weapon type. Negative
|
||||
// values here mean that the weapon cannot be found in the first N areas (so
|
||||
@@ -90,7 +88,7 @@ public:
|
||||
// of weapon that actually appears depends on this value and a value from
|
||||
// the following table.
|
||||
// V2/V3: -> parray<int8_t, 0x0C>
|
||||
/* 04 */ U32T subtype_base_table_offset;
|
||||
/* 04 */ U32T<BE> subtype_base_table_offset;
|
||||
|
||||
// This table specifies how many areas each weapon subtype appears in. For
|
||||
// example, if Sword (subtype 02, which is index 1 in this table and the
|
||||
@@ -99,7 +97,7 @@ public:
|
||||
// through Mine 1), and Gigush (the next sword subtype) can be found in Mine
|
||||
// 1 through Ruins 3.
|
||||
// V2/V3: -> parray<uint8_t, 0x0C>
|
||||
/* 08 */ U32T subtype_area_length_table_offset;
|
||||
/* 08 */ U32T<BE> subtype_area_length_table_offset;
|
||||
|
||||
// This index probability table specifies how likely each possible grind
|
||||
// value is. The table is indexed as [grind][subtype_area_index], where the
|
||||
@@ -114,28 +112,28 @@ public:
|
||||
// ...
|
||||
// C1 C2 C3 M1 // (Episode 1 area values from the example for reference)
|
||||
// V2/V3: -> parray<parray<uint8_t, 4>, 9>
|
||||
/* 0C */ U32T grind_prob_table_offset;
|
||||
/* 0C */ U32T<BE> grind_prob_table_offset;
|
||||
|
||||
// TODO: Figure out exactly how this table is used. Anchor: 80106D34
|
||||
// V2/V3: -> parray<uint8_t, 0x05>
|
||||
/* 10 */ U32T armor_shield_type_index_prob_table_offset;
|
||||
/* 10 */ U32T<BE> armor_shield_type_index_prob_table_offset;
|
||||
|
||||
// This index probability table specifies how common each possible slot
|
||||
// count is for armor drops.
|
||||
// V2/V3: -> parray<uint8_t, 0x05>
|
||||
/* 14 */ U32T armor_slot_count_prob_table_offset;
|
||||
/* 14 */ U32T<BE> armor_slot_count_prob_table_offset;
|
||||
|
||||
// This array (indexed by enemy_type) specifies the range of meseta values
|
||||
// that each enemy can drop.
|
||||
// V2/V3: -> parray<Range<U16T>, 0x64>
|
||||
/* 18 */ U32T enemy_meseta_ranges_offset;
|
||||
/* 18 */ U32T<BE> enemy_meseta_ranges_offset;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the percent
|
||||
// chance that the enemy drops anything at all. (This check is done before
|
||||
// the rare drop check, so the chance of getting a rare item from an enemy
|
||||
// is essentially this probability multiplied by the rare drop rate.)
|
||||
// V2/V3: -> parray<uint8_t, 0x64>
|
||||
/* 1C */ U32T enemy_type_drop_probs_offset;
|
||||
/* 1C */ U32T<BE> enemy_type_drop_probs_offset;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the class of
|
||||
// item that the enemy can drop. The values are:
|
||||
@@ -147,12 +145,12 @@ public:
|
||||
// 05 = meseta
|
||||
// Anything else = no item
|
||||
// V2/V3: -> parray<uint8_t, 0x64>
|
||||
/* 20 */ U32T enemy_item_classes_offset;
|
||||
/* 20 */ U32T<BE> enemy_item_classes_offset;
|
||||
|
||||
// This table (indexed by area - 1) specifies the ranges of meseta values
|
||||
// that can drop from boxes.
|
||||
// V2/V3: -> parray<Range<U16T>, 0x0A>
|
||||
/* 24 */ U32T box_meseta_ranges_offset;
|
||||
/* 24 */ U32T<BE> box_meseta_ranges_offset;
|
||||
|
||||
// This array specifies the chance that a rare weapon will have each
|
||||
// possible bonus value. This is indexed as [(bonus_value - 10 / 5)][spec],
|
||||
@@ -162,7 +160,7 @@ public:
|
||||
// for rare items, spec is always 5.
|
||||
// V2: -> parray<parray<uint8_t, 5>, 0x17>
|
||||
// V3: -> parray<parray<U16T, 6>, 0x17>
|
||||
/* 28 */ U32T bonus_value_prob_table_offset;
|
||||
/* 28 */ U32T<BE> bonus_value_prob_table_offset;
|
||||
|
||||
// This array specifies the value of spec to be used in the above lookup for
|
||||
// non-rare items. This is NOT an index probability table; this is a direct
|
||||
@@ -178,7 +176,7 @@ public:
|
||||
// bonus; in all other areas except Ruins 3, they can have at most two
|
||||
// bonuses, and in Ruins 3, they can have up to three bonuses.
|
||||
// V2/V3: // -> parray<parray<uint8_t, 10>, 3>
|
||||
/* 2C */ U32T nonrare_bonus_prob_spec_offset;
|
||||
/* 2C */ U32T<BE> nonrare_bonus_prob_spec_offset;
|
||||
|
||||
// This array specifies the chance that a weapon will have each bonus type.
|
||||
// The table is indexed as [bonus_type][area - 1] for non-rare items; for
|
||||
@@ -193,37 +191,37 @@ public:
|
||||
// [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
// V2/V3: -> parray<parray<uint8_t, 10>, 6>
|
||||
/* 30 */ U32T bonus_type_prob_table_offset;
|
||||
/* 30 */ U32T<BE> bonus_type_prob_table_offset;
|
||||
|
||||
// This array (indexed by area - 1) specifies a multiplier of used in
|
||||
// special ability determination. It seems this uses the star values from
|
||||
// ItemPMT, but not yet clear exactly in what way.
|
||||
// TODO: Figure out exactly what this does. Anchor: 80106FEC
|
||||
// V2/V3: -> parray<uint8_t, 0x0A>
|
||||
/* 34 */ U32T special_mult_offset;
|
||||
/* 34 */ U32T<BE> special_mult_offset;
|
||||
|
||||
// This array (indexed by area - 1) specifies the probability that any
|
||||
// non-rare weapon will have a special ability.
|
||||
// V2/V3: -> parray<uint8_t, 0x0A>
|
||||
/* 38 */ U32T special_percent_offset;
|
||||
/* 38 */ U32T<BE> special_percent_offset;
|
||||
|
||||
// This index probability table is indexed by [tool_class][area - 1]. The
|
||||
// tool class refers to an entry in ItemPMT, which links it to the actual
|
||||
// item code.
|
||||
// V2/V3: -> parray<parray<U16T, 0x0A>, 0x1C>
|
||||
/* 3C */ U32T tool_class_prob_table_offset;
|
||||
/* 3C */ U32T<BE> tool_class_prob_table_offset;
|
||||
|
||||
// This index probability table determines how likely each technique is to
|
||||
// appear. The table is indexed as [technique_num][area - 1].
|
||||
// V2/V3: -> parray<parray<uint8_t, 0x0A>, 0x13>
|
||||
/* 40 */ U32T technique_index_prob_table_offset;
|
||||
/* 40 */ U32T<BE> technique_index_prob_table_offset;
|
||||
|
||||
// This table specifies the ranges for technique disk levels. The table is
|
||||
// indexed as [technique_num][area - 1]. If either min or max in the range
|
||||
// is 0xFF, or if max < min, technique disks are not dropped for that
|
||||
// technique and area pair.
|
||||
// V2/V3: -> parray<parray<Range<uint8_t>, 0x0A>, 0x13>
|
||||
/* 44 */ U32T technique_level_ranges_offset;
|
||||
/* 44 */ U32T<BE> technique_level_ranges_offset;
|
||||
|
||||
/* 48 */ uint8_t armor_or_shield_type_bias;
|
||||
/* 49 */ parray<uint8_t, 3> unused1;
|
||||
@@ -234,7 +232,7 @@ public:
|
||||
// game uniformly chooses a random number of stars in the acceptable
|
||||
// range, then uniformly chooses a random unit with that many stars.
|
||||
// V2/V3: -> parray<uint8_t, 0x0A>
|
||||
/* 4C */ U32T unit_max_stars_offset;
|
||||
/* 4C */ U32T<BE> unit_max_stars_offset;
|
||||
|
||||
// This index probability table determines which type of items drop from
|
||||
// boxes. The table is indexed as [item_class][area - 1], with item_class
|
||||
@@ -253,7 +251,7 @@ public:
|
||||
// [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
// V2/V3: -> parray<parray<uint8_t, 10>, 7>
|
||||
/* 50 */ U32T box_item_class_prob_table_offset;
|
||||
/* 50 */ U32T<BE> box_item_class_prob_table_offset;
|
||||
|
||||
// There are several unused fields here.
|
||||
} __packed__;
|
||||
@@ -264,7 +262,7 @@ public:
|
||||
};
|
||||
|
||||
std::shared_ptr<const Table> get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
void print(FILE* stream) const;
|
||||
|
||||
protected:
|
||||
@@ -287,7 +285,7 @@ public:
|
||||
|
||||
class JSONCommonItemSet : public CommonItemSet {
|
||||
public:
|
||||
explicit JSONCommonItemSet(const JSON& json);
|
||||
explicit JSONCommonItemSet(const phosg::JSON& json);
|
||||
};
|
||||
|
||||
// Note: There are clearly better ways of doing this, but this implementation
|
||||
@@ -348,7 +346,7 @@ public:
|
||||
|
||||
protected:
|
||||
std::shared_ptr<const std::string> data;
|
||||
StringReader r;
|
||||
phosg::StringReader r;
|
||||
|
||||
struct TableSpec {
|
||||
be_uint32_t offset;
|
||||
@@ -463,7 +461,7 @@ private:
|
||||
int8_t get_luck(uint32_t start_offset, uint8_t delta_index) const;
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
StringReader r;
|
||||
phosg::StringReader r;
|
||||
|
||||
struct DeltaProbabilityEntry {
|
||||
uint8_t delta_index;
|
||||
|
||||
+12
-12
@@ -14,7 +14,7 @@
|
||||
using namespace std;
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<CompressPhase>(CompressPhase v) {
|
||||
const char* phosg::name_for_enum<CompressPhase>(CompressPhase v) {
|
||||
switch (v) {
|
||||
case CompressPhase::INDEX:
|
||||
return "INDEX";
|
||||
@@ -118,7 +118,7 @@ struct WindowIndex {
|
||||
};
|
||||
|
||||
struct LZSSInterleavedWriter {
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
size_t buf_offset;
|
||||
uint8_t next_control_bit;
|
||||
uint8_t buf[0x19];
|
||||
@@ -166,7 +166,7 @@ struct LZSSInterleavedWriter {
|
||||
|
||||
class ControlStreamReader {
|
||||
public:
|
||||
ControlStreamReader(StringReader& r)
|
||||
ControlStreamReader(phosg::StringReader& r)
|
||||
: r(r),
|
||||
bits(0x0000) {}
|
||||
|
||||
@@ -188,7 +188,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
StringReader& r;
|
||||
phosg::StringReader& r;
|
||||
uint16_t bits;
|
||||
};
|
||||
|
||||
@@ -493,7 +493,7 @@ void PRSCompressor::add(const void* data, size_t size) {
|
||||
throw logic_error("compressor is closed");
|
||||
}
|
||||
|
||||
StringReader r(data, size);
|
||||
phosg::StringReader r(data, size);
|
||||
while (!r.eof()) {
|
||||
this->add_byte(r.get_u8());
|
||||
}
|
||||
@@ -874,8 +874,8 @@ PRSDecompressResult prs_decompress_with_meta(
|
||||
// is encountered partway through an opcode, we throw instead, because it's
|
||||
// likely the input has been truncated or is malformed in some way.
|
||||
|
||||
StringWriter w;
|
||||
StringReader r(data, size);
|
||||
phosg::StringWriter w;
|
||||
phosg::StringReader r(data, size);
|
||||
ControlStreamReader cr(r);
|
||||
|
||||
while (!r.eof()) {
|
||||
@@ -959,7 +959,7 @@ string prs_decompress(const string& data, size_t max_output_size, bool allow_unt
|
||||
|
||||
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
|
||||
size_t ret = 0;
|
||||
StringReader r(data, size);
|
||||
phosg::StringReader r(data, size);
|
||||
ControlStreamReader cr(r);
|
||||
|
||||
while (!r.eof()) {
|
||||
@@ -1011,7 +1011,7 @@ size_t prs_decompress_size(const string& data, size_t max_output_size, bool allo
|
||||
|
||||
void prs_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
size_t output_bytes = 0;
|
||||
StringReader r(data, size);
|
||||
phosg::StringReader r(data, size);
|
||||
ControlStreamReader cr(r);
|
||||
|
||||
while (!r.eof()) {
|
||||
@@ -1249,8 +1249,8 @@ string bc0_decompress(const string& data) {
|
||||
}
|
||||
|
||||
string bc0_decompress(const void* data, size_t size) {
|
||||
StringReader r(data, size);
|
||||
StringWriter w;
|
||||
phosg::StringReader r(data, size);
|
||||
phosg::StringWriter w;
|
||||
|
||||
// Unlike PRS, BC0 uses a memo which "rolls over" every 0x1000 bytes. The
|
||||
// boundaries of these "memo pages" are offset by -0x12 bytes for some reason,
|
||||
@@ -1322,7 +1322,7 @@ void bc0_disassemble(FILE* stream, const string& data) {
|
||||
}
|
||||
|
||||
void bc0_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
StringReader r(data, size);
|
||||
phosg::StringReader r(data, size);
|
||||
uint16_t control_stream_bits = 0x0000;
|
||||
|
||||
size_t output_bytes = 0;
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ enum class CompressPhase {
|
||||
};
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<CompressPhase>(CompressPhase v);
|
||||
const char* phosg::name_for_enum<CompressPhase>(CompressPhase v);
|
||||
|
||||
typedef std::function<void(CompressPhase phase, size_t input_progress, size_t input_size, size_t output_size)> ProgressCallback;
|
||||
|
||||
@@ -146,7 +146,7 @@ private:
|
||||
WrappedLog<0x101> forward_log;
|
||||
IndexedLog<0x2000> reverse_log;
|
||||
|
||||
StringWriter output;
|
||||
phosg::StringWriter output;
|
||||
};
|
||||
|
||||
// These functions use PRSCompressor to compress a buffer of data. This is
|
||||
|
||||
+99
-66
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
@@ -24,6 +25,7 @@ static const uint32_t primes1[] = {
|
||||
0x313, 0x31D, 0x329, 0x32B, 0x335, 0x337, 0x33B, 0x33D, 0x347, 0x355, 0x359,
|
||||
0x35B, 0x35F, 0x36D, 0x371, 0x373, 0x377, 0x38B, 0x38F, 0x397, 0x3A1, 0x3A9,
|
||||
0x3AD, 0x3B3, 0x3B9, 0x3C7, 0x3CB, 0x3D1, 0x3D7, 0x3DF, 0x3E5};
|
||||
|
||||
static const uint32_t primes2[] = {
|
||||
0x3F1, 0x3F5, 0x3FB, 0x3FD, 0x407, 0x409, 0x40F, 0x419, 0x41B, 0x425, 0x427,
|
||||
0x42D, 0x43F, 0x443, 0x445, 0x449, 0x44F, 0x455, 0x45D, 0x463, 0x469, 0x47F,
|
||||
@@ -135,6 +137,8 @@ static const uint32_t primes2[] = {
|
||||
0x2627, 0x2629, 0x2635, 0x263B, 0x263F, 0x264B, 0x2653, 0x2659, 0x2665,
|
||||
0x2669, 0x266F, 0x267B, 0x2681, 0x2683, 0x268F, 0x269B, 0x269F, 0x26AD,
|
||||
0x26B3, 0x26C3, 0x26C9, 0x26CB, 0x26D5, 0x26DD, 0x26EF, 0x26F5};
|
||||
static constexpr size_t num_primes2 = sizeof(primes2) / sizeof(primes2[0]);
|
||||
|
||||
static const uint32_t primes3[] = {
|
||||
0x2717, 0x2719, 0x2735, 0x2737, 0x274D, 0x2753, 0x2755, 0x275F, 0x276B,
|
||||
0x276D, 0x2773, 0x2777, 0x277F, 0x2795, 0x279B, 0x279D, 0x27A7, 0x27AF,
|
||||
@@ -1107,15 +1111,19 @@ static const uint32_t primes3[] = {
|
||||
0x18569, 0x1857B, 0x1857D, 0x18581, 0x18587, 0x18589, 0x18595, 0x185B1,
|
||||
0x185B7, 0x185CB, 0x185D1, 0x185E1, 0x185E9, 0x185EF, 0x185F5, 0x185F9,
|
||||
0x185FF, 0x18613, 0x1861F};
|
||||
static constexpr size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
|
||||
|
||||
static bool check_prime3(uint64_t prime3) {
|
||||
static vector<bool> primes3_set;
|
||||
static mutex primes3_init_mutex;
|
||||
if (primes3_set.empty()) {
|
||||
size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
|
||||
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
|
||||
primes3_set.resize(primes3_set_size, false);
|
||||
for (size_t z = 0; z < num_primes3; z++) {
|
||||
primes3_set[primes3[z] - primes3[0]] = true;
|
||||
lock_guard g(primes3_init_mutex);
|
||||
if (primes3_set.empty()) {
|
||||
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
|
||||
primes3_set.resize(primes3_set_size, false);
|
||||
for (size_t z = 0; z < num_primes3; z++) {
|
||||
primes3_set[primes3[z] - primes3[0]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint64_t offset = prime3 - primes3[0];
|
||||
@@ -1174,7 +1182,7 @@ static uint64_t decode_dc_serial_number_str(const string& s) {
|
||||
if (new_ch == '\0') {
|
||||
return INVALID_PRODUCT;
|
||||
}
|
||||
serial_number = (serial_number << 4) | value_for_hex_char(new_ch);
|
||||
serial_number = (serial_number << 4) | phosg::value_for_hex_char(new_ch);
|
||||
}
|
||||
return serial_number;
|
||||
}
|
||||
@@ -1227,8 +1235,8 @@ bool dc_serial_number_is_valid_slow(const string& s, uint8_t domain, uint8_t sub
|
||||
}
|
||||
|
||||
for (; offset1 < limit1; offset1++) {
|
||||
for (size_t offset2 = 0; offset2 < sizeof(primes2) / sizeof(primes2[0]); offset2++) {
|
||||
for (size_t offset3 = 0; offset3 < sizeof(primes3) / sizeof(primes3[0]); offset3++) {
|
||||
for (size_t offset2 = 0; offset2 < num_primes2; offset2++) {
|
||||
for (size_t offset3 = 0; offset3 < num_primes3; offset3++) {
|
||||
if (primes1[offset1] * primes2[offset2] * primes3[offset3] == serial_number) {
|
||||
return true;
|
||||
}
|
||||
@@ -1251,7 +1259,7 @@ bool decoded_dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t doma
|
||||
continue;
|
||||
}
|
||||
uint64_t sub1 = sub0 / primes1[offset1];
|
||||
for (size_t offset2 = 0; offset2 < sizeof(primes2) / sizeof(primes2[0]); offset2++) {
|
||||
for (size_t offset2 = 0; offset2 < num_primes2; offset2++) {
|
||||
if (sub1 % primes2[offset2]) {
|
||||
continue;
|
||||
}
|
||||
@@ -1291,12 +1299,12 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||
throw runtime_error("invalid domain");
|
||||
}
|
||||
|
||||
size_t det1 = (subdomain == 0xFF) ? random_object<uint32_t>() : subdomain;
|
||||
size_t det1 = (subdomain == 0xFF) ? phosg::random_object<uint32_t>() : subdomain;
|
||||
size_t index1 = offset1 + (det1 % (limit1 - offset1));
|
||||
size_t index2 = random_object<uint32_t>() % (sizeof(primes2) / sizeof(primes2[0]));
|
||||
size_t index3 = random_object<uint32_t>() % (sizeof(primes3) / sizeof(primes3[0]));
|
||||
size_t index2 = phosg::random_object<uint32_t>() % num_primes2;
|
||||
size_t index3 = phosg::random_object<uint32_t>() % num_primes3;
|
||||
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
|
||||
string s = string_printf("%08X", value);
|
||||
string s = phosg::string_printf("%08X", value);
|
||||
|
||||
string ret;
|
||||
for (char ch : s) {
|
||||
@@ -1306,56 +1314,81 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||
}
|
||||
|
||||
unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) {
|
||||
vector<uint8_t> domains;
|
||||
if (domain == 0xFF) {
|
||||
domains.emplace_back(0x00);
|
||||
domains.emplace_back(0x01);
|
||||
domains.emplace_back(0x02);
|
||||
} else {
|
||||
domains.emplace_back(domain);
|
||||
DCSerialNumberIterator iter;
|
||||
|
||||
if (domain < 3) {
|
||||
iter.domain = domain;
|
||||
iter.end_domain = domain + 1;
|
||||
} else if (domain != 0xFF) {
|
||||
throw runtime_error("invalid domain");
|
||||
}
|
||||
|
||||
vector<uint8_t> subdomains;
|
||||
if (subdomain == 0xFF) {
|
||||
subdomains.emplace_back(0x00);
|
||||
subdomains.emplace_back(0x01);
|
||||
subdomains.emplace_back(0x02);
|
||||
} else {
|
||||
subdomains.emplace_back(subdomain);
|
||||
if (subdomain < 3) {
|
||||
iter.subdomain = subdomain;
|
||||
iter.start_subdomain = subdomain;
|
||||
iter.end_subdomain = subdomain + 1;
|
||||
} else if (subdomain != 0xFF) {
|
||||
throw runtime_error("invalid subdomain");
|
||||
}
|
||||
|
||||
uint32_t serial_number;
|
||||
unordered_map<uint32_t, string> ret;
|
||||
for (uint8_t domain : domains) {
|
||||
size_t offset1, limit1;
|
||||
if (domain == 0) {
|
||||
offset1 = 0x00;
|
||||
limit1 = 0x03;
|
||||
} else if (domain == 1) {
|
||||
offset1 = 0x1E;
|
||||
limit1 = 0x21;
|
||||
} else if (domain == 2) {
|
||||
offset1 = 0x3C;
|
||||
limit1 = 0x3F;
|
||||
} else {
|
||||
throw runtime_error("invalid domain");
|
||||
}
|
||||
|
||||
for (uint8_t subdomain : subdomains) {
|
||||
size_t index1 = offset1 + (subdomain % (limit1 - offset1));
|
||||
for (size_t index2 = 0; index2 < sizeof(primes2) / sizeof(primes2[0]); index2++) {
|
||||
for (size_t index3 = 0; index3 < sizeof(primes3) / sizeof(primes3[0]); index3++) {
|
||||
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
|
||||
ret[encode_dc_serial_number_int(value)].push_back(((domain << 2) & 3) | (subdomain & 3));
|
||||
}
|
||||
fprintf(stderr, "... domain=%hhu subdomain=%hhu index2=%zu results=%zu (0x%zX)\n", domain, subdomain, index2, ret.size(), ret.size());
|
||||
}
|
||||
while ((serial_number = iter.next()) != 0) {
|
||||
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
|
||||
if (iter.index3 == 0) {
|
||||
fprintf(stderr, "... (it) domain=%hhu subdomain=%hhu index2=%hu results=%zu (0x%zX)\n", iter.domain, iter.subdomain, iter.index2, ret.size(), ret.size());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t DCSerialNumberIterator::next() {
|
||||
if (!this->started) {
|
||||
this->started = true;
|
||||
} else if (!this->complete) {
|
||||
this->index3++;
|
||||
if (this->index3 >= num_primes3) {
|
||||
this->index3 = 0;
|
||||
this->index2++;
|
||||
if (this->index2 >= num_primes2) {
|
||||
this->index2 = 0;
|
||||
this->subdomain++;
|
||||
if (this->subdomain >= this->end_subdomain) {
|
||||
this->subdomain = this->start_subdomain;
|
||||
this->domain++;
|
||||
if (this->domain >= this->end_domain) {
|
||||
this->serial_number = 0;
|
||||
this->complete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this->complete) {
|
||||
size_t index1 = (30 * this->domain) + (this->subdomain % 3);
|
||||
return encode_dc_serial_number_int(primes1[index1] * primes2[this->index2] * primes3[this->index3]);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t DCSerialNumberIterator::total_count() const {
|
||||
return (this->end_domain - this->start_domain) * (this->end_subdomain - this->start_subdomain) * num_primes2 * num_primes3;
|
||||
}
|
||||
|
||||
size_t DCSerialNumberIterator::progress() const {
|
||||
size_t domains_done = this->domain - this->start_domain;
|
||||
size_t subdomains_per_domain = this->end_subdomain - this->start_subdomain;
|
||||
size_t subdomains_done = this->subdomain - this->start_subdomain;
|
||||
return (
|
||||
(domains_done * subdomains_per_domain * num_primes2 * num_primes3) +
|
||||
(subdomains_done * num_primes2 * num_primes3) +
|
||||
(this->index2 * num_primes3) +
|
||||
this->index3);
|
||||
}
|
||||
|
||||
void dc_serial_number_speed_test(uint64_t seed) {
|
||||
uint32_t effective_seed = (seed & 0xFFFFFFFF00000000) ? random_object<uint32_t>() : seed;
|
||||
uint32_t effective_seed = (seed & 0xFFFFFFFF00000000) ? phosg::random_object<uint32_t>() : seed;
|
||||
fprintf(stderr, "Product speed test with seed=%08" PRIX32 "\n", effective_seed);
|
||||
PSOV2Encryption crypt(effective_seed);
|
||||
uint64_t time_slow = 0;
|
||||
@@ -1363,15 +1396,15 @@ void dc_serial_number_speed_test(uint64_t seed) {
|
||||
size_t num_disagreements = 0;
|
||||
static constexpr size_t count = 0x1000;
|
||||
for (size_t z = 0; z < count; z++) {
|
||||
string s = string_printf("%08X", crypt.next());
|
||||
string s = phosg::string_printf("%08X", crypt.next());
|
||||
|
||||
uint64_t start = now();
|
||||
uint64_t start = phosg::now();
|
||||
bool is_valid_fast = dc_serial_number_is_valid_fast(s, 1, 0xFF);
|
||||
time_fast += now() - start;
|
||||
time_fast += phosg::now() - start;
|
||||
|
||||
start = now();
|
||||
start = phosg::now();
|
||||
bool is_valid_slow = dc_serial_number_is_valid_slow(s, 1, 0xFF);
|
||||
time_slow += now() - start;
|
||||
time_slow += phosg::now() - start;
|
||||
|
||||
if (((z & 0xF) == 0) || is_valid_slow || is_valid_fast) {
|
||||
fprintf(stderr, "... %02zX: %s => %s %s%s\n", z, s.c_str(), is_valid_slow ? "SLOW" : "----", is_valid_fast ? "FAST" : "----", is_valid_slow != is_valid_fast ? " !!!" : "");
|
||||
@@ -1391,13 +1424,13 @@ string decrypt_dp_address_jpn(
|
||||
const string& executable,
|
||||
const string& values,
|
||||
const string& indexes) {
|
||||
StringReader values_r(values);
|
||||
StringReader indexes_r(indexes);
|
||||
phosg::StringReader values_r(values);
|
||||
phosg::StringReader indexes_r(indexes);
|
||||
|
||||
size_t fixup_values_offset = values_r.pget_u32l(0x3FFC) - 0x8C004000;
|
||||
size_t fixup_steps_offset = indexes_r.pget_u32l(0x3BFC) - 0x8C008400;
|
||||
StringReader fixup_values_r = values_r.sub(fixup_values_offset);
|
||||
StringReader fixup_steps_r = indexes_r.sub(fixup_steps_offset);
|
||||
phosg::StringReader fixup_values_r = values_r.sub(fixup_values_offset);
|
||||
phosg::StringReader fixup_steps_r = indexes_r.sub(fixup_steps_offset);
|
||||
|
||||
auto decrypted = decrypt_pr2_data<false>(executable);
|
||||
size_t fixup_offset = 0;
|
||||
@@ -1417,9 +1450,9 @@ EncryptedDCv2Executables encrypt_dp_address_jpn(const string& executable, const
|
||||
EncryptedDCv2Executables ret;
|
||||
|
||||
string compressed = prs_compress(executable);
|
||||
ret.executable = encrypt_pr2_data<false>(compressed, executable.size(), random_object<uint32_t>() & 0x7FFFFF7F);
|
||||
ret.executable = encrypt_pr2_data<false>(compressed, executable.size(), phosg::random_object<uint32_t>() & 0x7FFFFF7F);
|
||||
|
||||
StringReader indexes_r(indexes);
|
||||
phosg::StringReader indexes_r(indexes);
|
||||
size_t fixup_steps_offset = indexes_r.pget_u32l(0x3BFC) - 0x8C008400;
|
||||
ret.indexes = indexes;
|
||||
ret.indexes.at(fixup_steps_offset) = 0;
|
||||
@@ -1431,7 +1464,7 @@ std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_ke
|
||||
throw runtime_error("size is not a multiple of 4");
|
||||
}
|
||||
|
||||
StringReader r(data);
|
||||
phosg::StringReader r(data);
|
||||
if (mask_key < 0) {
|
||||
unordered_map<uint32_t, size_t> key_freq;
|
||||
while (!r.eof()) {
|
||||
@@ -1447,11 +1480,11 @@ std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_ke
|
||||
if (mask_key < 0) {
|
||||
throw runtime_error("cannot determine mask key");
|
||||
}
|
||||
log_info("Determined %08" PRIX64 " to be the most likely mask key", mask_key);
|
||||
phosg::log_info("Determined %08" PRIX64 " to be the most likely mask key", mask_key);
|
||||
r.go(0);
|
||||
}
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
while (!r.eof()) {
|
||||
w.put_u32l(r.get_u32l() ^ mask_key);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,25 @@ bool decoded_dc_serial_number_is_valid_fast(
|
||||
std::string generate_dc_serial_number(uint8_t domain, uint8_t subdomain = 0xFF);
|
||||
std::unordered_map<uint32_t, std::string> generate_all_dc_serial_numbers(uint8_t domain = 0xFF, uint8_t subdomain = 0xFF);
|
||||
|
||||
struct DCSerialNumberIterator {
|
||||
bool started = false;
|
||||
bool complete = false;
|
||||
uint8_t domain = 0;
|
||||
uint8_t start_domain = 0;
|
||||
uint8_t end_domain = 3;
|
||||
uint8_t subdomain = 0;
|
||||
uint8_t start_subdomain = 0;
|
||||
uint8_t end_subdomain = 3;
|
||||
uint16_t index2 = 0;
|
||||
uint16_t index3 = 0;
|
||||
uint32_t serial_number = 0;
|
||||
|
||||
uint32_t next();
|
||||
|
||||
size_t total_count() const;
|
||||
size_t progress() const;
|
||||
};
|
||||
|
||||
void dc_serial_number_speed_test(uint64_t seed = 0xFFFFFFFFFFFFFFFF);
|
||||
|
||||
struct EncryptedDCv2Executables {
|
||||
|
||||
+5
-5
@@ -34,15 +34,15 @@ DNSServer::~DNSServer() {
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& socket_path) {
|
||||
this->add_socket(::listen(socket_path, 0, 0));
|
||||
this->add_socket(phosg::listen(socket_path, 0, 0));
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& addr, int port) {
|
||||
this->add_socket(::listen(addr, port, 0));
|
||||
this->add_socket(phosg::listen(addr, port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::listen(int port) {
|
||||
this->add_socket(::listen("", port, 0));
|
||||
this->add_socket(phosg::listen("", port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::add_socket(int fd) {
|
||||
@@ -66,7 +66,7 @@ string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t re
|
||||
const char* data = reinterpret_cast<const char*>(vdata);
|
||||
size_t name_len = strlen(&data[12]) + 1;
|
||||
|
||||
be_uint32_t be_resolved_address = resolved_address;
|
||||
phosg::be_uint32_t be_resolved_address = resolved_address;
|
||||
|
||||
string response;
|
||||
response.append(data, 2);
|
||||
@@ -104,7 +104,7 @@ void DNSServer::on_receive_message(int fd, short) {
|
||||
|
||||
} else if (bytes < 0x0C) {
|
||||
dns_server_log.warning("input query too small");
|
||||
print_data(stderr, input.data(), bytes);
|
||||
phosg::print_data(stderr, input.data(), bytes);
|
||||
|
||||
} else if (!this->banned_ipv4_ranges->check(remote)) {
|
||||
input.resize(bytes);
|
||||
|
||||
@@ -0,0 +1,870 @@
|
||||
#include "DownloadSession.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ProxyCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string random_name() {
|
||||
string ret;
|
||||
size_t length = (phosg::random_object<size_t>() % 12) + 4;
|
||||
static const string alphabet = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890-+<>:\"\',.";
|
||||
while (ret.size() < length) {
|
||||
ret.push_back(alphabet[phosg::random_object<size_t>() % alphabet.size()]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
DownloadSession::DownloadSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
const std::string& output_dir,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file,
|
||||
uint32_t hardware_id,
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
const std::string& xb_gamertag,
|
||||
uint64_t xb_user_id,
|
||||
uint64_t xb_account_id,
|
||||
std::shared_ptr<PSOBBCharacterFile> character,
|
||||
const std::unordered_set<std::string>& ship_menu_selections,
|
||||
const std::vector<std::string>& on_request_complete_commands,
|
||||
bool interactive,
|
||||
bool show_command_data)
|
||||
: output_dir(output_dir),
|
||||
bb_key_file(bb_key_file),
|
||||
hardware_id(hardware_id),
|
||||
serial_number(serial_number),
|
||||
access_key(access_key),
|
||||
username(username),
|
||||
password(password),
|
||||
xb_gamertag(xb_gamertag),
|
||||
xb_user_id(xb_user_id),
|
||||
xb_account_id(xb_account_id),
|
||||
character(character),
|
||||
ship_menu_selections(ship_menu_selections),
|
||||
on_request_complete_commands(on_request_complete_commands),
|
||||
interactive(interactive),
|
||||
log(phosg::string_printf("[DownloadSession:%s] ", phosg::name_for_enum(version)), proxy_server_log.min_level),
|
||||
base(base),
|
||||
channel(
|
||||
version,
|
||||
language,
|
||||
DownloadSession::dispatch_on_channel_input,
|
||||
DownloadSession::dispatch_on_channel_error,
|
||||
this,
|
||||
phosg::render_sockaddr_storage(remote),
|
||||
show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END,
|
||||
show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END),
|
||||
guild_card_number(0),
|
||||
prev_cmd_data(0),
|
||||
client_config(0),
|
||||
sent_96(false),
|
||||
should_request_category_list(true),
|
||||
current_request(0),
|
||||
current_game_config_index(0),
|
||||
in_game(false),
|
||||
bin_complete(false),
|
||||
dat_complete(false) {
|
||||
if (this->output_dir.empty()) {
|
||||
this->output_dir = ".";
|
||||
}
|
||||
|
||||
switch (this->channel.version) {
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
if (this->hardware_id == 0 || this->serial_number == 0 || this->access_key.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::PC_V2:
|
||||
if (this->serial_number == 0 || this->access_key.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
if (this->serial_number == 0 || this->access_key.empty() || this->password.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
if (this->xb_gamertag.empty() || this->xb_user_id == 0 || this->xb_account_id == 0) {
|
||||
throw runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
if (this->username.empty() || this->password.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
|
||||
this->character->inventory.language = this->channel.language;
|
||||
|
||||
if (remote.ss_family != AF_INET) {
|
||||
throw runtime_error("remote is not AF_INET");
|
||||
}
|
||||
|
||||
string netloc_str = phosg::render_sockaddr_storage(remote);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(
|
||||
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(phosg::string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(phosg::string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
auto* session = reinterpret_cast<DownloadSession*>(ch.context_obj);
|
||||
session->on_channel_input(command, flag, data);
|
||||
}
|
||||
|
||||
void DownloadSession::send_93_9D_9E(bool extended) {
|
||||
if (is_v1(this->channel.version)) {
|
||||
C_LoginExtendedV1_DC_93 ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.hardware_id.encode(phosg::string_printf("%08" PRIX32, this->hardware_id));
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
this->channel.send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93));
|
||||
|
||||
} else if (is_v2(this->channel.version)) {
|
||||
C_LoginExtended_PC_9D ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
size_t data_size = extended
|
||||
? ((this->channel.version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_GC_9D))
|
||||
: sizeof(C_Login_DC_PC_GC_9D);
|
||||
this->channel.send(0x9D, 0x01, &ret, data_size);
|
||||
|
||||
} else if (this->channel.version == Version::GC_V3) {
|
||||
C_LoginExtended_GC_9E ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
ret.client_config = this->client_config;
|
||||
this->channel.send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_GC_9E));
|
||||
|
||||
} else if (this->channel.version == Version::XB_V3) {
|
||||
C_LoginExtended_XB_9E ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(this->xb_gamertag);
|
||||
ret.access_key.encode(phosg::string_printf("%016" PRIX64, this->xb_user_id));
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
ret.netloc.internal_ipv4_address = phosg::random_object<uint32_t>();
|
||||
ret.netloc.external_ipv4_address = phosg::random_object<uint32_t>();
|
||||
ret.netloc.port = 9500;
|
||||
phosg::random_data(&ret.netloc.mac_address, sizeof(ret.netloc.mac_address));
|
||||
ret.netloc.sg_ip_address = phosg::random_object<uint32_t>();
|
||||
ret.netloc.spi = phosg::random_object<uint32_t>();
|
||||
ret.netloc.account_id = this->xb_account_id;
|
||||
ret.netloc.unknown_a3.clear(0);
|
||||
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||
ret.xb_user_id_low = this->xb_user_id;
|
||||
this->channel.send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_DC_PC_GC_9D));
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::send_61_98(bool is_98) {
|
||||
uint8_t command = is_98 ? 0x98 : 0x61;
|
||||
|
||||
if (is_v1(this->channel.version)) {
|
||||
C_CharacterData_DCv1_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
this->channel.send(command, 0x01, ret);
|
||||
|
||||
} else if (this->channel.version == Version::DC_V2) {
|
||||
C_CharacterData_DCv2_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
ret.records.challenge = this->character->challenge_records;
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
this->channel.send(command, 0x02, ret);
|
||||
|
||||
} else if (this->channel.version == Version::PC_V2) {
|
||||
C_CharacterData_PC_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
ret.records.challenge = this->character->challenge_records;
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
this->channel.send(command, 0x02, ret);
|
||||
|
||||
} else if (is_v3(this->channel.version)) {
|
||||
C_CharacterData_V3_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
ret.records.challenge = this->character->challenge_records;
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
ret.info_board.encode(this->character->info_board.decode());
|
||||
this->channel.send(command, 0x03, ret);
|
||||
|
||||
} else if (this->channel.version == Version::BB_V4) {
|
||||
C_CharacterData_BB_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = this->character->disp;
|
||||
ret.records.challenge = this->character->challenge_records;
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
ret.info_board.encode(this->character->info_board.decode());
|
||||
this->channel.send(command, 0x04, ret);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::string& data) {
|
||||
// TODO: Use the iovec form of print_data here instead of
|
||||
// prepend_command_header (which copies the string)
|
||||
string full_cmd = prepend_command_header(this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
|
||||
for (size_t z = 0; z < 0x28 && z < data.size(); z++) {
|
||||
this->prev_cmd_data[z] = data[z];
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 0x03: {
|
||||
if (this->channel.version != Version::BB_V4) {
|
||||
throw runtime_error("BB server sent non-BB encryption command");
|
||||
}
|
||||
if (!this->bb_key_file) {
|
||||
throw runtime_error("BB encryption requires a key file");
|
||||
}
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF);
|
||||
this->channel.crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
||||
this->channel.crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
||||
this->log.info("Enabled BB encryption");
|
||||
throw runtime_error("not yet implemented"); // Send 93
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x02:
|
||||
case 0x17:
|
||||
case 0x91:
|
||||
case 0x9B: {
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
|
||||
if (uses_v3_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
} else if (!uses_v4_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
} else {
|
||||
throw runtime_error("BB server sent non-BB encryption command");
|
||||
}
|
||||
|
||||
if (command == 0x02) {
|
||||
bool is_extended = (this->channel.version == Version::XB_V3);
|
||||
this->send_93_9D_9E(is_extended);
|
||||
|
||||
} else {
|
||||
if (is_v1(this->channel.version)) {
|
||||
C_LoginV1_DC_PC_V3_90 ret;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
this->channel.send(0x90, 0x00, ret);
|
||||
|
||||
} else if (is_v2(this->channel.version)) {
|
||||
C_Login_DC_PC_V3_9A ret;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
this->channel.send(0x9A, 0x00, ret);
|
||||
|
||||
} else if (this->channel.version == Version::GC_V3) {
|
||||
C_VerifyAccount_V3_DB ret;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.password.encode(this->password);
|
||||
this->channel.send(0xDB, 0x00, ret);
|
||||
|
||||
} else if (this->channel.version == Version::XB_V3) {
|
||||
this->send_93_9D_9E(true);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x90:
|
||||
case 0x9A: {
|
||||
if (flag == 1) {
|
||||
if (is_v1(this->channel.version)) {
|
||||
C_RegisterV1_DC_92 ret;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.language = this->channel.language;
|
||||
ret.hardware_id.encode(phosg::string_printf("%08" PRIX32, this->hardware_id));
|
||||
this->channel.send(0x92, 0x00, ret);
|
||||
|
||||
} else if (!is_v4(this->channel.version)) {
|
||||
C_Register_DC_PC_V3_9C ret;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.language = this->channel.language;
|
||||
if (this->channel.version == Version::XB_V3) {
|
||||
ret.serial_number.encode(this->xb_gamertag);
|
||||
ret.access_key.encode(phosg::string_printf("%016" PRIX64, this->xb_user_id));
|
||||
ret.password.encode("xbox-pso");
|
||||
} else {
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.password.encode(this->password);
|
||||
}
|
||||
this->channel.send(0x9C, 0x00, ret);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
|
||||
} else if (flag == 0 || flag == 2) {
|
||||
this->send_93_9D_9E(true);
|
||||
|
||||
} else {
|
||||
throw runtime_error("login failed");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x92:
|
||||
case 0x9C:
|
||||
if (flag == 0) {
|
||||
throw runtime_error("server rejected login credentials");
|
||||
}
|
||||
this->send_93_9D_9E(true);
|
||||
break;
|
||||
|
||||
case 0x9F: {
|
||||
if (is_v1_or_v2(this->channel.version)) {
|
||||
throw runtime_error("invalid command");
|
||||
}
|
||||
this->channel.send(0x9F, 0x00, this->client_config);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xB2: {
|
||||
C_ExecuteCodeResult_B3 ret;
|
||||
ret.checksum = 0;
|
||||
ret.return_value = 0;
|
||||
this->channel.send(0xB3, 0x00, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x04: {
|
||||
const auto& cmd = check_size_t<S_UpdateClientConfig_V3_04>(data, 0x08, sizeof(S_UpdateClientConfig_V3_04));
|
||||
if (!is_v1_or_v2(this->channel.version)) {
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
size_t read_index = z + 8;
|
||||
this->client_config[z] = (read_index < data.size()) ? data[read_index] : this->prev_cmd_data[read_index];
|
||||
}
|
||||
}
|
||||
this->guild_card_number = cmd.guild_card_number;
|
||||
if (!this->sent_96) {
|
||||
C_CharSaveInfo_DCv2_PC_V3_BB_96 ret;
|
||||
ret.creation_timestamp = this->character->creation_timestamp;
|
||||
ret.event_counter = this->character->save_count;
|
||||
this->channel.send(0x96, 0x00, ret);
|
||||
this->sent_96 = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x97:
|
||||
this->channel.send(0xB1, 0x00);
|
||||
break;
|
||||
|
||||
case 0x95:
|
||||
this->send_61_98(false);
|
||||
break;
|
||||
|
||||
case 0xB1:
|
||||
this->channel.send(0x99, 0x00);
|
||||
break;
|
||||
|
||||
case 0x1A:
|
||||
case 0xD5:
|
||||
if (is_v3(this->channel.version)) {
|
||||
this->channel.send(0xD6, 0x00);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07:
|
||||
case 0x1F:
|
||||
case 0xA0:
|
||||
case 0xA1: {
|
||||
C_MenuSelection_10_Flag00 ret;
|
||||
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto* items = check_size_vec_t<CmdT>(data, flag + 1);
|
||||
size_t item_index;
|
||||
this->log.info("Ship Select menu:");
|
||||
for (item_index = 1; item_index <= flag; item_index++) {
|
||||
const auto& item = items[item_index];
|
||||
auto text = strip_color(item.text.decode());
|
||||
this->log.info("%zu: (%08" PRIX32 " %08" PRIX32 ") %s", item_index, item.menu_id.load(), item.item_id.load(), text.c_str());
|
||||
if (this->ship_menu_selections.count(text)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (item_index > flag) {
|
||||
if (this->interactive) {
|
||||
while (item_index == 0 || item_index > flag) {
|
||||
this->log.info("Choose response index:");
|
||||
string input = phosg::fgets(stdin);
|
||||
item_index = stoul(input, nullptr, 0);
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("unhandled menu selection");
|
||||
}
|
||||
}
|
||||
ret.menu_id = items[item_index].menu_id;
|
||||
ret.item_id = items[item_index].item_id;
|
||||
};
|
||||
|
||||
if (uses_utf16(this->channel.version)) {
|
||||
handle_command.operator()<S_MenuEntry_PC_BB_07_1F>();
|
||||
} else {
|
||||
handle_command.operator()<S_MenuEntry_DC_V3_07_1F>();
|
||||
}
|
||||
|
||||
this->channel.send(0x10, 0x00, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x01:
|
||||
case 0x11:
|
||||
case 0x60:
|
||||
case 0x62:
|
||||
case 0x68:
|
||||
case 0x69:
|
||||
case 0x6C:
|
||||
case 0x6D:
|
||||
case 0x88:
|
||||
case 0x8A:
|
||||
case 0xB0:
|
||||
case 0xC5:
|
||||
case 0xDA:
|
||||
break;
|
||||
|
||||
case 0x1D:
|
||||
this->channel.send(0x1D, 0x00);
|
||||
break;
|
||||
|
||||
case 0x19: {
|
||||
const auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xFFFF);
|
||||
|
||||
sockaddr_storage ss;
|
||||
auto* sin = reinterpret_cast<sockaddr_in*>(&ss);
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = htonl(cmd.address);
|
||||
sin->sin_port = htons(cmd.port);
|
||||
string netloc_str = phosg::render_sockaddr_storage(ss);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(phosg::string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
this->channel.crypt_in.reset();
|
||||
this->channel.crypt_out.reset();
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(), reinterpret_cast<const sockaddr*>(&ss), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(phosg::string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x83: {
|
||||
const auto* items = check_size_vec_t<S_LobbyListEntry_83>(data, flag, true);
|
||||
this->lobby_menu_items.clear();
|
||||
for (size_t z = 0; z < flag; z++) {
|
||||
this->lobby_menu_items.emplace_back(items[z]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x67: {
|
||||
// Technically we should assign item IDs here, but the server will never
|
||||
// be able to see that we didn't, so we don't bother
|
||||
|
||||
const auto& game_config = this->game_configs[this->current_game_config_index];
|
||||
if (this->channel.version == Version::PC_V2) {
|
||||
C_CreateGame_PC_C1 ret;
|
||||
ret.name.encode(random_name());
|
||||
ret.password.encode(random_name());
|
||||
ret.difficulty = 0;
|
||||
ret.battle_mode = (game_config.mode == GameMode::BATTLE);
|
||||
ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE);
|
||||
ret.episode = 1;
|
||||
this->channel.send(0xC1, 0x00, ret);
|
||||
|
||||
} else if (!is_v4(this->channel.version)) {
|
||||
C_CreateGame_DC_V3_0C_C1_Ep3_EC ret;
|
||||
ret.name.encode(random_name());
|
||||
ret.password.encode(random_name());
|
||||
ret.difficulty = 0;
|
||||
ret.battle_mode = (game_config.mode == GameMode::BATTLE);
|
||||
ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE);
|
||||
if (is_v1(this->channel.version)) {
|
||||
ret.episode = 0;
|
||||
} else if (game_config.episode == Episode::EP1) {
|
||||
ret.episode = 1;
|
||||
} else if (game_config.episode == Episode::EP2) {
|
||||
ret.episode = 2;
|
||||
} else if (game_config.episode == Episode::EP4) {
|
||||
ret.episode = 4;
|
||||
} else {
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
this->channel.send(is_v1(this->channel.version) ? 0x0C : 0xC1, 0x00, ret);
|
||||
|
||||
} else {
|
||||
C_CreateGame_BB_C1 ret;
|
||||
ret.name.encode(random_name());
|
||||
ret.password.encode(random_name());
|
||||
ret.difficulty = 0;
|
||||
ret.battle_mode = (game_config.mode == GameMode::BATTLE);
|
||||
ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE);
|
||||
if (game_config.episode == Episode::EP1) {
|
||||
ret.episode = 1;
|
||||
} else if (game_config.episode == Episode::EP2) {
|
||||
ret.episode = 2;
|
||||
} else if (game_config.episode == Episode::EP4) {
|
||||
ret.episode = 4;
|
||||
} else {
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
ret.solo_mode = (game_config.mode == GameMode::SOLO);
|
||||
this->channel.send(is_v1(this->channel.version) ? 0x0C : 0xC1, 0x00, ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x64: {
|
||||
this->in_game = true;
|
||||
this->bin_complete = false;
|
||||
this->dat_complete = false;
|
||||
for (size_t z = 0; z < this->character->inventory.num_items; z++) {
|
||||
this->character->inventory.items[z].data.id = 0x00010000 + z;
|
||||
}
|
||||
|
||||
if (!is_v1(this->channel.version)) {
|
||||
this->channel.send(0x8A, 0x00);
|
||||
}
|
||||
this->channel.send(0x6F, 0x00);
|
||||
this->send_next_request();
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xA2: {
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto* items = check_size_vec_t<CmdT>(data, flag);
|
||||
for (size_t z = 0; z < flag; z++) {
|
||||
const auto& item = items[z];
|
||||
uint64_t request = (static_cast<uint64_t>(item.menu_id) << 32) | static_cast<uint64_t>(item.item_id);
|
||||
if (!this->done_requests.count(request)) {
|
||||
this->log.info("Adding request %016" PRIX64, request);
|
||||
this->pending_requests.emplace(request, item.name.decode());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this->channel.version == Version::PC_V2) {
|
||||
handle_command.operator()<S_QuestMenuEntry_PC_A2_A4>();
|
||||
} else if (this->channel.version == Version::XB_V3) {
|
||||
handle_command.operator()<S_QuestMenuEntry_XB_A2_A4>();
|
||||
} else if (this->channel.version == Version::BB_V4) {
|
||||
handle_command.operator()<S_QuestMenuEntry_BB_A2_A4>();
|
||||
} else {
|
||||
handle_command.operator()<S_QuestMenuEntry_DC_GC_A2_A4>();
|
||||
}
|
||||
this->send_next_request();
|
||||
break;
|
||||
}
|
||||
case 0x44:
|
||||
case 0xA6: {
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto& cmd = check_size_t<CmdT>(data, 0xFFFF);
|
||||
string internal_name = cmd.filename.decode();
|
||||
string filtered_name;
|
||||
for (char ch : internal_name) {
|
||||
filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_');
|
||||
}
|
||||
string local_filename = phosg::string_printf(
|
||||
"%s/%016" PRIX64 "_%" PRIu64 "_%s_%c_%s",
|
||||
this->output_dir.c_str(),
|
||||
this->current_request,
|
||||
phosg::now(),
|
||||
phosg::name_for_enum(this->channel.version),
|
||||
char_for_language_code(this->channel.language),
|
||||
filtered_name.c_str());
|
||||
this->open_files.emplace(internal_name, OpenFile{.request = this->current_request, .filename = local_filename, .total_size = cmd.file_size, .data = ""});
|
||||
};
|
||||
|
||||
if (is_dc(this->channel.version)) {
|
||||
handle_command.operator()<S_OpenFile_DC_44_A6>();
|
||||
} else if (!is_v4(this->channel.version)) {
|
||||
handle_command.operator()<S_OpenFile_PC_GC_44_A6>();
|
||||
} else {
|
||||
handle_command.operator()<S_OpenFile_BB_44_A6>();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x13:
|
||||
case 0xA7: {
|
||||
const auto& cmd = check_size_t<S_WriteFile_13_A7>(data);
|
||||
string internal_filename = cmd.filename.decode();
|
||||
|
||||
if (!is_v1_or_v2(this->channel.version)) {
|
||||
C_WriteFileConfirmation_V3_BB_13_A7 ret;
|
||||
ret.filename.encode(internal_filename);
|
||||
this->channel.send(command, flag, ret);
|
||||
}
|
||||
|
||||
auto f_it = this->open_files.find(internal_filename.c_str());
|
||||
if (f_it == this->open_files.end()) {
|
||||
this->log.warning("Received data for non-open file %s", internal_filename.c_str());
|
||||
break;
|
||||
}
|
||||
auto& f = this->open_files.at(cmd.filename.decode());
|
||||
size_t block_offset = flag * 0x400;
|
||||
size_t allowed_block_size = (block_offset < f.total_size)
|
||||
? min<size_t>(f.total_size - block_offset, 0x400)
|
||||
: 0;
|
||||
size_t data_size = min<size_t>(cmd.data_size, allowed_block_size);
|
||||
size_t block_end_offset = block_offset + data_size;
|
||||
if (block_end_offset > f.data.size()) {
|
||||
f.data.resize(block_end_offset);
|
||||
}
|
||||
memcpy(f.data.data() + block_offset, cmd.data.data(), data_size);
|
||||
if (cmd.data_size != 0x400) {
|
||||
phosg::save_file(f.filename, f.data);
|
||||
this->log.info("Wrote file %s (%zu bytes)", f.filename.c_str(), f.data.size());
|
||||
this->open_files.erase(internal_filename);
|
||||
if (phosg::ends_with(internal_filename, ".bin")) {
|
||||
this->bin_complete = true;
|
||||
} else if (phosg::ends_with(internal_filename, ".dat")) {
|
||||
this->dat_complete = true;
|
||||
}
|
||||
if (this->open_files.empty() && this->bin_complete && this->dat_complete) {
|
||||
if (is_v1_or_v2(this->channel.version)) {
|
||||
this->on_request_complete();
|
||||
} else {
|
||||
this->channel.send(0xAC, 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xAC: {
|
||||
if (is_v1_or_v2(this->channel.version)) {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
this->on_request_complete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::send_next_request() {
|
||||
if (should_request_category_list) {
|
||||
this->log.info("Requesting quest list");
|
||||
this->channel.send(0xA2, 0x00);
|
||||
if (is_v4(this->channel.version)) {
|
||||
this->channel.send(0xA2, 0x01);
|
||||
}
|
||||
this->should_request_category_list = false;
|
||||
|
||||
} else if (!this->pending_requests.empty()) {
|
||||
if (interactive) {
|
||||
const auto& config = this->game_configs[this->current_game_config_index];
|
||||
this->log.info("Items available to expand (mode=%s, episode=%s):", name_for_mode(config.mode), name_for_episode(config.episode));
|
||||
for (const auto& it : this->pending_requests) {
|
||||
this->log.info("%016" PRIX64 ": %s", it.first, it.second.c_str());
|
||||
}
|
||||
this->log.info("Choose item to expand by ID (q to quit; s to skip to next config):");
|
||||
string input = phosg::fgets(stdin);
|
||||
if (input.empty() || (input == "q\n")) {
|
||||
this->channel.disconnect();
|
||||
return;
|
||||
} else if (input == "s\n") {
|
||||
this->pending_requests.clear();
|
||||
this->on_request_complete();
|
||||
} else {
|
||||
this->current_request = stoull(input, nullptr, 16);
|
||||
this->done_requests.emplace(this->current_request);
|
||||
this->pending_requests.erase(this->current_request);
|
||||
}
|
||||
|
||||
} else {
|
||||
auto item_it = this->pending_requests.begin();
|
||||
this->current_request = item_it->first;
|
||||
this->done_requests.emplace(this->current_request);
|
||||
this->pending_requests.erase(item_it);
|
||||
this->log.info("Sending request %016" PRIX64, this->current_request);
|
||||
}
|
||||
|
||||
C_MenuSelection_10_Flag00 cmd;
|
||||
cmd.menu_id = (this->current_request >> 32) & 0xFFFFFFFF;
|
||||
cmd.item_id = this->current_request & 0xFFFFFFFF;
|
||||
this->channel.send(0x10, 0x00, cmd);
|
||||
} else {
|
||||
this->log.info("No pending requests with current parameters");
|
||||
this->on_request_complete();
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::on_request_complete() {
|
||||
for (const auto& data : this->on_request_complete_commands) {
|
||||
this->channel.send(data);
|
||||
}
|
||||
|
||||
this->send_61_98(true);
|
||||
this->in_game = false;
|
||||
|
||||
const auto& item = this->lobby_menu_items.at(this->lobby_menu_items.size() / 2);
|
||||
C_LobbySelection_84 ret84;
|
||||
ret84.menu_id = item.menu_id;
|
||||
ret84.item_id = item.item_id;
|
||||
this->channel.send(0x84, 0x00, ret84);
|
||||
|
||||
if (this->pending_requests.empty()) {
|
||||
// Advance to next mode/episode combination
|
||||
this->current_game_config_index++;
|
||||
bool v1 = is_v1(this->channel.version);
|
||||
bool v2 = is_v2(this->channel.version);
|
||||
bool v3 = is_v3(this->channel.version);
|
||||
while ((this->current_game_config_index < this->game_configs.size()) &&
|
||||
((v1 && !this->game_configs[this->current_game_config_index].v1) ||
|
||||
(v2 && !this->game_configs[this->current_game_config_index].v2) ||
|
||||
(v3 && !this->game_configs[this->current_game_config_index].v3))) {
|
||||
this->current_game_config_index++;
|
||||
}
|
||||
if (this->current_game_config_index >= this->game_configs.size()) {
|
||||
this->log.info("All modes complete");
|
||||
this->channel.disconnect();
|
||||
} else {
|
||||
const auto& config = this->game_configs[this->current_game_config_index];
|
||||
this->log.info("Advancing to %s mode in %s", name_for_mode(config.mode), name_for_episode(config.episode));
|
||||
this->should_request_category_list = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<DownloadSession*>(ch.context_obj);
|
||||
session->on_channel_error(events);
|
||||
}
|
||||
|
||||
void DownloadSession::on_channel_error(short events) {
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
this->log.info("Server channel connected");
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log.warning("Error %d (%s) in server stream", err, evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->log.info("Server endpoint has disconnected");
|
||||
this->channel.disconnect();
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<DownloadSession::GameConfig> DownloadSession::game_configs({
|
||||
{.mode = GameMode::NORMAL, .episode = Episode::EP1, .v1 = true, .v2 = true, .v3 = true},
|
||||
{.mode = GameMode::NORMAL, .episode = Episode::EP2, .v1 = false, .v2 = false, .v3 = true},
|
||||
{.mode = GameMode::NORMAL, .episode = Episode::EP4, .v1 = false, .v2 = false, .v3 = false},
|
||||
{.mode = GameMode::BATTLE, .episode = Episode::EP1, .v1 = false, .v2 = true, .v3 = true},
|
||||
{.mode = GameMode::CHALLENGE, .episode = Episode::EP1, .v1 = false, .v2 = true, .v3 = true},
|
||||
{.mode = GameMode::CHALLENGE, .episode = Episode::EP2, .v1 = false, .v2 = false, .v3 = true},
|
||||
{.mode = GameMode::SOLO, .episode = Episode::EP1, .v1 = false, .v2 = false, .v3 = false},
|
||||
{.mode = GameMode::SOLO, .episode = Episode::EP2, .v1 = false, .v2 = false, .v3 = false},
|
||||
{.mode = GameMode::SOLO, .episode = Episode::EP4, .v1 = false, .v2 = false, .v3 = false},
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class DownloadSession {
|
||||
public:
|
||||
DownloadSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
const std::string& output_dir,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file,
|
||||
uint32_t hardware_id,
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
const std::string& xb_gamertag,
|
||||
uint64_t xb_user_id,
|
||||
uint64_t xb_account_id,
|
||||
std::shared_ptr<PSOBBCharacterFile> character,
|
||||
const std::unordered_set<std::string>& ship_menu_selections,
|
||||
const std::vector<std::string>& on_request_complete_commands,
|
||||
bool interactive,
|
||||
bool show_command_data);
|
||||
DownloadSession(const DownloadSession&) = delete;
|
||||
DownloadSession(DownloadSession&&) = delete;
|
||||
DownloadSession& operator=(const DownloadSession&) = delete;
|
||||
DownloadSession& operator=(DownloadSession&&) = delete;
|
||||
virtual ~DownloadSession() = default;
|
||||
|
||||
protected:
|
||||
// Config (must be set by caller)
|
||||
std::string output_dir;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
|
||||
uint32_t hardware_id;
|
||||
uint32_t serial_number;
|
||||
std::string access_key;
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string xb_gamertag;
|
||||
uint64_t xb_user_id;
|
||||
uint64_t xb_account_id;
|
||||
std::shared_ptr<PSOBBCharacterFile> character;
|
||||
std::unordered_set<std::string> ship_menu_selections;
|
||||
std::vector<std::string> on_request_complete_commands;
|
||||
bool interactive;
|
||||
|
||||
// State (set during session)
|
||||
phosg::PrefixedLogger log;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
Channel channel;
|
||||
uint32_t guild_card_number;
|
||||
parray<uint8_t, 0x28> prev_cmd_data;
|
||||
parray<uint8_t, 0x20> client_config;
|
||||
bool sent_96;
|
||||
std::vector<S_LobbyListEntry_83> lobby_menu_items;
|
||||
|
||||
bool should_request_category_list;
|
||||
uint64_t current_request;
|
||||
std::map<uint64_t, std::string> pending_requests;
|
||||
std::unordered_set<uint64_t> done_requests;
|
||||
|
||||
struct OpenFile {
|
||||
uint64_t request;
|
||||
std::string filename;
|
||||
size_t total_size;
|
||||
std::string data;
|
||||
};
|
||||
std::unordered_map<std::string, OpenFile> open_files;
|
||||
|
||||
struct GameConfig {
|
||||
GameMode mode;
|
||||
Episode episode;
|
||||
bool v1;
|
||||
bool v2;
|
||||
bool v3;
|
||||
};
|
||||
static const std::vector<GameConfig> game_configs;
|
||||
size_t current_game_config_index;
|
||||
bool in_game;
|
||||
bool bin_complete;
|
||||
bool dat_complete;
|
||||
|
||||
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void dispatch_on_channel_error(Channel& ch, short events);
|
||||
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
|
||||
void on_channel_error(short events);
|
||||
|
||||
void send_next_request();
|
||||
void on_request_complete();
|
||||
|
||||
void assign_item_ids(uint32_t base_item_id);
|
||||
void send_93_9D_9E(bool extended);
|
||||
void send_61_98(bool is_98);
|
||||
};
|
||||
+9
-6
@@ -10,7 +10,7 @@
|
||||
using namespace std;
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<EnemyType>(EnemyType type) {
|
||||
const char* phosg::name_for_enum<EnemyType>(EnemyType type) {
|
||||
switch (type) {
|
||||
case EnemyType::UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
@@ -274,7 +274,7 @@ const char* name_for_enum<EnemyType>(EnemyType type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
EnemyType enum_for_name<EnemyType>(const char* name) {
|
||||
EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
|
||||
static const unordered_map<string, EnemyType> names({
|
||||
{"UNKNOWN", EnemyType::UNKNOWN},
|
||||
{"NONE", EnemyType::NONE},
|
||||
@@ -672,7 +672,8 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
|
||||
case EnemyType::SO_DIMENIAN:
|
||||
return 0x55;
|
||||
default:
|
||||
throw runtime_error(string_printf("%s does not have battle parameters in Episode 1", name_for_enum(enemy_type)));
|
||||
throw out_of_range(phosg::string_printf(
|
||||
"%s does not have battle parameters in Episode 1", phosg::name_for_enum(enemy_type)));
|
||||
}
|
||||
break;
|
||||
case Episode::EP2:
|
||||
@@ -787,7 +788,8 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
|
||||
case EnemyType::SO_DIMENIAN:
|
||||
return 0x55;
|
||||
default:
|
||||
throw runtime_error(string_printf("%s does not have battle parameters in Episode 2", name_for_enum(enemy_type)));
|
||||
throw out_of_range(phosg::string_printf(
|
||||
"%s does not have battle parameters in Episode 2", phosg::name_for_enum(enemy_type)));
|
||||
}
|
||||
break;
|
||||
case Episode::EP4:
|
||||
@@ -847,7 +849,8 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
|
||||
case EnemyType::KONDRIEU:
|
||||
return 0x2A;
|
||||
default:
|
||||
throw runtime_error(string_printf("%s does not have battle parameters in Episode 4", name_for_enum(enemy_type)));
|
||||
throw out_of_range(phosg::string_printf(
|
||||
"%s does not have battle parameters in Episode 4", phosg::name_for_enum(enemy_type)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1072,7 +1075,7 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::ZU_ALT:
|
||||
return 0x4A;
|
||||
default:
|
||||
throw runtime_error(string_printf("%s does not have a rare table entry", name_for_enum(enemy_type)));
|
||||
throw runtime_error(phosg::string_printf("%s does not have a rare table entry", phosg::name_for_enum(enemy_type)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@
|
||||
#include <phosg/Tools.hh>
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
enum class EnemyType {
|
||||
UNKNOWN = -1,
|
||||
@@ -138,9 +139,9 @@ enum class EnemyType {
|
||||
};
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<EnemyType>(EnemyType type);
|
||||
const char* phosg::name_for_enum<EnemyType>(EnemyType type);
|
||||
template <>
|
||||
EnemyType enum_for_name<EnemyType>(const char* name);
|
||||
EnemyType phosg::enum_for_name<EnemyType>(const char* name);
|
||||
|
||||
bool enemy_type_valid_for_episode(Episode episode, EnemyType enemy_type);
|
||||
uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type);
|
||||
|
||||
@@ -12,10 +12,10 @@ namespace Episode3 {
|
||||
void BattleRecord::PlayerEntry::print(FILE* stream) const {
|
||||
// TODO: Format this nicely somehow. Maybe factor out the functions in
|
||||
// QuestScript that format some of these structures
|
||||
print_data(stream, this, sizeof(*this), 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stream, this, sizeof(*this), 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
|
||||
BattleRecord::Event::Event(StringReader& r) {
|
||||
BattleRecord::Event::Event(phosg::StringReader& r) {
|
||||
this->type = r.get<Event::Type>();
|
||||
this->timestamp = r.get_u64l();
|
||||
switch (this->type) {
|
||||
@@ -46,7 +46,7 @@ BattleRecord::Event::Event(StringReader& r) {
|
||||
}
|
||||
}
|
||||
|
||||
void BattleRecord::Event::serialize(StringWriter& w) const {
|
||||
void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
|
||||
w.put(this->type);
|
||||
w.put_u64l(this->timestamp);
|
||||
switch (this->type) {
|
||||
@@ -81,7 +81,7 @@ void BattleRecord::Event::serialize(StringWriter& w) const {
|
||||
}
|
||||
|
||||
void BattleRecord::Event::print(FILE* stream) const {
|
||||
string time_str = format_time(this->timestamp);
|
||||
string time_str = phosg::format_time(this->timestamp);
|
||||
fprintf(stream, "Event @%016" PRIX64 " (%s) ", this->timestamp, time_str.c_str());
|
||||
switch (this->type) {
|
||||
case Type::PLAYER_JOIN:
|
||||
@@ -103,23 +103,23 @@ void BattleRecord::Event::print(FILE* stream) const {
|
||||
break;
|
||||
case Type::BATTLE_COMMAND:
|
||||
fprintf(stream, "BATTLE_COMMAND\n");
|
||||
print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::GAME_COMMAND:
|
||||
fprintf(stream, "GAME_COMMAND\n");
|
||||
print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::EP3_GAME_COMMAND:
|
||||
fprintf(stream, "EP3_GAME_COMMAND\n");
|
||||
print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::CHAT_MESSAGE:
|
||||
fprintf(stream, "CHAT_MESSAGE %08" PRIX32 "\n", this->guild_card_number);
|
||||
print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::SERVER_DATA_COMMAND:
|
||||
fprintf(stream, "SERVER_DATA_COMMAND\n");
|
||||
print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown event type in battle record");
|
||||
@@ -137,7 +137,7 @@ BattleRecord::BattleRecord(const string& data)
|
||||
behavior_flags(0),
|
||||
battle_start_timestamp(0),
|
||||
battle_end_timestamp(0) {
|
||||
StringReader r(data);
|
||||
phosg::StringReader r(data);
|
||||
|
||||
uint64_t signature = r.get_u64l();
|
||||
bool has_random_stream;
|
||||
@@ -161,7 +161,7 @@ BattleRecord::BattleRecord(const string& data)
|
||||
}
|
||||
|
||||
string BattleRecord::serialize() const {
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put_u64l(this->SIGNATURE_V2);
|
||||
w.put_u64l(this->battle_start_timestamp);
|
||||
w.put_u64l(this->battle_end_timestamp);
|
||||
@@ -202,7 +202,7 @@ void BattleRecord::add_player(
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = Event::Type::PLAYER_JOIN;
|
||||
ev.timestamp = now();
|
||||
ev.timestamp = phosg::now();
|
||||
auto& player = ev.players.emplace_back();
|
||||
player.lobby_data = lobby_data;
|
||||
player.inventory = inventory;
|
||||
@@ -216,7 +216,7 @@ void BattleRecord::delete_player(uint8_t client_id) {
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = Event::Type::PLAYER_LEAVE;
|
||||
ev.timestamp = now();
|
||||
ev.timestamp = phosg::now();
|
||||
ev.leaving_client_id = client_id;
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ void BattleRecord::add_command(Event::Type type, const void* data, size_t size)
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = type;
|
||||
ev.timestamp = now();
|
||||
ev.timestamp = phosg::now();
|
||||
ev.data.assign(reinterpret_cast<const char*>(data), size);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ void BattleRecord::add_command(Event::Type type, string&& data) {
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = type;
|
||||
ev.timestamp = now();
|
||||
ev.timestamp = phosg::now();
|
||||
ev.data = std::move(data);
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ void BattleRecord::add_chat_message(
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = Event::Type::CHAT_MESSAGE;
|
||||
ev.timestamp = now();
|
||||
ev.timestamp = phosg::now();
|
||||
ev.guild_card_number = guild_card_number;
|
||||
ev.data = std::move(data);
|
||||
}
|
||||
@@ -287,7 +287,7 @@ void BattleRecord::set_battle_start_timestamp() {
|
||||
if (this->battle_start_timestamp != 0) {
|
||||
throw logic_error("battle start timestamp is already set");
|
||||
}
|
||||
this->battle_start_timestamp = now();
|
||||
this->battle_start_timestamp = phosg::now();
|
||||
|
||||
// First, find the correct map definition subcommand to keep, and execute
|
||||
// player join/leave events to get the present players
|
||||
@@ -357,12 +357,12 @@ void BattleRecord::set_battle_start_timestamp() {
|
||||
}
|
||||
|
||||
void BattleRecord::set_battle_end_timestamp() {
|
||||
this->battle_end_timestamp = now();
|
||||
this->battle_end_timestamp = phosg::now();
|
||||
}
|
||||
|
||||
void BattleRecord::print(FILE* stream) const {
|
||||
string start_str = format_time(this->battle_start_timestamp);
|
||||
string end_str = format_time(this->battle_end_timestamp);
|
||||
string start_str = phosg::format_time(this->battle_start_timestamp);
|
||||
string end_str = phosg::format_time(this->battle_end_timestamp);
|
||||
fprintf(stream, "BattleRecord %s behavior_flags=%08" PRIX32 " start=%016" PRIX64 " (%s) end=%016" PRIX64 " (%s); %zu events\n",
|
||||
this->is_writable ? "writable" : "read-only",
|
||||
this->behavior_flags,
|
||||
@@ -394,13 +394,12 @@ void BattleRecordPlayer::set_lobby(shared_ptr<Lobby> l) {
|
||||
|
||||
void BattleRecordPlayer::start() {
|
||||
if (this->play_start_timestamp == 0) {
|
||||
this->play_start_timestamp = now();
|
||||
this->play_start_timestamp = phosg::now();
|
||||
this->schedule_events();
|
||||
}
|
||||
}
|
||||
|
||||
void BattleRecordPlayer::dispatch_schedule_events(
|
||||
evutil_socket_t, short, void* ctx) {
|
||||
void BattleRecordPlayer::dispatch_schedule_events(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<BattleRecordPlayer*>(ctx)->schedule_events();
|
||||
}
|
||||
|
||||
@@ -413,7 +412,7 @@ void BattleRecordPlayer::schedule_events() {
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
uint64_t relative_ts = now() - this->play_start_timestamp + this->record->battle_start_timestamp;
|
||||
uint64_t relative_ts = phosg::now() - this->play_start_timestamp + this->record->battle_start_timestamp;
|
||||
|
||||
if (this->event_it == this->record->events.end()) {
|
||||
if (relative_ts >= this->record->battle_end_timestamp) {
|
||||
@@ -426,7 +425,7 @@ void BattleRecordPlayer::schedule_events() {
|
||||
} else {
|
||||
// There are no more events to play, but the battle has not officially
|
||||
// ended yet - reschedule the event for the end time
|
||||
auto tv = usecs_to_timeval(this->record->battle_end_timestamp - relative_ts);
|
||||
auto tv = phosg::usecs_to_timeval(this->record->battle_end_timestamp - relative_ts);
|
||||
event_add(this->next_command_ev.get(), &tv);
|
||||
}
|
||||
break;
|
||||
@@ -467,7 +466,7 @@ void BattleRecordPlayer::schedule_events() {
|
||||
} else {
|
||||
// The next event should not occur yet, so reschedule for the time when
|
||||
// it should occur
|
||||
auto tv = usecs_to_timeval(this->event_it->timestamp - relative_ts);
|
||||
auto tv = phosg::usecs_to_timeval(this->event_it->timestamp - relative_ts);
|
||||
event_add(this->next_command_ev.get(), &tv);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ public:
|
||||
std::string data;
|
||||
|
||||
Event() = default;
|
||||
explicit Event(StringReader& r);
|
||||
void serialize(StringWriter& w) const;
|
||||
explicit Event(phosg::StringReader& r);
|
||||
void serialize(phosg::StringWriter& w) const;
|
||||
void print(FILE* stream) const;
|
||||
};
|
||||
|
||||
@@ -126,7 +126,7 @@ private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
std::shared_ptr<struct event> next_command_ev;
|
||||
StringReader random_r;
|
||||
phosg::StringReader random_r;
|
||||
};
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
+63
-22
@@ -123,7 +123,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
int8_t dice_roll_value,
|
||||
int8_t random_percent) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("apply_abnormal_condition(%02hhX, @%04X, @%04X, %hd, %hhd, %hhd): ", def_effect_index, target_card_ref, sc_card_ref, value, dice_roll_value, random_percent));
|
||||
auto log = s->log_stack(phosg::string_printf("apply_abnormal_condition(%02hhX, @%04X, @%04X, %hd, %hhd, %hhd): ", def_effect_index, target_card_ref, sc_card_ref, value, dice_roll_value, random_percent));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
ssize_t existing_cond_index;
|
||||
@@ -298,7 +298,7 @@ void Card::commit_attack(
|
||||
size_t strike_number,
|
||||
int16_t* out_effective_damage) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("commit_attack(@%04hX #%04hX, @%04hX #%04hX => %hd (str%zu)): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id(), damage, strike_number));
|
||||
auto log = s->log_stack(phosg::string_printf("commit_attack(@%04hX #%04hX, @%04hX #%04hX => %hd (str%zu)): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id(), damage, strike_number));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
int16_t effective_damage = damage;
|
||||
@@ -507,7 +507,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
}
|
||||
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("execute_attack(@%04X #%04X, @%04X #%04X): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("execute_attack(@%04X #%04X, @%04X #%04X): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
this->card_flags &= 0xFFFFFFF3;
|
||||
@@ -714,7 +714,7 @@ int32_t Card::move_to_location(const Location& loc) {
|
||||
uint8_t trap_type = s->overlay_state.tiles[loc.y][loc.x] & 0x0F;
|
||||
uint16_t trap_card_id = s->overlay_state.trap_card_ids_nte[trap_type];
|
||||
if (other_ps->replace_assist_card_by_id(trap_card_id)) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 1;
|
||||
cmd.client_id = other_ps->client_id;
|
||||
cmd.unknown_a2[0] = trap_card_id;
|
||||
@@ -728,7 +728,7 @@ int32_t Card::move_to_location(const Location& loc) {
|
||||
for (size_t warp_end = 0; warp_end < 2; warp_end++) {
|
||||
if ((s->warp_positions[warp_type][warp_end][0] == this->loc.x) &&
|
||||
(s->warp_positions[warp_type][warp_end][1] == this->loc.y)) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
|
||||
cmd.loc.x = this->loc.x;
|
||||
cmd.loc.y = this->loc.y;
|
||||
this->loc.x = s->warp_positions[warp_type][warp_end ^ 1][0];
|
||||
@@ -905,7 +905,7 @@ void Card::clear_action_chain_and_metadata_and_most_flags() {
|
||||
|
||||
void Card::compute_action_chain_results(bool apply_action_conditions, bool ignore_this_card_ap_tp) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("compute_action_chain_results(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("compute_action_chain_results(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
this->action_chain.compute_attack_medium(s);
|
||||
@@ -914,7 +914,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
this->action_chain.chain.tp_effect_bonus = 0;
|
||||
|
||||
log.debug("(initial) medium=%s, strike_count=%hhu, ap_effect_bonus=%hhd, tp_effect_bonus=%hhd",
|
||||
name_for_enum(this->action_chain.chain.attack_medium),
|
||||
phosg::name_for_enum(this->action_chain.chain.attack_medium),
|
||||
this->action_chain.chain.strike_count,
|
||||
this->action_chain.chain.ap_effect_bonus,
|
||||
this->action_chain.chain.tp_effect_bonus);
|
||||
@@ -1154,6 +1154,11 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string chain_str = this->action_chain.str(s);
|
||||
log.debug("result computed as %s", chain_str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Card::unknown_802380C0() {
|
||||
@@ -1220,13 +1225,24 @@ void Card::move_phase_before() {
|
||||
|
||||
void Card::unknown_80236374(shared_ptr<Card> other_card, const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("unknown_80236374(@%04hX #%04hX, @%04hX #%04hX): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("unknown_80236374(@%04hX #%04hX, @%04hX #%04hX): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id()));
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (as) {
|
||||
string as_str = as->str(s);
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
} else {
|
||||
log.debug("as = null");
|
||||
}
|
||||
}
|
||||
|
||||
auto check_card = [&](shared_ptr<Card> card) -> void {
|
||||
if (card) {
|
||||
if (!card->unknown_80236554(other_card, as)) {
|
||||
log.debug("check_card @%04hX #%04hX => false", card->get_card_ref(), card->get_card_id());
|
||||
card->action_metadata.clear_flags(0x20);
|
||||
} else {
|
||||
log.debug("check_card @%04hX #%04hX => true", card->get_card_ref(), card->get_card_id());
|
||||
card->action_metadata.set_flags(0x20);
|
||||
}
|
||||
}
|
||||
@@ -1361,13 +1377,15 @@ bool Card::is_guard_item() const {
|
||||
bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(other_card
|
||||
? string_printf("unknown_80236554(@%04hX #%04hX, @%04hX #%04hX): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id())
|
||||
: string_printf("unknown_80236554(@%04hX #%04hX, null): ", this->get_card_ref(), this->get_card_id()));
|
||||
if (as) {
|
||||
string as_str = as->str(s);
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
} else {
|
||||
log.debug("as = null");
|
||||
? phosg::string_printf("unknown_80236554(@%04hX #%04hX, @%04hX #%04hX): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id())
|
||||
: phosg::string_printf("unknown_80236554(@%04hX #%04hX, null): ", this->get_card_ref(), this->get_card_id()));
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (as) {
|
||||
string as_str = as->str(s);
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
} else {
|
||||
log.debug("as = null");
|
||||
}
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
@@ -1402,32 +1420,38 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
||||
log.debug("last attack damage stats cleared");
|
||||
|
||||
if (other_card) {
|
||||
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, other_card, this->shared_from_this(), 0x20, as);
|
||||
s->card_special->apply_action_conditions(EffectWhen::BEFORE_THIS_CARD_ATTACKED, other_card, this->shared_from_this(), 0x40, as);
|
||||
log.debug("applying BEFORE_ANY_CARD_ATTACK conditions");
|
||||
s->card_special->apply_action_conditions(
|
||||
EffectWhen::BEFORE_ANY_CARD_ATTACK, other_card, this->shared_from_this(), 0x20, as);
|
||||
log.debug("applying BEFORE_THIS_CARD_ATTACKED conditions");
|
||||
s->card_special->apply_action_conditions(
|
||||
EffectWhen::BEFORE_THIS_CARD_ATTACKED, other_card, this->shared_from_this(), 0x40, as);
|
||||
if (other_card->action_chain.check_flag(0x20000)) {
|
||||
log.debug("attack_bonus cleared due to cancellation");
|
||||
this->action_metadata.attack_bonus = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (this->card_flags & 2) {
|
||||
log.debug("attack_bonus cleared due to destruction");
|
||||
this->action_metadata.attack_bonus = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Card::unknown_802362D8(shared_ptr<Card> other_card) {
|
||||
void Card::execute_attack_on_all_valid_targets(shared_ptr<Card> attacker_card) {
|
||||
auto s = this->server();
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = s->player_states[client_id];
|
||||
if (ps) {
|
||||
shared_ptr<Card> card = ps->get_sc_card();
|
||||
if (card) {
|
||||
card->execute_attack(other_card);
|
||||
card->execute_attack(attacker_card);
|
||||
}
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
shared_ptr<Card> card = ps->get_set_card(set_index);
|
||||
if (card) {
|
||||
card->execute_attack(other_card);
|
||||
card->execute_attack(attacker_card);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1439,7 +1463,7 @@ void Card::apply_attack_result() {
|
||||
auto ps = this->player_state();
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
auto log = s->log_stack(string_printf("apply_attack_result(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("apply_attack_result(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id()));
|
||||
if (!this->action_chain.can_apply_attack()) {
|
||||
return;
|
||||
}
|
||||
@@ -1548,30 +1572,46 @@ void Card::apply_attack_result() {
|
||||
}
|
||||
}
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string as_str = as.str(s);
|
||||
log.debug("as constructed as %s", as_str.c_str());
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < this->action_chain.chain.target_card_ref_count; z++) {
|
||||
shared_ptr<Card> card = s->card_for_set_card_ref(this->action_chain.chain.target_card_refs[z]);
|
||||
if (card) {
|
||||
card->current_defense_power = card->action_metadata.attack_bonus;
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
log.debug("unknown_8024A6DC(@%04hX #%04hX) ...", card->get_card_ref(), card->get_card_id());
|
||||
s->card_special->unknown_8024A6DC(this->shared_from_this(), card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("compute_action_chain_results 1 ...");
|
||||
this->compute_action_chain_results(true, false);
|
||||
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
log.debug("apply_effects_before_attack ...");
|
||||
s->card_special->apply_effects_before_attack(this->shared_from_this());
|
||||
}
|
||||
if (!(this->card_flags & 2)) {
|
||||
log.debug("compute_action_chain_results 2 ...");
|
||||
this->compute_action_chain_results(true, false);
|
||||
log.debug("check_for_attack_interference ...");
|
||||
s->card_special->check_for_attack_interference(this->shared_from_this());
|
||||
}
|
||||
log.debug("compute_action_chain_results 3 ...");
|
||||
this->compute_action_chain_results(true, false);
|
||||
|
||||
log.debug("unknown_80236374 ...");
|
||||
this->unknown_80236374(this->shared_from_this(), nullptr);
|
||||
this->unknown_802362D8(this->shared_from_this());
|
||||
log.debug("execute_attack_on_all_valid_targets ...");
|
||||
this->execute_attack_on_all_valid_targets(this->shared_from_this());
|
||||
}
|
||||
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
log.debug("apply_effects_after_attack ...");
|
||||
s->card_special->apply_effects_after_attack(this->shared_from_this());
|
||||
}
|
||||
ps->stats.num_attacks_given++;
|
||||
@@ -1584,6 +1624,7 @@ void Card::apply_attack_result() {
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = s->player_states[client_id];
|
||||
if (ps) {
|
||||
log.debug("unknown_8023C110(%zu) ...", client_id);
|
||||
ps->unknown_8023C110();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
void dice_phase_before();
|
||||
bool is_guard_item() const;
|
||||
bool unknown_80236554(std::shared_ptr<Card> other_card, const ActionState* as);
|
||||
void unknown_802362D8(std::shared_ptr<Card> other_card);
|
||||
void execute_attack_on_all_valid_targets(std::shared_ptr<Card> attacker_card);
|
||||
void apply_attack_result();
|
||||
|
||||
private:
|
||||
|
||||
+173
-62
@@ -21,7 +21,7 @@ static string refs_str_for_cards_vector(const vector<shared_ptr<T>>& cards) {
|
||||
if (!ret.empty()) {
|
||||
ret += ", ";
|
||||
}
|
||||
ret += string_printf("@%04hX", ref_for_card(card));
|
||||
ret += phosg::string_printf("@%04hX", ref_for_card(card));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -244,15 +244,21 @@ void CardSpecial::apply_action_conditions(
|
||||
shared_ptr<Card> defender_card,
|
||||
uint32_t flags,
|
||||
const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack("apply_action_conditions: ");
|
||||
|
||||
ActionState temp_as;
|
||||
if (attacker_card == defender_card) {
|
||||
temp_as = this->create_attack_state_from_card_action_chain(attacker_card);
|
||||
if (as) {
|
||||
log.debug("using action state from override");
|
||||
temp_as = *as;
|
||||
} else {
|
||||
log.debug("using action state from attacker card");
|
||||
}
|
||||
} else {
|
||||
temp_as = this->create_defense_state_for_card_pair_action_chains(attacker_card, defender_card);
|
||||
log.debug("using action state from card pair");
|
||||
}
|
||||
|
||||
this->apply_defense_conditions(temp_as, when, defender_card, flags);
|
||||
@@ -297,22 +303,47 @@ bool CardSpecial::apply_defense_condition(
|
||||
shared_ptr<Card> defender_card,
|
||||
uint32_t flags,
|
||||
bool unknown_p8) {
|
||||
auto s = this->server();
|
||||
bool is_nte = s->options.is_nte();
|
||||
auto log = s->log_stack("apply_defense_condition: ");
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
log.debug(
|
||||
"when=%s, cond_index=%hhu, defender_card=(@%04hX #%04hX), flags=%08" PRIX32 ", p8=%s",
|
||||
phosg::name_for_enum(when),
|
||||
cond_index,
|
||||
defender_card->get_card_ref(),
|
||||
defender_card->get_card_id(),
|
||||
flags,
|
||||
unknown_p8 ? "true" : "false");
|
||||
auto defender_cond_str = defender_cond->str(s);
|
||||
auto defense_state_str = defense_state.str(s);
|
||||
log.debug("defender_cond = %s", defender_cond_str.c_str());
|
||||
log.debug("defense_state = %s", defense_state_str.c_str());
|
||||
}
|
||||
|
||||
if (defender_cond->type == ConditionType::NONE) {
|
||||
log.debug("no condition");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto orig_eff = this->original_definition_for_condition(*defender_cond);
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
auto orig_eff_str = orig_eff->str();
|
||||
log.debug("orig_eff = %s", orig_eff_str.c_str());
|
||||
}
|
||||
|
||||
uint16_t attacker_card_ref = defense_state.attacker_card_ref;
|
||||
if (attacker_card_ref == 0xFFFF) {
|
||||
attacker_card_ref = defense_state.original_attacker_card_ref;
|
||||
}
|
||||
log.debug("attacker_card_ref = @%04hX", attacker_card_ref);
|
||||
|
||||
auto s = this->server();
|
||||
bool is_nte = s->options.is_nte();
|
||||
bool defender_has_ability_trap = !is_nte && this->card_ref_has_ability_trap(*defender_cond);
|
||||
log.debug("defender_has_ability_trap = %s", defender_has_ability_trap ? "true" : "false");
|
||||
|
||||
if ((is_nte || (flags & 4)) && !this->is_card_targeted_by_condition(*defender_cond, defense_state, defender_card)) {
|
||||
log.debug("not targeted by condition");
|
||||
if (defender_cond->type != ConditionType::NONE) {
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
@@ -328,6 +359,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if ((when == EffectWhen::AFTER_ANY_CARD_ATTACK) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) {
|
||||
log.debug("deleting guom condition");
|
||||
CardShortStatus stat = defender_card->get_short_status();
|
||||
if (stat.card_flags & 4) {
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
@@ -364,6 +396,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if ((when == EffectWhen::BEFORE_DICE_PHASE_THIS_TEAM_TURN) && (flags & 4) && !defender_has_ability_trap && (defender_cond->type == ConditionType::ACID)) {
|
||||
log.debug("applying acid");
|
||||
int16_t hp = defender_card->get_current_hp();
|
||||
if (hp > 0) {
|
||||
this->send_6xB4x06_for_stat_delta(defender_card, defender_cond->card_ref, 0x20, -1, 0, 1);
|
||||
@@ -373,9 +406,11 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if (!orig_eff || (orig_eff->when != when)) {
|
||||
log.debug("unsetting flag 4");
|
||||
flags &= ~4;
|
||||
}
|
||||
if ((flags == 0) || defender_has_ability_trap) {
|
||||
log.debug("no condition remains to apply");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -392,8 +427,10 @@ bool CardSpecial::apply_defense_condition(
|
||||
|
||||
string expr = orig_eff->expr.decode();
|
||||
int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll);
|
||||
log.debug("execute_effect ...");
|
||||
this->execute_effect(*defender_cond, defender_card, expr_value, defender_cond->value, orig_eff->type, flags, attacker_card_ref);
|
||||
if (flags & 4) {
|
||||
log.debug("recomputing action chaing results");
|
||||
if (is_nte || !(defender_card->card_flags & 2)) {
|
||||
defender_card->compute_action_chain_results(true, false);
|
||||
}
|
||||
@@ -403,6 +440,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) {
|
||||
log.debug("dice roll was used; setting dice display flag");
|
||||
defender_cond->flags |= 1;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x08;
|
||||
@@ -453,7 +491,7 @@ bool CardSpecial::apply_stat_deltas_to_all_cards_from_all_conditions_with_card_r
|
||||
|
||||
bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condition& cond, shared_ptr<Card> card) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
string cond_str = cond.str(s);
|
||||
@@ -585,7 +623,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
break;
|
||||
trial_unimplemented:
|
||||
default:
|
||||
log.debug("%s: no adjustments for condition type", name_for_enum(cond_type));
|
||||
log.debug("%s: no adjustments for condition type", phosg::name_for_enum(cond_type));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1189,7 +1227,7 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
|
||||
|
||||
StatSwapType CardSpecial::compute_stat_swap_type(shared_ptr<const Card> card) const {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("compute_stat_swap_type(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("compute_stat_swap_type(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
if (!card) {
|
||||
log.debug("card is missing");
|
||||
return StatSwapType::NONE;
|
||||
@@ -1199,7 +1237,7 @@ StatSwapType CardSpecial::compute_stat_swap_type(shared_ptr<const Card> card) co
|
||||
for (size_t cond_index = 0; cond_index < 9; cond_index++) {
|
||||
auto& cond = card->action_chain.conditions[cond_index];
|
||||
if (cond.type != ConditionType::NONE) {
|
||||
auto cond_log = log.sub(string_printf("(%zu) ", cond_index));
|
||||
auto cond_log = log.sub(phosg::string_printf("(%zu) ", cond_index));
|
||||
string cond_str = cond.str(s);
|
||||
cond_log.debug("%s", cond_str.c_str());
|
||||
if (!this->card_ref_has_ability_trap(cond)) {
|
||||
@@ -1675,7 +1713,7 @@ int32_t CardSpecial::evaluate_effect_expr(
|
||||
const char* expr,
|
||||
DiceRoll& dice_roll) const {
|
||||
auto log = this->server()->log_stack("evaluate_effect_expr: ");
|
||||
if (log.min_level == LogLevel::DEBUG) {
|
||||
if (log.min_level == phosg::LogLevel::DEBUG) {
|
||||
log.debug("ast, expr=\"%s\", dice_roll=(client_id=%02hhX, a2=%02hhX, value=%02hhX, value_used_in_expr=%s, a5=%04hX)", expr, dice_roll.client_id, dice_roll.unknown_a2, dice_roll.value, dice_roll.value_used_in_expr ? "true" : "false", dice_roll.unknown_a5);
|
||||
ast.print(stderr);
|
||||
}
|
||||
@@ -1769,10 +1807,10 @@ bool CardSpecial::execute_effect(
|
||||
uint32_t unknown_p7,
|
||||
uint16_t attacker_card_ref) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("execute_effect(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("execute_effect(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
{
|
||||
string cond_str = cond.str(s);
|
||||
log.debug("cond=%s, card=@%04hX, expr_value=%hd, unknown_p5=%hd, cond_type=%s, unknown_p7=%" PRIu32 ", attacker_card_ref=@%04hX", cond_str.c_str(), ref_for_card(card), expr_value, unknown_p5, name_for_enum(cond_type), unknown_p7, attacker_card_ref);
|
||||
log.debug("cond=%s, card=@%04hX, expr_value=%hd, unknown_p5=%hd, cond_type=%s, unknown_p7=%" PRIu32 ", attacker_card_ref=@%04hX", cond_str.c_str(), ref_for_card(card), expr_value, unknown_p5, phosg::name_for_enum(cond_type), unknown_p7, attacker_card_ref);
|
||||
}
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
@@ -2804,7 +2842,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
int16_t p_target_type,
|
||||
bool apply_usability_filters) const {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("get_targeted_cards_for_condition(@%04hX, %hhu, @%04hX): ", card_ref, def_effect_index, setter_card_ref));
|
||||
auto log = s->log_stack(phosg::string_printf("get_targeted_cards_for_condition(@%04hX, %hhu, @%04hX): ", card_ref, def_effect_index, setter_card_ref));
|
||||
log.debug("card_ref=@%04hX, def_effect_index=%02hhX, setter_card_ref=@%04hX, as, p_target_type=%hd, apply_usability_filters=%s", card_ref, def_effect_index, setter_card_ref, p_target_type, apply_usability_filters ? "true" : "false");
|
||||
|
||||
vector<shared_ptr<const Card>> ret;
|
||||
@@ -2836,7 +2874,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
AttackMedium attack_medium = card2
|
||||
? card2->action_chain.chain.attack_medium
|
||||
: AttackMedium::UNKNOWN;
|
||||
log.debug("attack_medium=%s", name_for_enum(attack_medium));
|
||||
log.debug("attack_medium=%s", phosg::name_for_enum(attack_medium));
|
||||
|
||||
auto add_card_refs = [&](const vector<uint16_t>& result_card_refs) -> void {
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
@@ -3209,17 +3247,27 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
}
|
||||
break;
|
||||
case 0x27: // p39
|
||||
ret = this->find_all_set_cards_with_cost_in_range(4, 99);
|
||||
if (!s->options.is_nte()) {
|
||||
ret = this->filter_cards_by_range(ret, card1, card1_loc, card2);
|
||||
}
|
||||
break;
|
||||
case 0x28: // p40
|
||||
ret = this->find_all_set_cards_with_cost_in_range(0, 3);
|
||||
case 0x28: { // p40
|
||||
auto log3940 = log.sub("(p39/p40) ");
|
||||
ret = this->find_all_set_cards_with_cost_in_range(
|
||||
(p_target_type == 0x27) ? 4 : 0,
|
||||
(p_target_type == 0x27) ? 99 : 3);
|
||||
if (log3940.should_log(phosg::LogLevel::DEBUG)) {
|
||||
for (const auto& card : ret) {
|
||||
log3940.debug("found target @%04hX #%04hX", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
}
|
||||
if (!s->options.is_nte()) {
|
||||
log3940.debug("filtering targets");
|
||||
ret = this->filter_cards_by_range(ret, card1, card1_loc, card2);
|
||||
if (log3940.should_log(phosg::LogLevel::DEBUG)) {
|
||||
for (const auto& card : ret) {
|
||||
log3940.debug("retained target @%04hX #%04hX", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x29: { // p41
|
||||
auto ps = card1->player_state();
|
||||
if (card1 && ps) {
|
||||
@@ -3476,41 +3524,62 @@ bool CardSpecial::is_card_targeted_by_condition(
|
||||
const ActionState& as,
|
||||
shared_ptr<const Card> card) const {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack("is_card_targeted_by_condition: ");
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
log.debug("card=(@%04hX #%04hX)", card->get_card_ref(), card->get_card_id());
|
||||
auto cond_str = cond.str(s);
|
||||
auto as_str = as.str(s);
|
||||
log.debug("cond = %s", cond_str.c_str());
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
}
|
||||
|
||||
if (cond.type == ConditionType::NONE) {
|
||||
log.debug("condition is NONE (=> true)");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ce = s->definition_for_card_ref(cond.card_ref);
|
||||
auto sc_card = s->card_for_set_card_ref(cond.card_ref);
|
||||
if (cond.type != ConditionType::NONE) {
|
||||
if ((!sc_card || ((sc_card != card) && (sc_card->card_flags & 2))) &&
|
||||
ce &&
|
||||
((ce->def.type == CardType::ITEM) || ce->def.is_sc()) &&
|
||||
(cond.remaining_turns != 100) &&
|
||||
(s->options.is_nte() || (client_id_for_card_ref(card->get_card_ref()) == client_id_for_card_ref(cond.card_ref)))) {
|
||||
return false;
|
||||
}
|
||||
if (cond.remaining_turns == 102) {
|
||||
if (sc_card && ((sc_card == card) || !(sc_card->card_flags & 2))) {
|
||||
string arg3_s = ce->def.effects[cond.card_definition_effect_index].arg3.decode();
|
||||
if (arg3_s.size() < 1) {
|
||||
throw runtime_error("card definition arg3 is missing");
|
||||
}
|
||||
auto target_cards = this->get_targeted_cards_for_condition(
|
||||
cond.card_ref,
|
||||
cond.card_definition_effect_index,
|
||||
cond.condition_giver_card_ref,
|
||||
as,
|
||||
atoi(arg3_s.c_str() + 1),
|
||||
0);
|
||||
for (auto c : target_cards) {
|
||||
if (c == card) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
if ((!sc_card || ((sc_card != card) && (sc_card->card_flags & 2))) &&
|
||||
ce &&
|
||||
((ce->def.type == CardType::ITEM) || ce->def.is_sc()) &&
|
||||
(cond.remaining_turns != 100) &&
|
||||
(s->options.is_nte() || (client_id_for_card_ref(card->get_card_ref()) == client_id_for_card_ref(cond.card_ref)))) {
|
||||
log.debug("failed item or SC check (=> false)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cond.remaining_turns != 102) {
|
||||
log.debug("remaining_turns != 102 (=> true)");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sc_card && ((sc_card == card) || !(sc_card->card_flags & 2))) {
|
||||
string arg3_s = ce->def.effects[cond.card_definition_effect_index].arg3.decode();
|
||||
if (arg3_s.size() < 1) {
|
||||
throw runtime_error("card definition arg3 is missing");
|
||||
}
|
||||
auto target_cards = this->get_targeted_cards_for_condition(
|
||||
cond.card_ref,
|
||||
cond.card_definition_effect_index,
|
||||
cond.condition_giver_card_ref,
|
||||
as,
|
||||
atoi(arg3_s.c_str() + 1),
|
||||
0);
|
||||
for (auto c : target_cards) {
|
||||
if (c == card) {
|
||||
log.debug("targeted by p condition (=> true)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
log.debug("not targeted by p condition (=> false)");
|
||||
return false;
|
||||
} else {
|
||||
|
||||
log.debug("SC check does not apply");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardSpecial::on_card_set(shared_ptr<PlayerState> ps, uint16_t card_ref) {
|
||||
@@ -3944,13 +4013,13 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
bool apply_defense_condition_to_all_cards,
|
||||
uint16_t apply_defense_condition_to_card_ref) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("evaluate_and_apply_effects(%s, @%04hX, @%04hX): ", name_for_enum(when), set_card_ref, sc_card_ref));
|
||||
auto log = s->log_stack(phosg::string_printf("evaluate_and_apply_effects(%s, @%04hX, @%04hX): ", phosg::name_for_enum(when), set_card_ref, sc_card_ref));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
{
|
||||
string as_str = as.str(s);
|
||||
log.debug("when=%s, set_card_ref=@%04hX, as=%s, sc_card_ref=@%04hX, apply_defense_condition_to_all_cards=%s, apply_defense_condition_to_card_ref=@%04hX",
|
||||
name_for_enum(when), set_card_ref, as_str.c_str(), sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref);
|
||||
phosg::name_for_enum(when), set_card_ref, as_str.c_str(), sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref);
|
||||
}
|
||||
|
||||
if (!is_nte) {
|
||||
@@ -4010,12 +4079,12 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
log.debug("inputs: dice_roll=%02hhX, random_percent=%hhu, unknown_v1=%s", dice_roll.value, random_percent, unknown_v1 ? "true" : "false");
|
||||
|
||||
for (size_t def_effect_index = 0; (def_effect_index < 3) && !unknown_v1 && (ce->def.effects[def_effect_index].type != ConditionType::NONE); def_effect_index++) {
|
||||
auto effect_log = log.sub(string_printf("(effect:%zu) ", def_effect_index));
|
||||
auto effect_log = log.sub(phosg::string_printf("(effect:%zu) ", def_effect_index));
|
||||
const auto& card_effect = ce->def.effects[def_effect_index];
|
||||
string card_effect_str = card_effect.str();
|
||||
effect_log.debug("effect: %s", card_effect_str.c_str());
|
||||
if (card_effect.when != when) {
|
||||
effect_log.debug("does not apply (effect.when=%s, when=%s)", name_for_enum(card_effect.when), name_for_enum(when));
|
||||
effect_log.debug("does not apply (effect.when=%s, when=%s)", phosg::name_for_enum(card_effect.when), phosg::name_for_enum(when));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4067,7 +4136,7 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < targeted_cards.size(); z++) {
|
||||
auto target_log = effect_log.sub(string_printf("(target:@%04hX) ", targeted_cards[z]->get_card_ref()));
|
||||
auto target_log = effect_log.sub(phosg::string_printf("(target:@%04hX) ", targeted_cards[z]->get_card_ref()));
|
||||
dice_roll.value_used_in_expr = false;
|
||||
string arg2_str = card_effect.arg2.decode();
|
||||
target_log.debug("arg2_str = %s", arg2_str.c_str());
|
||||
@@ -4075,8 +4144,7 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
this->evaluate_effect_arg2_condition(
|
||||
as, targeted_cards[z], arg2_str.c_str(), dice_roll, set_card_ref, sc_card_ref, random_percent, when)) {
|
||||
target_log.debug("arg2 condition passed");
|
||||
auto env_stats = this->compute_attack_env_stats(
|
||||
as, targeted_cards[z], dice_roll, set_card_ref, sc_card_ref);
|
||||
auto env_stats = this->compute_attack_env_stats(as, targeted_cards[z], dice_roll, set_card_ref, sc_card_ref);
|
||||
string expr_str = card_effect.expr.decode();
|
||||
int16_t value = this->evaluate_effect_expr(env_stats, expr_str.c_str(), dice_roll);
|
||||
target_log.debug("expr = %s, value = %hd", expr_str.c_str(), value);
|
||||
@@ -4642,32 +4710,75 @@ vector<shared_ptr<const Card>> CardSpecial::filter_cards_by_range(
|
||||
shared_ptr<const Card> card1,
|
||||
const Location& card1_loc,
|
||||
shared_ptr<const Card> card2) const {
|
||||
auto log = this->server()->log_stack("filter_cards_by_range: ");
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
auto card1_str = card1 ? phosg::string_printf("@%04hX #%04hX", card1->get_card_ref(), card1->get_card_id()) : "null";
|
||||
auto card2_str = card2 ? phosg::string_printf("@%04hX #%04hX", card2->get_card_ref(), card2->get_card_id()) : "null";
|
||||
auto loc_str = card1_loc.str();
|
||||
log.debug("card1=(%s), card2=(%s), loc=%s", card1_str.c_str(), card2_str.c_str(), loc_str.c_str());
|
||||
|
||||
for (const auto& card : cards) {
|
||||
if (card) {
|
||||
log.debug("input card: @%04hX #%04hX", card->get_card_ref(), card->get_card_id());
|
||||
} else {
|
||||
log.debug("input card: null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<const Card>> ret;
|
||||
if (!card1 || cards.empty()) {
|
||||
log.debug("card1 missing or input list is blank");
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto ps = card1->player_state();
|
||||
if (!ps) {
|
||||
log.debug("ps is missing");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: Remove hardcoded card ID here (Earthquake)
|
||||
uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2);
|
||||
log.debug("card_id = #%04hX", card_id);
|
||||
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->options.card_index, card_id, card1_loc, this->server()->map_and_rules);
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
auto loc_str = card1_loc.str();
|
||||
log.debug("compute_effective_range(range, ci, #%04hX, %s, map) =>", card_id, loc_str.c_str());
|
||||
for (size_t y = 0; y < 9; y++) {
|
||||
const uint8_t* row = &range[y * 9];
|
||||
log.debug(" range[%zu] = %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX",
|
||||
y, row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]);
|
||||
}
|
||||
}
|
||||
|
||||
auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM);
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
for (uint16_t card_ref : card_refs_in_range) {
|
||||
log.debug("ref in range: @%04hX", card_ref);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto card : cards) {
|
||||
if (!card || (card->get_card_ref() == 0xFFFF)) {
|
||||
if (card) {
|
||||
log.debug("(@%04hX #%04hX) out of range", card->get_card_ref(), card->get_card_id());
|
||||
} else {
|
||||
log.debug("(null) card missing");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (uint16_t card_ref_in_range : card_refs_in_range) {
|
||||
if (card_ref_in_range == card->get_card_ref()) {
|
||||
log.debug("(@%04hX #%04hX) in range", card->get_card_ref(), card->get_card_id());
|
||||
ret.emplace_back(card);
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.debug("(@%04hX #%04hX) out of range", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -4767,7 +4878,7 @@ void CardSpecial::dice_phase_before_for_card(shared_ptr<Card> card) {
|
||||
template <EffectWhen When1, EffectWhen When2>
|
||||
void CardSpecial::apply_effects_on_phase_change_t(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("apply_effects_on_phase_change_t<%s, %s>(@%04hX #%04hX): ", name_for_enum(When1), name_for_enum(When2), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("apply_effects_on_phase_change_t<%s, %s>(@%04hX #%04hX): ", phosg::name_for_enum(When1), phosg::name_for_enum(When2), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
ActionState as;
|
||||
@@ -4818,7 +4929,7 @@ void CardSpecial::unknown_8024945C(shared_ptr<Card> unknown_p2, const ActionStat
|
||||
}
|
||||
|
||||
void CardSpecial::unknown_8024966C(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
|
||||
auto log = this->server()->log_stack(string_printf("unknown_8024966C(@%04hX #%04hX): ", unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
auto log = this->server()->log_stack(phosg::string_printf("unknown_8024966C(@%04hX #%04hX): ", unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
|
||||
ActionState as;
|
||||
if (!existing_as) {
|
||||
@@ -4969,8 +5080,8 @@ template <
|
||||
EffectWhen WhenTargetsAndActionCards>
|
||||
void CardSpecial::apply_effects_before_or_after_attack(shared_ptr<Card> unknown_p2) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("apply_effects_before_or_after_attack<%s, %s, %s, %s>(@%04hX #%04hX): ",
|
||||
name_for_enum(WhenAllCards), name_for_enum(WhenAttackerAndActionCards), name_for_enum(WhenAttackerOrHunterSCCard), name_for_enum(WhenTargetsAndActionCards), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
auto log = s->log_stack(phosg::string_printf("apply_effects_before_or_after_attack<%s, %s, %s, %s>(@%04hX #%04hX): ",
|
||||
phosg::name_for_enum(WhenAllCards), phosg::name_for_enum(WhenAttackerAndActionCards), phosg::name_for_enum(WhenAttackerOrHunterSCCard), phosg::name_for_enum(WhenTargetsAndActionCards), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
|
||||
ActionState as = this->create_attack_state_from_card_action_chain(unknown_p2);
|
||||
|
||||
|
||||
+342
-225
File diff suppressed because it is too large
Load Diff
+129
-63
@@ -16,6 +16,7 @@
|
||||
#include "../PlayerSubordinates.hh"
|
||||
#include "../Text.hh"
|
||||
#include "../TextIndex.hh"
|
||||
#include "../Types.hh"
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
@@ -506,7 +507,7 @@ struct CardDefinition {
|
||||
|
||||
void decode_code();
|
||||
std::string str() const;
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(Stat, 4);
|
||||
|
||||
struct Effect {
|
||||
@@ -522,7 +523,6 @@ struct CardDefinition {
|
||||
// rules; for example, the expression "4+4//2" results in 4, not 6.
|
||||
/* 02 */ pstring<TextEncoding::ASCII, 0x0F> expr;
|
||||
// when specifies in which phase the effect should activate.
|
||||
// TODO: There are many values that can be used here; document them.
|
||||
/* 11 */ EffectWhen when;
|
||||
// arg1 generally specifies how long the effect activates for.
|
||||
/* 12 */ pstring<TextEncoding::ASCII, 4> arg1;
|
||||
@@ -543,7 +543,7 @@ struct CardDefinition {
|
||||
bool is_empty() const;
|
||||
static std::string str_for_arg(const std::string& arg);
|
||||
std::string str(const char* separator = ", ", const TextSet* text_archive = nullptr) const;
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(Effect, 0x20);
|
||||
|
||||
/* 0000 */ be_uint32_t card_id;
|
||||
@@ -810,7 +810,7 @@ struct CardDefinition {
|
||||
|
||||
void decode_range();
|
||||
std::string str(bool single_line = true, const TextSet* text_archive = nullptr) const;
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(CardDefinition, 0x128);
|
||||
|
||||
struct CardDefinitionsFooter {
|
||||
@@ -907,23 +907,25 @@ struct PlayerConfig {
|
||||
// card counts array is encrypted in memory most of the time, and they went
|
||||
// out of their way to ensure the game uses an area of memory that almost no
|
||||
// other game uses, which is also used by the Action Replay.)
|
||||
/* 05A4:0450 */ parray<be_uint64_t, 0x1C2> rare_tokens;
|
||||
/* 05A4:0450 */ parray<be_uint64_t, 450> rare_tokens;
|
||||
/* 13B4:1260 */ parray<uint8_t, 0x80> unknown_a7;
|
||||
/* 1434:12E0 */ parray<DeckDefinition, 25> decks;
|
||||
/* 2118:1FC4 */ parray<uint8_t, 0x08> unknown_a8;
|
||||
/* 2120:1FCC */ be_uint32_t offline_clv_exp; // CLvOff = this / 100
|
||||
/* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = this / 100
|
||||
/* 2120:1FCC */ be_uint32_t offline_clv_exp; // CLvOff = (this / 100) + 1
|
||||
/* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = (this / 100) + 1
|
||||
struct PlayerReference {
|
||||
/* 00 */ be_uint32_t guild_card_number;
|
||||
/* 04 */ pstring<TextEncoding::MARKED, 0x18> name;
|
||||
} __packed_ws__(PlayerReference, 0x1C);
|
||||
// This array is updated when a battle is started (via a 6xB4x05 command). The
|
||||
// client adds the opposing players' info to ths first two entries here if the
|
||||
// opponents are human. (The existing entries are always moved back by two
|
||||
// slots, but if one or both opponents are not humans, one or both of the
|
||||
// newly-vacated slots is not filled in.)
|
||||
// These two arrays are updated when a battle is started (via a 6xB4x05
|
||||
// command). The client adds the opposing players' info to ths first two
|
||||
// entries in recent_human_opponents if the opponents are human. (The
|
||||
// existing entries are always moved back by two slots, but if one or both
|
||||
// opponents are not humans, one or both of the newly-vacated slots is not
|
||||
// filled in.) Both arrays have the most recent entries at the beginning.
|
||||
/* 2128:1FD4 */ parray<PlayerReference, 10> recent_human_opponents;
|
||||
/* 2240:20EC */ parray<uint8_t, 0x28> unknown_a10;
|
||||
/* 2240:20EC */ parray<be_uint32_t, 5> recent_battle_start_timestamps;
|
||||
/* 2254:2100 */ parray<uint8_t, 0x14> unknown_a10;
|
||||
/* 2268:2114 */ be_uint32_t init_timestamp;
|
||||
/* 226C:2118 */ be_uint32_t last_online_battle_start_timestamp;
|
||||
// In a certain situation, unknown_t3 is set to init_timestamp plus a multiple
|
||||
@@ -941,6 +943,41 @@ struct PlayerConfig {
|
||||
void encrypt(uint8_t basis);
|
||||
} __packed_ws__(PlayerConfig, 0x2350);
|
||||
|
||||
struct PlayerConfigNTE {
|
||||
/* 0000 */ pstring<TextEncoding::MARKED, 12> rank_text;
|
||||
/* 000C */ parray<uint8_t, 0x1C> unknown_a1;
|
||||
/* 0028 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
|
||||
/* 0050 */ parray<be_uint32_t, 10> choice_search_config;
|
||||
/* 0078 */ parray<be_uint32_t, 0x10> scenario_progress; // Final has 0x30 entries here
|
||||
/* 00B8 */ PlayerRecordsBattleBE unused_offline_records;
|
||||
/* 00D0 */ parray<uint8_t, 4> unknown_a4;
|
||||
/* 00D4 */ uint8_t is_encrypted;
|
||||
/* 00D5 */ uint8_t basis;
|
||||
/* 00D6 */ parray<uint8_t, 2> unused;
|
||||
/* 00D8 */ parray<uint8_t, 1000> card_counts;
|
||||
/* 04C0 */ parray<be_uint16_t, 50> card_count_checksums;
|
||||
/* 0524 */ parray<be_uint64_t, 300> rare_tokens;
|
||||
/* 0E84 */ parray<DeckDefinition, 25> decks;
|
||||
/* 1B68 */ parray<uint8_t, 0x08> unknown_a8;
|
||||
/* 1B70 */ be_uint32_t offline_clv_exp;
|
||||
/* 1B74 */ be_uint32_t online_clv_exp;
|
||||
/* 1B78 */ parray<PlayerConfig::PlayerReference, 10> recent_human_opponents;
|
||||
/* 1C90 */ parray<be_uint32_t, 5> recent_battle_start_timestamps;
|
||||
/* 1CA4 */ parray<uint8_t, 0x14> unknown_a10;
|
||||
/* 1CB8 */ be_uint32_t init_timestamp;
|
||||
/* 1CBC */ be_uint32_t last_online_battle_start_timestamp;
|
||||
/* 1CC0 */ be_uint32_t unknown_t3;
|
||||
/* 1CC4 */ parray<uint8_t, 0x94> unknown_a14;
|
||||
/* 1D58 */
|
||||
|
||||
PlayerConfigNTE() = default;
|
||||
explicit PlayerConfigNTE(const PlayerConfig& config);
|
||||
operator PlayerConfig() const;
|
||||
|
||||
void decrypt();
|
||||
void encrypt(uint8_t basis);
|
||||
} __packed_ws__(PlayerConfigNTE, 0x1D58);
|
||||
|
||||
enum class HPType : uint8_t {
|
||||
DEFEAT_PLAYER = 0,
|
||||
DEFEAT_TEAM = 1,
|
||||
@@ -995,8 +1032,8 @@ struct Rules {
|
||||
// likely be more work than it's worth.
|
||||
|
||||
Rules() = default;
|
||||
explicit Rules(const JSON& json);
|
||||
JSON json() const;
|
||||
explicit Rules(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
bool operator==(const Rules& other) const = default;
|
||||
bool operator!=(const Rules& other) const = default;
|
||||
void clear();
|
||||
@@ -1096,6 +1133,42 @@ struct CompressedMapHeader { // .mnm file format
|
||||
// Compressed data immediately follows (which decompresses to a MapDefinition)
|
||||
} __packed_ws__(CompressedMapHeader, 8);
|
||||
|
||||
struct OverlayState {
|
||||
// In the tiles array, the high 4 bits of each value are the tile type, and
|
||||
// the low 4 bits are the subtype. The types are:
|
||||
// 10: blocked by rock (as if the corresponding map_tiles value was 00)
|
||||
// 20: blocked by fence (as if the corresponding map_tiles value was 00)
|
||||
// 30-34: teleporters (2 of each value may be present)
|
||||
// 40-4F: traps on NTE
|
||||
// 40-44: traps on non-NTE (there may be up to 8 of each type, and one of
|
||||
// each is chosen to be a real trap at battle start); the trap types are:
|
||||
// 40: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
||||
// 41: Gold Rush, Charity, Requiem
|
||||
// 42: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
// 43: Brave Wind, Homesick, Fly
|
||||
// 44: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
// 50: blocked by metal box (appears as an improperly-z-buffered teal cube in
|
||||
// preview; behaves like 10 and 20 in game)
|
||||
// Any other value here will behave like 00 (no special tile behavior).
|
||||
parray<parray<uint8_t, 0x10>, 0x10> tiles;
|
||||
|
||||
// This field appears to be unused in both NTE and the final version. Perhaps
|
||||
// it had some meaning in a pre-NTE version.
|
||||
parray<le_uint32_t, 5> unused1;
|
||||
|
||||
// TODO: Figure out exactly where these colors are used
|
||||
parray<le_uint32_t, 0x10> trap_tile_colors_nte; // Unused on non-NTE
|
||||
|
||||
// This specifies the assist card IDs that each trap value (40-4F) will set
|
||||
// when triggered. This only has an effect on NTE; on non-NTE, this is unused
|
||||
// and a fixed set of assist cards is used instead. (On newserv, the set of
|
||||
// used assist cards can be overridden in the server configuration.)
|
||||
parray<le_uint16_t, 0x10> trap_card_ids_nte;
|
||||
|
||||
OverlayState();
|
||||
void clear();
|
||||
} __packed_ws__(OverlayState, 0x174);
|
||||
|
||||
struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
// If tag is not 0x00000100, the game considers the map to be corrupt in
|
||||
// offline mode and will delete it (if it's a download quest). The tag field
|
||||
@@ -1191,7 +1264,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
/* 48 */
|
||||
|
||||
std::string str() const;
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(CameraSpec, 0x48);
|
||||
|
||||
// This array specifies the camera zone maps. A camera zone map is a subset of
|
||||
@@ -1215,24 +1288,10 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
// (it is not yet known what the major index represents).
|
||||
/* 1AB8 */ parray<parray<CameraSpec, 2>, 3> overview_specs;
|
||||
|
||||
// In the modification_tiles array, the values are:
|
||||
// 10 = blocked by rock (as if the corresponding map_tiles value was 00)
|
||||
// 20 = blocked by fence (as if the corresponding map_tiles value was 00)
|
||||
// 30-34 = teleporters (2 of each value may be present)
|
||||
// 40-4F = traps on NTE
|
||||
// 40-44 = traps on non-NTE (one of each type is chosen at random to be a real
|
||||
// trap at battle start time)
|
||||
// 50 = blocked by metal box (appears as improperly-z-buffered teal cube in
|
||||
// preview; behaves like 10 and 20 in game)
|
||||
// The assist cards that each trap type can contain are:
|
||||
// 40: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
||||
// 41: Gold Rush, Charity, Requiem
|
||||
// 42: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
// 43: Brave Wind, Homesick, Fly
|
||||
// 44: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
/* 1C68 */ parray<parray<uint8_t, 0x10>, 0x10> modification_tiles;
|
||||
// This specifies the locations of blocked tiles, teleporters, and traps. See
|
||||
// the comments in OverlayState for details.
|
||||
/* 1C68 */ OverlayState overlay_state;
|
||||
|
||||
/* 1D68 */ parray<uint8_t, 0x74> unknown_a5;
|
||||
/* 1DDC */ Rules default_rules;
|
||||
|
||||
/* 1DF0 */ pstring<TextEncoding::MARKED, 0x14> name;
|
||||
@@ -1249,7 +1308,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
/* 00 */ pstring<TextEncoding::MARKED, 0x18> deck_name;
|
||||
/* 18 */ parray<be_uint16_t, 0x20> card_ids; // Last one appears to always be FFFF
|
||||
/* 58 */
|
||||
JSON json(uint8_t language) const;
|
||||
phosg::JSON json(uint8_t language) const;
|
||||
} __packed_ws__(NPCDeck, 0x58);
|
||||
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if name[0] == 0
|
||||
|
||||
@@ -1265,7 +1324,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
// TODO: Figure out exactly how these are used and document here.
|
||||
/* 0018 */ parray<be_uint16_t, 0x7E> params;
|
||||
/* 0114 */
|
||||
JSON json(uint8_t language) const;
|
||||
phosg::JSON json(uint8_t language) const;
|
||||
} __packed_ws__(AIParams, 0x114);
|
||||
/* 20F0 */ parray<AIParams, 3> npc_ai_params; // Unused if name[0] == 0
|
||||
|
||||
@@ -1318,7 +1377,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
// strings, excluding any that are empty or begin with the character '^'.
|
||||
/* 0004 */ parray<pstring<TextEncoding::MARKED, 0x40>, 4> strings;
|
||||
/* 0104 */
|
||||
JSON json(uint8_t language) const;
|
||||
phosg::JSON json(uint8_t language) const;
|
||||
} __packed_ws__(DialogueSet, 0x104);
|
||||
|
||||
// There are up to 0x10 of these per valid NPC, but only the first 13 of them
|
||||
@@ -1327,7 +1386,13 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
/* 28F0 */ parray<parray<DialogueSet, 0x10>, 3> dialogue_sets;
|
||||
|
||||
// These card IDs are always given to the player when they win a battle on
|
||||
// this map. Unused entries should be set to FFFF.
|
||||
// this map. Unused entries should be set to FFFF. Cards in this array are
|
||||
// ignored if they have any of these features (in the card definition):
|
||||
// - type is HUNTERS_SC or ARKZ_SC
|
||||
// - card_class is BOSS_ATTACK_ACTION or BOSS_TECH
|
||||
// - rank is D1, D2, or D3
|
||||
// - cannot_drop is 1 (specifically 1; other values don't prevent cards from
|
||||
// appearing)
|
||||
/* 59B0 */ parray<be_uint16_t, 0x10> reward_card_ids;
|
||||
|
||||
// These fields are used when determining which cards to drop after the battle
|
||||
@@ -1348,10 +1413,12 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
/* 59DC */ uint8_t map_category;
|
||||
|
||||
// This field determines block graphics to be used in the Cyber environment.
|
||||
// There are 10 block types (0-9); if this value is > 9, type 0 is used.
|
||||
// There are 10 block types (0-9); if this value is > 9, type 0 is used. This
|
||||
// field has no effect in Ep3 NTE, even though there are 6 different block
|
||||
// texture files on the NTE disc.
|
||||
/* 59DD */ uint8_t cyber_block_type;
|
||||
|
||||
/* 59DE */ parray<uint8_t, 2> unknown_a11;
|
||||
/* 59DE */ be_uint16_t unknown_a11;
|
||||
|
||||
// This array specifies which SC characters can't participate in the quest
|
||||
// (that is, the player is not allowed to choose decks with these SC cards).
|
||||
@@ -1401,7 +1468,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
|
||||
bool operator==(const EntryState& other) const = default;
|
||||
bool operator!=(const EntryState& other) const = default;
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
} __packed_ws__(EntryState, 2);
|
||||
/* 5A10 */ parray<EntryState, 4> entry_states;
|
||||
/* 5A18 */
|
||||
@@ -1417,7 +1484,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
|
||||
void assert_semantically_equivalent(const MapDefinition& other) const;
|
||||
|
||||
std::string str(const CardIndex* card_index, uint8_t language) const;
|
||||
JSON json(uint8_t language) const;
|
||||
phosg::JSON json(uint8_t language) const;
|
||||
} __packed_ws__(MapDefinition, 0x5A18);
|
||||
|
||||
struct MapDefinitionTrial {
|
||||
@@ -1435,9 +1502,8 @@ struct MapDefinitionTrial {
|
||||
/* 0118 */ parray<parray<parray<parray<uint8_t, 0x10>, 0x10>, 10>, 2> camera_zone_maps;
|
||||
/* 1518 */ parray<parray<MapDefinition::CameraSpec, 10>, 2> camera_zone_specs;
|
||||
/* 1AB8 */ parray<parray<MapDefinition::CameraSpec, 2>, 3> overview_specs;
|
||||
/* 1C68 */ parray<parray<uint8_t, 0x10>, 0x10> modification_tiles;
|
||||
/* 1D68 */ parray<uint8_t, 0x74> unknown_a5;
|
||||
/* 1DD4 */ RulesTrial default_rules;
|
||||
/* 1C68 */ OverlayState overlay_state;
|
||||
/* 1DDC */ RulesTrial default_rules;
|
||||
/* 1DE8 */ pstring<TextEncoding::MARKED, 0x14> name;
|
||||
/* 1DFC */ pstring<TextEncoding::MARKED, 0x14> location_name;
|
||||
/* 1E10 */ pstring<TextEncoding::MARKED, 0x3C> quest_name;
|
||||
@@ -1459,7 +1525,7 @@ struct MapDefinitionTrial {
|
||||
/* 4172 */ be_int16_t field_offset_y;
|
||||
/* 4174 */ uint8_t map_category;
|
||||
/* 4175 */ uint8_t cyber_block_type;
|
||||
/* 4176 */ parray<uint8_t, 2> unknown_a11;
|
||||
/* 4176 */ be_uint16_t unknown_a11;
|
||||
// TODO: This field may contain some version of unavailable_sc_cards and/or
|
||||
// entry_states from MapDefinition, but the format isn't the same
|
||||
/* 4178 */ parray<uint8_t, 0x28> unknown_t12;
|
||||
@@ -1500,7 +1566,7 @@ public:
|
||||
std::shared_ptr<const CardEntry> definition_for_name_normalized(const std::string& name) const;
|
||||
std::set<uint32_t> all_ids() const;
|
||||
uint64_t definitions_mtime() const;
|
||||
JSON definitions_json() const;
|
||||
phosg::JSON definitions_json() const;
|
||||
|
||||
private:
|
||||
static std::string normalize_card_name(const std::string& name);
|
||||
@@ -1581,37 +1647,37 @@ private:
|
||||
|
||||
// TODO: Figure out how to declare these inside the Episode3 namespace.
|
||||
template <>
|
||||
Episode3::HPType enum_for_name<Episode3::HPType>(const char* name);
|
||||
Episode3::HPType phosg::enum_for_name<Episode3::HPType>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::HPType>(Episode3::HPType hp_type);
|
||||
const char* phosg::name_for_enum<Episode3::HPType>(Episode3::HPType hp_type);
|
||||
template <>
|
||||
Episode3::DiceExchangeMode enum_for_name<Episode3::DiceExchangeMode>(const char* name);
|
||||
Episode3::DiceExchangeMode phosg::enum_for_name<Episode3::DiceExchangeMode>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::DiceExchangeMode>(Episode3::DiceExchangeMode dice_exchange_mode);
|
||||
const char* phosg::name_for_enum<Episode3::DiceExchangeMode>(Episode3::DiceExchangeMode dice_exchange_mode);
|
||||
template <>
|
||||
Episode3::AllowedCards enum_for_name<Episode3::AllowedCards>(const char* name);
|
||||
Episode3::AllowedCards phosg::enum_for_name<Episode3::AllowedCards>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AllowedCards>(Episode3::AllowedCards allowed_cards);
|
||||
const char* phosg::name_for_enum<Episode3::AllowedCards>(Episode3::AllowedCards allowed_cards);
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::BattlePhase>(Episode3::BattlePhase phase);
|
||||
const char* phosg::name_for_enum<Episode3::BattlePhase>(Episode3::BattlePhase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::SetupPhase>(Episode3::SetupPhase phase);
|
||||
const char* phosg::name_for_enum<Episode3::SetupPhase>(Episode3::SetupPhase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::RegistrationPhase>(Episode3::RegistrationPhase phase);
|
||||
const char* phosg::name_for_enum<Episode3::RegistrationPhase>(Episode3::RegistrationPhase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::ActionSubphase>(Episode3::ActionSubphase phase);
|
||||
const char* phosg::name_for_enum<Episode3::ActionSubphase>(Episode3::ActionSubphase phase);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AttackMedium>(Episode3::AttackMedium medium);
|
||||
const char* phosg::name_for_enum<Episode3::AttackMedium>(Episode3::AttackMedium medium);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CriterionCode>(Episode3::CriterionCode code);
|
||||
const char* phosg::name_for_enum<Episode3::CriterionCode>(Episode3::CriterionCode code);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CardType>(Episode3::CardType type);
|
||||
const char* phosg::name_for_enum<Episode3::CardType>(Episode3::CardType type);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::CardClass>(Episode3::CardClass cc);
|
||||
const char* phosg::name_for_enum<Episode3::CardClass>(Episode3::CardClass cc);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::ConditionType>(Episode3::ConditionType cond_type);
|
||||
const char* phosg::name_for_enum<Episode3::ConditionType>(Episode3::ConditionType cond_type);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::EffectWhen>(Episode3::EffectWhen when);
|
||||
const char* phosg::name_for_enum<Episode3::EffectWhen>(Episode3::EffectWhen when);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::Direction>(Episode3::Direction d);
|
||||
const char* phosg::name_for_enum<Episode3::Direction>(Episode3::Direction d);
|
||||
|
||||
@@ -99,17 +99,4 @@ MapAndRulesStateTrial::operator MapAndRulesState() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
OverlayState::OverlayState() {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void OverlayState::clear() {
|
||||
for (size_t y = 0; y < this->tiles.size(); y++) {
|
||||
this->tiles[y].clear(0);
|
||||
}
|
||||
this->unused1.clear(0);
|
||||
this->unused2.clear(0);
|
||||
this->trap_card_ids_nte.clear(0);
|
||||
}
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
@@ -67,14 +67,4 @@ struct MapAndRulesStateTrial {
|
||||
operator MapAndRulesState() const;
|
||||
} __packed_ws__(MapAndRulesStateTrial, 0x130);
|
||||
|
||||
struct OverlayState {
|
||||
parray<parray<uint8_t, 0x10>, 0x10> tiles;
|
||||
parray<le_uint32_t, 5> unused1;
|
||||
parray<le_uint32_t, 0x10> unused2;
|
||||
parray<le_uint16_t, 0x10> trap_card_ids_nte; // Unused on non-NTE
|
||||
|
||||
OverlayState();
|
||||
void clear();
|
||||
} __packed_ws__(OverlayState, 0x174);
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
@@ -132,6 +132,11 @@ shared_ptr<const Server> PlayerState::server() const {
|
||||
return s;
|
||||
}
|
||||
|
||||
bool PlayerState::is_alive() const {
|
||||
auto sc_card = this->get_sc_card();
|
||||
return (sc_card && !(sc_card->card_flags & 2));
|
||||
}
|
||||
|
||||
bool PlayerState::draw_cards_allowed() const {
|
||||
if (this->assist_flags & AssistFlag::IS_SKIPPING_TURN) {
|
||||
return false;
|
||||
@@ -608,7 +613,7 @@ void PlayerState::discard_and_redraw_hand() {
|
||||
}
|
||||
|
||||
if (!s->options.is_nte()) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 3;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
@@ -712,7 +717,7 @@ bool PlayerState::do_mulligan() {
|
||||
}
|
||||
|
||||
if (!s->options.is_nte()) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 3;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
@@ -1415,7 +1420,7 @@ bool PlayerState::set_card_from_hand(
|
||||
s->send_6xB4x05();
|
||||
|
||||
if (!is_nte) {
|
||||
G_AddToSetCardlog_Ep3_6xB4x4A cmd;
|
||||
G_AddToSetCardLog_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.card_refs[0] = card_ref;
|
||||
cmd.client_id = this->client_id;
|
||||
@@ -1780,16 +1785,16 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
auto card = s->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
if (card) {
|
||||
card->loc.direction = pa.facing_direction;
|
||||
log.debug("set facing direction to %s", name_for_enum(card->loc.direction));
|
||||
log.debug("set facing direction to %s", phosg::name_for_enum(card->loc.direction));
|
||||
|
||||
G_AddToSetCardlog_Ep3_6xB4x4A cmd;
|
||||
G_AddToSetCardLog_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.round_num = s->get_round_num();
|
||||
cmd.entry_count = 0;
|
||||
size_t z = 0;
|
||||
do {
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
||||
log.debug("on action card ref %s", ref_str.c_str());
|
||||
}
|
||||
@@ -1826,7 +1831,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
for (size_t z = 0; (z < 4 * 9) && (pa.target_card_refs[z] != 0xFFFF); z++) {
|
||||
auto target_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||
if (target_card) {
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.target_card_refs[z]);
|
||||
log.debug("on target card ref %s", ref_str.c_str());
|
||||
}
|
||||
@@ -1842,7 +1847,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
}
|
||||
}
|
||||
if (!is_nte) {
|
||||
G_AddToSetCardlog_Ep3_6xB4x4A cmd;
|
||||
G_AddToSetCardLog_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.round_num = s->get_round_num();
|
||||
@@ -1856,7 +1861,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
this->subtract_or_check_atk_or_def_points_for_action(pa, 1);
|
||||
}
|
||||
for (size_t z = 0; (z < pa.action_card_refs.size()) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
||||
log.debug("discarding %s from hand", ref_str.c_str());
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
std::shared_ptr<Server> server();
|
||||
std::shared_ptr<const Server> server() const;
|
||||
|
||||
bool is_alive() const;
|
||||
|
||||
bool draw_cards_allowed() const;
|
||||
void apply_assist_card_effect_on_set(std::shared_ptr<PlayerState> setter_ps);
|
||||
void apply_dice_effects();
|
||||
|
||||
@@ -64,11 +64,11 @@ void Condition::clear_FF() {
|
||||
std::string Condition::str(shared_ptr<const Server> s) const {
|
||||
auto card_ref_str = s->debug_str_for_card_ref(this->card_ref);
|
||||
auto giver_ref_str = s->debug_str_for_card_ref(this->condition_giver_card_ref);
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"Condition[type=%s, turns=%hhu, a_arg=%hhd, dice=%hhu, flags=%02hhX, "
|
||||
"def_eff_index=%hhu, ref=%s, value=%hd, giver_ref=%s "
|
||||
"percent=%hhu value8=%hd order=%hu a8=%hu]",
|
||||
name_for_enum(this->type),
|
||||
phosg::name_for_enum(this->type),
|
||||
this->remaining_turns,
|
||||
this->a_arg_value,
|
||||
this->dice_roll_value,
|
||||
@@ -103,7 +103,7 @@ void EffectResult::clear() {
|
||||
std::string EffectResult::str(shared_ptr<const Server> s) const {
|
||||
string attacker_ref_str = s->debug_str_for_card_ref(this->attacker_card_ref);
|
||||
string target_ref_str = s->debug_str_for_card_ref(this->target_card_ref);
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"EffectResult[att_ref=%s, target_ref=%s, value=%hhd, "
|
||||
"cur_hp=%hhd, ap=%hhd, tp=%hhd, flags=%02hhX, op=%hhd, "
|
||||
"cond_index=%hhu, dice=%hhu]",
|
||||
@@ -139,7 +139,7 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const {
|
||||
std::string CardShortStatus::str(shared_ptr<const Server> s) const {
|
||||
string loc_s = this->loc.str();
|
||||
string ref_str = s->debug_str_for_card_ref(this->card_ref);
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"CardShortStatus[ref=%s, cur_hp=%hd, flags=%08" PRIX32 ", loc=%s, "
|
||||
"u1=%04hX, max_hp=%hhd, u2=%hhu]",
|
||||
ref_str.c_str(),
|
||||
@@ -193,13 +193,13 @@ std::string ActionState::str(shared_ptr<const Server> s) const {
|
||||
string original_attacker_ref_s = s->debug_str_for_card_ref(this->original_attacker_card_ref);
|
||||
string target_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
|
||||
string action_refs_s = s->debug_str_for_card_refs(this->action_card_refs);
|
||||
return string_printf(
|
||||
"ActionState[client=%hu, u=%hhu, facing=%s, attacker_ref=%s, "
|
||||
return phosg::string_printf(
|
||||
"ActionState[client=%hX, u=%hhu, facing=%s, attacker_ref=%s, "
|
||||
"def_ref=%s, target_refs=%s, action_refs=%s, "
|
||||
"orig_attacker_ref=%s]",
|
||||
this->client_id.load(),
|
||||
this->unused,
|
||||
name_for_enum(this->facing_direction),
|
||||
phosg::name_for_enum(this->facing_direction),
|
||||
attacker_ref_s.c_str(),
|
||||
defense_ref_s.c_str(),
|
||||
target_refs_s.c_str(),
|
||||
@@ -243,7 +243,7 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
|
||||
string unknown_card_ref_a3_s = s->debug_str_for_card_ref(this->unknown_card_ref_a3);
|
||||
string attack_action_card_refs_s = s->debug_str_for_card_refs(this->attack_action_card_refs);
|
||||
string target_card_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"ActionChain[eff_ap=%hhd, eff_tp=%hhd, ap_bonus=%hhd, damage=%hhd, "
|
||||
"acting_ref=%s, unknown_ref_a3=%s, attack_action_refs=%s, "
|
||||
"attack_action_ref_count=%hhu, medium=%s, target_ref_count=%hhu, "
|
||||
@@ -258,9 +258,9 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
|
||||
unknown_card_ref_a3_s.c_str(),
|
||||
attack_action_card_refs_s.c_str(),
|
||||
this->attack_action_card_ref_count,
|
||||
name_for_enum(this->attack_medium),
|
||||
phosg::name_for_enum(this->attack_medium),
|
||||
this->target_card_ref_count,
|
||||
name_for_enum(this->action_subphase),
|
||||
phosg::name_for_enum(this->action_subphase),
|
||||
this->strike_count,
|
||||
this->damage_multiplier,
|
||||
this->attack_number,
|
||||
@@ -341,7 +341,7 @@ std::string ActionChainWithConds::str(shared_ptr<const Server> s) const {
|
||||
if (ret.back() != '[') {
|
||||
ret += ", ";
|
||||
}
|
||||
ret += string_printf("%zu:", z);
|
||||
ret += phosg::string_printf("%zu:", z);
|
||||
ret += this->conditions[z].str(s);
|
||||
}
|
||||
}
|
||||
@@ -580,7 +580,7 @@ std::string ActionMetadata::str(shared_ptr<const Server> s) const {
|
||||
string target_card_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
|
||||
string defense_card_refs_s = s->debug_str_for_card_refs(this->defense_card_refs);
|
||||
string original_attacker_card_refs_s = s->debug_str_for_card_refs(this->original_attacker_card_refs);
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"ActionMetadata[ref=%s, target_ref_count=%hhu, def_ref_count=%hhu, "
|
||||
"subphase=%s, def_power=%hhd, def_bonus=%hhd, "
|
||||
"att_bonus=%hhd, flags=%08" PRIX32 ", target_refs=%s, "
|
||||
@@ -588,7 +588,7 @@ std::string ActionMetadata::str(shared_ptr<const Server> s) const {
|
||||
card_ref_s.c_str(),
|
||||
this->target_card_ref_count,
|
||||
this->defense_card_ref_count,
|
||||
name_for_enum(this->action_subphase),
|
||||
phosg::name_for_enum(this->action_subphase),
|
||||
this->defense_power,
|
||||
this->defense_bonus,
|
||||
this->attack_bonus,
|
||||
@@ -683,7 +683,7 @@ std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
|
||||
string set_card_refs_s = s->debug_str_for_card_refs(this->set_card_refs);
|
||||
string hand_card_refs2_s = s->debug_str_for_card_refs(this->hand_card_refs2);
|
||||
string set_card_refs2_s = s->debug_str_for_card_refs(this->set_card_refs2);
|
||||
return string_printf(
|
||||
return phosg::string_printf(
|
||||
"HandAndEquipState[dice=[%hhu, %hhu], atk=%hhu, def=%hhu, atk2=%hhu, "
|
||||
"a1=%hhu, total_set_cost=%hhu, is_cpu=%hhu, assist_flags=%08" PRIX32 ", "
|
||||
"hand_refs=%s, assist_ref=%s, set_refs=%s, sc_ref=%s, hand_refs2=%s, "
|
||||
@@ -858,7 +858,7 @@ static bool is_card_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
const Location& anchor_loc,
|
||||
const CardShortStatus& ss,
|
||||
PrefixedLogger* log) {
|
||||
phosg::PrefixedLogger* log) {
|
||||
if (ss.card_ref == 0xFFFF) {
|
||||
if (log) {
|
||||
log->debug("is_card_within_range: (false) ss.card_ref missing");
|
||||
@@ -899,7 +899,7 @@ vector<uint16_t> get_card_refs_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
const Location& loc,
|
||||
const parray<CardShortStatus, 0x10>& short_statuses,
|
||||
PrefixedLogger* log) {
|
||||
phosg::PrefixedLogger* log) {
|
||||
vector<uint16_t> ret;
|
||||
if (is_card_within_range(range, loc, short_statuses[0], log)) {
|
||||
if (log) {
|
||||
|
||||
@@ -329,6 +329,6 @@ std::vector<uint16_t> get_card_refs_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
const Location& loc,
|
||||
const parray<CardShortStatus, 0x10>& short_statuses,
|
||||
PrefixedLogger* log = nullptr);
|
||||
phosg::PrefixedLogger* log = nullptr);
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
@@ -15,8 +15,8 @@ void compute_effective_range(
|
||||
uint16_t card_id,
|
||||
const Location& loc,
|
||||
shared_ptr<const MapAndRulesState> map_and_rules,
|
||||
PrefixedLogger* log) {
|
||||
if (log && log->should_log(LogLevel::DEBUG)) {
|
||||
phosg::PrefixedLogger* log) {
|
||||
if (log && log->should_log(phosg::LogLevel::DEBUG)) {
|
||||
string loc_str = loc.str();
|
||||
log->debug("compute_effective_range: card_id=#%04hX, loc=%s", card_id, loc_str.c_str());
|
||||
log->debug("compute_effective_range: map_and_rules->map:");
|
||||
@@ -941,7 +941,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
AttackMedium attack_medium) const {
|
||||
auto s = this->server();
|
||||
bool is_nte = s->options.is_nte();
|
||||
auto log = s->log_stack(string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", name_for_enum(attack_medium)));
|
||||
auto log = s->log_stack(phosg::string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", phosg::name_for_enum(attack_medium)));
|
||||
|
||||
if (static_cast<uint8_t>(attack_medium) & 0x80) {
|
||||
attack_medium = AttackMedium::UNKNOWN;
|
||||
@@ -969,7 +969,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
}
|
||||
criterion_code = ce1->def.effects[def_effect_index].apply_criterion;
|
||||
}
|
||||
log.debug("criterion_code=%s", name_for_enum(criterion_code));
|
||||
log.debug("criterion_code=%s", phosg::name_for_enum(criterion_code));
|
||||
|
||||
// For item usability checks, prevent criteria that depend on player
|
||||
// positioning/team setup
|
||||
@@ -1865,7 +1865,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
|
||||
return -0x7E;
|
||||
}
|
||||
|
||||
int32_t x_offset, y_offset;
|
||||
int32_t x_offset = 0, y_offset = 0;
|
||||
this->offsets_for_direction(summon_area_loc, &x_offset, &y_offset);
|
||||
if (x_offset == 0) {
|
||||
if ((loc->x < 1) && (loc->x >= this->map_and_rules->map.width - 1)) {
|
||||
@@ -2059,7 +2059,7 @@ shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint3
|
||||
|
||||
uint32_t RulerServer::get_card_id_with_effective_range(
|
||||
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const {
|
||||
auto log = this->server()->log_stack(string_printf("get_card_id_with_effective_range(@%04hX, #%04hX): ", card_ref, card_id_override));
|
||||
auto log = this->server()->log_stack(phosg::string_printf("get_card_id_with_effective_range(@%04hX, #%04hX): ", card_ref, card_id_override));
|
||||
|
||||
uint16_t card_id = (card_id_override == 0xFFFF)
|
||||
? this->card_id_for_card_ref(card_ref)
|
||||
@@ -2076,7 +2076,8 @@ uint32_t RulerServer::get_card_id_with_effective_range(
|
||||
if (this->card_ref_or_sc_has_fixed_range(card_ref)) {
|
||||
// Undo the override that may have been passed in
|
||||
log.debug("@%04hX has FIXED_RANGE", card_ref);
|
||||
auto orig_ce = this->definition_for_card_id(this->card_id_for_card_ref(card_ref));
|
||||
card_id = this->card_id_for_card_ref(card_ref);
|
||||
auto orig_ce = this->definition_for_card_id(card_id);
|
||||
if (orig_ce && (static_cast<uint8_t>(effective_target_mode) < 6)) {
|
||||
log.debug("ce valid for #%04hX with effective target mode %s; overriding to %s", card_id, name_for_target_mode(effective_target_mode), name_for_target_mode(orig_ce->def.target_mode));
|
||||
effective_target_mode = orig_ce->def.target_mode;
|
||||
|
||||
@@ -19,7 +19,7 @@ void compute_effective_range(
|
||||
uint16_t card_id,
|
||||
const Location& loc,
|
||||
std::shared_ptr<const MapAndRulesState> map_and_rules,
|
||||
PrefixedLogger* log = nullptr);
|
||||
phosg::PrefixedLogger* log = nullptr);
|
||||
|
||||
bool card_linkage_is_valid(
|
||||
std::shared_ptr<const CardIndex::CardEntry> right_def,
|
||||
|
||||
+52
-89
@@ -12,7 +12,8 @@ using namespace std;
|
||||
namespace Episode3 {
|
||||
|
||||
// This is (obviously) not the original string. The original string is:
|
||||
// "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
|
||||
// "03/05/29 18:00 by K.Toya" (NTE)
|
||||
// "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" (Final)
|
||||
static const char* VERSION_SIGNATURE =
|
||||
"newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
|
||||
static const char* VERSION_SIGNATURE_NTE =
|
||||
@@ -82,7 +83,7 @@ Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
||||
|
||||
Server::~Server() noexcept(false) {
|
||||
if (this->logger_stack.size() != 1) {
|
||||
throw logic_error(string_printf("incorrect logger stack size: expected 1, received %zu", this->logger_stack.size()));
|
||||
throw logic_error(phosg::string_printf("incorrect logger stack size: expected 1, received %zu", this->logger_stack.size()));
|
||||
}
|
||||
delete this->logger_stack.back();
|
||||
}
|
||||
@@ -125,7 +126,7 @@ Server::StackLogger::StackLogger(const Server* s, const std::string& prefix)
|
||||
s->logger_stack.push_back(this);
|
||||
}
|
||||
|
||||
Server::StackLogger::StackLogger(const Server* s, const std::string& prefix, LogLevel min_level)
|
||||
Server::StackLogger::StackLogger(const Server* s, const std::string& prefix, phosg::LogLevel min_level)
|
||||
: PrefixedLogger(prefix, min_level),
|
||||
server(s) {
|
||||
s->logger_stack.push_back(this);
|
||||
@@ -172,9 +173,9 @@ std::string Server::debug_str_for_card_ref(uint16_t card_ref) const {
|
||||
auto ce = this->definition_for_card_ref(card_ref);
|
||||
if (ce) {
|
||||
string name = ce->def.en_name.decode();
|
||||
return string_printf("@%04hX (#%04" PRIX32 " %s)", card_ref, ce->def.card_id.load(), name.c_str());
|
||||
return phosg::string_printf("@%04hX (#%04" PRIX32 " %s)", card_ref, ce->def.card_id.load(), name.c_str());
|
||||
} else {
|
||||
return string_printf("@%04hX (missing)", card_ref);
|
||||
return phosg::string_printf("@%04hX (missing)", card_ref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +186,9 @@ std::string Server::debug_str_for_card_id(uint16_t card_id) const {
|
||||
auto ce = this->definition_for_card_id(card_id);
|
||||
if (ce) {
|
||||
string name = ce->def.en_name.decode();
|
||||
return string_printf("#%04hX (%s)", card_id, name.c_str());
|
||||
return phosg::string_printf("#%04hX (%s)", card_id, name.c_str());
|
||||
} else {
|
||||
return string_printf("#%04hX (missing)", card_id);
|
||||
return phosg::string_printf("#%04hX (missing)", card_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
||||
!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING) &&
|
||||
(size >= 8)) {
|
||||
masked_data.assign(reinterpret_cast<const char*>(data), size);
|
||||
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(masked_data.data(), masked_data.size(), mask_key);
|
||||
data = masked_data.data();
|
||||
size = masked_data.size();
|
||||
@@ -260,7 +261,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
||||
|
||||
} else if ((this->options.behavior_flags & BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING) &&
|
||||
this->log().info("Generated command")) {
|
||||
print_data(stderr, data, size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
|
||||
phosg::print_data(stderr, data, size, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,9 +273,9 @@ void Server::send_6xB4x46() const {
|
||||
// debugging easier.
|
||||
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
||||
cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, 1);
|
||||
cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||
string build_date = format_time(BUILD_TIMESTAMP);
|
||||
cmd.date_str2.encode(string_printf("newserv %s compiled at %s", GIT_REVISION_HASH, build_date.c_str()), 1);
|
||||
cmd.date_str1.encode(phosg::format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||
string build_date = phosg::format_time(BUILD_TIMESTAMP);
|
||||
cmd.date_str2.encode(phosg::string_printf("newserv %s compiled at %s", GIT_REVISION_HASH, build_date.c_str()), 1);
|
||||
this->send(cmd);
|
||||
}
|
||||
|
||||
@@ -283,7 +284,7 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
|
||||
|
||||
const auto& compressed = vm->compressed(is_nte);
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
uint32_t subcommand_size = (compressed.size() + sizeof(G_MapData_Ep3_6xB6x41) + 3) & (~3);
|
||||
w.put<G_MapData_Ep3_6xB6x41>({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed.size(), 0});
|
||||
w.write(compressed);
|
||||
@@ -347,7 +348,7 @@ __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(con
|
||||
if (l && (this->options.behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
std::string buf = string_vprintf(fmt, va);
|
||||
std::string buf = phosg::string_vprintf(fmt, va);
|
||||
va_end(va);
|
||||
send_text_message(l, buf);
|
||||
}
|
||||
@@ -358,7 +359,7 @@ __attribute__((format(printf, 2, 3))) void Server::send_info_message_printf(cons
|
||||
if (l) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
std::string buf = string_vprintf(fmt, va);
|
||||
std::string buf = phosg::string_vprintf(fmt, va);
|
||||
va_end(va);
|
||||
send_text_message(l, buf);
|
||||
}
|
||||
@@ -375,8 +376,7 @@ void Server::send_debug_command_received_message(uint8_t subsubcommand, const ch
|
||||
this->send_debug_message_printf("$C5*/CAx%02hhX %s", subsubcommand, description);
|
||||
}
|
||||
|
||||
void Server::send_debug_message_if_error_code_nonzero(
|
||||
uint8_t client_id, int32_t error_code) const {
|
||||
void Server::send_debug_message_if_error_code_nonzero(uint8_t client_id, int32_t error_code) const {
|
||||
if (error_code < 0) {
|
||||
this->send_debug_message_printf("$C4%hhu/ERROR -0x%zX", client_id, static_cast<ssize_t>(-error_code));
|
||||
} else if (error_code > 0) {
|
||||
@@ -392,8 +392,7 @@ void Server::add_team_exp(uint8_t team_id, int32_t exp) {
|
||||
}
|
||||
}
|
||||
|
||||
this->team_exp[team_id] = clamp<int16_t>(
|
||||
this->team_exp[team_id] + exp, 0, this->team_client_count[team_id] * 96);
|
||||
this->team_exp[team_id] = clamp<int16_t>(this->team_exp[team_id] + exp, 0, this->team_client_count[team_id] * 96);
|
||||
|
||||
uint8_t dice_boost = this->team_exp[team_id] / (this->team_client_count[team_id] * 12);
|
||||
this->card_special->adjust_dice_boost_if_team_has_condition_52(team_id, &dice_boost, 0);
|
||||
@@ -475,29 +474,7 @@ shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
||||
}
|
||||
|
||||
shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
||||
// TODO: It'd be nice to deduplicate this function with the non-const version.
|
||||
if (card_ref == 0xFFFF) {
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t client_id = client_id_for_card_ref(card_ref);
|
||||
if (client_id == 0xFF) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
return nullptr;
|
||||
}
|
||||
auto card = ps->get_sc_card();
|
||||
if (card && (card->get_card_ref() == card_ref)) {
|
||||
return card;
|
||||
}
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
card = ps->get_set_card(set_index);
|
||||
if (card && (card->get_card_ref() == card_ref)) {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return const_cast<Server*>(this)->card_for_set_card_ref(card_ref);
|
||||
}
|
||||
|
||||
uint16_t Server::card_id_for_card_ref(uint16_t card_ref) const {
|
||||
@@ -862,7 +839,7 @@ void Server::draw_phase_after() {
|
||||
// facilities used are different.
|
||||
uint64_t limit_5mins = this->map_and_rules->rules.overall_time_limit;
|
||||
uint64_t end_usecs = this->battle_start_usecs + (limit_5mins * 300 * 1000 * 1000);
|
||||
if (now() >= end_usecs) {
|
||||
if (phosg::now() >= end_usecs) {
|
||||
this->overall_time_expired = true;
|
||||
}
|
||||
}
|
||||
@@ -972,7 +949,7 @@ void Server::end_action_phase() {
|
||||
|
||||
bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
||||
auto log = this->log_stack("enqueue_attack_or_defense: ");
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string s = pa->str(this->shared_from_this());
|
||||
log.debug("input: %s", s.c_str());
|
||||
}
|
||||
@@ -1022,7 +999,7 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
||||
|
||||
size_t attack_index = this->num_pending_attacks++;
|
||||
this->pending_attacks[attack_index] = *pa;
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string pa_str = this->pending_attacks[attack_index].str(this->shared_from_this());
|
||||
log.debug("set pending attack %zu: %s", attack_index, pa_str.c_str());
|
||||
}
|
||||
@@ -1180,7 +1157,7 @@ void Server::move_phase_after() {
|
||||
(abs(sc_card->loc.x - trap_x) < 2) &&
|
||||
(abs(sc_card->loc.y - trap_y) < 2) &&
|
||||
ps->replace_assist_card_by_id(trap_card_id)) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 0x01;
|
||||
cmd.client_id = client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
@@ -1711,7 +1688,7 @@ void Server::setup_and_start_battle() {
|
||||
cmd.start_battle = 1;
|
||||
this->send(cmd);
|
||||
}
|
||||
this->battle_start_usecs = now();
|
||||
this->battle_start_usecs = phosg::now();
|
||||
|
||||
this->send_6xB4x46();
|
||||
|
||||
@@ -1831,8 +1808,8 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
auto header = check_size_t<G_CardBattleCommandHeader>(data, 0xFFFF);
|
||||
size_t expected_size = header.size * 4;
|
||||
if (expected_size < data.size()) {
|
||||
print_data(stderr, data);
|
||||
throw runtime_error(string_printf("command is incomplete: expected %zX bytes, received %zX bytes", expected_size, data.size()));
|
||||
phosg::print_data(stderr, data);
|
||||
throw runtime_error(phosg::string_printf("command is incomplete: expected %zX bytes, received %zX bytes", expected_size, data.size()));
|
||||
}
|
||||
if (header.subcommand != 0xB3) {
|
||||
throw runtime_error("server data command is not 6xB3");
|
||||
@@ -1860,8 +1837,7 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
|
||||
void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_RedrawInitialHand_Ep3_CAx0B>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -1893,8 +1869,7 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data)
|
||||
|
||||
void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndInitialRedrawPhase_Ep3_CAx0C>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 2");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 2");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -1975,8 +1950,7 @@ void Server::handle_CAx0D_end_non_action_phase(shared_ptr<Client>, const string&
|
||||
|
||||
void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_DiscardCardFromHand_Ep3_CAx0E>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2061,8 +2035,7 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& d
|
||||
|
||||
void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_MoveFieldCharacter_Ep3_CAx10>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2102,8 +2075,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string&
|
||||
|
||||
void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EnqueueAttackOrDefense_Ep3_CAx11>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2141,8 +2113,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const st
|
||||
|
||||
void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndAttackList_Ep3_CAx12>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2237,8 +2208,7 @@ void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client> c, const st
|
||||
|
||||
void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerDeck_Ep3_CAx14>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK");
|
||||
|
||||
if (!this->battle_in_progress) {
|
||||
if ((this->setup_phase == SetupPhase::REGISTRATION) &&
|
||||
@@ -2259,7 +2229,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
||||
}
|
||||
}
|
||||
if (verify_error) {
|
||||
throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error));
|
||||
throw runtime_error(phosg::string_printf("invalid deck: -0x%" PRIX32, verify_error));
|
||||
}
|
||||
if (!this->options.is_nte() && !(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
||||
this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids);
|
||||
@@ -2284,8 +2254,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
||||
|
||||
void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "HARD RESET");
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
|
||||
|
||||
// In the original implementation, this command recreates the server object.
|
||||
// This is possible because the dispatch function is not part of the server
|
||||
@@ -2302,8 +2271,7 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, con
|
||||
|
||||
void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerName_Ep3_CAx1B>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME");
|
||||
this->send_debug_command_received_message(in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME");
|
||||
|
||||
if (in_cmd.entry.client_id < 4) {
|
||||
if (!this->is_registration_complete()) {
|
||||
@@ -2334,8 +2302,7 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
|
||||
|
||||
void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_StartBattle_Ep3_CAx1D>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "START BATTLE");
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "START BATTLE");
|
||||
|
||||
if (!this->battle_in_progress) {
|
||||
bool is_nte = this->options.is_nte();
|
||||
@@ -2381,8 +2348,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
|
||||
void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndBattle_Ep3_CAx21>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "END BATTLE");
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "END BATTLE");
|
||||
if (this->setup_phase == SetupPhase::BATTLE_ENDED) {
|
||||
this->battle_finished = true;
|
||||
|
||||
@@ -2396,8 +2362,7 @@ void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
||||
|
||||
void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndDefenseList_Ep3_CAx28>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2457,8 +2422,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
||||
const auto& in_cmd = check_size_t<G_PhotonBlastRequest_Ep3_CAx34>(data);
|
||||
|
||||
uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref);
|
||||
this->send_debug_command_received_message(
|
||||
card_ref_client_id, in_cmd.header.subsubcommand, "SUB ALLY ATK");
|
||||
this->send_debug_command_received_message(card_ref_client_id, in_cmd.header.subsubcommand, "SUB ALLY ATK");
|
||||
|
||||
if (card_ref_client_id >= 4) {
|
||||
return;
|
||||
@@ -2531,8 +2495,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
||||
|
||||
void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_AdvanceFromStartingRollsPhase_Ep3_CAx37>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 1");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 1");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2579,7 +2542,7 @@ void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const st
|
||||
uint8_t language = sender_c ? sender_c->language() : 1;
|
||||
const auto& list_data = this->options.map_index->get_compressed_list(num_players, language);
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_Ep3_6xB6x40) + 3) & (~3);
|
||||
w.put<G_MapList_Ep3_6xB6x40>(G_MapList_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, list_data.size(), 0});
|
||||
w.write(list_data);
|
||||
@@ -2644,14 +2607,15 @@ void Server::send_6xB6x41_to_all_clients() const {
|
||||
void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
||||
const auto& cmd = check_size_t<G_MapDataRequest_Ep3_CAx41>(data);
|
||||
this->send_debug_command_received_message(cmd.header.subsubcommand, "MAP DATA");
|
||||
this->last_chosen_map = this->options.map_index->for_number(cmd.map_number);
|
||||
this->send_6xB6x41_to_all_clients();
|
||||
if (!this->options.tournament || (this->options.tournament->get_map()->map_number == cmd.map_number)) {
|
||||
this->last_chosen_map = this->options.map_index->for_number(cmd.map_number);
|
||||
this->send_6xB6x41_to_all_clients();
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndTurn_Ep3_CAx48>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
@@ -2668,8 +2632,7 @@ void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
||||
|
||||
void Server::handle_CAx49_card_counts(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
|
||||
this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
|
||||
|
||||
// Note: Sega's implmentation completely ignores this command. This
|
||||
// implementation is not based on the original code.
|
||||
@@ -2828,7 +2791,7 @@ void Server::unknown_8023EEF4() {
|
||||
auto card = this->attack_cards[this->unknown_a14];
|
||||
if (this->get_current_team_turn() == card->get_team_id()) {
|
||||
ActionState as = this->pending_attacks_with_cards[this->unknown_a14];
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
log.debug("card @%04hX #%04hX can attack", card->get_card_ref(), card->get_card_id());
|
||||
string as_str = as.str(this->shared_from_this());
|
||||
log.debug("as: %s", as_str.c_str());
|
||||
@@ -2838,7 +2801,7 @@ void Server::unknown_8023EEF4() {
|
||||
} else {
|
||||
this->replace_targets_due_to_destruction_or_conditions(&as);
|
||||
}
|
||||
if (log.should_log(LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string as_str = as.str(this->shared_from_this());
|
||||
log.debug("as after target replacement: %s", as_str.c_str());
|
||||
}
|
||||
@@ -2858,8 +2821,8 @@ void Server::unknown_8023EEF4() {
|
||||
log.debug("a14 (%" PRIu32 ") < num_pending_attacks_with_cards (%" PRIu32 ")", this->unknown_a14, this->num_pending_attacks_with_cards);
|
||||
this->defense_list_ended_for_client.clear(false);
|
||||
|
||||
G_SetActionState_Ep3_6xB4x29 cmd;
|
||||
cmd.unknown_a1 = this->unknown_a14;
|
||||
G_UpdateAttackTargets_Ep3_6xB4x29 cmd;
|
||||
cmd.attack_number = this->unknown_a14;
|
||||
cmd.state = this->pending_attacks_with_cards[this->unknown_a14];
|
||||
if (is_nte) {
|
||||
this->replace_targets_due_to_destruction_nte(&cmd.state);
|
||||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
std::shared_ptr<const CardIndex> card_index;
|
||||
std::shared_ptr<const MapIndex> map_index;
|
||||
uint32_t behavior_flags;
|
||||
std::shared_ptr<StringReader> opt_rand_stream;
|
||||
std::shared_ptr<phosg::StringReader> opt_rand_stream;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
std::shared_ptr<const Tournament> tournament;
|
||||
std::array<std::vector<uint16_t>, 5> trap_card_ids;
|
||||
@@ -85,10 +85,10 @@ public:
|
||||
~Server() noexcept(false);
|
||||
void init();
|
||||
|
||||
class StackLogger : public PrefixedLogger {
|
||||
class StackLogger : public phosg::PrefixedLogger {
|
||||
public:
|
||||
StackLogger(const Server* s, const std::string& prefix);
|
||||
StackLogger(const Server* s, const std::string& prefix, LogLevel min_level);
|
||||
StackLogger(const Server* s, const std::string& prefix, phosg::LogLevel min_level);
|
||||
StackLogger(const StackLogger&) = delete;
|
||||
StackLogger(StackLogger&&);
|
||||
StackLogger& operator=(const StackLogger&) = delete;
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
for (size_t z = 0; z < count; z++) {
|
||||
if (refs[z] != 0xFFFF) {
|
||||
std::string ref_str = this->debug_str_for_card_ref(refs[z]);
|
||||
ret += string_printf("%zu:%s ", z, ref_str.c_str());
|
||||
ret += phosg::string_printf("%zu:%s ", z, ref_str.c_str());
|
||||
}
|
||||
}
|
||||
if (ret.size() > 1) {
|
||||
|
||||
+26
-26
@@ -49,16 +49,16 @@ string Tournament::Team::str() const {
|
||||
num_com_players += player.is_com();
|
||||
}
|
||||
|
||||
string ret = string_printf("[Team/%zu %s %zuH/%zuC/%zuP name=%s pass=%s rounds=%zu",
|
||||
string ret = phosg::string_printf("[Team/%zu %s %zuH/%zuC/%zuP name=%s pass=%s rounds=%zu",
|
||||
this->index, this->is_active ? "active" : "inactive",
|
||||
num_human_players, num_com_players, this->max_players, this->name.c_str(),
|
||||
this->password.c_str(), this->num_rounds_cleared);
|
||||
for (const auto& player : this->players) {
|
||||
if (player.is_human()) {
|
||||
if (player.player_name.empty()) {
|
||||
ret += string_printf(" %08" PRIX32, player.account_id);
|
||||
ret += phosg::string_printf(" %08" PRIX32, player.account_id);
|
||||
} else {
|
||||
ret += string_printf(" %08" PRIX32 " (%s)", player.account_id, player.player_name.c_str());
|
||||
ret += phosg::string_printf(" %08" PRIX32 " (%s)", player.account_id, player.player_name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ Tournament::Match::Match(
|
||||
|
||||
string Tournament::Match::str() const {
|
||||
string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
|
||||
return string_printf("[Match round=%zu winner=%s]", this->round_num, winner_str.c_str());
|
||||
return phosg::string_printf("[Match round=%zu winner=%s]", this->round_num, winner_str.c_str());
|
||||
}
|
||||
|
||||
bool Tournament::Match::resolve_if_skippable() {
|
||||
@@ -230,7 +230,7 @@ bool Tournament::Match::resolve_if_skippable() {
|
||||
// entirely and just make one team advance arbitrarily (note that this also
|
||||
// handles the case where both preceding winner teams are empty)
|
||||
if (!winner_a->has_any_human_players() && !winner_b->has_any_human_players()) {
|
||||
this->set_winner_team((random_object<uint8_t>() & 1) ? winner_b : winner_a);
|
||||
this->set_winner_team((phosg::random_object<uint8_t>() & 1) ? winner_b : winner_a);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ Tournament::Tournament(
|
||||
const Rules& rules,
|
||||
size_t num_teams,
|
||||
uint8_t flags)
|
||||
: log(string_printf("[Tournament:%s] ", name.c_str())),
|
||||
: log(phosg::string_printf("[Tournament:%s] ", name.c_str())),
|
||||
map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
name(name),
|
||||
@@ -342,8 +342,8 @@ Tournament::Tournament(
|
||||
Tournament::Tournament(
|
||||
shared_ptr<const MapIndex> map_index,
|
||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const JSON& json)
|
||||
: log(string_printf("[Tournament:%s] ", json.get_string("name").c_str())),
|
||||
const phosg::JSON& json)
|
||||
: log(phosg::string_printf("[Tournament:%s] ", json.get_string("name").c_str())),
|
||||
map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
source_json(json),
|
||||
@@ -504,14 +504,14 @@ void Tournament::create_bracket_matches() {
|
||||
this->final_match = current_round_matches.at(0);
|
||||
}
|
||||
|
||||
JSON Tournament::json() const {
|
||||
auto teams_list = JSON::list();
|
||||
phosg::JSON Tournament::json() const {
|
||||
auto teams_list = phosg::JSON::list();
|
||||
for (auto team : this->teams) {
|
||||
auto players_list = JSON::list();
|
||||
auto players_list = phosg::JSON::list();
|
||||
for (const auto& player : team->players) {
|
||||
if (player.is_human()) {
|
||||
if (!player.player_name.empty()) {
|
||||
players_list.emplace_back(JSON::list({player.account_id, player.player_name}));
|
||||
players_list.emplace_back(phosg::JSON::list({player.account_id, player.player_name}));
|
||||
} else {
|
||||
players_list.emplace_back(player.account_id);
|
||||
}
|
||||
@@ -519,7 +519,7 @@ JSON Tournament::json() const {
|
||||
players_list.emplace_back(player.com_deck->deck_name);
|
||||
}
|
||||
}
|
||||
teams_list.emplace_back(JSON::dict({
|
||||
teams_list.emplace_back(phosg::JSON::dict({
|
||||
{"max_players", team->max_players},
|
||||
{"player_specs", std::move(players_list)},
|
||||
{"name", team->name},
|
||||
@@ -527,7 +527,7 @@ JSON Tournament::json() const {
|
||||
{"num_rounds_cleared", team->num_rounds_cleared},
|
||||
}));
|
||||
}
|
||||
return JSON::dict({
|
||||
return phosg::JSON::dict({
|
||||
{"name", this->name},
|
||||
{"map_number", this->map->map_number},
|
||||
{"rules", this->rules.json()},
|
||||
@@ -649,7 +649,7 @@ void Tournament::start() {
|
||||
if (this->flags & Flag::SHUFFLE_ENTRIES) {
|
||||
// Shuffle all the tournament entries
|
||||
for (size_t z = this->teams.size(); z > 0; z--) {
|
||||
size_t index = random_object<uint32_t>() % z;
|
||||
size_t index = phosg::random_object<uint32_t>() % z;
|
||||
if (index != z - 1) {
|
||||
this->teams[z - 1].swap(this->teams[index]);
|
||||
}
|
||||
@@ -665,7 +665,7 @@ void Tournament::start() {
|
||||
auto m = this->zero_round_matches[z];
|
||||
auto t = m->winner_team;
|
||||
if (t->name.empty()) {
|
||||
t->name = has_com_teams ? string_printf("COM:%zu", z) : "(no entrant)";
|
||||
t->name = has_com_teams ? phosg::string_printf("COM:%zu", z) : "(no entrant)";
|
||||
}
|
||||
for (const auto& player : t->players) {
|
||||
if (player.is_com()) {
|
||||
@@ -791,16 +791,16 @@ TournamentIndex::TournamentIndex(
|
||||
return;
|
||||
}
|
||||
|
||||
JSON json;
|
||||
phosg::JSON json;
|
||||
try {
|
||||
json = JSON::parse(load_file(this->state_filename));
|
||||
} catch (const cannot_open_file&) {
|
||||
json = JSON::list();
|
||||
json = phosg::JSON::parse(phosg::load_file(this->state_filename));
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
json = phosg::JSON::list();
|
||||
}
|
||||
|
||||
if (json.is_list()) {
|
||||
if (json.size() > 0x20) {
|
||||
throw runtime_error("tournament JSON list length is incorrect");
|
||||
throw runtime_error("tournament phosg::JSON list length is incorrect");
|
||||
}
|
||||
for (size_t z = 0; z < min<size_t>(json.size(), 0x20); z++) {
|
||||
if (!json.at(z).is_null()) {
|
||||
@@ -815,13 +815,13 @@ TournamentIndex::TournamentIndex(
|
||||
}
|
||||
} else if (json.is_dict()) {
|
||||
if (json.size() > 0x20) {
|
||||
throw runtime_error("tournament JSON dict length is incorrect");
|
||||
throw runtime_error("tournament phosg::JSON dict length is incorrect");
|
||||
}
|
||||
for (const auto& it : json.as_dict()) {
|
||||
auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second);
|
||||
tourn->init();
|
||||
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
||||
// This is logic_error instead of runtime_error because JSON dicts are
|
||||
// This is logic_error instead of runtime_error because phosg::JSON dicts are
|
||||
// supposed to already have unique keys
|
||||
throw logic_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||
}
|
||||
@@ -829,7 +829,7 @@ TournamentIndex::TournamentIndex(
|
||||
this->menu_item_id_to_tournament.emplace_back(tourn);
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("tournament state root JSON is not a list or dict");
|
||||
throw runtime_error("tournament state root phosg::JSON is not a list or dict");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,11 +838,11 @@ void TournamentIndex::save() const {
|
||||
return;
|
||||
}
|
||||
|
||||
auto json = JSON::dict();
|
||||
auto json = phosg::JSON::dict();
|
||||
for (const auto& it : this->name_to_tournament) {
|
||||
json.emplace(it.second->get_name(), it.second->json());
|
||||
}
|
||||
save_file(this->state_filename, json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
phosg::save_file(this->state_filename, json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
}
|
||||
|
||||
shared_ptr<Tournament> TournamentIndex::create_tournament(
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
bool is_com() const;
|
||||
bool is_human() const;
|
||||
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
|
||||
struct Team : public std::enable_shared_from_this<Team> {
|
||||
@@ -115,11 +115,11 @@ public:
|
||||
Tournament(
|
||||
std::shared_ptr<const MapIndex> map_index,
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const JSON& json);
|
||||
const phosg::JSON& json);
|
||||
~Tournament() = default;
|
||||
void init();
|
||||
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
|
||||
inline const std::string& get_name() const {
|
||||
return this->name;
|
||||
@@ -165,11 +165,11 @@ public:
|
||||
private:
|
||||
void create_bracket_matches();
|
||||
|
||||
PrefixedLogger log;
|
||||
phosg::PrefixedLogger log;
|
||||
|
||||
std::shared_ptr<const MapIndex> map_index;
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index;
|
||||
JSON source_json;
|
||||
phosg::JSON source_json;
|
||||
std::string name;
|
||||
std::shared_ptr<const MapIndex::Map> map;
|
||||
Rules rules;
|
||||
|
||||
@@ -20,7 +20,7 @@ FileContentsCache::File::File(
|
||||
shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
|
||||
const string& name, string&& data, uint64_t t) {
|
||||
if (t == 0) {
|
||||
t = now();
|
||||
t = phosg::now();
|
||||
}
|
||||
auto new_file = make_shared<File>(name, std::move(data), t);
|
||||
auto emplace_ret = this->name_to_file.emplace(name, new_file);
|
||||
@@ -37,7 +37,7 @@ shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
|
||||
}
|
||||
|
||||
FileContentsCache::GetResult FileContentsCache::get_or_load(const std::string& name) {
|
||||
return this->get(name, load_file);
|
||||
return this->get(name, phosg::load_file);
|
||||
}
|
||||
|
||||
FileContentsCache::GetResult FileContentsCache::get_or_load(const char* name) {
|
||||
@@ -59,7 +59,7 @@ shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(
|
||||
|
||||
FileContentsCache::GetResult FileContentsCache::get(const std::string& name,
|
||||
std::function<std::string(const std::string&)> generate) {
|
||||
uint64_t t = now();
|
||||
uint64_t t = phosg::now();
|
||||
try {
|
||||
auto& entry = this->name_to_file.at(name);
|
||||
if (this->ttl_usecs && (t - entry->load_time < this->ttl_usecs)) {
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
}
|
||||
template <typename T, typename NameT>
|
||||
GetObjResult<T> get_obj(NameT name, std::function<T(const std::string&)> generate) {
|
||||
uint64_t t = now();
|
||||
uint64_t t = phosg::now();
|
||||
try {
|
||||
auto& f = this->name_to_file.at(name);
|
||||
if (f->data->size() != sizeof(T)) {
|
||||
|
||||
+54
-54
@@ -59,7 +59,7 @@ string CompiledFunctionCode::generate_client_command_t(
|
||||
footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
|
||||
footer.unused2.clear(0);
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
if (!label_writes.empty()) {
|
||||
string modified_code = this->code;
|
||||
for (const auto& it : label_writes) {
|
||||
@@ -91,7 +91,7 @@ string CompiledFunctionCode::generate_client_command_t(
|
||||
footer.relocations_offset = override_relocations_offset;
|
||||
} else {
|
||||
for (uint16_t delta : this->relocation_deltas) {
|
||||
w.put<typename FooterT::U16T>(delta);
|
||||
w.put<U16T<FooterT::IsBE>>(delta);
|
||||
}
|
||||
if (this->relocation_deltas.size() & 1) {
|
||||
w.put_u16(0);
|
||||
@@ -161,24 +161,24 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
}
|
||||
|
||||
// Look in the function directory first, then the system directory
|
||||
string asm_filename = string_printf("%s/%s.%s.inc.s", function_directory.c_str(), name.c_str(), arch_name_token);
|
||||
if (!isfile(asm_filename)) {
|
||||
asm_filename = string_printf("%s/%s.%s.inc.s", system_directory.c_str(), name.c_str(), arch_name_token);
|
||||
string asm_filename = phosg::string_printf("%s/%s.%s.inc.s", function_directory.c_str(), name.c_str(), arch_name_token);
|
||||
if (!phosg::isfile(asm_filename)) {
|
||||
asm_filename = phosg::string_printf("%s/%s.%s.inc.s", system_directory.c_str(), name.c_str(), arch_name_token);
|
||||
}
|
||||
if (isfile(asm_filename)) {
|
||||
if (phosg::isfile(asm_filename)) {
|
||||
if (!get_include_stack.emplace(name).second) {
|
||||
throw runtime_error("mutual recursion between includes: " + name);
|
||||
}
|
||||
EmulatorBase::AssembleResult ret;
|
||||
ResourceDASM::EmulatorBase::AssembleResult ret;
|
||||
switch (arch) {
|
||||
case CompiledFunctionCode::Architecture::POWERPC:
|
||||
ret = PPC32Emulator::assemble(load_file(asm_filename), get_include);
|
||||
ret = ResourceDASM::PPC32Emulator::assemble(phosg::load_file(asm_filename), get_include);
|
||||
break;
|
||||
case CompiledFunctionCode::Architecture::X86:
|
||||
ret = X86Emulator::assemble(load_file(asm_filename), get_include);
|
||||
ret = ResourceDASM::X86Emulator::assemble(phosg::load_file(asm_filename), get_include);
|
||||
break;
|
||||
case CompiledFunctionCode::Architecture::SH4:
|
||||
ret = SH4Emulator::assemble(load_file(asm_filename), get_include);
|
||||
ret = ResourceDASM::SH4Emulator::assemble(phosg::load_file(asm_filename), get_include);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown architecture");
|
||||
@@ -188,23 +188,23 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
}
|
||||
|
||||
string bin_filename = function_directory + "/" + name + ".inc.bin";
|
||||
if (isfile(bin_filename)) {
|
||||
return load_file(bin_filename);
|
||||
if (phosg::isfile(bin_filename)) {
|
||||
return phosg::load_file(bin_filename);
|
||||
}
|
||||
bin_filename = system_directory + "/" + name + ".inc.bin";
|
||||
if (isfile(bin_filename)) {
|
||||
return load_file(bin_filename);
|
||||
if (phosg::isfile(bin_filename)) {
|
||||
return phosg::load_file(bin_filename);
|
||||
}
|
||||
throw runtime_error("data not found for include: " + name + " (from " + asm_filename + " or " + bin_filename + ")");
|
||||
};
|
||||
|
||||
EmulatorBase::AssembleResult assembled;
|
||||
ResourceDASM::EmulatorBase::AssembleResult assembled;
|
||||
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
|
||||
assembled = PPC32Emulator::assemble(text, get_include);
|
||||
assembled = ResourceDASM::PPC32Emulator::assemble(text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::X86) {
|
||||
assembled = X86Emulator::assemble(text, get_include);
|
||||
assembled = ResourceDASM::X86Emulator::assemble(text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::SH4) {
|
||||
assembled = SH4Emulator::assemble(text, get_include);
|
||||
assembled = ResourceDASM::SH4Emulator::assemble(text, get_include);
|
||||
} else {
|
||||
throw runtime_error("invalid architecture");
|
||||
}
|
||||
@@ -229,7 +229,7 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : ret->label_offsets) {
|
||||
if (starts_with(it.first, "reloc")) {
|
||||
if (phosg::starts_with(it.first, "reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
}
|
||||
}
|
||||
@@ -260,28 +260,28 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
return;
|
||||
}
|
||||
|
||||
string system_dir_path = ends_with(directory, "/") ? (directory + "System") : (directory + "/System");
|
||||
string system_dir_path = phosg::ends_with(directory, "/") ? (directory + "System") : (directory + "/System");
|
||||
|
||||
uint32_t next_menu_item_id = 1;
|
||||
for (const auto& subdir_name : list_directory_sorted(directory)) {
|
||||
string subdir_path = ends_with(directory, "/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
|
||||
if (!isdir(subdir_path)) {
|
||||
for (const auto& subdir_name : phosg::list_directory_sorted(directory)) {
|
||||
string subdir_path = phosg::ends_with(directory, "/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
|
||||
if (!phosg::isdir(subdir_path)) {
|
||||
function_compiler_log.warning("Skipping %s (not a directory)", subdir_name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& filename : list_directory_sorted(subdir_path)) {
|
||||
for (const auto& filename : phosg::list_directory_sorted(subdir_path)) {
|
||||
try {
|
||||
if (!ends_with(filename, ".s")) {
|
||||
if (!phosg::ends_with(filename, ".s")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = filename.substr(0, filename.size() - 2);
|
||||
if (ends_with(name, ".inc")) {
|
||||
if (phosg::ends_with(name, ".inc")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_patch = ends_with(name, ".patch");
|
||||
bool is_patch = phosg::ends_with(name, ".patch");
|
||||
if (is_patch) {
|
||||
name.resize(name.size() - 6);
|
||||
}
|
||||
@@ -290,15 +290,15 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
CompiledFunctionCode::Architecture arch = CompiledFunctionCode::Architecture::UNKNOWN;
|
||||
uint32_t specific_version = 0;
|
||||
string short_name = name;
|
||||
if (ends_with(name, ".ppc")) {
|
||||
if (phosg::ends_with(name, ".ppc")) {
|
||||
arch = CompiledFunctionCode::Architecture::POWERPC;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (ends_with(name, ".x86")) {
|
||||
} else if (phosg::ends_with(name, ".x86")) {
|
||||
arch = CompiledFunctionCode::Architecture::X86;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (ends_with(name, ".sh4")) {
|
||||
} else if (phosg::ends_with(name, ".sh4")) {
|
||||
arch = CompiledFunctionCode::Architecture::SH4;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
@@ -321,11 +321,11 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
}
|
||||
|
||||
string path = subdir_path + "/" + filename;
|
||||
string text = load_file(path);
|
||||
string text = phosg::load_file(path);
|
||||
auto code = compile_function_code(arch, subdir_path, system_dir_path, name, text);
|
||||
if (code->index != 0) {
|
||||
if (!this->index_to_function.emplace(code->index, code).second) {
|
||||
throw runtime_error(string_printf(
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"duplicate function index: %08" PRIX32, code->index));
|
||||
}
|
||||
}
|
||||
@@ -338,11 +338,11 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
this->menu_item_id_and_specific_version_to_patch_function.emplace(
|
||||
static_cast<uint64_t>(code->menu_item_id) << 32 | specific_version, code);
|
||||
this->name_and_specific_version_to_patch_function.emplace(
|
||||
string_printf("%s-%08" PRIX32, short_name.c_str(), specific_version), code);
|
||||
phosg::string_printf("%s-%08" PRIX32, short_name.c_str(), specific_version), code);
|
||||
}
|
||||
|
||||
string index_prefix = code->index ? string_printf("%02X => ", code->index) : "";
|
||||
string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : "";
|
||||
string index_prefix = code->index ? phosg::string_printf("%02X => ", code->index) : "";
|
||||
string patch_prefix = is_patch ? phosg::string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : "";
|
||||
function_compiler_log.info("Compiled function %s%s%s (%s)",
|
||||
index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch));
|
||||
|
||||
@@ -354,13 +354,13 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> FunctionCodeIndex::patch_menu(uint32_t specific_version) const {
|
||||
auto suffix = string_printf("-%08" PRIX32, specific_version);
|
||||
auto suffix = phosg::string_printf("-%08" PRIX32, specific_version);
|
||||
|
||||
auto ret = make_shared<Menu>(MenuID::PATCHES, "Patches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
for (const auto& it : this->name_and_specific_version_to_patch_function) {
|
||||
const auto& fn = it.second;
|
||||
if (fn->hide_from_patches_menu || !ends_with(it.first, suffix)) {
|
||||
if (fn->hide_from_patches_menu || !phosg::ends_with(it.first, suffix)) {
|
||||
continue;
|
||||
}
|
||||
ret->items.emplace_back(
|
||||
@@ -374,13 +374,13 @@ shared_ptr<const Menu> FunctionCodeIndex::patch_menu(uint32_t specific_version)
|
||||
|
||||
shared_ptr<const Menu> FunctionCodeIndex::patch_switches_menu(
|
||||
uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const {
|
||||
auto suffix = string_printf("-%08" PRIX32, specific_version);
|
||||
auto suffix = phosg::string_printf("-%08" PRIX32, specific_version);
|
||||
|
||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patch switches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
for (const auto& it : this->name_and_specific_version_to_patch_function) {
|
||||
const auto& fn = it.second;
|
||||
if (fn->hide_from_patches_menu || !ends_with(it.first, suffix)) {
|
||||
if (fn->hide_from_patches_menu || !phosg::ends_with(it.first, suffix)) {
|
||||
continue;
|
||||
}
|
||||
string name;
|
||||
@@ -404,7 +404,7 @@ bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const {
|
||||
std::shared_ptr<const CompiledFunctionCode> FunctionCodeIndex::get_patch(
|
||||
const std::string& name, uint32_t specific_version) const {
|
||||
return this->name_and_specific_version_to_patch_function.at(
|
||||
string_printf("%s-%08" PRIX32, name.c_str(), specific_version));
|
||||
phosg::string_printf("%s-%08" PRIX32, name.c_str(), specific_version));
|
||||
}
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
@@ -412,7 +412,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
function_compiler_log.info("Function compiler is not available");
|
||||
return;
|
||||
}
|
||||
if (!isdir(directory)) {
|
||||
if (!phosg::isdir(directory)) {
|
||||
function_compiler_log.info("DOL file directory is missing");
|
||||
return;
|
||||
}
|
||||
@@ -422,9 +422,9 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
uint32_t next_menu_item_id = 0;
|
||||
for (const auto& filename : list_directory_sorted(directory)) {
|
||||
bool is_dol = ends_with(filename, ".dol");
|
||||
bool is_compressed_dol = ends_with(filename, ".dol.prs");
|
||||
for (const auto& filename : phosg::list_directory_sorted(directory)) {
|
||||
bool is_dol = phosg::ends_with(filename, ".dol");
|
||||
bool is_compressed_dol = phosg::ends_with(filename, ".dol.prs");
|
||||
if (!is_dol && !is_compressed_dol) {
|
||||
continue;
|
||||
}
|
||||
@@ -436,13 +436,13 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
dol->name = name;
|
||||
|
||||
string path = directory + "/" + filename;
|
||||
string file_data = load_file(path);
|
||||
string file_data = phosg::load_file(path);
|
||||
|
||||
string description;
|
||||
if (is_compressed_dol) {
|
||||
size_t decompressed_size = prs_decompress_size(file_data);
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(file_data.size());
|
||||
w.put_u32b(decompressed_size);
|
||||
w.write(file_data);
|
||||
@@ -451,15 +451,15 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string compressed_size_str = format_size(file_data.size());
|
||||
string decompressed_size_str = format_size(decompressed_size);
|
||||
string compressed_size_str = phosg::format_size(file_data.size());
|
||||
string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||
function_compiler_log.info("Loaded compressed DOL file %s (%s -> %s)",
|
||||
dol->name.c_str(), compressed_size_str.c_str(), decompressed_size_str.c_str());
|
||||
description = string_printf("$C6%s$C7\n%s\n%s (orig)",
|
||||
description = phosg::string_printf("$C6%s$C7\n%s\n%s (orig)",
|
||||
dol->name.c_str(), compressed_size_str.c_str(), decompressed_size_str.c_str());
|
||||
|
||||
} else {
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(0);
|
||||
w.put_u32b(file_data.size());
|
||||
w.write(file_data);
|
||||
@@ -468,9 +468,9 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string size_str = format_size(dol->data.size());
|
||||
string size_str = phosg::format_size(dol->data.size());
|
||||
function_compiler_log.info("Loaded DOL file %s (%s)", filename.c_str(), size_str.c_str());
|
||||
description = string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str());
|
||||
description = phosg::string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str());
|
||||
}
|
||||
|
||||
this->name_to_file.emplace(dol->name, dol);
|
||||
@@ -503,7 +503,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
data.region_code = *region_code;
|
||||
for (uint8_t version_code = 0; version_code < 8; version_code++) {
|
||||
data.version_code = version_code;
|
||||
uint32_t checksum = crc32(&data, sizeof(data));
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
@@ -515,7 +515,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
data.region_code = 'J';
|
||||
data.system_code = 'D';
|
||||
data.version_code = 0;
|
||||
uint32_t checksum = crc32(&data, sizeof(data));
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
|
||||
+13
-14
@@ -5,16 +5,15 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
struct GSLHeaderEntryT {
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
|
||||
|
||||
pstring<TextEncoding::ASCII, 0x20> filename;
|
||||
U32T offset; // In pages, so actual offset is this * 0x800
|
||||
U32T size;
|
||||
U32T<BE> offset; // In pages, so actual offset is this * 0x800
|
||||
U32T<BE> size;
|
||||
uint64_t unused;
|
||||
} __packed__;
|
||||
|
||||
@@ -23,12 +22,12 @@ using GSLHeaderEntryBE = GSLHeaderEntryT<true>;
|
||||
check_struct_size(GSLHeaderEntry, 0x30);
|
||||
check_struct_size(GSLHeaderEntryBE, 0x30);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
void GSLArchive::load_t() {
|
||||
StringReader r(*this->data);
|
||||
phosg::StringReader r(*this->data);
|
||||
uint64_t min_data_offset = 0xFFFFFFFFFFFFFFFF;
|
||||
while (r.where() < min_data_offset) {
|
||||
const auto& entry = r.get<GSLHeaderEntryT<IsBigEndian>>();
|
||||
const auto& entry = r.get<GSLHeaderEntryT<BE>>();
|
||||
if (entry.filename.empty()) {
|
||||
break;
|
||||
}
|
||||
@@ -71,10 +70,10 @@ string GSLArchive::get_copy(const string& name) const {
|
||||
}
|
||||
}
|
||||
|
||||
StringReader GSLArchive::get_reader(const string& name) const {
|
||||
phosg::StringReader GSLArchive::get_reader(const string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return StringReader(this->data->data() + entry.offset, entry.size);
|
||||
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("GSL does not contain file: " + name);
|
||||
}
|
||||
@@ -84,16 +83,16 @@ string GSLArchive::generate(const unordered_map<string, string>& files, bool big
|
||||
return big_endian ? GSLArchive::generate_t<true>(files) : GSLArchive::generate_t<false>(files);
|
||||
}
|
||||
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
string GSLArchive::generate_t(const unordered_map<string, string>& files) {
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
|
||||
// Make sure there's enough space for a blank header entry before any file's
|
||||
// data pages begin
|
||||
uint32_t data_start_offset = ((sizeof(GSLHeaderEntryT<IsBigEndian>) * (files.size() + 1)) + 0x7FF) & (~0x7FF);
|
||||
uint32_t data_start_offset = ((sizeof(GSLHeaderEntryT<BE>) * (files.size() + 1)) + 0x7FF) & (~0x7FF);
|
||||
uint32_t data_offset = data_start_offset;
|
||||
for (const auto& file : files) {
|
||||
GSLHeaderEntryT<IsBigEndian> entry;
|
||||
GSLHeaderEntryT<BE> entry;
|
||||
entry.filename.encode(file.first);
|
||||
entry.offset = data_offset >> 11;
|
||||
entry.size = file.second.size();
|
||||
|
||||
+3
-3
@@ -21,14 +21,14 @@ public:
|
||||
|
||||
std::pair<const void*, size_t> get(const std::string& name) const;
|
||||
std::string get_copy(const std::string& name) const;
|
||||
StringReader get_reader(const std::string& name) const;
|
||||
phosg::StringReader get_reader(const std::string& name) const;
|
||||
|
||||
static std::string generate(const std::unordered_map<std::string, std::string>& files, bool big_endian);
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
void load_t();
|
||||
template <bool IsBigEndian>
|
||||
template <bool BE>
|
||||
static std::string generate_t(const std::unordered_map<std::string, std::string>& files);
|
||||
|
||||
std::shared_ptr<const std::string> data;
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ struct GVRHeader {
|
||||
be_uint16_t height;
|
||||
} __packed_ws__(GVRHeader, 0x10);
|
||||
|
||||
string encode_gvm(const Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index) {
|
||||
string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index) {
|
||||
int8_t dimensions_field = -2;
|
||||
{
|
||||
size_t h = img.get_height();
|
||||
@@ -66,7 +66,7 @@ string encode_gvm(const Image& img, GVRDataFormat data_format, const std::string
|
||||
throw invalid_argument("cannot encode pixel format");
|
||||
}
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put<GVMFileHeader>({.signature = 0x47564D48, .header_size = 0x48, .flags = 0x000F, .num_files = 1});
|
||||
GVMFileEntry file_entry;
|
||||
file_entry.file_num = 0;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ enum class GVRDataFormat : uint8_t {
|
||||
DXT1 = 0x0E,
|
||||
};
|
||||
|
||||
std::string encode_gvm(const Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index);
|
||||
std::string encode_gvm(const phosg::Image& img, GVRDataFormat data_format, const std::string& internal_name, uint32_t global_index);
|
||||
|
||||
constexpr uint16_t encode_rgb565(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return ((r << 8) & 0xF800) | ((g << 3) & 0x07E0) | ((b >> 3) & 0x001F);
|
||||
|
||||
+384
-146
@@ -110,7 +110,7 @@ unordered_multimap<string, string> HTTPServer::parse_url_params(const string& qu
|
||||
if (query.empty()) {
|
||||
return params;
|
||||
}
|
||||
for (auto it : split(query, '&')) {
|
||||
for (auto it : phosg::split(query, '&')) {
|
||||
size_t first_equals = it.find('=');
|
||||
if (first_equals != string::npos) {
|
||||
string value(it, first_equals + 1);
|
||||
@@ -119,8 +119,8 @@ unordered_multimap<string, string> HTTPServer::parse_url_params(const string& qu
|
||||
for (; read_offset < value.size(); write_offset++) {
|
||||
if ((value[read_offset] == '%') && (read_offset < value.size() - 2)) {
|
||||
value[write_offset] =
|
||||
static_cast<char>(value_for_hex_char(value[read_offset + 1]) << 4) |
|
||||
static_cast<char>(value_for_hex_char(value[read_offset + 2]));
|
||||
static_cast<char>(phosg::value_for_hex_char(value[read_offset + 1]) << 4) |
|
||||
static_cast<char>(phosg::value_for_hex_char(value[read_offset + 2]));
|
||||
read_offset += 3;
|
||||
} else if (value[write_offset] == '+') {
|
||||
value[write_offset] = ' ';
|
||||
@@ -132,8 +132,7 @@ unordered_multimap<string, string> HTTPServer::parse_url_params(const string& qu
|
||||
}
|
||||
value.resize(write_offset);
|
||||
|
||||
params.emplace(piecewise_construct, forward_as_tuple(it, 0, first_equals),
|
||||
forward_as_tuple(value));
|
||||
params.emplace(piecewise_construct, forward_as_tuple(it, 0, first_equals), forward_as_tuple(value));
|
||||
} else {
|
||||
params.emplace(it, "");
|
||||
}
|
||||
@@ -172,7 +171,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
}
|
||||
|
||||
void HTTPServer::listen(const string& socket_path) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
int fd = phosg::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d (HTTP)", socket_path.c_str(), fd);
|
||||
this->add_socket(fd);
|
||||
}
|
||||
@@ -181,8 +180,8 @@ void HTTPServer::listen(const string& addr, int port) {
|
||||
if (port == 0) {
|
||||
this->listen(addr);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
int fd = phosg::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = phosg::render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d (HTTP)", netloc_str.c_str(), fd);
|
||||
this->add_socket(fd);
|
||||
}
|
||||
@@ -204,30 +203,257 @@ void HTTPServer::wait_for_stop() {
|
||||
this->th.join();
|
||||
}
|
||||
|
||||
HTTPServer::WebsocketClient::WebsocketClient(struct evhttp_connection* conn)
|
||||
: conn(conn),
|
||||
bev(evhttp_connection_get_bufferevent(this->conn)),
|
||||
pending_opcode(0xFF),
|
||||
last_communication_time(phosg::now()) {}
|
||||
|
||||
HTTPServer::WebsocketClient::~WebsocketClient() {
|
||||
evhttp_connection_free(this->conn);
|
||||
}
|
||||
|
||||
void HTTPServer::WebsocketClient::reset_pending_frame() {
|
||||
this->pending_opcode = 0xFF;
|
||||
this->pending_data.clear();
|
||||
}
|
||||
|
||||
shared_ptr<HTTPServer::WebsocketClient> HTTPServer::enable_websockets(struct evhttp_request* req) {
|
||||
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct evkeyvalq* in_headers = evhttp_request_get_input_headers(req);
|
||||
const char* connection_header = evhttp_find_header(in_headers, "Connection");
|
||||
if (!connection_header || strcasecmp(connection_header, "upgrade")) {
|
||||
return nullptr;
|
||||
}
|
||||
const char* upgrade_header = evhttp_find_header(in_headers, "Upgrade");
|
||||
if (!upgrade_header || strcasecmp(upgrade_header, "websocket")) {
|
||||
return nullptr;
|
||||
}
|
||||
const char* sec_websocket_key_header = evhttp_find_header(in_headers, "Sec-WebSocket-Key");
|
||||
if (!sec_websocket_key_header) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Note: it's important that we make a copy of this header's value since
|
||||
// we're about to free the original
|
||||
string sec_websocket_key = sec_websocket_key_header;
|
||||
string sec_websocket_accept_data = sec_websocket_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
string sec_websocket_accept = phosg::base64_encode(phosg::sha1(sec_websocket_accept_data));
|
||||
|
||||
// Hijack the bufferevent since it's no longer handling HTTP at all
|
||||
struct evhttp_connection* conn = evhttp_request_get_connection(req);
|
||||
struct bufferevent* bev = evhttp_connection_get_bufferevent(conn);
|
||||
bufferevent_setcb(bev, &this->dispatch_on_websocket_read, NULL, &this->dispatch_on_websocket_error, this);
|
||||
bufferevent_enable(bev, EV_READ | EV_WRITE);
|
||||
|
||||
// Send the HTTP reply, which enables websockets
|
||||
struct evbuffer* out_buf = bufferevent_get_output(bev);
|
||||
evbuffer_add_printf(out_buf, "HTTP/1.1 101 Switching Protocols\r\n\
|
||||
Upgrade: websocket\r\n\
|
||||
Connection: upgrade\r\n\
|
||||
Sec-WebSocket-Accept: %s\r\n\
|
||||
\r\n",
|
||||
sec_websocket_accept.c_str());
|
||||
|
||||
return this->bev_to_websocket_client.emplace(bev, new WebsocketClient(conn)).first->second;
|
||||
}
|
||||
|
||||
void HTTPServer::dispatch_on_websocket_read(struct bufferevent* bev, void* ctx) {
|
||||
reinterpret_cast<HTTPServer*>(ctx)->on_websocket_read(bev);
|
||||
}
|
||||
|
||||
void HTTPServer::dispatch_on_websocket_error(struct bufferevent* bev, short events, void* ctx) {
|
||||
reinterpret_cast<HTTPServer*>(ctx)->on_websocket_error(bev, events);
|
||||
}
|
||||
|
||||
void HTTPServer::on_websocket_read(struct bufferevent* bev) {
|
||||
struct evbuffer* in_buf = bufferevent_get_input(bev);
|
||||
|
||||
for (;;) {
|
||||
// We need at most 10 bytes to determine if there's a valid frame, or as
|
||||
// little as 2
|
||||
string header_data(10, '\0');
|
||||
ssize_t bytes_read = evbuffer_copyout(in_buf, const_cast<char*>(header_data.data()), header_data.size());
|
||||
|
||||
if (bytes_read < 2) {
|
||||
break; // Full header not yet available
|
||||
}
|
||||
|
||||
// Get the payload size
|
||||
bool has_mask = header_data[1] & 0x80;
|
||||
size_t header_size = 2;
|
||||
size_t payload_size = header_data[1] & 0x7F;
|
||||
if (payload_size == 0x7F) {
|
||||
if (bytes_read < 10) {
|
||||
break; // Full 64-bit header not yet available
|
||||
}
|
||||
payload_size = phosg::bswap64(*reinterpret_cast<const uint64_t*>(&header_data[2]));
|
||||
header_size = 10;
|
||||
} else if (payload_size == 0x7E) {
|
||||
if (bytes_read < 4) {
|
||||
break; // Full 16-bit size header not yet available
|
||||
}
|
||||
payload_size = phosg::bswap16(*reinterpret_cast<const uint16_t*>(&header_data[2]));
|
||||
header_size = 4;
|
||||
}
|
||||
if (evbuffer_get_length(in_buf) < header_size + payload_size) {
|
||||
break; // Full message not yet available
|
||||
}
|
||||
|
||||
// Full message is available; skip the header bytes (we already read them)
|
||||
// and read the masking key if needed
|
||||
evbuffer_drain(in_buf, header_size);
|
||||
uint8_t mask_key[4];
|
||||
if (has_mask) {
|
||||
evbuffer_remove(in_buf, mask_key, 4);
|
||||
}
|
||||
|
||||
shared_ptr<WebsocketClient> c = this->bev_to_websocket_client.at(bev);
|
||||
c->last_communication_time = phosg::now();
|
||||
|
||||
// Read and unmask message data
|
||||
string payload(payload_size, '\0');
|
||||
evbuffer_remove(in_buf, const_cast<char*>(payload.data()), payload_size);
|
||||
if (has_mask) {
|
||||
for (size_t x = 0; x < payload_size; x++) {
|
||||
payload[x] ^= mask_key[x & 3];
|
||||
}
|
||||
}
|
||||
|
||||
// If the current message is a control message, respond appropriately
|
||||
// (these can be sent in the middle of fragmented messages)
|
||||
uint8_t opcode = header_data[0] & 0x0F;
|
||||
if (opcode & 0x08) {
|
||||
if (opcode == 0x0A) {
|
||||
// Ping response; ignore it
|
||||
|
||||
} else if (opcode == 0x08) {
|
||||
// Close message
|
||||
this->send_websocket_message(bev, payload, 0x08);
|
||||
this->disconnect_websocket_client(bev);
|
||||
|
||||
} else if (opcode == 0x09) {
|
||||
// Ping message
|
||||
this->send_websocket_message(bev, payload, 0x0A);
|
||||
|
||||
} else {
|
||||
// Unknown control message type
|
||||
this->disconnect_websocket_client(bev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If there's an existing pending message, the current message's opcode
|
||||
// should be zero; if there's no pending message, it must not be zero
|
||||
if ((c->pending_opcode != 0xFF) == (opcode != 0)) {
|
||||
this->disconnect_websocket_client(bev);
|
||||
break;
|
||||
}
|
||||
|
||||
// At this point, we have read a full message; we must not break out of
|
||||
// this loop in case there are further messages available.
|
||||
|
||||
// Save the message opcode, if present, and append the frame data
|
||||
if (opcode) {
|
||||
c->pending_opcode = opcode;
|
||||
}
|
||||
c->pending_data += payload;
|
||||
|
||||
// If the FIN bit is set, then the frame is complete - append the payload
|
||||
// to any pending payloads and call the message handler. If the FIN bit
|
||||
// isn't set, we need to receive at least one continuation frame to
|
||||
// complete the message.
|
||||
if (header_data[0] & 0x80) {
|
||||
this->handle_websocket_message(c, c->pending_opcode, c->pending_data);
|
||||
c->reset_pending_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::on_websocket_error(struct bufferevent* bev, short events) {
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->disconnect_websocket_client(bev);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPServer::disconnect_websocket_client(struct bufferevent* bev) {
|
||||
auto it = this->bev_to_websocket_client.find(bev);
|
||||
this->handle_websocket_disconnect(it->second);
|
||||
this->bev_to_websocket_client.erase(it);
|
||||
}
|
||||
|
||||
void HTTPServer::send_websocket_message(struct bufferevent* bev,
|
||||
const string& message, uint8_t opcode) {
|
||||
string header;
|
||||
header.push_back(0x80 | (opcode & 0x0F));
|
||||
if (message.size() > 65535) {
|
||||
header.push_back(0x7F);
|
||||
header.resize(10);
|
||||
*reinterpret_cast<uint64_t*>(const_cast<char*>(header.data() + 2)) = phosg::bswap64(message.size());
|
||||
} else if (message.size() > 0x7D) {
|
||||
header.push_back(0x7E);
|
||||
header.resize(4);
|
||||
*reinterpret_cast<uint16_t*>(const_cast<char*>(header.data() + 2)) = phosg::bswap16(message.size());
|
||||
} else {
|
||||
header.push_back(message.size());
|
||||
}
|
||||
|
||||
struct evbuffer* out_buf = bufferevent_get_output(bev);
|
||||
evbuffer_add(out_buf, header.data(), header.size());
|
||||
evbuffer_add(out_buf, message.data(), message.size());
|
||||
}
|
||||
|
||||
void HTTPServer::send_websocket_message(shared_ptr<WebsocketClient> c, const string& message, uint8_t opcode) {
|
||||
this->send_websocket_message(c->bev, message, opcode);
|
||||
}
|
||||
|
||||
void HTTPServer::handle_websocket_message(shared_ptr<WebsocketClient>, uint8_t, const string&) {
|
||||
// Currently we just ignore any messages from the client
|
||||
}
|
||||
|
||||
void HTTPServer::handle_websocket_disconnect(shared_ptr<WebsocketClient> c) {
|
||||
this->rare_drop_subscribers.erase(c);
|
||||
}
|
||||
|
||||
void HTTPServer::send_rare_drop_notification(shared_ptr<const phosg::JSON> message) {
|
||||
forward_to_event_thread(this->base, [this, message]() -> void {
|
||||
if (this->rare_drop_subscribers.empty()) {
|
||||
return;
|
||||
}
|
||||
string serialized = message->serialize();
|
||||
for (const auto& c : this->rare_drop_subscribers) {
|
||||
this->send_websocket_message(c, serialized);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HTTPServer::dispatch_handle_request(struct evhttp_request* req, void* ctx) {
|
||||
reinterpret_cast<HTTPServer*>(ctx)->handle_request(req);
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_quest_json_st(shared_ptr<const Quest> q) {
|
||||
phosg::JSON HTTPServer::generate_quest_json_st(shared_ptr<const Quest> q) {
|
||||
if (!q) {
|
||||
return nullptr;
|
||||
}
|
||||
auto battle_rules_json = q->battle_rules ? q->battle_rules->json() : nullptr;
|
||||
auto challenge_template_index_json = (q->challenge_template_index >= 0)
|
||||
? q->challenge_template_index
|
||||
: JSON(nullptr);
|
||||
return JSON::dict({
|
||||
: phosg::JSON(nullptr);
|
||||
return phosg::JSON::dict({
|
||||
{"Number", q->quest_number},
|
||||
{"Episode", name_for_episode(q->episode)},
|
||||
{"Joinable", q->joinable},
|
||||
{"LockStatusRegister", (q->lock_status_register >= 0) ? q->lock_status_register : JSON(nullptr)},
|
||||
{"LockStatusRegister", (q->lock_status_register >= 0) ? q->lock_status_register : phosg::JSON(nullptr)},
|
||||
{"Name", q->name},
|
||||
{"BattleRules", std::move(battle_rules_json)},
|
||||
{"ChallengeTemplateIndex", std::move(challenge_template_index_json)},
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_client_config_json_st(const Client::Config& config) {
|
||||
phosg::JSON HTTPServer::generate_client_config_json_st(const Client::Config& config) {
|
||||
const char* drop_notifications_mode = "unknown";
|
||||
switch (config.get_drop_notification_mode()) {
|
||||
case Client::ItemDropNotificationMode::NOTHING:
|
||||
@@ -244,7 +470,7 @@ JSON HTTPServer::generate_client_config_json_st(const Client::Config& config) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto ret = JSON::dict({
|
||||
auto ret = phosg::JSON::dict({
|
||||
{"SpecificVersion", config.specific_version},
|
||||
{"SwitchAssistEnabled", (config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? true : false)},
|
||||
{"InfiniteHPEnabled", (config.check_flag(Client::Flag::INFINITE_HP_ENABLED) ? true : false)},
|
||||
@@ -260,46 +486,46 @@ JSON HTTPServer::generate_client_config_json_st(const Client::Config& config) {
|
||||
{"ProxyBlockFunctionCalls", (config.check_flag(Client::Flag::PROXY_BLOCK_FUNCTION_CALLS) ? true : false)},
|
||||
{"ProxyEp3UnmaskWhispers", (config.check_flag(Client::Flag::PROXY_EP3_UNMASK_WHISPERS) ? true : false)},
|
||||
});
|
||||
ret.emplace("OverrideRandomSeed", config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED) ? config.override_random_seed : JSON(nullptr));
|
||||
ret.emplace("OverrideSectionID", (config.override_section_id != 0xFF) ? config.override_section_id : JSON(nullptr));
|
||||
ret.emplace("OverrideLobbyEvent", (config.override_lobby_event != 0xFF) ? config.override_lobby_event : JSON(nullptr));
|
||||
ret.emplace("OverrideLobbyNumber", (config.override_lobby_number != 0x80) ? config.override_lobby_number : JSON(nullptr));
|
||||
ret.emplace("OverrideRandomSeed", config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED) ? config.override_random_seed : phosg::JSON(nullptr));
|
||||
ret.emplace("OverrideSectionID", (config.override_section_id != 0xFF) ? config.override_section_id : phosg::JSON(nullptr));
|
||||
ret.emplace("OverrideLobbyEvent", (config.override_lobby_event != 0xFF) ? config.override_lobby_event : phosg::JSON(nullptr));
|
||||
ret.emplace("OverrideLobbyNumber", (config.override_lobby_number != 0x80) ? config.override_lobby_number : phosg::JSON(nullptr));
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_account_json_st(shared_ptr<const Account> a) {
|
||||
auto dc_nte_licenses_json = JSON::list();
|
||||
phosg::JSON HTTPServer::generate_account_json_st(shared_ptr<const Account> a) {
|
||||
auto dc_nte_licenses_json = phosg::JSON::list();
|
||||
for (const auto& it : a->dc_nte_licenses) {
|
||||
dc_nte_licenses_json.emplace_back(it.first);
|
||||
}
|
||||
auto dc_licenses_json = JSON::list();
|
||||
auto dc_licenses_json = phosg::JSON::list();
|
||||
for (const auto& it : a->dc_licenses) {
|
||||
dc_licenses_json.emplace_back(it.first);
|
||||
}
|
||||
auto pc_licenses_json = JSON::list();
|
||||
auto pc_licenses_json = phosg::JSON::list();
|
||||
for (const auto& it : a->pc_licenses) {
|
||||
pc_licenses_json.emplace_back(it.first);
|
||||
}
|
||||
auto gc_licenses_json = JSON::list();
|
||||
auto gc_licenses_json = phosg::JSON::list();
|
||||
for (const auto& it : a->gc_licenses) {
|
||||
gc_licenses_json.emplace_back(it.first);
|
||||
}
|
||||
auto xb_licenses_json = JSON::list();
|
||||
auto xb_licenses_json = phosg::JSON::list();
|
||||
for (const auto& it : a->xb_licenses) {
|
||||
xb_licenses_json.emplace_back(it.first);
|
||||
}
|
||||
auto bb_licenses_json = JSON::list();
|
||||
auto bb_licenses_json = phosg::JSON::list();
|
||||
for (const auto& it : a->bb_licenses) {
|
||||
bb_licenses_json.emplace_back(it.first);
|
||||
}
|
||||
auto auto_patches_json = JSON::list();
|
||||
auto auto_patches_json = phosg::JSON::list();
|
||||
for (const auto& it : a->auto_patches_enabled) {
|
||||
auto_patches_json.emplace_back(it);
|
||||
}
|
||||
return JSON::dict({
|
||||
return phosg::JSON::dict({
|
||||
{"AccountID", a->account_id},
|
||||
{"Flags", a->flags},
|
||||
{"BanEndTime", a->ban_end_time ? a->ban_end_time : JSON(nullptr)},
|
||||
{"BanEndTime", a->ban_end_time ? a->ban_end_time : phosg::JSON(nullptr)},
|
||||
{"Ep3CurrentMeseta", a->ep3_current_meseta},
|
||||
{"Ep3TotalMesetaEarned", a->ep3_total_meseta_earned},
|
||||
{"BBTeamID", a->bb_team_id},
|
||||
@@ -316,11 +542,11 @@ JSON HTTPServer::generate_account_json_st(shared_ptr<const Account> a) {
|
||||
});
|
||||
};
|
||||
|
||||
JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared_ptr<const ItemNameIndex> item_name_index) {
|
||||
auto ret = JSON::dict({
|
||||
phosg::JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared_ptr<const ItemNameIndex> item_name_index) {
|
||||
auto ret = phosg::JSON::dict({
|
||||
{"ID", c->id},
|
||||
{"RemoteAddress", render_sockaddr_storage(c->channel.remote_addr)},
|
||||
{"Version", name_for_enum(c->version())},
|
||||
{"RemoteAddress", phosg::render_sockaddr_storage(c->channel.remote_addr)},
|
||||
{"Version", phosg::name_for_enum(c->version())},
|
||||
{"SubVersion", c->sub_version},
|
||||
{"Config", HTTPServer::generate_client_config_json_st(c->config)},
|
||||
{"Language", name_for_language_code(c->language())},
|
||||
@@ -329,7 +555,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
{"LocationFloor", c->floor},
|
||||
{"CanChat", c->can_chat},
|
||||
});
|
||||
ret.emplace("Account", c->login ? HTTPServer::generate_account_json_st(c->login->account) : JSON(nullptr));
|
||||
ret.emplace("Account", c->login ? HTTPServer::generate_account_json_st(c->login->account) : phosg::JSON(nullptr));
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
ret.emplace("LobbyID", l->lobby_id);
|
||||
@@ -354,10 +580,10 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
ret.emplace("NumLuckMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::LUCK));
|
||||
}
|
||||
}
|
||||
JSON items_json = JSON::list();
|
||||
phosg::JSON items_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
||||
const auto& item = p->inventory.items[z];
|
||||
auto item_dict = JSON::dict({
|
||||
auto item_dict = phosg::JSON::dict({
|
||||
{"Flags", item.flags.load()},
|
||||
{"Data", item.data.hex()},
|
||||
{"ItemID", item.data.id.load()},
|
||||
@@ -376,17 +602,17 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
ret.emplace("LCK", p->disp.stats.char_stats.lck.load());
|
||||
ret.emplace("EXP", p->disp.stats.experience.load());
|
||||
ret.emplace("Meseta", p->disp.stats.meseta.load());
|
||||
auto tech_levels_json = JSON::dict();
|
||||
auto tech_levels_json = phosg::JSON::dict();
|
||||
for (size_t z = 0; z < 0x13; z++) {
|
||||
auto level = p->get_technique_level(z);
|
||||
tech_levels_json.emplace(name_for_technique(z), (level != 0xFF) ? level : JSON(nullptr));
|
||||
tech_levels_json.emplace(name_for_technique(z), (level != 0xFF) ? level : phosg::JSON(nullptr));
|
||||
}
|
||||
ret.emplace("TechniqueLevels", std::move(tech_levels_json));
|
||||
}
|
||||
ret.emplace("Height", p->disp.stats.height.load());
|
||||
ret.emplace("Level", p->disp.stats.level.load());
|
||||
ret.emplace("NameColor", p->disp.visual.name_color.load());
|
||||
ret.emplace("ExtraModel", (p->disp.visual.validation_flags & 2) ? p->disp.visual.extra_model : JSON(nullptr));
|
||||
ret.emplace("ExtraModel", (p->disp.visual.validation_flags & 2) ? p->disp.visual.extra_model : phosg::JSON(nullptr));
|
||||
ret.emplace("SectionID", name_for_section_id(p->disp.visual.section_id));
|
||||
ret.emplace("CharClass", name_for_char_class(p->disp.visual.section_id));
|
||||
ret.emplace("Costume", p->disp.visual.costume.load());
|
||||
@@ -405,7 +631,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
|
||||
ret.emplace("AutoReply", p->auto_reply.decode(c->language()));
|
||||
ret.emplace("InfoBoard", p->info_board.decode(c->language()));
|
||||
auto battle_place_counts = JSON::list({
|
||||
auto battle_place_counts = phosg::JSON::list({
|
||||
p->battle_records.place_counts[0].load(),
|
||||
p->battle_records.place_counts[1].load(),
|
||||
p->battle_records.place_counts[2].load(),
|
||||
@@ -415,8 +641,8 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
ret.emplace("BattleDisconnectCount", p->battle_records.disconnect_count.load());
|
||||
|
||||
if (!is_ep3(c->version())) {
|
||||
auto json_for_challenge_times = []<size_t Count>(const parray<ChallengeTime, Count>& times) -> JSON {
|
||||
auto times_json = JSON::list();
|
||||
auto json_for_challenge_times = []<size_t Count>(const parray<ChallengeTime, Count>& times) -> phosg::JSON {
|
||||
auto times_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < times.size(); z++) {
|
||||
times_json.emplace_back(times[z].decode());
|
||||
}
|
||||
@@ -436,7 +662,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
uint8_t day = (p->challenge_records.grave_time >> 16) & 0xFF;
|
||||
uint8_t hour = (p->challenge_records.grave_time >> 8) & 0xFF;
|
||||
uint8_t minute = p->challenge_records.grave_time & 0xFF;
|
||||
ret.emplace("ChallengeGraveTime", string_printf("%04hu-%02hhu-%02hhu %02hhu:%02hhu:00", year, month, day, hour, minute));
|
||||
ret.emplace("ChallengeGraveTime", phosg::string_printf("%04hu-%02hhu-%02hhu %02hhu:%02hhu:00", year, month, day, hour, minute));
|
||||
}
|
||||
string grave_enemy_types;
|
||||
if (p->challenge_records.grave_defeated_by_enemy_rt_index) {
|
||||
@@ -444,7 +670,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
if (!grave_enemy_types.empty()) {
|
||||
grave_enemy_types += "/";
|
||||
}
|
||||
grave_enemy_types += name_for_enum(type);
|
||||
grave_enemy_types += phosg::name_for_enum(type);
|
||||
}
|
||||
}
|
||||
ret.emplace("ChallengeGraveDefeatedByEnemy", std::move(grave_enemy_types));
|
||||
@@ -465,7 +691,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c, shared
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_proxy_client_json_st(shared_ptr<const ProxyServer::LinkedSession> ses) {
|
||||
phosg::JSON HTTPServer::generate_proxy_client_json_st(shared_ptr<const ProxyServer::LinkedSession> ses) {
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
@@ -476,35 +702,35 @@ JSON HTTPServer::generate_proxy_client_json_st(shared_ptr<const ProxyServer::Lin
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
|
||||
auto lobby_players_json = JSON::list();
|
||||
auto lobby_players_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < ses->lobby_players.size(); z++) {
|
||||
const auto& p = ses->lobby_players[z];
|
||||
if (p.guild_card_number) {
|
||||
lobby_players_json.emplace_back(JSON::dict({
|
||||
lobby_players_json.emplace_back(phosg::JSON::dict({
|
||||
{"GuildCardNumber", p.guild_card_number},
|
||||
{"Name", p.name},
|
||||
{"Language", name_for_language_code(p.language)},
|
||||
{"SectionID", name_for_section_id(p.section_id)},
|
||||
{"CharClass", name_for_char_class(p.char_class)},
|
||||
}));
|
||||
lobby_players_json.back().emplace("XBUserID", p.xb_user_id ? p.xb_user_id : JSON(nullptr));
|
||||
lobby_players_json.back().emplace("XBUserID", p.xb_user_id ? p.xb_user_id : phosg::JSON(nullptr));
|
||||
} else {
|
||||
lobby_players_json.emplace_back(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = JSON::dict({
|
||||
auto ret = phosg::JSON::dict({
|
||||
{"ID", ses->id},
|
||||
{"RemoteClientAddress", render_sockaddr_storage(ses->client_channel.remote_addr)},
|
||||
{"RemoteServerAddress", render_sockaddr_storage(ses->server_channel.remote_addr)},
|
||||
{"RemoteClientAddress", phosg::render_sockaddr_storage(ses->client_channel.remote_addr)},
|
||||
{"RemoteServerAddress", phosg::render_sockaddr_storage(ses->server_channel.remote_addr)},
|
||||
{"LocalPort", ses->local_port},
|
||||
{"NextDestination", render_sockaddr_storage(ses->next_destination)},
|
||||
{"Version", name_for_enum(ses->version())},
|
||||
{"NextDestination", phosg::render_sockaddr_storage(ses->next_destination)},
|
||||
{"Version", phosg::name_for_enum(ses->version())},
|
||||
{"SubVersion", ses->sub_version},
|
||||
{"Name", ses->character_name},
|
||||
{"DCHardwareID", ses->hardware_id},
|
||||
{"RemoteGuildCardNumber", ses->remote_guild_card_number},
|
||||
{"RemoteClientConfigData", format_data_string(&ses->remote_client_config_data[0], ses->remote_client_config_data.size())},
|
||||
{"RemoteClientConfigData", phosg::format_data_string(&ses->remote_client_config_data[0], ses->remote_client_config_data.size())},
|
||||
{"Config", HTTPServer::generate_client_config_json_st(ses->config)},
|
||||
{"Language", name_for_language_code(ses->language())},
|
||||
{"LobbyClientID", ses->lobby_client_id},
|
||||
@@ -533,19 +759,19 @@ JSON HTTPServer::generate_proxy_client_json_st(shared_ptr<const ProxyServer::Lin
|
||||
ret.emplace("DropMode", "proxy");
|
||||
break;
|
||||
}
|
||||
ret.emplace("Account", ses->login ? HTTPServer::generate_account_json_st(ses->login->account) : JSON(nullptr));
|
||||
ret.emplace("Account", ses->login ? HTTPServer::generate_account_json_st(ses->login->account) : phosg::JSON(nullptr));
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<const ItemNameIndex> item_name_index) {
|
||||
phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<const ItemNameIndex> item_name_index) {
|
||||
std::array<std::shared_ptr<Client>, 12> clients;
|
||||
|
||||
auto client_ids_json = JSON::list();
|
||||
auto client_ids_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < l->max_clients; z++) {
|
||||
client_ids_json.emplace_back(l->clients[z] ? l->clients[z]->id : JSON(nullptr));
|
||||
client_ids_json.emplace_back(l->clients[z] ? l->clients[z]->id : phosg::JSON(nullptr));
|
||||
}
|
||||
|
||||
auto ret = JSON::dict({
|
||||
auto ret = phosg::JSON::dict({
|
||||
{"ID", l->lobby_id},
|
||||
{"AllowedVersions", l->allowed_versions},
|
||||
{"Event", l->event},
|
||||
@@ -570,7 +796,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
ret.emplace("QuestSelectionInProgress", l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS));
|
||||
ret.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS));
|
||||
ret.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS));
|
||||
auto variations_json = JSON::list();
|
||||
auto variations_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < l->variations.size(); z++) {
|
||||
variations_json.emplace_back(l->variations[z].load());
|
||||
}
|
||||
@@ -613,11 +839,11 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
}
|
||||
}
|
||||
|
||||
auto floor_items_json = JSON::list();
|
||||
auto floor_items_json = phosg::JSON::list();
|
||||
for (size_t floor = 0; floor < l->floor_item_managers.size(); floor++) {
|
||||
for (const auto& it : l->floor_item_managers[floor].items) {
|
||||
const auto& item = it.second;
|
||||
auto item_dict = JSON::dict({
|
||||
auto item_dict = phosg::JSON::dict({
|
||||
{"LocationFloor", floor},
|
||||
{"LocationX", item->x},
|
||||
{"LocationZ", item->z},
|
||||
@@ -642,7 +868,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
|
||||
auto ep3s = l->ep3_server;
|
||||
if (ep3s) {
|
||||
auto players_json = JSON::list();
|
||||
auto players_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (!ep3s->name_entries[z].present) {
|
||||
players_json.emplace_back(nullptr);
|
||||
@@ -650,9 +876,9 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
auto lc = l->clients[z];
|
||||
|
||||
auto deck_entry = ep3s->deck_entries[z];
|
||||
JSON deck_json = nullptr;
|
||||
phosg::JSON deck_json = nullptr;
|
||||
if (deck_entry) {
|
||||
auto cards_json = JSON::list();
|
||||
auto cards_json = phosg::JSON::list();
|
||||
for (size_t w = 0; w < deck_entry->card_ids.size(); w++) {
|
||||
try {
|
||||
const auto& ce = ep3s->options.card_index->definition_for_id(deck_entry->card_ids[w]);
|
||||
@@ -671,7 +897,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
cards_json.emplace_back(deck_entry->card_ids[w].load());
|
||||
}
|
||||
}
|
||||
deck_json = JSON::dict({
|
||||
deck_json = phosg::JSON::dict({
|
||||
{"Name", deck_entry->name.decode(lc ? lc->language() : 1)},
|
||||
{"TeamID", deck_entry->team_id.load()},
|
||||
{"Cards", std::move(cards_json)},
|
||||
@@ -680,7 +906,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
});
|
||||
}
|
||||
|
||||
auto player_json = JSON::dict({
|
||||
auto player_json = phosg::JSON::dict({
|
||||
{"PlayerName", ep3s->name_entries[z].name.decode(lc ? lc->language() : 1)},
|
||||
{"ClientID", ep3s->name_entries[z].client_id},
|
||||
{"IsCOM", !!ep3s->name_entries[z].is_cpu_player},
|
||||
@@ -689,13 +915,13 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
players_json.emplace_back(std::move(player_json));
|
||||
}
|
||||
}
|
||||
auto battle_state_json = JSON::dict({
|
||||
auto battle_state_json = phosg::JSON::dict({
|
||||
{"BehaviorFlags", ep3s->options.behavior_flags},
|
||||
{"RandomSeed", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->seed() : JSON(nullptr)},
|
||||
{"RandomOffset", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->absolute_offset() : JSON(nullptr)},
|
||||
{"RandomSeed", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->seed() : phosg::JSON(nullptr)},
|
||||
{"RandomOffset", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->absolute_offset() : phosg::JSON(nullptr)},
|
||||
{"Tournament", ep3s->options.tournament ? ep3s->options.tournament->json() : nullptr},
|
||||
{"MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : JSON(nullptr)},
|
||||
{"EnvironmentNumber", ep3s->map_and_rules ? ep3s->map_and_rules->environment_number : JSON(nullptr)},
|
||||
{"MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : phosg::JSON(nullptr)},
|
||||
{"EnvironmentNumber", ep3s->map_and_rules ? ep3s->map_and_rules->environment_number : phosg::JSON(nullptr)},
|
||||
{"Rules", ep3s->map_and_rules ? ep3s->map_and_rules->rules.json() : nullptr},
|
||||
{"Players", std::move(players_json)},
|
||||
{"IsBattleFinished", ep3s->battle_finished},
|
||||
@@ -703,13 +929,13 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
{"RoundNumber", ep3s->round_num},
|
||||
{"FirstTeamTurn", ep3s->first_team_turn},
|
||||
{"CurrentTeamTurn", ep3s->current_team_turn1},
|
||||
{"BattlePhase", name_for_enum(ep3s->battle_phase)},
|
||||
{"BattlePhase", phosg::name_for_enum(ep3s->battle_phase)},
|
||||
{"SetupPhase", ep3s->setup_phase},
|
||||
{"RegistrationPhase", ep3s->registration_phase},
|
||||
{"ActionSubphase", ep3s->action_subphase},
|
||||
{"BattleStartTimeUsecs", ep3s->battle_start_usecs},
|
||||
{"TeamEXP", JSON::list({ep3s->team_exp[0], ep3s->team_exp[1]})},
|
||||
{"TeamDiceBonus", JSON::list({ep3s->team_dice_bonus[0], ep3s->team_dice_bonus[1]})},
|
||||
{"TeamEXP", phosg::JSON::list({ep3s->team_exp[0], ep3s->team_exp[1]})},
|
||||
{"TeamDiceBonus", phosg::JSON::list({ep3s->team_dice_bonus[0], ep3s->team_dice_bonus[1]})},
|
||||
});
|
||||
// std::shared_ptr<StateFlags> state_flags;
|
||||
// std::array<std::shared_ptr<PlayerState>, 4> player_states;
|
||||
@@ -721,7 +947,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
if (watched_lobby) {
|
||||
ret.emplace("WatchedLobbyID", watched_lobby->lobby_id);
|
||||
}
|
||||
auto watcher_lobby_ids_json = JSON::list();
|
||||
auto watcher_lobby_ids_json = phosg::JSON::list();
|
||||
for (const auto& watcher_lobby : l->watcher_lobbies) {
|
||||
watcher_lobby_ids_json.emplace_back(watcher_lobby->lobby_id);
|
||||
}
|
||||
@@ -738,9 +964,9 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_game_server_clients_json() const {
|
||||
return call_on_event_thread<JSON>(this->state->base, [&]() {
|
||||
auto res = JSON::list();
|
||||
phosg::JSON HTTPServer::generate_game_server_clients_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
auto res = phosg::JSON::list();
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
res.emplace_back(this->generate_game_client_json_st(it.second, this->state->item_name_index_opt(it.second->version())));
|
||||
}
|
||||
@@ -748,9 +974,9 @@ JSON HTTPServer::generate_game_server_clients_json() const {
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_proxy_server_clients_json() const {
|
||||
return call_on_event_thread<JSON>(this->state->base, [&]() {
|
||||
JSON res = JSON::list();
|
||||
phosg::JSON HTTPServer::generate_proxy_server_clients_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
phosg::JSON res = phosg::JSON::list();
|
||||
if (this->state->proxy_server) {
|
||||
for (const auto& it : this->state->proxy_server->all_sessions()) {
|
||||
res.emplace_back(this->generate_proxy_client_json_st(it.second));
|
||||
@@ -760,8 +986,8 @@ JSON HTTPServer::generate_proxy_server_clients_json() const {
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_server_info_json() const {
|
||||
return call_on_event_thread<JSON>(this->state->base, [&]() {
|
||||
phosg::JSON HTTPServer::generate_server_info_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
size_t game_count = 0;
|
||||
size_t lobby_count = 0;
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
@@ -771,12 +997,12 @@ JSON HTTPServer::generate_server_info_json() const {
|
||||
lobby_count++;
|
||||
}
|
||||
}
|
||||
uint64_t uptime_usecs = now() - this->state->creation_time;
|
||||
return JSON::dict({
|
||||
uint64_t uptime_usecs = phosg::now() - this->state->creation_time;
|
||||
return phosg::JSON::dict({
|
||||
{"StartTimeUsecs", this->state->creation_time},
|
||||
{"StartTime", format_time(this->state->creation_time)},
|
||||
{"StartTime", phosg::format_time(this->state->creation_time)},
|
||||
{"UptimeUsecs", uptime_usecs},
|
||||
{"Uptime", format_duration(uptime_usecs)},
|
||||
{"Uptime", phosg::format_duration(uptime_usecs)},
|
||||
{"LobbyCount", lobby_count},
|
||||
{"GameCount", game_count},
|
||||
{"ClientCount", this->state->channel_to_client.size()},
|
||||
@@ -786,9 +1012,9 @@ JSON HTTPServer::generate_server_info_json() const {
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_lobbies_json() const {
|
||||
return call_on_event_thread<JSON>(this->state->base, [&]() {
|
||||
JSON res = JSON::list();
|
||||
phosg::JSON HTTPServer::generate_lobbies_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
phosg::JSON res = phosg::JSON::list();
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
res.emplace_back(this->generate_lobby_json_st(it.second, this->state->item_name_index_opt(it.second->base_version)));
|
||||
}
|
||||
@@ -796,46 +1022,46 @@ JSON HTTPServer::generate_lobbies_json() const {
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_summary_json() const {
|
||||
auto ret = call_on_event_thread<JSON>(this->state->base, [&]() {
|
||||
auto clients_json = JSON::list();
|
||||
phosg::JSON HTTPServer::generate_summary_json() const {
|
||||
auto ret = call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
auto clients_json = phosg::JSON::list();
|
||||
for (const auto& it : this->state->channel_to_client) {
|
||||
auto c = it.second;
|
||||
auto p = c->character(false, false);
|
||||
auto l = c->lobby.lock();
|
||||
clients_json.emplace_back(JSON::dict({
|
||||
clients_json.emplace_back(phosg::JSON::dict({
|
||||
{"ID", c->id},
|
||||
{"AccountID", c->login ? c->login->account->account_id : JSON(nullptr)},
|
||||
{"Name", p ? p->disp.name.decode(it.second->language()) : JSON(nullptr)},
|
||||
{"Version", name_for_enum(it.second->version())},
|
||||
{"AccountID", c->login ? c->login->account->account_id : phosg::JSON(nullptr)},
|
||||
{"Name", p ? p->disp.name.decode(it.second->language()) : phosg::JSON(nullptr)},
|
||||
{"Version", phosg::name_for_enum(it.second->version())},
|
||||
{"Language", name_for_language_code(it.second->language())},
|
||||
{"Level", p ? p->disp.stats.level + 1 : JSON(nullptr)},
|
||||
{"Class", p ? name_for_char_class(p->disp.visual.char_class) : JSON(nullptr)},
|
||||
{"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : JSON(nullptr)},
|
||||
{"LobbyID", l ? l->lobby_id : JSON(nullptr)},
|
||||
{"Level", p ? p->disp.stats.level + 1 : phosg::JSON(nullptr)},
|
||||
{"Class", p ? name_for_char_class(p->disp.visual.char_class) : phosg::JSON(nullptr)},
|
||||
{"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : phosg::JSON(nullptr)},
|
||||
{"LobbyID", l ? l->lobby_id : phosg::JSON(nullptr)},
|
||||
}));
|
||||
}
|
||||
|
||||
auto proxy_clients_json = JSON::list();
|
||||
auto proxy_clients_json = phosg::JSON::list();
|
||||
if (this->state->proxy_server) {
|
||||
for (const auto& it : this->state->proxy_server->all_sessions()) {
|
||||
proxy_clients_json.emplace_back(JSON::dict({
|
||||
{"AccountID", it.second->login ? it.second->login->account->account_id : JSON(nullptr)},
|
||||
proxy_clients_json.emplace_back(phosg::JSON::dict({
|
||||
{"AccountID", it.second->login ? it.second->login->account->account_id : phosg::JSON(nullptr)},
|
||||
{"Name", it.second->character_name},
|
||||
{"Version", name_for_enum(it.second->version())},
|
||||
{"Version", phosg::name_for_enum(it.second->version())},
|
||||
{"Language", name_for_language_code(it.second->language())},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
auto games_json = JSON::list();
|
||||
auto games_json = phosg::JSON::list();
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
auto l = it.second;
|
||||
if (l->is_game()) {
|
||||
auto game_json = JSON::dict({
|
||||
auto game_json = phosg::JSON::dict({
|
||||
{"ID", l->lobby_id},
|
||||
{"Name", l->name},
|
||||
{"BaseVersion", name_for_enum(l->base_version)},
|
||||
{"BaseVersion", phosg::name_for_enum(l->base_version)},
|
||||
{"Players", l->count_clients()},
|
||||
{"CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED)},
|
||||
{"Episode", name_for_episode(l->episode)},
|
||||
@@ -845,7 +1071,7 @@ JSON HTTPServer::generate_summary_json() const {
|
||||
auto ep3s = l->ep3_server;
|
||||
game_json.emplace("BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS));
|
||||
game_json.emplace("IsSpectatorTeam", l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM));
|
||||
game_json.emplace("MapNumber", (ep3s && ep3s->last_chosen_map) ? ep3s->last_chosen_map->map_number : JSON(nullptr));
|
||||
game_json.emplace("MapNumber", (ep3s && ep3s->last_chosen_map) ? ep3s->last_chosen_map->map_number : phosg::JSON(nullptr));
|
||||
game_json.emplace("Rules", (ep3s && ep3s->map_and_rules) ? ep3s->map_and_rules->rules.json() : nullptr);
|
||||
} else {
|
||||
game_json.emplace("QuestSelectionInProgress", l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS));
|
||||
@@ -860,7 +1086,7 @@ JSON HTTPServer::generate_summary_json() const {
|
||||
}
|
||||
}
|
||||
|
||||
return JSON::dict({
|
||||
return phosg::JSON::dict({
|
||||
{"Clients", std::move(clients_json)},
|
||||
{"ProxyClients", std::move(proxy_clients_json)},
|
||||
{"Games", std::move(games_json)},
|
||||
@@ -870,8 +1096,8 @@ JSON HTTPServer::generate_summary_json() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_all_json() const {
|
||||
return JSON::dict({
|
||||
phosg::JSON HTTPServer::generate_all_json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"Clients", this->generate_game_server_clients_json()},
|
||||
{"ProxyClients", this->generate_proxy_server_clients_json()},
|
||||
{"Lobbies", this->generate_lobbies_json()},
|
||||
@@ -879,43 +1105,43 @@ JSON HTTPServer::generate_all_json() const {
|
||||
});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_ep3_cards_json(bool trial) const {
|
||||
phosg::JSON HTTPServer::generate_ep3_cards_json(bool trial) const {
|
||||
auto index = call_on_event_thread<shared_ptr<const Episode3::CardIndex>>(this->state->base, [&]() {
|
||||
return trial ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
||||
});
|
||||
return index->definitions_json();
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_common_tables_json() const {
|
||||
phosg::JSON HTTPServer::generate_common_tables_json() const {
|
||||
auto [set_v2, set_v3_v4] = call_on_event_thread<pair<shared_ptr<const CommonItemSet>, shared_ptr<const CommonItemSet>>>(this->state->base, [&]() {
|
||||
return make_pair(this->state->common_item_set_v2, this->state->common_item_set_v3_v4);
|
||||
});
|
||||
return JSON::dict({{"v1_v2", set_v2->json()}, {"v3_v4", set_v3_v4->json()}});
|
||||
return phosg::JSON::dict({{"v1_v2", set_v2->json()}, {"v3_v4", set_v3_v4->json()}});
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_rare_tables_json() const {
|
||||
phosg::JSON HTTPServer::generate_rare_tables_json() const {
|
||||
auto sets = call_on_event_thread<unordered_map<string, shared_ptr<const RareItemSet>>>(this->state->base, [&]() {
|
||||
return this->state->rare_item_sets;
|
||||
});
|
||||
JSON ret = JSON::list();
|
||||
phosg::JSON ret = phosg::JSON::list();
|
||||
for (const auto& it : sets) {
|
||||
ret.emplace_back(it.first);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
JSON HTTPServer::generate_rare_table_json(const std::string& table_name) const {
|
||||
phosg::JSON HTTPServer::generate_rare_table_json(const std::string& table_name) const {
|
||||
try {
|
||||
auto colls = call_on_event_thread<pair<shared_ptr<const RareItemSet>, shared_ptr<const ItemNameIndex>>>(this->state->base, [&]() {
|
||||
const auto& table = this->state->rare_item_sets.at(table_name);
|
||||
shared_ptr<const ItemNameIndex> name_index;
|
||||
if (ends_with(table_name, "-v1")) {
|
||||
if (phosg::ends_with(table_name, "-v1")) {
|
||||
name_index = this->state->item_name_index_opt(Version::DC_V1);
|
||||
} else if (ends_with(table_name, "-v2")) {
|
||||
} else if (phosg::ends_with(table_name, "-v2")) {
|
||||
name_index = this->state->item_name_index_opt(Version::PC_V2);
|
||||
} else if (ends_with(table_name, "-v3")) {
|
||||
} else if (phosg::ends_with(table_name, "-v3")) {
|
||||
name_index = this->state->item_name_index_opt(Version::GC_V3);
|
||||
} else if (ends_with(table_name, "-v4")) {
|
||||
} else if (phosg::ends_with(table_name, "-v4")) {
|
||||
name_index = this->state->item_name_index_opt(Version::BB_V4);
|
||||
}
|
||||
return make_pair(table, name_index);
|
||||
@@ -927,9 +1153,9 @@ JSON HTTPServer::generate_rare_table_json(const std::string& table_name) const {
|
||||
}
|
||||
|
||||
void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
shared_ptr<const JSON> ret;
|
||||
shared_ptr<const phosg::JSON> ret;
|
||||
uint32_t serialize_options = 0;
|
||||
uint64_t start_time = now();
|
||||
uint64_t start_time = phosg::now();
|
||||
string uri = evhttp_request_get_uri(req);
|
||||
|
||||
try {
|
||||
@@ -942,14 +1168,14 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
|
||||
static const string default_format_option = "false";
|
||||
if (this->get_url_param(query, "format", &default_format_option) == "true") {
|
||||
serialize_options |= JSON::SerializeOption::FORMAT | JSON::SerializeOption::SORT_DICT_KEYS;
|
||||
serialize_options |= phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::SORT_DICT_KEYS;
|
||||
}
|
||||
if (this->get_url_param(query, "hex", &default_format_option) == "true") {
|
||||
serialize_options |= JSON::SerializeOption::HEX_INTEGERS;
|
||||
serialize_options |= phosg::JSON::SerializeOption::HEX_INTEGERS;
|
||||
}
|
||||
|
||||
if (uri == "/") {
|
||||
auto endpoints_json = JSON::list({
|
||||
auto endpoints_json = phosg::JSON::list({
|
||||
"/y/data/ep3-cards",
|
||||
"/y/data/ep3-cards-trial",
|
||||
"/y/data/common-tables",
|
||||
@@ -960,35 +1186,47 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
"/y/proxy-clients",
|
||||
"/y/lobbies",
|
||||
"/y/server",
|
||||
"/y/rare-drops/stream",
|
||||
"/y/summary",
|
||||
"/y/all",
|
||||
});
|
||||
ret = make_shared<JSON>(JSON::dict({{"endpoints", std::move(endpoints_json)}}));
|
||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"endpoints", std::move(endpoints_json)}}));
|
||||
|
||||
} else if (uri == "/y/rare-drops/stream") {
|
||||
auto c = this->enable_websockets(req);
|
||||
if (!c) {
|
||||
throw http_error(400, "this path requires a websocket connection");
|
||||
} else {
|
||||
this->rare_drop_subscribers.emplace(c);
|
||||
auto version_message = phosg::JSON::dict({{"ServerType", "newserv"}});
|
||||
this->send_websocket_message(c, version_message.serialize());
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (uri == "/y/data/ep3-cards") {
|
||||
ret = make_shared<JSON>(this->generate_ep3_cards_json(false));
|
||||
ret = make_shared<phosg::JSON>(this->generate_ep3_cards_json(false));
|
||||
} else if (uri == "/y/data/ep3-cards-trial") {
|
||||
ret = make_shared<JSON>(this->generate_ep3_cards_json(true));
|
||||
ret = make_shared<phosg::JSON>(this->generate_ep3_cards_json(true));
|
||||
} else if (uri == "/y/data/common-tables") {
|
||||
ret = make_shared<JSON>(this->generate_common_tables_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_common_tables_json());
|
||||
} else if (uri == "/y/data/rare-tables") {
|
||||
ret = make_shared<JSON>(this->generate_rare_tables_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_rare_tables_json());
|
||||
} else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) {
|
||||
ret = make_shared<JSON>(this->generate_rare_table_json(uri.substr(20)));
|
||||
ret = make_shared<phosg::JSON>(this->generate_rare_table_json(uri.substr(20)));
|
||||
} else if (uri == "/y/data/config") {
|
||||
ret = call_on_event_thread<shared_ptr<const JSON>>(this->state->base, [this]() { return this->state->config_json; });
|
||||
ret = call_on_event_thread<shared_ptr<const phosg::JSON>>(this->state->base, [this]() { return this->state->config_json; });
|
||||
} else if (uri == "/y/clients") {
|
||||
ret = make_shared<JSON>(this->generate_game_server_clients_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_game_server_clients_json());
|
||||
} else if (uri == "/y/proxy-clients") {
|
||||
ret = make_shared<JSON>(this->generate_proxy_server_clients_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_proxy_server_clients_json());
|
||||
} else if (uri == "/y/lobbies") {
|
||||
ret = make_shared<JSON>(this->generate_lobbies_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_lobbies_json());
|
||||
} else if (uri == "/y/server") {
|
||||
ret = make_shared<JSON>(this->generate_server_info_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_server_info_json());
|
||||
} else if (uri == "/y/summary") {
|
||||
ret = make_shared<JSON>(this->generate_summary_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_summary_json());
|
||||
} else if (uri == "/y/all") {
|
||||
ret = make_shared<JSON>(this->generate_all_json());
|
||||
ret = make_shared<phosg::JSON>(this->generate_all_json());
|
||||
|
||||
} else {
|
||||
throw http_error(404, "unknown action");
|
||||
@@ -1008,20 +1246,20 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t handler_end = now();
|
||||
uint64_t handler_end = phosg::now();
|
||||
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
||||
string* serialized = new string(ret->serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options));
|
||||
string* serialized = new string(ret->serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options));
|
||||
size_t size = serialized->size();
|
||||
uint64_t serialize_end = now();
|
||||
uint64_t serialize_end = phosg::now();
|
||||
auto cleanup = +[](const void*, size_t, void* s) -> void {
|
||||
delete reinterpret_cast<string*>(s);
|
||||
};
|
||||
evbuffer_add_reference(out_buffer.get(), serialized->data(), serialized->size(), cleanup, serialized);
|
||||
this->send_response(req, 200, "application/json", out_buffer.get());
|
||||
|
||||
string handler_time = format_duration(handler_end - start_time);
|
||||
string serialize_time = format_duration(serialize_end - handler_end);
|
||||
string size_str = format_size(size);
|
||||
string handler_time = phosg::format_duration(handler_end - start_time);
|
||||
string serialize_time = phosg::format_duration(serialize_end - handler_end);
|
||||
string size_str = phosg::format_size(size);
|
||||
server_log.info("[HTTPServer] %s in [handler: %s, serialize: %s, size: %s]",
|
||||
uri.c_str(), handler_time.c_str(), serialize_time.c_str(), size_str.c_str());
|
||||
}
|
||||
|
||||
+54
-16
@@ -28,6 +28,8 @@ public:
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
void send_rare_drop_notification(std::shared_ptr<const phosg::JSON> message);
|
||||
|
||||
protected:
|
||||
class http_error : public std::runtime_error {
|
||||
public:
|
||||
@@ -35,11 +37,47 @@ protected:
|
||||
int code;
|
||||
};
|
||||
|
||||
struct WebsocketClient {
|
||||
struct evhttp_connection* conn;
|
||||
struct bufferevent* bev;
|
||||
|
||||
uint8_t pending_opcode;
|
||||
std::string pending_data;
|
||||
|
||||
uint64_t last_communication_time;
|
||||
|
||||
void* context;
|
||||
|
||||
WebsocketClient(struct evhttp_connection* conn);
|
||||
~WebsocketClient();
|
||||
|
||||
void reset_pending_frame();
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct evhttp> http;
|
||||
std::thread th;
|
||||
|
||||
std::unordered_set<std::shared_ptr<WebsocketClient>> rare_drop_subscribers;
|
||||
|
||||
std::unordered_map<struct bufferevent*, std::shared_ptr<WebsocketClient>> bev_to_websocket_client;
|
||||
|
||||
std::shared_ptr<WebsocketClient> enable_websockets(struct evhttp_request* req);
|
||||
|
||||
static void dispatch_on_websocket_read(struct bufferevent* bev, void* ctx);
|
||||
static void dispatch_on_websocket_error(struct bufferevent* bev, short events, void* ctx);
|
||||
|
||||
void on_websocket_read(struct bufferevent* bev);
|
||||
void on_websocket_error(struct bufferevent* bev, short events);
|
||||
|
||||
void disconnect_websocket_client(struct bufferevent* bev);
|
||||
void send_websocket_message(struct bufferevent* bev, const std::string& message, uint8_t opcode = 0x01);
|
||||
void send_websocket_message(std::shared_ptr<WebsocketClient> c, const std::string& message, uint8_t opcode = 0x01);
|
||||
|
||||
virtual void handle_websocket_message(std::shared_ptr<WebsocketClient> c, uint8_t opcode, const std::string& message);
|
||||
virtual void handle_websocket_disconnect(std::shared_ptr<WebsocketClient> c);
|
||||
|
||||
void thread_fn();
|
||||
|
||||
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
|
||||
@@ -56,21 +94,21 @@ protected:
|
||||
const std::string& key,
|
||||
const std::string* _default = nullptr);
|
||||
|
||||
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
|
||||
static JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
JSON generate_game_server_clients_json() const;
|
||||
JSON generate_proxy_server_clients_json() const;
|
||||
JSON generate_server_info_json() const;
|
||||
JSON generate_lobbies_json() const;
|
||||
JSON generate_summary_json() const;
|
||||
JSON generate_all_json() const;
|
||||
static phosg::JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
|
||||
static phosg::JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static phosg::JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static phosg::JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static phosg::JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static phosg::JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
phosg::JSON generate_game_server_clients_json() const;
|
||||
phosg::JSON generate_proxy_server_clients_json() const;
|
||||
phosg::JSON generate_server_info_json() const;
|
||||
phosg::JSON generate_lobbies_json() const;
|
||||
phosg::JSON generate_summary_json() const;
|
||||
phosg::JSON generate_all_json() const;
|
||||
|
||||
JSON generate_ep3_cards_json(bool trial) const;
|
||||
JSON generate_common_tables_json() const;
|
||||
JSON generate_rare_tables_json() const;
|
||||
JSON generate_rare_table_json(const std::string& table_name) const;
|
||||
phosg::JSON generate_ep3_cards_json(bool trial) const;
|
||||
phosg::JSON generate_common_tables_json() const;
|
||||
phosg::JSON generate_rare_tables_json() const;
|
||||
phosg::JSON generate_rare_table_json(const std::string& table_name) const;
|
||||
};
|
||||
|
||||
+10
-10
@@ -23,7 +23,7 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
||||
this->total_size = size;
|
||||
this->payload_size = size;
|
||||
|
||||
StringReader r(header_start, size);
|
||||
phosg::StringReader r(header_start, size);
|
||||
|
||||
// Parse link-layer header
|
||||
Protocol proto = Protocol::NONE;
|
||||
@@ -126,35 +126,35 @@ string FrameInfo::header_str() const {
|
||||
|
||||
string ret;
|
||||
if (this->ether) {
|
||||
ret = string_printf(
|
||||
ret = phosg::string_printf(
|
||||
"ETHER:%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX->%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
|
||||
this->ether->src_mac[0], this->ether->src_mac[1], this->ether->src_mac[2],
|
||||
this->ether->src_mac[3], this->ether->src_mac[4], this->ether->src_mac[5],
|
||||
this->ether->dest_mac[0], this->ether->dest_mac[1], this->ether->dest_mac[2],
|
||||
this->ether->dest_mac[3], this->ether->dest_mac[4], this->ether->dest_mac[5]);
|
||||
} else if (this->hdlc) {
|
||||
ret = string_printf("HDLC:%02hhX/%02hhX", this->hdlc->address, this->hdlc->control);
|
||||
ret = phosg::string_printf("HDLC:%02hhX/%02hhX", this->hdlc->address, this->hdlc->control);
|
||||
} else {
|
||||
return "<invalid-frame-info>";
|
||||
}
|
||||
|
||||
if (this->arp) {
|
||||
ret += string_printf(
|
||||
ret += phosg::string_printf(
|
||||
",ARP,hw_type=%04hX,proto_type=%04hX,hw_addr_len=%02hhX,proto_addr_len=%02hhX,op=%04hX",
|
||||
this->arp->hardware_type.load(), this->arp->protocol_type.load(), this->arp->hwaddr_len, this->arp->paddr_len, this->arp->operation.load());
|
||||
|
||||
} else if (this->ipv4) {
|
||||
ret += string_printf(
|
||||
ret += phosg::string_printf(
|
||||
",IPv4,size=%04hX,src=%08" PRIX32 ",dest=%08" PRIX32,
|
||||
this->ipv4->size.load(), this->ipv4->src_addr.load(), this->ipv4->dest_addr.load());
|
||||
|
||||
if (this->udp) {
|
||||
ret += string_printf(
|
||||
ret += phosg::string_printf(
|
||||
",UDP,src_port=%04hX,dest_port=%04hX,size=%04hX",
|
||||
this->udp->src_port.load(), this->udp->dest_port.load(), this->udp->size.load());
|
||||
|
||||
} else if (this->tcp) {
|
||||
ret += string_printf(
|
||||
ret += phosg::string_printf(
|
||||
",TCP,src_port=%04hX,dest_port=%04hX,seq=%08" PRIX32 ",ack=%08" PRIX32 ",flags=%04hX(",
|
||||
this->tcp->src_port.load(), this->tcp->dest_port.load(), this->tcp->seq_num.load(), this->tcp->ack_num.load(), this->tcp->flags.load());
|
||||
if (this->tcp->flags & TCPHeader::Flag::FIN) {
|
||||
@@ -175,14 +175,14 @@ string FrameInfo::header_str() const {
|
||||
ret += ')';
|
||||
|
||||
} else {
|
||||
ret += string_printf(",proto=%02hhX", this->ipv4->protocol);
|
||||
ret += phosg::string_printf(",proto=%02hhX", this->ipv4->protocol);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this->ether) {
|
||||
ret += string_printf(",proto=%04hX", this->ether->protocol.load());
|
||||
ret += phosg::string_printf(",proto=%04hX", this->ether->protocol.load());
|
||||
} else if (this->hdlc) {
|
||||
ret += string_printf(",proto=%04hX", this->hdlc->protocol.load());
|
||||
ret += phosg::string_printf(",proto=%04hX", this->hdlc->protocol.load());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -161,8 +161,8 @@ struct FrameInfo {
|
||||
|
||||
std::string header_str() const;
|
||||
|
||||
inline StringReader read_payload() const {
|
||||
return StringReader(this->payload, this->payload_size);
|
||||
inline phosg::StringReader read_payload() const {
|
||||
return phosg::StringReader(this->payload, this->payload_size);
|
||||
}
|
||||
|
||||
void truncate(size_t new_total_size);
|
||||
|
||||
+50
-51
@@ -22,7 +22,7 @@ using namespace std;
|
||||
static const size_t DEFAULT_RESEND_PUSH_USECS = 200000; // 200ms
|
||||
|
||||
static string unescape_hdlc_frame(const void* data, size_t size) {
|
||||
StringReader r(data, size);
|
||||
phosg::StringReader r(data, size);
|
||||
if (r.get_u8(data) != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not begin with 7E");
|
||||
}
|
||||
@@ -53,7 +53,7 @@ static string escape_hdlc_frame(const void* data, size_t size, uint32_t escape_c
|
||||
throw runtime_error("HDLC frame too small for start and end sentinels");
|
||||
}
|
||||
|
||||
StringReader r(data, size);
|
||||
phosg::StringReader r(data, size);
|
||||
if (r.pget_u8(size - 1) != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not end with 7E");
|
||||
}
|
||||
@@ -104,9 +104,9 @@ string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
||||
be_uint32_t be_addr = addr;
|
||||
char addr_str[INET_ADDRSTRLEN];
|
||||
if (!inet_ntop(AF_INET, &be_addr, addr_str, INET_ADDRSTRLEN)) {
|
||||
return string_printf("<UNKNOWN>:%hu", port);
|
||||
return phosg::string_printf("<UNKNOWN>:%hu", port);
|
||||
} else {
|
||||
return string_printf("%s:%hu", addr_str, port);
|
||||
return phosg::string_printf("%s:%hu", addr_str, port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ string IPStackSimulator::str_for_tcp_connection(shared_ptr<const IPClient> c, co
|
||||
string server_netloc_str = str_for_ipv4_netloc(conn.server_addr, conn.server_port);
|
||||
string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn.client_port);
|
||||
int fd = bufferevent_getfd(c->bev.get());
|
||||
return string_printf("%d+%016" PRIX64 " (%s -> %s)",
|
||||
return phosg::string_printf("%d+%016" PRIX64 " (%s -> %s)",
|
||||
fd, key, client_netloc_str.c_str(), server_netloc_str.c_str());
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ IPStackSimulator::~IPStackSimulator() {
|
||||
}
|
||||
|
||||
void IPStackSimulator::listen(const string& name, const string& socket_path, Protocol proto) {
|
||||
int fd = ::listen(socket_path, 0, SOMAXCONN);
|
||||
int fd = phosg::listen(socket_path, 0, SOMAXCONN);
|
||||
ip_stack_simulator_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, name.c_str());
|
||||
this->add_socket(name, fd, proto);
|
||||
}
|
||||
@@ -146,8 +146,8 @@ void IPStackSimulator::listen(const string& name, const string& addr, int port,
|
||||
if (port == 0) {
|
||||
this->listen(name, addr, proto);
|
||||
} else {
|
||||
int fd = ::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = render_netloc(addr, port);
|
||||
int fd = phosg::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = phosg::render_netloc(addr, port);
|
||||
ip_stack_simulator_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, name.c_str());
|
||||
this->add_socket(name, fd, proto);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ IPStackSimulator::IPClient::IPClient(
|
||||
ipv4_addr(0),
|
||||
idle_timeout_event(event_new(sim->base.get(), -1, EV_TIMEOUT, &IPStackSimulator::IPClient::dispatch_on_idle_timeout, this), event_free) {
|
||||
uint64_t idle_timeout_usecs = sim->state->client_idle_timeout_usecs;
|
||||
struct timeval tv = usecs_to_timeval(idle_timeout_usecs);
|
||||
struct timeval tv = phosg::usecs_to_timeval(idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &tv);
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ void IPStackSimulator::dispatch_on_listen_accept(
|
||||
|
||||
void IPStackSimulator::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
phosg::get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
if (this->state->banned_ipv4_ranges->check(remote_addr)) {
|
||||
close(fd);
|
||||
return;
|
||||
@@ -304,7 +304,7 @@ void IPStackSimulator::IPClient::on_client_input(struct bufferevent* bev) {
|
||||
}
|
||||
|
||||
uint64_t idle_timeout_usecs = sim ? sim->state->client_idle_timeout_usecs : 60000000;
|
||||
struct timeval tv = usecs_to_timeval(idle_timeout_usecs);
|
||||
struct timeval tv = phosg::usecs_to_timeval(idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &tv);
|
||||
|
||||
switch (this->protocol) {
|
||||
@@ -325,7 +325,7 @@ void IPStackSimulator::IPClient::on_client_input(struct bufferevent* bev) {
|
||||
sim->on_client_frame(this->shared_from_this(), frame);
|
||||
} catch (const exception& e) {
|
||||
if (ip_stack_simulator_log.warning("Failed to process frame: %s", e.what())) {
|
||||
print_data(stderr, frame);
|
||||
phosg::print_data(stderr, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,7 @@ void IPStackSimulator::IPClient::on_client_input(struct bufferevent* bev) {
|
||||
sim->on_client_frame(this->shared_from_this(), frame);
|
||||
} catch (const exception& e) {
|
||||
if (ip_stack_simulator_log.warning("Failed to process frame: %s", e.what())) {
|
||||
print_data(stderr, frame);
|
||||
phosg::print_data(stderr, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +416,7 @@ void IPStackSimulator::send_layer3_frame(shared_ptr<IPClient> c, FrameInfo::Prot
|
||||
evbuffer_add(out_buf, ðer, sizeof(ether));
|
||||
evbuffer_add(out_buf, data, size);
|
||||
if (this->pcap_text_log_file) {
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.write(ðer, sizeof(ether));
|
||||
w.write(data, size);
|
||||
this->log_frame(w.str());
|
||||
@@ -451,7 +451,7 @@ void IPStackSimulator::send_layer3_frame(shared_ptr<IPClient> c, FrameInfo::Prot
|
||||
throw logic_error("unknown layer 3 protocol");
|
||||
}
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put(hdlc);
|
||||
w.write(data, size);
|
||||
w.put_u16l(FrameInfo::computed_hdlc_checksum(w.str().data() + 1, w.size() - 1));
|
||||
@@ -459,7 +459,7 @@ void IPStackSimulator::send_layer3_frame(shared_ptr<IPClient> c, FrameInfo::Prot
|
||||
|
||||
string escaped = escape_hdlc_frame(w.str(), c->hdlc_escape_control_character_flags);
|
||||
if (ip_stack_simulator_log.debug("Sending HDLC frame to virtual network (escaped to %zX bytes)", escaped.size())) {
|
||||
print_data(stderr, w.str());
|
||||
phosg::print_data(stderr, w.str());
|
||||
}
|
||||
|
||||
if (c->protocol == Protocol::HDLC_TAPSERVER) {
|
||||
@@ -490,12 +490,12 @@ void IPStackSimulator::on_client_frame(shared_ptr<IPClient> c, const string& fra
|
||||
effective_data = &hdlc_unescaped_data;
|
||||
}
|
||||
if (ip_stack_simulator_log.debug("Virtual network sent frame")) {
|
||||
print_data(stderr, *effective_data);
|
||||
phosg::print_data(stderr, *effective_data);
|
||||
}
|
||||
this->log_frame(*effective_data);
|
||||
|
||||
FrameInfo fi(link_type, *effective_data);
|
||||
if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) {
|
||||
if (ip_stack_simulator_log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string fi_header = fi.header_str();
|
||||
ip_stack_simulator_log.debug("Frame header: %s", fi_header.c_str());
|
||||
}
|
||||
@@ -510,7 +510,7 @@ void IPStackSimulator::on_client_frame(shared_ptr<IPClient> c, const string& fra
|
||||
uint16_t expected_checksum = fi.computed_hdlc_checksum();
|
||||
uint16_t stored_checksum = fi.stored_hdlc_checksum();
|
||||
if (expected_checksum != stored_checksum) {
|
||||
throw runtime_error(string_printf(
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"HDLC checksum is incorrect (%04hX expected, %04hX received)",
|
||||
expected_checksum, stored_checksum));
|
||||
}
|
||||
@@ -533,7 +533,7 @@ void IPStackSimulator::on_client_frame(shared_ptr<IPClient> c, const string& fra
|
||||
} else if (fi.ipv4) {
|
||||
uint16_t expected_ipv4_checksum = fi.computed_ipv4_header_checksum();
|
||||
if (fi.ipv4->checksum != expected_ipv4_checksum) {
|
||||
throw runtime_error(string_printf(
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"IPv4 header checksum is incorrect (%04hX expected, %04hX received)",
|
||||
expected_ipv4_checksum, fi.ipv4->checksum.load()));
|
||||
}
|
||||
@@ -545,7 +545,7 @@ void IPStackSimulator::on_client_frame(shared_ptr<IPClient> c, const string& fra
|
||||
if (fi.udp) {
|
||||
uint16_t expected_udp_checksum = fi.computed_udp4_checksum();
|
||||
if (fi.udp->checksum != expected_udp_checksum) {
|
||||
throw runtime_error(string_printf(
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"UDP checksum is incorrect (%04hX expected, %04hX received)",
|
||||
expected_udp_checksum, fi.udp->checksum.load()));
|
||||
}
|
||||
@@ -554,7 +554,7 @@ void IPStackSimulator::on_client_frame(shared_ptr<IPClient> c, const string& fra
|
||||
} else if (fi.tcp) {
|
||||
uint16_t expected_tcp_checksum = fi.computed_tcp4_checksum();
|
||||
if (fi.tcp->checksum != expected_tcp_checksum) {
|
||||
throw runtime_error(string_printf(
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"TCP checksum is incorrect (%04hX expected, %04hX received)",
|
||||
expected_tcp_checksum, fi.tcp->checksum.load()));
|
||||
}
|
||||
@@ -576,7 +576,7 @@ void IPStackSimulator::on_client_lcp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
while (!opts_r.eof()) {
|
||||
uint8_t opt = opts_r.get_u8();
|
||||
string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||
StringReader opt_data_r(opt_data);
|
||||
phosg::StringReader opt_data_r(opt_data);
|
||||
switch (opt) {
|
||||
case 0x01: // Maximum receive unit
|
||||
// TODO: Currently we ignore this, but we probably should use it.
|
||||
@@ -593,7 +593,7 @@ void IPStackSimulator::on_client_lcp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
case 0x04: // Quality protocol
|
||||
case 0x07: // Protocol field compression
|
||||
case 0x08: // Address and control field compression
|
||||
throw runtime_error(string_printf("unimplemented LCP option %02hhX (%zu bytes)", opt, opt_data.size()));
|
||||
throw runtime_error(phosg::string_printf("unimplemented LCP option %02hhX (%zu bytes)", opt, opt_data.size()));
|
||||
default:
|
||||
throw runtime_error("unknown LCP option");
|
||||
}
|
||||
@@ -602,7 +602,7 @@ void IPStackSimulator::on_client_lcp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
// lazy to do this right now. In our situation, it should suffice to
|
||||
// simply always send a Configure-Request to the client with a magic
|
||||
// number not equal to the one we received.
|
||||
StringWriter opts_w;
|
||||
phosg::StringWriter opts_w;
|
||||
opts_w.put_u8(0x01); // Maximum receive unit
|
||||
opts_w.put_u8(0x04);
|
||||
opts_w.put_u16b(1500);
|
||||
@@ -615,7 +615,7 @@ void IPStackSimulator::on_client_lcp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
opts_w.put_u8(0x05); // Magic number (bitwise inverse of the remote end's)
|
||||
opts_w.put_u8(0x06);
|
||||
opts_w.put_u32b(~c->hdlc_remote_magic_number);
|
||||
StringWriter request_w;
|
||||
phosg::StringWriter request_w;
|
||||
request_w.put<LCPHeader>(LCPHeader{
|
||||
.command = 0x01, // Configure-Request
|
||||
.request_id = fi.lcp->request_id,
|
||||
@@ -624,7 +624,7 @@ void IPStackSimulator::on_client_lcp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
request_w.write(opts_w.str());
|
||||
this->send_layer3_frame(c, FrameInfo::Protocol::LCP, request_w.str());
|
||||
|
||||
StringWriter ack_w;
|
||||
phosg::StringWriter ack_w;
|
||||
ack_w.put<LCPHeader>(LCPHeader{
|
||||
.command = 0x02, // Configure-Ack
|
||||
.request_id = fi.lcp->request_id,
|
||||
@@ -679,7 +679,7 @@ void IPStackSimulator::on_client_pap_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
ip_stack_simulator_log.info("Client logged in with username \"%s\" and password", username.c_str());
|
||||
|
||||
static const string login_message = "newserv PPP simulator";
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put<PAPHeader>(PAPHeader{
|
||||
.command = 0x02, // Authenticate-Ack
|
||||
.request_id = fi.pap->request_id,
|
||||
@@ -698,11 +698,11 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
uint32_t remote_ip = 0;
|
||||
uint32_t remote_primary_dns = 0;
|
||||
uint32_t remote_secondary_dns = 0;
|
||||
StringWriter rejected_opts_w;
|
||||
phosg::StringWriter rejected_opts_w;
|
||||
while (!opts_r.eof()) {
|
||||
uint8_t opt = opts_r.get_u8();
|
||||
string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||
StringReader opt_data_r(opt_data);
|
||||
phosg::StringReader opt_data_r(opt_data);
|
||||
switch (opt) {
|
||||
case 0x01: // IP addresses (deprecated as of 1992; we don't support it at all)
|
||||
throw runtime_error("IPCP client sent IP-Addresses option");
|
||||
@@ -723,7 +723,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
case 0x82: // Primary NBNS server address
|
||||
case 0x84: // Secondary NBNS server address
|
||||
case 0x04: // Mobile IP address
|
||||
throw runtime_error(string_printf("unimplemented IPCP option %02hhX (%zu bytes)", opt, opt_data.size()));
|
||||
throw runtime_error(phosg::string_printf("unimplemented IPCP option %02hhX (%zu bytes)", opt, opt_data.size()));
|
||||
default:
|
||||
throw runtime_error("unknown IPCP option");
|
||||
}
|
||||
@@ -731,7 +731,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
|
||||
if (!rejected_opts_w.str().empty()) {
|
||||
// Send a Configure-Reject if the client specified IP header compression
|
||||
StringWriter reject_w;
|
||||
phosg::StringWriter reject_w;
|
||||
reject_w.put<IPCPHeader>(IPCPHeader{
|
||||
.command = 0x04, // Configure-Reject
|
||||
.request_id = fi.ipcp->request_id,
|
||||
@@ -745,7 +745,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
(remote_secondary_dns != 0x24242424)) {
|
||||
// Send a Configure-Nak if the client's request doesn't exactly match
|
||||
// what we want them to use.
|
||||
StringWriter opts_w;
|
||||
phosg::StringWriter opts_w;
|
||||
opts_w.put_u8(0x03); // IP address
|
||||
opts_w.put_u8(0x06);
|
||||
opts_w.put_u32b(0x1E1E1E1E);
|
||||
@@ -756,7 +756,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
opts_w.put_u8(0x06);
|
||||
opts_w.put_u32b(0x24242424);
|
||||
|
||||
StringWriter nak_w;
|
||||
phosg::StringWriter nak_w;
|
||||
nak_w.put<IPCPHeader>(IPCPHeader{
|
||||
.command = 0x03, // Configure-Nak
|
||||
.request_id = fi.ipcp->request_id,
|
||||
@@ -770,7 +770,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
|
||||
// As with LCP, we technically should implement the state machine, but I
|
||||
// continue to be lazy.
|
||||
StringWriter opts_w;
|
||||
phosg::StringWriter opts_w;
|
||||
opts_w.put_u8(0x03); // IP address
|
||||
opts_w.put_u8(0x06);
|
||||
opts_w.put_u32b(0x39393939);
|
||||
@@ -781,7 +781,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
opts_w.put_u8(0x06);
|
||||
opts_w.put_u32b(0x24242424);
|
||||
|
||||
StringWriter request_w;
|
||||
phosg::StringWriter request_w;
|
||||
request_w.put<IPCPHeader>(IPCPHeader{
|
||||
.command = 0x01, // Configure-Request
|
||||
.request_id = fi.ipcp->request_id,
|
||||
@@ -790,7 +790,7 @@ void IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPClient> c, const FrameI
|
||||
request_w.write(opts_w.str());
|
||||
this->send_layer3_frame(c, FrameInfo::Protocol::IPCP, request_w.str());
|
||||
|
||||
StringWriter ack_w;
|
||||
phosg::StringWriter ack_w;
|
||||
ack_w.put<IPCPHeader>(IPCPHeader{
|
||||
.command = 0x02, // Configure-Ack
|
||||
.request_id = fi.ipcp->request_id,
|
||||
@@ -841,7 +841,7 @@ void IPStackSimulator::on_client_arp_frame(
|
||||
reinterpret_cast<const uint8_t*>(fi.payload) + 6);
|
||||
}
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put<ARPHeader>(ARPHeader{
|
||||
.hardware_type = fi.arp->hardware_type,
|
||||
.protocol_type = fi.arp->protocol_type,
|
||||
@@ -942,7 +942,7 @@ void IPStackSimulator::on_client_udp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
throw runtime_error("client sent unknown DHCP command option");
|
||||
}
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
DHCPHeader r_dhcp;
|
||||
r_dhcp.opcode = 2; // Response
|
||||
r_dhcp.hardware_type = 1; // Ethernet
|
||||
@@ -1027,13 +1027,13 @@ void IPStackSimulator::on_client_udp_frame(shared_ptr<IPClient> c, const FrameIn
|
||||
r_udp.checksum = FrameInfo::computed_udp4_checksum(
|
||||
r_ipv4, r_udp, r_data.data(), r_data.size());
|
||||
|
||||
if (ip_stack_simulator_log.should_log(LogLevel::DEBUG)) {
|
||||
if (ip_stack_simulator_log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
|
||||
ip_stack_simulator_log.debug("Sending UDP response to %s", remote_str.c_str());
|
||||
print_data(stderr, r_data);
|
||||
phosg::print_data(stderr, r_data);
|
||||
}
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put(r_ipv4);
|
||||
w.put(r_udp);
|
||||
w.write(r_data);
|
||||
@@ -1079,7 +1079,7 @@ void IPStackSimulator::on_client_tcp_frame(
|
||||
throw runtime_error("TCP SYN contains extra flags");
|
||||
}
|
||||
|
||||
StringReader options_r(fi.tcp + 1, fi.tcp_options_size);
|
||||
phosg::StringReader options_r(fi.tcp + 1, fi.tcp_options_size);
|
||||
size_t max_frame_size = 1400;
|
||||
while (!options_r.eof()) {
|
||||
uint8_t option = options_r.get_u8();
|
||||
@@ -1135,7 +1135,7 @@ void IPStackSimulator::on_client_tcp_frame(
|
||||
conn.server_port = fi.tcp->dest_port;
|
||||
conn.client_port = fi.tcp->src_port;
|
||||
conn.next_client_seq = fi.tcp->seq_num + 1;
|
||||
conn.acked_server_seq = random_object<uint32_t>();
|
||||
conn.acked_server_seq = phosg::random_object<uint32_t>();
|
||||
conn.resend_push_usecs = DEFAULT_RESEND_PUSH_USECS;
|
||||
conn.next_push_max_frame_size = max_frame_size;
|
||||
conn.awaiting_first_ack = true;
|
||||
@@ -1240,7 +1240,7 @@ void IPStackSimulator::on_client_tcp_frame(
|
||||
// server immediately.
|
||||
} else if (fi.payload_size != 0) {
|
||||
|
||||
string conn_str = ip_stack_simulator_log.should_log(LogLevel::WARNING)
|
||||
string conn_str = ip_stack_simulator_log.should_log(phosg::LogLevel::WARNING)
|
||||
? this->str_for_tcp_connection(c, *conn)
|
||||
: "";
|
||||
|
||||
@@ -1285,7 +1285,7 @@ void IPStackSimulator::on_client_tcp_frame(
|
||||
conn_str.c_str());
|
||||
}
|
||||
if (was_logged) {
|
||||
print_data(stderr, payload, payload_size);
|
||||
phosg::print_data(stderr, payload, payload_size);
|
||||
}
|
||||
|
||||
// Send the new data to the server
|
||||
@@ -1388,7 +1388,7 @@ void IPStackSimulator::send_pending_push_frame(
|
||||
conn.acked_server_seq, bytes_to_send, pending_bytes);
|
||||
|
||||
this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn.pending_data.get(), bytes_to_send);
|
||||
struct timeval resend_push_timeout = usecs_to_timeval(conn.resend_push_usecs);
|
||||
struct timeval resend_push_timeout = phosg::usecs_to_timeval(conn.resend_push_usecs);
|
||||
event_add(conn.resend_push_event.get(), &resend_push_timeout);
|
||||
|
||||
// If the client isn't responding to our PSHes, back off exponentially up to
|
||||
@@ -1441,7 +1441,7 @@ void IPStackSimulator::send_tcp_frame(
|
||||
const void* linear_data = src_bytes ? evbuffer_pullup(src_buf, src_bytes) : nullptr;
|
||||
tcp.checksum = FrameInfo::computed_tcp4_checksum(ipv4, tcp, linear_data, src_bytes);
|
||||
|
||||
StringWriter w;
|
||||
phosg::StringWriter w;
|
||||
w.put(ipv4);
|
||||
w.put(tcp);
|
||||
if (src_bytes) {
|
||||
@@ -1488,7 +1488,7 @@ void IPStackSimulator::on_server_input(shared_ptr<IPClient> c, IPClient::TCPConn
|
||||
|
||||
auto sim = c->sim.lock();
|
||||
uint64_t idle_timeout_usecs = sim ? sim->state->client_idle_timeout_usecs : 60000000;
|
||||
struct timeval tv = usecs_to_timeval(idle_timeout_usecs);
|
||||
struct timeval tv = phosg::usecs_to_timeval(idle_timeout_usecs);
|
||||
event_add(c->idle_timeout_event.get(), &tv);
|
||||
|
||||
evbuffer_add_buffer(conn.pending_data.get(), buf);
|
||||
@@ -1534,8 +1534,7 @@ void IPStackSimulator::on_server_error(
|
||||
|
||||
void IPStackSimulator::log_frame(const string& data) const {
|
||||
if (this->pcap_text_log_file) {
|
||||
print_data(this->pcap_text_log_file, data, 0, nullptr,
|
||||
PrintDataFlags::SKIP_SEPARATOR);
|
||||
phosg::print_data(this->pcap_text_log_file, data, 0, nullptr, phosg::PrintDataFlags::SKIP_SEPARATOR);
|
||||
fputc('\n', this->pcap_text_log_file);
|
||||
fflush(this->pcap_text_log_file);
|
||||
}
|
||||
|
||||
+6
-6
@@ -4,10 +4,10 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
IPV4RangeSet::IPV4RangeSet(const JSON& json) {
|
||||
IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
||||
for (const auto& it : json.as_list()) {
|
||||
// String should be of the form a.b.c.d or a.b.c.d/e
|
||||
auto tokens = split(it->as_string(), '/');
|
||||
auto tokens = phosg::split(it->as_string(), '/');
|
||||
|
||||
size_t mask_bits;
|
||||
if (tokens.size() == 1) {
|
||||
@@ -21,7 +21,7 @@ IPV4RangeSet::IPV4RangeSet(const JSON& json) {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
|
||||
auto addr_tokens = split(tokens[0], '.');
|
||||
auto addr_tokens = phosg::split(tokens[0], '.');
|
||||
if (addr_tokens.size() != 4) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
}
|
||||
@@ -40,12 +40,12 @@ IPV4RangeSet::IPV4RangeSet(const JSON& json) {
|
||||
}
|
||||
}
|
||||
|
||||
JSON IPV4RangeSet::json() const {
|
||||
auto ret = JSON::list();
|
||||
phosg::JSON IPV4RangeSet::json() const {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (const auto& it : this->ranges) {
|
||||
uint32_t addr = it.first;
|
||||
uint8_t mask_bits = it.second;
|
||||
ret.emplace_back(string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
|
||||
ret.emplace_back(phosg::string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
|
||||
static_cast<uint8_t>((addr >> 24) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 16) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 8) & 0xFF),
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@
|
||||
class IPV4RangeSet {
|
||||
public:
|
||||
IPV4RangeSet() = default;
|
||||
explicit IPV4RangeSet(const JSON& json);
|
||||
explicit IPV4RangeSet(const phosg::JSON& json);
|
||||
|
||||
JSON json() const;
|
||||
phosg::JSON json() const;
|
||||
|
||||
bool check(uint32_t addr) const;
|
||||
bool check(const struct sockaddr_storage& ss) const;
|
||||
|
||||
+12
-12
@@ -184,7 +184,7 @@ int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||
}
|
||||
|
||||
string IntegralExpression::FlagLookupNode::str() const {
|
||||
return string_printf("F_%04hX", this->flag_index);
|
||||
return phosg::string_printf("F_%04hX", this->flag_index);
|
||||
}
|
||||
|
||||
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
|
||||
@@ -214,7 +214,7 @@ int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& e
|
||||
}
|
||||
|
||||
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
return phosg::string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
}
|
||||
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
|
||||
@@ -296,7 +296,7 @@ int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
|
||||
}
|
||||
|
||||
string IntegralExpression::ConstantNode::str() const {
|
||||
return string_printf("%" PRId64, this->value);
|
||||
return phosg::string_printf("%" PRId64, this->value);
|
||||
}
|
||||
|
||||
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
|
||||
@@ -340,16 +340,16 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
||||
// Check for binary operators at the root level
|
||||
using BinType = BinaryOperatorNode::Type;
|
||||
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
||||
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
||||
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
||||
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
||||
{{make_pair("&", BinType::BITWISE_AND)}},
|
||||
{{make_pair("^", BinType::BITWISE_XOR)}},
|
||||
{{make_pair("|", BinType::BITWISE_OR)}},
|
||||
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
||||
{{make_pair("||", BinType::LOGICAL_OR)}},
|
||||
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
||||
{{make_pair("|", BinType::BITWISE_OR)}},
|
||||
{{make_pair("^", BinType::BITWISE_XOR)}},
|
||||
{{make_pair("&", BinType::BITWISE_AND)}},
|
||||
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
||||
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
||||
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
||||
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
||||
};
|
||||
for (const auto& operators : binary_operator_levels) {
|
||||
size_t paren_level = 0;
|
||||
|
||||
+29
-24
@@ -36,7 +36,7 @@ ItemCreator::ItemCreator(
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
shared_ptr<const BattleRules> restrictions)
|
||||
: log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||
: log(phosg::string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||
logic_version(stack_limits->version),
|
||||
stack_limits(stack_limits),
|
||||
episode(episode),
|
||||
@@ -63,8 +63,8 @@ void ItemCreator::set_random_crypt(shared_ptr<PSOLFGEncryption> new_random_crypt
|
||||
void ItemCreator::set_section_id(uint8_t new_section_id) {
|
||||
if (this->section_id != new_section_id) {
|
||||
this->section_id = new_section_id;
|
||||
this->log.prefix = string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ",
|
||||
name_for_enum(stack_limits->version),
|
||||
this->log.prefix = phosg::string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ",
|
||||
phosg::name_for_enum(stack_limits->version),
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
@@ -85,27 +85,33 @@ bool ItemCreator::are_rare_drops_allowed() const {
|
||||
}
|
||||
|
||||
uint8_t ItemCreator::normalize_area_number(uint8_t area) const {
|
||||
if (!this->restrictions || (this->restrictions->box_drop_area == 0) || (area < 0x10) || (area > 0x11)) {
|
||||
if (this->restrictions && (this->restrictions->box_drop_area != 0)) {
|
||||
return this->restrictions->box_drop_area - 1;
|
||||
} else {
|
||||
switch (this->episode) {
|
||||
case Episode::EP1:
|
||||
if (area >= 0x0F) {
|
||||
if (area >= 0x11) {
|
||||
throw runtime_error("invalid Episode 1 area number");
|
||||
}
|
||||
switch (area) {
|
||||
case 11:
|
||||
return 2; // Dragon -> Cave 1
|
||||
case 12:
|
||||
return 5; // De Rol Le -> Mine 1
|
||||
case 13:
|
||||
return 7; // Vol Opt -> Ruins 1
|
||||
case 14:
|
||||
return 9; // Dark Falz -> Ruins 3
|
||||
case 0x0B: // Dragon -> Cave 1
|
||||
return 2;
|
||||
case 0x0C: // De Rol Le -> Mine 1
|
||||
return 5;
|
||||
case 0x0D: // Vol Opt -> Ruins 1
|
||||
return 7;
|
||||
case 0x0E: // Dark Falz -> Ruins 3
|
||||
case 0x10: // Palace -> Ruins 3
|
||||
case 0x11: // Spaceship -> Ruins 3
|
||||
return 9;
|
||||
case 0x0F: // Lobby
|
||||
throw runtime_error("visual lobby does not have item drop tables");
|
||||
default:
|
||||
return area - 1;
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
case Episode::EP2: {
|
||||
static const vector<uint8_t> area_subs = {
|
||||
static const array<uint8_t, 0x11> area_subs = {
|
||||
0x00, // 13 (VR Temple Alpha)
|
||||
0x01, // 14 (VR Temple Beta)
|
||||
0x02, // 15 (VR Spaceship Alpha)
|
||||
@@ -127,7 +133,7 @@ uint8_t ItemCreator::normalize_area_number(uint8_t area) const {
|
||||
if ((area >= 0x13) && (area < 0x24)) {
|
||||
return area_subs.at(area - 0x13);
|
||||
}
|
||||
return area - 1;
|
||||
throw runtime_error("invalid Episode 2 area number");
|
||||
}
|
||||
case Episode::EP4:
|
||||
if (area >= 0x24 && area < 0x2D) {
|
||||
@@ -137,9 +143,6 @@ uint8_t ItemCreator::normalize_area_number(uint8_t area) const {
|
||||
default:
|
||||
throw logic_error("invalid episode number");
|
||||
}
|
||||
|
||||
} else {
|
||||
return this->restrictions->box_drop_area - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +212,9 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop_with_area_norm(uint8_t are
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_monster_item_drop_with_area_norm(uint32_t enemy_type, uint8_t area_norm) {
|
||||
if (enemy_type > 0x58) {
|
||||
// Note: The original GC implementation uses (enemy_type > 0x58) here; we
|
||||
// extend it to the full array size for BB
|
||||
if (enemy_type >= 0x64) {
|
||||
this->log.warning("Invalid enemy type: %" PRIX32, enemy_type);
|
||||
return DropResult();
|
||||
}
|
||||
@@ -298,13 +303,13 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area_nor
|
||||
for (const auto& spec : rare_specs) {
|
||||
item = this->check_rate_and_create_rare_item(spec, area_norm);
|
||||
if (!item.empty()) {
|
||||
if (this->log.should_log(LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Box spec %08" PRIX32 " produced item %s", spec.probability, hex.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this->log.should_log(LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Box spec %08" PRIX32 " did not produce item %s", spec.probability, hex.c_str());
|
||||
}
|
||||
@@ -359,13 +364,13 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_
|
||||
for (const auto& spec : rare_specs) {
|
||||
item = this->check_rate_and_create_rare_item(spec, area_norm);
|
||||
if (!item.empty()) {
|
||||
if (this->log.should_log(LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Enemy spec %08" PRIX32 " produced item %s", spec.probability, hex.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this->log.should_log(LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Enemy spec %08" PRIX32 " did not produce item %s", spec.probability, hex.c_str());
|
||||
}
|
||||
@@ -553,7 +558,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
break;
|
||||
case BattleRules::WeaponAndArmorMode::FORBID_RARES:
|
||||
if (this->item_parameter_table->is_item_rare(item)) {
|
||||
this->log.info("Restricted: rare items not allowed");
|
||||
this->log.info("Restricted: rare weapons and armors not allowed");
|
||||
item.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ public:
|
||||
void set_section_id(uint8_t new_section_id);
|
||||
|
||||
private:
|
||||
PrefixedLogger log;
|
||||
phosg::PrefixedLogger log;
|
||||
Version logic_version;
|
||||
std::shared_ptr<const ItemData::StackLimits> stack_limits;
|
||||
Episode episode;
|
||||
|
||||
+17
-13
@@ -21,7 +21,7 @@ ItemData::StackLimits::StackLimits(
|
||||
max_tool_stack_sizes_by_data1_1(max_tool_stack_sizes_by_data1_1),
|
||||
max_meseta_stack_size(max_meseta_stack_size) {}
|
||||
|
||||
ItemData::StackLimits::StackLimits(Version version, const JSON& json)
|
||||
ItemData::StackLimits::StackLimits(Version version, const phosg::JSON& json)
|
||||
: version(version) {
|
||||
this->max_tool_stack_sizes_by_data1_1.clear();
|
||||
for (const auto& limit_json : json.at("ToolLimits").as_list()) {
|
||||
@@ -53,8 +53,8 @@ ItemData::ItemData(const ItemData& other) {
|
||||
|
||||
ItemData::ItemData(uint64_t first, uint64_t second) {
|
||||
*reinterpret_cast<be_uint64_t*>(&this->data1[0]) = first;
|
||||
this->data1d[2] = bswap32((second >> 32) & 0xFFFFFFFF);
|
||||
this->data2d = bswap32(second & 0xFFFFFFFF);
|
||||
this->data1d[2] = phosg::bswap32((second >> 32) & 0xFFFFFFFF);
|
||||
this->data2d = phosg::bswap32(second & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
ItemData& ItemData::operator=(const ItemData& other) {
|
||||
@@ -142,18 +142,22 @@ bool ItemData::is_wrapped(const StackLimits& limits) const {
|
||||
}
|
||||
}
|
||||
|
||||
void ItemData::wrap(const StackLimits& limits) {
|
||||
void ItemData::wrap(const StackLimits& limits, uint8_t present_color) {
|
||||
switch (this->data1[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
this->data1[4] |= 0x40;
|
||||
this->data1[5] = (this->data1[5] & 0xF0) | (present_color & 0x0F);
|
||||
break;
|
||||
case 1:
|
||||
this->data1[4] = (this->data1[4] & 0xF0) | 0x40 | (present_color & 0x0F);
|
||||
break;
|
||||
case 2:
|
||||
// Mags cannot have custom present colors
|
||||
this->data2[2] |= 0x40;
|
||||
break;
|
||||
case 3:
|
||||
if (!this->is_stackable(limits)) {
|
||||
this->data1[3] |= 0x40;
|
||||
this->data1[3] = (this->data1[3] & 0xF0) | 0x40 | (present_color & 0x0F);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
@@ -167,14 +171,14 @@ void ItemData::unwrap(const StackLimits& limits) {
|
||||
switch (this->data1[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
this->data1[4] &= 0xBF;
|
||||
this->data1[4] &= 0xB0;
|
||||
break;
|
||||
case 2:
|
||||
this->data2[2] &= 0xBF;
|
||||
this->data2[2] &= 0xB0;
|
||||
break;
|
||||
case 3:
|
||||
if (!this->is_stackable(limits)) {
|
||||
this->data1[3] &= 0xBF;
|
||||
this->data1[3] &= 0xB0;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
@@ -400,7 +404,7 @@ void ItemData::decode_for_version(Version from_version) {
|
||||
// data2d field, since internally it's actually a uint32_t. We treat it
|
||||
// as individual bytes instead, so we correct for the client's
|
||||
// byteswapping here.
|
||||
this->data2d = bswap32(this->data2d);
|
||||
this->data2d = phosg::bswap32(this->data2d);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -482,7 +486,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
|
||||
this->data2w[1] = this->data2[0] | ((this->data2[2] << 15) & 0x8000);
|
||||
this->data2w[0] = this->data2[1];
|
||||
} else if (is_big_endian(to_version)) {
|
||||
this->data2d = bswap32(this->data2d);
|
||||
this->data2d = phosg::bswap32(this->data2d);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -743,12 +747,12 @@ ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t p
|
||||
}
|
||||
|
||||
string ItemData::hex() const {
|
||||
return string_printf("%08" PRIX32 " %08" PRIX32 " %08" PRIX32 " (%08" PRIX32 ") %08" PRIX32,
|
||||
return phosg::string_printf("%08" PRIX32 " %08" PRIX32 " %08" PRIX32 " (%08" PRIX32 ") %08" PRIX32,
|
||||
this->data1db[0].load(), this->data1db[1].load(), this->data1db[2].load(), this->id.load(), this->data2db.load());
|
||||
}
|
||||
|
||||
string ItemData::short_hex() const {
|
||||
auto ret = string_printf("%08" PRIX32 "%08" PRIX32 "%08" PRIX32 "%08" PRIX32,
|
||||
auto ret = phosg::string_printf("%08" PRIX32 "%08" PRIX32 "%08" PRIX32 "%08" PRIX32,
|
||||
this->data1db[0].load(), this->data1db[1].load(), this->data1db[2].load(), this->data2db.load());
|
||||
size_t offset = ret.find_last_not_of('0');
|
||||
if (offset != string::npos) {
|
||||
|
||||
+7
-6
@@ -62,7 +62,7 @@ struct ItemData {
|
||||
uint32_t max_meseta_stack_size;
|
||||
|
||||
StackLimits(Version version, const std::vector<uint8_t>& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size);
|
||||
StackLimits(Version version, const JSON& json);
|
||||
StackLimits(Version version, const phosg::JSON& json);
|
||||
StackLimits(const StackLimits& other) = default;
|
||||
StackLimits(StackLimits&& other) = default;
|
||||
StackLimits& operator=(const StackLimits& other) = default;
|
||||
@@ -77,20 +77,19 @@ struct ItemData {
|
||||
|
||||
// QUICK ITEM FORMAT REFERENCE
|
||||
// data1/0 data1/4 data1/8 data2
|
||||
// Weapon: 00ZZZZGG SS00AABB AABBAABB 00000000
|
||||
// Weapon: 00ZZZZGG SSNNAABB AABBAABB 00000000
|
||||
// Armor: 0101ZZ00 FFTTDDDD EEEE0000 00000000
|
||||
// Shield: 0102ZZ00 FFTTDDDD EEEE0000 00000000
|
||||
// Unit: 0103ZZ00 FF00RRRR 00000000 00000000
|
||||
// Mag: 02ZZLLWW HHHHIIII JJJJKKKK YYQQPPVV
|
||||
// Tool: 03ZZZZFF 00CC0000 00000000 00000000
|
||||
// Tool: 03ZZZZUU 00CC0000 00000000 00000000
|
||||
// Meseta: 04000000 00000000 00000000 MMMMMMMM
|
||||
// 01034D00 00000000 204E0000
|
||||
// A = attribute type (for S-ranks, custom name)
|
||||
// B = attribute amount (for S-ranks, custom name)
|
||||
// C = stack size (for tools)
|
||||
// D = DEF bonus
|
||||
// E = EVP bonus
|
||||
// F = flags (40=present; for tools, unused if item is stackable)
|
||||
// F = armor/shield/unit flags (40=present; low 16 bits are present color)
|
||||
// G = weapon grind
|
||||
// H = mag DEF
|
||||
// I = mag POW
|
||||
@@ -98,11 +97,13 @@ struct ItemData {
|
||||
// K = mag MIND
|
||||
// L = mag level
|
||||
// M = meseta amount
|
||||
// N = present color (weapon only; for other types this is in the flags field)
|
||||
// P = mag flags (40=present, 04=has left pb, 02=has right pb, 01=has center pb)
|
||||
// Q = mag IQ
|
||||
// R = unit modifier (little-endian)
|
||||
// S = weapon flags (80=unidentified, 40=present) and special (low 6 bits)
|
||||
// T = slot count
|
||||
// U = tool flags (40=present; unused if item is stackable)
|
||||
// V = mag color
|
||||
// W = photon blasts
|
||||
// Y = mag synchro
|
||||
@@ -151,7 +152,7 @@ struct ItemData {
|
||||
uint32_t primary_identifier() const;
|
||||
|
||||
bool is_wrapped(const StackLimits& limits) const;
|
||||
void wrap(const StackLimits& limits);
|
||||
void wrap(const StackLimits& limits, uint8_t present_color);
|
||||
void unwrap(const StackLimits& limits);
|
||||
|
||||
bool is_stackable(const StackLimits& limits) const;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user