FoxiGram/libxray/xray.go
instant992 8e79f2ee9c FoxiGram: Telegram client with built-in Xray VLESS proxy
Based on Nekogram. Key additions:
- Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app)
- Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so
- Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI
- Auto-restore proxy on restart, rebind to active network (LTE/WiFi)
- Server credentials externalized to git-ignored XrayServers.java (+ template)
- libxray Go source included; compiled .so, keystore, google-services.json ignored
2026-06-08 16:41:07 +04:00

253 lines
6.3 KiB
Go

package main
/*
#include <jni.h>
#include <stdlib.h>
#include <string.h>
static jstring make_jstring(JNIEnv *env, const char *s) {
return (*env)->NewStringUTF(env, s ? s : "");
}
static const char* get_jstring(JNIEnv *env, jstring s, jboolean *isCopy) {
return (*env)->GetStringUTFChars(env, s, isCopy);
}
static void release_jstring(JNIEnv *env, jstring s, const char *c) {
(*env)->ReleaseStringUTFChars(env, s, c);
}
static jint register_natives(JNIEnv *env, jclass clazz, JNINativeMethod *methods, jint n) {
return (*env)->RegisterNatives(env, clazz, methods, n);
}
static jclass find_class(JNIEnv *env, const char *name) {
return (*env)->FindClass(env, name);
}
*/
import "C"
import (
"bytes"
"context"
"encoding/json"
"fmt"
"sync"
"unsafe"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf/serial"
_ "github.com/xtls/xray-core/app/dispatcher"
_ "github.com/xtls/xray-core/app/dns"
_ "github.com/xtls/xray-core/app/proxyman/inbound"
_ "github.com/xtls/xray-core/app/proxyman/outbound"
_ "github.com/xtls/xray-core/proxy/vless/outbound"
_ "github.com/xtls/xray-core/proxy/socks"
_ "github.com/xtls/xray-core/transport/internet/reality"
_ "github.com/xtls/xray-core/transport/internet/tcp"
_ "github.com/xtls/xray-core/transport/internet/grpc"
)
var (
mu sync.Mutex
instance *core.Instance
)
type VlessConfig struct {
Address string `json:"address"`
Port int `json:"port"`
UUID string `json:"uuid"`
PublicKey string `json:"publicKey"`
ShortID string `json:"shortId"`
Fingerprint string `json:"fingerprint"`
ServerName string `json:"serverName"`
LocalPort int `json:"localPort"`
// Transport: "tcp" (default) or "grpc"
Network string `json:"network"`
ServiceName string `json:"serviceName"`
}
func buildConfig(cfg VlessConfig) ([]byte, error) {
fp := cfg.Fingerprint
if fp == "" {
fp = "chrome"
}
sni := cfg.ServerName
if sni == "" {
sni = cfg.Address
}
lp := cfg.LocalPort
if lp == 0 {
lp = 10808
}
network := cfg.Network
if network == "" {
network = "tcp"
}
// Build streamSettings based on transport
var streamSettings string
if network == "grpc" {
streamSettings = fmt.Sprintf(`{
"network": "grpc",
"security": "reality",
"realitySettings": {
"serverName": %q,
"fingerprint": %q,
"publicKey": %q,
"shortId": %q,
"spiderX": "/"
},
"grpcSettings": {
"serviceName": %q,
"multiMode": false
}
}`, sni, fp, cfg.PublicKey, cfg.ShortID, cfg.ServiceName)
} else {
streamSettings = fmt.Sprintf(`{
"network": "tcp",
"security": "reality",
"realitySettings": {
"serverName": %q,
"fingerprint": %q,
"publicKey": %q,
"shortId": %q,
"spiderX": "/"
}
}`, sni, fp, cfg.PublicKey, cfg.ShortID)
}
// flow only for tcp/vision, not grpc
flow := ""
if network != "grpc" {
flow = `"xtls-rprx-vision"`
} else {
flow = `""`
}
raw := fmt.Sprintf(`{
"log": {"loglevel": "warning"},
"inbounds": [{
"listen": "127.0.0.1",
"port": %d,
"protocol": "socks",
"settings": {"auth": "noauth", "udp": false}
}],
"outbounds": [{
"protocol": "vless",
"settings": {
"vnext": [{
"address": %q,
"port": %d,
"users": [{"id": %q, "encryption": "none", "flow": %s}]
}]
},
"streamSettings": %s
}]
}`, lp, cfg.Address, cfg.Port, cfg.UUID, flow, streamSettings)
return []byte(raw), nil
}
func xrayStart(configJSON string) string {
mu.Lock()
defer mu.Unlock()
if instance != nil {
_ = instance.Close()
instance = nil
}
var cfg VlessConfig
if err := json.Unmarshal([]byte(configJSON), &cfg); err != nil {
return "config parse error: " + err.Error()
}
xrayJSON, err := buildConfig(cfg)
if err != nil {
return "build config error: " + err.Error()
}
confObj, err := serial.DecodeJSONConfig(bytes.NewReader(xrayJSON))
if err != nil {
return "decode config error: " + err.Error() + " | json: " + string(xrayJSON)
}
pbCfg, err := confObj.Build()
if err != nil {
return "load config error: " + err.Error() + " | json: " + string(xrayJSON)
}
inst, err := core.New(pbCfg)
if err != nil {
return "create error: " + err.Error()
}
if err := inst.Start(); err != nil {
return "start error: " + err.Error()
}
instance = inst
return ""
}
func xrayStop() string {
mu.Lock()
defer mu.Unlock()
if instance == nil {
return ""
}
err := instance.Close()
instance = nil
if err != nil {
return err.Error()
}
return ""
}
func xrayRunning() string {
mu.Lock()
defer mu.Unlock()
if instance != nil {
return "true"
}
return "false"
}
//export Java_org_telegram_messenger_XrayController_StartXray
func Java_org_telegram_messenger_XrayController_StartXray(env *C.JNIEnv, clazz C.jclass, cfg C.jstring) C.jstring {
var isCopy C.jboolean
cs := C.get_jstring(env, cfg, &isCopy)
result := xrayStart(C.GoString(cs))
C.release_jstring(env, cfg, cs)
cr := C.CString(result)
defer C.free(unsafe.Pointer(cr))
return C.make_jstring(env, cr)
}
//export Java_org_telegram_messenger_XrayController_StopXray
func Java_org_telegram_messenger_XrayController_StopXray(env *C.JNIEnv, clazz C.jclass) C.jstring {
result := xrayStop()
cr := C.CString(result)
defer C.free(unsafe.Pointer(cr))
return C.make_jstring(env, cr)
}
//export Java_org_telegram_messenger_XrayController_IsRunning
func Java_org_telegram_messenger_XrayController_IsRunning(env *C.JNIEnv, clazz C.jclass) C.jstring {
result := xrayRunning()
cr := C.CString(result)
defer C.free(unsafe.Pointer(cr))
return C.make_jstring(env, cr)
}
//export Java_org_telegram_messenger_XrayController_initXrayBridge
func Java_org_telegram_messenger_XrayController_initXrayBridge(env *C.JNIEnv, clazz C.jclass, dir C.jstring) {
}
//export Java_org_telegram_messenger_XrayController_GetLocalSocksPort
func Java_org_telegram_messenger_XrayController_GetLocalSocksPort(env *C.JNIEnv, clazz C.jclass, cfg C.jstring) C.jstring {
var isCopy C.jboolean
cs := C.get_jstring(env, cfg, &isCopy)
var v VlessConfig
_ = json.Unmarshal([]byte(C.GoString(cs)), &v)
C.release_jstring(env, cfg, cs)
lp := v.LocalPort
if lp == 0 {
lp = 10808
}
result := fmt.Sprintf("%d", lp)
cr := C.CString(result)
defer C.free(unsafe.Pointer(cr))
return C.make_jstring(env, cr)
}
var _ = context.Background
func main() {}