initial commit
This commit is contained in:
3
go.mod
Normal file
3
go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module git.sunturtle.xyz/zephyr/umadump-gametora
|
||||
|
||||
go 1.25.6
|
||||
165
umadump-gametora.go
Normal file
165
umadump-gametora.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DumpCard struct {
|
||||
ID int `json:"card_id"`
|
||||
Level int `json:"talent_level"`
|
||||
Rarity int `json:"rarity"`
|
||||
CreateTime int `json:"create_time"`
|
||||
}
|
||||
|
||||
type DumpSupport struct {
|
||||
ID int `json:"supportCardId"`
|
||||
Level int `json:"level"`
|
||||
LB int `json:"limitBreakCount"`
|
||||
MaxLevel int `json:"maxLevel"`
|
||||
CreateTime int `json:"createTime"`
|
||||
Exp int `json:"exp"`
|
||||
Stock int `json:"stock"`
|
||||
Favorite bool `json:"isFavoriteLock"`
|
||||
BestTraining int `json:"bestTraining"`
|
||||
}
|
||||
|
||||
type Collection struct {
|
||||
App string `json:"app"` // "gametora"
|
||||
Game string `json:"game"` // "umamusume"
|
||||
Type string `json:"type"` // "collection"
|
||||
Version int `json:"version"` // 4
|
||||
Timestamp string `json:"timestamp"` // time.Now().String()
|
||||
Servers map[string]CollectionServer `json:"servers"`
|
||||
}
|
||||
|
||||
type CollectionServer struct {
|
||||
CharCards map[string]int `json:"charCards"`
|
||||
Supports map[string]int `json:"supports"`
|
||||
Veterans struct{} `json:"-"` // `json:"veterans"`
|
||||
}
|
||||
|
||||
type Localize struct {
|
||||
CardID int `json:"card_id"` // character card, or trainee
|
||||
SupportID int `json:"id"` // support card
|
||||
TID string `json:"tid"`
|
||||
// many other fields we don't need
|
||||
}
|
||||
|
||||
func decode[T any](r io.Reader) ([]T, error) {
|
||||
var l []T
|
||||
d := json.NewDecoder(r)
|
||||
err := d.Decode(&l)
|
||||
return l, err
|
||||
}
|
||||
|
||||
func tidMap(ctx context.Context, url string) (map[int]string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := decode[Localize](resp.Body)
|
||||
m := make(map[int]string, len(l))
|
||||
for _, v := range l {
|
||||
m[cmp.Or(v.CardID, v.SupportID)] = v.TID
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func filejson[T any](path string) ([]T, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decode[T](f)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
dumpDir string
|
||||
out string
|
||||
)
|
||||
flag.StringVar(&dumpDir, "dump", ".", "`dir`ectory containing umadump output files")
|
||||
flag.StringVar(&out, "o", "", "output `file` (default stdout)")
|
||||
flag.Parse()
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
stop()
|
||||
}()
|
||||
|
||||
var err error
|
||||
w := os.Stdout
|
||||
if out != "" {
|
||||
w, err = os.Create(out)
|
||||
if err != nil {
|
||||
fatal("output", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
charas, err := filejson[DumpCard](filepath.Join(dumpDir, "card_data.json"))
|
||||
if err != nil {
|
||||
fatal("character dump", slog.Any("err", err))
|
||||
}
|
||||
supports, err := filejson[DumpSupport](filepath.Join(dumpDir, "support_card_data.json"))
|
||||
if err != nil {
|
||||
fatal("support card dump", slog.Any("err", err))
|
||||
}
|
||||
charaTID, err := tidMap(ctx, "https://gametora.com/loc/umamusume/character_cards.json")
|
||||
if err != nil {
|
||||
fatal("character localization file", slog.Any("err", err))
|
||||
}
|
||||
supportTID, err := tidMap(ctx, "https://gametora.com/loc/umamusume/support_cards.json")
|
||||
if err != nil {
|
||||
fatal("support card localization file", slog.Any("err", err))
|
||||
}
|
||||
|
||||
charaLevels := make(map[string]int, len(charas))
|
||||
for _, v := range charas {
|
||||
charaLevels[charaTID[v.ID]] = v.Rarity
|
||||
}
|
||||
supportLevels := make(map[string]int, len(supports))
|
||||
for _, v := range supports {
|
||||
supportLevels[supportTID[v.ID]] = v.LB
|
||||
}
|
||||
|
||||
collection := Collection{
|
||||
App: "gametora",
|
||||
Game: "umamusume",
|
||||
Type: "collection",
|
||||
Version: 4,
|
||||
Timestamp: time.Now().String(),
|
||||
Servers: map[string]CollectionServer{
|
||||
"en": {
|
||||
CharCards: charaLevels,
|
||||
Supports: supportLevels,
|
||||
},
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(collection)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := w.Write(b); err != nil {
|
||||
fatal("writing output", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
func fatal(msg string, args ...any) {
|
||||
slog.Error(msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user