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
253 lines
6.3 KiB
Go
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() {}
|