nodemule
v0.2.1
Published
Search-only eD2k and Kad client library for Node.js
Readme
nodemule
A search-only eD2k and Kad client library for Node.js, with ESM exports, TypeScript declarations, and zero runtime dependencies.
- Search eD2k servers over TCP and UDP, and Kad2 peers over UDP.
- Load server and bootstrap lists from URLs or local files.
- Stream results, merge duplicates across networks, and inspect search progress.
- Keep multiple eD2k connections in a pool, search them concurrently, and disconnect after an idle window.
- Filter by filename expression, file type, extension, size, and source count.
nodemule does not download, upload, or share files. Version 0.1.0 is experimental: public eD2k and Kad searches have been exercised, but server acceptance and peer availability vary. Search results do not prove file availability or complete network coverage.
Installation
npm install nodemuleRequires Node.js ^18.20 || ^20.20 || >=22.0.0. Use ESM imports in a .mjs file or a project with "type": "module".
Quick start
Load a server list and search one eD2k server:
import { createClient, loadServerMet, resultToJSON } from 'nodemule';
const servers = await loadServerMet('http://upd.emule-security.org/server.met');
const client = createClient({ ed2k: { servers } });
try {
// A server search connects and logs in automatically when needed.
const task = client.ed2k.search(
{ text: 'ubuntu', extension: 'iso' },
{ scope: 'server', timeoutMs: 60_000, maxResults: 100 },
);
const results = new Map();
for await (const batch of task) {
for (const { file } of batch.changes) {
results.set(file.key, file);
console.log(resultToJSON(file));
}
}
// Inspect the summary even when results were returned.
console.log(await task.done);
} finally {
await client.close();
}The list URLs in this document are third-party services. They are contacted only when explicitly passed to a loader; the library has no built-in list subscription.
Server and bootstrap configuration
createClient() is synchronous. Load lists first with loadServerMet() and loadNodesDat(), which return arrays accepted by ed2k.servers and kad.bootstrap:
import { createClient, loadServerMet, loadNodesDat } from 'nodemule';
const [servers, bootstrap] = await Promise.all([
loadServerMet('http://upd.emule-security.org/server.met'),
loadNodesDat('http://upd.emule-security.org/nodes.dat'),
]);
const client = createClient({
ed2k: { servers },
kad: { bootstrap, udpPort: 0 },
});Both loaders accept HTTP/HTTPS URLs, local paths, and file: URL objects:
const servers = await loadServerMet('./config/server.met');
const bootstrap = await loadNodesDat('./config/nodes.dat');Relative paths resolve against the process working directory. HTTP redirects are followed. Files are read only. Loading does not connect to endpoints, periodically refresh lists, or verify reachability. Duplicate endpoints are removed while retaining list order; a list with no usable entries returns [].
The optional second argument accepts:
| Option | Default | Purpose |
| --- | --- | --- |
| timeoutMs | 30_000 | I/O timeout in milliseconds |
| maxBytes | 8 MiB | File or decoded HTTP body limit; configurable up to 64 MiB |
| signal | — | AbortSignal for cancellation |
Loading failures reject with LibraryError. Codes include HTTP_ERROR, LIST_LOAD_FAILED, LIST_TOO_LARGE, BAD_PACKET, TIMEOUT, ABORTED, and INVALID_ARGUMENT.
loadServerMet() supports headers 0xe0 and 0x0e and dynamic hostname tags. It imports plain TCP endpoints; UDP defaults to TCP port plus 4. Cached capability flags, UDP keys, and obfuscation ports are discarded. Unsupported endpoints, including TCP ports above 65531, are skipped.
loadNodesDat() supports versioned v1/v2/v3 regular lists and v3 bootstrap lists. It imports node IDs, IPv4 addresses, UDP/TCP ports, and protocol versions. Saved verification state and client-specific UDP keys are discarded. Legacy unversioned Kad1 lists are unsupported.
You can also configure endpoints directly or combine them with loaded arrays. The following domains are placeholders:
const client = createClient({
ed2k: {
servers: [
{ host: 'server-a.example', port: 4661 },
{ host: 'server-b.example', port: 4661, udpPort: 4665 },
],
},
kad: {
bootstrap: [{ host: 'kad-peer.example', port: 4672 }],
udpPort: 0,
},
});Search networks
With both networks configured, start Kad before a combined search:
try {
await client.kad.start();
const task = client.search({ text: 'ubuntu' }, {
networks: ['ed2k', 'kad'],
ed2kScope: 'global',
timeoutMs: 60_000,
maxResults: 500,
});
for await (const batch of task) {
for (const { kind, file } of batch.changes) {
console.log(kind, file.name, file.ed2kLink);
}
}
console.log(await task.done);
} finally {
await client.close();
}kad.start() binds UDP and begins bootstrapping. Search waits for readiness within its timeout; applications can also call kad.waitUntilReady({ timeoutMs: 30_000 }) explicitly.
| Method | Behavior |
| --- | --- |
| client.ed2k.search(query, { scope: 'server' }) | Search the current server over TCP; connect when needed |
| client.ed2k.search(query, { scope: 'global' }) | Search over TCP alongside sequential UDP requests to other configured servers |
| client.kad.search(query) | Search Kad after kad.start() |
| client.search(query, options) | Search selected networks concurrently and merge results; defaults to both networks and global eD2k scope |
A failure in one network does not stop the other. Global eD2k search is bounded by the configured list and timeout. If TCP fails, the primary server is also attempted over UDP within the remaining time.
UDP capability probes select the appropriate query format. If a probe receives no response, the client falls back to basic UDP search and emits CAPABILITIES_UNKNOWN. For manually configured servers, udpPort overrides TCP port plus 4, and udpFlags supplies known capabilities.
Kad supports bounded routing, periodic refresh, paginated keyword search, compressed responses, and v6+ UDP obfuscation. It advertises itself as a firewalled search client. Searches use ephemeral UDP sockets separate from the maintenance port.
Persistent multi-server searches with ClientPool
Client retains its existing behavior: a server-scoped eD2k search uses the current TCP connection, or the first server in the list that successfully connects and logs in. An empty search result does not cause it to try the next server. Its global search uses one TCP connection plus UDP queries.
Use ClientPool (or createClientPool) to search every configured eD2k server over TCP, with independent connections and automatic pagination. The pool also supports one shared Kad client. Kad bootstrap nodes are routing entry points, not a list of persistent TCP connections.
import { createClientPool, loadServerMet, loadNodesDat } from 'nodemule';
const [servers, bootstrap] = await Promise.all([
loadServerMet('http://upd.emule-security.org/server.met'),
loadNodesDat('./config/nodes.dat'),
]);
const pool = createClientPool({
ed2k: {
servers,
maxConnections: servers.length,
connectConcurrency: 3,
connectTimeoutMs: 15_000,
requestTimeoutMs: 15_000,
},
kad: { bootstrap }, // Omit for an eD2k-only pool.
idleTimeoutMs: 60 * 60 * 1000,
reconnect: { maxAttempts: 3, retryDelayMs: 1_000 },
});
async function search(text) {
const task = pool.search({ text }, {
networks: ['ed2k', 'kad'], // Omit to use the configured networks.
concurrency: 4,
timeoutMs: 120_000,
serverTimeoutMs: 30_000,
maxResults: 2_000,
});
for await (const batch of task) {
for (const { kind, file } of batch.changes) {
console.log(kind, file.name, file.observations);
}
}
const summary = await task.done;
console.log(summary.reason, summary.uniqueResults);
console.table(summary.backends.find(b => b.network === 'ed2k')?.servers);
}
try {
// Optional warm-up. search() also connects/starts Kad automatically.
const readiness = await pool.start({ serverTimeoutMs: 30_000 });
console.log(readiness);
await search('ubuntu');
await search('debian'); // Reuses connections from the previous search.
// Keep this pool alive for later application requests if desired.
} finally {
await pool.close(); // Application shutdown: permanently closes this pool.
}Connection and search limits
| Option | Default | Meaning |
| --- | --- | --- |
| ed2k.maxConnections | Unique server count | Capacity for retained eD2k connections. Must be at least the unique server count; the pool never evicts an idle connection to make room. Duplicate host/port entries use the first configuration. |
| ed2k.connectConcurrency | 3 | Maximum concurrent connection/login operations across the pool, including retry delays. Successful retained connections do not consume a connecting slot. |
| Search concurrency | 4 | Maximum concurrent eD2k server operations in this search, including preparation and queue waits. Different searches share each server's serial queue; only one search runs on a TCP connection at a time. |
| Search timeoutMs | 60000 | Entire search budget, including all queues and both networks. |
| Search serverTimeoutMs | 30000 | Per-eD2k-server budget after acquiring that server's queue. Includes waiting for a connecting slot, login attempts, retry delays, and all search pages. |
| Search maxResults | 500 | Global unique-file limit after merging results from all servers and Kad. Reaching it cancels remaining work. |
| Search kadMaxPagesPerNode | 5 | Kad page budget per index node and routing keyword, maximum 32. |
| maxActiveSearches | 32 | Maximum simultaneous search tasks accepted by the pool, maximum 256. |
For 16 servers, maxConnections: 16 and connectConcurrency: 3 retain up to 16 connections while connecting/logging in to at most three servers at once. Search concurrency is independent. To start searches against all servers concurrently, set search concurrency to the list length; connections still respect the connecting limit.
pool.start() warms all configured eD2k connections and waits for Kad readiness. Its optional serverTimeoutMs defaults to 30000 and also bounds the Kad readiness wait. It returns { servers, kad? }, reporting individual failures without discarding successful connections. Calling start() explicitly can retry disconnected servers; repeated calls reuse healthy connections. With idleTimeoutMs: 0, warm-up connections are released when warm-up ends, so normally call search() directly.
Idle windows and on-demand reconnection
idleTimeoutMs defaults to 3600000 milliseconds (one hour). A successful or failed warm-up starts the idle window when it finishes. Starting a search suspends the idle countdown; when the last active search/warm-up finishes, the full window starts again. Long-running or overlapping searches are never interrupted by the idle timer. Invalid search calls do not reset the timer.
- While idle, healthy eD2k connections retain their normal keepalive behavior. Kad retains its routing maintenance.
- If a server disconnects during the window, it stays disconnected. There is no background eD2k reconnect loop. The next search needing that server (or an explicit
start()) reconnects it. - At idle expiry, all eD2k connections close and Kad sockets/maintenance stop. The pool keeps its in-memory identity and Kad routing information. A later search automatically wakes the selected networks.
- Set
idleTimeoutMs: 0to disconnect immediately after all active searches finish. The pool remains reusable; overlapping searches do not disconnect each other's connections. pool.close()cancels all work and permanently releases resources. Subsequentstart()orsearch()calls fail withCLOSED. It is safe to callclose()more than once.
reconnect.maxAttempts defaults to 3 and counts the initial eD2k connection attempt plus retries, both on initial use and after a disconnection. 1 means one attempt with no retry. reconnect.retryDelayMs defaults to 1000 and may be 0. Attempts and delays remain subject to the operation's timeout and cancellation. These options apply to connection/login, not search replay or Kad bootstrap: a connection lost after a search is sent is reported as a server failure, and is retried only on the next operation. Kad uses its existing bootstrap behavior and starts on demand.
Results, cancellation, and monitoring
pool.search() returns the same streaming SearchTask API as Client. Files are deduplicated across servers and Kad, retaining source observations; later observations can produce updated changes. Partial server failures do not cancel healthy servers. Inspect summary.backends even when the overall reason is completed: the eD2k backend includes a servers array with { server, status, attempts, resultsReceived, uniqueResults, errors }. attempts: 0 means no connection attempt was made (for example, an existing connection was reused or the server was cancelled before starting). Server statuses are completed, failed, timeout, or cancelled; servers not reached before cancellation/limits remain cancelled.
task.cancel() cancels only that task. An in-flight TCP search connection is closed to prevent late responses contaminating the next search; other idle connections are retained until the pool's idle deadline. Cancelling a task still queued behind another search does not close that other search's connection. Pool searches always fetch TCP pages automatically; manual pagination is available only through the original single-server Client API.
pool.connections returns snapshots of { server, status }, and pool.kadStatus exposes the shared Kad state. The pool emits networkState (with a server field for eD2k), serverMessage, and warning events. Search progress events aggregate work across all servers. ClientPool also supports Symbol.asyncDispose.
Queries and filtering
text supports uppercase AND, OR, NOT, parentheses, and double-quoted phrases. Adjacent terms imply AND. Precedence is NOT, then AND, then OR; parentheses override it. Terms and phrases match filename substrings case-insensitively. Lowercase operator words are ordinary search terms; quote uppercase operators to search for them literally.
const task = client.search({
text: '("ubuntu server" OR debian) AND NOT (beta OR nightly)',
extension: 'iso',
minSize: 1_000_000_000n,
});Within quoted phrases, \" escapes a quote and \\ escapes a backslash. Every expanded OR branch must contain a positive term: ubuntu NOT beta is valid, but NOT beta and ubuntu OR NOT beta are rejected. Malformed expressions throw INVALID_QUERY synchronously. Expressions are bounded to 1,024 UTF-8 bytes, 128 tokens, 32 nesting levels, 32 expanded branches, and 256 expanded terms.
The following fields are optional:
| Field | Type | Meaning |
| --- | --- | --- |
| type | audio / video / image / document / program / archive | File type |
| extension | string | Extension without a leading dot, such as iso |
| minSize / maxSize | bigint | Inclusive file-size bounds in bytes |
| minSources | number | At least one observation must report this many sources |
Boolean expressions are encoded using protocol AND, OR, and AND-NOT nodes. All results are also filtered locally against the complete expression and query conditions. Exact phrase matching is enforced locally because remote peers may tokenize strings. Multiword phrase exclusions are omitted from the wire expression to avoid excluding valid matches with separated words; they still apply locally. archive is a library-level classification based on the extension. Size bounds exceeding uint32 are applied locally when a server lacks large-file query support. Basic UDP search sends the text expression without attribute conditions, which are applied locally. A server's own result limit can therefore reduce the number of matching results after filtering. Type inference uses a limited extension set rather than a complete MIME classifier.
Kad selects the longest valid positive keyword from each expanded OR branch, searches each distinct MD4 routing target, and merges the results. Excluded terms are never used as routing keywords. A valid keyword occupies at least 3 UTF-8 bytes; if any branch lacks one, the Kad backend fails with INVALID_QUERY in the task summary, without stopping an eD2k backend. All targets share the task timeout and result budget. Modern Unicode lowercasing may differ from older eMule character tables in edge cases; non-ASCII keywords still require public-network compatibility testing.
Search tasks, pagination, and errors
SearchTask is a single-consumer async iterable yielding { changes: [{ kind, file }] }. Each change has kind added or updated and contains a complete file snapshot. Unread updates for slow consumers are coalesced by file key; this is not an audit log of every intermediate state. Files are deduplicated by hash and size, with at most 32 alternative names and 128 observations per file. maxResults limits the total number of unique results.
File sizes use bigint; use resultToJSON() for JSON output. Missing sources or completeSources values remain absent rather than becoming zero. Source counts from different networks are not added together.
const controller = new AbortController();
const task = client.ed2k.search({ text: 'ubuntu' }, {
signal: controller.signal,
timeoutMs: 60_000,
fetchMore: 'manual', // Only available with scope: 'server'.
});
for await (const batch of task) {
// Process this batch of results.
if (task.hasMore) await task.requestMore();
}fetchMore defaults to auto. Manual mode waits for requestMore(), cancellation, or the task timeout. Calling requestMore() when no manual page is available rejects with NO_MORE_RESULTS. These manual pagination methods apply only to server-scope eD2k searches. There is no universal page number or cursor.
Kad automatically requests offsets 0, 300, 600, and so on from an index node when the preceding page contains 300 records. Use maxPagesPerNode with kad.search(), or kadMaxPagesPerNode with client.search(); both default to 5 and accept integers from 1 to 32. The budget applies separately to each node and routing keyword. Set it to 1 for first-page-only behavior:
const task = client.kad.search({ text: 'ubuntu OR debian' }, {
maxPagesPerNode: 3,
maxResults: 2_000,
timeoutMs: 60_000,
});Pagination stops on a short page, no response, a full repeated page with no new file/name pairs, the page budget, cancellation, timeout, or the overall result limit. Identical datagrams within a page are ignored, and files are deduplicated across pages and nodes. Page offsets count received records before local filtering, not unique matching files. Retired page sockets remain bound until that node's paginated search ends to prevent immediate port reuse.
UDP has no reliable end-of-page marker. Each page collects replies for kad.searchWindowMs; packet loss or late replies can make a full page appear short and prevent further paging. No response on a subsequent page is reported as UDP_NO_RESPONSE, not proof that the index is exhausted. paginationLimited reports a full page stopped by the page budget or repeated-page detection; it does not prove completeness when false.
task.cancel(), AbortSignal, maxResults, and the overall timeout can stop a search. Breaking out of the for await loop also cancels the task. The overall timeout includes queueing and network preparation. Each eD2k client serializes entire search sessions. Interrupting a TCP search closes its connection; later searches log in again to prevent old replies from entering a new task. ed2k.disconnect() aborts active and queued eD2k operations; newly submitted searches can reconnect afterward.
await task.done returns a summary and does not reject because of network errors during the search:
reason:completed,timeout,limit,cancelled, orfailed.uniqueResultsandelapsedMs.backends: each network'sstatusanderrors. Errors have a stablecodeand a readablemessage.progress: the final progress and coverage snapshot described below.
completed means the planned queries have ended; still inspect each backend's status. Partial failures do not erase results already received. UDP search has no reliable empty-result or completion message. Missing replies are recorded as UDP_NO_RESPONSE, with a timeout summary, rather than being reported as a confirmed empty result. Even when replies arrive, the client cannot prove that no UDP responses were lost.
Invalid search arguments throw LibraryError synchronously. Failures from explicit connect() or waitUntilReady() calls reject their promises. Before task.done resolves, the task cleans up its active request resources while retaining reusable network connections.
Search progress and coverage
Read task.progress at any time or subscribe to the task's progress event. Updates are coalesced within a microtask; snapshots are independent objects, and a final snapshot is included in task.done. Progress events do not consume the result iterator.
const task = client.search({ text: '(ubuntu OR debian) NOT beta' }, {
kadMaxPagesPerNode: 3,
});
task.on('progress', progress => {
console.log(progress.state, progress.elapsedMs, progress.uniqueResults);
for (const backend of progress.backends) {
console.log(backend.network, backend.phase, {
discovered: backend.targetsDiscovered,
queried: backend.targetsQueried,
responded: backend.targetsResponded,
timedOut: backend.targetsTimedOut,
requests: backend.searchRequests,
});
}
});
for await (const batch of task) {
// Process matching files.
}
const summary = await task.done;
console.log(summary.progress);Each backend snapshot contains:
| Field | Meaning |
| --- | --- |
| phase | Most recent activity: queued, preparing, lookup, searching, or finished. Concurrent operations may overlap. |
| targetsDiscovered | Distinct candidate file-search endpoints: configured/selected eD2k servers or selected responsive Kad index nodes. This count can grow. |
| targetsQueried | Distinct endpoints for which a file-search request was attempted. |
| targetsResponded | Distinct endpoints returning at least one valid file-search response, including a valid empty response. |
| targetsTimedOut / targetsFailed | Distinct endpoints with a timed-out or failed file-search request. |
| searchRequests | Counters attempted, responded, timedOut, failed, cancelled, and pending; each later TCP/Kad page counts as another request. |
| lookupRequests | The same counters for this search's Kad routing requests. Startup, periodic refresh, TCP login, DNS, and UDP capability probes are excluded. |
| responsePackets | Valid file-search packets processed; identical Kad datagrams within a page count once. |
| resultsReceived | Records in those packets, before local filtering and file deduplication. |
| uniqueResults | Matching files deduplicated within this network by hash and size. |
| paginationLimited | A full Kad page reached its page budget, or a full repeated page produced no new file/name pairs. |
Both eD2k and Kad expose resultsReceived and uniqueResults in live task.progress.backends snapshots and final summary.backends entries (also in summary.progress.backends). ClientPool eD2k per-server summaries use the same definitions, scoped to that server. Raw counts include duplicate file records and records rejected by local filters; identical Kad datagrams within a page are discarded before counting. Unique counts use hash + size after filtering, within the respective server or network. All counters start at zero, including for failed or unqueried backends.
summary.uniqueResults is the number of files retained across all networks after deduplication and the global result limit. Do not add per-server or per-network unique counts to calculate this total: the same file can appear in several places. For example, two eD2k servers and Kad may each report one unique file while the global count is still one. Backend/server statistics count all matching files in a processed batch, so they can also exceed the global retained count when maxResults truncates that batch.
Endpoints are deduplicated by configured host and TCP port for eD2k, and by node ID, IPv4 address, and UDP port for Kad. Multiple pages, OR branches, or TCP/UDP attempts against the same endpoint do not increase the endpoint count. Outcome sets can overlap: a node can respond to one page and time out on another. A request can receive valid packets and later fail or be cancelled, so response and outcome counters are not disjoint. All pending counters settle to zero before task.done resolves.
Coverage describes only this task's known and queried endpoints. It is not a percentage of the entire eD2k/Kad network, and even querying every discovered endpoint does not guarantee all files or packets were received. Failures before a file-search request is attempted appear in backend errors rather than file-search request counters.
Events and persistence
Register listeners before connecting:
client.on('networkState', ({ network, state }) => console.log(network, state));
client.on('serverMessage', ({ server, message }) => console.log(server, message));
client.on('warning', warning => console.warn(warning.code, warning.message));client.exportState() returns a versioned, JSON-serializable object containing identity, server configuration, and responsive Kad nodes. Persist it using your own storage, then restore with createClient({ state: saved }). Explicit configuration overrides saved fields. State excludes active connections and search tasks; restored clients must reconnect or restart Kad. Exported peers may no longer be reachable.
User hashes and Kad node IDs are generated when omitted. client.close() is idempotent and permanently closes the instance. client.kad.stop() stops Kad and permits a later restart.
Connection recovery
Read client.ed2k.status or subscribe to networkState for connecting, connected, and disconnected events. connect() tries configured servers in order; without an explicit server, an existing connection is preserved. A subsequent search reconnects when necessary.
There is no background retry loop or automatic replay of interrupted searches. Applications needing continuous reconnection should implement bounded retries with backoff and cancel pending retries during shutdown. Explicit disconnects and failures can both emit disconnected; events do not include a reason or intentional-disconnect flag, and duplicate notifications are possible.
Connection attempts share the eD2k search queue. Silent network failures may only become visible on a request timeout; connected is not an ongoing reachability guarantee.
Kad has no single persistent server connection. Its ready state means bootstrap completed, not that all peers remain available. Inspect search summaries before deciding to bootstrap again:
await client.kad.stop();
await client.kad.start();
await client.kad.waitUntilReady({ timeoutMs: 30_000 });Stopping Kad interrupts active Kad searches. Submit new searches after recovery. A readiness timeout does not itself stop bootstrap; call kad.stop() to abandon it.
API reference
See TypeScript declarations for all configuration options, result types, and events. Public exports are createClient, Client, SearchTask, LibraryError, resultToJSON, loadServerMet, and loadNodesDat.
Development
These commands require a source checkout; development scripts and examples are not included in the npm package.
npm ci
npm test
npm run checkThe default tests use local mock peers and fixed fixtures. They do not contact public services. Syntax and TypeScript checks run with npm run check.
Optional public-network tests
npm run test:live
npm run test:live:kadBoth commands fetch fresh lists from eMule Security and search for ubuntu. They contact public endpoints and can take several minutes. They do not download or share files.
| Command | Optional arguments | Default report |
| --- | --- | --- |
| npm run test:live -- [report-path] [list-source] | Output path, then server.met URL or local path | artifacts/live-ed2k.json |
| npm run test:live:kad -- [list-source] [report-path] | nodes.dat URL or local path, then output path | artifacts/live-kad.json |
Reports record errors, search summaries, and result samples. Network failures are recorded rather than necessarily producing a nonzero exit status; inspect the report. Empty lists and loading failures stop the scripts. Generated reports are ignored by Git and are not distributed with the package.
For a configuration-file example, edit examples/config.example.json, save it as config.local.json, and run:
node examples/search.mjs ./config.local.json "ubuntu"Limitations
- IPv4 only. Servers and Kad bootstrap peers must be supplied explicitly or loaded by the application.
- No downloads, uploads, file sharing, inbound peer handshakes, or High ID acquisition. Some servers reject Low ID or search-only clients.
- No eD2k server TCP/UDP obfuscation or Kad1. Kad UDP obfuscation is a separate supported protocol.
- Search coverage is bounded by configured servers, discovered peers, timeouts, and result limits. UDP packet loss makes completeness impossible to guarantee.
- Search metadata is not verified against file contents, and filtering does not implement eMule's full spam protection.
- No automatic list subscriptions, continuous reconnection, or replay of interrupted searches.
License
GPL-2.0-or-later. See NOTICE for attribution and protocol references. nodemule does not depend on eMule binaries or launch an eMule process.
