lychee-slicer/main.go
2024-10-21 16:46:23 +02:00

203 lines
5.3 KiB
Go

package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
"github.com/elazarl/goproxy"
"github.com/tidwall/sjson"
"vaclive.party/software/lychee-slicer/patcher"
"vaclive.party/software/lychee-slicer/platform"
)
const ip = "127.0.0.1"
type graphqlRequest struct {
OperationName string `json:"operationName"`
}
type userData struct {
operationName string
}
func main() {
verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
program := flag.String("program", "", "program to patch and/or proxy")
runPatch := flag.Bool("patch", false, "only patch the program")
runProxy := flag.Bool("proxy", false, "only start the proxy and program")
flag.Parse()
if *program == "" {
*program = platform.FindProgram()
if *program == "" {
flag.Usage()
log.Fatal("Program not specified")
}
}
if !*runPatch && !*runProxy {
log.Println("No flags specified, running both patch and proxy")
*runProxy = true
*runPatch = true
}
if *runPatch {
err := patcher.Patch(*program)
if err != nil {
log.Fatal("Patch:", err)
}
}
if !*runProxy {
return
}
log.Println("Starting proxy")
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = *verbose
host := "graph.mango3d.io:443"
proxy.OnRequest(goproxy.ReqHostIs(host)).HandleConnectFunc(goproxy.AlwaysMitm)
proxy.OnRequest(goproxy.ReqHostIs(host)).DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (ret_req *http.Request, ret_res *http.Response) {
defer func() {
if e := recover(); e != nil {
ctx.Warnf("Panic: %v", e)
ret_req = req
ret_res = goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusBadGateway, "Panic")
}
}()
if req.Method != "POST" {
return req, nil
}
body, err := io.ReadAll(req.Body)
if err != nil {
panic(err)
}
req.Body.Close()
req.Body = io.NopCloser(bytes.NewBuffer(body))
var graphql graphqlRequest
if err := json.Unmarshal(body, &graphql); err != nil {
ctx.Warnf("Unmarshal: %v", err)
return req, nil
}
ctx.UserData = &userData{operationName: graphql.OperationName}
fmt.Println("OperationName:", graphql.OperationName)
return req, nil
})
modifyResponse := func(name string, callback func(string) string) {
proxy.OnResponse(operationNameIs(name)).DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) (ret *http.Response) {
defer func() {
if e := recover(); e != nil {
ret = goproxy.NewResponse(resp.Request, goproxy.ContentTypeText, http.StatusBadGateway, "Panic")
}
}()
bb, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
resp.Body.Close()
b := string(bb)
log.Println(name+":", b)
b = callback(b)
resp.Body = io.NopCloser(bytes.NewBuffer([]byte(b)))
return resp
})
}
modifyResponse("GetUser", func(b string) string {
b, _ = sjson.Set(b, "data.user.status.isPro", true)
b, _ = sjson.Set(b, "data.user.status.isLifetime", true)
b, _ = sjson.Set(b, "data.user.status.type", "lifetime")
b, _ = sjson.Set(b, "data.user.status.featureTags", []string{"LYCHEE_SLICER_PRO_SLA", "LYCHEE_SLICER_PRO_FDM", "LYCHEE_SLICER_PREMIUM"})
b, _ = sjson.Set(b, "data.user.licenses.0.macAddressesLimit", 9999999)
return b
})
modifyResponse("userCheck", func(b string) string {
b, _ = sjson.Set(b, "data.consumer.user.status.isPremium", true)
b, _ = sjson.Set(b, "data.consumer.user.status.isPro", true)
b, _ = sjson.Set(b, "data.consumer.user.status.isLifetime", true)
b, _ = sjson.Set(b, "data.consumer.user.status.featureTags", []string{"LYCHEE_SLICER_PRO_SLA", "LYCHEE_SLICER_PRO_FDM", "LYCHEE_SLICER_PREMIUM"})
b, _ = sjson.Set(b, "data.consumer.user.status.type", "lifetime")
return b
})
modifyResponse("checkLicenseWithoutDuration", func(b string) string {
b, _ = sjson.Set(b, "data.checkLicenseWithoutDuration.isPro", true)
b, _ = sjson.Set(b, "data.checkLicenseWithoutDuration.isLifetime", true)
b, _ = sjson.Set(b, "data.checkLicenseWithoutDuration.type", "lifetime")
b, _ = sjson.Set(b, "data.checkLicenseWithoutDuration.mac_addresses_limit", 9999999)
return b
})
modifyResponse("getLicense", func(b string) string {
return b
})
modifyResponse("CanUserGetTrial", func(b string) string {
return b
})
port, err := getFreePort()
if err != nil {
log.Fatal("GetFreePort:", err)
}
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, port))
if err != nil {
log.Fatal("Listen:", err)
}
log.Println("Listening on", ln.Addr().String(), port)
go func() {
cmd := exec.Command(*program, fmt.Sprintf("--proxy-server=%s:%d", ip, port), "--ignore-certificate-errors")
cmd.Args = append(cmd.Args, flag.Args()...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Fatal(cmd.Run())
os.Exit(0)
}()
log.Fatal(http.Serve(ln, proxy))
}
func getFreePort() (port int, err error) {
var a *net.TCPAddr
if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil {
var l *net.TCPListener
if l, err = net.ListenTCP("tcp", a); err == nil {
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
}
return
}
func operationNameIs(operationName string) goproxy.RespCondition {
return goproxy.RespConditionFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) bool {
if ctx.UserData == nil {
return false
}
userData := ctx.UserData.(*userData)
return userData.operationName == operationName
})
}