Compare commits

...

4 Commits

Author SHA1 Message Date
e79282d8cc add user routes 2026-09-04 23:03:50 +08:00
a28c1dec9b changed param logic & add more routes 2026-09-04 18:08:20 +08:00
86cde9bb97 add some routes 2026-09-03 18:48:23 +08:00
c4ccba256d write base server 2026-09-02 18:16:35 +08:00
37 changed files with 521768 additions and 0 deletions

1
.env.example Normal file
View File

@@ -0,0 +1 @@
SERVER=TW

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
resp/
.env
db.json
.idea

68
db/db.go Normal file
View File

@@ -0,0 +1,68 @@
package db
import (
"encoding/json"
"fmt"
"os"
"sync"
)
type Database struct {
Users map[string][]map[string]any `json:"Users"`
}
type UserIdType string
const (
UserId UserIdType = "userId"
Bid UserIdType = "bid"
)
var (
DB Database
mu sync.Mutex
path = "db.json"
)
func Init(servers []string) {
data, err := os.ReadFile(path)
if err == nil {
json.Unmarshal(data, &DB)
}
if DB.Users == nil {
DB.Users = make(map[string][]map[string]any)
}
for _, s := range servers {
if DB.Users[s] == nil {
DB.Users[s] = []map[string]any{}
}
}
Save()
}
func Save() {
mu.Lock()
defer mu.Unlock()
data, _ := json.MarshalIndent(DB, "", " ")
tmp := path + ".tmp"
os.WriteFile(tmp, data, 0644)
os.Rename(tmp, path)
}
func FindUser(id string, idType ...UserIdType) map[string]any {
key := UserId
if len(idType) > 0 {
key = idType[0]
}
for _, user := range DB.Users[os.Getenv("SERVER")] {
if fmt.Sprintf("%v", user[string(key)]) == id {
return user
}
}
return nil
}

50
gen/gen.go Normal file
View File

@@ -0,0 +1,50 @@
//go:build ignore
package main
import (
"os"
"path/filepath"
"strings"
"text/template"
)
const tmpl = `// Code generated by gen/gen.go — DO NOT EDIT
package main
import (
{{- range .}}
_ "{{.}}"
{{- end}}
)
`
func main() {
var packages []string
moduleName := "bdgrp_go"
filepath.WalkDir("routes", func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
if !strings.HasSuffix(d.Name(), ".go") {
return nil
}
pkg := moduleName + "/" + filepath.ToSlash(filepath.Dir(path))
for _, p := range packages {
if p == pkg {
return nil
}
}
packages = append(packages, pkg)
return nil
})
f, _ := os.Create("routes_gen.go")
defer f.Close()
template.Must(template.New("").Parse(tmpl)).Execute(f, packages)
}

25
go.mod Normal file
View File

@@ -0,0 +1,25 @@
module bdgrp_go
go 1.27.1
require (
github.com/gofiber/fiber/v2 v2.52.15
github.com/gookit/color v1.6.1
github.com/joho/godotenv v1.5.1
google.golang.org/protobuf v1.36.12
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.30.0 // indirect
)

41
go.sum Normal file
View File

@@ -0,0 +1,41 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/gofiber/fiber/v2 v2.52.15 h1:Cov1uKeVPyu9q0jSrN60W+A8XNX+/WK8J7cy5osHLIk=
github.com/gofiber/fiber/v2 v2.52.15/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=

64
main.go
View File

@@ -1 +1,65 @@
//go:generate go run gen/gen.go
package main
import (
"log"
"os"
"regexp"
"slices"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gookit/color"
"github.com/joho/godotenv"
"bdgrp_go/db"
"bdgrp_go/routes"
)
const PORT = ":8482"
var servers = []string{"TW", "JP", "GL", "CN"}
func main() {
if _, err := os.Stat(".env"); os.IsNotExist(err) {
log.Fatal("Error: .env not found.")
}
godotenv.Load()
server := os.Getenv("SERVER")
valid := slices.Contains(servers, server)
if !valid {
log.Fatal("Error: Unknown Server.")
}
for _, s := range servers {
os.MkdirAll("resp/"+s, 0755)
}
db.Init(servers)
app := fiber.New(fiber.Config{
StrictRouting: true,
ETag: false,
})
app.Use(logger.New())
re := regexp.MustCompile(`/+`)
app.Use(func(c *fiber.Ctx) error {
c.Path(re.ReplaceAllString(c.Path(), "/"))
return c.Next()
})
routes.LoadAll(app)
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString(os.Getenv("SERVER"))
})
log.Printf("Running on %s", PORT)
color.HEX("#00AABB").Printf("Server: %s\n", server)
app.Listen(PORT)
}

