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
84 lines
2.9 KiB
C++
84 lines
2.9 KiB
C++
#pragma once
|
|
// EterPack — reader/writer for this m2dev fork's single-file asset pack
|
|
// (PackLib/Pack.cpp). NOT the classic Metin2 .eix/.epk format.
|
|
//
|
|
// Layout:
|
|
// [Header: u64 entry_num, u64 data_begin, u8 nonce[24]] (40 bytes)
|
|
// [Entry x entry_num] each XChaCha20-encrypted with Header.nonce + PACK_KEY
|
|
// Entry: char name[NAME_FIELD], u64 offset, u64 file_size,
|
|
// u64 compressed_size, u8 encryption, u8 nonce[24]
|
|
// [data blob @ data_begin]
|
|
// per file @ data_begin+offset, compressed_size bytes:
|
|
// encryption==0 -> zstd frame -> zstd-decompress to file_size
|
|
// encryption==1 -> XChaCha20(entry.nonce)-> zstd-decompress to file_size
|
|
//
|
|
// NAME_FIELD is FILENAME_MAX+1 on the packer's platform (MSVC PackMaker = 261).
|
|
// The reader derives it from (data_begin - 40) / entry_num, falling back to the
|
|
// known platform values, so it stays compatible with real packs.
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace mtpack {
|
|
|
|
inline constexpr int PACK_NONCE_SIZE = 24;
|
|
inline constexpr int PACK_KEY_SIZE = 32;
|
|
|
|
// PackLib/config.h PACK_KEY — the fork ships this hardcoded.
|
|
inline constexpr uint8_t PACK_KEY[PACK_KEY_SIZE] = {
|
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
|
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
|
|
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
|
|
0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10
|
|
};
|
|
|
|
// MSVC PackMaker.exe: FILENAME_MAX == 260, so the name field is 261 bytes.
|
|
inline constexpr int PACK_NAME_FIELD_DEFAULT = 261;
|
|
|
|
struct Entry {
|
|
std::string name;
|
|
uint64_t offset = 0;
|
|
uint64_t file_size = 0;
|
|
uint64_t compressed_size = 0;
|
|
uint8_t encryption = 0;
|
|
uint8_t nonce[PACK_NONCE_SIZE] = {};
|
|
};
|
|
|
|
class EterPack {
|
|
public:
|
|
// Load the index (mmaps the file; keeps it open for read()).
|
|
bool open(const std::string &path, std::string *err = nullptr);
|
|
void close();
|
|
|
|
bool has(const std::string &name) const { return m_by_name.count(norm(name)) != 0; }
|
|
std::vector<std::string> names() const;
|
|
size_t count() const { return m_index.size(); }
|
|
|
|
// Decompress (+decrypt) one file into `out`.
|
|
bool read(const std::string &name, std::vector<uint8_t> &out, std::string *err = nullptr) const;
|
|
|
|
int name_field() const { return m_name_field; }
|
|
|
|
private:
|
|
static std::string norm(std::string s); // '\\'->'/', lowercase
|
|
|
|
std::vector<uint8_t> m_file; // whole pack in memory (simple; mmap later)
|
|
uint64_t m_data_begin = 0;
|
|
std::vector<Entry> m_index;
|
|
std::unordered_map<std::string, size_t> m_by_name;
|
|
int m_name_field = PACK_NAME_FIELD_DEFAULT;
|
|
};
|
|
|
|
// Build a pack from (name, bytes) pairs. `encrypt` -> per-file encryption==1.
|
|
// Uses PACK_NAME_FIELD_DEFAULT so real clients / PackLib can read it back.
|
|
struct InputFile {
|
|
std::string name;
|
|
std::vector<uint8_t> data;
|
|
};
|
|
bool write_pack(const std::string &out_path, const std::vector<InputFile> &files, bool encrypt,
|
|
std::string *err = nullptr);
|
|
|
|
} // namespace mtpack
|