Skip to content

Commit

Permalink
add item pickup patch
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzziqersoftware committed Dec 8, 2024
1 parent 3424d64 commit 4b3dcbb
Show file tree
Hide file tree
Showing 28 changed files with 656 additions and 20 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ Like quests, Episode 3 card definitions, maps, and quests are cached in memory.

*Everything in this section requires resource_dasm to be installed, so newserv can use the assemblers and disassemblers from its libresource_file library. If resource_dasm is not installed, newserv will still build and run, but these features will not be available.*

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.
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/WriteMemoryGC.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. *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.*

Expand Down Expand Up @@ -478,7 +478,7 @@ The specific versions are:

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.
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, ReadMemoryWordGC.ppc.s, WriteMemoryGC.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.

Like other kinds of data, functions and DOL files are cached in memory. If you've changed any of these files, you can run `reload functions` or `reload dol-files` in the interactive shell to make the changes take effect without restarting the server.

Expand Down Expand Up @@ -548,6 +548,8 @@ Some commands only work on the game server and not on the proxy server. The chat
* You'll be placed into the last available slot in lobbies and games instead of the first, unless you're joining a BB solo-mode game.
* You'll be able to join games with any PSO version, not only those for which crossplay is normally supported. Be prepared for client crashes and other client-side brokenness if you do this. Do not submit any issues for broken behaviors in crossplay, unless the situation is explicitly supported (see the "Cross-version play" section above).
* The rest of the commands in this section are enabled on the game server. (They are always enabled on the proxy server.)
* `$readmem <address>` (game server only): Reads 4 bytes from the given address and shows you the values.
* `$writemem <address> <data>` (game server only): Writes data to the given address. Data is not required to be any specific size.
* `$quest <number>` (game server only): Load a quest by quest number. Can be used to load battle or challenge quests with only one player present. Debug is not required to be enabled if the specified quest has the AllowStartFromChatCommand field set in its metadata file.
* `$qcall <function-id>`: Call a quest function on your client.
* `$qcheck <flag-num>` (game server only): Show the value of a quest flag. This command can be used without debug mode enabled. If you're in a game, show the value of the flag in that game; if you're in the lobby, show the saved value of that quest flag for your character (BB only).
Expand Down
19 changes: 13 additions & 6 deletions src/AddressTranslator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <phosg/Filesystem.hh>
#include <phosg/Strings.hh>
#include <resource_file/ExecutableFormats/DOLFile.hh>
#include <resource_file/ExecutableFormats/PEFile.hh>
#include <resource_file/ExecutableFormats/XBEFile.hh>

using namespace std;
Expand Down Expand Up @@ -112,26 +113,32 @@ class AddressTranslator {
this->directory.pop_back();
}
for (const auto& filename : phosg::list_directory(this->directory)) {
if (filename.size() < 4) {
continue;
}
string name = filename.substr(0, filename.size() - 4);
string path = directory + "/" + filename;

if (phosg::ends_with(filename, ".dol")) {
string name = filename.substr(0, filename.size() - 4);
string path = directory + "/" + filename;
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 (phosg::ends_with(filename, ".xbe")) {
string name = filename.substr(0, filename.size() - 4);
string path = directory + "/" + filename;
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 (phosg::ends_with(filename, ".exe")) {
ResourceDASM::PEFile pe(path.c_str());
auto mem = make_shared<ResourceDASM::MemoryContext>();
pe.load_into(mem);
this->mems.emplace(name, mem);
this->log.info("Loaded %s", name.c_str());
} else if (phosg::ends_with(filename, ".bin")) {
string name = filename.substr(0, filename.size() - 4);
string path = directory + "/" + filename;
string data = phosg::load_file(path);
auto mem = make_shared<ResourceDASM::MemoryContext>();
mem->allocate_at(0x8C010000, data.size());
Expand Down
94 changes: 94 additions & 0 deletions src/ChatCommands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,98 @@ static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> ses, cons
}
}

static bool console_address_in_range(Version version, uint32_t addr) {
if (is_dc(version)) {
return ((addr > 0x8C000000) && (addr <= 0x8CFFFFFC));
} else if (is_gc(version)) {
return ((addr > 0x80000000) && (addr <= 0x817FFFFC));
} else {
return true;
}
}

static void server_command_readmem(shared_ptr<Client> c, const std::string& args) {
check_debug_enabled(c);

uint32_t addr = stoul(args, nullptr, 16);
if (!console_address_in_range(c->version(), addr)) {
send_text_message(c, "$C4Address out of\nrange");
return;
}

prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), addr]() {
auto c = wc.lock();
if (!c) {
return;
}
try {
auto s = c->require_server_state();
const char* function_name = is_dc(c->version())
? "ReadMemoryWordDC"
: is_gc(c->version())
? "ReadMemoryWordGC"
: "ReadMemoryWordX86";
auto fn = s->function_code_index->name_to_function.at(function_name);
send_function_call(c, fn, {{"address", addr}});
c->function_call_response_queue.emplace_back([wc = weak_ptr<Client>(c), addr](uint32_t ret, uint32_t) {
auto c = wc.lock();
if (c) {
string data_str;
if (is_big_endian(c->version())) {
be_uint32_t v = ret;
data_str = phosg::format_data_string(&v, sizeof(v));
} else {
le_uint32_t v = ret;
data_str = phosg::format_data_string(&v, sizeof(v));
}
send_text_message_printf(c, "Bytes at %08" PRIX32 ":\n$C6%s", addr, data_str.c_str());
}
});
} catch (const out_of_range&) {
send_text_message(c, "Invalid patch name");
}
});
}