58
master/master.go Normal file
View File

@@ -0,0 +1,58 @@
package master
import (
"bdgrp_go/pbutil"
"bdgrp_go/utils"
"bytes"
"compress/bzip2"
"io"
"os"
"path/filepath"
"sync"
)
var (
cache map[string]any
mu sync.RWMutex
)
func GetMaster() map[string]any {
mu.RLock()
defer mu.RUnlock()
if cache != nil {
return cache
}
mu.RUnlock()
return ReloadMaster()
}
func ReloadMaster() map[string]any {
server := os.Getenv("SERVER")
filePath := filepath.Join("resp", server, "suitemaster.bz2")
fileBuffer, err := os.ReadFile(filePath)
if err != nil {
return cache
}
decrypted, err := utils.Decrypt(fileBuffer)
if err != nil {
return cache
}
decompressed, err := io.ReadAll(bzip2.NewReader(bytes.NewReader(decrypted)))
if err != nil {
return cache
}
result, err := pbutil.Decode("SuiteMasterGetResponse", decompressed)
if err != nil {
return cache
}
mu.Lock()
cache = result
mu.Unlock()
return cache
}

65
pbutil/pbutil.go Normal file
View File

@@ -0,0 +1,65 @@
package pbutil
import (
"encoding/json"
"fmt"
"os"
"strings"
_ "bdgrp_go/proto/cn"
_ "bdgrp_go/proto/gl"
_ "bdgrp_go/proto/jp"
_ "bdgrp_go/proto/tw"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)
func fullName(message string) protoreflect.FullName {
server := strings.ToLower(os.Getenv("SERVER"))
// twdori.proto.ce.UserLogin
return protoreflect.FullName(fmt.Sprintf("%sdori.proto.ce.%s", server, message))
}
func Decode(message string, data []byte) (map[string]any, error) {
mt, err := protoregistry.GlobalTypes.FindMessageByName(fullName(message))
if err != nil {
return nil, fmt.Errorf("message %s not found", message)
}
msg := mt.New().Interface()
if err := proto.Unmarshal(data, msg); err != nil {
return nil, err
}
return ToMap(msg)
}
func Encode(message string, data map[string]any) ([]byte, error) {
mt, err := protoregistry.GlobalTypes.FindMessageByName(fullName(message))
if err != nil {
return nil, fmt.Errorf("message %s not found", message)
}
msg := mt.New().Interface()
// map → json → proto
jsonBytes, _ := json.Marshal(data)
if err := protojson.Unmarshal(jsonBytes, msg); err != nil {
return nil, err
}
return proto.Marshal(msg)
}
func ToMap(msg proto.Message) (map[string]any, error) {
jsonBytes, err := protojson.Marshal(msg)
if err != nil {
return nil, err
}
var result map[string]any
json.Unmarshal(jsonBytes, &result)
return result, nil
}

135797
proto/cn/cn.go Normal file

File diff suppressed because it is too large Load Diff

125786
proto/gl/gl.go Normal file

File diff suppressed because it is too large Load Diff

129339
proto/jp/jp.go Normal file

File diff suppressed because it is too large Load Diff

129280
proto/tw/tw.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
package acdm
import (
"fmt"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Post(path, acdm)
fmt.Println("Loaded:", path)
})
}
func acdm(c *fiber.Ctx) error {
return c.SendString("")
}

53
routes/api/application.go Normal file
View File

@@ -0,0 +1,53 @@
package api
import (
"fmt"
"os"
"path/filepath"
"bdgrp_go/routes"
"bdgrp_go/routes/upstream"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, application)
fmt.Println("Loaded:", path)
})
}
func application(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
cachePath := filepath.Join("resp", server, "application.binpb")
headers := map[string]string{
"User-Agent": c.Get("User-Agent"),
"Content-Type": c.Get("Content-Type"),
"Accept": c.Get("Accept"),
"Accept-Encoding": c.Get("Accept-Encoding"),
"x-clientversion": c.Get("x-clientversion"),
}
if server == "CN" {
headers["x-platformid"] = c.Get("x-platformid")
headers["x-deviceid"] = c.Get("x-deviceid")
headers["x-channelid"] = c.Get("x-channelid")
headers["x-clientplatform"] = c.Get("x-clientplatform")
}
url := fmt.Sprintf("%s/api/application", upstream.BaseUrl())
data, err := upstream.Fetch(url, headers)
if err != nil {
data, err = os.ReadFile(cachePath)
if err != nil {
return c.Status(500).SendString("no cache available")
}
} else {
os.WriteFile(cachePath, data, 0644)
}
return c.Send(data)
}

