Serve community products from local packs
The proxy serves products from local packs (t5de-api's format) under local IDs from 2,000,000,000: product info and permission answers (with the parent's derivation chain from IMVU), the product's CFL, and the shop's bundle lookup. Community products are taken out of outfit saves and outfit chat commands before IMVU sees them, remembered per account with the outfit they belong to, and added back when the client loads that outfit. Changed requests are re-signed with the session key from the login response. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b2eed27b99
commit
887e8a2364
7 files changed
+1177
-21
No files matched your search
+65
-18
@@ -24,12 +24,20 @@ type state struct {
|
||||
}
|
||||
|
||||
type stateData struct {
|
||||
IDs map[string]int64 `json:"ids"` // product ID -> local ID
|
||||
Worn map[string][]string `json:"worn"` // IMVU user ID -> product IDs
|
||||
IDs map[string]int64 `json:"ids"` // product ID -> local ID
|
||||
Worn map[string]wornOutfit `json:"worn"` // IMVU user ID -> what they wear
|
||||
}
|
||||
|
||||
// wornOutfit is an account's outfit as last saved or sent: the IMVU products IMVU got, and the community products
|
||||
// taken out. The community products are added back only to that same outfit: if IMVU's outfit changed elsewhere
|
||||
// (another client, the website), they'd no longer fit it.
|
||||
type wornOutfit struct {
|
||||
IMVU []int64 `json:"imvu"`
|
||||
Products []string `json:"products"`
|
||||
}
|
||||
|
||||
func loadState(path string) (*state, error) {
|
||||
s := &state{path: path, data: stateData{IDs: map[string]int64{}, Worn: map[string][]string{}}}
|
||||
s := &state{path: path, data: stateData{IDs: map[string]int64{}, Worn: map[string]wornOutfit{}}}
|
||||
b, err := os.ReadFile(path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s, nil
|
||||
@@ -44,7 +52,7 @@ func loadState(path string) (*state, error) {
|
||||
s.data.IDs = map[string]int64{}
|
||||
}
|
||||
if s.data.Worn == nil {
|
||||
s.data.Worn = map[string][]string{}
|
||||
s.data.Worn = map[string]wornOutfit{}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -67,30 +75,57 @@ func (s *state) localID(product string) (int64, error) {
|
||||
return next, s.saveLocked()
|
||||
}
|
||||
|
||||
// worn is the community products the user wears.
|
||||
func (s *state) worn(user string) []string {
|
||||
// worn is the community products the user wears with the IMVU outfit look, if they were recorded with it.
|
||||
func (s *state) worn(user string, look []int64) []string {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return append([]string(nil), s.data.Worn[user]...)
|
||||
}
|
||||
|
||||
// setWorn records the community products the user wears.
|
||||
func (s *state) setWorn(user string, products []string) error {
|
||||
products = append([]string(nil), products...)
|
||||
sort.Strings(products)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if equal(s.data.Worn[user], products) {
|
||||
w, ok := s.data.Worn[user]
|
||||
if !ok || !sameSet(w.IMVU, look) {
|
||||
return nil
|
||||
}
|
||||
if len(products) == 0 {
|
||||
return append([]string(nil), w.Products...)
|
||||
}
|
||||
|
||||
// setWorn records the user's outfit: the IMVU products IMVU got, and the community products taken out.
|
||||
func (s *state) setWorn(user string, imvu []int64, products []string) error {
|
||||
w := wornOutfit{IMVU: append([]int64(nil), imvu...), Products: append([]string(nil), products...)}
|
||||
sort.Slice(w.IMVU, func(i, j int) bool { return w.IMVU[i] < w.IMVU[j] })
|
||||
sort.Strings(w.Products)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
old, had := s.data.Worn[user]
|
||||
if had && sameSet(old.IMVU, w.IMVU) && equal(old.Products, w.Products) {
|
||||
return nil
|
||||
}
|
||||
if len(w.Products) == 0 {
|
||||
if !had {
|
||||
return nil
|
||||
}
|
||||
delete(s.data.Worn, user)
|
||||
} else {
|
||||
s.data.Worn[user] = products
|
||||
s.data.Worn[user] = w
|
||||
}
|
||||
return s.saveLocked()
|
||||
}
|
||||
|
||||
// sameSet reports whether a and b hold the same IDs, in any order.
|
||||
func sameSet(a, b []int64) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
count := map[int64]int{}
|
||||
for _, v := range a {
|
||||
count[v]++
|
||||
}
|
||||
for _, v := range b {
|
||||
if count[v] == 0 {
|
||||
return false
|
||||
}
|
||||
count[v]--
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func equal(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
@@ -117,3 +152,15 @@ func (s *state) saveLocked() error {
|
||||
}
|
||||
return os.Rename(tmp, s.path)
|
||||
}
|
||||
|
||||
// productID is the product a local ID was given to.
|
||||
func (s *state) productID(local int64) (string, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for id, l := range s.data.IDs {
|
||||
if l == local {
|
||||
return id, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
Reference in new issue
Block a user