Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
51 changes: 50 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,56 @@
"WebFetch(domain:github.com)",
"Bash(find:*)",
"Bash(npm install:*)",
"Bash(git checkout:*)"
"Bash(git checkout:*)",
"Bash(netstat:*)",
"Bash(taskkill:*)",
"Bash(npm start)",
"Bash(sqlite3:*)",
"Bash(tasklist)",
"Bash(wmic process where:*)",
"mcp__ide__getDiagnostics",
"Bash(where sqlite3)",
"Bash(del:*)",
"Read(//c/Users/Cyan/Desktop/**)",
"Bash(for:*)",
"Bash(do mv \"H:\\game\\nodejs-flyff\\src\\common$file\" \"H:\\game\\nodejs-flyff\\src\\types$file\")",
"Bash(done)",
"Bash(do mv \"$file\" \"../types/$file\")",
"Bash(do mv \"$file\" \"../../../game/battle/arbiters/$file\")",
"Bash(do mv \"$file\" \"../../resources/formats/dyo/$file\")",
"Bash(do mv \"$file\" \"../../resources/formats/rgn/$file\")",
"Bash(timeout 15s npx tsc --noEmit)",
"Bash(timeout 10s npm start)",
"Bash(do if [ -e \"H:\\game\\nodejs-flyff$file\" ])",
"Bash(then echo \"$file exists\")",
"Bash(else echo \"$file missing\")",
"Bash(fi)",
"Bash(timeout 10s npx tsc --noEmit src/game/world/worldMap.ts)",
"Read(//h/flyff/Rhisis/**)",
"Bash(timeout 10s npx tsc --noEmit)",
"Bash(timeout 10s npx tsc --noEmit src/builders/resourceBuilder.ts)",
"Bash(timeout 10s npx tsc --noEmit --skipLibCheck src/builders/resourceBuilder.ts)",
"Bash(timeout 10s npm run build)",
"Bash(timeout 15s npm run lint)",
"Bash(timeout 10s npx tsc)",
"Bash(timeout 10s npx tsc --noEmit src/helpers/logger.ts)",
"Bash(timeout:*)",
"Read(//h/**)",
"Bash(git restore:*)",
"Bash(dir:*)",
"Bash(copy:*)",
"Bash(do cp \"H:\\game\\L19.4\\Server\\Resource$file\" \"H:\\game\\nodejs-flyff\\src\\resources\\res\\data$file\")",
"Bash(npm start:*)",
"Bash(cat:*)",
"Bash(FLYFF_SERVER_TYPE=world timeout 10s npm start)",
"Bash(grep:*)",
"Bash(FLYFF_SERVER_TYPE=world timeout 15s npm start)",
"Bash(cmd /c:*)",
"Bash(findstr:*)",
"Read(//h//**)",
"Bash(FLYFF_SERVER_TYPE=world timeout 10s npx ts-node src/main.ts world)",
"Bash(awk:*)",
"WebFetch(domain:gist.github.com)"
],
"deny": [],
"ask": []
Expand Down
13 changes: 13 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ module.exports = {
parserOptions: {
sourceType: 'script'
}
},
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-undef': 'off',
'no-unused-vars': 'off'
}
}
],
parserOptions: {
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
data/*
node_modules
build
build
Neuz
agent.md
logs
51 changes: 51 additions & 0 deletions analyze-structure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Analyze the exact structure step by step
const hexData = '5E D7 39 DC 95 0C 00 00 00 ED 3C CF F9 FF FF FF FF 0B 00 00 00 C5 1C 0E 00 5E 20 26 47 17 48 00 00 00 D7 2E 34 7C FF FF FF FF F6 00 00 00 08 00 00 00 32 30 31 30 30 34 31 32 00 00 00 00 04 00 00 00 74 65 73 74 20 00 00 00 38 39 64 31 65 64 32 32 61 61 63 35 38 66 35 62 62 65 61 35 33 62 32 66 64 65 38 31 61 39 34 36 F5 D0 B5 65';

const buffer = Buffer.from(hexData.replace(/\s+/g, ''), 'hex');

console.log('Analyzing packet structure...');
console.log('Total length:', buffer.length);

// Print every 4 bytes as both little-endian and big-endian integers
console.log('\n=== 4-byte chunks analysis ===');
for (let i = 0; i < buffer.length - 3; i += 4) {
const le = buffer.readUInt32LE(i);
const be = buffer.readUInt32BE(i);
const bytes = Array.from(buffer.subarray(i, i + 4)).map(b => b.toString(16).padStart(2, '0')).join(' ');
console.log(`Pos ${i.toString().padStart(2, '0')}: [${bytes}] LE=${le.toString().padStart(10)} BE=${be.toString().padStart(10)} (0x${le.toString(16).padStart(8, '0')})`);

// Check if this could be the F6 command
if (le === 0x000000f6) {
console.log(` *** FOUND F6 COMMAND AT POSITION ${i} ***`);
}
}

console.log('\n=== Structure Analysis ===');
// Based on FlyFF protocol: [5E] [HASH-Length] [INT-Length] [HASH-Data] [INT-Data] [Command] ...

let pos = 0;
console.log(`Pos ${pos}: Header = 0x${buffer[pos].toString(16)}`);
pos++;

// The next pattern appears to be multiple nested structures
console.log('\nTrying to parse as nested structures...');

// Pattern seems to be: length + data, length + data, etc.
while (pos < buffer.length - 8 && pos < 50) { // Limit to avoid infinite loop
const length = buffer.readUInt32LE(pos);
console.log(`Pos ${pos}: Length field = ${length} (0x${length.toString(16)})`);

if (length > 0 && length < 1000) { // Reasonable length
pos += 4;
console.log(`Pos ${pos}: Data (${length} bytes):`, buffer.subarray(pos, pos + Math.min(length, 16)).toString('hex'));
pos += length;
} else if (length === 0x000000f6) {
console.log(` *** This is the F6 command! ***`);
break;
} else {
console.log(` Skipping unreasonable length...`);
pos += 4;
}

if (pos >= buffer.length) break;
}
167 changes: 167 additions & 0 deletions docs/FLOW_TEST_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Player Join Flow Test Guide

This guide explains how to test the complete player join communication logic from login to world entry.

## The Flow Implementation

Based on the original C++ Neuz client flow, the Node.js implementation now supports the complete sequence:

### 1. Login Phase
- Client connects to Login Server (port 23000)
- Sends `CERTIFY` packet with credentials
- Receives `SERVER_LIST` packet

### 2. Cluster/Character Selection Phase
- Client connects to Cluster Server (port 28000)
- Sends `GET_CHARACTER_LIST` packet
- Receives character list and optionally `LOGIN_PROTECT_NUMPAD` for PIN
- Sends `PRE_JOIN` packet to start character selection
- If PIN protection enabled:
- Receives `LOGIN_PROTECT_CERT` with success/failure
- On success, proceeds; on failure, gets new numpad
- Sends `SEL_PLAYER` packet to select character
- Receives `PLAYER_ID` packet with authKey for world server

### 3. World Server Transition
- Client connects to World Server (port 5400)
- Sends `JOIN_GAME` packet with authKey and character details
- Receives `SNAPSHOT` packet containing world state and spawns successfully

## Key Implementation Features

### PIN Protection (Login Protect)
- Configurable via `cluster_server.settings["login-protect"]`
- Uses numpad scrambling to secure PIN entry
- Validates against character's `bankPin` field
- Generates new numpad on failed attempts

### Comprehensive Logging
All packet handlers now include detailed logging:
- **prejoin.ts**: Logs PIN verification process step by step
- **selectPlayer.ts**: Logs character validation and authKey generation
- **joinGame.ts**: Logs complete world server join process
- **testClient.ts**: Shows client-side packet flow

### Database Integration
- Validates credentials against database accounts
- Loads character data with relationships (equipments, items)
- Initializes player state (position, stats, inventory, etc.)
- Supports character lookup by ID and name fallback

## Testing the Flow

### Prerequisites

1. **Start all servers** in separate terminals:
```bash
# Terminal 1 - Login Server
npm run dev login

# Terminal 2 - Cluster Server
npm run dev cluster

# Terminal 3 - World Server
npm run dev world
```

2. **Setup test data** (if needed):
```bash
npx ts-node src/setupTestData.ts
```

### Running the Test Client

```bash
npm run test-client
```

### Expected Output

The test client will show a complete flow like this:

```
Connected to 127.0.0.1:23000 (LOGIN)
Sent CERTIFY packet
Received packet: WELCOME (0x0)
Session ID: 1234567
Received packet: SERVER_LIST (0xfd)
Received server list

Connected to 127.0.0.1:28000 (CLUSTER)
Sent GET_CHARACTER_LIST packet
Received packet: CHARACTER_LIST (0xf3)
Received character list
Sent PRE_JOIN packet
Received packet: LOGIN_PROTECT_NUMPAD (0x88100200)
Received numpad ID: 123
Sent PRE_JOIN packet with PIN: 1234
Received packet: LOGIN_PROTECT_CERT (0x88100201)
PIN verification result: SUCCESS
PIN verification successful, selecting character
Sent SEL_PLAYER packet
Received packet: PLAYER_ID (0xff)
Received auth key: 1876543210

Connected to 127.0.0.1:5400 (WORLD)
Sent JOIN_GAME packet
Received packet: SNAPSHOT (0xffffff00)
Received SNAPSHOT packet - join successful!
Successfully joined world server with character ID 1
```

### Server Log Output

Each server will show detailed logs of the packet handling process:

**Cluster Server Logs:**
```
[ClusterServer] PRE_JOIN received for user: testuser, character: testchar (ID: 1), secretNum: 1234
[ClusterServer] Login protection enabled, verifying PIN for character: testchar
[ClusterServer] Extracted PIN: 1234, Character PIN: 1234
[ClusterServer] PIN verification result: SUCCESS
[ClusterServer] PIN verification successful for testchar, sending PRE_JOIN acknowledgment
[ClusterServer] SEL_PLAYER received for characterId: 1
[ClusterServer] Generated authKey: 1876543210 for character: testchar (1)
[ClusterServer] Sent PLAYER_ID with authKey: 1876543210 - client should now connect to world server
```

**World Server Logs:**
```
[WorldServer] JOIN_GAME received from user: testuser
[WorldServer] Character ID: 1
[WorldServer] Auth Key: 1876543210
[WorldServer] Account validation successful for user: testuser (Account ID: 1)
[WorldServer] Character validation successful: testchar (ID: 1) for user: testuser
[WorldServer] Creating join complete snapshot for character: testchar
[WorldServer] Sending SNAPSHOT packet to client for character: testchar
[WorldServer] ✓ Character testchar (ID: 1) joined world server successfully! AuthKey: 1876543210
[WorldServer] ✓ Player spawned in world at position (6968, 3328, 0) on map 1
```

## Troubleshooting

### Common Issues

1. **"Character not found"** - Make sure test data is set up
2. **"PIN verification failed"** - Check that bankPin in database matches test PIN (1234)
3. **"Connection refused"** - Ensure all servers are running on correct ports
4. **Database errors** - Verify database entities are properly configured

### Debug Options

- Enable packet hex dumps by uncommenting debug lines in packet handlers
- Check Redis connectivity for numpad storage
- Verify database relationships are loading correctly
- Monitor network connections between servers

## Configuration

Key settings in server configs:

```yaml
cluster_server:
settings:
login-protect: true # Enable/disable PIN protection
```

The implementation successfully replicates the original C++ client flow while adding modern Node.js features like comprehensive logging, database integration, and error handling.
File renamed without changes.
Loading