53
routes/api/cleanupinfo.go Normal file
View File

@@ -0,0 +1,53 @@
package api
import (
"bdgrp_go/routes/upstream"
"fmt"
"os"
"path/filepath"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, cleanupinfo)
fmt.Println("Loaded:", path)
})
}
func cleanupinfo(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
cachePath := filepath.Join("resp", server, "cleanupinfo.binpb")
headers := map[string]string{
"User-Agent": c.Get("User-Agent"),
"Content-Type": c.Get("Content-Type"),
"Accept": c.Get("Accept"),
"Accept-Encoding": c.Get("Accept-Encoding"),
"x-clientversion": c.Get("x-clientversion"),
}
if server == "CN" {
headers["x-platformid"] = c.Get("x-platformid")
headers["x-deviceid"] = c.Get("x-deviceid")
headers["x-channelid"] = c.Get("x-channelid")
headers["x-clientplatform"] = c.Get("x-clientplatform")
}
url := fmt.Sprintf("%s/api/application", upstream.BaseUrl())
data, err := upstream.Fetch(url, headers)
if err != nil {
data, err = os.ReadFile(cachePath)
if err != nil {
return c.Status(500).SendString("no cache available")
}
} else {
os.WriteFile(cachePath, data, 0644)
}
return c.Send(data)
}

View File

@@ -0,0 +1,53 @@
package information
import (
"fmt"
"os"
"path/filepath"
"bdgrp_go/routes"
"bdgrp_go/routes/upstream"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, application)
fmt.Println("Loaded:", path)
})
}
func application(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
cachePath := filepath.Join("resp", server, "overview.binpb")
headers := map[string]string{
"User-Agent": c.Get("User-Agent"),
"Content-Type": c.Get("Content-Type"),
"Accept": c.Get("Accept"),
"Accept-Encoding": c.Get("Accept-Encoding"),
"x-clientversion": c.Get("x-clientversion"),
}
if server == "CN" {
headers["x-platformid"] = c.Get("x-platformid")
headers["x-deviceid"] = c.Get("x-deviceid")
headers["x-channelid"] = c.Get("x-channelid")
headers["x-clientplatform"] = c.Get("x-clientplatform")
}
url := fmt.Sprintf("%s/api/information/overview", upstream.BaseUrl())
data, err := upstream.Fetch(url, headers)
if err != nil {
data, err = os.ReadFile(cachePath)
if err != nil {
return c.Status(500).SendString("no cache available")
}
} else {
os.WriteFile(cachePath, data, 0644)
}
return c.Send(data)
}

View File

@@ -0,0 +1,49 @@
package api
import (
"fmt"
"os"
"path/filepath"
"bdgrp_go/routes"
"bdgrp_go/routes/upstream"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, rewardmap)
fmt.Println("Loaded:", path)
})
}
func rewardmap(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
cachePath := filepath.Join("resp", server, "config.binpb")
headers := map[string]string{
"User-Agent": c.Get("User-Agent"),
"Content-Type": c.Get("Content-Type"),
"Accept": c.Get("Accept"),
"Accept-Encoding": c.Get("Accept-Encoding"),
"x-clientversion": c.Get("x-clientversion"),
"x-platformid": c.Get("x-platformid"),
"x-deviceid": c.Get("x-deviceid"),
"x-channelid": c.Get("x-channelid"),
"x-clientplatform": c.Get("x-clientplatform"),
}
data, err := upstream.Fetch("https://l3-prod-all-bd.bilibiligame.net/api/missionreward.map", headers)
if err != nil {
data, err = os.ReadFile(cachePath)
if err != nil {
return c.Status(500).SendString("no cache available")
}
} else {
os.WriteFile(cachePath, data, 0644)
}
return c.Send(data)
}

21
routes/api/multiroom.go Normal file
View File

