-
Notifications
You must be signed in to change notification settings - Fork 4
/
proxy.go
368 lines (327 loc) · 10.8 KB
/
proxy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/elazarl/goproxy"
)
type Proxy struct {
ClientActive bool
LatestClientIP string
LatestClientUserAgent string
} //the struct ready for Wails
var (
//blackList = []string{}
blockUpdate bool //if block update
urlMap = map[string]string{} //global url mapping
server *http.Server //the proxy server
logServer *http.Server //a global variable to hold the log server
logMsgs []string //the logs
firstClientIP string //the first client IP
firstClientUserAgent string //the first client User-Agent
px = &Proxy{}
lastError error = nil
)
func init() {
// Initialize log server at software start-up
px = &Proxy{}
px.OtherLog(0)
StartLogServer()
//px = &Proxy{}
}
// log record
func Logger(logType, logContent string) {
logMsg := fmt.Sprintf("%s-%s-%s", time.Now().Format("15:04:05"), logType, logContent)
log.Printf(logMsg)
logMsgs = append(logMsgs, logMsg)
}
// log server,transfer the logs to the frontend
// browse logs : http://localhost:29090/logs
func StartLogServer() {
if logServer == nil {
logServer = &http.Server{Addr: ":29090"} // use a new server to serve the logs
http.HandleFunc("/logs", SendLogs)
http.HandleFunc("/client-status", ClientStatus)
go func() {
if err := logServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
Logger("ERROR", fmt.Sprintf("Failed to start log server: %v", err))
}
}()
}
}
// initialize the proxy server
func (px *Proxy) InitProxy() *goproxy.ProxyHttpServer {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = false
proxy.OnRequest().DoFunc(px.ClientRequest) //mapping url
proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
// get and record the client IP and User-Agent (HTTPS)
clientIP := strings.Split(ctx.Req.RemoteAddr, ":")[0]
userAgent := ctx.Req.Header.Get("User-Agent")
Logger("HTTPS", host)
fmt.Println("(HTTPS)Captured userAgent:", userAgent)
// Update the latest client information
px.LatestClientIP = clientIP
px.LatestClientUserAgent = userAgent
px.ClientActive = true
var deviceType string
if userAgent != "" {
deviceType = Detector(userAgent)
}
Logger("HTTPS", fmt.Sprintf("***New client connected: IP=%s, device=%s", clientIP, deviceType))
//blk ps4 update server
if blockUpdate && strings.Contains(host, "gs-sec.ww.np.dl.playstation.net") {
return goproxy.RejectConnect, host
}
return goproxy.OkConnect, host
})
return proxy
}
// adjust the client activity status
func (px *Proxy) CheckClientActivity() {
go func() {
for {
time.Sleep(90 * time.Second) // every *seconds check
if px.ClientActive {
px.ClientActive = false // reset the client activity status
} else {
// if the client is not active, then clear the client info
firstClientIP = ""
firstClientUserAgent = ""
Logger("INFO", "***Client disconnected")
}
}
}()
}
// select mode to set url mapping or block update
// update the urlMap or blockUpdate's status based on the mode
func (px *Proxy) SetMode(mode int, jsonLink string) (string, error) {
if mode == 1 {
psn := &PSN{}
gameDetails, err := psn.Details(jsonLink)
if err != nil {
Logger("ERROR", fmt.Sprintf("Error getting game details: %v", err))
return "", err
}
var details map[string]string
err = json.Unmarshal([]byte(gameDetails), &details)
if err != nil {
Logger("ERROR", fmt.Sprintf("Error unmarshalling game details: %v", err))
return "", err
}
lastJSONURL := details["LastJSONURL"]
urlMap[lastJSONURL] = jsonLink
gameName := details["GameName"]
Logger("INFO", fmt.Sprintf("URL mapping:\n[%s]%s\nwill be mapped to\n[%s]%s", psn.ExtractVersion(lastJSONURL), lastJSONURL, psn.ExtractVersion(jsonLink), jsonLink))
Logger("INFO", fmt.Sprintf("MODE1 (JSON mapping) enabled.\nGame: %s\nDowngrade version: %s", gameName, psn.ExtractVersion(jsonLink)))
return gameDetails, nil
} else if mode == 2 {
blockUpdate = true
Logger("INFO", "MODE 2 (Block update) enabled")
return "", nil
}
return "", fmt.Errorf("invalid mode")
}
type ProxyInfo struct {
IP string
Port string
}
// start proxy server. already use setmode to set url mapping or block update
func (px *Proxy) StartProxy(port string) {
if server != nil {
px.StopProxy()
}
psn := &PSN{}
ip := psn.LocalIP()
proxy := px.InitProxy()
address := ip + ":" + port
server = &http.Server{
Addr: address,
Handler: proxy,
}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
Logger("ERROR", fmt.Sprintf("Failed to start proxy server, please try another PORT: %v", err))
lastError = err
} else {
lastError = nil
}
}()
Logger("INFO", fmt.Sprintf(`PROXY SERVER STARTED
IP : %s PORT : %s
`, ip, port))
log.Printf("[StartProxy] blockUpdate: %v\n -------urlMap: %v", blockUpdate, urlMap)
}
// send the server start failure info to the frontend
func (px *Proxy) GetLastServerError() string {
if lastError != nil {
return lastError.Error()
}
return ""
}
func (px *Proxy) StopProxy() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if server != nil {
if err := server.Shutdown(ctx); err != nil { //close the proxy server
Logger("ERROR", fmt.Sprintf("Failed to shutdown proxy server: %v", err))
}
server = nil
Logger("INFO", fmt.Sprintf("PROXY SERVER STOPPED"))
}
blockUpdate = false
urlMap = make(map[string]string) //initialize the urlMap
log.Printf("[StopProxy] Reset blockUpdate and urlMap to initial values\n -----blockUpdate: %v\n -----urlMap: %v", blockUpdate, urlMap)
}
// map the request to the target url, and get the size of the .pkg file
func (px *Proxy) ClientRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
psn := &PSN{}
fullURL := req.URL.String() // URL (including '?' parameters) captured from the console request
baseURL := strings.Split(fullURL, "?")[0] //no ?
// Capture and update client IP and User-Agent for HTTP connections
clientIP := strings.Split(ctx.Req.RemoteAddr, ":")[0]
userAgent := ctx.Req.Header.Get("User-Agent")
fmt.Println("(HTTP)Captured userAgent:", userAgent)
if px.LatestClientIP != clientIP || px.LatestClientUserAgent != userAgent {
px.LatestClientIP = clientIP
px.LatestClientUserAgent = userAgent
px.ClientActive = true
deviceType := Detector(userAgent)
Logger("HTTP", fmt.Sprintf("***New client connected: IP=%s, device=%s", clientIP, deviceType))
}
// URL Mapping
if newURL, ok := urlMap[baseURL]; ok {
parsedNewURL, err := url.Parse(newURL)
if err != nil {
Logger("ERROR", fmt.Sprintf("Error parsing new URL: %s\n", err))
return req, nil
}
req.URL = parsedNewURL
Logger("INFO", fmt.Sprintf(`URL mapping successful!%s --> %s`, psn.ExtractVersion(baseURL), psn.ExtractVersion(newURL)))
return req, nil
}
// get the size of the .pkg file
if strings.Contains(fullURL, ".pkg") {
go func() {
size, err := Sizer(fullURL)
if err != nil {
Logger("ERROR", fmt.Sprintf("Failed to get .pkg file size: %v", err))
} else {
var logMsg string
cusaID := regexp.MustCompile(`CUSA\d{5}`).FindString(fullURL)
ppsaID := regexp.MustCompile(`PPSA\d{5}`).FindString(fullURL)
switch {
case strings.Contains(fullURL, "ppkgo") && strings.Contains(fullURL, "DP.pkg"):
logMsg = fmt.Sprintf("[Downloading Delta Patch][%s][%s]\n%s", cusaID, size, fullURL)
case strings.Contains(fullURL, "/ppkgo"):
logMsg = fmt.Sprintf("[Downloading Patch][%s][%s]\n%s", cusaID, size, fullURL)
case strings.Contains(fullURL, "/appkgo"):
logMsg = fmt.Sprintf("[Downloading Base Game][%s][%s]\n%s", cusaID, size, fullURL)
case strings.Contains(fullURL, "PPSA"):
logMsg = fmt.Sprintf("[Downloading PS5 Game][%s][%s]\n%s", ppsaID, size, fullURL)
default:
logMsg = fmt.Sprintf("[Downloading unknown File][%s]\n%s", size, fullURL)
}
Logger("HTTP", logMsg)
}
}()
} else {
Logger("HTTP", "\x20\x20"+fullURL)
}
return req, nil
}
// get the size of the .pkg file
func Sizer(pkgURL string) (string, error) {
resp, err := http.Head(pkgURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
size := resp.Header.Get("Content-Length")
if size == "" {
return "UNKNOWN SIZE", nil
}
sizeInt, err := strconv.ParseInt(size, 10, 64)
if err != nil {
return "", err
}
sizeInMB := fmt.Sprintf("%.2fMB", float64(sizeInt)/1024/1024)
return sizeInMB, nil
}
// set CORS for the log server
func SetupCORS(w *http.ResponseWriter, req *http.Request) {
(*w).Header().Set("Access-Control-Allow-Origin", "*")
(*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
(*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
}
// send logs to frontend using json
func SendLogs(w http.ResponseWriter, r *http.Request) {
SetupCORS(&w, r)
if r.Method == "OPTIONS" {
return
}
response := map[string]interface{}{
"logs": logMsgs,
"firstClientIP": firstClientIP,
"firstClientUserAgent": firstClientUserAgent,
}
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(w, "Failed to marshal logs", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResponse)
}
// ClientStatus handles the /client-status endpoint
func ClientStatus(w http.ResponseWriter, r *http.Request) {
SetupCORS(&w, r)
if r.Method == "OPTIONS" {
return
}
response := map[string]interface{}{
"clientActive": px.ClientActive,
"latestClientIP": px.LatestClientIP,
"latestClientUserAgent": px.LatestClientUserAgent,
}
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(w, "Failed to marshal client status", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResponse)
}
func (px *Proxy) OtherLog(num int) {
switch num {
case 1:
Logger("INFO", "MODE 1 selected,please input correct JSON link first")
case 0:
Logger("INFO", `
RewindPS4 1.0
Github: https://github.com/Ailyth99/RewindPS4`)
}
}
func Detector(userAgent string) string {
switch {
case strings.Contains(userAgent, "PlayStation 5"):
return "PS5"
case strings.Contains(userAgent, "PlayStation 4"):
return "PS4"
case strings.Contains(userAgent, "Vita"):
return "PSVita"
case strings.Contains(userAgent, "PlayStation 3"), strings.Contains(userAgent, "PS3"):
return "PS3"
case strings.Contains(userAgent, "nnAcc"), strings.Contains(userAgent, "nnPre"):
return "Switch"
default:
return "Unknown"
}
}