Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sunrise/Sunrise.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@
<ClCompile Include="src\middleware\web_service\messages\opcode702_codec.cpp" />
<ClCompile Include="src\middleware\web_service\messages\opcode406_codec.cpp" />
<ClCompile Include="src\middleware\web_service\messages\opcode1820_codec.cpp" />
<ClCompile Include="src\middleware\web_service\messages\opcode501_request_codec.cpp" />
<ClCompile Include="src\middleware\web_service\messages\opcode502.cpp" />
<ClCompile Include="src\state\runtime\state_account_runtime.cpp" />
<ClCompile Include="src\state\runtime\state_account_roster_runtime.cpp" />
<ClCompile Include="src\state\runtime\state_account_acquisition_runtime.cpp" />
<ClCompile Include="src\state\runtime\state_account_reward_grant_runtime.cpp" />
<ClCompile Include="src\state\runtime\state_account_dismantle_runtime.cpp" />
Expand Down Expand Up @@ -1765,6 +1768,8 @@
<ClInclude Include="src\middleware\web_service\messages\opcode503.h" />
<ClInclude Include="src\middleware\web_service\messages\opcode206.h" />
<ClInclude Include="src\middleware\web_service\messages\opcode501_codec.h" />
<ClInclude Include="src\middleware\web_service\messages\opcode501_request_codec.h" />
<ClInclude Include="src\middleware\web_service\messages\opcode502.h" />
<ClInclude Include="src\middleware\web_service\messages\opcode505\opcode505_codec.h" />
<ClInclude Include="src\middleware\web_service\messages\opcode601\opcode601_codec.h" />
<ClInclude Include="src\middleware\datagen\family4\account\selection_patch\account_selection_patch_encoder.h" />
Expand Down
2 changes: 1 addition & 1 deletion Sunrise/resources/database/investment_defaults.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
INSERT INTO account VALUES (1, -7013740688821976832, 1);
INSERT INTO characters VALUES (0, -7013740688821976831, 0, 0, 1, 50, 1, 1.0, 308080871, 1, 65535, -1, 57);
INSERT INTO characters VALUES (0, -7013740688821976831, 0, 0, 1, 50, 1, 1.0, 308080871, 1, 65535, -1, 57, X'000000000000000000000000000000000000000000000000000000000000000000000000', 0);
INSERT INTO items VALUES (0, 0, 0, 4611686018427387905, 3843477312, 106, 1, 0, 0, 1, 12, 4, 7, 10, 11, 2, 0);
INSERT INTO sockets VALUES (4611686018427387905, 0, 2874284214);
INSERT INTO sockets VALUES (4611686018427387905, 1, 3433396027);
Expand Down
6 changes: 4 additions & 2 deletions Sunrise/resources/database/investment_schema.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PRAGMA application_id = 1397902921;
PRAGMA user_version = 2;
PRAGMA user_version = 3;