@@ -0,0 +1,21 @@
package api
import (
"fmt"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Post(path, multiroom)
fmt.Println("Loaded:", path)
})
}
func multiroom(c *fiber.Ctx) error {
return c.SendString("")
}

View File

@@ -0,0 +1,21 @@
package create
import (
"fmt"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Post(path, multiroom)
fmt.Println("Loaded:", path)
})
}
func multiroom(c *fiber.Ctx) error {
return c.SendString("")
}

View File

@@ -0,0 +1,55 @@
package suite
import (
mastermanager "bdgrp_go/master"
"fmt"
"os"
"path/filepath"
"bdgrp_go/routes"
"bdgrp_go/routes/upstream"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, master)
fmt.Println("Loaded:", path)
})
}
func master(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
cachePath := filepath.Join("resp", server, "suitemaster.bz2")
headers := map[string]string{
"User-Agent": c.Get("User-Agent"),
"Content-Type": c.Get("Content-Type"),
"Accept": c.Get("Accept"),
"Accept-Encoding": c.Get("Accept-Encoding"),
"x-clientversion": c.Get("x-clientversion"),
}
if server == "CN" {
headers["x-platformid"] = c.Get("x-platformid")
headers["x-deviceid"] = c.Get("x-deviceid")
headers["x-channelid"] = c.Get("x-channelid")
headers["x-clientplatform"] = c.Get("x-clientplatform")
}
url := fmt.Sprintf("%s/api/suite/master", upstream.BaseUrl())
data, err := upstream.Fetch(url, headers)
if err != nil {
data, err = os.ReadFile(cachePath)
if err != nil {
return c.Status(500).SendString("no cache available")
}
} else {
os.WriteFile(cachePath, data, 0644)
mastermanager.ReloadMaster()
}
return c.Send(data)
}

View File

@@ -0,0 +1,57 @@
package system
import (
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"fmt"
"os"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, application)
fmt.Println("Loaded:", path)
})
}
func application(c *fiber.Ctx) error {
var offset int
server := os.Getenv("SERVER")
switch server {
case "TW", "CN":
offset = 28800000
break
case "JP":
offset = 32400000
break
case "GL":
offset = 0
break
}
data := map[string]any{
"serverDate": strconv.FormatInt(time.Now().Add(27*time.Hour).UnixMilli(), 10),
"timeZoneRawOffset": offset,
}
encoded, err := pbutil.Encode("ServerSystem", data)
if err != nil {
fmt.Println(err)
return c.Status(500).SendString("")
}
encrypted, err := utils.Encrypt(encoded)
if err != nil {
fmt.Println(err)
}
return c.Send(encrypted)
}

View File

@@ -0,0 +1,49 @@
package system
import (
"bdgrp_go/routes/upstream"
"fmt"
"os"
"path/filepath"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, config)
fmt.Println("Loaded:", path)
})
}
func config(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
cachePath := filepath.Join("resp", server, "config.binpb")
headers := map[string]string{
"User-Agent": c.Get("User-Agent"),
"Content-Type": c.Get("Content-Type"),
"Accept": c.Get("Accept"),
"Accept-Encoding": c.Get("Accept-Encoding"),
"x-clientversion": c.Get("x-clientversion"),
"x-platformid": c.Get("x-platformid"),
"x-deviceid": c.Get("x-deviceid"),
"x-channelid": c.Get("x-channelid"),
"x-clientplatform": c.Get("x-clientplatform"),
}
data, err := upstream.Fetch("https://l3-prod-all-bd.bilibiligame.net/api/system/config", headers)
if err != nil {
data, err = os.ReadFile(cachePath)
if err != nil {
return c.Status(500).SendString("no cache available")
}
} else {
os.WriteFile(cachePath, data, 0644)
}
return c.Send(data)
}

21
routes/api/track.go Normal file
View File

@@ -0,0 +1,21 @@
package api
import (
"fmt"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, track)
fmt.Println("Loaded:", path)
})
}
func track(c *fiber.Ctx) error {
return c.SendString("")
}

View File

@@ -0,0 +1,21 @@
package heart
import (
"fmt"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, heart)
fmt.Println("Loaded:", path)
})
}
func heart(c *fiber.Ctx) error {
return c.SendString("")
}

141
routes/api/user/index.go Normal file
View File