static void server_command_writemem(shared_ptr<Client> c, const std::string& args) {
check_debug_enabled(c);

auto tokens = phosg::split(args, ' ');
if (tokens.size() < 2) {
send_text_message(c, "Incorrect arguments");
return;
}

uint32_t addr = stoul(tokens[0], nullptr, 16);
if (!console_address_in_range(c->version(), addr)) {
send_text_message(c, "$C4Address out of\nrange");
return;
}

tokens.erase(tokens.begin());
std::string data = phosg::parse_data_string(phosg::join(tokens, " "));

prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), addr, data]() {
auto c = wc.lock();
if (!c) {
return;
}
try {
auto s = c->require_server_state();
const char* function_name = is_dc(c->version())
? "WriteMemoryDC"
: is_gc(c->version())
? "WriteMemoryGC"
: "WriteMemoryX86";
auto fn = s->function_code_index->name_to_function.at(function_name);
send_function_call(c, fn, {{"dest_addr", addr}, {"size", data.size()}}, data.data(), data.size());
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
} catch (const out_of_range&) {
send_text_message(c, "Invalid patch name");
}
});
}

static void server_command_persist(shared_ptr<Client> c, const std::string&) {
auto l = c->require_lobby();
if (l->check_flag(Lobby::Flag::DEFAULT)) {
Expand Down Expand Up @@ -2703,6 +2795,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$qsyncall", {server_command_qsyncall, proxy_command_qsyncall}},
{"$quest", {server_command_quest, nullptr}},
{"$rand", {server_command_rand, proxy_command_rand}},
{"$readmem", {server_command_readmem, nullptr}},
{"$save", {server_command_save, nullptr}},
{"$savechar", {server_command_savechar, nullptr}},
{"$saverec", {server_command_saverec, nullptr}},
Expand All @@ -2728,6 +2821,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$warpall", {server_command_warpall, proxy_command_warpall}},
{"$what", {server_command_what, nullptr}},
{"$where", {server_command_where, nullptr}},
{"$writemem", {server_command_writemem, nullptr}},
});

