initial commit

This commit is contained in:
2026-05-06 16:24:34 -04:00
commit e6fa98f707
2 changed files with 168 additions and 0 deletions

165
umadump-gametora.go Normal file
View 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)
}