@@ -0,0 +1,141 @@
package user
import (
"bdgrp_go/db"
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"fmt"
"maps"
"os"
"time"
"github.com/google/uuid"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Post(path, postUser)
fmt.Println("Loaded:", path)
})
}
func postUser(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
decrypted, err := utils.Decrypt(c.Body())
if err != nil {
fmt.Println(err)
}
decoded, err := pbutil.Decode("UserPostRequest", decrypted)
if err != nil {
fmt.Println(err)
}
var (
userName string
introduction string
)
switch server {
case "TW":
userName = "新人工作人員"
introduction = "你好!"
case "JP":
userName = "新人スタッフ"
introduction = "よろしくお願いします!"
case "GL":
userName = "New Staff"
introduction = "Nice to meet you!"
}
hash := uuid.New().String()
users := db.DB.Users[server]
nextId := 1001
if len(users) > 0 {
last := users[len(users)-1]
if id, ok := last["userId"].(float64); ok {
nextId = int(id) + 1
}
}
newUser := map[string]any{
"userId": nextId,
"hash": hash,
"userName": userName,
"clientVersion": decoded["clientVersion"].(string),
"platform": decoded["platform"].(string),
"deviceModel": decoded["deviceModel"].(string),
"operatingSystem": decoded["operatingSystem"].(string),
"birthMonth": "199001",
"tutorialStatus": "end",
"introduction": introduction,
"unknownString": "standard",
"tutorialEndedAt": time.Now().UnixMilli(),
}
newGamedata := map[string]any{
"rank": 114,
"exp": 67,
"coin": 114514,
"mainDeck": 1,
"paidStar": 0,
"freeStar": 514,
"seal": 0,
"degree": []any{100, nil},
"publishTotalDeckPowerFlg": false,
"publishBandRankFlg": false,
"publishMusicClearedFlg": false,
"publishMusicFullComboFlg": false,
"publishHighScoreRatingFlg": false,
"pooledExp": 0,
"totalExp": 0,
"nextExp": 0,
"publishUpdatedAtFlg": true,
"startDashLoginBonusReceiveFlg": false,
"publishMusicAllPerfectFlg": false,
"publishDeckRankFlg": false,
"publishStageAchievementConditionsFlg": false,
"publishStageFriendRankingFlg": true,
"publishCharacterRankFlg": false,
"loginDays": 0,
"useProfileSettingDegree": true,
"useProfileSettingSituation": true,
"selectedCharacterType": "character_situation",
}
maps.Copy(newUser, newGamedata)
db.DB.Users[server] = append(db.DB.Users[server], newUser)
db.Save()
data := map[string]any{
"userId": fmt.Sprintf("%v", newUser["userId"]),
"hash": newUser["hash"],
"userName": newUser["userName"],
"clientVersion": newUser["clientVersion"],
"platform": newUser["platform"],
"deviceModel": newUser["deviceModel"],
"operatingSystem": newUser["operatingSystem"],
"tutorialStatus": newUser["tutorialStatus"],
"introduction": newUser["introduction"],
"unknownString": newUser["unknownString"],
"tutorialEndedAt": fmt.Sprintf("%v", newUser["tutorialEndedAt"]),
}
encoded, err := pbutil.Encode("UserRegistration", data)
if err != nil {
fmt.Println(err)
}
encrypted, err := utils.Encrypt(encoded)
if err != nil {
fmt.Println(err)
}
return c.Send(encrypted)
}

131
routes/api/user/login.go Normal file
View File

