Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
ygopro-deck-encode
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
ygopro-deck-encode
Commits
4fd99c63
Commit
4fd99c63
authored
May 21, 2025
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
optimize ygom 29 bit thing
parent
8851e0ef
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
199 additions
and
67 deletions
+199
-67
src/ygom.ts
src/ygom.ts
+101
-67
tests/sample.spec.ts
tests/sample.spec.ts
+98
-0
No files found.
src/ygom.ts
View file @
4fd99c63
import
{
fromBase64Url
,
toBase64Url
}
from
"
./base64
"
;
import
{
fromBase64Url
,
toBase64Url
}
from
"
./base64
"
;
// === 常量
区域
===
// === 常量 ===
const
QUERY_YGO_TYPE
=
'
ygotype
'
;
const
QUERY_YGO_TYPE
=
'
ygotype
'
;
const
QUERY_VERSION
=
'
v
'
;
const
QUERY_VERSION
=
'
v
'
;
const
ARG_DECK
=
'
deck
'
;
const
ARG_DECK
=
'
deck
'
;
const
QUERY_DECK
=
'
d
'
;
const
QUERY_DECK
=
'
d
'
;
const
QUERY_NAME
=
'
name
'
;
const
QUERY_NAME
=
'
name
'
;
const
URL_SCHEME_HTTP
=
'
http
'
;
const
URL_SCHEME_HTTP
=
'
http
'
;
const
URL_HOST_DECK
=
'
deck.ourygo.top
'
;
const
URL_HOST_DECK
=
'
deck.ourygo.top
'
;
// === 工具函数 ===
// === BitWriter 类 ===
function
toBinary
(
value
:
number
,
length
:
number
):
string
{
class
BitWriter
{
return
value
.
toString
(
2
).
padStart
(
length
,
'
0
'
);
private
buffer
:
number
[]
=
[];
}
private
current
=
0
;
private
bitPos
=
0
;
writeBits
(
value
:
number
,
length
:
number
)
{
while
(
length
>
0
)
{
const
remain
=
8
-
this
.
bitPos
;
const
take
=
Math
.
min
(
remain
,
length
);
const
shift
=
length
-
take
;
this
.
current
|=
((
value
>>
shift
)
&
((
1
<<
take
)
-
1
))
<<
(
remain
-
take
);
this
.
bitPos
+=
take
;
length
-=
take
;
if
(
this
.
bitPos
===
8
)
{
this
.
buffer
.
push
(
this
.
current
);
this
.
current
=
0
;
this
.
bitPos
=
0
;
}
}
}
function
encodeCards
(
cards
:
number
[]):
string
{
finish
():
Uint8Array
{
const
bits
:
string
[]
=
[];
if
(
this
.
bitPos
>
0
)
this
.
buffer
.
push
(
this
.
current
);
for
(
let
i
=
0
;
i
<
cards
.
length
;
)
{
return
new
Uint8Array
(
this
.
buffer
);
const
id
=
cards
[
i
];
let
count
=
1
;
while
(
i
+
count
<
cards
.
length
&&
cards
[
i
+
count
]
===
id
&&
count
<
3
)
count
++
;
const
prefix
=
count
===
2
?
'
10
'
:
count
===
3
?
'
11
'
:
'
01
'
;
bits
.
push
(
prefix
+
toBinary
(
id
,
27
));
i
+=
count
;
}
}
return
bits
.
join
(
''
);
}
}
function
countUnique
(
cards
:
number
[]):
number
{
// === BitReader 类 ===
let
num
=
0
;
class
BitReader
{
for
(
let
i
=
0
;
i
<
cards
.
length
;
i
++
)
{
private
index
=
0
;
const
id
=
cards
[
i
];
private
bitPos
=
0
;
if
(
id
>
0
)
{
num
++
;
constructor
(
private
bytes
:
Uint8Array
)
{}
if
(
i
<
cards
.
length
-
1
&&
cards
[
i
+
1
]
===
id
)
{
i
++
;
readBits
(
length
:
number
):
number
{
if
(
i
<
cards
.
length
-
1
&&
cards
[
i
+
1
]
===
id
)
{
let
result
=
0
;
i
++
;
while
(
length
>
0
)
{
const
remain
=
8
-
this
.
bitPos
;
const
take
=
Math
.
min
(
remain
,
length
);
const
bits
=
(
this
.
bytes
[
this
.
index
]
>>
(
remain
-
take
))
&
((
1
<<
take
)
-
1
);
result
=
(
result
<<
take
)
|
bits
;
this
.
bitPos
+=
take
;
length
-=
take
;
if
(
this
.
bitPos
===
8
)
{
this
.
bitPos
=
0
;
this
.
index
++
;
}
}
}
}
return
result
;
}
}
}
return
num
;
}
}
// === 主函数 ===
// === 工具函数 ===
function
countUnique
(
cards
:
number
[]):
number
{
let
count
=
0
;
for
(
let
i
=
0
;
i
<
cards
.
length
;
)
{
const
id
=
cards
[
i
];
count
++
;
while
(
i
<
cards
.
length
&&
cards
[
i
]
===
id
)
i
++
;
}
return
count
;
}
// === 主函数:编码 ===
export
function
toYGOMobileDeckURL
(
export
function
toYGOMobileDeckURL
(
main
:
number
[],
main
:
number
[],
extra
:
number
[],
extra
:
number
[],
...
@@ -57,20 +86,32 @@ export function toYGOMobileDeckURL(
...
@@ -57,20 +86,32 @@ export function toYGOMobileDeckURL(
const
eNum
=
countUnique
(
extra
);
const
eNum
=
countUnique
(
extra
);
const
sNum
=
countUnique
(
side
);
const
sNum
=
countUnique
(
side
);
const
header
=
toBinary
(
mNum
,
8
)
+
toBinary
(
eNum
,
4
)
+
toBinary
(
sNum
,
4
);
const
writer
=
new
BitWriter
();
let
bitString
=
header
+
encodeCards
(
main
)
+
encodeCards
(
extra
)
+
encodeCards
(
side
);
writer
.
writeBits
(
mNum
,
8
);
while
(
bitString
.
length
%
8
!==
0
)
bitString
+=
'
0
'
;
writer
.
writeBits
(
eNum
,
4
);
writer
.
writeBits
(
sNum
,
4
);
const
bytes
=
new
Uint8Array
(
bitString
.
length
/
8
);
const
encodeSection
=
(
cards
:
number
[])
=>
{
for
(
let
i
=
0
;
i
<
bytes
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
cards
.
length
;
)
{
bytes
[
i
]
=
parseInt
(
bitString
.
slice
(
i
*
8
,
i
*
8
+
8
),
2
);
const
id
=
cards
[
i
];
let
count
=
1
;
while
(
i
+
count
<
cards
.
length
&&
cards
[
i
+
count
]
===
id
&&
count
<
3
)
count
++
;
const
prefix
=
count
===
2
?
0b10
:
count
===
3
?
0b11
:
0b01
;
writer
.
writeBits
(
prefix
,
2
);
writer
.
writeBits
(
id
,
27
);
i
+=
count
;
}
}
};
let
encoded
=
toBase64Url
(
bytes
)
encodeSection
(
main
);
encodeSection
(
extra
);
encodeSection
(
side
);
const
encoded
=
toBase64Url
(
writer
.
finish
());
const
searchParams
=
new
URLSearchParams
();
const
searchParams
=
new
URLSearchParams
();
for
(
const
[
k
ey
,
value
]
of
Object
.
entries
(
customParams
))
{
for
(
const
[
k
,
v
]
of
Object
.
entries
(
customParams
))
{
searchParams
.
set
(
k
ey
,
value
);
searchParams
.
set
(
k
,
v
);
}
}
searchParams
.
set
(
QUERY_YGO_TYPE
,
ARG_DECK
);
searchParams
.
set
(
QUERY_YGO_TYPE
,
ARG_DECK
);
searchParams
.
set
(
QUERY_VERSION
,
'
1
'
);
searchParams
.
set
(
QUERY_VERSION
,
'
1
'
);
...
@@ -79,7 +120,7 @@ export function toYGOMobileDeckURL(
...
@@ -79,7 +120,7 @@ export function toYGOMobileDeckURL(
return
`
${
URL_SCHEME_HTTP
}
://
${
URL_HOST_DECK
}
?
${
searchParams
.
toString
()}
`
;
return
`
${
URL_SCHEME_HTTP
}
://
${
URL_HOST_DECK
}
?
${
searchParams
.
toString
()}
`
;
}
}
// === 主函数:解码 ===
export
function
fromYGOMobileDeckURL
(
uri
:
string
):
{
export
function
fromYGOMobileDeckURL
(
uri
:
string
):
{
main
:
number
[];
main
:
number
[];
extra
:
number
[];
extra
:
number
[];
...
@@ -88,46 +129,39 @@ export function fromYGOMobileDeckURL(uri: string): {
...
@@ -88,46 +129,39 @@ export function fromYGOMobileDeckURL(uri: string): {
}
{
}
{
const
url
=
new
URL
(
uri
);
const
url
=
new
URL
(
uri
);
if
(
url
.
searchParams
.
get
(
QUERY_YGO_TYPE
)
!==
ARG_DECK
)
{
if
(
url
.
searchParams
.
get
(
QUERY_YGO_TYPE
)
!==
ARG_DECK
)
{
throw
new
Error
(
'
Not a YGO Mobile deck URI
'
);
throw
new
Error
(
'
Invalid deck URL
'
);
}
}
le
t
encoded
=
url
.
searchParams
.
get
(
QUERY_DECK
);
cons
t
encoded
=
url
.
searchParams
.
get
(
QUERY_DECK
);
if
(
!
encoded
)
throw
new
Error
(
'
Missing deck data
'
);
if
(
!
encoded
)
throw
new
Error
(
'
Missing deck data
'
);
const
bytes
=
fromBase64Url
(
encoded
);
const
bytes
=
fromBase64Url
(
encoded
);
const
reader
=
new
BitReader
(
bytes
);
const
bits
=
Array
.
from
(
bytes
)
const
mNum
=
reader
.
readBits
(
8
);
.
map
((
b
)
=>
b
.
toString
(
2
).
padStart
(
8
,
'
0
'
))
const
eNum
=
reader
.
readBits
(
4
);
.
join
(
''
);
const
sNum
=
reader
.
readBits
(
4
);
const
total
=
mNum
+
eNum
+
sNum
;
const
mNum
=
parseInt
(
bits
.
slice
(
0
,
8
),
2
);
const
eNum
=
parseInt
(
bits
.
slice
(
8
,
12
),
2
);
const
sNum
=
parseInt
(
bits
.
slice
(
12
,
16
),
2
);
const
all
=
mNum
+
eNum
+
sNum
;
const
cards
:
number
[]
=
[];
let
pos
=
16
;
const
res
=
{
const
res
ult
=
{
main
:
[]
as
number
[],
main
:
[]
as
number
[],
extra
:
[]
as
number
[],
extra
:
[]
as
number
[],
side
:
[]
as
number
[],
side
:
[]
as
number
[],
}
};
let
i
=
0
;
while
(
pos
+
29
<=
bits
.
length
&&
cards
.
length
<
all
)
{
for
(
let
i
=
0
;
i
<
total
;
i
++
)
{
const
countBits
=
bits
.
slice
(
pos
,
pos
+
2
);
const
prefix
=
reader
.
readBits
(
2
);
const
count
=
countBits
===
'
10
'
?
2
:
countBits
===
'
11
'
?
3
:
1
;
const
count
=
prefix
===
0b10
?
2
:
prefix
===
0b11
?
3
:
1
;
const
id
=
parseInt
(
bits
.
slice
(
pos
+
2
,
pos
+
29
),
2
);
const
id
=
reader
.
readBits
(
27
);
const
field
=
i
<
mNum
?
'
main
'
:
i
<
mNum
+
eNum
?
'
extra
'
:
'
side
'
;
const
target
=
res
[
field
].
push
(...
Array
(
count
).
fill
(
id
));
i
<
mNum
?
result
.
main
:
i
<
mNum
+
eNum
?
result
.
extra
:
result
.
side
;
pos
+=
29
;
for
(
let
j
=
0
;
j
<
count
;
j
++
)
{
i
++
;
target
.
push
(
id
);
}
}
}
return
{
return
{
...
res
,
...
res
ult
,
name
:
url
.
searchParams
.
get
(
QUERY_NAME
),
name
:
url
.
searchParams
.
get
(
QUERY_NAME
)
??
undefined
,
};
};
}
}
tests/sample.spec.ts
View file @
4fd99c63
...
@@ -55,6 +55,98 @@ describe('Sample test.', () => {
...
@@ -55,6 +55,98 @@ describe('Sample test.', () => {
it
(
'
should encode and decode ygomobile
'
,
()
=>
{
it
(
'
should encode and decode ygomobile
'
,
()
=>
{
const
uri
=
'
http://deck.ourygo.top?name=%E7%99%BD%E9%93%B6%E5%9F%8E%E7%A0%81&ygotype=deck&v=1&d=J-xK4Mka02AuEAMf2dV6mj7aRemuJNQJK8BwrcEYh0MqOJqBJSgYqUwxPEVhOG8R18WWVKmzkT-xEdwxbmGVkkOdrVIpufaYI3Hs8oOrcya8Bi40h9G79iFW80rq-o6P-AHsusPY5nmvHLol0DIqEykESVlf6VSbxVJp-j7XZtTE0XvmJW80rqH28R4rgyRovOusVJzbutenYFBA_cyK6d3UWcQkJQlLjaroWavH-INFA56k5DQNWOQ1gpvxrKVBLgEk1olpolKmSgriramLlgtBK1EQ6C6oi94ZyHe7N6T7mqE6peds7mahrORP6A
'
;
const
uri
=
'
http://deck.ourygo.top?name=%E7%99%BD%E9%93%B6%E5%9F%8E%E7%A0%81&ygotype=deck&v=1&d=J-xK4Mka02AuEAMf2dV6mj7aRemuJNQJK8BwrcEYh0MqOJqBJSgYqUwxPEVhOG8R18WWVKmzkT-xEdwxbmGVkkOdrVIpufaYI3Hs8oOrcya8Bi40h9G79iFW80rq-o6P-AHsusPY5nmvHLol0DIqEykESVlf6VSbxVJp-j7XZtTE0XvmJW80rqH28R4rgyRovOusVJzbutenYFBA_cyK6d3UWcQkJQlLjaroWavH-INFA56k5DQNWOQ1gpvxrKVBLgEk1olpolKmSgriramLlgtBK1EQ6C6oi94ZyHe7N6T7mqE6peds7mahrORP6A
'
;
const
correctYdk
=
`#created by OURYGO
#main
22812963
55410872
102380
89631139
89631139
89631139
95718355
95718355
95718355
59323650
22938501
96540807
6637331
33854624
72656408
63198739
63198739
14558127
14558127
23434538
23434538
94145021
17947697
17947697
97268402
17725109
18144507
25311006
31786838
31786838
31786838
80326401
80326401
80326401
24224830
24224830
29095457
29095457
48130397
65681983
67171933
67171933
67171933
71143015
56506740
56506740
24382602
10045474
10045474
22634473
43219114
27781371
62089826
85442146
48130397
8240199
22812963
22812963
#extra
12381100
43228023
56532353
2129638
11443677
11765832
21123811
89604813
63436931
10515412
59822133
59822133
74586817
29301450
42097666
!side
20292186
85103922
34267821
87126721
87126721
84192580
7608148
12444060
15693423
15693423
20899496
30748475
82732705
94145021
94145021`
const
deck
=
new
YGOProDeck
().
fromYGOMobileDeckURL
(
uri
);
const
deck
=
new
YGOProDeck
().
fromYGOMobileDeckURL
(
uri
);
expect
(
deck
.
main
).
toHaveLength
(
58
);
expect
(
deck
.
main
).
toHaveLength
(
58
);
expect
(
deck
.
extra
).
toHaveLength
(
15
);
expect
(
deck
.
extra
).
toHaveLength
(
15
);
...
@@ -62,6 +154,12 @@ describe('Sample test.', () => {
...
@@ -62,6 +154,12 @@ describe('Sample test.', () => {
expect
(
deck
.
main
[
0
]).
toBe
(
22812963
);
expect
(
deck
.
main
[
0
]).
toBe
(
22812963
);
expect
(
deck
.
extra
[
0
]).
toBe
(
12381100
);
expect
(
deck
.
extra
[
0
]).
toBe
(
12381100
);
expect
(
deck
.
side
[
0
]).
toBe
(
20292186
);
expect
(
deck
.
side
[
0
]).
toBe
(
20292186
);
const
correctDeck
=
new
YGOProDeck
().
fromYdkString
(
correctYdk
);
expect
(
deck
.
main
).
toStrictEqual
(
correctDeck
.
main
);
expect
(
deck
.
extra
).
toStrictEqual
(
correctDeck
.
extra
);
expect
(
deck
.
side
).
toStrictEqual
(
correctDeck
.
side
);
const
uri2
=
deck
.
toYGOMobileDeckURL
();
const
uri2
=
deck
.
toYGOMobileDeckURL
();
expect
(
uri2
).
toBe
(
uri
);
expect
(
uri2
).
toBe
(
uri
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment