Skip to content

Commit 034937d

Browse files
author
Vladyslav Lyshenko
committed
feat: add gen codes (generate, toGenCode, parseGenCode, genCodeFromLink)
1 parent b2b6ad6 commit 034937d

3 files changed

Lines changed: 393 additions & 0 deletions

File tree

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,29 @@ for _, kc := range item.Keychains {
8484
}
8585
```
8686

87+
## Gen codes
88+
89+
Generate a Steam inspect URL from item parameters (defindex, paintindex, paintseed, paintwear):
90+
91+
```go
92+
import "github.com/vlydev/cs2-masked-inspect-go/cs2inspect"
93+
94+
// Generate a Steam inspect URL
95+
url, err := cs2inspect.Generate(7, 474, 306, 0.22540508, nil)
96+
97+
// Convert to gen code
98+
item := &cs2inspect.ItemPreviewData{DefIndex: 7, PaintIndex: 474, PaintSeed: 306}
99+
pw := float32(0.22540508); item.PaintWear = &pw
100+
code := cs2inspect.ToGenCode(item, "!gen") // "!gen 7 474 306 0.22540508"
101+
102+
// Parse a gen code
103+
item2, err := cs2inspect.ParseGenCode("!gen 7 474 306 0.22540508")
104+
105+
// Convert an existing inspect link directly to a gen code
106+
code, err := cs2inspect.GenCodeFromLink("steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20001A...", "!gen")
107+
// "!gen 7 474 306 0.22540508"
108+
```
109+
87110
## API Reference
88111

89112
### Functions

cs2inspect/gen_code.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package cs2inspect
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
// InspectBase is the Steam inspect URL prefix.
11+
const InspectBase = "steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20"
12+
13+
// formatFloat formats a float32, stripping trailing zeros (max 8 decimal places).
14+
func formatFloat(v float32) string {
15+
s := strconv.FormatFloat(float64(v), 'f', 8, 32)
16+
s = strings.TrimRight(s, "0")
17+
s = strings.TrimRight(s, ".")
18+
if s == "" {
19+
return "0"
20+
}
21+
return s
22+
}
23+
24+
func serializeStickerPairs(stickers []Sticker, padTo int) []string {
25+
result := []string{}
26+
filtered := []Sticker{}
27+
for _, s := range stickers {
28+
if s.StickerID != 0 {
29+
filtered = append(filtered, s)
30+
}
31+
}
32+
33+
if padTo >= 0 {
34+
slotMap := map[uint32]Sticker{}
35+
for _, s := range filtered {
36+
slotMap[s.Slot] = s
37+
}
38+
for slot := 0; slot < padTo; slot++ {
39+
if s, ok := slotMap[uint32(slot)]; ok {
40+
var wear float32
41+
if s.Wear != nil {
42+
wear = *s.Wear
43+
}
44+
result = append(result, fmt.Sprintf("%d", s.StickerID), formatFloat(wear))
45+
} else {
46+
result = append(result, "0", "0")
47+
}
48+
}
49+
} else {
50+
// Sort by slot
51+
sorted := make([]Sticker, len(filtered))
52+
copy(sorted, filtered)
53+
for i := 0; i < len(sorted)-1; i++ {
54+
for j := i + 1; j < len(sorted); j++ {
55+
if sorted[j].Slot < sorted[i].Slot {
56+
sorted[i], sorted[j] = sorted[j], sorted[i]
57+
}
58+
}
59+
}
60+
for _, s := range sorted {
61+
var wear float32
62+
if s.Wear != nil {
63+
wear = *s.Wear
64+
}
65+
result = append(result, fmt.Sprintf("%d", s.StickerID), formatFloat(wear))
66+
}
67+
}
68+
69+
return result
70+
}
71+
72+
// ToGenCode converts an ItemPreviewData to a gen code string.
73+
//
74+
// The prefix is typically "!gen" or "!g". The gen code format is:
75+
//
76+
// !gen {defindex} {paintindex} {paintseed} {paintwear} [{sticker pairs x5}] [{keychain pairs}]
77+
func ToGenCode(item *ItemPreviewData, prefix string) string {
78+
var wearStr string
79+
if item.PaintWear != nil {
80+
wearStr = formatFloat(*item.PaintWear)
81+
} else {
82+
wearStr = "0"
83+
}
84+
85+
parts := []string{
86+
fmt.Sprintf("%d", item.DefIndex),
87+
fmt.Sprintf("%d", item.PaintIndex),
88+
fmt.Sprintf("%d", item.PaintSeed),
89+
wearStr,
90+
}
91+
92+
hasStickers := false
93+
for _, s := range item.Stickers {
94+
if s.StickerID != 0 {
95+
hasStickers = true
96+
break
97+
}
98+
}
99+
hasKeychains := false
100+
for _, s := range item.Keychains {
101+
if s.StickerID != 0 {
102+
hasKeychains = true
103+
break
104+
}
105+
}
106+
107+
if hasStickers || hasKeychains {
108+
parts = append(parts, serializeStickerPairs(item.Stickers, 5)...)
109+
parts = append(parts, serializeStickerPairs(item.Keychains, -1)...)
110+
}
111+
112+
payload := strings.Join(parts, " ")
113+
if prefix != "" {
114+
return prefix + " " + payload
115+
}
116+
return payload
117+
}
118+
119+
// GenerateOptions holds optional parameters for Generate.
120+
type GenerateOptions struct {
121+
Rarity uint32
122+
Quality uint32
123+
Stickers []Sticker
124+
Keychains []Sticker
125+
}
126+
127+
// Generate creates a full Steam inspect URL from item parameters.
128+
//
129+
// Returns an error if serialization fails (e.g., PaintWear out of range).
130+
func Generate(defIndex, paintIndex, paintSeed uint32, paintWear float32, opts *GenerateOptions) (string, error) {
131+
if opts == nil {
132+
opts = &GenerateOptions{}
133+
}
134+
pw := paintWear
135+
data := &ItemPreviewData{
136+
DefIndex: defIndex,
137+
PaintIndex: paintIndex,
138+
PaintSeed: paintSeed,
139+
PaintWear: &pw,
140+
Rarity: opts.Rarity,
141+
Quality: opts.Quality,
142+
Stickers: opts.Stickers,
143+
Keychains: opts.Keychains,
144+
}
145+
hex, err := Serialize(data)
146+
if err != nil {
147+
return "", err
148+
}
149+
return InspectBase + hex, nil
150+
}
151+
152+
// GenCodeFromLink generates a gen code string from an existing CS2 inspect link.
153+
//
154+
// Deserializes the inspect link and converts the item data to gen code format.
155+
// Returns an error if deserialization fails.
156+
func GenCodeFromLink(hexOrUrl string, prefix string) (string, error) {
157+
item, err := Deserialize(hexOrUrl)
158+
if err != nil {
159+
return "", err
160+
}
161+
return ToGenCode(item, prefix), nil
162+
}
163+
164+
// ParseGenCode parses a gen code string into an ItemPreviewData.
165+
//
166+
// Accepts codes like:
167+
//
168+
// "!gen 7 474 306 0.22540508"
169+
// "7 941 2 0.22540508 0 0 0 0 7203 0 0 0 0 0 36 0"
170+
//
171+
// Returns an error if the code has fewer than 4 tokens.
172+
func ParseGenCode(genCode string) (*ItemPreviewData, error) {
173+
tokens := strings.Fields(strings.TrimSpace(genCode))
174+
175+
// Skip leading !-prefixed command
176+
if len(tokens) > 0 && strings.HasPrefix(tokens[0], "!") {
177+
tokens = tokens[1:]
178+
}
179+
180+
if len(tokens) < 4 {
181+
return nil, fmt.Errorf("gen code must have at least 4 tokens, got: %q", genCode)
182+
}
183+
184+
defIndex64, err := strconv.ParseUint(tokens[0], 10, 32)
185+
if err != nil {
186+
return nil, fmt.Errorf("invalid defindex: %w", err)
187+
}
188+
paintIndex64, err := strconv.ParseUint(tokens[1], 10, 32)
189+
if err != nil {
190+
return nil, fmt.Errorf("invalid paintindex: %w", err)
191+
}
192+
paintSeed64, err := strconv.ParseUint(tokens[2], 10, 32)
193+
if err != nil {
194+
return nil, fmt.Errorf("invalid paintseed: %w", err)
195+
}
196+
paintWear64, err := strconv.ParseFloat(tokens[3], 32)
197+
if err != nil {
198+
return nil, fmt.Errorf("invalid paintwear: %w", err)
199+
}
200+
201+
rest := tokens[4:]
202+
stickers := []Sticker{}
203+
keychains := []Sticker{}
204+
205+
if len(rest) >= 10 {
206+
stickerTokens := rest[:10]
207+
for slot := 0; slot < 5; slot++ {
208+
sid, _ := strconv.ParseUint(stickerTokens[slot*2], 10, 32)
209+
wear64, _ := strconv.ParseFloat(stickerTokens[slot*2+1], 32)
210+
wear := float32(wear64)
211+
if sid != 0 {
212+
s := Sticker{Slot: uint32(slot), StickerID: uint32(sid), Wear: &wear}
213+
stickers = append(stickers, s)
214+
}
215+
}
216+
rest = rest[10:]
217+
}
218+
219+
for i := 0; i+1 < len(rest); i += 2 {
220+
sid, _ := strconv.ParseUint(rest[i], 10, 32)
221+
wear64, _ := strconv.ParseFloat(rest[i+1], 32)
222+
wear := float32(wear64)
223+
if sid != 0 {
224+
kc := Sticker{Slot: uint32(i / 2), StickerID: uint32(sid), Wear: &wear}
225+
keychains = append(keychains, kc)
226+
}
227+
}
228+
229+
pw := float32(paintWear64)
230+
_ = math.MaxFloat32 // ensure math is used
231+
232+
return &ItemPreviewData{
233+
DefIndex: uint32(defIndex64),
234+
PaintIndex: uint32(paintIndex64),
235+
PaintSeed: uint32(paintSeed64),
236+
PaintWear: &pw,
237+
Stickers: stickers,
238+
Keychains: keychains,
239+
}, nil
240+
}

0 commit comments

Comments
 (0)