struct SplitCommand {
Expand Down
2 changes: 1 addition & 1 deletion src/FunctionCompiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ string CompiledFunctionCode::generate_client_command_t(
if (offset > modified_code.size() - 4) {
throw runtime_error("label out of range");
}
*reinterpret_cast<be_uint32_t*>(modified_code.data() + offset) = it.second;
*reinterpret_cast<U32T<FooterT::IsBE>*>(modified_code.data() + offset) = it.second;
}
w.write(modified_code);
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/ReceiveCommands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2745,7 +2745,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
// base address for loading the file.
send_function_call(
c,
s->function_code_index->name_to_function.at("ReadMemoryWord"),
s->function_code_index->name_to_function.at("ReadMemoryWordGC"),
{{"address", 0x80000034}}); // ArenaHigh from GC globals
}
break;
Expand Down Expand Up @@ -2927,7 +2927,7 @@ static void send_dol_file_chunk(shared_ptr<Client> c, uint32_t start_addr) {
string data_to_send = c->loading_dol_file->data.substr(offset, bytes_to_send);

auto s = c->require_server_state();
auto fn = s->function_code_index->name_to_function.at("WriteMemory");
auto fn = s->function_code_index->name_to_function.at("WriteMemoryGC");
unordered_map<string, uint32_t> label_writes(
{{"dest_addr", start_addr}, {"size", bytes_to_send}});
send_function_call(c, fn, label_writes, data_to_send.data(), data_to_send.size());
Expand All @@ -2946,10 +2946,10 @@ static void on_B3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
c->function_call_response_queue.pop_front();
} else if (c->loading_dol_file.get()) {
auto called_fn = s->function_code_index->index_to_function.at(flag);
if (called_fn->short_name == "ReadMemoryWord") {
if (called_fn->short_name == "ReadMemoryWordGC") {
c->dol_base_addr = (cmd.return_value - c->loading_dol_file->data.size()) & (~3);
send_dol_file_chunk(c, c->dol_base_addr);
} else if (called_fn->short_name == "WriteMemory") {
} else if (called_fn->short_name == "WriteMemoryGC") {
if (cmd.return_value >= c->dol_base_addr + c->loading_dol_file->data.size()) {
auto fn = s->function_code_index->name_to_function.at("RunDOL");
unordered_map<string, uint32_t> label_writes({{"dol_base_ptr", c->dol_base_addr}});
Expand Down
8 changes: 5 additions & 3 deletions src/WordSelectTable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ WordSelectSet::WordSelectSet(const string& data, Version version, const vector<s
this->parse_non_windows_t<true, 0x63F, 0x693>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::GC_EP3_NTE:
case Version::GC_V3:
this->parse_non_windows_t<true, 0x67C, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
case Version::GC_V3:
case Version::GC_EP3:
this->parse_non_windows_t<true, 0x804, 0x68C>(decrypt_and_decompress_pr2_data<true>(data), use_sjis);
break;
Expand All @@ -159,11 +159,13 @@ const string& WordSelectSet::string_for_token(uint16_t token_id) const {
void WordSelectSet::print(FILE* stream) const {
fprintf(stream, "strings:\n");
for (size_t z = 0; z < this->strings.size(); z++) {
fprintf(stream, " [%04zX] \"%s\"\n", z, this->strings[z].c_str());
auto escaped = phosg::escape_controls_utf8(this->strings[z]);
fprintf(stream, " [%04zX] \"%s\"\n", z, escaped.c_str());
}
fprintf(stream, "token_id_to_string_id:\n");
for (size_t z = 0; z < this->token_id_to_string_id.size(); z++) {
fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], this->string_for_token(z).c_str());
auto escaped = phosg::escape_controls_utf8(this->string_for_token(z));
fprintf(stream, " [%04zX] %04zX \"%s\"\n", z, this->token_id_to_string_id[z], escaped.c_str());
}
}

Expand Down
29 changes: 29 additions & 0 deletions system/client-functions/ItemPickup/ItemPickup.3OE0.patch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.meta name="Item pickup"
.meta description="Prevents picking\nup items unless you\nhold the Z button"
# Original code by Ralf @ GC-Forever
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
# https://www.gc-forever.com/forums/viewtopic.php?t=2049

entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksGC
.data 0x8000B938
.data 0x00000020
.data 0x387C0550
.data 0x38800100
.data 0x4834428D
.data 0x2C030000
.data 0x4182000C
.data 0x7F83E378
.data 0x481AA150
.data 0x481AA15C
.data 0x801B5A9C
.data 0x00000004
.data 0x4BE55E9C
.data 0x8024CC0C
.data 0x00000004
.data 0x38800008
.data 0x00000000
.data 0x00000000
29 changes: 29 additions & 0 deletions system/client-functions/ItemPickup/ItemPickup.3OE1.patch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.meta name="Item pickup"
.meta description="Prevents picking\nup items unless you\nhold the Z button"
# Original code by Ralf @ GC-Forever
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
# https://www.gc-forever.com/forums/viewtopic.php?t=2049

entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksGC
.data 0x8000B938
.data 0x00000020
.data 0x387C0550
.data 0x38800100
.data 0x483442D1
.data 0x2C030000
.data 0x4182000C
.data 0x7F83E378
.data 0x481AA150
.data 0x481AA15C
.data 0x801B5A9C
.data 0x00000004
.data 0x4BE55E9C
.data 0x8024CC0C
.data 0x00000004
.data 0x38800008
.data 0x00000000
.data 0x00000000
29 changes: 29 additions & 0 deletions system/client-functions/ItemPickup/ItemPickup.3OE2.patch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.meta name="Item pickup"
.meta description="Prevents picking\nup items unless you\nhold the Z button"
# Original code by Ralf @ GC-Forever
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
# https://www.gc-forever.com/forums/viewtopic.php?t=2049

entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksGC
.data 0x8000B938
.data 0x00000020
.data 0x387C0550
.data 0x38800100
.data 0x48345EB9
.data 0x2C030000
.data 0x4182000C
.data 0x7F83E378
.data 0x481AA2E8
.data 0x481AA2F4
.data 0x801B5C34
.data 0x00000004
.data 0x4BE55D04
.data 0x8024DD88
.data 0x00000004
.data 0x38800008
.data 0x00000000
.data 0x00000000
29 changes: 29 additions & 0 deletions system/client-functions/ItemPickup/ItemPickup.3OJ2.patch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.meta name="Item pickup"
.meta description="Prevents picking\nup items unless you\nhold the Z button"
# Original code by Ralf @ GC-Forever
# https://www.gc-forever.com/forums/viewtopic.php?t=2050
# https://www.gc-forever.com/forums/viewtopic.php?t=2049

entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksGC
.data 0x8000B938
.data 0x00000020
.data 0x387C0550
.data 0x38800100
.data 0x483433D9
.data 0x2C030000
.data 0x4182000C
.data 0x7F83E378
.data 0x481A9D64
.data 0x481A9D70
.data 0x801B56B0
.data 0x00000004
.data 0x4BE56288
.data 0x8024C384
.data 0x00000004
.data 0x38800008
.data 0x00000000
.data 0x00000000
Loading

0 comments on commit 4b3dcbb

Please sign in to comment.