Metin2 game client (P0–P11) + mobile asset pipeline
Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
phases, EntityStore world model, ~all GC/CG headers. char create/delete,
private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
token), system-option + game-option + ESC system menu, private-shop 39-grid,
party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.
Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.
Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).
ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
#include <gr2/gr2.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void wr32(std::vector<uint8_t> &bytes, size_t off, uint32_t value) {
|
||||
bytes[off + 0] = uint8_t(value);
|
||||
bytes[off + 1] = uint8_t(value >> 8);
|
||||
bytes[off + 2] = uint8_t(value >> 16);
|
||||
bytes[off + 3] = uint8_t(value >> 24);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> granny_header(size_t size, uint32_t section_count) {
|
||||
std::vector<uint8_t> bytes(size, 0);
|
||||
const uint32_t magic[] = { 0xCAB067B8, 0x0FB16DF8, 0x7E8C7284, 0x1E00195E };
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
wr32(bytes, size_t(i) * 4, magic[i]);
|
||||
}
|
||||
constexpr size_t h = 32;
|
||||
wr32(bytes, h + 0, 6); // Version
|
||||
wr32(bytes, h + 4, uint32_t(size));
|
||||
wr32(bytes, h + 12, 40); // section table starts at byte 72
|
||||
wr32(bytes, h + 16, section_count);
|
||||
// Root type/object remain section 0, offset 0. With no section this must be
|
||||
// rejected by the FileInfo stage rather than returned as an empty File.
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bool expect_failure(const std::vector<uint8_t> &bytes, const char *stage) {
|
||||
gr2::LoadError err;
|
||||
auto file = gr2::File::load(bytes.data(), bytes.size(), &err);
|
||||
if (file || err.stage != stage) {
|
||||
std::fprintf(stderr, "expected failure stage '%s', got file=%d stage='%s' message='%s'\n",
|
||||
stage, file.has_value(), err.stage.c_str(), err.message.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
bool ok = true;
|
||||
ok &= expect_failure(std::vector<uint8_t>(80, 0), "header");
|
||||
ok &= expect_failure(granny_header(80, 0), "fileinfo");
|
||||
|
||||
auto unsupported = granny_header(160, 1);
|
||||
constexpr size_t section = 72;
|
||||
wr32(unsupported, section + 0, 4); // unsupported BitKnit
|
||||
wr32(unsupported, section + 4, uint32_t(unsupported.size()));
|
||||
wr32(unsupported, section + 12, 1); // non-empty expanded section
|
||||
ok &= expect_failure(unsupported, "section");
|
||||
|
||||
auto bad_offset = granny_header(160, 1);
|
||||
wr32(bad_offset, section + 4, UINT32_MAX);
|
||||
ok &= expect_failure(bad_offset, "section");
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user