CREATE TABLE account (
id INTEGER PRIMARY KEY CHECK (id = 1),
Expand All @@ -20,7 +20,9 @@ CREATE TABLE characters (
content_bypass INTEGER NOT NULL CHECK (content_bypass IN (0, 1)),
equipped_title INTEGER NOT NULL CHECK (equipped_title BETWEEN 0 AND 65535),
acquired_subclass_mask INTEGER NOT NULL,
next_inventory_serial INTEGER NOT NULL CHECK (next_inventory_serial BETWEEN 0 AND 4294967295)
next_inventory_serial INTEGER NOT NULL CHECK (next_inventory_serial BETWEEN 0 AND 4294967295),
appearance_header BLOB NOT NULL CHECK (length(appearance_header) = 36),
appearance_header_valid INTEGER NOT NULL CHECK (appearance_header_valid IN (0, 1))
) STRICT;

CREATE TABLE items (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ constexpr std::size_t kCardFlagOffset = 8;
static_cast<std::int8_t>(character.gender),
static_cast<std::int8_t>(character.characterClass),
};
std::memcpy(identity.headerBlock.data(),
layout::kHeaderBlockBytes.data(),
layout::kHeaderBlockBytes.size());
const auto& headerSource =
character.appearanceHeaderValid ? character.appearanceHeader : layout::kHeaderBlockBytes;
std::memcpy(identity.headerBlock.data(), headerSource.data(), headerSource.size());

output = {};
appearance::apply_sentinels(output);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,10 @@ bool encode(const state::CharacterState& state,
object.identity.race = static_cast<std::int8_t>(state.race);
object.identity.gender = static_cast<std::int8_t>(state.gender);
object.identity.characterClass = static_cast<std::int8_t>(state.characterClass);
std::memcpy(object.customisationHeader.data(),
character_record::layout::kHeaderBlockBytes.data(),
character_record::layout::kHeaderBlockBytes.size());
const auto& headerSource = state.appearanceHeaderValid
? state.appearanceHeader
: character_record::layout::kHeaderBlockBytes;
std::memcpy(object.customisationHeader.data(), headerSource.data(), headerSource.size());
object.lastOrbitedDestination = state.lastOrbitedDestination;
// No previous activity is tracked, and a zero here would name the orbit activity.
object.previousActivityIndex = kAbsentActivityIndex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ constexpr std::uint8_t kCharacterSoidWidth = 64;

} // namespace

/** Writes the echoed header, the shared status pair, the character id, and the trailer. */
/** Writes the echoed header, the caller's status pair, the character id, and the trailer. */
bool encode_response(const Message& message,
const StatusResponse& status,
std::uint64_t characterSoid,
std::span<std::byte> output,
std::size_t& written) noexcept {
Expand All @@ -26,9 +27,6 @@ bool encode_response(const Message& message,
message.transactionId);

encoding::bits::Writer writer(output.subspan(kEnvelopeHeaderSize));
// The reply names a character the roster already holds, so no revision publishes it.
StatusResponse status{};
status.value = kNoFamily4Publication;
// The id carries no presence bit or bias.
bool encoded = status::write_fields(writer, ResponseShape::statusPair, status)
&& writer.write(characterSoid, kCharacterSoidWidth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ inline constexpr std::uint16_t kOpcode = 501;

/**
* Encodes the create-character response: the status pair then the character object id.
* The status is the caller's, because this opcode has no single right answer. A refusal has to
* say so in the code -- the SOID field alone cannot, since zero is what a refusal would send and
* the Client reads the code, not the id -- and the value feeds the Family-4 version wait, which
* needs the revision a staged publication will carry, or kNoFamily4Publication when none will.
* @param message Parsed request whose envelope fields are echoed.
* @param characterSoid Character object id; must be published in family 3.
* @param status Logical status pair: refusal code, and the revision the wait should expect.
* @param characterSoid Character object id; must be published in family 3. Zero on a refusal.
* @param output Caller-owned svc-11 response-body storage.
* @param written Receives encoded response-body bytes.
* @return True when the fixed response fits the output buffer.
*/
[[nodiscard]] bool encode_response(const Message& message,
const StatusResponse& status,
std::uint64_t characterSoid,
std::span<std::byte> output,
std::size_t& written) noexcept;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include "opcode501_request_codec.h"

#include "../../encoding/bit_reader.h"

namespace sunrise::middleware::web_service::messages::opcode501 {
namespace {

/** The identity triple and appearance header end here; the 64-byte name follows. */
constexpr std::size_t kIdentityAndHeaderBits = 261;

constexpr std::uint64_t kByteBias = 0x80U;
constexpr std::uint64_t kWordBias = 0x8000U;

/** Writes the record's little-endian byte order, which is not the wire's bit order. */
void put8(state::AppearanceHeader& header, std::size_t offset, std::uint64_t value) noexcept {
header[offset] = static_cast<std::uint8_t>(value & 0xFFU);
}

void put16(state::AppearanceHeader& header, std::size_t offset, std::uint64_t value) noexcept {
header[offset] = static_cast<std::uint8_t>(value & 0xFFU);
header[offset + 1U] = static_cast<std::uint8_t>((value >> 8U) & 0xFFU);
}

void put32(state::AppearanceHeader& header, std::size_t offset, std::uint64_t value) noexcept {
put16(header, offset, value & 0xFFFFU);
put16(header, offset + 2U, (value >> 16U) & 0xFFFFU);
}

/**
* Reads one 1-bit presence flag followed by an 8-bit biased value.
* @return True when the field is present and readable. An absent field fails closed: nothing on
* the wire carries the value that was skipped, so there is nothing to fall back to.
*/
[[nodiscard]] bool read_optional_byte(encoding::bits::Reader& reader, std::uint8_t& value) noexcept {
std::uint64_t present = 0;
if (!reader.read(1, present) || present == 0) {
return false;
}
std::uint64_t raw = 0;
if (!reader.read(8, raw)) {
return false;
}
value = static_cast<std::uint8_t>(raw - kByteBias);
return true;
}

/** Reads an 8-bit +0x80-biased header field straight into the record block. */
[[nodiscard]] bool
copy_byte(encoding::bits::Reader& reader, state::AppearanceHeader& header, std::size_t offset) noexcept {
std::uint64_t raw = 0;
if (!reader.read(8, raw)) {
return false;
}
put8(header, offset, raw - kByteBias);
return true;
}

/** Reads a 16-bit +0x8000-biased header field straight into the record block. */
[[nodiscard]] bool
copy_word(encoding::bits::Reader& reader, state::AppearanceHeader& header, std::size_t offset) noexcept {
std::uint64_t raw = 0;
if (!reader.read(16, raw)) {
return false;
}
put16(header, offset, raw - kWordBias);
return true;
}

/**
* Byte offsets of every appearance-header field inside the 36-byte record block. The gaps at 5,
* 26 and 33 are the struct's own alignment padding and stay zero.
*/
constexpr std::size_t kOffsetF0 = 0;
constexpr std::size_t kOffsetF2 = 2;
constexpr std::size_t kOffsetF3 = 3;
constexpr std::size_t kOffsetF4 = 4;
constexpr std::size_t kOffsetF6 = 6;
constexpr std::size_t kOffsetF8 = 8;
constexpr std::size_t kOffsetF10 = 10;
constexpr std::size_t kOffsetF12 = 12;
constexpr std::size_t kOffsetF18 = 18;
constexpr std::size_t kOffsetF24 = 24;
constexpr std::size_t kOffsetF28 = 28;
constexpr std::size_t kOffsetF32 = 32;

/** Both triples of 16-bit customization indices hold 3 entries. */
constexpr std::size_t kIndexTripleCount = 3;
/** Each entry in those triples is 2 bytes wide in the record block. */
constexpr std::size_t kIndexTripleStride = 2;

/** The record requires 1 in the trailing field; the request always carries 0 there. */
constexpr std::uint64_t kRecordTrailingField = 1;

/** Highest valid race, gender and class wire values, matching the state enums. */
constexpr std::uint8_t kMaxRace = 2;
constexpr std::uint8_t kMaxGender = 1;
constexpr std::uint8_t kMaxClass = 2;

} // namespace

bool parse_request(const Message& message, Request& request) noexcept {
request = {};
if (message.opcode != kOpcode || message.payload.size() * 8U < kIdentityAndHeaderBits) {
return false;
}
encoding::bits::Reader reader(message.payload);
if (!read_optional_byte(reader, request.race) || !read_optional_byte(reader, request.gender)
|| !read_optional_byte(reader, request.characterClass)) {
return false;
}
if (request.race > kMaxRace || request.gender > kMaxGender
|| request.characterClass > kMaxClass) {
return false;
}

auto& header = request.appearanceHeader;
if (!copy_word(reader, header, kOffsetF0) || !copy_byte(reader, header, kOffsetF2)
|| !copy_byte(reader, header, kOffsetF3) || !copy_byte(reader, header, kOffsetF4)
|| !copy_word(reader, header, kOffsetF6) || !copy_word(reader, header, kOffsetF8)
|| !copy_word(reader, header, kOffsetF10)) {
return false;
}
for (std::size_t index = 0; index < kIndexTripleCount; ++index) {
if (!copy_word(reader, header, kOffsetF12 + index * kIndexTripleStride)) {
return false;
}
}
for (std::size_t index = 0; index < kIndexTripleCount; ++index) {
if (!copy_word(reader, header, kOffsetF18 + index * kIndexTripleStride)) {
return false;
}
}
if (!copy_word(reader, header, kOffsetF24)) {
return false;
}
std::uint64_t emptyHashSentinel = 0;
if (!reader.read(32, emptyHashSentinel)) {
return false;
}
put32(header, kOffsetF28, emptyHashSentinel);

// Read but not copied: the request's own value is 0 and the record rejects it.
std::uint64_t requestTrailingField = 0;
if (!reader.read(2, requestTrailingField)) {
return false;
}
put8(header, kOffsetF32, kRecordTrailingField);
return true;
}

} // namespace sunrise::middleware::web_service::messages::opcode501
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <cstdint>

#include "../../../state/account/account_state.h"
#include "../web_service_envelope.h"
#include "opcode501_codec.h"

namespace sunrise::middleware::web_service::messages::opcode501 {

/** The create-character request: the identity triple plus the authored appearance header. */
struct Request {
std::uint8_t characterClass{};
std::uint8_t gender{};
std::uint8_t race{};
/**
* The appearance header in record byte order, ready to memcpy into either character
* encoder's block. Its values index per-race customization tables, so it is only
* meaningful beside the `race` it arrived with.
*/
state::AppearanceHeader appearanceHeader{};
};

/**
* Parses the create-character request body.
*
* The payload is a big-endian bitstream, not a byte-aligned struct: every field is written at
* whatever bit offset the previous one ended on. Wire order is depth-first over
* `{ int8 triple[3]; CharHeader header; char name[64]; }`, where `triple` is race, gender and
* class in that order. Each `triple` entry is a 1-bit presence flag followed by 8 value bits
* biased by +0x80; the header's fields are 8, 16 or 32 bits biased by +0x80, +0x8000 or not at
* all; and the 64-byte name begins at bit 261. This parser consumes the first 261 bits and
* ignores the name, which every observed capture leaves as the placeholder "Player".
*
* The header is emitted in the record's own little-endian struct layout rather than as parsed
* fields, since both character encoders want it as an opaque block. Its last field is the one
* value that is not copied through: the request carries 0 where the record requires 1.
*
* @param message Parsed Web Service envelope.
* @param request Receives the parsed creation fields.
* @return True when the payload holds all 261 bits, every presence flag is set, and race,
* gender and class are all in range.
*/
[[nodiscard]] bool parse_request(const Message& message, Request& request) noexcept;

} // namespace sunrise::middleware::web_service::messages::opcode501
22 changes: 22 additions & 0 deletions Sunrise/src/middleware/web_service/messages/opcode502.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "opcode502.h"

#include "../../encoding/bit_reader.h"

namespace sunrise::middleware::web_service::messages::opcode502 {
namespace {

constexpr std::uint8_t kCharacterSoidWidth = 64;
constexpr std::size_t kMinimumPayloadSize = 8;

} // namespace

bool parse_request(const Message& message, Request& request) noexcept {
request = {};
if (message.opcode != kOpcode || message.payload.size() < kMinimumPayloadSize) {
return false;
}
encoding::bits::Reader reader(message.payload);
return reader.read(kCharacterSoidWidth, request.characterSoid);
}

} // namespace sunrise::middleware::web_service::messages::opcode502
25 changes: 25 additions & 0 deletions Sunrise/src/middleware/web_service/messages/opcode502.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <cstdint>

#include "../web_service_envelope.h"

namespace sunrise::middleware::web_service::messages::opcode502 {

/** Web Service opcode for the delete-character request. */
inline constexpr std::uint16_t kOpcode = 502;

/** The targeted character. */
struct Request {
std::uint64_t characterSoid{};
};

/**
* Parses the bare 64-bit targeted-character id.
* @param message Parsed Web Service envelope.
* @param request Receives the targeted character id.
* @return True when the whole id is present.
*/
[[nodiscard]] bool parse_request(const Message& message, Request& request) noexcept;

} // namespace sunrise::middleware::web_service::messages::opcode502
Loading