diff --git a/README.md b/README.md
index e9249d0e..4600b471 100644
--- a/README.md
+++ b/README.md
@@ -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.*
@@ -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.
@@ -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
` (game server only): Reads 4 bytes from the given address and shows you the values.
+ * `$writemem ` (game server only): Writes data to the given address. Data is not required to be any specific size.
* `$quest ` (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 `: Call a quest function on your client.
* `$qcheck ` (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).
diff --git a/src/AddressTranslator.cc b/src/AddressTranslator.cc
index b2d1cca0..7aec23f4 100644
--- a/src/AddressTranslator.cc
+++ b/src/AddressTranslator.cc
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
using namespace std;
@@ -112,9 +113,13 @@ public:
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();
dol.load_into(mem);
@@ -122,16 +127,18 @@ public:
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();
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();
+ 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();
mem->allocate_at(0x8C010000, data.size());
diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc
index 7419b293..064f5c26 100644
--- a/src/ChatCommands.cc
+++ b/src/ChatCommands.cc
@@ -891,6 +891,98 @@ static void proxy_command_patch(shared_ptr 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 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(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(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 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(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 c, const std::string&) {
auto l = c->require_lobby();
if (l->check_flag(Lobby::Flag::DEFAULT)) {
@@ -2703,6 +2795,7 @@ static const unordered_map 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}},
@@ -2728,6 +2821,7 @@ static const unordered_map 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 {
diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc
index 651401d4..a7d5e273 100644
--- a/src/FunctionCompiler.cc
+++ b/src/FunctionCompiler.cc
@@ -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(modified_code.data() + offset) = it.second;
+ *reinterpret_cast*>(modified_code.data() + offset) = it.second;
}
w.write(modified_code);
} else {
diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc
index dbf8de9b..7703dbef 100644
--- a/src/ReceiveCommands.cc
+++ b/src/ReceiveCommands.cc
@@ -2745,7 +2745,7 @@ static void on_10(shared_ptr 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;
@@ -2927,7 +2927,7 @@ static void send_dol_file_chunk(shared_ptr 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 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());
@@ -2946,10 +2946,10 @@ static void on_B3(shared_ptr 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 label_writes({{"dol_base_ptr", c->dol_base_addr}});
diff --git a/src/WordSelectTable.cc b/src/WordSelectTable.cc
index 462724cf..03864272 100644
--- a/src/WordSelectTable.cc
+++ b/src/WordSelectTable.cc
@@ -135,9 +135,9 @@ WordSelectSet::WordSelectSet(const string& data, Version version, const vectorparse_non_windows_t(decrypt_and_decompress_pr2_data(data), use_sjis);
break;
case Version::GC_EP3_NTE:
+ case Version::GC_V3:
this->parse_non_windows_t(decrypt_and_decompress_pr2_data(data), use_sjis);
break;
- case Version::GC_V3:
case Version::GC_EP3:
this->parse_non_windows_t(decrypt_and_decompress_pr2_data(data), use_sjis);
break;
@@ -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());
}
}
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OE0.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OE0.patch.s
new file mode 100644
index 00000000..c93aae89
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OE0.patch.s
@@ -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
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OE1.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OE1.patch.s
new file mode 100644
index 00000000..7c79dffa
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OE1.patch.s
@@ -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
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OE2.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OE2.patch.s
new file mode 100644
index 00000000..a8b0e9f0
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OE2.patch.s
@@ -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
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OJ2.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OJ2.patch.s
new file mode 100644
index 00000000..ff1105f7
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OJ2.patch.s
@@ -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
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OJ3.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OJ3.patch.s
new file mode 100644
index 00000000..14f42734
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OJ3.patch.s
@@ -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 0x483447DD
+ .data 0x2C030000
+ .data 0x4182000C
+ .data 0x7F83E378
+ .data 0x481AA1B8
+ .data 0x481AA1C4
+ .data 0x801B5B04
+ .data 0x00000004
+ .data 0x4BE55E34
+ .data 0x8024CDD0
+ .data 0x00000004
+ .data 0x38800008
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OJ4.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OJ4.patch.s
new file mode 100644
index 00000000..6dfd48a5
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OJ4.patch.s
@@ -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 0x48345D45
+ .data 0x2C030000
+ .data 0x4182000C
+ .data 0x7F83E378
+ .data 0x481AC370
+ .data 0x481AC37C
+ .data 0x801B7CBC
+ .data 0x00000004
+ .data 0x4BE53C7C
+ .data 0x8024DD28
+ .data 0x00000004
+ .data 0x38800008
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OJ5.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OJ5.patch.s
new file mode 100644
index 00000000..1a7e080f
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OJ5.patch.s
@@ -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 0x48345AED
+ .data 0x2C030000
+ .data 0x4182000C
+ .data 0x7F83E378
+ .data 0x481AA284
+ .data 0x481AA290
+ .data 0x801B5BD0
+ .data 0x00000004
+ .data 0x4BE55D68
+ .data 0x8024DAC4
+ .data 0x00000004
+ .data 0x38800008
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.3OP0.patch.s b/system/client-functions/ItemPickup/ItemPickup.3OP0.patch.s
new file mode 100644
index 00000000..a77bdb46
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.3OP0.patch.s
@@ -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 0x483452AD
+ .data 0x2C030000
+ .data 0x4182000C
+ .data 0x7F83E378
+ .data 0x481AA7A4
+ .data 0x481AA7B0
+ .data 0x801B60F0
+ .data 0x00000004
+ .data 0x4BE55848
+ .data 0x8024D5D0
+ .data 0x00000004
+ .data 0x38800008
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OED.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OED.patch.s
new file mode 100644
index 00000000..85a97e53
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OED.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDC99
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDE26
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FDE76
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FDEC5
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025ADAD
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OEU.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OEU.patch.s
new file mode 100644
index 00000000..107b261b
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OEU.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDC99
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDE26
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FDE76
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FDEC5
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025AEED
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OJB.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OJB.patch.s
new file mode 100644
index 00000000..e991aaf2
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OJB.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDA89
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDC16
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FDC66
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FDCB5
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025A94D
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OJD.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OJD.patch.s
new file mode 100644
index 00000000..fd86c8b5
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OJD.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDBE9
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDD76
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FDDC6
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FDE15
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025ACCD
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OJU.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OJU.patch.s
new file mode 100644
index 00000000..8542b8c2
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OJU.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDE69
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDFF6
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FE046
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FE095
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025B07D
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OPD.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OPD.patch.s
new file mode 100644
index 00000000..f4bc0155
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OPD.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDCB9
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDE46
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FDE96
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FDEE5
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025ADCD
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/ItemPickup/ItemPickup.4OPU.patch.s b/system/client-functions/ItemPickup/ItemPickup.4OPU.patch.s
new file mode 100644
index 00000000..06781d2c
--- /dev/null
+++ b/system/client-functions/ItemPickup/ItemPickup.4OPU.patch.s
@@ -0,0 +1,29 @@
+.meta name="Item pickup"
+.meta description="Prevents picking\nup items unless you\nhold the white or\nblack 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
+# Xbox port by fuzziqersoftware
+
+entry_ptr:
+reloc0:
+ .offsetof start
+start:
+ .include WriteCodeBlocksXB
+ .data 0x001FDD29
+ .data 0x07
+ .binary E8880100009090
+ .data 0x001FDEB6
+ .data 0x0A
+ .binary 8B866C05000085C0EB46
+ .data 0x001FDF06
+ .data 0x09
+ .binary 74038A40013408EB46
+ .data 0x001FDF55
+ .data 0x0A
+ .binary 7507F68624030000E0C3
+ .data 0x0025AF1D
+ .data 0x01
+ .binary 00
+ .data 0x00000000
+ .data 0x00000000
diff --git a/system/client-functions/System/ReadMemoryWordDC.sh4.s b/system/client-functions/System/ReadMemoryWordDC.sh4.s
new file mode 100644
index 00000000..30b034f2
--- /dev/null
+++ b/system/client-functions/System/ReadMemoryWordDC.sh4.s
@@ -0,0 +1,13 @@
+entry_ptr:
+reloc0:
+ .offsetof start
+
+start:
+ mova r0, [address]
+ mov.l r0, [r0]
+ rets
+ mov.l r0, [r0]
+
+ .align 4
+address:
+ .data 0
diff --git a/system/client-functions/System/ReadMemoryWord.ppc.s b/system/client-functions/System/ReadMemoryWordGC.ppc.s
similarity index 100%
rename from system/client-functions/System/ReadMemoryWord.ppc.s
rename to system/client-functions/System/ReadMemoryWordGC.ppc.s
diff --git a/system/client-functions/System/ReadMemoryWordX86.x86.s b/system/client-functions/System/ReadMemoryWordX86.x86.s
new file mode 100644
index 00000000..e61fe905
--- /dev/null
+++ b/system/client-functions/System/ReadMemoryWordX86.x86.s
@@ -0,0 +1,13 @@
+entry_ptr:
+reloc0:
+ .offsetof start
+
+start:
+ call resume
+address:
+ .data 0
+resume:
+ pop eax
+ mov eax, [eax]
+ mov eax, [eax]
+ ret
diff --git a/system/client-functions/System/WriteMemoryDC.sh4.s b/system/client-functions/System/WriteMemoryDC.sh4.s
new file mode 100644
index 00000000..6c46dd2f
--- /dev/null
+++ b/system/client-functions/System/WriteMemoryDC.sh4.s
@@ -0,0 +1,33 @@
+.meta name="Write memory"
+.meta description="Writes data to any location in memory"
+
+entry_ptr:
+reloc0:
+ .offsetof start
+
+start:
+ mova r0, [dest_addr]
+ mov r4, r0
+ mov.l r0, [r4]
+ mov.l r5, [r4 + 4]
+ add r4, 8
+again:
+ test r5, r5
+ bt done
+ mov.b r6, [r4]
+ mov.b [r0], r6
+ add r4, 1
+ add r0, 1
+ bs again
+ add r5, -1
+done:
+ rets
+ nop
+
+ .align 4
+dest_addr:
+ .data 0
+size:
+ .data 0
+
+data_to_write:
diff --git a/system/client-functions/System/WriteMemory.ppc.s b/system/client-functions/System/WriteMemoryGC.ppc.s
similarity index 96%
rename from system/client-functions/System/WriteMemory.ppc.s
rename to system/client-functions/System/WriteMemoryGC.ppc.s
index 5dedc318..f2a40149 100644
--- a/system/client-functions/System/WriteMemory.ppc.s
+++ b/system/client-functions/System/WriteMemoryGC.ppc.s
@@ -3,7 +3,7 @@
# This is also the file I've chosen to document how to write code for newserv's
# functions subsystem. There are three kinds of functions: includes, patches,
-# and general functions. This file, WriteMemory, is a general function. It
+# and general functions. This file, WriteMemoryGC, is a general function. It
# writes a variable-length block of data to a specified address in the client's
# memory.
@@ -28,7 +28,7 @@
# For example, to use this function to write the bytes 38 00 00 05 to the
# address 8010521C, send_function_call could be called like this:
-# auto fn = s->function_code_index->name_to_function.at("WriteMemory");
+# auto fn = s->function_code_index->name_to_function.at("WriteMemoryGC");
# unordered_map label_writes(
# {{"dest_addr", 0x8010521C}, {"size", 4}});
# string suffix("\x38\x00\x00\x05", 4);
@@ -52,8 +52,8 @@
# when sending the B2 command. This is needed if the server needs to do
# something when the B3 response is received. If specified, the index must be in
# the range 01-FF. The DOL loading functionality, which this function is a part
-# of, uses indexes E0, E1, and E2, but the WriteMemory function can also be used
-# for other purposes.
+# of, uses indexes E0, E1, and E2, but the WriteMemoryGC function can also be
+# used for other purposes.
.meta index=E1
# To hide a patch from the Patches menu (so it can only be used with the $patch
diff --git a/system/client-functions/System/WriteMemoryX86.x86.s b/system/client-functions/System/WriteMemoryX86.x86.s
new file mode 100644
index 00000000..f1ace410
--- /dev/null
+++ b/system/client-functions/System/WriteMemoryX86.x86.s
@@ -0,0 +1,37 @@
+.meta name="Write memory"
+.meta description="Writes data to any location in memory"
+
+entry_ptr:
+reloc0:
+ .offsetof start
+
+start:
+ jmp get_block_ptr
+get_block_ptr_ret:
+ xchg ebx, [esp]
+ mov eax, [ebx]
+ mov ecx, [ebx + 4]
+ add ebx, 8
+
+again:
+ test ecx, ecx
+ jz done
+ mov dl, [ebx]
+ mov [eax], dl
+ inc ebx
+ inc eax
+ dec ecx
+ jmp again
+
+done:
+ pop ebx
+ ret
+
+get_block_ptr:
+ call get_block_ptr_ret
+dest_addr:
+ .data 0
+size:
+ .data 0
+
+data_to_write:
diff --git a/system/text-sets/gc-v3/ws_data.bin b/system/text-sets/gc-v3/ws_data.bin
old mode 100644
new mode 100755
index 635bc2fe..7d44ee7d
Binary files a/system/text-sets/gc-v3/ws_data.bin and b/system/text-sets/gc-v3/ws_data.bin differ