add a bit more of dc nte

This commit is contained in:
Martin Michelsen
2022-10-29 19:27:44 -07:00
parent 52625aed9c
commit 2ce9e58177
7 changed files with 106 additions and 36 deletions
+2 -1
View File
@@ -47,7 +47,7 @@ Current known issues / missing features / things to do:
newserv supports several versions of PSO. Specifically:
| Version | Basic commands | Lobbies | Games | Proxy |
|----------------------|----------------|---------------|---------------|---------------|
| Dreamcast Trial | Not supported | Not supported | Not supported | Not supported |
| Dreamcast Trial | Partial (6) | Not supported | Not supported | Not supported |
| Dreamcast V1 | Supported (1) | Supported | Supported | Supported |
| Dreamcast V2 | Supported (1) | Supported | Supported | Supported |
| PC | Supported | Supported | Supported | Supported |
@@ -65,6 +65,7 @@ newserv supports several versions of PSO. Specifically:
3. *Episode 3 players can download quests, join lobbies, create and join games, and trade cards, but CARD battles are not implemented yet. Tournaments are also not supported.*
4. *newserv's implementation of PSOX is based on disassembly of the client executable; it has never been tested with a real client and most likely doesn't work.*
5. *Some basic features are not implemented in Blue Burst games, so the games are not very playable. A lot of work has to be done to get BB games to a playable state.*
6. *Support for PSO Dreamcast Trial Edition is very incomplete and probably never will be complete. This is really just exploring a curiosity that sheds some light on early network engineering done by Sega, not an actual attempt at supporting this version of the game.*
## Usage
+1 -1
View File
@@ -610,7 +610,7 @@ static void server_command_convert_char_to_bb(shared_ptr<ServerState> s,
// Request the player data. The client will respond with a 61, and the handler
// for that command will execute the conversion
send_command(c, 0x95, 0x00);
send_get_player_info(c);
}
////////////////////////////////////////////////////////////////////////////////
+8 -1
View File
@@ -1268,11 +1268,18 @@ struct C_Login_DCNTE_8B {
ptext<char, 0x30> password;
ptext<char, 0x10> name;
parray<uint8_t, 2> unused;
};
struct C_LoginExtended_DCNTE_8B : C_Login_DCNTE_8B {
SC_MeetUserExtension<char> extension;
};
// 8C: Invalid command
// 8D: Invalid command
// 8D (S->C): Request player data (DC NTE only)
// Behaves the same as 95 (S->C) on all other versions. DC NTE crashes if it
// receives 95, so this is used instead.
// 8E: Invalid command
// 8F: Invalid command
+12 -8
View File
@@ -68,19 +68,23 @@ namespace ProxyOptionsMenuItemID {
struct MenuItem {
enum Flag {
INVISIBLE_ON_DC = 0x01,
INVISIBLE_ON_PC = 0x02,
INVISIBLE_ON_GC = 0x04,
INVISIBLE_ON_XB = 0x08,
INVISIBLE_ON_BB = 0x10,
// For menu items to be visible on DCNTE, they must not have either of the
// following two flags. (The INVISIBLE_ON_GCNTE flag behaves similarly.)
INVISIBLE_ON_DCNTE = 0x001,
INVISIBLE_ON_DC = 0x002,
INVISIBLE_ON_PC = 0x004,
INVISIBLE_ON_GC_TRIAL_EDITION = 0x008,
INVISIBLE_ON_GC = 0x010,
INVISIBLE_ON_XB = 0x020,
INVISIBLE_ON_BB = 0x040,
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_XB | INVISIBLE_ON_BB,
XB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_XB,
REQUIRES_MESSAGE_BOXES = 0x20,
REQUIRES_SEND_FUNCTION_CALL = 0x40,
REQUIRES_SAVE_DISABLED = 0x80,
REQUIRES_MESSAGE_BOXES = 0x080,
REQUIRES_SEND_FUNCTION_CALL = 0x100,
REQUIRES_SAVE_DISABLED = 0x200,
};
uint32_t item_id;
+27 -7
View File
@@ -336,7 +336,8 @@ static void on_login_8_dcnte(shared_ptr<ServerState> s, shared_ptr<Client> c,
static void on_login_b_dcnte(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 8B
const auto& cmd = check_size_t<C_Login_DCNTE_8B>(data);
const auto& cmd = check_size_t<C_Login_DCNTE_8B>(data,
sizeof(C_Login_DCNTE_8B), sizeof(C_LoginExtended_DCNTE_8B));
c->channel.version = GameVersion::DC;
c->flags |= flags_for_version(c->version(), -1);
c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_TRIAL_EDITION;
@@ -346,7 +347,7 @@ static void on_login_b_dcnte(shared_ptr<ServerState> s, shared_ptr<Client> c,
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
c->set_license(l);
send_command(c, 0x8B, 0x01);
// send_command(c, 0x8B, 0x01);
} catch (const incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
@@ -361,9 +362,21 @@ static void on_login_b_dcnte(shared_ptr<ServerState> s, shared_ptr<Client> c,
serial_number, cmd.access_key, true);
s->license_manager->add(l);
c->set_license(l);
send_command(c, 0x8B, 0x01);
// send_command(c, 0x8B, 0x01);
}
}
if (cmd.is_extended) {
const auto& ext_cmd = check_size_t<C_LoginExtended_DCNTE_8B>(data);
if (ext_cmd.extension.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = ext_cmd.extension.lobby_id;
}
}
if (!c->should_disconnect) {
send_update_client_config(c);
on_login_complete(s, c);
}
}
static void on_login_0_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -1188,8 +1201,14 @@ static void on_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
c->should_send_to_lobby_server = true;
if (!(c->flags & Client::Flag::SAVE_ENABLED)) {
c->flags |= Client::Flag::SAVE_ENABLED;
send_command(c, 0x97, 0x01);
send_update_client_config(c);
// DC NTE crashes if it receives a 97 command, so we instead do the
// redirect immediately
if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_TRIAL_EDITION)) {
send_client_to_lobby_server(s, c);
} else {
send_command(c, 0x97, 0x01);
send_update_client_config(c);
}
} else {
send_client_to_lobby_server(s, c);
}
@@ -3178,7 +3197,7 @@ static on_command_t handlers[0x100][6] = {
/* 88 */ {nullptr, on_login_8_dcnte, nullptr, on_login_8_dcnte, nullptr, nullptr, }, /* 88 */
/* 89 */ {nullptr, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, }, /* 89 */
/* 8A */ {nullptr, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, }, /* 8A */
/* 8B */ {nullptr, on_login_b_dcnte, nullptr, nullptr, nullptr, nullptr, }, /* 8B */
/* 8B */ {nullptr, on_login_b_dcnte, nullptr, on_login_b_dcnte, nullptr, nullptr, }, /* 8B */
/* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8C */
/* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8D */
/* 8E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8E */
@@ -3317,13 +3336,14 @@ static void check_unlicensed_command(GameVersion version, uint8_t command) {
case GameVersion::XB:
// See comment in the DC case above for why DC commands are included here.
if (command != 0x88 && // DC NTE
command != 0x8B && // DC NTE
command != 0x90 && // DC v1
command != 0x93 && // DC v1
command != 0x9A && // DC v2
command != 0x9D && // DC v2, GC trial edition
command != 0x9E && // GC non-trial
command != 0xDB) { // GC non-trial
throw runtime_error("only commands 90, 93, 9A, 9D, 9E, and DB may be sent before login");
throw runtime_error("only commands 88, 8B, 90, 93, 9A, 9D, 9E, and DB may be sent before login");
}
break;
case GameVersion::BB:
+50 -15
View File
@@ -23,6 +23,8 @@ using namespace std;
const unordered_set<uint32_t> v2_crypt_initial_client_commands({
0x00260088, // (17) DCNTE license check
0x00B0008B, // (02) DCNTE login
0x0114008B, // (02) DCNTE extended login
0x00280090, // (17) DCv1 license check
0x00B00093, // (02) DCv1 login
0x01140093, // (02) DCv1 extended login
@@ -881,21 +883,49 @@ void send_menu_t(
}
for (const auto& item : items) {
if (((c->version() == GameVersion::DC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_DC)) ||
((c->version() == GameVersion::PC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_PC)) ||
((c->version() == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) ||
((c->version() == GameVersion::XB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_XB)) ||
((c->version() == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) ||
((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_D6)) ||
((item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) && (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL)) ||
((item.flags & MenuItem::Flag::REQUIRES_SAVE_DISABLED) && (c->flags & Client::Flag::SAVE_ENABLED))) {
continue;
bool is_visible = true;
switch (c->version()) {
case GameVersion::DC:
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DC);
if (c->flags & Client::Flag::IS_TRIAL_EDITION) {
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DCNTE);
}
break;
case GameVersion::PC:
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_PC);
break;
case GameVersion::GC:
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC);
if (c->flags & Client::Flag::IS_TRIAL_EDITION) {
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC_TRIAL_EDITION);
}
break;
case GameVersion::XB:
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_XB);
break;
case GameVersion::BB:
is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_BB);
break;
default:
throw runtime_error("menus not supported for this game version");
}
if (item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) {
is_visible &= !(c->flags & Client::Flag::NO_D6);
}
if (item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) {
is_visible &= !(c->flags & Client::Flag::NO_SEND_FUNCTION_CALL);
}
if (item.flags & MenuItem::Flag::REQUIRES_SAVE_DISABLED) {
is_visible &= !(c->flags & Client::Flag::SAVE_ENABLED);
}
if (is_visible) {
auto& e = entries.emplace_back();
e.menu_id = menu_id;
e.item_id = item.item_id;
e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04;
e.text = item.name;
}
auto& e = entries.emplace_back();
e.menu_id = menu_id;
e.item_id = item.item_id;
e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04;
e.text = item.name;
}
send_command_vt(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries);
@@ -1294,7 +1324,12 @@ void send_self_leave_notification(shared_ptr<Client> c) {
}
void send_get_player_info(shared_ptr<Client> c) {
send_command(c, 0x95, 0x00);
if ((c->version() == GameVersion::DC) &&
(c->flags & Client::Flag::IS_TRIAL_EDITION)) {
send_command(c, 0x8D, 0x00);
} else {
send_command(c, 0x95, 0x00);
}
}
+6 -3
View File
@@ -406,8 +406,11 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
this->main_menu.emplace_back(MainMenuItemID::GO_TO_LOBBY, u"Go to lobby",
u"Join the lobby", 0);
this->main_menu.emplace_back(MainMenuItemID::INFORMATION, u"Information",
u"View server\ninformation", MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
uint32_t proxy_destinations_menu_item_flags =
// DCNTE doesn't support multiple ship select menus without changing
// servers (via a 19 command) apparently :(
MenuItem::Flag::INVISIBLE_ON_DCNTE |
(this->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
(this->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
(this->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) |
@@ -416,7 +419,7 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
u"Connect to another\nserver", proxy_destinations_menu_item_flags);
this->main_menu.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests",
u"Download quests", MenuItem::Flag::INVISIBLE_ON_BB);
u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
if (!this->function_code_index->patch_menu_empty()) {
this->main_menu.emplace_back(MainMenuItemID::PATCHES, u"Patches",
u"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
@@ -429,7 +432,7 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
u"Disconnect", 0);
this->main_menu.emplace_back(MainMenuItemID::CLEAR_LICENSE, u"Clear license",
u"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password",
MenuItem::Flag::INVISIBLE_ON_BB);
MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
try {
this->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string());