@@ -0,0 +1,131 @@
package user
import (
"bdgrp_go/db"
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"fmt"
"maps"
"os"
"time"
"github.com/google/uuid"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Post(path, login)
fmt.Println("Loaded:", path)
})
}
func login(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
decrypted, err := utils.Decrypt(c.Body())
if err != nil {
fmt.Println(err)
}
decoded, err := pbutil.Decode("UserLogin", decrypted)
if err != nil {
fmt.Println(err)
}
user := db.FindUser(fmt.Sprintf("%v", decoded["bid"]), db.Bid)
if user == nil {
hash := uuid.New().String()
users := db.DB.Users[server]
nextId := 1001
if len(users) > 0 {
last := users[len(users)-1]
if id, ok := last["userId"].(float64); ok {
nextId = int(id) + 1
}
}
newUser := map[string]any{
"bid": decoded["bid"].(string),
"userId": nextId,
"hash": hash,
"userName": "新手工作人员",
"clientVersion": decoded["clientVersion"].(string),
"platform": decoded["platform"].(string),
"deviceModel": decoded["deviceModel"].(string),
"operatingSystem": decoded["operatingSystem"].(string),
"birthMonth": "199001",
"tutorialStatus": "end",
"introduction": "请多多关照!",
"unknownString": "standard",
"tutorialEndedAt": time.Now().UnixMilli(),
}
newGamedata := map[string]any{
"rank": 114,
"exp": 67,
"coin": 114514,
"mainDeck": 1,
"paidStar": 0,
"freeStar": 514,
"seal": 0,
"degree": []any{100, nil},
"publishTotalDeckPowerFlg": false,
"publishBandRankFlg": false,
"publishMusicClearedFlg": false,
"publishMusicFullComboFlg": false,
"publishHighScoreRatingFlg": false,
"pooledExp": 0,
"totalExp": 0,
"nextExp": 0,
"publishUpdatedAtFlg": true,
"startDashLoginBonusReceiveFlg": false,
"publishMusicAllPerfectFlg": false,
"publishDeckRankFlg": false,
"publishStageAchievementConditionsFlg": false,
"publishStageFriendRankingFlg": true,
"publishCharacterRankFlg": false,
"loginDays": 0,
"useProfileSettingDegree": true,
"useProfileSettingSituation": true,
"selectedCharacterType": "character_situation",
}
maps.Copy(newUser, newGamedata)
db.DB.Users[server] = append(db.DB.Users[server], newUser)
db.Save()
user = newUser
}
data := map[string]any{
"userId": fmt.Sprintf("%v", user["userId"]),
"hash": user["hash"],
"userName": user["userName"],
"clientVersion": user["clientVersion"],
"platform": user["platform"],
"deviceModel": user["deviceModel"],
"operatingSystem": user["operatingSystem"],
"tutorialStatus": user["tutorialStatus"],
"introduction": user["introduction"],
"unknownString": user["unknownString"],
"tutorialEndedAt": fmt.Sprintf("%v", user["tutorialEndedAt"]),
}
encoded, err := pbutil.Encode("UserRegistration", data)
if err != nil {
fmt.Println(err)
}
encrypted, err := utils.Encrypt(encoded)
if err != nil {
fmt.Println(err)
}
return c.Send(encrypted)
}

View File

@@ -0,0 +1,59 @@
package user
import (
"bdgrp_go/db"
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"fmt"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, get)
app.Put(path, putUser)
fmt.Println("Loaded:", path)
})
}
func get(c *fiber.Ctx) error {
return c.SendString("")
}
func putUser(c *fiber.Ctx) error {
decrypted, _ := utils.Decrypt(c.Body())
decoded, err := pbutil.Decode("UserPutRequest", decrypted)
if err != nil {
fmt.Println(err)
}
userId := c.Params("userid")
user := db.FindUser(userId)
if v, ok := decoded["userName"].(string); ok && v != "" {
user["userName"] = v
}
if v, ok := decoded["birthMonth"].(string); ok && v != "" {
user["birthMonth"] = v
}
if v, ok := decoded["introduction"].(string); ok && v != "" {
user["introduction"] = v
}
if v, ok := decoded["tutorialStatus"].(string); ok && v != "" {
user["tutorialStatus"] = v
}
if v, ok := decoded["mainDeck"].(float64); ok && v != 0 {
user["mainDeck"] = uint64(v)
}
if v, ok := decoded["degree"].(float64); ok && v != 0 {
user["degree"] = uint32(v)
}
db.Save()
return c.SendString("")
}

View File

@@ -0,0 +1,70 @@
package param_userid
import (
"bdgrp_go/db"
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"crypto/sha1"
"fmt"
"os"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Put(path, Auth)
fmt.Println("Loaded:", path)
})
}
func Auth(c *fiber.Ctx) error {
server := os.Getenv("SERVER")
userid := c.Params("userid")
decrypted, err := utils.Decrypt(c.Body())
if err != nil {
return c.Status(500).SendString("")
}
decoded, err := pbutil.Decode("UserAuthRequest", decrypted)
if err != nil {
return c.Status(500).SendString("")
}
hash := fmt.Sprintf("%x", sha1.Sum([]byte(userid+":")))
errData := map[string]any{
"httpStatus": 403,
"errorCode": "",
"errorMessage": "signature invalid.",
}
sendErr := func() error {
encoded, _ := pbutil.Encode("ClientErrorResponse", errData)
encrypted, _ := utils.Encrypt(encoded)
return c.Status(403).Send(encrypted)
}
user := db.FindUser(userid)
if hash != decoded["hash1"] || user == nil {
return sendErr()
}
if c.Get("x-signature") != user["hash"] {
return sendErr()
}
// 更新 clientVersion
for i, u := range db.DB.Users[server] {
if fmt.Sprintf("%v", u["userId"]) == userid {
db.DB.Users[server][i]["clientVersion"] = c.Get("x-clientversion")
break
}
}
db.Save()
return c.Send(nil)
}

