# How v2rayN Handles QR Code Scanning for vmess:// and ss:// URLs

> Discover how v2rayN scans QR codes using ZXing.SkiaSharp to decode vmess:// and ss:// URLs. Learn how it parses and stores configuration data in this technical deep dive.

- Repository: [2dust/v2rayN](https://github.com/2dust/v2rayN)
- Tags: how-to-guide
- Published: 2026-02-27

---

**v2rayN uses ZXing.SkiaSharp to decode QR codes from screen captures or image files, then routes the extracted vmess:// or ss:// URLs through FmtHandler to protocol-specific parsers that convert them into ProfileItem objects stored in SQLite.**

v2rayN is a Windows GUI client for V2Ray and Xray cores that simplifies proxy configuration through QR code scanning. Understanding how v2rayN handles QR code scanning for vmess:// and ss:// URLs reveals a robust pipeline from image capture to database persistence. This article examines the complete workflow implemented in the 2dust/v2rayN repository, from the UI triggers in `MainWindowViewModel` to the protocol-specific parsing logic in `VmessFmt` and `ShadowsocksFmt`.

## The QR Code Scanning Pipeline Overview

When a user selects **"Scan QR code on screen"** or **"Scan QR code from an image"**, v2rayN executes a nine-step pipeline:

1. UI triggers scan command via `AddServerViaScanCmd`
2. Image capture or file loading via `QRCodeUtils.CaptureScreen`
3. QR decoding via `QRCodeUtils.ParseBarcode` using ZXing.SkiaSharp
4. Result processing via `MainWindowViewModel.AddScanResultAsync`
5. Batch import via `ConfigHandler.AddBatchServers`
6. Protocol detection via `FmtHandler.ResolveConfig`
7. URL parsing via `VmessFmt.Resolve` or `ShadowsocksFmt.Resolve`
8. Profile creation as `ProfileItem` objects
9. Database persistence via SQLite

## Step 1: UI Trigger and Image Capture

The process begins in [`ServiceLib/ViewModels/MainWindowViewModel.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/ViewModels/MainWindowViewModel.cs) when the `AddServerViaScanCmd` command executes. Depending on user selection, it raises either `EViewAction.ScanScreenTask` or `EViewAction.ScanImageTask`.

For screen captures, the application calls `QRCodeUtils.CaptureScreen`, which is implemented in [`ServiceLib/Common/QRCodeUtils.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Common/QRCodeUtils.cs). This method captures the entire screen bitmap on Windows systems. For image files, the file path passes directly to the decoding routine.

## Step 2: QR Code Decoding with ZXing.SkiaSharp

The core decoding logic resides in `QRCodeUtils.ParseBarcode`. This method uses **SkiaSharp** to load the bitmap and **ZXing.SkiaSharp** to read the barcode data.

```csharp
// QRCodeUtils.ParseBarcode – returns the raw URL string
string? url = QRCodeUtils.ParseBarcode(imageBytes);
// url might be something like "vmess://eyJ...==" or "ss://YWVzLTI1Ni1j..."
// If the QR is mirrored, a second attempt with a flipped bitmap is performed internally.

```

If the initial scan fails, the implementation attempts a second pass with a horizontally flipped bitmap to handle mirrored QR codes.

## Step 3: Routing URLs to the Import Engine

Once decoded, `MainWindowViewModel.AddScanResultAsync` processes the raw string:

```csharp
private async Task AddScanResultAsync(string? result)
{
    if (result.IsNullOrEmpty())
        NoticeManager.Instance.Enqueue(ResUI.NoValidQRcodeFound);
    else
    {
        // Import the server(s) – handles vmess, ss, vless, … automatically
        int imported = await ConfigHandler.AddBatchServers(_config, result,
                                                          _config.SubIndexId, false);
        if (imported > 0)
            NoticeManager.Instance.Enqueue(ResUI.SuccessfullyImportedServerViaScan);
    }
}

```

This method delegates to `ConfigHandler.AddBatchServers` in [`ServiceLib/Handler/ConfigHandler.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Handler/ConfigHandler.cs), which handles batch imports from any source.

## Step 4: Protocol-Specific Parsing

`ConfigHandler.AddBatchServers` invokes `FmtHandler.ResolveConfig` in [`ServiceLib/Handler/Fmt/FmtHandler.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Handler/Fmt/FmtHandler.cs). This dispatcher examines the URL prefix and routes to the appropriate formatter.

### Parsing vmess:// URLs

For `vmess://` links, `VmessFmt.Resolve` in [`ServiceLib/Handler/Fmt/VmessFmt.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Handler/Fmt/VmessFmt.cs) handles two formats:

1. **Standard vmess URI**: Base64-encoded JSON following the VMess AEAD standard
2. **Legacy JSON**: Plain JSON objects in the QR code

```csharp
// Inside VmessFmt.Resolve
var vmessQRCode = JsonUtils.Deserialize<VmessQRCode>(jsonString);
if (vmessQRCode != null)
{
    var profile = new ProfileItem { ConfigType = EConfigType.VMess };
    profile.Address = Utils.ToString(vmessQRCode.add);
    profile.Port    = vmessQRCode.port;
    profile.Password = Utils.ToString(vmessQRCode.id);
    profile.SetProtocolExtra(new ProtocolExtraItem {
        AlterId = vmessQRCode.aid.ToString(),
        VmessSecurity = vmessQRCode.scy.IsNullOrEmpty()
                         ? Global.DefaultSecurity : vmessQRCode.scy,
    });
    // … fill network, TLS, etc.
    return profile;
}

```

### Parsing ss:// URLs

For `ss://` links, `ShadowsocksFmt.Resolve` in [`ServiceLib/Handler/Fmt/ShadowsocksFmt.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Handler/Fmt/ShadowsocksFmt.cs) supports:

1. **Legacy format**: `ss://BASE64(method:password)@host:port`
2. **SIP002 format**: `ss://BASE64(method:password)@host:port/?plugin=...`

```csharp
// Inside ShadowsocksFmt.Resolve
if (str.StartsWith(Global.ProtocolShares[EConfigType.Shadowsocks]))
{
    // Strip prefix, Base64‑decode, then split "method:password@host:port"
    var decoded = Utils.Base64Decode(str.Substring(prefix.Length));
    var parts = decoded.Split('@');
    var methodPwd = parts[0].Split(':');
    var hostPort = parts[1].Split(':');

    var profile = new ProfileItem { ConfigType = EConfigType.Shadowsocks };
    profile.Address = hostPort[0];
    profile.Port    = int.Parse(hostPort[1]);
    profile.SetProtocolExtra(new ProtocolExtraItem {
        SsMethod = methodPwd[0],
        // Shadowsocks password is the second part
        SsPassword = methodPwd[1],
    });
    return profile;
}

```

## Step 5: Database Persistence

After parsing, the `ProfileItem` objects return to `ConfigHandler.AddBatchServers`, which inserts them into the SQLite database via `SQLiteHelper.Instance.InsertAllAsync`. The UI refreshes automatically, displaying the new server in the profile list with a success notification.

## Summary

- **v2rayN QR code scanning** begins with screen capture or image selection in [`MainWindowViewModel.cs`](https://github.com/2dust/v2rayN/blob/main/MainWindowViewModel.cs)
- **ZXing.SkiaSharp** handles decoding in `QRCodeUtils.ParseBarcode`, with fallback for mirrored codes
- **FmtHandler.ResolveConfig** routes URLs to protocol-specific parsers based on the scheme prefix
- **VmessFmt.Resolve** handles both Base64-encoded and JSON vmess formats
- **ShadowsocksFmt.Resolve** supports legacy and SIP002 ss:// URL formats
- All parsed servers persist as **ProfileItem** objects in SQLite via `ConfigHandler.AddBatchServers`

## Frequently Asked Questions

### How does v2rayN handle invalid or mirrored QR codes?

If `QRCodeUtils.ParseBarcode` fails to decode a QR code on the first attempt, it automatically retries with a horizontally flipped bitmap. This handles cases where the QR code is mirrored or reflected. If both attempts fail, the method returns null and the UI displays "No valid QR code found."

### What QR code formats are supported for vmess:// URLs?

v2rayN supports two vmess QR code formats in `VmessFmt.Resolve`: the standard vmess URI format (`vmess://BASE64_ENCODED_JSON`) and legacy plain JSON objects. The parser attempts JSON deserialization first, then falls back to Base64 decoding if needed, extracting fields like address, port, UUID, alterId, and security settings.

### Can v2rayN scan QR codes from files as well as the screen?

Yes. The `MainWindowViewModel` exposes two distinct commands: one for screen scanning (`EViewAction.ScanScreenTask`) that uses `QRCodeUtils.CaptureScreen`, and one for image files (`EViewAction.ScanImageTask`) that passes the selected file path directly to `QRCodeUtils.ParseBarcode`. Both paths converge on the same decoding and import logic.

### How does v2rayN distinguish between vmess and Shadowsocks URLs?

The `FmtHandler.ResolveConfig` method acts as a dispatcher, checking the URL prefix against the `Global.ProtocolShares` dictionary. If the string starts with `vmess://`, it delegates to `VmessFmt.Resolve`. If it starts with `ss://`, it delegates to `ShadowsocksFmt.Resolve`. This allows v2rayN to handle mixed batches of different protocol URLs in a single scan operation.