View File

@@ -0,0 +1,21 @@
package param_userid
import (
"fmt"
"bdgrp_go/routes"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Put(path, fcm)
fmt.Println("Loaded:", path)
})
}
func fcm(c *fiber.Ctx) error {
return c.SendString("")
}

View File

@@ -0,0 +1,87 @@
package param_userid
import (
"bdgrp_go/db"
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"fmt"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Get(path, GetUser)
fmt.Println("Loaded:", path)
})
}
func GetUser(c *fiber.Ctx) error {
userid := c.Params("userid")
user := db.FindUser(userid)
if user == nil {
return c.Status(404).SendString("")
}
userId := fmt.Sprintf("%v", user["userId"])
data := map[string]any{
"userRegistration": map[string]any{
"userId": userId,
"hash": user["hash"],
"userName": user["userName"],
"clientVersion": user["clientVersion"],
"platform": user["platform"],
"deviceModel": user["deviceModel"],
"operatingSystem": user["operatingSystem"],
"birthMonth": user["birthMonth"],
"tutorialStatus": user["tutorialStatus"],
"introduction": user["introduction"],
"tutorialEndedAt": fmt.Sprintf("%v", user["tutorialEndedAt"]),
},
"userGamedata": map[string]any{
"userId": userId,
"rank": user["rank"],
"exp": user["exp"],
"coin": fmt.Sprintf("%v", user["coin"]),
"mainDeck": user["mainDeck"],
"paidStar": user["paidStar"],
"freeStar": user["freeStar"],
"seal": user["seal"],
"degree": user["degree"].([]any)[0],
"publishTotalDeckPowerFlg": user["publishTotalDeckPowerFlg"],
"publishBandRankFlg": user["publishBandRankFlg"],
"publishMusicClearedFlg": user["publishMusicClearedFlg"],
"publishMusicFullComboFlg": user["publishMusicFullComboFlg"],
"publishHighScoreRatingFlg": user["publishHighScoreRatingFlg"],
"pooledExp": fmt.Sprintf("%v", user["pooledExp"]),
"totalExp": fmt.Sprintf("%v", user["totalExp"]),
"nextExp": user["nextExp"],
"publishUpdatedAtFlg": user["publishUpdatedAtFlg"],
"userPaidStarRecallResponse": nil,
"startDashLoginBonusReceiveFlg": user["startDashLoginBonusReceiveFlg"],
"publishMusicAllPerfectFlg": user["publishMusicAllPerfectFlg"],
"publishDeckRankFlg": user["publishDeckRankFlg"],
"publishStageAchievementConditionsFlg": user["publishStageAchievementConditionsFlg"],
"publishStageFriendRankingFlg": user["publishStageFriendRankingFlg"],
"publishCharacterRankFlg": user["publishCharacterRankFlg"],
"loginDays": user["loginDays"],
},
}
encoded, err := pbutil.Encode("UserGetResponse", data)
if err != nil {
fmt.Println(err)
}
encrypted, err := utils.Encrypt(encoded)
if err != nil {
fmt.Println(err)
}
return c.Send(encrypted)
}

View File

@@ -0,0 +1,38 @@
package validation
import (
"bdgrp_go/pbutil"
"bdgrp_go/routes"
"bdgrp_go/utils"
"fmt"
"github.com/gofiber/fiber/v2"
)
func init() {
path := routes.PathOf()
routes.Register(func(app *fiber.App) {
app.Post(path, ngword)
fmt.Println("Loaded:", path)
})
}
func ngword(c *fiber.Ctx) error {
data := map[string]any{
"isBadWord": false,
}
encoded, err := pbutil.Encode("NgWordResponse", data)
if err != nil {
fmt.Println(err)
return c.Status(500).SendString("")
}
encrypted, err := utils.Encrypt(encoded)
if err != nil {
fmt.Println(err)
}
return c.Send(encrypted)
}

23
routes/helper.go Normal file
View File

@@ -0,0 +1,23 @@
package routes
import (
"regexp"
"runtime"
"strings"
)
func PathOf() string {
_, file, _, _ := runtime.Caller(1)
idx := strings.Index(file, "routes/")
path := file[idx+len("routes"):]
path = strings.TrimSuffix(path, ".go")
if strings.HasSuffix(path, "/index") {
path = strings.TrimSuffix(path, "index")
}
re := regexp.MustCompile(`param_(\w+)`)
path = re.ReplaceAllString(path, ":$1")
return path
}

17
routes/registry.go Normal file
View File

@@ -0,0 +1,17 @@
package routes
import "github.com/gofiber/fiber/v2"
type RouteHandler func(*fiber.App)
var registry []RouteHandler
func Register(handler RouteHandler) {
registry = append(registry, handler)
}
func LoadAll(app *fiber.App) {
for _, handler := range registry {
handler(app)
}
}

View File

@@ -0,0 +1,31 @@
package upstream
import (
"io"
"net/http"
"os"
)
var BaseUrls = map[string]string{
"TW": "https://v1010-bd.mobimon.com.tw",
"JP": "https://api.garupa.jp",
"GL": "https://api.app-bang-dream-gbp.com",
"CN": "https://l3-prod-all-bd.bilibiligame.net",
}
func Fetch(url string, headers map[string]string) ([]byte, error) {
req, _ := http.NewRequest("GET", url, nil)
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func BaseUrl() string {
return BaseUrls[os.Getenv("SERVER")]
}

17
routes_gen.go Normal file
View File

@@ -0,0 +1,17 @@
// Code generated by gen/gen.go — DO NOT EDIT
package main
import (
_ "bdgrp_go/routes/api/acdm"
_ "bdgrp_go/routes/api"
_ "bdgrp_go/routes/api/information"
_ "bdgrp_go/routes/api/multiroom/create"
_ "bdgrp_go/routes/api/suite"
_ "bdgrp_go/routes/api/system"
_ "bdgrp_go/routes/api/user/heart"
_ "bdgrp_go/routes/api/user"
_ "bdgrp_go/routes/api/user/param_userid"
_ "bdgrp_go/routes/api/validation"
_ "bdgrp_go/routes"
_ "bdgrp_go/routes/upstream"
)

81
utils/crypto.go Normal file
View File

@@ -0,0 +1,81 @@
package utils
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
"os"
)
var keys = map[string]string{
"TW": "mikumikulukaluka",
"JP": "mikumikulukaluka",
"GL": "RXzFAC87WR28eNcP",
"CN": "wakakabiliwakaka",
}
var ivs = map[string]string{
"TW": "lukalukamikumiku",
"JP": "lukalukamikumiku",
"GL": "hrk0k7knXlit3S_y",
"CN": "biliwakakawohaha",
}
func getKeyIV() ([]byte, []byte, error) {
server := os.Getenv("SERVER")
key, ok := keys[server]
if !ok {
return nil, nil, fmt.Errorf("unknown server: %s", server)
}
return []byte(key), []byte(ivs[server]), nil
}
func Decrypt(buf []byte) ([]byte, error) {
key, iv, err := getKeyIV()
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(buf)%aes.BlockSize != 0 {
return nil, fmt.Errorf("invalid block size")
}
decrypted := make([]byte, len(buf))
cipher.NewCBCDecrypter(block, iv).CryptBlocks(decrypted, buf)
// 手動移除 padding(setAutoPadding(false) 對應)
padLen := int(decrypted[len(decrypted)-1])
if padLen > 0 && padLen <= 16 && padLen <= len(decrypted) {
decrypted = decrypted[:len(decrypted)-padLen]
}
return decrypted, nil
}
func Encrypt(buf []byte) ([]byte, error) {
key, iv, err := getKeyIV()
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// 手動加 PKCS7 padding(setAutoPadding(true) 對應)
blockSize := block.BlockSize()
padding := blockSize - len(buf)%blockSize
padded := append(buf, bytes.Repeat([]byte{byte(padding)}, padding)...)
encrypted := make([]byte, len(padded))
cipher.NewCBCEncrypter(block, iv).CryptBlocks(encrypted, padded)
return encrypted, nil
}