Merge upstream changes from 2026-05-31
This commit is contained in:
@@ -81,7 +81,6 @@ set(SOURCES
|
|||||||
src/Episode3/RulerServer.cc
|
src/Episode3/RulerServer.cc
|
||||||
src/Episode3/Server.cc
|
src/Episode3/Server.cc
|
||||||
src/Episode3/Tournament.cc
|
src/Episode3/Tournament.cc
|
||||||
src/FileContentsCache.cc
|
|
||||||
src/GameServer.cc
|
src/GameServer.cc
|
||||||
src/GSLArchive.cc
|
src/GSLArchive.cc
|
||||||
src/HTTPServer.cc
|
src/HTTPServer.cc
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
## General
|
## General
|
||||||
|
|
||||||
- Make a server patch version of story flag fixer quest
|
- Make a server patch version of story flag fixer quest
|
||||||
- Fix enemy flag mapping in v2/v3 crossplay and test
|
|
||||||
- Handle items in crossplay - use the replacement table
|
- Handle items in crossplay - use the replacement table
|
||||||
- Make proxy server handle all login commands on non-BB, including sending 9C when needed
|
- Make proxy server handle all login commands on non-BB, including sending 9C when needed
|
||||||
- Add $switchit command (activates switch flag(s) for nearest object, e.g. laser fence, door, fog collision)
|
- Add $switchit command (activates switch flag(s) for nearest object, e.g. laser fence, door, fog collision)
|
||||||
- Add a way to persist flags across connections, at least on v3, because of Meet User + B2 enable quest interactions - maybe update the quest to patch one of the login commands so the server can tell it's enabled
|
- Add a way to persist flags across connections, at least on v3, because of Meet User + B2 enable quest interactions - maybe update the quest to patch one of the login commands so the server can tell it's enabled
|
||||||
- Handle MeetUserExtensions properly in 41 and C4 commands on the proxy (rewrite the embedded 19 command and put some metadata in the persistent config, perhaps)
|
- Handle MeetUserExtensions properly in 41 and C4 commands on the proxy (rewrite the embedded 19 command and put some metadata in the persistent config, perhaps)
|
||||||
- Clean up ItemParameterTable implementation (see comment at the top of the class definition)
|
|
||||||
- Make UI strings localizable (e.g. entries in menus, welcome message, etc.)
|
- Make UI strings localizable (e.g. entries in menus, welcome message, etc.)
|
||||||
|
|
||||||
## PSO DC
|
## PSO DC
|
||||||
@@ -31,5 +29,4 @@
|
|||||||
## PSOBB
|
## PSOBB
|
||||||
|
|
||||||
- Make server-specified rare enemies work with maps loaded by the proxy
|
- Make server-specified rare enemies work with maps loaded by the proxy
|
||||||
- Record some BB tests
|
- Implement BB replay tests properly and record some
|
||||||
- Add all necessary Guild Card number rewrites in BB commands on the proxy
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ Version codes (from README.md):
|
|||||||
4OEU: PSO Xbox US TU
|
4OEU: PSO Xbox US TU
|
||||||
4OPD: PSO Xbox EU Disc
|
4OPD: PSO Xbox EU Disc
|
||||||
4OPU: PSO Xbox EU TU
|
4OPU: PSO Xbox EU TU
|
||||||
|
50YJ: PSO BB US 1.24.3
|
||||||
59NJ: PSO BB JP 1.25.11
|
59NJ: PSO BB JP 1.25.11
|
||||||
59NL: PSO BB JP 1.25.13 (including the Tethealla client)
|
59NL: PSO BB JP 1.25.13 (including the Tethealla client)
|
||||||
|
|
||||||
@@ -81,6 +82,7 @@ Disable item equip restrictions ("God of equip")
|
|||||||
3OJ5 => 041050D4 38000005
|
3OJ5 => 041050D4 38000005
|
||||||
3OJT => 0415BF50 38000005
|
3OJT => 0415BF50 38000005
|
||||||
3OP0 => 041052D4 38000005
|
3OP0 => 041052D4 38000005
|
||||||
|
5OYJ => 005C8C8F E9A7000000
|
||||||
59NJ => 005C9F35 E9A7000000
|
59NJ => 005C9F35 E9A7000000
|
||||||
59NL => 005C9F31 E9A7000000
|
59NL => 005C9F31 E9A7000000
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ All items visible in Pioneer 2
|
|||||||
3OE1 => 04102D88 38600000
|
3OE1 => 04102D88 38600000
|
||||||
|
|
||||||
Mags visible in Pioneer 2
|
Mags visible in Pioneer 2
|
||||||
|
5OYJ => 005D7053 EB04
|
||||||
59NJ => 005D8F27 EB04
|
59NJ => 005D8F27 EB04
|
||||||
59NL => 005D8F4B EB04
|
59NL => 005D8F4B EB04
|
||||||
|
|
||||||
@@ -96,6 +99,9 @@ Disable pause menu background + offset
|
|||||||
0428735C 4800000C
|
0428735C 4800000C
|
||||||
3OE2 => 0424CED8 48000370
|
3OE2 => 0424CED8 48000370
|
||||||
042887D8 4800000C
|
042887D8 4800000C
|
||||||
|
5OYJ => 00713758 9090
|
||||||
|
0072D417 9090
|
||||||
|
0072D27E 90E9
|
||||||
59NJ => 00719C58 9090
|
59NJ => 00719C58 9090
|
||||||
00733C57 9090
|
00733C57 9090
|
||||||
00733ABE 90E9
|
00733ABE 90E9
|
||||||
@@ -642,6 +648,8 @@ Fast tekker (skips wind-up jingle)
|
|||||||
0023EF77 jmp +0x0A
|
0023EF77 jmp +0x0A
|
||||||
4OPU => 0023F14C mov dword [ebp + 0x14C], 1
|
4OPU => 0023F14C mov dword [ebp + 0x14C], 1
|
||||||
0023F167 jmp +0x0A
|
0023F167 jmp +0x0A
|
||||||
|
5OYJ => 006D3F7B mov dword [edi + 0x14C], 1
|
||||||
|
006D3F98 jmp +0x0B
|
||||||
59NJ => 006DA14B mov dword [edi + 0x14C], 1
|
59NJ => 006DA14B mov dword [edi + 0x14C], 1
|
||||||
006DA168 jmp +0x0B
|
006DA168 jmp +0x0B
|
||||||
59NL => 006DA113 mov dword [edi + 0x14C], 1
|
59NL => 006DA113 mov dword [edi + 0x14C], 1
|
||||||
@@ -980,6 +988,8 @@ Override Challenge mode random enemy location tables limit
|
|||||||
4OEU => 002E742C XXXXXXXX (count as little-endian dword)
|
4OEU => 002E742C XXXXXXXX (count as little-endian dword)
|
||||||
4OPD => 002E720C XXXXXXXX (count as little-endian dword)
|
4OPD => 002E720C XXXXXXXX (count as little-endian dword)
|
||||||
4OPU => 002E745C XXXXXXXX (count as little-endian dword)
|
4OPU => 002E745C XXXXXXXX (count as little-endian dword)
|
||||||
|
5OYJ => 008075C3 XXXXXXXX (count * 4 as little-endian dword)
|
||||||
|
008075DC XXXXXXXX (count as little-endian dword)
|
||||||
59NJ => 0080FA3F XXXXXXXX (count * 4 as little-endian dword)
|
59NJ => 0080FA3F XXXXXXXX (count * 4 as little-endian dword)
|
||||||
0080FA58 XXXXXXXX (count as little-endian dword)
|
0080FA58 XXXXXXXX (count as little-endian dword)
|
||||||
59NL => 0080ECB7 XXXXXXXX (count * 4 as little-endian dword)
|
59NL => 0080ECB7 XXXXXXXX (count * 4 as little-endian dword)
|
||||||
@@ -1117,3 +1127,6 @@ Rappy size modifier
|
|||||||
3OE1 => 040C1E24 48000020 // Disable flag check in render
|
3OE1 => 040C1E24 48000020 // Disable flag check in render
|
||||||
045D0718 40800000 // X/Z scale as float (here, 4.0)
|
045D0718 40800000 // X/Z scale as float (here, 4.0)
|
||||||
045D071C 40800000 // Y scale as float (here, 4.0)
|
045D071C 40800000 // Y scale as float (here, 4.0)
|
||||||
|
|
||||||
|
Disable HP reduction warning sound in Challenge mode
|
||||||
|
3OE1 => 04076A28 4E800020
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
########################################################################
|
||||||
|
DOWNLOAD
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
The official installer for this client is seemingly lost to time.
|
||||||
|
However, we do still have access to a download link to a directory of
|
||||||
|
the game client. Located at the bottom of this post:
|
||||||
|
https://github.com/fuzziqersoftware/newserv/discussions/734
|
||||||
|
|
||||||
|
The correct client exe to use, would be PsoBB.pat inside the
|
||||||
|
"3. PSOBB Executable" directory. While the file extension is .pat, it
|
||||||
|
can be renamed and changed to .exe .
|
||||||
|
|
||||||
|
However, PsoBB.exe in its current state will not work on its own.
|
||||||
|
As it is packed with a version of ASProtect. Which will impede you from
|
||||||
|
removing GameGuard, as well as modifying the client to connect to a desired
|
||||||
|
IP address.
|
||||||
|
|
||||||
|
There are two ways around this.
|
||||||
|
|
||||||
|
1. Use a code injection dll
|
||||||
|
2. Unpack the exe
|
||||||
|
|
||||||
|
As far as I know, There is currently not any code injection dll projects
|
||||||
|
available for use with this client. So our main option is going to be
|
||||||
|
unpacking the client.
|
||||||
|
|
||||||
|
There are several ways to unpack a client. For the sake of simplicity, we
|
||||||
|
will use a automated program.
|
||||||
|
Something like:
|
||||||
|
https://github.com/Hendi48/ASpirin
|
||||||
|
Originally found in this issue:
|
||||||
|
https://github.com/fuzziqersoftware/newserv/issues/748
|
||||||
|
|
||||||
|
You will know the process was successful if the new resulting exe file
|
||||||
|
has a much larger file size than the original.
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
REMOVE GAMEGUARD
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
The first step in being able to use this client, is removing GameGuard.
|
||||||
|
|
||||||
|
In order to do this, we will prevent GameGuard from initializing by
|
||||||
|
forcing the responsible function to return.
|
||||||
|
|
||||||
|
00844A9C - ret (or C3 in hex)
|
||||||
|
|
||||||
|
This will effectively stop GameGuard from ever starting.
|
||||||
|
However, the client has checks on startup to see if GameGuard is running,
|
||||||
|
and will close the game if it detects otherwise.
|
||||||
|
|
||||||
|
008444BB - jmp 008444DD
|
||||||
|
|
||||||
|
Now there is nothing in the way from starting up the game.
|
||||||
|
Find and edit the client's IP addresses, and have fun.
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
NOTES
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
Despite being a US client primarily using english, the client seems to
|
||||||
|
still have a reliance on having Japanese-IME enabled.
|
||||||
|
You can get around any kind of issue with this by patching out the need
|
||||||
|
for IME.
|
||||||
|
|
||||||
|
008582CC - call dword ptr ds:[0x008E0228]
|
||||||
|
|
||||||
|
Alternatively, in a hex editor, you can search for:
|
||||||
|
"EB 1A 6A 00 FF 15 9C C3"
|
||||||
|
Once found, replace with:
|
||||||
|
"EB 1A 6A 00 FF 15 28 02"
|
||||||
@@ -3,7 +3,7 @@ PSOBB SUPPORT FILES, NOTES & RESOURCES
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
CLIENT LOCALIZATION
|
CLIENT LOCALIZATION
|
||||||
|
|
||||||
By default PSOBB loads everything in Japanese so it requires some extra files
|
By default PSOBB JP clients load everything in Japanese so it requires some extra files
|
||||||
to properly implement the English localization from SOA, these files are offered
|
to properly implement the English localization from SOA, these files are offered
|
||||||
here inside the usbb-resources folder for your convenience they are the same ones
|
here inside the usbb-resources folder for your convenience they are the same ones
|
||||||
from the old official USBB client
|
from the old official USBB client
|
||||||
|
|||||||
+261
-108
@@ -1,5 +1,7 @@
|
|||||||
|
* = verified from client or quest disassembly
|
||||||
|
|
||||||
0007 = Set by rico capsule in caves
|
0007 = Set by rico capsule in caves
|
||||||
000B = P2 Tyrell Start
|
000B * = Has seen first single-mode Tyrell conversation (Episode 1)
|
||||||
000C = P2 Irene Start
|
000C = P2 Irene Start
|
||||||
000D = P2 Scientist 1 Start
|
000D = P2 Scientist 1 Start
|
||||||
000E = P2 Scientist 2 Start
|
000E = P2 Scientist 2 Start
|
||||||
@@ -8,79 +10,118 @@
|
|||||||
0011 = Read a rico capsule (any)
|
0011 = Read a rico capsule (any)
|
||||||
0012 = P2 Scientist after talking to Irene.
|
0012 = P2 Scientist after talking to Irene.
|
||||||
0013 = P2 Menu 6, quest counter / Tekker talked to
|
0013 = P2 Menu 6, quest counter / Tekker talked to
|
||||||
0014 = Entered Forest 1
|
0014 * = Has been to Forest 1
|
||||||
0015 = Entered Forest 2
|
0015 = Entered Forest 2
|
||||||
0016 = Entered Dragon Area
|
0016 * = TBoss1Dragon encountered
|
||||||
0017 = Dragon defeated
|
0017 * = Dragon defeated (Caves unlocked in online/multi mode (pre-V4); sets command 10 quest tier to 1)
|
||||||
0018 = Caves unlocked
|
0018 * = Talked with Tyrell after defeating Dragon (Caves unlocked in offline single mode (pre-V4))
|
||||||
0018 = P2 Principle after defeating dragon
|
|
||||||
0019 = P2 Scientist after defeating dragon
|
0019 = P2 Scientist after defeating dragon
|
||||||
001E = Entered Caves 1 (Gov 2-1)
|
001E * = Has been to Cave 1
|
||||||
001F = Entered De Rol Le in 2-4
|
001F = Entered De Rol Le in 2-4
|
||||||
0020 = De Rol Le defeated
|
0020 * = De Rol Le defeated (Mines unlocked in online/multi mode (pre-V4); sets command 10 quest tier to 2)
|
||||||
0021 = Mines unlocked (P2 Tyrell after defeating De Rol Le)
|
0021 * = Talked with Tyrell after defeating De Rol Le (Mines unlocked in offline single mode (pre-V4))
|
||||||
0028 = Entered Mines 1
|
0028 * = Has been to Mine 1
|
||||||
0029 = Entered Vol Opt Area
|
0029 * = TBoss3Volopt encountered
|
||||||
002A = Defeated Vol Opt
|
002A * = Vol Opt defeated (Ruins unlocked in online/multi mode (pre-V4); sets command 10 quest tier to 3)
|
||||||
002B = Set by rico capsule about the 3 seals (after vol opt).
|
002B = Set by rico capsule about the 3 seals (after vol opt).
|
||||||
002C = Activated Forest monument
|
002C * = Activated Forest monument (checked by TODoorVoShip_update and TODoorVoShip_init)
|
||||||
002D = Activated Caves monument (Gov 2-2)
|
002D * = Activated Caves monument (checked by TODoorVoShip_update and TODoorVoShip_init)
|
||||||
002E = Activated Mines monument
|
002E * = Activated Mines monument (checked by TODoorVoShip_update and TODoorVoShip_init)
|
||||||
002F = Activated all monuments
|
002F * = Activated all monuments (checked by TODoorVoShip_update and TODoorVoShip_init; causes Vol Opt to construct different objects upon defeat; see 3OE1:8003CB50, 3OE1:80040E68)
|
||||||
0030 = Entered Ruins 1
|
0030 * = Has been to Ruins 1 (Ruins unlocked in offline single mode (pre-V4))
|
||||||
0032 = Entered Falz 1
|
0032 * = TBoss4Type1 encountered
|
||||||
0035 = Hard mode unlocked
|
0033 * = TBoss4Type1 defeated; sets command 10 quest tier to 4
|
||||||
0036 = Entered Falz 3 // Very Hard mode unlocked (?)
|
0034 * = TBoss4Type2 encountered
|
||||||
0037 = Ultimate unlocked
|
0035 * = TBoss4Type2 defeated; unlocks Hard if set in Normal
|
||||||
0046 = One CCA door lock unlocked
|
0036 * = TBoss4Type3 encountered
|
||||||
0047 = One CCA door lock unlocked
|
0037 * = TBoss4Type3 defeated; unlocks Very Hard or Ultimate if set in Hard or Very Hard respectively
|
||||||
0048 = One CCA door lock unlocked
|
0046 * = First CCA door lock unlocked (TODO: Cleared by 59NL:006585E4; is this triggered anywhere?)
|
||||||
|
0047 * = Second CCA door lock unlocked (TODO: Cleared by 59NL:006585E4; is this triggered anywhere?)
|
||||||
|
0048 * = Third CCA door lock unlocked (TODO: Cleared by 59NL:006585E4; is this triggered anywhere?)
|
||||||
0049 = Entered Laboratory
|
0049 = Entered Laboratory
|
||||||
004A = Lab Assistant Start
|
004A = Lab Assistant Start
|
||||||
004B = Entered Temple Beta
|
004B * = Has been to VR Temple Beta
|
||||||
004C = Defeated Barba Ray
|
004C * = Barba Ray defeated (VR Spaceship unlocked in online/multi mode (pre-V4))
|
||||||
004D = Lab Assistant after defeating barba ray
|
004D * = Talked with Lab Assistant after defeating Barba Ray (VR Spaceship unlocked in offline single mode (pre-V4))
|
||||||
004E = Entered Spaceship Beta
|
004E * = Has been to VR Spaceship Beta
|
||||||
004F = Defeated Gol Dragon
|
004F * = TBoss8Dragon defeated (CCA unlocked in online/multi mode (pre-V4))
|
||||||
0051 = Entered CCA
|
0050 * = CCA unlocked in offline single mode (pre-V4)
|
||||||
0052 = Defeated Gal Gyrphon // Defeated Gol dragon in seat of heart (?)
|
0051 * = Has been to CCA
|
||||||
0054 = Entered Seabed Upper
|
0052 * = Gal Gryphon defeated (Seabed unlocked in online/multi mode (pre-V4))
|
||||||
0057 = Defeated Olga Flow
|
0053 * = Seabed unlocked in offline single mode (pre-V4)
|
||||||
005B = Lab Natasha Start
|
0054 * = Has been to Seabed Upper
|
||||||
|
0055 * = Has been to Seabed Lower
|
||||||
|
0056 * = TBoss6PlotFalz encountered
|
||||||
|
0057 * = TBoss6PlotFalz defeated (unlocks the next difficulty level)
|
||||||
|
005B * = Has seen first single-mode Natasha conversation (Episode 1)
|
||||||
005C = Lab Natasha after VR temple
|
005C = Lab Natasha after VR temple
|
||||||
005D = Lab Natasha after VR Spaceship
|
005D = Lab Natasha after VR Spaceship
|
||||||
005E = Lab Assistant after defeating Gal gryphon
|
005E = Lab Assistant after defeating Gal gryphon
|
||||||
005F = After reading the last capsule from flowen
|
005F = After reading the last capsule from flowen
|
||||||
0060 = Lab Natasha after CCA
|
0060 = Lab Natasha after CCA
|
||||||
0065 = Cleared Magnitude of Metal
|
0064 * = Magnitude of Metal unlocked
|
||||||
0067 = Cleared Claiming a Stake
|
0065 * = Magnitude of Metal completed
|
||||||
0069 = Cleared Value of Money
|
0066 * = Claiming A Stake unlocked
|
||||||
006B = Cleared Battle Training
|
0067 * = Claiming A Stake completed
|
||||||
006D = Cleared Journalistic Pursuit
|
0068 * = The Value of Money unlocked
|
||||||
006F = Cleared The Fake in Yellow
|
0069 * = The Value of Money completed
|
||||||
0071 = Cleared Native Research
|
006A * = Battle Training unlocked
|
||||||
0073 = Cleared Forest of Sorrow
|
006B * = Battle Training completed
|
||||||
0075 = Cleared Gran Squall
|
006C * = Journalistic Pursuit unlocked
|
||||||
0077 = Cleared Addicting Food
|
006D * = Journalistic Pursuit completed
|
||||||
0079 = Cleared The Lost Bride
|
006E * = The Fake in yellow unlocked
|
||||||
007B = Cleared Waterfall Tears
|
006F * = The Fake in yellow completed
|
||||||
007D = Cleared Black Paper
|
0070 * = Native Research unlocked
|
||||||
007F = Cleared Secret Delivery
|
0071 * = Native Research completed
|
||||||
0081 = Cleared Soul of a Blacksmith
|
0072 * = Forest of Sorrow unlocked
|
||||||
0083 = Cleared Letter from Lionel
|
0073 * = Forest of Sorrow completed
|
||||||
0085 = Cleared The Grave's Butler
|
0074 * = Gran Squall unlocked
|
||||||
0087 = Cleared Knowing One's Heart
|
0075 * = Gran Squall completed
|
||||||
0089 = Cleared The Retired Hunter
|
0076 * = Addicting Food unlocked
|
||||||
008B = Cleared Dr. Osto's Research
|
0077 * = Addicting Food completed
|
||||||
008D = Cleared Unsealed Door
|
0078 * = The Lost Bride unlocked
|
||||||
008F = Cleared Soul of Steel
|
0079 * = The Lost Bride completed
|
||||||
0091 = Cleared Doc's Secret Plan (able to make enemy part weapons)
|
007A * = Waterfall tears unlocked
|
||||||
0093 = Cleared Seek my Master
|
007B * = Waterfall tears completed
|
||||||
0095 = Cleared From the Depths
|
007C * = Black Paper unlocked
|
||||||
|
007D * = Black Paper completed
|
||||||
|
007E * = Secret Delivery unlocked
|
||||||
|
007F * = Secret Delivery completed
|
||||||
|
0080 * = Soul of a Blacksmith unlocked
|
||||||
|
0081 * = Soul of a Blacksmith completed
|
||||||
|
0082 * = Letter from Lionel unlocked
|
||||||
|
0083 * = Letter from Lionel completed
|
||||||
|
0084 * = The Grave's Butler unlocked
|
||||||
|
0085 * = The Grave's Butler completed
|
||||||
|
0086 * = Knowing One's Heart unlocked
|
||||||
|
0087 * = Knowing One's Heart completed
|
||||||
|
0088 * = Retired Hunter unlocked
|
||||||
|
0089 * = Retired Hunter completed
|
||||||
|
008A * = Dr. Osto's Research unlocked
|
||||||
|
008B * = Dr. Osto's Research completed
|
||||||
|
008C * = The Unsealed Door unlocked
|
||||||
|
008D * = The Unsealed Door completed
|
||||||
|
008E * = Soul of Steel unlocked
|
||||||
|
008F * = Soul of Steel completed
|
||||||
|
0090 * = Doc's Secret Plan unlocked
|
||||||
|
0091 * = Doc's Secret Plan completed
|
||||||
|
0092 * = Seek My Master unlocked
|
||||||
|
0093 * = Seek My Master completed
|
||||||
|
0094 * = From the Depths unlocked
|
||||||
|
0095 * = From the Depths completed
|
||||||
0096 = Unknown (set in the fake in yellow)
|
0096 = Unknown (set in the fake in yellow)
|
||||||
0097 = Seat of heart unknown
|
0097 * = TBoss4Type3 defeated while any player was using Dark Flow, Dark Meteor, or Dark Bridge
|
||||||
|
0098 * = TBoss6PlotFalz defeated while any player was using a Read weapon (e.g. Red Saber, Reg Handgun, etc.) or any of Rico's armors (Red Ring, Rico's Glasses, Rico's Earring)
|
||||||
|
0099 * = TBoss4Thanks destroyed
|
||||||
|
009A * = TBoss6Thanks destroyed
|
||||||
009B = Cleared Central Dome Fire Swirl
|
009B = Cleared Central Dome Fire Swirl
|
||||||
00A1 = Cleared Seat of the Heart
|
009F * = Central Dome Fire Swirl unlocked
|
||||||
|
00A0 * = Central Dome Fire Swirl completed
|
||||||
|
00A1 * = Seat of the Heart completed
|
||||||
|
00A2 * = The East Tower completed
|
||||||
|
00A3 * = The East Tower completed
|
||||||
|
00A4 * = The West Tower completed
|
||||||
|
00A5 * = The West Tower completed
|
||||||
00C9 = Got an enemy weapon converted
|
00C9 = Got an enemy weapon converted
|
||||||
00CA = unknown Fake In Yellow
|
00CA = unknown Fake In Yellow
|
||||||
00CE = unknown Fake In Yellow
|
00CE = unknown Fake In Yellow
|
||||||
@@ -129,59 +170,171 @@
|
|||||||
01AE = Capsule elly CCA
|
01AE = Capsule elly CCA
|
||||||
01B3 = After reading a capsule from flowen
|
01B3 = After reading a capsule from flowen
|
||||||
01D6 = Set after unlocking vr spaceship
|
01D6 = Set after unlocking vr spaceship
|
||||||
01F5 = Episode1: Cleared government 1-1
|
01F5 * = Episode1: Cleared government 1-1
|
||||||
01F7 = Episode1: Cleared government 1-2
|
01F7 * = Episode1: Cleared government 1-2
|
||||||
01F9 = Episode1: Cleared government 1-3
|
01F9 * = Episode1: Cleared government 1-3; Caves unlocked (V4)
|
||||||
01FB = Episode1: Cleared government 2-1
|
01FB * = Episode1: Cleared government 2-1
|
||||||
01FD = Episode1: Cleared government 2-2
|
01FD * = Episode1: Cleared government 2-2
|
||||||
01FF = Episode1: Cleared government 2-3
|
01FF * = Episode1: Cleared government 2-3
|
||||||
0201 = Episode1: Cleared government 2-4
|
0201 * = Episode1: Cleared government 2-4; Mines unlocked (V4)
|
||||||
0203 = Episode1: Cleared government 3-1
|
0203 * = Episode1: Cleared government 3-1
|
||||||
0205 = Episode1: Cleared government 3-2
|
0205 * = Episode1: Cleared government 3-2
|
||||||
0207 = Episode1: Cleared government 3-3
|
0207 * = Episode1: Cleared government 3-3; Ruins unlocked (V4)
|
||||||
0209 = Episode1: Cleared government 4-1
|
0209 * = Episode1: Cleared government 4-1
|
||||||
020B = Episode1: Cleared government 4-2
|
020B * = Episode1: Cleared government 4-2
|
||||||
020D = Episode1: Cleared government 4-3
|
020D * = Episode1: Cleared government 4-3
|
||||||
020F = Episode1: Cleared government 4-4
|
020F * = Episode1: Cleared government 4-4
|
||||||
0211 = Episode1: Cleared government 4-5
|
0211 * = Episode1: Cleared government 4-5
|
||||||
0213 = Episode2: Cleared government 5-1 // Talked to Tekker (?)
|
0213 * = Episode2: Cleared government 5-1
|
||||||
0214 = Entered Forest 1
|
0215 * = Episode2: Cleared government 5-2
|
||||||
0215 = Episode2: Cleared government 5-2
|
0217 * = Episode2: Cleared government 5-3
|
||||||
0217 = Episode2: Cleared government 5-3 // Defeated Dragon (?)
|
0219 * = Episode2: Cleared government 5-4
|
||||||
0219 = Episode2: Cleared government 5-4
|
021B * = Episode2: Cleared government 5-5; VR Spaceship unlocked (V4)
|
||||||
021B = Episode2: Cleared government 5-5
|
021D * = Episode2: Cleared government 6-1
|
||||||
021D = Episode2: Cleared government 6-1
|
021F * = Episode2: Cleared government 6-2
|
||||||
021F = Episode2: Cleared government 6-2
|
0221 * = Episode2: Cleared government 6-3
|
||||||
0220 = Defeated De Rol Le
|
0223 * = Episode2: Cleared government 6-4
|
||||||
0221 = Episode2: Cleared government 6-3
|
0225 * = Episode2: Cleared government 6-5; CCA unlocked (V4)
|
||||||
0223 = Episode2: Cleared government 6-4
|
0227 * = Episode2: Cleared government 7-1
|
||||||
0225 = Episode2: Cleared government 6-5
|
0229 * = Episode2: Cleared government 7-2
|
||||||
0227 = Episode2: Cleared government 7-1
|
022B * = Episode2: Cleared government 7-3
|
||||||
0229 = Episode2: Cleared government 7-2
|
022D * = Episode2: Cleared government 7-4
|
||||||
022A = Defeated Vol Opt (002A and 022A together on hard mode)
|
022F * = Episode2: Cleared government 7-5; Seabed unlocked (V4)
|
||||||
022B = Episode2: Cleared government 7-3 // Rico capsule after Vol Opt, at Ruins door (?)
|
0231 * = Episode2: Cleared government 8-1
|
||||||
022D = Episode2: Cleared government 7-4 // Entered Caves 2 (?)
|
0233 * = Episode2: Cleared government 8-2
|
||||||
022F = Episode2: Cleared government 7-5
|
0235 * = Episode2: Cleared government 8-3
|
||||||
0230 = Entered Ruins 1
|
|
||||||
0231 = Episode2: Cleared government 8-1
|
|
||||||
0233 = Episode2: Cleared government 8-2
|
|
||||||
0234 = Entered Falz 2
|
|
||||||
0235 = Episode2: Cleared government 8-3
|
|
||||||
0246 = Activated Jungle East big door switch
|
0246 = Activated Jungle East big door switch
|
||||||
0248 = Activated Seaside big door switch
|
0248 = Activated Seaside big door switch
|
||||||
024F = Defeated Gol Dragon
|
024F = Defeated Gol Dragon
|
||||||
0252 = Defeated Gal Gryphon
|
0252 = Defeated Gal Gryphon
|
||||||
02BD = Episode4: Cleared government 9-1
|
02BD * = Episode4: Cleared government 9-1; Crater West unlocked
|
||||||
02BE = Episode4: Cleared government 9-2
|
02BE * = Episode4: Cleared government 9-2; Crater South unlocked
|
||||||
02BF = Episode4: Cleared government 9-3
|
02BF * = Episode4: Cleared government 9-3; Crater North unlocked
|
||||||
02C0 = Episode4: Cleared government 9-4
|
02C0 * = Episode4: Cleared government 9-4; Crater Interior unlocked
|
||||||
02C1 = Episode4: Cleared government 9-5
|
02C1 * = Episode4: Cleared government 9-5; Desert unlocked
|
||||||
02C2 = Episode4: Cleared government 9-6
|
02C2 * = Episode4: Cleared government 9-6
|
||||||
02C3 = Episode4: Cleared government 9-7
|
02C3 * = Episode4: Cleared government 9-7
|
||||||
02C4 = Episode4: Cleared government 9-8
|
02C4 * = Episode4: Cleared government 9-8
|
||||||
0314 = Entered Forest 1
|
0314 = Entered Forest 1
|
||||||
0330 = Entered Ruins 1
|
0330 = Entered Ruins 1
|
||||||
03FA = P2 Menu 7, G-Counter // Talked to Momoka
|
03FA = P2 Menu 7, G-Counter // Talked to Momoka
|
||||||
03FB = Nol start
|
03FB = Nol start
|
||||||
03FC = Cleared Ep2 government on ultimate
|
03FC = Cleared Ep2 government on ultimate
|
||||||
03FE = Cleared Ep2 government on normal-vh
|
03FE * = TODO (3OE1:8019BA34, 3OE1:80218F74, 59NL:0066A287)
|
||||||
|
|
||||||
|
Quest unlock conditions:
|
||||||
|
QUESTNUM NAMEIDX SDESCIDX LDESCIDX ENFL CPFL FLAGS FILE
|
||||||
|
00000001 00000000 00000001 00000002 006A 006B 00000000 quest04
|
||||||
|
00000002 00000003 00000004 00000005 0066 0067 00000000 quest02
|
||||||
|
00000003 00000006 00000007 00000008 0064 0065 00000000 quest01
|
||||||
|
00000004 00000009 0000000A 0000000B 0068 0069 00000000 quest03
|
||||||
|
00000005 0000000C 0000000D 0000000E 006C 006D 00000000 quest05
|
||||||
|
00000006 0000000F 00000010 00000011 006E 006F 00000000 quest06
|
||||||
|
00000007 00000012 00000013 00000014 0070 0071 00000000 quest07
|
||||||
|
00000008 00000015 00000016 00000017 0072 0073 00000000 quest08
|
||||||
|
00000009 00000018 00000019 0000001A 0074 0075 00000000 quest09
|
||||||
|
0000000A 0000001B 0000001C 0000001D 0076 0077 00000000 quest10
|
||||||
|
0000000B 0000001E 0000001F 00000020 0078 0079 00000000 quest11
|
||||||
|
0000000C 00000021 00000022 00000023 007A 007B 00000000 quest12
|
||||||
|
0000000D 00000024 00000025 00000026 007C 007D 00000000 quest13
|
||||||
|
0000000E 00000027 00000028 00000029 007E 007F 00000000 quest14
|
||||||
|
0000000F 0000002A 0000002B 0000002C 0080 0081 00000000 quest15
|
||||||
|
00000010 0000002D 0000002E 0000002F 0082 0083 00000000 quest16
|
||||||
|
00000011 00000030 00000031 00000032 0084 0085 00000000 quest17
|
||||||
|
00000012 00000033 00000034 00000035 0086 0087 00000000 quest18
|
||||||
|
00000013 00000036 00000037 00000038 0088 0089 00000000 quest19
|
||||||
|
00000014 00000039 0000003A 0000003B 008A 008B 00000000 quest20
|
||||||
|
00000015 0000003C 0000003D 0000003E 008C 008D 00000000 quest21
|
||||||
|
00000016 0000003F 00000040 00000041 008E 008F 00000000 quest22
|
||||||
|
00000017 00000042 00000043 00000044 0090 0091 00000000 quest23
|
||||||
|
00000018 00000045 00000046 00000047 0092 0093 00000000 quest24
|
||||||
|
00000019 00000048 00000049 0000004A 0094 0095 00000000 quest25
|
||||||
|
0000001A 0000004B 0000004C 0000004D 009F 00A0 00000000 quest26 (plus only; CDFS)
|
||||||
|
0000001B 0000004E 0000004F 00000050 FFFF 00A1 00000000 quest27 (plus only; ep2; Seat of the Heart)
|
||||||
|
0000001C 00000051 00000052 00000053 00A2 00A3 00000000 quest28 (plus only; ep2; The East Tower)
|
||||||
|
0000001D 00000054 00000055 00000056 00A4 00A5 00000000 quest29 (plus only; ep2; THe West Tower)
|
||||||
|
|
||||||
|
// Pre-BB
|
||||||
|
/* quest01 */ F_0064 = true;
|
||||||
|
/* quest02 */ F_0066 = true;
|
||||||
|
/* quest03 */ F_0068 = F_0065 && F_0067 && F_006B && F_0018;
|
||||||
|
/* quest04 */ F_006A = true;
|
||||||
|
/* quest05 */ F_006C = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest06 */ F_006E = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest07 */ F_0070 = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest08 */ F_0072 = F_0071;
|
||||||
|
/* quest09 */ F_0074 = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest10 */ F_0076 = F_0065 && F_0067 && F_006B && F_0018;
|
||||||
|
/* quest11 */ F_0078 = F_0065 && F_0067 && F_006B && F_0018;
|
||||||
|
/* quest12 */ F_007A = F_0077 && F_0079 && F_007F && F_0085;
|
||||||
|
/* quest13 */ F_007C = F_007B;
|
||||||
|
/* quest14 */ F_007E = F_0065 && F_0067 && F_006B && F_0018;
|
||||||
|
/* quest15 */ F_0080 = F_0077 && F_0079 && F_007F && F_0085;
|
||||||
|
/* quest16 */ F_0082 = F_0065 && F_0067 && F_006B && F_0021;
|
||||||
|
/* quest17 */ F_0084 = F_0065 && F_0067 && F_006B && F_0018;
|
||||||
|
/* quest18 */ F_0086 = F_0065 && F_0067 && F_006B && F_0021;
|
||||||
|
/* quest19 */ F_0088 = F_0065 && F_0067 && F_006B && F_0030;
|
||||||
|
/* quest20 */ F_008A = F_0065 && F_0067 && F_006B && F_0021;
|
||||||
|
/* quest21 */ F_008C = F_008B && F_007F && F_0021;
|
||||||
|
/* quest22 */ F_008E = F_008D && F_0030 && F_0091;
|
||||||
|
/* quest23 */ F_0090 = F_007F && F_0030;
|
||||||
|
/* quest24 */ F_0092 = F_0065 && F_0067 && F_006B && F_0030;
|
||||||
|
/* quest25 */ F_0094 = F_008D && F_0030 && F_0091;
|
||||||
|
/* quest26 (Plus only) */ F_009F = F_0095;
|
||||||
|
/* quest28 (Plus only) */ F_00A2 = F_00A1;
|
||||||
|
/* quest29 (Plus only) */ F_00A4 = F_00A3;
|
||||||
|
|
||||||
|
// BB
|
||||||
|
/* quest01 */ F_0064 = true;
|
||||||
|
/* quest02 */ F_0066 = true;
|
||||||
|
/* quest03 */ F_0068 = F_0065 && F_0067 && F_006B && F_01F9;
|
||||||
|
/* quest04 */ F_006A = true;
|
||||||
|
/* quest05 */ F_006C = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest06 */ F_006E = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest07 */ F_0070 = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest08 */ F_0072 = F_0071;
|
||||||
|
/* quest09 */ F_0074 = F_0065 && F_0067 && F_006B;
|
||||||
|
/* quest10 */ F_0076 = F_0065 && F_0067 && F_006B && F_01F9;
|
||||||
|
/* quest11 */ F_0078 = F_0065 && F_0067 && F_006B && F_01F9;
|
||||||
|
/* quest12 */ F_007A = F_0077 && F_0079 && F_007F && F_0085;
|
||||||
|
/* quest13 */ F_007C = F_007B;
|
||||||
|
/* quest14 */ F_007E = F_0065 && F_0067 && F_006B && F_01F9;
|
||||||
|
/* quest15 */ F_0080 = F_0077 && F_0079 && F_007F && F_0085;
|
||||||
|
/* quest16 */ F_0082 = F_0065 && F_0067 && F_006B && F_0201;
|
||||||
|
/* quest17 */ F_0084 = F_0065 && F_0067 && F_006B && F_01F9;
|
||||||
|
/* quest18 */ F_0086 = F_0065 && F_0067 && F_006B && F_0201;
|
||||||
|
/* quest19 */ F_0088 = F_0065 && F_0067 && F_006B && F_0207;
|
||||||
|
/* quest20 */ F_008A = F_0065 && F_0067 && F_006B && F_0201;
|
||||||
|
/* quest21 */ F_008C = F_008B && F_007F && F_0201;
|
||||||
|
/* quest22 */ F_008E = F_008D && F_0091 && F_0207;
|
||||||
|
/* quest23 */ F_0090 = F_007F && F_0207;
|
||||||
|
/* quest24 */ F_0092 = F_0065 && F_0067 && F_006B && F_0207;
|
||||||
|
/* quest25 */ F_0094 = F_008D && F_0091 && F_0207;
|
||||||
|
|
||||||
|
### T FLAG NAME REQUIREMENTS AVAILABLE_IF ENABLED_IF
|
||||||
|
001 1 0065 Magnitude of Metal !F_0065 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
002 1 0067 Claiming A Stake !F_0067 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
003 3 0069 The Value of Money T1, Caves F_0065 && F_0067 && F_006B && F_01F9 !F_0069 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
004 1 006B Battle Training !F_006B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
005 2 006D Journalistic Pursuit T1 F_0065 && F_0067 && F_006B !F_006D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
006 2 006F The Fake in yellow T1 F_0065 && F_0067 && F_006B !F_006F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
007 2 0071 Native Research T1 F_0065 && F_0067 && F_006B !F_0071 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
008 2 0073 Forest of Sorrow 007 F_0071 !F_0073 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
009 2 0075 Gran Squall T1 F_0065 && F_0067 && F_006B !F_0075 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
010 3 0077 Addicting Food T1 F_0065 && F_0067 && F_006B && F_01F9 !F_0077 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
011 3 0079 The Lost Bride T1 F_0065 && F_0067 && F_006B && F_01F9 !F_0079 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
012 3 007B Waterfall tears 010, 011, 014, 017 F_0077 && F_0079 && F_007F && F_0085 !F_007B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
013 3 007D Black Paper 012 F_007B !F_007D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
014 3 007F Secret Delivery T1 F_0065 && F_0067 && F_006B && F_01F9 !F_007F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
015 3 0081 Soul of a Blacksmith 010, 011, 014, 017 F_0077 && F_0079 && F_007F && F_0085 !F_0081 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
016 3 0083 Letter from Lionel T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_0083 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
017 3 0085 The Grave's Butler T1 F_0065 && F_0067 && F_006B && F_01F9 !F_0085 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
018 4 0087 Knowing One's Heart T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_0087 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
019 5 0089 Retired Hunter T1, Ruins F_0065 && F_0067 && F_006B && F_0207 !F_0089 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
020 4 008B Dr. Osto's Research T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_008B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
021 4 008D The Unsealed Door 020, 014 F_008B && F_007F && F_0201 !F_008D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
022 5 008F Soul of Steel 023 F_008D && F_0091 && F_0207 !F_008F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
023 5 0091 Doc's Secret Plan 014, Ruins F_007F && F_0207 !F_0091 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
024 5 0093 Seek My Master T1, Ruins F_0065 && F_0067 && F_006B && F_0207 !F_0093 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
025 5 0095 From the Depths 023 F_008D && F_0091 && F_0207 !F_0095 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
||||||
|
026 2 009B Central Dome Fire Swirl 008 F_0073
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
### T FLAG NAME REQUIREMENTS AVAILABLE_IF ENABLED_IF
|
|
||||||
001 1 0065 Magnitude of Metal !F_0065 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
002 1 0067 Claiming A Stake !F_0067 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
003 3 0069 The Value of Money T1, Caves F_0065 && F_0067 && F_006B && F_01F9 !F_0069 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
004 1 006B Battle Training !F_006B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
005 2 006D Journalistic Pursuit T1 F_0065 && F_0067 && F_006B !F_006D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
006 2 006F The Fake in yellow T1 F_0065 && F_0067 && F_006B !F_006F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
007 2 0071 Native Research T1 F_0065 && F_0067 && F_006B !F_0071 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
008 2 0073 Forest of Sorrow 007 F_0071 !F_0073 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
009 2 0075 Gran Squall T1 F_0065 && F_0067 && F_006B !F_0075 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
010 3 0077 Addicting Food T1 F_0065 && F_0067 && F_006B !F_0077 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
011 3 0079 The Lost Bride T1 F_0065 && F_0067 && F_006B !F_0079 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
012 3 007B Waterfall tears 010, 011, 014, 017 F_0077 && F_0079 && F_007F && F_0085 !F_007B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
013 3 007D Black Paper 012 F_007B !F_007D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
014 3 007F Secret Delivery T1 F_0065 && F_0067 && F_006B !F_007F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
015 3 0081 Soul of a Blacksmith 010, 011, 014, 017 F_0077 && F_0079 && F_007F && F_0085 !F_0081 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
016 3 0083 Letter from Lionel T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_0083 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
017 3 0085 The Grave's Butler T1 F_0065 && F_0067 && F_006B !F_0085 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
018 4 0087 Knowing One's Heart T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_0087 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
019 5 0089 Retired Hunter T1, Ruins F_0065 && F_0067 && F_006B && F_0207 !F_0089 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
020 4 008B Dr. Osto's Research T1, Mines F_0065 && F_0067 && F_006B && F_0201 !F_008B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
021 4 008D The Unsealed Door 020, 014 F_008B && F_007F !F_008D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
022 5 008F Soul of Steel 023 F_0091 !F_008F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
023 5 0091 Doc's Secret Plan 014, Ruins F_007F && F_0207 !F_0091 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
024 5 0093 Seek My Master T1, Ruins F_0065 && F_0067 && F_006B && F_0207 !F_0093 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
025 5 0095 From the Depths 023 F_0091 !F_0095 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)
|
|
||||||
026 2 009B Central Dome Fire Swirl 008 F_0073
|
|
||||||
+10
-13
@@ -9,10 +9,7 @@
|
|||||||
|
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
AFSArchive::AFSArchive(std::shared_ptr<const std::string> data) : data(data) {
|
||||||
|
|
||||||
AFSArchive::AFSArchive(shared_ptr<const string> data)
|
|
||||||
: data(data) {
|
|
||||||
struct FileHeader {
|
struct FileHeader {
|
||||||
be_uint32_t magic;
|
be_uint32_t magic;
|
||||||
le_uint32_t num_files;
|
le_uint32_t num_files;
|
||||||
@@ -26,7 +23,7 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
|
|||||||
phosg::StringReader r(*this->data);
|
phosg::StringReader r(*this->data);
|
||||||
const auto& header = r.get<FileHeader>();
|
const auto& header = r.get<FileHeader>();
|
||||||
if (header.magic != 0x41465300) { // 'AFS\0'
|
if (header.magic != 0x41465300) { // 'AFS\0'
|
||||||
throw runtime_error("file is not an AFS archive");
|
throw std::runtime_error("file is not an AFS archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
while (this->entries.size() < header.num_files) {
|
while (this->entries.size() < header.num_files) {
|
||||||
@@ -35,21 +32,21 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
std::pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||||
const auto& entry = this->entries.at(index);
|
const auto& entry = this->entries.at(index);
|
||||||
if (entry.offset > this->data->size()) {
|
if (entry.offset > this->data->size()) {
|
||||||
throw out_of_range("entry begins beyond end of archive");
|
throw std::out_of_range("entry begins beyond end of archive");
|
||||||
}
|
}
|
||||||
if (entry.offset + entry.size > this->data->size()) {
|
if (entry.offset + entry.size > this->data->size()) {
|
||||||
throw out_of_range("entry extends beyond end of archive");
|
throw std::out_of_range("entry extends beyond end of archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
return std::make_pair(this->data->data() + entry.offset, entry.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
string AFSArchive::get_copy(size_t index) const {
|
std::string AFSArchive::get_copy(size_t index) const {
|
||||||
auto ret = this->get(index);
|
auto ret = this->get(index);
|
||||||
return string(reinterpret_cast<const char*>(ret.first), ret.second);
|
return std::string(reinterpret_cast<const char*>(ret.first), ret.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader AFSArchive::get_reader(size_t index) const {
|
phosg::StringReader AFSArchive::get_reader(size_t index) const {
|
||||||
@@ -57,12 +54,12 @@ phosg::StringReader AFSArchive::get_reader(size_t index) const {
|
|||||||
return phosg::StringReader(ret.first, ret.second);
|
return phosg::StringReader(ret.first, ret.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
string AFSArchive::generate(const vector<string>& files, bool big_endian) {
|
std::string AFSArchive::generate(const std::vector<std::string>& files, bool big_endian) {
|
||||||
return big_endian ? AFSArchive::generate_t<true>(files) : AFSArchive::generate_t<false>(files);
|
return big_endian ? AFSArchive::generate_t<true>(files) : AFSArchive::generate_t<false>(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
string AFSArchive::generate_t(const vector<string>& files) {
|
std::string AFSArchive::generate_t(const std::vector<std::string>& files) {
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
w.put_u32b(0x41465300); // 'AFS\0'
|
w.put_u32b(0x41465300); // 'AFS\0'
|
||||||
w.put<U32T<BE>>(files.size());
|
w.put<U32T<BE>>(files.size());
|
||||||
|
|||||||
+189
-190
@@ -10,23 +10,21 @@
|
|||||||
|
|
||||||
#include "Account.hh"
|
#include "Account.hh"
|
||||||
|
|
||||||
using namespace std;
|
std::shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
|
||||||
|
auto ret = std::make_shared<DCNTELicense>();
|
||||||
shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
|
|
||||||
auto ret = make_shared<DCNTELicense>();
|
|
||||||
ret->serial_number = json.get_string("SerialNumber");
|
ret->serial_number = json.get_string("SerialNumber");
|
||||||
ret->access_key = json.get_string("AccessKey");
|
ret->access_key = json.get_string("AccessKey");
|
||||||
if (ret->serial_number.size() > 16) {
|
if (ret->serial_number.size() > 16) {
|
||||||
throw runtime_error("serial number is too long");
|
throw std::runtime_error("serial number is too long");
|
||||||
}
|
}
|
||||||
if (ret->serial_number.empty()) {
|
if (ret->serial_number.empty()) {
|
||||||
throw runtime_error("serial number is too short");
|
throw std::runtime_error("serial number is too short");
|
||||||
}
|
}
|
||||||
if (ret->access_key.size() > 16) {
|
if (ret->access_key.size() > 16) {
|
||||||
throw runtime_error("access key is too long");
|
throw std::runtime_error("access key is too long");
|
||||||
}
|
}
|
||||||
if (ret->access_key.empty()) {
|
if (ret->access_key.empty()) {
|
||||||
throw runtime_error("access key is too short");
|
throw std::runtime_error("access key is too short");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -35,15 +33,15 @@ phosg::JSON DCNTELicense::json() const {
|
|||||||
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
|
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
|
std::shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
|
||||||
auto ret = make_shared<V1V2License>();
|
auto ret = std::make_shared<V1V2License>();
|
||||||
ret->serial_number = json.get_int("SerialNumber");
|
ret->serial_number = json.get_int("SerialNumber");
|
||||||
ret->access_key = json.get_string("AccessKey");
|
ret->access_key = json.get_string("AccessKey");
|
||||||
if (ret->serial_number == 0) {
|
if (ret->serial_number == 0) {
|
||||||
throw runtime_error("serial number is zero");
|
throw std::runtime_error("serial number is zero");
|
||||||
}
|
}
|
||||||
if (ret->access_key.size() != 8) {
|
if (ret->access_key.size() != 8) {
|
||||||
throw runtime_error("access key length is incorrect");
|
throw std::runtime_error("access key length is incorrect");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -52,19 +50,19 @@ phosg::JSON V1V2License::json() const {
|
|||||||
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
|
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
|
std::shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
|
||||||
auto ret = make_shared<GCLicense>();
|
auto ret = std::make_shared<GCLicense>();
|
||||||
ret->serial_number = json.get_int("SerialNumber");
|
ret->serial_number = json.get_int("SerialNumber");
|
||||||
ret->access_key = json.get_string("AccessKey");
|
ret->access_key = json.get_string("AccessKey");
|
||||||
ret->password = json.get_string("Password");
|
ret->password = json.get_string("Password");
|
||||||
if (ret->serial_number == 0) {
|
if (ret->serial_number == 0) {
|
||||||
throw runtime_error("serial number is zero");
|
throw std::runtime_error("serial number is zero");
|
||||||
}
|
}
|
||||||
if (ret->access_key.size() != 12) {
|
if (ret->access_key.size() != 12) {
|
||||||
throw runtime_error("access key length is incorrect");
|
throw std::runtime_error("access key length is incorrect");
|
||||||
}
|
}
|
||||||
if (ret->password.empty()) {
|
if (ret->password.empty()) {
|
||||||
throw runtime_error("password is too short");
|
throw std::runtime_error("password is too short");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -77,19 +75,19 @@ phosg::JSON GCLicense::json() const {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
|
std::shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
|
||||||
auto ret = make_shared<XBLicense>();
|
auto ret = std::make_shared<XBLicense>();
|
||||||
ret->gamertag = json.get_string("GamerTag");
|
ret->gamertag = json.get_string("GamerTag");
|
||||||
ret->user_id = json.get_int("UserID");
|
ret->user_id = json.get_int("UserID");
|
||||||
ret->account_id = json.get_int("AccountID");
|
ret->account_id = json.get_int("AccountID");
|
||||||
if (ret->gamertag.empty()) {
|
if (ret->gamertag.empty()) {
|
||||||
throw runtime_error("gamertag is too short");
|
throw std::runtime_error("gamertag is too short");
|
||||||
}
|
}
|
||||||
if (ret->user_id == 0) {
|
if (ret->user_id == 0) {
|
||||||
throw runtime_error("user ID is zero");
|
throw std::runtime_error("user ID is zero");
|
||||||
}
|
}
|
||||||
if (ret->account_id == 0) {
|
if (ret->account_id == 0) {
|
||||||
throw runtime_error("account ID is zero");
|
throw std::runtime_error("account ID is zero");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -98,21 +96,21 @@ phosg::JSON XBLicense::json() const {
|
|||||||
return phosg::JSON::dict({{"GamerTag", this->gamertag}, {"UserID", this->user_id}, {"AccountID", this->account_id}});
|
return phosg::JSON::dict({{"GamerTag", this->gamertag}, {"UserID", this->user_id}, {"AccountID", this->account_id}});
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
|
std::shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
|
||||||
auto ret = make_shared<BBLicense>();
|
auto ret = std::make_shared<BBLicense>();
|
||||||
ret->username = json.get_string("UserName");
|
ret->username = json.get_string("UserName");
|
||||||
ret->password = json.get_string("Password");
|
ret->password = json.get_string("Password");
|
||||||
if (ret->username.size() > 16) {
|
if (ret->username.size() > 16) {
|
||||||
throw runtime_error("username is too long");
|
throw std::runtime_error("username is too long");
|
||||||
}
|
}
|
||||||
if (ret->username.empty()) {
|
if (ret->username.empty()) {
|
||||||
throw runtime_error("username is too short");
|
throw std::runtime_error("username is too short");
|
||||||
}
|
}
|
||||||
if (ret->password.size() > 16) {
|
if (ret->password.size() > 16) {
|
||||||
throw runtime_error("password is too long");
|
throw std::runtime_error("password is too long");
|
||||||
}
|
}
|
||||||
if (ret->password.empty()) {
|
if (ret->password.empty()) {
|
||||||
throw runtime_error("password is too short");
|
throw std::runtime_error("password is too short");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -132,51 +130,51 @@ Account::Account(const phosg::JSON& json)
|
|||||||
uint64_t format_version = 0;
|
uint64_t format_version = 0;
|
||||||
try {
|
try {
|
||||||
format_version = json.get_int("FormatVersion");
|
format_version = json.get_int("FormatVersion");
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format_version == 0) {
|
if (format_version == 0) {
|
||||||
// Original format - no account ID
|
// Original format - no account ID
|
||||||
this->account_id = json.get_int("SerialNumber");
|
this->account_id = json.get_int("SerialNumber");
|
||||||
string access_key = json.get_string("AccessKey", "");
|
std::string access_key = json.get_string("AccessKey", "");
|
||||||
string dc_nte_serial_number = json.get_string("DCNTESerialNumber", "");
|
std::string dc_nte_serial_number = json.get_string("DCNTESerialNumber", "");
|
||||||
string dc_nte_access_key = json.get_string("DCNTEAccessKey", "");
|
std::string dc_nte_access_key = json.get_string("DCNTEAccessKey", "");
|
||||||
string gc_password = json.get_string("GCPassword", "");
|
std::string gc_password = json.get_string("GCPassword", "");
|
||||||
string xb_gamertag = json.get_string("XBGamerTag", "");
|
std::string xb_gamertag = json.get_string("XBGamerTag", "");
|
||||||
uint64_t xb_user_id = json.get_int("XBUserID", 0);
|
uint64_t xb_user_id = json.get_int("XBUserID", 0);
|
||||||
uint64_t xb_account_id = json.get_int("XBAccountID", 0);
|
uint64_t xb_account_id = json.get_int("XBAccountID", 0);
|
||||||
string bb_username = json.get_string("BBUsername", "");
|
std::string bb_username = json.get_string("BBUsername", "");
|
||||||
string bb_password = json.get_string("BBPassword", "");
|
std::string bb_password = json.get_string("BBPassword", "");
|
||||||
if (access_key.size() == 12) {
|
if (access_key.size() == 12) {
|
||||||
if (!gc_password.empty()) {
|
if (!gc_password.empty()) {
|
||||||
auto lic = make_shared<GCLicense>();
|
auto lic = std::make_shared<GCLicense>();
|
||||||
lic->serial_number = this->account_id;
|
lic->serial_number = this->account_id;
|
||||||
lic->access_key = access_key;
|
lic->access_key = access_key;
|
||||||
lic->password = gc_password;
|
lic->password = gc_password;
|
||||||
this->gc_licenses.emplace(lic->serial_number, lic);
|
this->gc_licenses.emplace(lic->serial_number, lic);
|
||||||
}
|
}
|
||||||
} else if (access_key.size() >= 8) {
|
} else if (access_key.size() >= 8) {
|
||||||
auto lic = make_shared<V1V2License>();
|
auto lic = std::make_shared<V1V2License>();
|
||||||
lic->serial_number = this->account_id;
|
lic->serial_number = this->account_id;
|
||||||
lic->access_key = access_key.substr(0, 8);
|
lic->access_key = access_key.substr(0, 8);
|
||||||
this->dc_licenses.emplace(lic->serial_number, lic);
|
this->dc_licenses.emplace(lic->serial_number, lic);
|
||||||
this->pc_licenses.emplace(lic->serial_number, lic);
|
this->pc_licenses.emplace(lic->serial_number, lic);
|
||||||
}
|
}
|
||||||
if (!dc_nte_serial_number.empty() && !dc_nte_access_key.empty()) {
|
if (!dc_nte_serial_number.empty() && !dc_nte_access_key.empty()) {
|
||||||
auto lic = make_shared<DCNTELicense>();
|
auto lic = std::make_shared<DCNTELicense>();
|
||||||
lic->serial_number = dc_nte_serial_number;
|
lic->serial_number = dc_nte_serial_number;
|
||||||
lic->access_key = dc_nte_access_key;
|
lic->access_key = dc_nte_access_key;
|
||||||
this->dc_nte_licenses.emplace(lic->serial_number, lic);
|
this->dc_nte_licenses.emplace(lic->serial_number, lic);
|
||||||
}
|
}
|
||||||
if (!xb_gamertag.empty() && xb_user_id && xb_account_id) {
|
if (!xb_gamertag.empty() && xb_user_id && xb_account_id) {
|
||||||
auto lic = make_shared<XBLicense>();
|
auto lic = std::make_shared<XBLicense>();
|
||||||
lic->gamertag = xb_gamertag;
|
lic->gamertag = xb_gamertag;
|
||||||
lic->user_id = xb_user_id;
|
lic->user_id = xb_user_id;
|
||||||
lic->account_id = xb_account_id;
|
lic->account_id = xb_account_id;
|
||||||
this->xb_licenses.emplace(lic->user_id, lic);
|
this->xb_licenses.emplace(lic->user_id, lic);
|
||||||
}
|
}
|
||||||
if (!bb_username.empty() && !bb_password.empty()) {
|
if (!bb_username.empty() && !bb_password.empty()) {
|
||||||
auto lic = make_shared<BBLicense>();
|
auto lic = std::make_shared<BBLicense>();
|
||||||
lic->username = bb_username;
|
lic->username = bb_username;
|
||||||
lic->password = bb_password;
|
lic->password = bb_password;
|
||||||
this->bb_licenses.emplace(lic->username, lic);
|
this->bb_licenses.emplace(lic->username, lic);
|
||||||
@@ -223,7 +221,7 @@ Account::Account(const phosg::JSON& json)
|
|||||||
for (const auto& it : json.get_list("AutoPatchesEnabled")) {
|
for (const auto& it : json.get_list("AutoPatchesEnabled")) {
|
||||||
this->auto_patches_enabled.emplace(it->as_string());
|
this->auto_patches_enabled.emplace(it->as_string());
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,11 +277,11 @@ phosg::JSON Account::json() const {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
string Account::str() const {
|
std::string Account::str() const {
|
||||||
std::string ret = std::format("Account: {:010}/{:08X}\n", this->account_id, this->account_id);
|
std::string ret = std::format("Account: {:010}/{:08X}\n", this->account_id, this->account_id);
|
||||||
|
|
||||||
if (this->flags) {
|
if (this->flags) {
|
||||||
string flags_str = "";
|
std::string flags_str = "";
|
||||||
if (this->flags == static_cast<uint32_t>(Flag::ROOT)) {
|
if (this->flags == static_cast<uint32_t>(Flag::ROOT)) {
|
||||||
flags_str = "ROOT";
|
flags_str = "ROOT";
|
||||||
} else if (this->flags == static_cast<uint32_t>(Flag::ADMINISTRATOR)) {
|
} else if (this->flags == static_cast<uint32_t>(Flag::ADMINISTRATOR)) {
|
||||||
@@ -334,7 +332,7 @@ string Account::str() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this->user_flags) {
|
if (this->user_flags) {
|
||||||
string user_flags_str = "";
|
std::string user_flags_str = "";
|
||||||
if (this->check_user_flag(UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST)) {
|
if (this->check_user_flag(UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST)) {
|
||||||
user_flags_str += "DISABLE_DROP_NOTIFICATION_BROADCAST,";
|
user_flags_str += "DISABLE_DROP_NOTIFICATION_BROADCAST,";
|
||||||
}
|
}
|
||||||
@@ -347,8 +345,7 @@ string Account::str() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this->ban_end_time) {
|
if (this->ban_end_time) {
|
||||||
string time_str = phosg::format_time(this->ban_end_time);
|
ret += std::format(" Banned until: {} ({})\n", this->ban_end_time, phosg::format_time(this->ban_end_time));
|
||||||
ret += std::format(" Banned until: {} ({})\n", this->ban_end_time, time_str);
|
|
||||||
}
|
}
|
||||||
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
||||||
ret += std::format(" Episode 3 meseta: {} (total earned: {})\n",
|
ret += std::format(" Episode 3 meseta: {} (total earned: {})\n",
|
||||||
@@ -399,20 +396,19 @@ string Account::str() const {
|
|||||||
void Account::save() const {
|
void Account::save() const {
|
||||||
if (!this->is_temporary) {
|
if (!this->is_temporary) {
|
||||||
auto json = this->json();
|
auto json = this->json();
|
||||||
string json_data = json.serialize(
|
std::string json_data = json.serialize(
|
||||||
phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS);
|
phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS);
|
||||||
string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
std::string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
||||||
phosg::save_file(filename, json_data);
|
phosg::save_file(filename, json_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::delete_file() const {
|
void Account::delete_file() const {
|
||||||
string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
std::filesystem::remove(std::format("system/licenses/{:010}.json", this->account_id));
|
||||||
remove(filename.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string Login::str() const {
|
std::string Login::str() const {
|
||||||
string ret = std::format("Account:{:08X}", this->account->account_id);
|
std::string ret = std::format("Account:{:08X}", this->account->account_id);
|
||||||
if (this->account_was_created) {
|
if (this->account_was_created) {
|
||||||
ret += " (new)";
|
ret += " (new)";
|
||||||
}
|
}
|
||||||
@@ -435,21 +431,21 @@ string Login::str() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t AccountIndex::count() const {
|
size_t AccountIndex::count() const {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->by_account_id.size();
|
return this->by_account_id.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Account> AccountIndex::from_account_id(uint32_t account_id) const {
|
std::shared_ptr<Account> AccountIndex::from_account_id(uint32_t account_id) const {
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->by_account_id.at(account_id);
|
return this->by_account_id.at(account_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw missing_account();
|
throw missing_account();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const string& serial_number, const string& access_key) {
|
std::shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const std::string& serial_number, const std::string& access_key) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account = this->by_dc_nte_serial_number.at(serial_number);
|
login->account = this->by_dc_nte_serial_number.at(serial_number);
|
||||||
login->dc_nte_license = login->account->dc_nte_licenses.at(serial_number);
|
login->dc_nte_license = login->account->dc_nte_licenses.at(serial_number);
|
||||||
if (login->dc_nte_license->access_key != access_key) {
|
if (login->dc_nte_license->access_key != access_key) {
|
||||||
@@ -461,30 +457,30 @@ shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const string& ser
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
std::shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
||||||
const string& serial_number, const string& access_key, bool allow_create) {
|
const std::string& serial_number, const std::string& access_key, bool allow_create) {
|
||||||
if (serial_number.empty()) {
|
if (serial_number.empty()) {
|
||||||
throw no_username();
|
throw no_username();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->from_dc_nte_credentials_locked(serial_number, access_key);
|
return this->from_dc_nte_credentials_locked(serial_number, access_key);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
try {
|
try {
|
||||||
return this->from_dc_nte_credentials_locked(serial_number, access_key);
|
return this->from_dc_nte_credentials_locked(serial_number, access_key);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow_create) {
|
if (allow_create) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = phosg::fnv1a32(serial_number) & 0x7FFFFFFF;
|
login->account->account_id = phosg::fnv1a32(serial_number) & 0x7FFFFFFF;
|
||||||
auto lic = make_shared<DCNTELicense>();
|
auto lic = std::make_shared<DCNTELicense>();
|
||||||
lic->serial_number = serial_number;
|
lic->serial_number = serial_number;
|
||||||
lic->access_key = access_key;
|
lic->access_key = access_key;
|
||||||
login->account->dc_nte_licenses.emplace(lic->serial_number, lic);
|
login->account->dc_nte_licenses.emplace(lic->serial_number, lic);
|
||||||
@@ -496,9 +492,9 @@ shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
std::shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
||||||
uint32_t serial_number, const string& access_key, const string& character_name) {
|
uint32_t serial_number, const std::string& access_key, const std::string& character_name) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account = this->by_dc_serial_number.at(serial_number);
|
login->account = this->by_dc_serial_number.at(serial_number);
|
||||||
login->dc_license = login->account->dc_licenses.at(serial_number);
|
login->dc_license = login->account->dc_licenses.at(serial_number);
|
||||||
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
||||||
@@ -514,30 +510,30 @@ shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_dc_credentials(
|
std::shared_ptr<Login> AccountIndex::from_dc_credentials(
|
||||||
uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) {
|
uint32_t serial_number, const std::string& access_key, const std::string& character_name, bool allow_create) {
|
||||||
if (serial_number == 0) {
|
if (serial_number == 0) {
|
||||||
throw no_username();
|
throw no_username();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->from_dc_credentials_locked(serial_number, access_key, character_name);
|
return this->from_dc_credentials_locked(serial_number, access_key, character_name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
try {
|
try {
|
||||||
return this->from_dc_credentials_locked(serial_number, access_key, character_name);
|
return this->from_dc_credentials_locked(serial_number, access_key, character_name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow_create) {
|
if (allow_create) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = serial_number;
|
login->account->account_id = serial_number;
|
||||||
auto lic = make_shared<V1V2License>();
|
auto lic = std::make_shared<V1V2License>();
|
||||||
lic->serial_number = serial_number;
|
lic->serial_number = serial_number;
|
||||||
lic->access_key = access_key;
|
lic->access_key = access_key;
|
||||||
login->account->dc_licenses.emplace(lic->serial_number, lic);
|
login->account->dc_licenses.emplace(lic->serial_number, lic);
|
||||||
@@ -549,19 +545,19 @@ shared_ptr<Login> AccountIndex::from_dc_credentials(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_number, bool allow_create) {
|
std::shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_number, bool allow_create) {
|
||||||
if (!allow_create) {
|
if (!allow_create) {
|
||||||
throw missing_account();
|
throw missing_account();
|
||||||
}
|
}
|
||||||
if (guild_card_number == 0xFFFFFFFF) {
|
if (guild_card_number == 0xFFFFFFFF) {
|
||||||
guild_card_number = phosg::random_object<uint32_t>() & 0x7FFFFFFF;
|
guild_card_number = phosg::random_object<uint32_t>() & 0x7FFFFFFF;
|
||||||
}
|
}
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = guild_card_number;
|
login->account->account_id = guild_card_number;
|
||||||
login->account->is_temporary = true;
|
login->account->is_temporary = true;
|
||||||
auto lic = make_shared<V1V2License>();
|
auto lic = std::make_shared<V1V2License>();
|
||||||
lic->serial_number = guild_card_number;
|
lic->serial_number = guild_card_number;
|
||||||
login->account->pc_licenses.emplace(lic->serial_number, lic);
|
login->account->pc_licenses.emplace(lic->serial_number, lic);
|
||||||
login->pc_license = lic;
|
login->pc_license = lic;
|
||||||
@@ -569,9 +565,9 @@ shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_numb
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
std::shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
||||||
uint32_t serial_number, const string& access_key, const string& character_name) {
|
uint32_t serial_number, const std::string& access_key, const std::string& character_name) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account = this->by_pc_serial_number.at(serial_number);
|
login->account = this->by_pc_serial_number.at(serial_number);
|
||||||
login->pc_license = login->account->pc_licenses.at(serial_number);
|
login->pc_license = login->account->pc_licenses.at(serial_number);
|
||||||
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
||||||
@@ -587,30 +583,30 @@ shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_pc_credentials(
|
std::shared_ptr<Login> AccountIndex::from_pc_credentials(
|
||||||
uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) {
|
uint32_t serial_number, const std::string& access_key, const std::string& character_name, bool allow_create) {
|
||||||
if (serial_number == 0) {
|
if (serial_number == 0) {
|
||||||
throw no_username();
|
throw no_username();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->from_pc_credentials_locked(serial_number, access_key, character_name);
|
return this->from_pc_credentials_locked(serial_number, access_key, character_name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
try {
|
try {
|
||||||
return this->from_pc_credentials_locked(serial_number, access_key, character_name);
|
return this->from_pc_credentials_locked(serial_number, access_key, character_name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow_create) {
|
if (allow_create) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = serial_number;
|
login->account->account_id = serial_number;
|
||||||
auto lic = make_shared<V1V2License>();
|
auto lic = std::make_shared<V1V2License>();
|
||||||
lic->serial_number = serial_number;
|
lic->serial_number = serial_number;
|
||||||
lic->access_key = access_key;
|
lic->access_key = access_key;
|
||||||
login->account->pc_licenses.emplace(lic->serial_number, lic);
|
login->account->pc_licenses.emplace(lic->serial_number, lic);
|
||||||
@@ -622,9 +618,12 @@ shared_ptr<Login> AccountIndex::from_pc_credentials(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
std::shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
||||||
uint32_t serial_number, const string& access_key, const string* password, const string& character_name) {
|
uint32_t serial_number,
|
||||||
auto login = make_shared<Login>();
|
const std::string& access_key,
|
||||||
|
const std::string* password,
|
||||||
|
const std::string& character_name) {
|
||||||
|
auto login = std::make_shared<Login>();
|
||||||
login->account = this->by_gc_serial_number.at(serial_number);
|
login->account = this->by_gc_serial_number.at(serial_number);
|
||||||
login->gc_license = login->account->gc_licenses.at(serial_number);
|
login->gc_license = login->account->gc_licenses.at(serial_number);
|
||||||
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
||||||
@@ -643,34 +642,34 @@ shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_gc_credentials(
|
std::shared_ptr<Login> AccountIndex::from_gc_credentials(
|
||||||
uint32_t serial_number,
|
uint32_t serial_number,
|
||||||
const string& access_key,
|
const std::string& access_key,
|
||||||
const string* password,
|
const std::string* password,
|
||||||
const string& character_name,
|
const std::string& character_name,
|
||||||
bool allow_create) {
|
bool allow_create) {
|
||||||
if (serial_number == 0) {
|
if (serial_number == 0) {
|
||||||
throw no_username();
|
throw no_username();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->from_gc_credentials_locked(serial_number, access_key, password, character_name);
|
return this->from_gc_credentials_locked(serial_number, access_key, password, character_name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
try {
|
try {
|
||||||
return this->from_gc_credentials_locked(serial_number, access_key, password, character_name);
|
return this->from_gc_credentials_locked(serial_number, access_key, password, character_name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow_create && password) {
|
if (allow_create && password) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = serial_number;
|
login->account->account_id = serial_number;
|
||||||
auto lic = make_shared<GCLicense>();
|
auto lic = std::make_shared<GCLicense>();
|
||||||
lic->serial_number = serial_number;
|
lic->serial_number = serial_number;
|
||||||
lic->access_key = access_key;
|
lic->access_key = access_key;
|
||||||
lic->password = *password;
|
lic->password = *password;
|
||||||
@@ -683,8 +682,8 @@ shared_ptr<Login> AccountIndex::from_gc_credentials(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
|
std::shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account = this->by_xb_user_id.at(user_id);
|
login->account = this->by_xb_user_id.at(user_id);
|
||||||
login->xb_license = login->account->xb_licenses.at(user_id);
|
login->xb_license = login->account->xb_licenses.at(user_id);
|
||||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||||
@@ -693,30 +692,30 @@ shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_xb_credentials(
|
std::shared_ptr<Login> AccountIndex::from_xb_credentials(
|
||||||
const string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) {
|
const std::string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) {
|
||||||
if (user_id == 0 || account_id == 0) {
|
if (user_id == 0 || account_id == 0) {
|
||||||
throw incorrect_access_key();
|
throw incorrect_access_key();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->from_xb_credentials_locked(user_id);
|
return this->from_xb_credentials_locked(user_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
try {
|
try {
|
||||||
return this->from_xb_credentials_locked(user_id);
|
return this->from_xb_credentials_locked(user_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow_create) {
|
if (allow_create) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = phosg::fnv1a32(gamertag) & 0x7FFFFFFF;
|
login->account->account_id = phosg::fnv1a32(gamertag) & 0x7FFFFFFF;
|
||||||
auto lic = make_shared<XBLicense>();
|
auto lic = std::make_shared<XBLicense>();
|
||||||
lic->gamertag = gamertag;
|
lic->gamertag = gamertag;
|
||||||
lic->user_id = user_id;
|
lic->user_id = user_id;
|
||||||
lic->account_id = account_id;
|
lic->account_id = account_id;
|
||||||
@@ -729,8 +728,9 @@ shared_ptr<Login> AccountIndex::from_xb_credentials(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_bb_credentials_locked(const string& username, const string* password) {
|
std::shared_ptr<Login> AccountIndex::from_bb_credentials_locked(
|
||||||
auto login = make_shared<Login>();
|
const std::string& username, const std::string* password) {
|
||||||
|
auto login = std::make_shared<Login>();
|
||||||
login->account = this->by_bb_username.at(username);
|
login->account = this->by_bb_username.at(username);
|
||||||
login->bb_license = login->account->bb_licenses.at(username);
|
login->bb_license = login->account->bb_licenses.at(username);
|
||||||
if (password && (login->bb_license->password != *password)) {
|
if (password && (login->bb_license->password != *password)) {
|
||||||
@@ -742,30 +742,30 @@ shared_ptr<Login> AccountIndex::from_bb_credentials_locked(const string& usernam
|
|||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Login> AccountIndex::from_bb_credentials(
|
std::shared_ptr<Login> AccountIndex::from_bb_credentials(
|
||||||
const string& username, const string* password, bool allow_create) {
|
const std::string& username, const std::string* password, bool allow_create) {
|
||||||
if (username.empty() || (password && password->empty())) {
|
if (username.empty() || (password && password->empty())) {
|
||||||
throw no_username();
|
throw no_username();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
return this->from_bb_credentials_locked(username, password);
|
return this->from_bb_credentials_locked(username, password);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
try {
|
try {
|
||||||
return this->from_bb_credentials_locked(username, password);
|
return this->from_bb_credentials_locked(username, password);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow_create && password) {
|
if (allow_create && password) {
|
||||||
auto login = make_shared<Login>();
|
auto login = std::make_shared<Login>();
|
||||||
login->account_was_created = true;
|
login->account_was_created = true;
|
||||||
login->account = make_shared<Account>();
|
login->account = std::make_shared<Account>();
|
||||||
login->account->account_id = phosg::fnv1a32(username) & 0x7FFFFFFF;
|
login->account->account_id = phosg::fnv1a32(username) & 0x7FFFFFFF;
|
||||||
auto lic = make_shared<BBLicense>();
|
auto lic = std::make_shared<BBLicense>();
|
||||||
lic->username = username;
|
lic->username = username;
|
||||||
lic->password = *password;
|
lic->password = *password;
|
||||||
login->account->bb_licenses.emplace(lic->username, lic);
|
login->account->bb_licenses.emplace(lic->username, lic);
|
||||||
@@ -777,9 +777,9 @@ shared_ptr<Login> AccountIndex::from_bb_credentials(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<shared_ptr<Account>> AccountIndex::all() const {
|
std::vector<std::shared_ptr<Account>> AccountIndex::all() const {
|
||||||
shared_lock g(this->lock);
|
std::shared_lock g(this->lock);
|
||||||
vector<shared_ptr<Account>> ret;
|
std::vector<std::shared_ptr<Account>> ret;
|
||||||
ret.reserve(this->by_account_id.size());
|
ret.reserve(this->by_account_id.size());
|
||||||
for (const auto& it : this->by_account_id) {
|
for (const auto& it : this->by_account_id) {
|
||||||
ret.emplace_back(it.second);
|
ret.emplace_back(it.second);
|
||||||
@@ -787,44 +787,44 @@ vector<shared_ptr<Account>> AccountIndex::all() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add(shared_ptr<Account> a) {
|
void AccountIndex::add(std::shared_ptr<Account> a) {
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
this->add_locked(a);
|
this->add_locked(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_locked(shared_ptr<Account> a) {
|
void AccountIndex::add_locked(std::shared_ptr<Account> a) {
|
||||||
if (this->force_all_temporary) {
|
if (this->force_all_temporary) {
|
||||||
a->is_temporary = true;
|
a->is_temporary = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& it : a->dc_nte_licenses) {
|
for (const auto& it : a->dc_nte_licenses) {
|
||||||
if (this->by_dc_nte_serial_number.count(it.second->serial_number)) {
|
if (this->by_dc_nte_serial_number.count(it.second->serial_number)) {
|
||||||
throw runtime_error("account already exists with this DC NTE serial number");
|
throw std::runtime_error("account already exists with this DC NTE serial number");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& it : a->dc_licenses) {
|
for (const auto& it : a->dc_licenses) {
|
||||||
if (this->by_dc_serial_number.count(it.second->serial_number)) {
|
if (this->by_dc_serial_number.count(it.second->serial_number)) {
|
||||||
throw runtime_error("account already exists with this DC serial number");
|
throw std::runtime_error("account already exists with this DC serial number");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& it : a->pc_licenses) {
|
for (const auto& it : a->pc_licenses) {
|
||||||
if (this->by_pc_serial_number.count(it.second->serial_number)) {
|
if (this->by_pc_serial_number.count(it.second->serial_number)) {
|
||||||
throw runtime_error("account already exists with this PC NTE serial number");
|
throw std::runtime_error("account already exists with this PC NTE serial number");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& it : a->gc_licenses) {
|
for (const auto& it : a->gc_licenses) {
|
||||||
if (this->by_gc_serial_number.count(it.second->serial_number)) {
|
if (this->by_gc_serial_number.count(it.second->serial_number)) {
|
||||||
throw runtime_error("account already exists with this GC serial number");
|
throw std::runtime_error("account already exists with this GC serial number");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& it : a->xb_licenses) {
|
for (const auto& it : a->xb_licenses) {
|
||||||
if (this->by_xb_user_id.count(it.second->user_id)) {
|
if (this->by_xb_user_id.count(it.second->user_id)) {
|
||||||
throw runtime_error("account already exists with this XB user ID");
|
throw std::runtime_error("account already exists with this XB user ID");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& it : a->bb_licenses) {
|
for (const auto& it : a->bb_licenses) {
|
||||||
if (this->by_bb_username.count(it.second->username)) {
|
if (this->by_bb_username.count(it.second->username)) {
|
||||||
throw runtime_error("account already exists with this BB username");
|
throw std::runtime_error("account already exists with this BB username");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,10 +854,10 @@ void AccountIndex::add_locked(shared_ptr<Account> a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove(uint32_t account_id) {
|
void AccountIndex::remove(uint32_t account_id) {
|
||||||
unique_lock g(this->lock);
|
std::unique_lock g(this->lock);
|
||||||
auto acc_it = this->by_account_id.find(account_id);
|
auto acc_it = this->by_account_id.find(account_id);
|
||||||
if (acc_it == this->by_account_id.end()) {
|
if (acc_it == this->by_account_id.end()) {
|
||||||
throw out_of_range("account does not exist");
|
throw std::out_of_range("account does not exist");
|
||||||
}
|
}
|
||||||
auto a = std::move(acc_it->second);
|
auto a = std::move(acc_it->second);
|
||||||
this->by_account_id.erase(acc_it);
|
this->by_account_id.erase(acc_it);
|
||||||
@@ -882,154 +882,153 @@ void AccountIndex::remove(uint32_t account_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_dc_nte_license(shared_ptr<Account> account, shared_ptr<DCNTELicense> license) {
|
void AccountIndex::add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license) {
|
||||||
if (!this->by_dc_nte_serial_number.emplace(license->serial_number, account).second) {
|
if (!this->by_dc_nte_serial_number.emplace(license->serial_number, account).second) {
|
||||||
throw runtime_error("serial number already registered");
|
throw std::runtime_error("serial number already registered");
|
||||||
}
|
}
|
||||||
if (!account->dc_nte_licenses.emplace(license->serial_number, license).second) {
|
if (!account->dc_nte_licenses.emplace(license->serial_number, license).second) {
|
||||||
this->by_dc_nte_serial_number.erase(license->serial_number);
|
this->by_dc_nte_serial_number.erase(license->serial_number);
|
||||||
throw logic_error("serial number registered in account but not in account index");
|
throw std::logic_error("serial number registered in account but not in account index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_dc_license(shared_ptr<Account> account, shared_ptr<V1V2License> license) {
|
void AccountIndex::add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license) {
|
||||||
if (!this->by_dc_serial_number.emplace(license->serial_number, account).second) {
|
if (!this->by_dc_serial_number.emplace(license->serial_number, account).second) {
|
||||||
throw runtime_error("serial number already registered");
|
throw std::runtime_error("serial number already registered");
|
||||||
}
|
}
|
||||||
if (!account->dc_licenses.emplace(license->serial_number, license).second) {
|
if (!account->dc_licenses.emplace(license->serial_number, license).second) {
|
||||||
this->by_dc_serial_number.erase(license->serial_number);
|
this->by_dc_serial_number.erase(license->serial_number);
|
||||||
throw logic_error("serial number registered in account but not in account index");
|
throw std::logic_error("serial number registered in account but not in account index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_pc_license(shared_ptr<Account> account, shared_ptr<V1V2License> license) {
|
void AccountIndex::add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license) {
|
||||||
if (!this->by_pc_serial_number.emplace(license->serial_number, account).second) {
|
if (!this->by_pc_serial_number.emplace(license->serial_number, account).second) {
|
||||||
throw runtime_error("serial number already registered");
|
throw std::runtime_error("serial number already registered");
|
||||||
}
|
}
|
||||||
if (!account->pc_licenses.emplace(license->serial_number, license).second) {
|
if (!account->pc_licenses.emplace(license->serial_number, license).second) {
|
||||||
this->by_pc_serial_number.erase(license->serial_number);
|
this->by_pc_serial_number.erase(license->serial_number);
|
||||||
throw logic_error("serial number registered in account but not in account index");
|
throw std::logic_error("serial number registered in account but not in account index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_gc_license(shared_ptr<Account> account, shared_ptr<GCLicense> license) {
|
void AccountIndex::add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license) {
|
||||||
if (!this->by_gc_serial_number.emplace(license->serial_number, account).second) {
|
if (!this->by_gc_serial_number.emplace(license->serial_number, account).second) {
|
||||||
throw runtime_error("serial number already registered");
|
throw std::runtime_error("serial number already registered");
|
||||||
}
|
}
|
||||||
if (!account->gc_licenses.emplace(license->serial_number, license).second) {
|
if (!account->gc_licenses.emplace(license->serial_number, license).second) {
|
||||||
this->by_gc_serial_number.erase(license->serial_number);
|
this->by_gc_serial_number.erase(license->serial_number);
|
||||||
throw logic_error("serial number registered in account but not in account index");
|
throw std::logic_error("serial number registered in account but not in account index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_xb_license(shared_ptr<Account> account, shared_ptr<XBLicense> license) {
|
void AccountIndex::add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license) {
|
||||||
if (!this->by_xb_user_id.emplace(license->user_id, account).second) {
|
if (!this->by_xb_user_id.emplace(license->user_id, account).second) {
|
||||||
throw runtime_error("user ID already registered");
|
throw std::runtime_error("user ID already registered");
|
||||||
}
|
}
|
||||||
if (!account->xb_licenses.emplace(license->user_id, license).second) {
|
if (!account->xb_licenses.emplace(license->user_id, license).second) {
|
||||||
this->by_xb_user_id.erase(license->user_id);
|
this->by_xb_user_id.erase(license->user_id);
|
||||||
throw logic_error("user ID registered in account but not in account index");
|
throw std::logic_error("user ID registered in account but not in account index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::add_bb_license(shared_ptr<Account> account, shared_ptr<BBLicense> license) {
|
void AccountIndex::add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license) {
|
||||||
if (!this->by_bb_username.emplace(license->username, account).second) {
|
if (!this->by_bb_username.emplace(license->username, account).second) {
|
||||||
throw runtime_error("username already registered");
|
throw std::runtime_error("username already registered");
|
||||||
}
|
}
|
||||||
if (!account->bb_licenses.emplace(license->username, license).second) {
|
if (!account->bb_licenses.emplace(license->username, license).second) {
|
||||||
this->by_bb_username.erase(license->username);
|
this->by_bb_username.erase(license->username);
|
||||||
throw logic_error("username registered in account but not in account index");
|
throw std::logic_error("username registered in account but not in account index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove_dc_nte_license(shared_ptr<Account> account, const string& serial_number) {
|
void AccountIndex::remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number) {
|
||||||
auto it = account->dc_nte_licenses.find(serial_number);
|
auto it = account->dc_nte_licenses.find(serial_number);
|
||||||
if (it == account->dc_nte_licenses.end()) {
|
if (it == account->dc_nte_licenses.end()) {
|
||||||
throw runtime_error("license not registered to account");
|
throw std::runtime_error("license not registered to account");
|
||||||
}
|
}
|
||||||
if (!this->by_dc_nte_serial_number.erase(it->second->serial_number)) {
|
if (!this->by_dc_nte_serial_number.erase(it->second->serial_number)) {
|
||||||
throw runtime_error("license registered in account but not in account index");
|
throw std::runtime_error("license registered in account but not in account index");
|
||||||
}
|
}
|
||||||
account->dc_nte_licenses.erase(it);
|
account->dc_nte_licenses.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove_dc_license(shared_ptr<Account> account, uint32_t serial_number) {
|
void AccountIndex::remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number) {
|
||||||
auto it = account->dc_licenses.find(serial_number);
|
auto it = account->dc_licenses.find(serial_number);
|
||||||
if (it == account->dc_licenses.end()) {
|
if (it == account->dc_licenses.end()) {
|
||||||
throw runtime_error("license not registered to account");
|
throw std::runtime_error("license not registered to account");
|
||||||
}
|
}
|
||||||
if (!this->by_dc_serial_number.erase(it->second->serial_number)) {
|
if (!this->by_dc_serial_number.erase(it->second->serial_number)) {
|
||||||
throw runtime_error("license registered in account but not in account index");
|
throw std::runtime_error("license registered in account but not in account index");
|
||||||
}
|
}
|
||||||
account->dc_licenses.erase(it);
|
account->dc_licenses.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove_pc_license(shared_ptr<Account> account, uint32_t serial_number) {
|
void AccountIndex::remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number) {
|
||||||
auto it = account->pc_licenses.find(serial_number);
|
auto it = account->pc_licenses.find(serial_number);
|
||||||
if (it == account->pc_licenses.end()) {
|
if (it == account->pc_licenses.end()) {
|
||||||
throw runtime_error("license not registered to account");
|
throw std::runtime_error("license not registered to account");
|
||||||
}
|
}
|
||||||
if (!this->by_pc_serial_number.erase(it->second->serial_number)) {
|
if (!this->by_pc_serial_number.erase(it->second->serial_number)) {
|
||||||
throw runtime_error("license registered in account but not in account index");
|
throw std::runtime_error("license registered in account but not in account index");
|
||||||
}
|
}
|
||||||
account->pc_licenses.erase(it);
|
account->pc_licenses.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove_gc_license(shared_ptr<Account> account, uint32_t serial_number) {
|
void AccountIndex::remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number) {
|
||||||
auto it = account->gc_licenses.find(serial_number);
|
auto it = account->gc_licenses.find(serial_number);
|
||||||
if (it == account->gc_licenses.end()) {
|
if (it == account->gc_licenses.end()) {
|
||||||
throw runtime_error("license not registered to account");
|
throw std::runtime_error("license not registered to account");
|
||||||
}
|
}
|
||||||
if (!this->by_gc_serial_number.erase(it->second->serial_number)) {
|
if (!this->by_gc_serial_number.erase(it->second->serial_number)) {
|
||||||
throw runtime_error("license registered in account but not in account index");
|
throw std::runtime_error("license registered in account but not in account index");
|
||||||
}
|
}
|
||||||
account->gc_licenses.erase(it);
|
account->gc_licenses.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove_xb_license(shared_ptr<Account> account, uint64_t user_id) {
|
void AccountIndex::remove_xb_license(std::shared_ptr<Account> account, uint64_t user_id) {
|
||||||
auto it = account->xb_licenses.find(user_id);
|
auto it = account->xb_licenses.find(user_id);
|
||||||
if (it == account->xb_licenses.end()) {
|
if (it == account->xb_licenses.end()) {
|
||||||
throw runtime_error("license not registered to account");
|
throw std::runtime_error("license not registered to account");
|
||||||
}
|
}
|
||||||
if (!this->by_xb_user_id.erase(it->second->user_id)) {
|
if (!this->by_xb_user_id.erase(it->second->user_id)) {
|
||||||
throw runtime_error("license registered in account but not in account index");
|
throw std::runtime_error("license registered in account but not in account index");
|
||||||
}
|
}
|
||||||
account->xb_licenses.erase(it);
|
account->xb_licenses.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountIndex::remove_bb_license(shared_ptr<Account> account, const string& username) {
|
void AccountIndex::remove_bb_license(std::shared_ptr<Account> account, const std::string& username) {
|
||||||
auto it = account->bb_licenses.find(username);
|
auto it = account->bb_licenses.find(username);
|
||||||
if (it == account->bb_licenses.end()) {
|
if (it == account->bb_licenses.end()) {
|
||||||
throw runtime_error("license not registered to account");
|
throw std::runtime_error("license not registered to account");
|
||||||
}
|
}
|
||||||
if (!this->by_bb_username.erase(it->second->username)) {
|
if (!this->by_bb_username.erase(it->second->username)) {
|
||||||
throw runtime_error("license registered in account but not in account index");
|
throw std::runtime_error("license registered in account but not in account index");
|
||||||
}
|
}
|
||||||
account->bb_licenses.erase(it);
|
account->bb_licenses.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
|
std::shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
|
||||||
shared_ptr<const Account> src_a, const string& variation_data) const {
|
std::shared_ptr<const Account> src_a, const std::string& variation_data) const {
|
||||||
auto ret = make_shared<Account>(*src_a);
|
auto ret = std::make_shared<Account>(*src_a);
|
||||||
ret->is_temporary = true;
|
ret->is_temporary = true;
|
||||||
ret->account_id = phosg::fnv1a32(&src_a->account_id, sizeof(src_a->account_id));
|
ret->account_id = phosg::fnv1a32(&src_a->account_id, sizeof(src_a->account_id));
|
||||||
ret->account_id = phosg::fnv1a32(variation_data, ret->account_id);
|
ret->account_id = phosg::fnv1a32(variation_data, ret->account_id);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountIndex::AccountIndex(bool force_all_temporary)
|
AccountIndex::AccountIndex(bool force_all_temporary) : force_all_temporary(force_all_temporary) {
|
||||||
: force_all_temporary(force_all_temporary) {
|
|
||||||
if (!this->force_all_temporary) {
|
if (!this->force_all_temporary) {
|
||||||
if (!std::filesystem::is_directory("system/licenses")) {
|
if (!std::filesystem::is_directory("system/licenses")) {
|
||||||
std::filesystem::create_directories("system/licenses");
|
std::filesystem::create_directories("system/licenses");
|
||||||
} else {
|
} else {
|
||||||
for (const auto& item : std::filesystem::directory_iterator("system/licenses")) {
|
for (const auto& item : std::filesystem::directory_iterator("system/licenses")) {
|
||||||
string filename = item.path().filename().string();
|
std::string filename = item.path().filename().string();
|
||||||
if (filename.ends_with(".json")) {
|
if (filename.ends_with(".json")) {
|
||||||
try {
|
try {
|
||||||
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + filename));
|
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + filename));
|
||||||
this->add(make_shared<Account>(json));
|
this->add(std::make_shared<Account>(json));
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
phosg::log_error_f("Failed to index account {}", filename);
|
phosg::log_error_f("Failed to index account {}", filename);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-106
@@ -14,8 +14,6 @@
|
|||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
#include "Types.hh"
|
#include "Types.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
class AddressTranslator {
|
class AddressTranslator {
|
||||||
public:
|
public:
|
||||||
enum class ExpandMethod {
|
enum class ExpandMethod {
|
||||||
@@ -63,7 +61,7 @@ public:
|
|||||||
case ExpandMethod::RAW_BOTH:
|
case ExpandMethod::RAW_BOTH:
|
||||||
return "RAW_BOTH";
|
return "RAW_BOTH";
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid expand method");
|
throw std::logic_error("invalid expand method");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ public:
|
|||||||
case ExpandMethod::RAW_BOTH:
|
case ExpandMethod::RAW_BOTH:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid expand method");
|
throw std::logic_error("invalid expand method");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,46 +105,46 @@ public:
|
|||||||
case ExpandMethod::RAW_BOTH:
|
case ExpandMethod::RAW_BOTH:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid expand method");
|
throw std::logic_error("invalid expand method");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddressTranslator(const string& directory)
|
AddressTranslator(const std::string& directory)
|
||||||
: log("[addr-trans] "),
|
: log("[addr-trans] "),
|
||||||
directory(directory) {
|
directory(directory) {
|
||||||
while (this->directory.ends_with("/")) {
|
while (this->directory.ends_with("/")) {
|
||||||
this->directory.pop_back();
|
this->directory.pop_back();
|
||||||
}
|
}
|
||||||
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
||||||
string filename = item.path().filename().string();
|
std::string filename = item.path().filename().string();
|
||||||
if (filename.size() < 4) {
|
if (filename.size() < 4) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
string name = filename.substr(0, filename.size() - 4);
|
std::string name = filename.substr(0, filename.size() - 4);
|
||||||
string path = directory + "/" + filename;
|
std::string path = directory + "/" + filename;
|
||||||
|
|
||||||
if (filename.ends_with(".dol")) {
|
if (filename.ends_with(".dol")) {
|
||||||
ResourceDASM::DOLFile dol(path.c_str());
|
ResourceDASM::DOLFile dol(path.c_str());
|
||||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
dol.load_into(mem);
|
dol.load_into(mem);
|
||||||
this->mems.emplace(name, mem);
|
this->mems.emplace(name, mem);
|
||||||
this->ppc_mems.emplace(mem);
|
this->ppc_mems.emplace(mem);
|
||||||
this->log.info_f("Loaded {}", name);
|
this->log.info_f("Loaded {}", name);
|
||||||
} else if (filename.ends_with(".xbe")) {
|
} else if (filename.ends_with(".xbe")) {
|
||||||
ResourceDASM::XBEFile xbe(path.c_str());
|
ResourceDASM::XBEFile xbe(path.c_str());
|
||||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
xbe.load_into(mem);
|
xbe.load_into(mem);
|
||||||
this->mems.emplace(name, mem);
|
this->mems.emplace(name, mem);
|
||||||
this->log.info_f("Loaded {}", name);
|
this->log.info_f("Loaded {}", name);
|
||||||
} else if (filename.ends_with(".exe")) {
|
} else if (filename.ends_with(".exe")) {
|
||||||
ResourceDASM::PEFile pe(path.c_str());
|
ResourceDASM::PEFile pe(path.c_str());
|
||||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
pe.load_into(mem);
|
pe.load_into(mem);
|
||||||
this->mems.emplace(name, mem);
|
this->mems.emplace(name, mem);
|
||||||
this->log.info_f("Loaded {}", name);
|
this->log.info_f("Loaded {}", name);
|
||||||
} else if (filename.ends_with(".bin")) {
|
} else if (filename.ends_with(".bin")) {
|
||||||
string data = phosg::load_file(path);
|
std::string data = phosg::load_file(path);
|
||||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
mem->allocate_at(0x8C010000, data.size());
|
mem->allocate_at(0x8C010000, data.size());
|
||||||
mem->memcpy(0x8C010000, data.data(), data.size());
|
mem->memcpy(0x8C010000, data.data(), data.size());
|
||||||
this->mems.emplace(name, mem);
|
this->mems.emplace(name, mem);
|
||||||
@@ -156,10 +154,10 @@ public:
|
|||||||
}
|
}
|
||||||
~AddressTranslator() = default;
|
~AddressTranslator() = default;
|
||||||
|
|
||||||
const string& get_source_filename() const {
|
const std::string& get_source_filename() const {
|
||||||
return this->src_filename;
|
return this->src_filename;
|
||||||
}
|
}
|
||||||
void set_source_file(const string& filename) {
|
void set_source_file(const std::string& filename) {
|
||||||
this->src_filename = filename;
|
this->src_filename = filename;
|
||||||
this->src_mem = this->mems.at(this->src_filename);
|
this->src_mem = this->mems.at(this->src_filename);
|
||||||
}
|
}
|
||||||
@@ -178,25 +176,25 @@ public:
|
|||||||
uint32_t opcode = r.get_u32b();
|
uint32_t opcode = r.get_u32b();
|
||||||
if ((opcode & 0xFFFF0000) == 0x3DA00000) {
|
if ((opcode & 0xFFFF0000) == 0x3DA00000) {
|
||||||
if (r13_high_found) {
|
if (r13_high_found) {
|
||||||
throw runtime_error("multiple values for r13_high");
|
throw std::runtime_error("multiple values for r13_high");
|
||||||
}
|
}
|
||||||
r13_high_found = true;
|
r13_high_found = true;
|
||||||
r13 |= (opcode << 16);
|
r13 |= (opcode << 16);
|
||||||
} else if ((opcode & 0xFFFF0000) == 0x3C400000) {
|
} else if ((opcode & 0xFFFF0000) == 0x3C400000) {
|
||||||
if (r2_high_found) {
|
if (r2_high_found) {
|
||||||
throw runtime_error("multiple values for r2_high");
|
throw std::runtime_error("multiple values for r2_high");
|
||||||
}
|
}
|
||||||
r2_high_found = true;
|
r2_high_found = true;
|
||||||
r2 |= (opcode << 16);
|
r2 |= (opcode << 16);
|
||||||
} else if ((opcode & 0xFFFF0000) == 0x61AD0000) {
|
} else if ((opcode & 0xFFFF0000) == 0x61AD0000) {
|
||||||
if (r13_low_found) {
|
if (r13_low_found) {
|
||||||
throw runtime_error("multiple values for r13_low");
|
throw std::runtime_error("multiple values for r13_low");
|
||||||
}
|
}
|
||||||
r13_low_found = true;
|
r13_low_found = true;
|
||||||
r13 |= (opcode & 0xFFFF);
|
r13 |= (opcode & 0xFFFF);
|
||||||
} else if ((opcode & 0xFFFF0000) == 0x60420000) {
|
} else if ((opcode & 0xFFFF0000) == 0x60420000) {
|
||||||
if (r2_low_found) {
|
if (r2_low_found) {
|
||||||
throw runtime_error("multiple values for r2_low");
|
throw std::runtime_error("multiple values for r2_low");
|
||||||
}
|
}
|
||||||
r2_low_found = true;
|
r2_low_found = true;
|
||||||
r2 |= (opcode & 0xFFFF);
|
r2 |= (opcode & 0xFFFF);
|
||||||
@@ -217,11 +215,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ParseDATConstructorTableSpec {
|
struct ParseDATConstructorTableSpec {
|
||||||
string src_name;
|
std::string src_name;
|
||||||
uint32_t index_addr;
|
uint32_t index_addr;
|
||||||
size_t num_areas;
|
size_t num_areas;
|
||||||
bool has_names;
|
bool has_names;
|
||||||
vector<uint32_t> x86_constructor_calls;
|
std::vector<uint32_t> x86_constructor_calls;
|
||||||
|
|
||||||
ParseDATConstructorTableSpec(const phosg::JSON& json) {
|
ParseDATConstructorTableSpec(const phosg::JSON& json) {
|
||||||
this->src_name = json.at("SourceName").as_string();
|
this->src_name = json.at("SourceName").as_string();
|
||||||
@@ -233,8 +231,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
|
static std::vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
|
||||||
vector<ParseDATConstructorTableSpec> ret;
|
std::vector<ParseDATConstructorTableSpec> ret;
|
||||||
for (const auto& z : json.as_list()) {
|
for (const auto& z : json.as_list()) {
|
||||||
ret.emplace_back(*z);
|
ret.emplace_back(*z);
|
||||||
}
|
}
|
||||||
@@ -267,25 +265,25 @@ public:
|
|||||||
|
|
||||||
// Returns {type: {constructor_addr: [(start_area, end_area), ...]}}
|
// Returns {type: {constructor_addr: [(start_area, end_area), ...]}}
|
||||||
template <typename EntryT>
|
template <typename EntryT>
|
||||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> parse_dat_constructor_table_t(
|
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> parse_dat_constructor_table_t(
|
||||||
shared_ptr<const ResourceDASM::MemoryContext>& mem, const ParseDATConstructorTableSpec& spec) {
|
std::shared_ptr<const ResourceDASM::MemoryContext>& mem, const ParseDATConstructorTableSpec& spec) {
|
||||||
if (!mem) {
|
if (!mem) {
|
||||||
throw runtime_error("no file selected");
|
throw std::runtime_error("no file selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
// On some of the x86 builds of the game (PCv2 and Xbox), the constructor tables aren't entirely static in the data
|
// On some of the x86 builds of the game (PCv2 and Xbox), the constructor tables aren't entirely static in the data
|
||||||
// sections - some parts are written during static initialization instead. To handle this, we make a copy of the
|
// sections - some parts are written during static initialization instead. To handle this, we make a copy of the
|
||||||
// immutable MemoryContext and run the static initialization functions using resource_dasm's emulator before
|
// immutable MemoryContext and run the static initialization functions using resource_dasm's emulator before
|
||||||
// parsing the constructor table.
|
// parsing the constructor table.
|
||||||
shared_ptr<const ResourceDASM::MemoryContext> effective_mem = mem;
|
std::shared_ptr<const ResourceDASM::MemoryContext> effective_mem = mem;
|
||||||
if (!spec.x86_constructor_calls.empty()) {
|
if (!spec.x86_constructor_calls.empty()) {
|
||||||
auto constructed_mem = make_shared<ResourceDASM::MemoryContext>(mem->duplicate());
|
auto constructed_mem = std::make_shared<ResourceDASM::MemoryContext>(mem->duplicate());
|
||||||
uint32_t esp = constructed_mem->allocate(0x1000) + 0x1000;
|
uint32_t esp = constructed_mem->allocate(0x1000) + 0x1000;
|
||||||
for (uint32_t constructor_addr : spec.x86_constructor_calls) {
|
for (uint32_t constructor_addr : spec.x86_constructor_calls) {
|
||||||
ResourceDASM::X86Emulator emu(constructed_mem);
|
ResourceDASM::X86Emulator emu(constructed_mem);
|
||||||
|
|
||||||
// Uncomment for debugging
|
// Uncomment for debugging
|
||||||
// auto debugger = make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
|
// auto debugger = std::make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
|
||||||
// debugger->bind(emu);
|
// debugger->bind(emu);
|
||||||
// debugger->state.mode = ResourceDASM::DebuggerMode::TRACE;
|
// debugger->state.mode = ResourceDASM::DebuggerMode::TRACE;
|
||||||
|
|
||||||
@@ -295,7 +293,7 @@ public:
|
|||||||
constructed_mem->write_u32l(esp - 4, 0xFFFFFFFF); // Return addr
|
constructed_mem->write_u32l(esp - 4, 0xFFFFFFFF); // Return addr
|
||||||
try {
|
try {
|
||||||
emu.execute();
|
emu.execute();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
if (regs.eip != 0xFFFFFFFF) {
|
if (regs.eip != 0xFFFFFFFF) {
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -304,7 +302,7 @@ public:
|
|||||||
effective_mem = constructed_mem;
|
effective_mem = constructed_mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
|
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> table;
|
||||||
|
|
||||||
auto index_r = effective_mem->reader(spec.index_addr, spec.num_areas * sizeof(uint32_t));
|
auto index_r = effective_mem->reader(spec.index_addr, spec.num_areas * sizeof(uint32_t));
|
||||||
for (size_t area = 0; area < spec.num_areas; area++) {
|
for (size_t area = 0; area < spec.num_areas; area++) {
|
||||||
@@ -322,18 +320,18 @@ public:
|
|||||||
if (!group.empty() && (group.back().second == (area - 1))) {
|
if (!group.empty() && (group.back().second == (area - 1))) {
|
||||||
group.back().second = area;
|
group.back().second = area;
|
||||||
} else {
|
} else {
|
||||||
group.emplace_back(make_pair(area, area));
|
group.emplace_back(std::make_pair(area, area));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (entries_r.eof()) {
|
if (entries_r.eof()) {
|
||||||
throw runtime_error("did not find end-of-entries marker");
|
throw std::runtime_error("did not find end-of-entries marker");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t area_mask_for_ranges(const vector<pair<size_t, size_t>>& ranges) {
|
static uint64_t area_mask_for_ranges(const std::vector<std::pair<size_t, size_t>>& ranges) {
|
||||||
uint64_t ret = 0;
|
uint64_t ret = 0;
|
||||||
for (const auto& [start, end] : ranges) {
|
for (const auto& [start, end] : ranges) {
|
||||||
for (size_t z = start; z <= end; z++) {
|
for (size_t z = start; z <= end; z++) {
|
||||||
@@ -344,7 +342,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void parse_dat_constructor_table(const ParseDATConstructorTableSpec& spec) {
|
void parse_dat_constructor_table(const ParseDATConstructorTableSpec& spec) {
|
||||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
|
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> table;
|
||||||
auto spec_mem = this->mems.at(spec.src_name);
|
auto spec_mem = this->mems.at(spec.src_name);
|
||||||
if (this->ppc_mems.count(spec_mem)) {
|
if (this->ppc_mems.count(spec_mem)) {
|
||||||
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
|
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
|
||||||
@@ -374,10 +372,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void parse_dat_constructor_table_multi(
|
void parse_dat_constructor_table_multi(
|
||||||
const vector<ParseDATConstructorTableSpec>& specs, bool is_enemies, bool print_area_masks) {
|
const std::vector<ParseDATConstructorTableSpec>& specs, bool is_enemies, bool print_area_masks) {
|
||||||
map<string, map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>>> all_tables;
|
std::map<std::string, std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>>> all_tables;
|
||||||
for (const auto& spec : specs) {
|
for (const auto& spec : specs) {
|
||||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
|
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> table;
|
||||||
auto spec_mem = this->mems.at(spec.src_name);
|
auto spec_mem = this->mems.at(spec.src_name);
|
||||||
if (this->ppc_mems.count(spec_mem)) {
|
if (this->ppc_mems.count(spec_mem)) {
|
||||||
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
|
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
|
||||||
@@ -389,14 +387,14 @@ public:
|
|||||||
all_tables.emplace(spec.src_name, std::move(table));
|
all_tables.emplace(spec.src_name, std::move(table));
|
||||||
}
|
}
|
||||||
|
|
||||||
map<string, size_t> version_widths;
|
std::map<std::string, size_t> version_widths;
|
||||||
map<uint32_t, map<string, string>> formatted_cells_for_type;
|
std::map<uint32_t, std::map<std::string, std::string>> formatted_cells_for_type;
|
||||||
for (const auto& spec : specs) {
|
for (const auto& spec : specs) {
|
||||||
const auto& table = all_tables.at(spec.src_name);
|
const auto& table = all_tables.at(spec.src_name);
|
||||||
size_t max_width = 0;
|
size_t max_width = 0;
|
||||||
|
|
||||||
for (const auto& [type, constructor_to_area_ranges] : table) {
|
for (const auto& [type, constructor_to_area_ranges] : table) {
|
||||||
string cell_data;
|
std::string cell_data;
|
||||||
for (const auto& [constructor, area_ranges] : constructor_to_area_ranges) {
|
for (const auto& [constructor, area_ranges] : constructor_to_area_ranges) {
|
||||||
if (!cell_data.empty()) {
|
if (!cell_data.empty()) {
|
||||||
cell_data.push_back(' ');
|
cell_data.push_back(' ');
|
||||||
@@ -417,14 +415,14 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
max_width = max<size_t>(max_width, cell_data.size());
|
max_width = std::max<size_t>(max_width, cell_data.size());
|
||||||
formatted_cells_for_type[type][spec.src_name] = std::move(cell_data);
|
formatted_cells_for_type[type][spec.src_name] = std::move(cell_data);
|
||||||
}
|
}
|
||||||
version_widths[spec.src_name] = max_width;
|
version_widths[spec.src_name] = max_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> formatted_lines;
|
std::vector<std::string> formatted_lines;
|
||||||
string header_line = "TYPE =>";
|
std::string header_line = "TYPE =>";
|
||||||
for (const auto& spec : specs) {
|
for (const auto& spec : specs) {
|
||||||
size_t width = version_widths.at(spec.src_name);
|
size_t width = version_widths.at(spec.src_name);
|
||||||
header_line.push_back(' ');
|
header_line.push_back(' ');
|
||||||
@@ -436,7 +434,7 @@ public:
|
|||||||
header_line += " NAME";
|
header_line += " NAME";
|
||||||
|
|
||||||
for (const auto& [type, formatted_cells] : formatted_cells_for_type) {
|
for (const auto& [type, formatted_cells] : formatted_cells_for_type) {
|
||||||
string line = std::format("{:04X} =>", type);
|
std::string line = std::format("{:04X} =>", type);
|
||||||
for (const auto& spec : specs) {
|
for (const auto& spec : specs) {
|
||||||
size_t width = version_widths.at(spec.src_name);
|
size_t width = version_widths.at(spec.src_name);
|
||||||
try {
|
try {
|
||||||
@@ -446,7 +444,7 @@ public:
|
|||||||
if (width > cell_data.size()) {
|
if (width > cell_data.size()) {
|
||||||
line.resize(line.size() + (width - cell_data.size()), ' ');
|
line.resize(line.size() + (width - cell_data.size()), ' ');
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
line.resize(line.size() + (width + 1), ' ');
|
line.resize(line.size() + (width + 1), ' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,21 +464,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t find_match(
|
uint32_t find_match(
|
||||||
shared_ptr<const ResourceDASM::MemoryContext> dest_mem,
|
std::shared_ptr<const ResourceDASM::MemoryContext> dest_mem,
|
||||||
uint32_t src_addr,
|
uint32_t src_addr,
|
||||||
uint32_t src_size,
|
uint32_t src_size,
|
||||||
ExpandMethod expand_method) const {
|
ExpandMethod expand_method) const {
|
||||||
bool is_ppc = this->is_ppc_expand_method(expand_method);
|
bool is_ppc = this->is_ppc_expand_method(expand_method);
|
||||||
bool is_ppc_data = this->is_ppc_data_expand_method(expand_method);
|
bool is_ppc_data = this->is_ppc_data_expand_method(expand_method);
|
||||||
if (!this->src_mem) {
|
if (!this->src_mem) {
|
||||||
throw runtime_error("no source file selected");
|
throw std::runtime_error("no source file selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src_size == 0) {
|
if (src_size == 0) {
|
||||||
src_size = is_ppc ? 4 : 1;
|
src_size = is_ppc ? 4 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<uint32_t, uint32_t> src_section = make_pair(0, 0);
|
std::pair<uint32_t, uint32_t> src_section = std::make_pair(0, 0);
|
||||||
for (const auto& sec : this->src_mem->allocated_blocks()) {
|
for (const auto& sec : this->src_mem->allocated_blocks()) {
|
||||||
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
|
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
|
||||||
src_section = sec;
|
src_section = sec;
|
||||||
@@ -488,7 +486,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!src_section.second) {
|
if (!src_section.second) {
|
||||||
throw runtime_error("source address not within any section");
|
throw std::runtime_error("source address not within any section");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* method_token = this->name_for_expand_method(expand_method);
|
const char* method_token = this->name_for_expand_method(expand_method);
|
||||||
@@ -570,7 +568,7 @@ public:
|
|||||||
if (num_matches == 1) {
|
if (num_matches == 1) {
|
||||||
return last_match_address;
|
return last_match_address;
|
||||||
} else if (num_matches == 0) {
|
} else if (num_matches == 0) {
|
||||||
throw runtime_error("did not find exactly one match");
|
throw std::runtime_error("did not find exactly one match");
|
||||||
}
|
}
|
||||||
bool can_expand_backward = false;
|
bool can_expand_backward = false;
|
||||||
bool can_expand_forward = false;
|
bool can_expand_forward = false;
|
||||||
@@ -614,10 +612,10 @@ public:
|
|||||||
can_expand_forward = (src_bytes_available_after > match_bytes_after);
|
can_expand_forward = (src_bytes_available_after > match_bytes_after);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid expand method");
|
throw std::logic_error("invalid expand method");
|
||||||
}
|
}
|
||||||
if (!can_expand_backward && !can_expand_forward) {
|
if (!can_expand_backward && !can_expand_forward) {
|
||||||
throw runtime_error("no further expansion is allowed");
|
throw std::runtime_error("no further expansion is allowed");
|
||||||
}
|
}
|
||||||
if (can_expand_backward) {
|
if (can_expand_backward) {
|
||||||
match_bytes_before += (is_ppc ? 4 : 1);
|
match_bytes_before += (is_ppc ? 4 : 1);
|
||||||
@@ -626,7 +624,7 @@ public:
|
|||||||
match_bytes_after += (is_ppc ? 4 : 1);
|
match_bytes_after += (is_ppc ? 4 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw runtime_error("scan field too long; too many matches");
|
throw std::runtime_error("scan field too long; too many matches");
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MatchType {
|
enum class MatchType {
|
||||||
@@ -637,18 +635,18 @@ public:
|
|||||||
|
|
||||||
void find_all_matches(uint32_t src_addr, uint32_t src_size, MatchType type) const {
|
void find_all_matches(uint32_t src_addr, uint32_t src_size, MatchType type) const {
|
||||||
if (!this->src_mem) {
|
if (!this->src_mem) {
|
||||||
throw runtime_error("no source file selected");
|
throw std::runtime_error("no source file selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
map<string, uint32_t> results;
|
std::map<std::string, uint32_t> results;
|
||||||
for (const auto& it : this->mems) {
|
for (const auto& it : this->mems) {
|
||||||
if (it.second == this->src_mem) {
|
if (it.second == this->src_mem) {
|
||||||
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
||||||
results.emplace(it.first, src_addr);
|
results.emplace(it.first, src_addr);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
vector<future<uint32_t>> futures;
|
std::vector<std::future<uint32_t>> futures;
|
||||||
static const vector<ExpandMethod> ppc_methods = {
|
static const std::vector<ExpandMethod> ppc_methods = {
|
||||||
ExpandMethod::PPC_TEXT_FORWARD,
|
ExpandMethod::PPC_TEXT_FORWARD,
|
||||||
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
|
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
|
||||||
ExpandMethod::PPC_TEXT_BACKWARD,
|
ExpandMethod::PPC_TEXT_BACKWARD,
|
||||||
@@ -660,7 +658,7 @@ public:
|
|||||||
ExpandMethod::PPC_DATA_BACKWARD,
|
ExpandMethod::PPC_DATA_BACKWARD,
|
||||||
ExpandMethod::PPC_DATA_BOTH,
|
ExpandMethod::PPC_DATA_BOTH,
|
||||||
};
|
};
|
||||||
static const vector<ExpandMethod> ppc_text_methods = {
|
static const std::vector<ExpandMethod> ppc_text_methods = {
|
||||||
ExpandMethod::PPC_TEXT_FORWARD,
|
ExpandMethod::PPC_TEXT_FORWARD,
|
||||||
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
|
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
|
||||||
ExpandMethod::PPC_TEXT_BACKWARD,
|
ExpandMethod::PPC_TEXT_BACKWARD,
|
||||||
@@ -669,18 +667,18 @@ public:
|
|||||||
ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER,
|
ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER,
|
||||||
ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN,
|
ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN,
|
||||||
};
|
};
|
||||||
static const vector<ExpandMethod> ppc_data_methods = {
|
static const std::vector<ExpandMethod> ppc_data_methods = {
|
||||||
ExpandMethod::PPC_DATA_FORWARD,
|
ExpandMethod::PPC_DATA_FORWARD,
|
||||||
ExpandMethod::PPC_DATA_BACKWARD,
|
ExpandMethod::PPC_DATA_BACKWARD,
|
||||||
ExpandMethod::PPC_DATA_BOTH,
|
ExpandMethod::PPC_DATA_BOTH,
|
||||||
};
|
};
|
||||||
static const vector<ExpandMethod> raw_methods = {
|
static const std::vector<ExpandMethod> raw_methods = {
|
||||||
ExpandMethod::RAW_FORWARD,
|
ExpandMethod::RAW_FORWARD,
|
||||||
ExpandMethod::RAW_BACKWARD,
|
ExpandMethod::RAW_BACKWARD,
|
||||||
ExpandMethod::RAW_BOTH,
|
ExpandMethod::RAW_BOTH,
|
||||||
};
|
};
|
||||||
|
|
||||||
const vector<ExpandMethod>* methods;
|
const std::vector<ExpandMethod>* methods;
|
||||||
if (this->ppc_mems.count(it.second)) {
|
if (this->ppc_mems.count(it.second)) {
|
||||||
if (type == MatchType::ANY) {
|
if (type == MatchType::ANY) {
|
||||||
methods = &ppc_methods;
|
methods = &ppc_methods;
|
||||||
@@ -689,7 +687,7 @@ public:
|
|||||||
} else if (type == MatchType::DATA) {
|
} else if (type == MatchType::DATA) {
|
||||||
methods = &ppc_data_methods;
|
methods = &ppc_data_methods;
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("invalid match type");
|
throw std::logic_error("invalid match type");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
methods = &raw_methods;
|
methods = &raw_methods;
|
||||||
@@ -699,14 +697,14 @@ public:
|
|||||||
futures.emplace_back(async(&AddressTranslator::find_match, this, it.second, src_addr, src_size, methods->at(z)));
|
futures.emplace_back(async(&AddressTranslator::find_match, this, it.second, src_addr, src_size, methods->at(z)));
|
||||||
}
|
}
|
||||||
|
|
||||||
unordered_set<uint32_t> match_addrs;
|
std::unordered_set<uint32_t> match_addrs;
|
||||||
for (size_t z = 0; z < futures.size(); z++) {
|
for (size_t z = 0; z < futures.size(); z++) {
|
||||||
const char* method_name = this->name_for_expand_method(methods->at(z));
|
const char* method_name = this->name_for_expand_method(methods->at(z));
|
||||||
try {
|
try {
|
||||||
uint32_t ret = futures[z].get();
|
uint32_t ret = futures[z].get();
|
||||||
log.info_f("({}) ({}) {:08X}", it.first, method_name, ret);
|
log.info_f("({}) ({}) {:08X}", it.first, method_name, ret);
|
||||||
match_addrs.emplace(ret);
|
match_addrs.emplace(ret);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
log.error_f("({}) ({}) failed: {}", it.first, method_name, e.what());
|
log.error_f("({}) ({}) failed: {}", it.first, method_name, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,12 +724,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t find_be_to_le_data_match(
|
uint32_t find_be_to_le_data_match(
|
||||||
shared_ptr<const ResourceDASM::MemoryContext> dest_mem, uint32_t src_addr, uint32_t src_size) const {
|
std::shared_ptr<const ResourceDASM::MemoryContext> dest_mem, uint32_t src_addr, uint32_t src_size) const {
|
||||||
if (src_size == 0) {
|
if (src_size == 0) {
|
||||||
src_size = 4;
|
src_size = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<uint32_t, uint32_t> src_section = make_pair(0, 0);
|
std::pair<uint32_t, uint32_t> src_section = std::make_pair(0, 0);
|
||||||
for (const auto& sec : this->src_mem->allocated_blocks()) {
|
for (const auto& sec : this->src_mem->allocated_blocks()) {
|
||||||
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
|
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
|
||||||
src_section = sec;
|
src_section = sec;
|
||||||
@@ -739,7 +737,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!src_section.second) {
|
if (!src_section.second) {
|
||||||
throw runtime_error("source address not within any section");
|
throw std::runtime_error("source address not within any section");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t src_offset = src_addr - src_section.first;
|
size_t src_offset = src_addr - src_section.first;
|
||||||
@@ -782,12 +780,12 @@ public:
|
|||||||
if (num_matches == 1) {
|
if (num_matches == 1) {
|
||||||
return last_match_address;
|
return last_match_address;
|
||||||
} else if (num_matches == 0) {
|
} else if (num_matches == 0) {
|
||||||
throw runtime_error("did not find exactly one match");
|
throw std::runtime_error("did not find exactly one match");
|
||||||
}
|
}
|
||||||
bool can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4);
|
bool can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4);
|
||||||
bool can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4);
|
bool can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4);
|
||||||
if (!can_expand_backward && !can_expand_forward) {
|
if (!can_expand_backward && !can_expand_forward) {
|
||||||
throw runtime_error("no further expansion is allowed");
|
throw std::runtime_error("no further expansion is allowed");
|
||||||
}
|
}
|
||||||
if (can_expand_backward) {
|
if (can_expand_backward) {
|
||||||
match_bytes_before += 4;
|
match_bytes_before += 4;
|
||||||
@@ -796,15 +794,15 @@ public:
|
|||||||
match_bytes_after += 4;
|
match_bytes_after += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw runtime_error("scan field too long; too many matches");
|
throw std::runtime_error("scan field too long; too many matches");
|
||||||
}
|
}
|
||||||
|
|
||||||
void find_all_be_to_le_data_matches(uint32_t src_addr, uint32_t src_size) const {
|
void find_all_be_to_le_data_matches(uint32_t src_addr, uint32_t src_size) const {
|
||||||
if (!this->src_mem) {
|
if (!this->src_mem) {
|
||||||
throw runtime_error("no source file selected");
|
throw std::runtime_error("no source file selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
map<string, uint32_t> results;
|
std::map<std::string, uint32_t> results;
|
||||||
for (const auto& it : this->mems) {
|
for (const auto& it : this->mems) {
|
||||||
if (it.second == this->src_mem) {
|
if (it.second == this->src_mem) {
|
||||||
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
||||||
@@ -815,7 +813,7 @@ public:
|
|||||||
try {
|
try {
|
||||||
ret = this->find_be_to_le_data_match(it.second, src_addr, src_size);
|
ret = this->find_be_to_le_data_match(it.second, src_addr, src_size);
|
||||||
log.info_f("({}) {:08X}", it.first, ret);
|
log.info_f("({}) {:08X}", it.first, ret);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
log.error_f("({}) failed: {}", it.first, e.what());
|
log.error_f("({}) failed: {}", it.first, e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,7 +829,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void find_data(const string& data) const {
|
void find_data(const std::string& data) const {
|
||||||
for (const auto& [name, mem] : this->mems) {
|
for (const auto& [name, mem] : this->mems) {
|
||||||
for (const auto& [sec_addr, sec_size] : mem->allocated_blocks()) {
|
for (const auto& [sec_addr, sec_size] : mem->allocated_blocks()) {
|
||||||
uint32_t last_addr = sec_addr + sec_size - data.size();
|
uint32_t last_addr = sec_addr + sec_size - data.size();
|
||||||
@@ -844,10 +842,10 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_command(const string& command) {
|
void handle_command(const std::string& command) {
|
||||||
auto tokens = phosg::split(command, ' ');
|
auto tokens = phosg::split(command, ' ');
|
||||||
if (tokens.empty()) {
|
if (tokens.empty()) {
|
||||||
throw runtime_error("no command given");
|
throw std::runtime_error("no command given");
|
||||||
}
|
}
|
||||||
phosg::strip_trailing_whitespace(tokens[tokens.size() - 1]);
|
phosg::strip_trailing_whitespace(tokens[tokens.size() - 1]);
|
||||||
|
|
||||||
@@ -856,7 +854,7 @@ public:
|
|||||||
} else if (tokens[0] == "find") {
|
} else if (tokens[0] == "find") {
|
||||||
this->find_data(phosg::parse_data_string(tokens.at(1)));
|
this->find_data(phosg::parse_data_string(tokens.at(1)));
|
||||||
} else if (tokens[0] == "only") {
|
} else if (tokens[0] == "only") {
|
||||||
unordered_set<string> to_keep{tokens.begin() + 1, tokens.end()};
|
std::unordered_set<std::string> to_keep{tokens.begin() + 1, tokens.end()};
|
||||||
for (auto it = this->mems.begin(); it != this->mems.end();) {
|
for (auto it = this->mems.begin(); it != this->mems.end();) {
|
||||||
if (to_keep.count(it->first)) {
|
if (to_keep.count(it->first)) {
|
||||||
it++;
|
it++;
|
||||||
@@ -891,7 +889,7 @@ public:
|
|||||||
auto specs = ParseDATConstructorTableSpec::from_json_list(phosg::JSON::parse(phosg::load_file(tokens.at(1))));
|
auto specs = ParseDATConstructorTableSpec::from_json_list(phosg::JSON::parse(phosg::load_file(tokens.at(1))));
|
||||||
this->parse_dat_constructor_table_multi(specs, is_enemies, true);
|
this->parse_dat_constructor_table_multi(specs, is_enemies, true);
|
||||||
} else if (!tokens[0].empty()) {
|
} else if (!tokens[0].empty()) {
|
||||||
throw runtime_error("unknown command");
|
throw std::runtime_error("unknown command");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,10 +902,10 @@ public:
|
|||||||
}
|
}
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
string command = phosg::fgets(stdin);
|
std::string command = phosg::fgets(stdin);
|
||||||
try {
|
try {
|
||||||
this->handle_command(command);
|
this->handle_command(command);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.error_f("Failed: {}", e.what());
|
this->log.error_f("Failed: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,14 +914,14 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
phosg::PrefixedLogger log;
|
phosg::PrefixedLogger log;
|
||||||
string directory;
|
std::string directory;
|
||||||
unordered_map<string, shared_ptr<const ResourceDASM::MemoryContext>> mems;
|
std::unordered_map<std::string, std::shared_ptr<const ResourceDASM::MemoryContext>> mems;
|
||||||
unordered_set<shared_ptr<const ResourceDASM::MemoryContext>> ppc_mems;
|
std::unordered_set<std::shared_ptr<const ResourceDASM::MemoryContext>> ppc_mems;
|
||||||
string src_filename;
|
std::string src_filename;
|
||||||
shared_ptr<const ResourceDASM::MemoryContext> src_mem;
|
std::shared_ptr<const ResourceDASM::MemoryContext> src_mem;
|
||||||
};
|
};
|
||||||
|
|
||||||
void run_address_translator(const string& directory, const string& use_filename, const string& command) {
|
void run_address_translator(const std::string& directory, const std::string& use_filename, const std::string& command) {
|
||||||
AddressTranslator trans(directory);
|
AddressTranslator trans(directory);
|
||||||
if (!use_filename.empty()) {
|
if (!use_filename.empty()) {
|
||||||
trans.set_source_file(use_filename);
|
trans.set_source_file(use_filename);
|
||||||
@@ -936,32 +934,32 @@ void run_address_translator(const string& directory, const string& use_filename,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<DiffEntry> diff_dol_files(const string& a_filename, const string& b_filename) {
|
std::vector<DiffEntry> diff_dol_files(const std::string& a_filename, const std::string& b_filename) {
|
||||||
ResourceDASM::DOLFile a(a_filename.c_str());
|
ResourceDASM::DOLFile a(a_filename.c_str());
|
||||||
ResourceDASM::DOLFile b(b_filename.c_str());
|
ResourceDASM::DOLFile b(b_filename.c_str());
|
||||||
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
|
auto a_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
|
auto b_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
a.load_into(a_mem);
|
a.load_into(a_mem);
|
||||||
b.load_into(b_mem);
|
b.load_into(b_mem);
|
||||||
|
|
||||||
uint32_t min_addr = 0xFFFFFFFF;
|
uint32_t min_addr = 0xFFFFFFFF;
|
||||||
uint32_t max_addr = 0x00000000;
|
uint32_t max_addr = 0x00000000;
|
||||||
for (const auto& sec : a.sections) {
|
for (const auto& sec : a.sections) {
|
||||||
min_addr = min<uint32_t>(min_addr, sec.address);
|
min_addr = std::min<uint32_t>(min_addr, sec.address);
|
||||||
max_addr = max<uint32_t>(max_addr, sec.address + sec.data.size());
|
max_addr = std::max<uint32_t>(max_addr, sec.address + sec.data.size());
|
||||||
}
|
}
|
||||||
for (const auto& sec : b.sections) {
|
for (const auto& sec : b.sections) {
|
||||||
min_addr = min<uint32_t>(min_addr, sec.address);
|
min_addr = std::min<uint32_t>(min_addr, sec.address);
|
||||||
max_addr = max<uint32_t>(max_addr, sec.address + sec.data.size());
|
max_addr = std::max<uint32_t>(max_addr, sec.address + sec.data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<DiffEntry> ret;
|
std::vector<DiffEntry> ret;
|
||||||
for (uint32_t addr = min_addr; addr < max_addr; addr += 4) {
|
for (uint32_t addr = min_addr; addr < max_addr; addr += 4) {
|
||||||
bool a_exists = a_mem->exists(addr, 4);
|
bool a_exists = a_mem->exists(addr, 4);
|
||||||
bool b_exists = b_mem->exists(addr, 4);
|
bool b_exists = b_mem->exists(addr, 4);
|
||||||
if (a_exists && b_exists) {
|
if (a_exists && b_exists) {
|
||||||
string a_value = a_mem->read(addr, 4);
|
std::string a_value = a_mem->read(addr, 4);
|
||||||
string b_value = b_mem->read(addr, 4);
|
std::string b_value = b_mem->read(addr, 4);
|
||||||
if (a_value != b_value) {
|
if (a_value != b_value) {
|
||||||
if (!ret.empty() && (ret.back().address + ret.back().b_data.size() == addr)) {
|
if (!ret.empty() && (ret.back().address + ret.back().b_data.size() == addr)) {
|
||||||
ret.back().a_data += a_value;
|
ret.back().a_data += a_value;
|
||||||
@@ -975,26 +973,26 @@ vector<DiffEntry> diff_dol_files(const string& a_filename, const string& b_filen
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<DiffEntry> diff_xbe_files(const string& a_filename, const string& b_filename) {
|
std::vector<DiffEntry> diff_xbe_files(const std::string& a_filename, const std::string& b_filename) {
|
||||||
ResourceDASM::XBEFile a(a_filename.c_str());
|
ResourceDASM::XBEFile a(a_filename.c_str());
|
||||||
ResourceDASM::XBEFile b(b_filename.c_str());
|
ResourceDASM::XBEFile b(b_filename.c_str());
|
||||||
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
|
auto a_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
|
auto b_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||||
a.load_into(a_mem);
|
a.load_into(a_mem);
|
||||||
b.load_into(b_mem);
|
b.load_into(b_mem);
|
||||||
|
|
||||||
uint32_t min_addr = 0xFFFFFFFF;
|
uint32_t min_addr = 0xFFFFFFFF;
|
||||||
uint32_t max_addr = 0x00000000;
|
uint32_t max_addr = 0x00000000;
|
||||||
for (const auto& sec : a.sections) {
|
for (const auto& sec : a.sections) {
|
||||||
min_addr = min<uint32_t>(min_addr, sec.addr);
|
min_addr = std::min<uint32_t>(min_addr, sec.addr);
|
||||||
max_addr = max<uint32_t>(max_addr, sec.addr + sec.size);
|
max_addr = std::max<uint32_t>(max_addr, sec.addr + sec.size);
|
||||||
}
|
}
|
||||||
for (const auto& sec : b.sections) {
|
for (const auto& sec : b.sections) {
|
||||||
min_addr = min<uint32_t>(min_addr, sec.addr);
|
min_addr = std::min<uint32_t>(min_addr, sec.addr);
|
||||||
max_addr = max<uint32_t>(max_addr, sec.addr + sec.size);
|
max_addr = std::max<uint32_t>(max_addr, sec.addr + sec.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<DiffEntry> ret;
|
std::vector<DiffEntry> ret;
|
||||||
for (uint32_t addr = min_addr; addr < max_addr; addr++) {
|
for (uint32_t addr = min_addr; addr < max_addr; addr++) {
|
||||||
bool a_exists = a_mem->exists(addr, 1);
|
bool a_exists = a_mem->exists(addr, 1);
|
||||||
bool b_exists = b_mem->exists(addr, 1);
|
bool b_exists = b_mem->exists(addr, 1);
|
||||||
|
|||||||
+19
-21
@@ -14,9 +14,7 @@
|
|||||||
#include "Revision.hh"
|
#include "Revision.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
static const std::unordered_map<int, const char*> explanation_for_response_code{
|
||||||
|
|
||||||
static const unordered_map<int, const char*> explanation_for_response_code{
|
|
||||||
{100, "Continue"},
|
{100, "Continue"},
|
||||||
{101, "Switching Protocols"},
|
{101, "Switching Protocols"},
|
||||||
{102, "Processing"},
|
{102, "Processing"},
|
||||||
@@ -92,7 +90,7 @@ const std::string* HTTPRequest::get_header(const std::string& name) const {
|
|||||||
if (its.first == its.second) {
|
if (its.first == its.second) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const string* ret = &its.first->second;
|
const std::string* ret = &its.first->second;
|
||||||
its.first++;
|
its.first++;
|
||||||
if (its.first != its.second) {
|
if (its.first != its.second) {
|
||||||
throw std::out_of_range("Header appears multiple times: " + name);
|
throw std::out_of_range("Header appears multiple times: " + name);
|
||||||
@@ -105,7 +103,7 @@ const std::string* HTTPRequest::get_query_param(const std::string& name) const {
|
|||||||
if (its.first == its.second) {
|
if (its.first == its.second) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const string* ret = &its.first->second;
|
const std::string* ret = &its.first->second;
|
||||||
its.first++;
|
its.first++;
|
||||||
if (its.first != its.second) {
|
if (its.first != its.second) {
|
||||||
throw std::out_of_range("Query parameter appears multiple times: " + name);
|
throw std::out_of_range("Query parameter appears multiple times: " + name);
|
||||||
@@ -113,7 +111,7 @@ const std::string* HTTPRequest::get_query_param(const std::string& name) const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void url_decode_inplace(string& s) {
|
static void url_decode_inplace(std::string& s) {
|
||||||
size_t write_offset = 0, read_offset = 0;
|
size_t write_offset = 0, read_offset = 0;
|
||||||
for (; read_offset < s.size(); write_offset++) {
|
for (; read_offset < s.size(); write_offset++) {
|
||||||
if ((s[read_offset] == '%') && (read_offset < s.size() - 2)) {
|
if ((s[read_offset] == '%') && (read_offset < s.size() - 2)) {
|
||||||
@@ -139,7 +137,7 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
|||||||
std::string request_line = co_await this->r.read_line("\r\n", max_line_size);
|
std::string request_line = co_await this->r.read_line("\r\n", max_line_size);
|
||||||
auto line_tokens = phosg::split(request_line, ' ');
|
auto line_tokens = phosg::split(request_line, ' ');
|
||||||
if (line_tokens.size() != 3) {
|
if (line_tokens.size() != 3) {
|
||||||
throw runtime_error("invalid HTTP request line");
|
throw std::runtime_error("invalid HTTP request line");
|
||||||
}
|
}
|
||||||
const auto& method_token = line_tokens[0];
|
const auto& method_token = line_tokens[0];
|
||||||
if (method_token == "GET") {
|
if (method_token == "GET") {
|
||||||
@@ -169,14 +167,14 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
|||||||
req.http_version = std::move(line_tokens[2]);
|
req.http_version = std::move(line_tokens[2]);
|
||||||
|
|
||||||
size_t fragment_start_offset = line_tokens[1].find('#');
|
size_t fragment_start_offset = line_tokens[1].find('#');
|
||||||
if (fragment_start_offset != string::npos) {
|
if (fragment_start_offset != std::string::npos) {
|
||||||
req.fragment = line_tokens[1].substr(fragment_start_offset + 1);
|
req.fragment = line_tokens[1].substr(fragment_start_offset + 1);
|
||||||
line_tokens[1].resize(fragment_start_offset);
|
line_tokens[1].resize(fragment_start_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t query_start_offset = line_tokens[1].find('?');
|
size_t query_start_offset = line_tokens[1].find('?');
|
||||||
string query;
|
std::string query;
|
||||||
if (query_start_offset != string::npos) {
|
if (query_start_offset != std::string::npos) {
|
||||||
query = line_tokens[1].substr(query_start_offset + 1);
|
query = line_tokens[1].substr(query_start_offset + 1);
|
||||||
line_tokens[1].resize(query_start_offset);
|
line_tokens[1].resize(query_start_offset);
|
||||||
}
|
}
|
||||||
@@ -189,12 +187,12 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
|||||||
auto query_tokens = phosg::split(query, '&');
|
auto query_tokens = phosg::split(query, '&');
|
||||||
for (auto& token : query_tokens) {
|
for (auto& token : query_tokens) {
|
||||||
size_t equals_pos = token.find('=');
|
size_t equals_pos = token.find('=');
|
||||||
if (equals_pos == string::npos) {
|
if (equals_pos == std::string::npos) {
|
||||||
url_decode_inplace(token);
|
url_decode_inplace(token);
|
||||||
req.query_params.emplace(std::move(token), "");
|
req.query_params.emplace(std::move(token), "");
|
||||||
} else {
|
} else {
|
||||||
string key = token.substr(0, equals_pos);
|
std::string key = token.substr(0, equals_pos);
|
||||||
string value = token.substr(equals_pos + 1);
|
std::string value = token.substr(equals_pos + 1);
|
||||||
url_decode_inplace(key);
|
url_decode_inplace(key);
|
||||||
url_decode_inplace(value);
|
url_decode_inplace(value);
|
||||||
req.query_params.emplace(std::move(key), std::move(value));
|
req.query_params.emplace(std::move(key), std::move(value));
|
||||||
@@ -217,11 +215,11 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size_t colon_pos = line.find(':');
|
size_t colon_pos = line.find(':');
|
||||||
if (colon_pos == string::npos) {
|
if (colon_pos == std::string::npos) {
|
||||||
throw runtime_error("malformed header line");
|
throw std::runtime_error("malformed header line");
|
||||||
}
|
}
|
||||||
string key = line.substr(0, colon_pos);
|
std::string key = line.substr(0, colon_pos);
|
||||||
string value = line.substr(colon_pos + 1);
|
std::string value = line.substr(colon_pos + 1);
|
||||||
phosg::strip_whitespace(key);
|
phosg::strip_whitespace(key);
|
||||||
phosg::strip_whitespace(value);
|
phosg::strip_whitespace(value);
|
||||||
prev_header_it = req.headers.emplace(phosg::tolower(key), std::move(value));
|
prev_header_it = req.headers.emplace(phosg::tolower(key), std::move(value));
|
||||||
@@ -230,7 +228,7 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
|||||||
|
|
||||||
auto transfer_encoding_header = req.get_header("transfer-encoding");
|
auto transfer_encoding_header = req.get_header("transfer-encoding");
|
||||||
if (transfer_encoding_header && phosg::tolower(*transfer_encoding_header) == "chunked") {
|
if (transfer_encoding_header && phosg::tolower(*transfer_encoding_header) == "chunked") {
|
||||||
deque<string> chunks;
|
std::deque<std::string> chunks;
|
||||||
size_t total_data_bytes = 0;
|
size_t total_data_bytes = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto line = co_await this->r.read_line("\r\n", 0x20);
|
auto line = co_await this->r.read_line("\r\n", 0x20);
|
||||||
@@ -306,7 +304,7 @@ asio::awaitable<WebSocketMessage> HTTPClient::recv_websocket_message(size_t max_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payload_size > max_data_size) {
|
if (payload_size > max_data_size) {
|
||||||
throw runtime_error("Incoming WebSocket message exceeds size limit");
|
throw std::runtime_error("Incoming WebSocket message exceeds size limit");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the masking key if present
|
// Read the masking key if present
|
||||||
@@ -380,7 +378,7 @@ asio::awaitable<WebSocketMessage> HTTPClient::recv_websocket_message(size_t max_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw logic_error("failed to receive websocket message");
|
throw std::logic_error("failed to receive websocket message");
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, size_t size, uint8_t opcode) {
|
asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, size_t size, uint8_t opcode) {
|
||||||
@@ -396,7 +394,7 @@ asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, size_
|
|||||||
w.put_u8(size);
|
w.put_u8(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
array<asio::const_buffer, 2> bufs = {asio::const_buffer(w.data(), w.size()), asio::const_buffer(data, size)};
|
std::array<asio::const_buffer, 2> bufs = {asio::const_buffer(w.data(), w.size()), asio::const_buffer(data, size)};
|
||||||
co_await asio::async_write(this->r.get_socket(), bufs, asio::use_awaitable);
|
co_await asio::async_write(this->r.get_socket(), bufs, asio::use_awaitable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-21
@@ -7,15 +7,12 @@
|
|||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
AsyncEvent::AsyncEvent(asio::any_io_executor ex) : executor(ex), is_set(false) {}
|
||||||
|
|
||||||
AsyncEvent::AsyncEvent(asio::any_io_executor ex)
|
|
||||||
: executor(ex), is_set(false) {}
|
|
||||||
|
|
||||||
void AsyncEvent::set() {
|
void AsyncEvent::set() {
|
||||||
std::vector<std::unique_ptr<asio::detail::awaitable_handler<asio::any_io_executor>>> waiters_to_resume;
|
std::vector<std::unique_ptr<asio::detail::awaitable_handler<asio::any_io_executor>>> waiters_to_resume;
|
||||||
{
|
{
|
||||||
lock_guard g(this->lock);
|
std::lock_guard g(this->lock);
|
||||||
this->is_set = true;
|
this->is_set = true;
|
||||||
this->waiters.swap(waiters_to_resume);
|
this->waiters.swap(waiters_to_resume);
|
||||||
}
|
}
|
||||||
@@ -28,7 +25,7 @@ void AsyncEvent::set() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEvent::clear() {
|
void AsyncEvent::clear() {
|
||||||
lock_guard g(this->lock);
|
std::lock_guard g(this->lock);
|
||||||
this->is_set = false;
|
this->is_set = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +33,12 @@ asio::awaitable<void> AsyncEvent::wait() {
|
|||||||
auto token = asio::use_awaitable_t<>{};
|
auto token = asio::use_awaitable_t<>{};
|
||||||
co_await asio::async_initiate<asio::use_awaitable_t<>, void()>(
|
co_await asio::async_initiate<asio::use_awaitable_t<>, void()>(
|
||||||
[this](auto&& handler) -> void {
|
[this](auto&& handler) -> void {
|
||||||
lock_guard g(this->lock);
|
std::lock_guard g(this->lock);
|
||||||
if (this->is_set) {
|
if (this->is_set) {
|
||||||
handler();
|
handler();
|
||||||
} else {
|
} else {
|
||||||
this->waiters.emplace_back(make_unique<asio::detail::awaitable_handler<asio::any_io_executor>>(std::move(handler)));
|
this->waiters.emplace_back(
|
||||||
|
std::make_unique<asio::detail::awaitable_handler<asio::any_io_executor>>(std::move(handler)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
token);
|
token);
|
||||||
@@ -49,17 +47,17 @@ asio::awaitable<void> AsyncEvent::wait() {
|
|||||||
AsyncSocketReader::AsyncSocketReader(asio::ip::tcp::socket&& sock)
|
AsyncSocketReader::AsyncSocketReader(asio::ip::tcp::socket&& sock)
|
||||||
: sock(std::move(sock)) {}
|
: sock(std::move(sock)) {}
|
||||||
|
|
||||||
asio::awaitable<string> AsyncSocketReader::read_line(const char* delimiter, size_t max_length) {
|
asio::awaitable<std::string> AsyncSocketReader::read_line(const char* delimiter, size_t max_length) {
|
||||||
size_t delimiter_size = strlen(delimiter);
|
size_t delimiter_size = strlen(delimiter);
|
||||||
if (delimiter_size == 0) {
|
if (delimiter_size == 0) {
|
||||||
throw logic_error("delimiter is empty");
|
throw std::logic_error("delimiter is empty");
|
||||||
}
|
}
|
||||||
size_t delimiter_backup_bytes = delimiter_size - 1;
|
size_t delimiter_backup_bytes = delimiter_size - 1;
|
||||||
|
|
||||||
size_t delimiter_pos = this->pending_data.find(delimiter);
|
size_t delimiter_pos = this->pending_data.find(delimiter);
|
||||||
while ((delimiter_pos == string::npos) && (!max_length || (this->pending_data.size() < max_length))) {
|
while ((delimiter_pos == std::string::npos) && (!max_length || (this->pending_data.size() < max_length))) {
|
||||||
size_t pre_size = this->pending_data.size();
|
size_t pre_size = this->pending_data.size();
|
||||||
this->pending_data.resize(min(max_length, this->pending_data.size() + 0x400));
|
this->pending_data.resize(std::min(max_length, this->pending_data.size() + 0x400));
|
||||||
|
|
||||||
auto buf = asio::buffer(this->pending_data.data() + pre_size, this->pending_data.size() - pre_size);
|
auto buf = asio::buffer(this->pending_data.data() + pre_size, this->pending_data.size() - pre_size);
|
||||||
size_t bytes_read = co_await this->sock.async_read_some(buf, asio::use_awaitable);
|
size_t bytes_read = co_await this->sock.async_read_some(buf, asio::use_awaitable);
|
||||||
@@ -69,18 +67,18 @@ asio::awaitable<string> AsyncSocketReader::read_line(const char* delimiter, size
|
|||||||
(delimiter_backup_bytes > pre_size) ? 0 : (pre_size - delimiter_backup_bytes));
|
(delimiter_backup_bytes > pre_size) ? 0 : (pre_size - delimiter_backup_bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delimiter_pos == string::npos) {
|
if (delimiter_pos == std::string::npos) {
|
||||||
throw runtime_error("line exceeds max length");
|
throw std::runtime_error("line exceeds max length");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: It's not great that we copy the data here. There's probably a more idiomatic and efficient way to do this.
|
// TODO: It's not great that we copy the data here. There's probably a more idiomatic and efficient way to do this.
|
||||||
string ret = this->pending_data.substr(0, delimiter_pos);
|
std::string ret = this->pending_data.substr(0, delimiter_pos);
|
||||||
this->pending_data = this->pending_data.substr(delimiter_pos + delimiter_size);
|
this->pending_data = this->pending_data.substr(delimiter_pos + delimiter_size);
|
||||||
co_return ret;
|
co_return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<string> AsyncSocketReader::read_data(size_t size) {
|
asio::awaitable<std::string> AsyncSocketReader::read_data(size_t size) {
|
||||||
string ret;
|
std::string ret;
|
||||||
if (this->pending_data.size() == size) {
|
if (this->pending_data.size() == size) {
|
||||||
this->pending_data.swap(ret);
|
this->pending_data.swap(ret);
|
||||||
} else if (this->pending_data.size() > size) {
|
} else if (this->pending_data.size() > size) {
|
||||||
@@ -111,7 +109,7 @@ asio::awaitable<void> AsyncSocketReader::read_data_into(void* data, size_t size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWriteCollector::add(string&& data) {
|
void AsyncWriteCollector::add(std::string&& data) {
|
||||||
const auto& item = this->owned_data.emplace_back(std::move(data));
|
const auto& item = this->owned_data.emplace_back(std::move(data));
|
||||||
bufs.emplace_back(asio::buffer(item.data(), item.size()));
|
bufs.emplace_back(asio::buffer(item.data(), item.size()));
|
||||||
}
|
}
|
||||||
@@ -121,14 +119,14 @@ void AsyncWriteCollector::add_reference(const void* data, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> AsyncWriteCollector::write(asio::ip::tcp::socket& sock) {
|
asio::awaitable<void> AsyncWriteCollector::write(asio::ip::tcp::socket& sock) {
|
||||||
deque<string> local_owned_data;
|
std::deque<std::string> local_owned_data;
|
||||||
local_owned_data.swap(this->owned_data);
|
local_owned_data.swap(this->owned_data);
|
||||||
vector<asio::const_buffer> local_bufs;
|
std::vector<asio::const_buffer> local_bufs;
|
||||||
local_bufs.swap(this->bufs);
|
local_bufs.swap(this->bufs);
|
||||||
co_await asio::async_write(sock, local_bufs, asio::use_awaitable);
|
co_await asio::async_write(sock, local_bufs, asio::use_awaitable);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> async_sleep(chrono::steady_clock::duration duration) {
|
asio::awaitable<void> async_sleep(std::chrono::steady_clock::duration duration) {
|
||||||
asio::steady_timer timer(co_await asio::this_coro::executor, duration);
|
asio::steady_timer timer(co_await asio::this_coro::executor, duration);
|
||||||
co_await timer.async_wait(asio::use_awaitable);
|
co_await timer.async_wait(asio::use_awaitable);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -259,7 +259,7 @@ asio::awaitable<std::invoke_result_t<FnT, ArgTs...>> call_on_thread_pool(asio::t
|
|||||||
using ReturnT = std::invoke_result_t<FnT, ArgTs...>;
|
using ReturnT = std::invoke_result_t<FnT, ArgTs...>;
|
||||||
auto bound = std::bind(std::forward<FnT>(f), std::forward<ArgTs>(args)...);
|
auto bound = std::bind(std::forward<FnT>(f), std::forward<ArgTs>(args)...);
|
||||||
|
|
||||||
// We have to use a shared_ptr here in case call_on_thread_pool is canceled (in that case, the posted callback will
|
// We have to use a std::shared_ptr here in case call_on_thread_pool is canceled (in that case, the posted callback will
|
||||||
// try to use promise after the call_on_thread_pool coroutine has been destroyed)
|
// try to use promise after the call_on_thread_pool coroutine has been destroyed)
|
||||||
auto promise = std::make_shared<AsyncPromise<ReturnT>>();
|
auto promise = std::make_shared<AsyncPromise<ReturnT>>();
|
||||||
asio::post(pool, [bound = std::move(bound), promise]() mutable {
|
asio::post(pool, [bound = std::move(bound), promise]() mutable {
|
||||||
|
|||||||
+18
-21
@@ -7,8 +7,6 @@
|
|||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
#include "Types.hh"
|
#include "Types.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
struct BMLHeaderT {
|
struct BMLHeaderT {
|
||||||
parray<uint8_t, 0x04> unknown_a1;
|
parray<uint8_t, 0x04> unknown_a1;
|
||||||
@@ -42,13 +40,13 @@ void BMLArchive::load_t() {
|
|||||||
const auto& entry = r.get<BMLHeaderEntryT<BE>>();
|
const auto& entry = r.get<BMLHeaderEntryT<BE>>();
|
||||||
|
|
||||||
if (offset + entry.compressed_size > this->data->size()) {
|
if (offset + entry.compressed_size > this->data->size()) {
|
||||||
throw runtime_error("BML data entry extends beyond end of data");
|
throw std::runtime_error("BML data entry extends beyond end of data");
|
||||||
}
|
}
|
||||||
size_t data_offset = offset;
|
size_t data_offset = offset;
|
||||||
offset = (offset + entry.compressed_size + 0x1F) & (~0x1F);
|
offset = (offset + entry.compressed_size + 0x1F) & (~0x1F);
|
||||||
|
|
||||||
if (offset + entry.compressed_gvm_size > this->data->size()) {
|
if (offset + entry.compressed_gvm_size > this->data->size()) {
|
||||||
throw runtime_error("BML GVM entry extends beyond end of data");
|
throw std::runtime_error("BML GVM entry extends beyond end of data");
|
||||||
}
|
}
|
||||||
size_t gvm_offset = offset;
|
size_t gvm_offset = offset;
|
||||||
offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F);
|
offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F);
|
||||||
@@ -57,8 +55,7 @@ void BMLArchive::load_t() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BMLArchive::BMLArchive(shared_ptr<const string> data, bool big_endian)
|
BMLArchive::BMLArchive(std::shared_ptr<const std::string> data, bool big_endian) : data(data) {
|
||||||
: data(data) {
|
|
||||||
if (big_endian) {
|
if (big_endian) {
|
||||||
this->load_t<true>();
|
this->load_t<true>();
|
||||||
} else {
|
} else {
|
||||||
@@ -66,42 +63,42 @@ BMLArchive::BMLArchive(shared_ptr<const string> data, bool big_endian)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unordered_map<string, BMLArchive::Entry> BMLArchive::all_entries() const {
|
const std::unordered_map<std::string, BMLArchive::Entry> BMLArchive::all_entries() const {
|
||||||
return this->entries;
|
return this->entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const void*, size_t> BMLArchive::get(const std::string& name) const {
|
std::pair<const void*, size_t> BMLArchive::get(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
return std::make_pair(this->data->data() + entry.offset, entry.size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("BML does not contain file: " + name);
|
throw std::out_of_range("BML does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const void*, size_t> BMLArchive::get_gvm(const std::string& name) const {
|
std::pair<const void*, size_t> BMLArchive::get_gvm(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
|
return std::make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("BML does not contain file: " + name);
|
throw std::out_of_range("BML does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string BMLArchive::get_copy(const string& name) const {
|
std::string BMLArchive::get_copy(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return this->data->substr(entry.offset, entry.size);
|
return this->data->substr(entry.offset, entry.size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("BML does not contain file: " + name);
|
throw std::out_of_range("BML does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader BMLArchive::get_reader(const string& name) const {
|
phosg::StringReader BMLArchive::get_reader(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("BML does not contain file: " + name);
|
throw std::out_of_range("BML does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-12
@@ -7,8 +7,6 @@
|
|||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
BattleParamsIndex::AttackData BattleParamsIndex::AttackData::from_json(const phosg::JSON& json) {
|
BattleParamsIndex::AttackData BattleParamsIndex::AttackData::from_json(const phosg::JSON& json) {
|
||||||
return AttackData{
|
return AttackData{
|
||||||
json.get_int("MinATP"),
|
json.get_int("MinATP"),
|
||||||
@@ -202,7 +200,7 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
|||||||
for (size_t z = 0; z < 0x60; z++) {
|
for (size_t z = 0; z < 0x60; z++) {
|
||||||
const auto& e = this->stats[static_cast<size_t>(difficulty)][z];
|
const auto& e = this->stats[static_cast<size_t>(difficulty)][z];
|
||||||
phosg::fwrite_fmt(stream, " {:02X} ", z);
|
phosg::fwrite_fmt(stream, " {:02X} ", z);
|
||||||
string names_str;
|
std::string names_str;
|
||||||
for (auto type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
for (auto type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
||||||
if (!names_str.empty()) {
|
if (!names_str.empty()) {
|
||||||
names_str += ", ";
|
names_str += ", ";
|
||||||
@@ -290,17 +288,17 @@ const BattleParamsIndex::Table& JSONBattleParamsIndex::get_table(bool solo, Epis
|
|||||||
case Episode::EP4:
|
case Episode::EP4:
|
||||||
return this->tables[!!solo][2];
|
return this->tables[!!solo][2];
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid episode");
|
throw std::invalid_argument("invalid episode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
||||||
shared_ptr<const string> data_on_ep1,
|
std::shared_ptr<const std::string> data_on_ep1,
|
||||||
shared_ptr<const string> data_on_ep2,
|
std::shared_ptr<const std::string> data_on_ep2,
|
||||||
shared_ptr<const string> data_on_ep4,
|
std::shared_ptr<const std::string> data_on_ep4,
|
||||||
shared_ptr<const string> data_off_ep1,
|
std::shared_ptr<const std::string> data_off_ep1,
|
||||||
shared_ptr<const string> data_off_ep2,
|
std::shared_ptr<const std::string> data_off_ep2,
|
||||||
shared_ptr<const string> data_off_ep4) {
|
std::shared_ptr<const std::string> data_off_ep4) {
|
||||||
this->files[0][0].data = data_on_ep1;
|
this->files[0][0].data = data_on_ep1;
|
||||||
this->files[0][1].data = data_on_ep2;
|
this->files[0][1].data = data_on_ep2;
|
||||||
this->files[0][2].data = data_on_ep4;
|
this->files[0][2].data = data_on_ep4;
|
||||||
@@ -312,7 +310,7 @@ BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
|||||||
for (uint8_t episode = 0; episode < 3; episode++) {
|
for (uint8_t episode = 0; episode < 3; episode++) {
|
||||||
auto& file = this->files[is_solo][episode];
|
auto& file = this->files[is_solo][episode];
|
||||||
if (file.data->size() < sizeof(Table)) {
|
if (file.data->size() < sizeof(Table)) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"battle params table size is incorrect (expected {:X} bytes, have {:X} bytes; is_solo={}, episode={})",
|
"battle params table size is incorrect (expected {:X} bytes, have {:X} bytes; is_solo={}, episode={})",
|
||||||
sizeof(Table), file.data->size(), is_solo, episode));
|
sizeof(Table), file.data->size(), is_solo, episode));
|
||||||
}
|
}
|
||||||
@@ -330,6 +328,6 @@ const BattleParamsIndex::Table& BinaryBattleParamsIndex::get_table(bool solo, Ep
|
|||||||
case Episode::EP4:
|
case Episode::EP4:
|
||||||
return *this->files[!!solo][2].table;
|
return *this->files[!!solo][2].table;
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid episode");
|
throw std::invalid_argument("invalid episode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-22
@@ -12,14 +12,12 @@
|
|||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
extern bool use_terminal_colors;
|
extern bool use_terminal_colors;
|
||||||
|
|
||||||
Channel::Channel(
|
Channel::Channel(
|
||||||
Version version,
|
Version version,
|
||||||
Language language,
|
Language language,
|
||||||
const string& name,
|
const std::string& name,
|
||||||
phosg::TerminalFormat terminal_send_color,
|
phosg::TerminalFormat terminal_send_color,
|
||||||
phosg::TerminalFormat terminal_recv_color,
|
phosg::TerminalFormat terminal_recv_color,
|
||||||
bool censor_received_credentials,
|
bool censor_received_credentials,
|
||||||
@@ -49,7 +47,7 @@ void Channel::send(
|
|||||||
size += b.second;
|
size += b.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
string send_data;
|
std::string send_data;
|
||||||
size_t logical_size;
|
size_t logical_size;
|
||||||
size_t send_data_size = 0;
|
size_t send_data_size = 0;
|
||||||
switch (this->version) {
|
switch (this->version) {
|
||||||
@@ -113,12 +111,12 @@ void Channel::send(
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw logic_error("unimplemented game version in send_command");
|
throw std::logic_error("unimplemented game version in send_command");
|
||||||
}
|
}
|
||||||
|
|
||||||
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00 bytes in size
|
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00 bytes in size
|
||||||
if (send_data_size > 0x7C00) {
|
if (send_data_size > 0x7C00) {
|
||||||
throw runtime_error("outbound command too large");
|
throw std::runtime_error("outbound command too large");
|
||||||
}
|
}
|
||||||
|
|
||||||
send_data.reserve(send_data_size);
|
send_data.reserve(send_data_size);
|
||||||
@@ -164,10 +162,10 @@ void Channel::send(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
|
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
|
||||||
this->send(cmd, flag, {make_pair(data, size)}, silent);
|
this->send(cmd, flag, {std::make_pair(data, size)}, silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
|
void Channel::send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent) {
|
||||||
this->send(cmd, flag, data.data(), data.size(), silent);
|
this->send(cmd, flag, data.data(), data.size(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +180,7 @@ void Channel::send(const void* data, size_t size, bool silent) {
|
|||||||
silent);
|
silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Channel::send(const string& data, bool silent) {
|
void Channel::send(const std::string& data, bool silent) {
|
||||||
this->send(data.data(), data.size(), silent);
|
this->send(data.data(), data.size(), silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +194,7 @@ asio::awaitable<Channel::Message> Channel::recv() {
|
|||||||
|
|
||||||
size_t command_logical_size = header.size(version);
|
size_t command_logical_size = header.size(version);
|
||||||
if (command_logical_size < header_size) {
|
if (command_logical_size < header_size) {
|
||||||
throw runtime_error("header size field is smaller than header");
|
throw std::runtime_error("header size field is smaller than header");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this is not reflected in the size field. This
|
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this is not reflected in the size field. This
|
||||||
@@ -205,7 +203,7 @@ asio::awaitable<Channel::Message> Channel::recv() {
|
|||||||
? ((command_logical_size + 7) & ~7)
|
? ((command_logical_size + 7) & ~7)
|
||||||
: command_logical_size;
|
: command_logical_size;
|
||||||
|
|
||||||
string command_data(command_physical_size - header_size, '\0');
|
std::string command_data(command_physical_size - header_size, '\0');
|
||||||
co_await this->recv_raw(command_data.data(), command_data.size());
|
co_await this->recv_raw(command_data.data(), command_data.size());
|
||||||
|
|
||||||
if (this->crypt_in.get()) {
|
if (this->crypt_in.get()) {
|
||||||
@@ -267,17 +265,17 @@ asio::awaitable<Channel::Message> Channel::recv() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<SocketChannel> SocketChannel::create(
|
std::shared_ptr<SocketChannel> SocketChannel::create(
|
||||||
std::shared_ptr<asio::io_context> io_context,
|
std::shared_ptr<asio::io_context> io_context,
|
||||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||||
Version version,
|
Version version,
|
||||||
Language language,
|
Language language,
|
||||||
const string& name,
|
const std::string& name,
|
||||||
phosg::TerminalFormat terminal_send_color,
|
phosg::TerminalFormat terminal_send_color,
|
||||||
phosg::TerminalFormat terminal_recv_color,
|
phosg::TerminalFormat terminal_recv_color,
|
||||||
bool censor_received_credentials,
|
bool censor_received_credentials,
|
||||||
bool censor_sent_credentials) {
|
bool censor_sent_credentials) {
|
||||||
shared_ptr<SocketChannel> ret(new SocketChannel(
|
std::shared_ptr<SocketChannel> ret(new SocketChannel(
|
||||||
io_context,
|
io_context,
|
||||||
std::move(sock),
|
std::move(sock),
|
||||||
version,
|
version,
|
||||||
@@ -296,7 +294,7 @@ SocketChannel::SocketChannel(
|
|||||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||||
Version version,
|
Version version,
|
||||||
Language language,
|
Language language,
|
||||||
const string& name,
|
const std::string& name,
|
||||||
phosg::TerminalFormat terminal_send_color,
|
phosg::TerminalFormat terminal_send_color,
|
||||||
phosg::TerminalFormat terminal_recv_color,
|
phosg::TerminalFormat terminal_recv_color,
|
||||||
bool censor_received_credentials,
|
bool censor_received_credentials,
|
||||||
@@ -320,7 +318,7 @@ void SocketChannel::disconnect() {
|
|||||||
this->send_buffer_nonempty_signal.set();
|
this->send_buffer_nonempty_signal.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SocketChannel::send_raw(string&& data) {
|
void SocketChannel::send_raw(std::string&& data) {
|
||||||
if (this->sock && !this->should_disconnect) {
|
if (this->sock && !this->should_disconnect) {
|
||||||
this->outbound_data.emplace_back(std::move(data));
|
this->outbound_data.emplace_back(std::move(data));
|
||||||
this->send_buffer_nonempty_signal.set();
|
this->send_buffer_nonempty_signal.set();
|
||||||
@@ -329,7 +327,7 @@ void SocketChannel::send_raw(string&& data) {
|
|||||||
|
|
||||||
asio::awaitable<void> SocketChannel::recv_raw(void* data, size_t size) {
|
asio::awaitable<void> SocketChannel::recv_raw(void* data, size_t size) {
|
||||||
if (!this->sock || this->should_disconnect) {
|
if (!this->sock || this->should_disconnect) {
|
||||||
throw runtime_error("Cannot receive on closed channel");
|
throw std::runtime_error("Cannot receive on closed channel");
|
||||||
}
|
}
|
||||||
co_await asio::async_read(*this->sock, asio::buffer(data, size), asio::use_awaitable);
|
co_await asio::async_read(*this->sock, asio::buffer(data, size), asio::use_awaitable);
|
||||||
}
|
}
|
||||||
@@ -339,11 +337,11 @@ asio::awaitable<void> SocketChannel::send_task() {
|
|||||||
auto this_sh = this->shared_from_this();
|
auto this_sh = this->shared_from_this();
|
||||||
|
|
||||||
while (this->sock->is_open()) {
|
while (this->sock->is_open()) {
|
||||||
deque<string> to_send;
|
std::deque<std::string> to_send;
|
||||||
to_send.swap(this->outbound_data);
|
to_send.swap(this->outbound_data);
|
||||||
|
|
||||||
if (!to_send.empty()) {
|
if (!to_send.empty()) {
|
||||||
vector<asio::const_buffer> bufs;
|
std::vector<asio::const_buffer> bufs;
|
||||||
bufs.reserve(to_send.size());
|
bufs.reserve(to_send.size());
|
||||||
for (const auto& it : to_send) {
|
for (const auto& it : to_send) {
|
||||||
bufs.emplace_back(asio::buffer(it.data(), it.size()));
|
bufs.emplace_back(asio::buffer(it.data(), it.size()));
|
||||||
@@ -376,7 +374,7 @@ PeerChannel::PeerChannel(
|
|||||||
|
|
||||||
void PeerChannel::link_peers(std::shared_ptr<PeerChannel> peer1, std::shared_ptr<PeerChannel> peer2) {
|
void PeerChannel::link_peers(std::shared_ptr<PeerChannel> peer1, std::shared_ptr<PeerChannel> peer2) {
|
||||||
if (peer1->connected() || peer2->connected()) {
|
if (peer1->connected() || peer2->connected()) {
|
||||||
throw logic_error("Cannot link already-connected peer channels");
|
throw std::logic_error("Cannot link already-connected peer channels");
|
||||||
}
|
}
|
||||||
peer1->peer = peer2;
|
peer1->peer = peer2;
|
||||||
peer2->peer = peer1;
|
peer2->peer = peer1;
|
||||||
@@ -400,7 +398,7 @@ void PeerChannel::disconnect() {
|
|||||||
this->send_buffer_nonempty_signal.set();
|
this->send_buffer_nonempty_signal.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerChannel::send_raw(string&& data) {
|
void PeerChannel::send_raw(std::string&& data) {
|
||||||
auto peer = this->peer.lock();
|
auto peer = this->peer.lock();
|
||||||
if (peer) {
|
if (peer) {
|
||||||
peer->inbound_data.emplace_back(std::move(data));
|
peer->inbound_data.emplace_back(std::move(data));
|
||||||
@@ -428,7 +426,7 @@ asio::awaitable<void> PeerChannel::recv_raw(void* data, size_t size) {
|
|||||||
this->inbound_data.pop_front();
|
this->inbound_data.pop_front();
|
||||||
}
|
}
|
||||||
} else if (!this->peer.lock()) {
|
} else if (!this->peer.lock()) {
|
||||||
throw runtime_error("Channel peer has disconnected");
|
throw std::runtime_error("Channel peer has disconnected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+164
-174
File diff suppressed because it is too large
Load Diff
+9
-11
@@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
#include "Client.hh"
|
#include "Client.hh"
|
||||||
|
|
||||||
using namespace std;
|
const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||||
|
|
||||||
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|
||||||
ChoiceSearchCategory{
|
ChoiceSearchCategory{
|
||||||
.id = 0x0001,
|
.id = 0x0001,
|
||||||
.name = "Level",
|
.name = "Level",
|
||||||
@@ -24,7 +22,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
{0x0009, "Level 121-160"},
|
{0x0009, "Level 121-160"},
|
||||||
{0x000A, "Level 161-200"},
|
{0x000A, "Level 161-200"},
|
||||||
},
|
},
|
||||||
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
.client_matches = +[](std::shared_ptr<Client> searcher_c, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||||
if (choice_id == 0x0000) {
|
if (choice_id == 0x0000) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -75,18 +73,18 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
{0x0008, "FOnewm"},
|
{0x0008, "FOnewm"},
|
||||||
{0x0009, "FOnewearl"},
|
{0x0009, "FOnewearl"},
|
||||||
},
|
},
|
||||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
.client_matches = +[](std::shared_ptr<Client>, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||||
switch (choice_id) {
|
switch (choice_id) {
|
||||||
case 0x0000:
|
case 0x0000:
|
||||||
return true;
|
return true;
|
||||||
case 0x0010:
|
case 0x0010:
|
||||||
return target_c->character_file()->disp.visual.class_flags & 0x20;
|
return target_c->character_file()->disp.visual.sh.class_flags & 0x20;
|
||||||
case 0x0011:
|
case 0x0011:
|
||||||
return target_c->character_file()->disp.visual.class_flags & 0x40;
|
return target_c->character_file()->disp.visual.sh.class_flags & 0x40;
|
||||||
case 0x0012:
|
case 0x0012:
|
||||||
return target_c->character_file()->disp.visual.class_flags & 0x80;
|
return target_c->character_file()->disp.visual.sh.class_flags & 0x80;
|
||||||
default:
|
default:
|
||||||
return ((choice_id - 1) == target_c->character_file()->disp.visual.char_class);
|
return ((choice_id - 1) == target_c->character_file()->disp.visual.sh.char_class);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -102,7 +100,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
{0x0005, "GC Episode 3"},
|
{0x0005, "GC Episode 3"},
|
||||||
{0x0006, "BB"},
|
{0x0006, "BB"},
|
||||||
},
|
},
|
||||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
.client_matches = +[](std::shared_ptr<Client>, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||||
if (choice_id == 0x0000) {
|
if (choice_id == 0x0000) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -142,7 +140,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
{0x0005, "Battle"},
|
{0x0005, "Battle"},
|
||||||
{0x0006, "Challenge"},
|
{0x0006, "Challenge"},
|
||||||
},
|
},
|
||||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
.client_matches = +[](std::shared_ptr<Client>, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||||
uint16_t target_choice_id = target_c->character_file()->choice_search_config.get_setting(0x0204);
|
uint16_t target_choice_id = target_c->character_file()->choice_search_config.get_setting(0x0204);
|
||||||
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
||||||
},
|
},
|
||||||
|
|||||||
+129
-134
@@ -16,11 +16,9 @@
|
|||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32;
|
const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32;
|
||||||
|
|
||||||
static atomic<uint64_t> next_id(1);
|
static std::atomic<uint64_t> next_client_id(1);
|
||||||
|
|
||||||
void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
||||||
this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
||||||
@@ -82,7 +80,7 @@ void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
|||||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid game version");
|
throw std::logic_error("invalid game version");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -151,7 +149,7 @@ void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
|||||||
this->set_flag(Flag::CAN_RECEIVE_ENABLE_B2_QUEST);
|
this->set_flag(Flag::CAN_RECEIVE_ENABLE_B2_QUEST);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error(std::format("unknown sub_version {:X}", sub_version));
|
throw std::runtime_error(std::format("unknown sub_version {:X}", sub_version));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +174,11 @@ void Client::set_drop_notification_mode(ItemDropNotificationMode new_mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Client::Client(
|
Client::Client(
|
||||||
shared_ptr<GameServer> server,
|
std::shared_ptr<GameServer> server,
|
||||||
shared_ptr<Channel> channel,
|
std::shared_ptr<Channel> channel,
|
||||||
ServerBehavior server_behavior)
|
ServerBehavior server_behavior)
|
||||||
: server(server),
|
: server(server),
|
||||||
id(next_id++),
|
id(next_client_id++),
|
||||||
log(std::format("[C-{:X}] ", this->id), client_log.min_level),
|
log(std::format("[C-{:X}] ", this->id), client_log.min_level),
|
||||||
channel(channel),
|
channel(channel),
|
||||||
server_behavior(server_behavior),
|
server_behavior(server_behavior),
|
||||||
@@ -239,13 +237,12 @@ Client::~Client() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Client::update_channel_name() {
|
void Client::update_channel_name() {
|
||||||
string default_name = this->channel->default_name();
|
std::string default_name = this->channel->default_name();
|
||||||
|
|
||||||
auto player = this->character_file(false, false);
|
auto player = this->character_file(false, false);
|
||||||
if (player) {
|
if (player) {
|
||||||
string name_str = player->disp.name.decode(this->language());
|
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}",
|
||||||
size_t level = player->disp.stats.level + 1;
|
this->id, player->disp.visual.name.decode(this->language()), player->disp.stats.level + 1, default_name);
|
||||||
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", this->id, name_str, level, default_name);
|
|
||||||
} else {
|
} else {
|
||||||
this->channel->name = std::format("C-{:X} @ {}", this->id, default_name);
|
this->channel->name = std::format("C-{:X} @ {}", this->id, default_name);
|
||||||
}
|
}
|
||||||
@@ -278,7 +275,7 @@ void Client::reschedule_ping_and_timeout_timers() {
|
|||||||
// The game doesn't use this timestamp; we only use it for debugging purposes
|
// The game doesn't use this timestamp; we only use it for debugging purposes
|
||||||
be_uint64_t timestamp = phosg::now();
|
be_uint64_t timestamp = phosg::now();
|
||||||
this->channel->send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t));
|
this->channel->send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t));
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.warning_f("Failed to send ping: {}", e.what());
|
this->log.warning_f("Failed to send ping: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,25 +303,25 @@ void Client::convert_account_to_temporary_if_nte() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<ServerState> Client::require_server_state() const {
|
std::shared_ptr<ServerState> Client::require_server_state() const {
|
||||||
auto server = this->server.lock();
|
auto server = this->server.lock();
|
||||||
if (!server) {
|
if (!server) {
|
||||||
throw logic_error("server is deleted");
|
throw std::logic_error("server is deleted");
|
||||||
}
|
}
|
||||||
return server->get_state();
|
return server->get_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Lobby> Client::require_lobby() const {
|
std::shared_ptr<Lobby> Client::require_lobby() const {
|
||||||
auto l = this->lobby.lock();
|
auto l = this->lobby.lock();
|
||||||
if (!l) {
|
if (!l) {
|
||||||
throw runtime_error("client not in any lobby");
|
throw std::runtime_error("client not in any lobby");
|
||||||
}
|
}
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const TeamIndex::Team> Client::team() const {
|
std::shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||||
if (!this->login) {
|
if (!this->login) {
|
||||||
throw logic_error("Client::team called on client with no account");
|
throw std::logic_error("Client::team called on client with no account");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->login->account->bb_team_id == 0) {
|
if (this->login->account->bb_team_id == 0) {
|
||||||
@@ -352,7 +349,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
|||||||
// The team membership is valid, but the player name may be different; update the team membership if needed
|
// The team membership is valid, but the player name may be different; update the team membership if needed
|
||||||
if (p) {
|
if (p) {
|
||||||
auto& m = member_it->second;
|
auto& m = member_it->second;
|
||||||
string name = p->disp.name.decode(this->language());
|
std::string name = p->disp.visual.name.decode(this->language());
|
||||||
if (m.name != name) {
|
if (m.name != name) {
|
||||||
this->log.info_f("Updating player name in team config");
|
this->log.info_f("Updating player name in team config");
|
||||||
s->team_index->update_member_name(this->login->account->account_id, name);
|
s->team_index->update_member_name(this->login->account->account_id, name);
|
||||||
@@ -363,8 +360,8 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Client::evaluate_quest_availability_expression(
|
bool Client::evaluate_quest_availability_expression(
|
||||||
shared_ptr<const IntegralExpression> expr,
|
std::shared_ptr<const IntegralExpression> expr,
|
||||||
shared_ptr<const Lobby> game,
|
std::shared_ptr<const Lobby> game,
|
||||||
uint8_t event,
|
uint8_t event,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
size_t num_players,
|
size_t num_players,
|
||||||
@@ -376,7 +373,7 @@ bool Client::evaluate_quest_availability_expression(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (game && !game->quest_flag_values) {
|
if (game && !game->quest_flag_values) {
|
||||||
throw logic_error("quest flags are missing from game");
|
throw std::logic_error("quest flags are missing from game");
|
||||||
}
|
}
|
||||||
auto p = this->character_file();
|
auto p = this->character_file();
|
||||||
IntegralExpression::Env env = {
|
IntegralExpression::Env env = {
|
||||||
@@ -389,15 +386,14 @@ bool Client::evaluate_quest_availability_expression(
|
|||||||
};
|
};
|
||||||
int64_t ret = expr->evaluate(env);
|
int64_t ret = expr->evaluate(env);
|
||||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||||
string expr_str = expr->str();
|
this->log.info_f("Evaluated integral expression {} => {}", expr->str(), ret ? "TRUE" : "FALSE");
|
||||||
this->log.info_f("Evaluated integral expression {} => {}", expr_str, ret ? "TRUE" : "FALSE");
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Client::can_see_quest(
|
bool Client::can_see_quest(
|
||||||
shared_ptr<const Quest> q,
|
std::shared_ptr<const Quest> q,
|
||||||
shared_ptr<const Lobby> game,
|
std::shared_ptr<const Lobby> game,
|
||||||
uint8_t event,
|
uint8_t event,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
size_t num_players,
|
size_t num_players,
|
||||||
@@ -417,8 +413,8 @@ bool Client::can_see_quest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Client::can_play_quest(
|
bool Client::can_play_quest(
|
||||||
shared_ptr<const Quest> q,
|
std::shared_ptr<const Quest> q,
|
||||||
shared_ptr<const Lobby> game,
|
std::shared_ptr<const Lobby> game,
|
||||||
uint8_t event,
|
uint8_t event,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
size_t num_players,
|
size_t num_players,
|
||||||
@@ -450,7 +446,7 @@ bool Client::can_use_chat_commands() const {
|
|||||||
return this->require_server_state()->enable_chat_commands;
|
return this->require_server_state()->enable_chat_commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::set_login(shared_ptr<Login> login) {
|
void Client::set_login(std::shared_ptr<Login> login) {
|
||||||
this->login = login;
|
this->login = login;
|
||||||
|
|
||||||
auto s = this->require_server_state();
|
auto s = this->require_server_state();
|
||||||
@@ -472,116 +468,116 @@ void Client::set_login(shared_ptr<Login> login) {
|
|||||||
|
|
||||||
// System file
|
// System file
|
||||||
|
|
||||||
string Client::system_filename(const string& bb_username) {
|
std::string Client::system_filename(const std::string& bb_username) {
|
||||||
return std::format("system/players/system_{}.psosys", bb_username);
|
return std::format("system/players/system_{}.psosys", bb_username);
|
||||||
}
|
}
|
||||||
|
|
||||||
string Client::system_filename() const {
|
std::string Client::system_filename() const {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have system data");
|
throw std::logic_error("non-BB players do not have system data");
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
return this->system_filename(this->login->bb_license->username);
|
return this->system_filename(this->login->bb_license->username);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBBaseSystemFile> Client::system_file(bool allow_load) {
|
std::shared_ptr<PSOBBBaseSystemFile> Client::system_file(bool allow_load) {
|
||||||
if (!this->system_data && allow_load) {
|
if (!this->system_data && allow_load) {
|
||||||
this->load_all_files();
|
this->load_all_files();
|
||||||
}
|
}
|
||||||
return this->system_data;
|
return this->system_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const PSOBBBaseSystemFile> Client::system_file(bool throw_if_missing) const {
|
std::shared_ptr<const PSOBBBaseSystemFile> Client::system_file(bool throw_if_missing) const {
|
||||||
if (!this->system_data.get() && throw_if_missing) {
|
if (!this->system_data.get() && throw_if_missing) {
|
||||||
throw runtime_error("system file is not loaded");
|
throw std::runtime_error("system file is not loaded");
|
||||||
}
|
}
|
||||||
return this->system_data;
|
return this->system_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_system_file() const {
|
void Client::save_system_file() const {
|
||||||
if (!this->system_data) {
|
if (!this->system_data) {
|
||||||
throw logic_error("no system file loaded");
|
throw std::logic_error("no system file loaded");
|
||||||
}
|
}
|
||||||
string filename = this->system_filename();
|
std::string filename = this->system_filename();
|
||||||
phosg::save_object_file(filename, *this->system_data);
|
phosg::save_object_file(filename, *this->system_data);
|
||||||
this->log.info_f("Saved system file {}", filename);
|
this->log.info_f("Saved system file {}", filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guild Card file
|
// Guild Card file
|
||||||
|
|
||||||
string Client::guild_card_filename(const string& bb_username) {
|
std::string Client::guild_card_filename(const std::string& bb_username) {
|
||||||
return std::format("system/players/guild_cards_{}.psocard", bb_username);
|
return std::format("system/players/guild_cards_{}.psocard", bb_username);
|
||||||
}
|
}
|
||||||
|
|
||||||
string Client::guild_card_filename() const {
|
std::string Client::guild_card_filename() const {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have saved Guild Card files");
|
throw std::logic_error("non-BB players do not have saved Guild Card files");
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
return this->guild_card_filename(this->login->bb_license->username);
|
return this->guild_card_filename(this->login->bb_license->username);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) {
|
std::shared_ptr<PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) {
|
||||||
if (!this->guild_card_data && allow_load) {
|
if (!this->guild_card_data && allow_load) {
|
||||||
this->load_all_files();
|
this->load_all_files();
|
||||||
}
|
}
|
||||||
return this->guild_card_data;
|
return this->guild_card_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) const {
|
std::shared_ptr<const PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) const {
|
||||||
if (!this->guild_card_data && allow_load) {
|
if (!this->guild_card_data && allow_load) {
|
||||||
throw runtime_error("account data is not loaded");
|
throw std::runtime_error("account data is not loaded");
|
||||||
}
|
}
|
||||||
return this->guild_card_data;
|
return this->guild_card_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_guild_card_file() const {
|
void Client::save_guild_card_file() const {
|
||||||
if (!this->guild_card_data.get()) {
|
if (!this->guild_card_data.get()) {
|
||||||
throw logic_error("no Guild Card file loaded");
|
throw std::logic_error("no Guild Card file loaded");
|
||||||
}
|
}
|
||||||
string filename = this->guild_card_filename();
|
std::string filename = this->guild_card_filename();
|
||||||
phosg::save_object_file(filename, *this->guild_card_data);
|
phosg::save_object_file(filename, *this->guild_card_data);
|
||||||
this->log.info_f("Saved Guild Card file {}", filename);
|
this->log.info_f("Saved Guild Card file {}", filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Character file
|
// Character file
|
||||||
|
|
||||||
string Client::character_filename(const std::string& bb_username, ssize_t index) {
|
std::string Client::character_filename(const std::string& bb_username, ssize_t index) {
|
||||||
if (bb_username.empty()) {
|
if (bb_username.empty()) {
|
||||||
throw logic_error("non-BB players do not have saved character filenames");
|
throw std::logic_error("non-BB players do not have saved character filenames");
|
||||||
}
|
}
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
throw logic_error("character index is not set");
|
throw std::logic_error("character index is not set");
|
||||||
}
|
}
|
||||||
return std::format("system/players/player_{}_{}.psochar", bb_username, index);
|
return std::format("system/players/player_{}_{}.psochar", bb_username, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
|
std::string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
|
||||||
return std::format("system/players/backup_player_{}_{}.{}",
|
return std::format("system/players/backup_player_{}_{}.{}",
|
||||||
account_id, index, is_ep3 ? "pso3char" : "psochar");
|
account_id, index, is_ep3 ? "pso3char" : "psochar");
|
||||||
}
|
}
|
||||||
|
|
||||||
string Client::character_filename() const {
|
std::string Client::character_filename() const {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have saved character filenames");
|
throw std::logic_error("non-BB players do not have saved character filenames");
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
return this->character_filename(this->login->bb_license->username, this->bb_character_index);
|
return this->character_filename(this->login->bb_license->username, this->bb_character_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allow_overlay) {
|
std::shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allow_overlay) {
|
||||||
if (this->overlay_character_data && allow_overlay) {
|
if (this->overlay_character_data && allow_overlay) {
|
||||||
return this->overlay_character_data;
|
return this->overlay_character_data;
|
||||||
}
|
}
|
||||||
if (!this->character_data && allow_load) {
|
if (!this->character_data && allow_load) {
|
||||||
if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) {
|
if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) {
|
||||||
throw runtime_error("character index not specified");
|
throw std::runtime_error("character index not specified");
|
||||||
}
|
}
|
||||||
this->load_all_files();
|
this->load_all_files();
|
||||||
if (!this->character_data) {
|
if (!this->character_data) {
|
||||||
@@ -591,35 +587,35 @@ shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allo
|
|||||||
return this->character_data;
|
return this->character_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const PSOBBCharacterFile> Client::character_file(bool throw_if_missing, bool allow_overlay) const {
|
std::shared_ptr<const PSOBBCharacterFile> Client::character_file(bool throw_if_missing, bool allow_overlay) const {
|
||||||
if (allow_overlay && this->overlay_character_data) {
|
if (allow_overlay && this->overlay_character_data) {
|
||||||
return this->overlay_character_data;
|
return this->overlay_character_data;
|
||||||
}
|
}
|
||||||
if (!this->character_data && throw_if_missing) {
|
if (!this->character_data && throw_if_missing) {
|
||||||
throw runtime_error("character data is not loaded");
|
throw std::runtime_error("character data is not loaded");
|
||||||
}
|
}
|
||||||
return this->character_data;
|
return this->character_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_character_file(
|
void Client::save_character_file(
|
||||||
const string& filename,
|
const std::string& filename,
|
||||||
shared_ptr<const PSOBBBaseSystemFile> system,
|
std::shared_ptr<const PSOBBBaseSystemFile> system,
|
||||||
shared_ptr<const PSOBBCharacterFile> character) {
|
std::shared_ptr<const PSOBBCharacterFile> character) {
|
||||||
PSOCHARFile::save(filename, system, character);
|
PSOCHARFile::save(filename, system, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_ep3_character_file(
|
void Client::save_ep3_character_file(
|
||||||
const string& filename,
|
const std::string& filename,
|
||||||
const PSOGCEp3CharacterFile::Character& character) {
|
const PSOGCEp3CharacterFile::Character& character) {
|
||||||
phosg::save_file(filename, &character, sizeof(character));
|
phosg::save_file(filename, &character, sizeof(character));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_character_file() {
|
void Client::save_character_file() {
|
||||||
if (!this->system_data.get()) {
|
if (!this->system_data.get()) {
|
||||||
throw logic_error("no system file loaded");
|
throw std::logic_error("no system file loaded");
|
||||||
}
|
}
|
||||||
if (!this->character_data.get()) {
|
if (!this->character_data.get()) {
|
||||||
throw logic_error("no character file loaded");
|
throw std::logic_error("no character file loaded");
|
||||||
}
|
}
|
||||||
if (this->should_update_play_time) {
|
if (this->should_update_play_time) {
|
||||||
// This is slightly inaccurate, since fractions of a second are truncated off each time we save. I'm lazy, so
|
// This is slightly inaccurate, since fractions of a second are truncated off each time we save. I'm lazy, so
|
||||||
@@ -643,18 +639,18 @@ void Client::save_character_file() {
|
|||||||
void Client::create_character_file(
|
void Client::create_character_file(
|
||||||
uint32_t guild_card_number,
|
uint32_t guild_card_number,
|
||||||
Language language,
|
Language language,
|
||||||
const PlayerDispDataBBPreview& preview,
|
const PlayerVisualConfigV4& visual,
|
||||||
shared_ptr<const LevelTable> level_table) {
|
std::shared_ptr<const LevelTable> level_table) {
|
||||||
this->log.info_f("Creating new character file");
|
this->log.info_f("Creating new character file");
|
||||||
this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table);
|
this->character_data = PSOBBCharacterFile::create_from_config(guild_card_number, language, visual, level_table);
|
||||||
this->save_character_file();
|
this->save_character_file();
|
||||||
this->log.info_f("Deleting bank file");
|
this->log.info_f("Deleting bank file");
|
||||||
this->bank_data.reset();
|
this->bank_data.reset();
|
||||||
std::filesystem::remove(this->bank_filename());
|
std::filesystem::remove(this->bank_filename());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_ptr<const LevelTable> level_table) {
|
void Client::create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table) {
|
||||||
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
|
this->overlay_character_data = std::make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
|
||||||
|
|
||||||
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
|
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
|
||||||
this->overlay_character_data->inventory.remove_all_items_of_type(0);
|
this->overlay_character_data->inventory.remove_all_items_of_type(0);
|
||||||
@@ -671,8 +667,8 @@ void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_p
|
|||||||
this->overlay_character_data->inventory.hp_from_materials = 0;
|
this->overlay_character_data->inventory.hp_from_materials = 0;
|
||||||
this->overlay_character_data->inventory.tp_from_materials = 0;
|
this->overlay_character_data->inventory.tp_from_materials = 0;
|
||||||
|
|
||||||
uint32_t target_level = clamp<uint32_t>(rules->char_level, 0, 199);
|
uint32_t target_level = std::clamp<uint32_t>(rules->char_level, 0, 199);
|
||||||
uint8_t char_class = this->overlay_character_data->disp.visual.char_class;
|
uint8_t char_class = this->overlay_character_data->disp.visual.sh.char_class;
|
||||||
auto& stats = this->overlay_character_data->disp.stats;
|
auto& stats = this->overlay_character_data->disp.stats;
|
||||||
|
|
||||||
level_table->reset_to_base(stats, char_class);
|
level_table->reset_to_base(stats, char_class);
|
||||||
@@ -703,11 +699,11 @@ void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_p
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Client::create_challenge_overlay(
|
void Client::create_challenge_overlay(
|
||||||
Version version, size_t template_index, shared_ptr<const LevelTable> level_table) {
|
Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table) {
|
||||||
auto p = this->character_file(true, false);
|
auto p = this->character_file(true, false);
|
||||||
const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index);
|
const auto& tpl = get_challenge_template_definition(version, p->disp.visual.sh.class_flags, template_index);
|
||||||
|
|
||||||
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*p);
|
this->overlay_character_data = std::make_shared<PSOBBCharacterFile>(*p);
|
||||||
auto overlay = this->overlay_character_data;
|
auto overlay = this->overlay_character_data;
|
||||||
|
|
||||||
for (size_t z = 0; z < overlay->inventory.items.size(); z++) {
|
for (size_t z = 0; z < overlay->inventory.items.size(); z++) {
|
||||||
@@ -722,11 +718,11 @@ void Client::create_challenge_overlay(
|
|||||||
|
|
||||||
overlay->inventory.items[13].extension_data2 = 1;
|
overlay->inventory.items[13].extension_data2 = 1;
|
||||||
|
|
||||||
level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.char_class);
|
level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.sh.char_class);
|
||||||
level_table->advance_to_level(overlay->disp.stats, tpl.level, overlay->disp.visual.char_class);
|
level_table->advance_to_level(overlay->disp.stats, tpl.level, overlay->disp.visual.sh.char_class);
|
||||||
|
|
||||||
const auto& stats_delta = level_table->stats_delta_for_level(
|
const auto& stats_delta = level_table->stats_delta_for_level(
|
||||||
overlay->disp.visual.char_class, overlay->disp.stats.level);
|
overlay->disp.visual.sh.char_class, overlay->disp.stats.level);
|
||||||
overlay->disp.stats.esp = 40;
|
overlay->disp.stats.esp = 40;
|
||||||
overlay->disp.stats.attack_range = 10.0;
|
overlay->disp.stats.attack_range = 10.0;
|
||||||
overlay->disp.stats.exp = stats_delta.exp;
|
overlay->disp.stats.exp = stats_delta.exp;
|
||||||
@@ -752,9 +748,9 @@ void Client::create_challenge_overlay(
|
|||||||
|
|
||||||
// Bank file
|
// Bank file
|
||||||
|
|
||||||
string Client::bank_filename(const std::string& bb_username, ssize_t index) {
|
std::string Client::bank_filename(const std::string& bb_username, ssize_t index) {
|
||||||
if (bb_username.empty()) {
|
if (bb_username.empty()) {
|
||||||
throw logic_error("non-BB players do not have saved bank files");
|
throw std::logic_error("non-BB players do not have saved bank files");
|
||||||
}
|
}
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return std::format("system/players/shared_bank_{}.psobank", bb_username);
|
return std::format("system/players/shared_bank_{}.psobank", bb_username);
|
||||||
@@ -763,19 +759,19 @@ string Client::bank_filename(const std::string& bb_username, ssize_t index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string Client::bank_filename() const {
|
std::string Client::bank_filename() const {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have saved bank filenames");
|
throw std::logic_error("non-BB players do not have saved bank filenames");
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
return this->bank_filename(this->login->bb_license->username, this->bb_bank_character_index);
|
return this->bank_filename(this->login->bb_license->username, this->bb_bank_character_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have saved bank files");
|
throw std::logic_error("non-BB players do not have saved bank files");
|
||||||
}
|
}
|
||||||
if (this->has_overlay()) {
|
if (this->has_overlay()) {
|
||||||
throw std::runtime_error("bank is inaccessible when overlay is present");
|
throw std::runtime_error("bank is inaccessible when overlay is present");
|
||||||
@@ -785,7 +781,7 @@ std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
|||||||
// If there's a psobank file, load it and ignore the character file bank
|
// If there's a psobank file, load it and ignore the character file bank
|
||||||
auto filename = this->bank_filename();
|
auto filename = this->bank_filename();
|
||||||
auto f = phosg::fopen_unique(filename, "rb");
|
auto f = phosg::fopen_unique(filename, "rb");
|
||||||
this->bank_data = make_shared<PlayerBank>();
|
this->bank_data = std::make_shared<PlayerBank>();
|
||||||
this->bank_data->load(f.get());
|
this->bank_data->load(f.get());
|
||||||
this->log.info_f("Loaded bank data from {}", filename);
|
this->log.info_f("Loaded bank data from {}", filename);
|
||||||
} catch (const phosg::cannot_open_file&) {
|
} catch (const phosg::cannot_open_file&) {
|
||||||
@@ -797,15 +793,16 @@ std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
|||||||
this->log.info_f("Using bank data from loaded character");
|
this->log.info_f("Using bank data from loaded character");
|
||||||
} else if (this->bb_bank_character_index >= 0) {
|
} else if (this->bb_bank_character_index >= 0) {
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
string filename = this->character_filename(this->login->bb_license->username, this->bb_bank_character_index);
|
std::string filename = this->character_filename(
|
||||||
|
this->login->bb_license->username, this->bb_bank_character_index);
|
||||||
auto character = PSOCHARFile::load_shared(filename, false).character_file;
|
auto character = PSOCHARFile::load_shared(filename, false).character_file;
|
||||||
this->bank_data = std::make_shared<PlayerBank>(character->bank);
|
this->bank_data = std::make_shared<PlayerBank>(character->bank);
|
||||||
this->log.info_f("Using bank data from {}", filename);
|
this->log.info_f("Using bank data from {}", filename);
|
||||||
} else {
|
} else {
|
||||||
// The shared bank doesn't exist; make a new one
|
// The shared bank doesn't exist; make a new one
|
||||||
this->bank_data = make_shared<PlayerBank>();
|
this->bank_data = std::make_shared<PlayerBank>();
|
||||||
this->log.info_f("Created new shared bank");
|
this->log.info_f("Created new shared bank");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -825,14 +822,14 @@ std::shared_ptr<const PlayerBank> Client::bank_file(bool throw_if_missing) const
|
|||||||
return this->bank_data;
|
return this->bank_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_bank_file(const string& filename, const PlayerBank& bank) {
|
void Client::save_bank_file(const std::string& filename, const PlayerBank& bank) {
|
||||||
auto f = phosg::fopen_unique(filename, "wb");
|
auto f = phosg::fopen_unique(filename, "wb");
|
||||||
bank.save(f.get());
|
bank.save(f.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::save_bank_file() const {
|
void Client::save_bank_file() const {
|
||||||
if (!this->bank_data) {
|
if (!this->bank_data) {
|
||||||
throw logic_error("no bank file loaded");
|
throw std::logic_error("no bank file loaded");
|
||||||
}
|
}
|
||||||
auto filename = this->bank_filename();
|
auto filename = this->bank_filename();
|
||||||
this->save_bank_file(filename, *this->bank_data);
|
this->save_bank_file(filename, *this->bank_data);
|
||||||
@@ -854,30 +851,28 @@ void Client::change_bank(ssize_t index) {
|
|||||||
|
|
||||||
// Legacy files
|
// Legacy files
|
||||||
|
|
||||||
string Client::legacy_account_filename() const {
|
std::string Client::legacy_account_filename() const {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have saved account data");
|
throw std::logic_error("non-BB players do not have saved account data");
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
return std::format("system/players/account_{}.nsa", this->login->bb_license->username);
|
return std::format("system/players/account_{}.nsa", this->login->bb_license->username);
|
||||||
}
|
}
|
||||||
|
|
||||||
string Client::legacy_player_filename() const {
|
std::string Client::legacy_player_filename() const {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("non-BB players do not have saved player files");
|
throw std::logic_error("non-BB players do not have saved player files");
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("client is not logged in");
|
throw std::logic_error("client is not logged in");
|
||||||
}
|
}
|
||||||
if (this->bb_character_index < 0) {
|
if (this->bb_character_index < 0) {
|
||||||
throw logic_error("character index is not set");
|
throw std::logic_error("character index is not set");
|
||||||
}
|
}
|
||||||
return std::format(
|
return std::format("system/players/player_{}_{}.nsc",
|
||||||
"system/players/player_{}_{}.nsc",
|
this->login->bb_license->username, static_cast<ssize_t>(this->bb_character_index + 1));
|
||||||
this->login->bb_license->username,
|
|
||||||
static_cast<ssize_t>(this->bb_character_index + 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders) {
|
void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders) {
|
||||||
@@ -891,20 +886,20 @@ void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_sende
|
|||||||
|
|
||||||
void Client::load_all_files() {
|
void Client::load_all_files() {
|
||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
|
||||||
this->character_data = make_shared<PSOBBCharacterFile>();
|
this->character_data = std::make_shared<PSOBBCharacterFile>();
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>();
|
||||||
this->bank_data = make_shared<PlayerBank>();
|
this->bank_data = std::make_shared<PlayerBank>();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->login || !this->login->bb_license) {
|
if (!this->login || !this->login->bb_license) {
|
||||||
throw logic_error("cannot load BB player data until client is logged in");
|
throw std::logic_error("cannot load BB player data until client is logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->system_data) {
|
if (!this->system_data) {
|
||||||
string sys_filename = this->system_filename();
|
std::string sys_filename = this->system_filename();
|
||||||
if (std::filesystem::is_regular_file(sys_filename)) {
|
if (std::filesystem::is_regular_file(sys_filename)) {
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
this->system_data = std::make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||||
this->log.info_f("Loaded system data from {}", sys_filename);
|
this->log.info_f("Loaded system data from {}", sys_filename);
|
||||||
} else {
|
} else {
|
||||||
this->log.info_f("System file is missing: {}", sys_filename);
|
this->log.info_f("System file is missing: {}", sys_filename);
|
||||||
@@ -912,7 +907,7 @@ void Client::load_all_files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||||
string char_filename = this->character_filename();
|
std::string char_filename = this->character_filename();
|
||||||
if (std::filesystem::is_regular_file(char_filename)) {
|
if (std::filesystem::is_regular_file(char_filename)) {
|
||||||
auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data);
|
auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data);
|
||||||
this->character_data = psochar.character_file;
|
this->character_data = psochar.character_file;
|
||||||
@@ -921,7 +916,7 @@ void Client::load_all_files() {
|
|||||||
// If there was no .psosys file, use the system file from the .psochar file instead
|
// If there was no .psosys file, use the system file from the .psochar file instead
|
||||||
if (!this->system_data) {
|
if (!this->system_data) {
|
||||||
if (!psochar.system_file) {
|
if (!psochar.system_file) {
|
||||||
throw logic_error("account system data not present, and also not loaded from psochar file");
|
throw std::logic_error("account system data not present, and also not loaded from psochar file");
|
||||||
}
|
}
|
||||||
this->system_data = psochar.system_file;
|
this->system_data = psochar.system_file;
|
||||||
this->log.info_f("Loaded system data from {}", char_filename);
|
this->log.info_f("Loaded system data from {}", char_filename);
|
||||||
@@ -936,9 +931,9 @@ void Client::load_all_files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this->guild_card_data) {
|
if (!this->guild_card_data) {
|
||||||
string card_filename = this->guild_card_filename();
|
std::string card_filename = this->guild_card_filename();
|
||||||
if (std::filesystem::is_regular_file(card_filename)) {
|
if (std::filesystem::is_regular_file(card_filename)) {
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
|
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||||
this->guild_card_data->delete_duplicates();
|
this->guild_card_data->delete_duplicates();
|
||||||
this->log.info_f("Loaded Guild Card data from {}", card_filename);
|
this->log.info_f("Loaded Guild Card data from {}", card_filename);
|
||||||
} else {
|
} else {
|
||||||
@@ -948,25 +943,25 @@ void Client::load_all_files() {
|
|||||||
|
|
||||||
// If any of the above files are still missing, try to load from .nsa/.nsc files instead
|
// If any of the above files are still missing, try to load from .nsa/.nsc files instead
|
||||||
if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
|
if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
|
||||||
string nsa_filename = this->legacy_account_filename();
|
std::string nsa_filename = this->legacy_account_filename();
|
||||||
shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
std::shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
||||||
if (std::filesystem::is_regular_file(nsa_filename)) {
|
if (std::filesystem::is_regular_file(nsa_filename)) {
|
||||||
nsa_data = make_shared<LegacySavedAccountDataBB>(phosg::load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
nsa_data = std::make_shared<LegacySavedAccountDataBB>(phosg::load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
||||||
if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) {
|
if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) {
|
||||||
throw runtime_error("account data header is incorrect");
|
throw std::runtime_error("account data header is incorrect");
|
||||||
}
|
}
|
||||||
if (!this->system_data) {
|
if (!this->system_data) {
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
|
this->system_data = std::make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
|
||||||
this->log.info_f("Loaded legacy system data from {}", nsa_filename);
|
this->log.info_f("Loaded legacy system data from {}", nsa_filename);
|
||||||
}
|
}
|
||||||
if (!this->guild_card_data) {
|
if (!this->guild_card_data) {
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(nsa_data->guild_card_file);
|
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>(nsa_data->guild_card_file);
|
||||||
this->log.info_f("Loaded legacy Guild Card data from {}", nsa_filename);
|
this->log.info_f("Loaded legacy Guild Card data from {}", nsa_filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||||
string nsc_filename = this->legacy_player_filename();
|
std::string nsc_filename = this->legacy_player_filename();
|
||||||
if (std::filesystem::is_regular_file(nsc_filename)) {
|
if (std::filesystem::is_regular_file(nsc_filename)) {
|
||||||
auto nsc_data = phosg::load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
auto nsc_data = phosg::load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
||||||
if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) {
|
if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) {
|
||||||
@@ -976,10 +971,10 @@ void Client::load_all_files() {
|
|||||||
nsc_data.battle_records.disconnect_count = 0;
|
nsc_data.battle_records.disconnect_count = 0;
|
||||||
nsc_data.battle_records.unknown_a1.clear(0);
|
nsc_data.battle_records.unknown_a1.clear(0);
|
||||||
} else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) {
|
} else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) {
|
||||||
throw runtime_error("legacy player data has incorrect signature");
|
throw std::runtime_error("legacy player data has incorrect signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->character_data = make_shared<PSOBBCharacterFile>();
|
this->character_data = std::make_shared<PSOBBCharacterFile>();
|
||||||
this->character_data->inventory = nsc_data.inventory;
|
this->character_data->inventory = nsc_data.inventory;
|
||||||
this->character_data->disp = nsc_data.disp;
|
this->character_data->disp = nsc_data.disp;
|
||||||
this->character_data->play_time_seconds = 0;
|
this->character_data->play_time_seconds = 0;
|
||||||
@@ -987,12 +982,12 @@ void Client::load_all_files() {
|
|||||||
this->character_data->death_count = nsc_data.death_count;
|
this->character_data->death_count = nsc_data.death_count;
|
||||||
this->character_data->bank = nsc_data.bank;
|
this->character_data->bank = nsc_data.bank;
|
||||||
this->character_data->guild_card.guild_card_number = this->login->account->account_id;
|
this->character_data->guild_card.guild_card_number = this->login->account->account_id;
|
||||||
this->character_data->guild_card.name = nsc_data.disp.name;
|
this->character_data->guild_card.name = nsc_data.disp.visual.name;
|
||||||
this->character_data->guild_card.description = nsc_data.guild_card_description;
|
this->character_data->guild_card.description = nsc_data.guild_card_description;
|
||||||
this->character_data->guild_card.present = 1;
|
this->character_data->guild_card.present = 1;
|
||||||
this->character_data->guild_card.language = nsc_data.inventory.language;
|
this->character_data->guild_card.language = nsc_data.inventory.language;
|
||||||
this->character_data->guild_card.section_id = nsc_data.disp.visual.section_id;
|
this->character_data->guild_card.section_id = nsc_data.disp.visual.sh.section_id;
|
||||||
this->character_data->guild_card.char_class = nsc_data.disp.visual.char_class;
|
this->character_data->guild_card.char_class = nsc_data.disp.visual.sh.char_class;
|
||||||
this->character_data->auto_reply = nsc_data.auto_reply;
|
this->character_data->auto_reply = nsc_data.auto_reply;
|
||||||
this->character_data->info_board = nsc_data.info_board;
|
this->character_data->info_board = nsc_data.info_board;
|
||||||
this->character_data->battle_records = nsc_data.battle_records;
|
this->character_data->battle_records = nsc_data.battle_records;
|
||||||
@@ -1015,7 +1010,7 @@ void Client::load_all_files() {
|
|||||||
// The system and Guild Card files can be auto-created if they can't be loaded. After this, system_data and
|
// The system and Guild Card files can be auto-created if they can't be loaded. After this, system_data and
|
||||||
// guild_card_data are always non-null, but character_data may still be null
|
// guild_card_data are always non-null, but character_data may still be null
|
||||||
if (!this->system_data) {
|
if (!this->system_data) {
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
|
||||||
auto s = this->require_server_state();
|
auto s = this->require_server_state();
|
||||||
if (s->bb_default_keyboard_config) {
|
if (s->bb_default_keyboard_config) {
|
||||||
this->system_data->key_config = *s->bb_default_keyboard_config;
|
this->system_data->key_config = *s->bb_default_keyboard_config;
|
||||||
@@ -1026,7 +1021,7 @@ void Client::load_all_files() {
|
|||||||
this->log.info_f("Created new system data");
|
this->log.info_f("Created new system data");
|
||||||
}
|
}
|
||||||
if (!this->guild_card_data) {
|
if (!this->guild_card_data) {
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>();
|
||||||
this->log.info_f("Created new Guild Card data");
|
this->log.info_f("Created new Guild Card data");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,7 +1037,7 @@ void Client::load_all_files() {
|
|||||||
|
|
||||||
if (this->character_data) {
|
if (this->character_data) {
|
||||||
// Clear legacy play_time field
|
// Clear legacy play_time field
|
||||||
this->character_data->disp.name.clear_after_bytes(0x18);
|
this->character_data->disp.visual.name.clear_after_bytes(0x18);
|
||||||
this->character_data->inventory.enforce_stack_limits(stack_limits);
|
this->character_data->inventory.enforce_stack_limits(stack_limits);
|
||||||
this->login->account->auto_reply_message = this->character_data->auto_reply.decode();
|
this->login->account->auto_reply_message = this->character_data->auto_reply.decode();
|
||||||
this->login->account->save();
|
this->login->account->save();
|
||||||
@@ -1055,7 +1050,7 @@ void Client::load_all_files() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> charfile) {
|
void Client::update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> charfile) {
|
||||||
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
|
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
|
||||||
|
|
||||||
Language lang = this->language();
|
Language lang = this->language();
|
||||||
@@ -1064,7 +1059,7 @@ void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> cha
|
|||||||
charfile->guild_card.language = lang;
|
charfile->guild_card.language = lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::update_bank_data_after_load(shared_ptr<PlayerBank> bank) {
|
void Client::update_bank_data_after_load(std::shared_ptr<PlayerBank> bank) {
|
||||||
auto s = this->require_server_state();
|
auto s = this->require_server_state();
|
||||||
auto limits = s->item_stack_limits(this->version());
|
auto limits = s->item_stack_limits(this->version());
|
||||||
for (auto& item : bank->items) {
|
for (auto& item : bank->items) {
|
||||||
@@ -1097,17 +1092,17 @@ void Client::save_all() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Client::load_backup_character(uint32_t account_id, size_t index) {
|
void Client::load_backup_character(uint32_t account_id, size_t index) {
|
||||||
string filename = this->backup_character_filename(account_id, index, false);
|
std::string filename = this->backup_character_filename(account_id, index, false);
|
||||||
this->character_data = PSOCHARFile::load_shared(filename, false).character_file;
|
this->character_data = PSOCHARFile::load_shared(filename, false).character_file;
|
||||||
this->update_character_data_after_load(this->character_data);
|
this->update_character_data_after_load(this->character_data);
|
||||||
this->v1_v2_last_reported_disp.reset();
|
this->v1_v2_last_reported_disp.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOGCEp3CharacterFile::Character> Client::load_ep3_backup_character(uint32_t account_id, size_t index) {
|
std::shared_ptr<PSOGCEp3CharacterFile::Character> Client::load_ep3_backup_character(uint32_t account_id, size_t index) {
|
||||||
string filename = this->backup_character_filename(account_id, index, true);
|
std::string filename = this->backup_character_filename(account_id, index, true);
|
||||||
auto ch = make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
|
auto ch = std::make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
|
||||||
this->character_data = PSOBBCharacterFile::create_from_file(*ch);
|
this->character_data = PSOBBCharacterFile::create_from_file(*ch);
|
||||||
this->ep3_config = make_shared<Episode3::PlayerConfig>(ch->ep3_config);
|
this->ep3_config = std::make_shared<Episode3::PlayerConfig>(ch->ep3_config);
|
||||||
this->update_character_data_after_load(this->character_data);
|
this->update_character_data_after_load(this->character_data);
|
||||||
this->v1_v2_last_reported_disp.reset();
|
this->v1_v2_last_reported_disp.reset();
|
||||||
return ch;
|
return ch;
|
||||||
|
|||||||
+4
-4
@@ -10,7 +10,6 @@
|
|||||||
#include "CommandFormats.hh"
|
#include "CommandFormats.hh"
|
||||||
#include "Episode3/BattleRecord.hh"
|
#include "Episode3/BattleRecord.hh"
|
||||||
#include "Episode3/Tournament.hh"
|
#include "Episode3/Tournament.hh"
|
||||||
#include "FileContentsCache.hh"
|
|
||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "PatchFileIndex.hh"
|
#include "PatchFileIndex.hh"
|
||||||
@@ -28,7 +27,7 @@ struct Lobby;
|
|||||||
class Parsed6x70Data;
|
class Parsed6x70Data;
|
||||||
|
|
||||||
struct GetPlayerInfoResult {
|
struct GetPlayerInfoResult {
|
||||||
// Exactly one of the following two shared_ptrs is not null
|
// Exactly one of the following two std::shared_ptrs is not null
|
||||||
std::shared_ptr<PSOBBCharacterFile> character;
|
std::shared_ptr<PSOBBCharacterFile> character;
|
||||||
std::shared_ptr<PSOGCEp3CharacterFile::Character> ep3_character;
|
std::shared_ptr<PSOGCEp3CharacterFile::Character> ep3_character;
|
||||||
bool is_full_info; // True if the client sent 30; false if it was 61 or 98
|
bool is_full_info; // True if the client sent 30; false if it was 61 or 98
|
||||||
@@ -192,8 +191,9 @@ public:
|
|||||||
};
|
};
|
||||||
bool should_update_play_time;
|
bool should_update_play_time;
|
||||||
std::unordered_set<uint32_t> blocked_senders;
|
std::unordered_set<uint32_t> blocked_senders;
|
||||||
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
std::unique_ptr<PlayerDispDataV123> v1_v2_last_reported_disp;
|
||||||
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
|
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
|
||||||
|
std::unordered_set<uint16_t> expected_game_state_sync_commands; // (command_num << 8) | target_client_id
|
||||||
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
||||||
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
||||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||||
@@ -316,7 +316,7 @@ public:
|
|||||||
void create_character_file(
|
void create_character_file(
|
||||||
uint32_t guild_card_number,
|
uint32_t guild_card_number,
|
||||||
Language language,
|
Language language,
|
||||||
const PlayerDispDataBBPreview& preview,
|
const PlayerVisualConfigV4& visual,
|
||||||
std::shared_ptr<const LevelTable> level_table);
|
std::shared_ptr<const LevelTable> level_table);
|
||||||
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
||||||
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
|
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
|
||||||
|
|||||||
+40
-42
@@ -18,8 +18,6 @@
|
|||||||
#include "Compression.hh"
|
#include "Compression.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
using Arch = ClientFunctionIndex::Function::Architecture;
|
using Arch = ClientFunctionIndex::Function::Architecture;
|
||||||
|
|
||||||
const char* name_for_architecture(Arch arch) {
|
const char* name_for_architecture(Arch arch) {
|
||||||
@@ -31,7 +29,7 @@ const char* name_for_architecture(Arch arch) {
|
|||||||
case Arch::X86:
|
case Arch::X86:
|
||||||
return "x86";
|
return "x86";
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid architecture");
|
throw std::logic_error("invalid architecture");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +42,7 @@ uint32_t specific_version_for_architecture(Arch arch) {
|
|||||||
case Arch::X86:
|
case Arch::X86:
|
||||||
return SPECIFIC_VERSION_X86_INDETERMINATE;
|
return SPECIFIC_VERSION_X86_INDETERMINATE;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid architecture");
|
throw std::logic_error("invalid architecture");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +85,8 @@ const T& get_with_sv_fallback(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
string ClientFunctionIndex::Function::generate_client_command_t(
|
std::string ClientFunctionIndex::Function::generate_client_command_t(
|
||||||
const unordered_map<string, uint32_t>& label_writes,
|
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||||
const void* suffix_data,
|
const void* suffix_data,
|
||||||
size_t suffix_size,
|
size_t suffix_size,
|
||||||
uint32_t override_relocations_offset) const {
|
uint32_t override_relocations_offset) const {
|
||||||
@@ -102,11 +100,11 @@ string ClientFunctionIndex::Function::generate_client_command_t(
|
|||||||
|
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
if (!label_writes.empty()) {
|
if (!label_writes.empty()) {
|
||||||
string modified_code = this->code;
|
std::string modified_code = this->code;
|
||||||
for (const auto& it : label_writes) {
|
for (const auto& it : label_writes) {
|
||||||
size_t offset = this->label_offsets.at(it.first);
|
size_t offset = this->label_offsets.at(it.first);
|
||||||
if (offset > modified_code.size() - 4) {
|
if (offset > modified_code.size() - 4) {
|
||||||
throw runtime_error("label out of range");
|
throw std::runtime_error("label out of range");
|
||||||
}
|
}
|
||||||
*reinterpret_cast<U32T<FooterT::IsBE>*>(modified_code.data() + offset) = it.second;
|
*reinterpret_cast<U32T<FooterT::IsBE>*>(modified_code.data() + offset) = it.second;
|
||||||
}
|
}
|
||||||
@@ -143,8 +141,8 @@ string ClientFunctionIndex::Function::generate_client_command_t(
|
|||||||
return std::move(w.str());
|
return std::move(w.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
string ClientFunctionIndex::Function::generate_client_command(
|
std::string ClientFunctionIndex::Function::generate_client_command(
|
||||||
const unordered_map<string, uint32_t>& label_writes,
|
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||||
const void* suffix_data,
|
const void* suffix_data,
|
||||||
size_t suffix_size,
|
size_t suffix_size,
|
||||||
uint32_t override_relocations_offset) const {
|
uint32_t override_relocations_offset) const {
|
||||||
@@ -153,11 +151,11 @@ string ClientFunctionIndex::Function::generate_client_command(
|
|||||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||||
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("invalid architecture");
|
throw std::logic_error("invalid architecture");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
static std::unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
||||||
std::unordered_set<uint32_t> all_specific_versions;
|
std::unordered_set<uint32_t> all_specific_versions;
|
||||||
struct Line {
|
struct Line {
|
||||||
std::string text;
|
std::string text;
|
||||||
@@ -170,7 +168,7 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
|||||||
auto& line = lines.emplace_back();
|
auto& line = lines.emplace_back();
|
||||||
line.text = std::move(line_text);
|
line.text = std::move(line_text);
|
||||||
|
|
||||||
string stripped_line = line.text;
|
std::string stripped_line = line.text;
|
||||||
phosg::strip_whitespace(stripped_line);
|
phosg::strip_whitespace(stripped_line);
|
||||||
|
|
||||||
if (stripped_line == ".all_versions") {
|
if (stripped_line == ".all_versions") {
|
||||||
@@ -190,7 +188,7 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
|||||||
|
|
||||||
static const std::string empty_str = "";
|
static const std::string empty_str = "";
|
||||||
|
|
||||||
unordered_map<uint32_t, std::string> ret;
|
std::unordered_map<uint32_t, std::string> ret;
|
||||||
for (uint32_t specific_version : all_specific_versions) {
|
for (uint32_t specific_version : all_specific_versions) {
|
||||||
std::deque<std::string> version_lines;
|
std::deque<std::string> version_lines;
|
||||||
bool include_current_line = true;
|
bool include_current_line = true;
|
||||||
@@ -220,15 +218,15 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
|||||||
} else {
|
} else {
|
||||||
std::string line_text = line.text;
|
std::string line_text = line.text;
|
||||||
size_t vers_offset = line_text.find("<VERS ");
|
size_t vers_offset = line_text.find("<VERS ");
|
||||||
while (vers_offset != string::npos) {
|
while (vers_offset != std::string::npos) {
|
||||||
size_t end_offset = line_text.find('>', vers_offset + 6);
|
size_t end_offset = line_text.find('>', vers_offset + 6);
|
||||||
if (end_offset == string::npos) {
|
if (end_offset == std::string::npos) {
|
||||||
throw runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
|
throw std::runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
|
||||||
str_for_specific_version(specific_version), line_znum + 1));
|
str_for_specific_version(specific_version), line_znum + 1));
|
||||||
}
|
}
|
||||||
auto tokens = phosg::split(line_text.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
|
auto tokens = phosg::split(line_text.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
|
||||||
if (current_vers_index >= tokens.size()) {
|
if (current_vers_index >= tokens.size()) {
|
||||||
throw runtime_error(std::format("(version {}) (line {}) invalid <VERS> replacement",
|
throw std::runtime_error(std::format("(version {}) (line {}) invalid <VERS> replacement",
|
||||||
str_for_specific_version(specific_version), line_znum + 1));
|
str_for_specific_version(specific_version), line_znum + 1));
|
||||||
}
|
}
|
||||||
line_text = line_text.substr(0, vers_offset) + tokens[current_vers_index] + line_text.substr(end_offset + 1);
|
line_text = line_text.substr(0, vers_offset) + tokens[current_vers_index] + line_text.substr(end_offset + 1);
|
||||||
@@ -243,12 +241,12 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_any_failure) {
|
ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise_on_any_failure) {
|
||||||
map<string, string> source_files;
|
std::map<std::string, std::string> source_files;
|
||||||
std::function<void(const std::string&)> add_directory = [&](const std::string& dir) -> void {
|
std::function<void(const std::string&)> add_directory = [&](const std::string& dir) -> void {
|
||||||
for (const auto& item : std::filesystem::directory_iterator(dir)) {
|
for (const auto& item : std::filesystem::directory_iterator(dir)) {
|
||||||
string item_name = item.path().filename().string();
|
std::string item_name = item.path().filename().string();
|
||||||
string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
|
std::string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
|
||||||
if (std::filesystem::is_directory(item_path)) {
|
if (std::filesystem::is_directory(item_path)) {
|
||||||
add_directory(item_path);
|
add_directory(item_path);
|
||||||
} else if (item_path.ends_with(".s") && std::filesystem::is_regular_file(item_path)) {
|
} else if (item_path.ends_with(".s") && std::filesystem::is_regular_file(item_path)) {
|
||||||
@@ -268,7 +266,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
};
|
};
|
||||||
add_directory(root_dir);
|
add_directory(root_dir);
|
||||||
|
|
||||||
unordered_map<string, string> include_cache;
|
std::unordered_map<std::string, std::string> include_cache;
|
||||||
uint32_t last_menu_item_id = 0;
|
uint32_t last_menu_item_id = 0;
|
||||||
for (const auto& [source_filename, source] : source_files) {
|
for (const auto& [source_filename, source] : source_files) {
|
||||||
if (!source_filename.ends_with(".s")) {
|
if (!source_filename.ends_with(".s")) {
|
||||||
@@ -288,15 +286,15 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [specific_version, source] : preprocessed) {
|
for (const auto& [specific_version, source] : preprocessed) {
|
||||||
shared_ptr<Function> fn = make_shared<Function>();
|
std::shared_ptr<Function> fn = std::make_shared<Function>();
|
||||||
fn->short_name = source_filename.substr(0, source_filename.size() - 2);
|
fn->short_name = source_filename.substr(0, source_filename.size() - 2);
|
||||||
fn->specific_version = specific_version;
|
fn->specific_version = specific_version;
|
||||||
fn->menu_item_id = ++last_menu_item_id;
|
fn->menu_item_id = ++last_menu_item_id;
|
||||||
fn->arch = architecture_for_specific_version(fn->specific_version);
|
fn->arch = architecture_for_specific_version(fn->specific_version);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
unordered_set<string> get_include_stack;
|
std::unordered_set<std::string> get_include_stack;
|
||||||
function<string(const string&, uint32_t)> get_include_for_sv = [&include_cache, &source_files, &get_include_stack, &get_include_for_sv](const string& name, uint32_t specific_version) -> string {
|
std::function<std::string(const std::string&, uint32_t)> get_include_for_sv = [&include_cache, &source_files, &get_include_stack, &get_include_for_sv](const std::string& name, uint32_t specific_version) -> std::string {
|
||||||
try {
|
try {
|
||||||
return get_with_sv_fallback(include_cache, name, specific_version);
|
return get_with_sv_fallback(include_cache, name, specific_version);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
@@ -309,7 +307,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
auto it = source_files.find(name + ".inc.s");
|
auto it = source_files.find(name + ".inc.s");
|
||||||
if (it != source_files.end()) {
|
if (it != source_files.end()) {
|
||||||
if (!get_include_stack.emplace(name).second) {
|
if (!get_include_stack.emplace(name).second) {
|
||||||
throw runtime_error("Mutual recursion between includes: " + name);
|
throw std::runtime_error("Mutual recursion between includes: " + name);
|
||||||
}
|
}
|
||||||
for (const auto& [include_specific_version, include_source] : preprocess_function_code(it->second)) {
|
for (const auto& [include_specific_version, include_source] : preprocess_function_code(it->second)) {
|
||||||
ResourceDASM::EmulatorBase::AssembleResult ret;
|
ResourceDASM::EmulatorBase::AssembleResult ret;
|
||||||
@@ -325,7 +323,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
ret = ResourceDASM::SH4Emulator::assemble(include_source, get_include);
|
ret = ResourceDASM::SH4Emulator::assemble(include_source, get_include);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown architecture");
|
throw std::runtime_error("unknown architecture");
|
||||||
}
|
}
|
||||||
if (client_functions_log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (client_functions_log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
client_functions_log.debug_f("({}) Compiled include {}-{}",
|
client_functions_log.debug_f("({}) Compiled include {}-{}",
|
||||||
@@ -347,7 +345,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
return get_with_sv_fallback(include_cache, name, specific_version);
|
return get_with_sv_fallback(include_cache, name, specific_version);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"Data not found for include {} ({})", name, str_for_specific_version(specific_version)));
|
"Data not found for include {} ({})", name, str_for_specific_version(specific_version)));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -365,7 +363,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
assembled = ResourceDASM::SH4Emulator::assemble(source, get_include);
|
assembled = ResourceDASM::SH4Emulator::assemble(source, get_include);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid architecture");
|
throw std::runtime_error("invalid architecture");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn->code = std::move(assembled.code);
|
fn->code = std::move(assembled.code);
|
||||||
@@ -396,17 +394,17 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
} else if (key == "show_return_value") {
|
} else if (key == "show_return_value") {
|
||||||
fn->show_return_value = true;
|
fn->show_return_value = true;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unknown metadata key: " + key);
|
throw std::runtime_error("unknown metadata key: " + key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fn->entrypoint_offset_offset = fn->label_offsets.at("entry_ptr");
|
fn->entrypoint_offset_offset = fn->label_offsets.at("entry_ptr");
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error("code does not contain entry_ptr label");
|
throw std::runtime_error("code does not contain entry_ptr label");
|
||||||
}
|
}
|
||||||
|
|
||||||
set<uint32_t> reloc_indexes;
|
std::set<uint32_t> reloc_indexes;
|
||||||
for (const auto& it : fn->label_offsets) {
|
for (const auto& it : fn->label_offsets) {
|
||||||
if (it.first.starts_with("reloc")) {
|
if (it.first.starts_with("reloc")) {
|
||||||
reloc_indexes.emplace(it.second / 4);
|
reloc_indexes.emplace(it.second / 4);
|
||||||
@@ -416,13 +414,13 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
for (const auto& it : reloc_indexes) {
|
for (const auto& it : reloc_indexes) {
|
||||||
uint32_t delta = it - prev_index;
|
uint32_t delta = it - prev_index;
|
||||||
if (delta > 0xFFFF) {
|
if (delta > 0xFFFF) {
|
||||||
throw runtime_error("relocation delta too far away");
|
throw std::runtime_error("relocation delta too far away");
|
||||||
}
|
}
|
||||||
fn->relocation_deltas.emplace_back(delta);
|
fn->relocation_deltas.emplace_back(delta);
|
||||||
prev_index = it;
|
prev_index = it;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
if (raise_on_any_failure) {
|
if (raise_on_any_failure) {
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -448,18 +446,18 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Menu> ClientFunctionIndex::patch_switches_menu(
|
std::shared_ptr<const Menu> ClientFunctionIndex::patch_switches_menu(
|
||||||
uint32_t specific_version,
|
uint32_t specific_version,
|
||||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const {
|
const std::unordered_set<std::string>& client_auto_patches_enabled) const {
|
||||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
auto ret = std::make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||||
|
|
||||||
auto map_it = this->functions_by_specific_version.find(specific_version);
|
auto map_it = this->functions_by_specific_version.find(specific_version);
|
||||||
if (map_it != this->functions_by_specific_version.end()) {
|
if (map_it != this->functions_by_specific_version.end()) {
|
||||||
for (auto [name, fn] : map_it->second) {
|
for (auto [name, fn] : map_it->second) {
|
||||||
if (fn->appears_in_patches_menu() && !server_auto_patches_enabled.count(fn->short_name)) {
|
if (fn->appears_in_patches_menu() && !server_auto_patches_enabled.count(fn->short_name)) {
|
||||||
string item_text;
|
std::string item_text;
|
||||||
item_text.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
item_text.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
||||||
item_text += fn->long_name.empty() ? fn->short_name : fn->long_name;
|
item_text += fn->long_name.empty() ? fn->short_name : fn->long_name;
|
||||||
ret->items.emplace_back(
|
ret->items.emplace_back(
|
||||||
@@ -496,7 +494,7 @@ std::shared_ptr<const ClientFunctionIndex::Function> ClientFunctionIndex::get_by
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||||
static unordered_map<uint32_t, uint32_t> checksum_to_specific_version;
|
static std::unordered_map<uint32_t, uint32_t> checksum_to_specific_version;
|
||||||
if (checksum_to_specific_version.empty()) {
|
if (checksum_to_specific_version.empty()) {
|
||||||
struct {
|
struct {
|
||||||
char system_code = 'G';
|
char system_code = 'G';
|
||||||
@@ -517,7 +515,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
|||||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||||
uint32_t specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
|
uint32_t specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
|
||||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||||
throw logic_error("multiple specific_versions have same header checksum");
|
throw std::logic_error("multiple specific_versions have same header checksum");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,7 +527,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
|||||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||||
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
|
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
|
||||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||||
throw logic_error("multiple specific_versions have same header checksum");
|
throw std::logic_error("multiple specific_versions have same header checksum");
|
||||||
}
|
}
|
||||||
data.system_code = 'G';
|
data.system_code = 'G';
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-43
@@ -412,18 +412,18 @@ struct S_UpdateClientConfig_DC_PC_04 {
|
|||||||
le_uint32_t guild_card_number = 0;
|
le_uint32_t guild_card_number = 0;
|
||||||
} __packed_ws__(S_UpdateClientConfig_DC_PC_04, 8);
|
} __packed_ws__(S_UpdateClientConfig_DC_PC_04, 8);
|
||||||
|
|
||||||
struct S_UpdateClientConfig_V3_04 {
|
template <size_t ClientConfigBytes>
|
||||||
|
struct S_UpdateClientConfigT_V3_BB_04 {
|
||||||
le_uint32_t player_tag = 0x00010000;
|
le_uint32_t player_tag = 0x00010000;
|
||||||
le_uint32_t guild_card_number = 0;
|
le_uint32_t guild_card_number = 0;
|
||||||
// This field is opaque to the client; it will send back the contents verbatim in subsequent 9E or 9F commands.
|
// This field is opaque to the client; it will send back the contents verbatim in subsequent 9E or 9F commands.
|
||||||
parray<uint8_t, 0x20> client_config;
|
parray<uint8_t, ClientConfigBytes> client_config;
|
||||||
} __packed_ws__(S_UpdateClientConfig_V3_04, 0x28);
|
} __attribute__((packed));
|
||||||
|
|
||||||
struct S_UpdateClientConfig_BB_04 {
|
using S_UpdateClientConfig_V3_04 = S_UpdateClientConfigT_V3_BB_04<0x20>;
|
||||||
le_uint32_t player_tag = 0x00010000;
|
using S_UpdateClientConfig_BB_04 = S_UpdateClientConfigT_V3_BB_04<0x28>;
|
||||||
le_uint32_t guild_card_number = 0;
|
check_struct_size(S_UpdateClientConfig_V3_04, 0x28);
|
||||||
parray<uint8_t, 0x28> client_config;
|
check_struct_size(S_UpdateClientConfig_BB_04, 0x30);
|
||||||
} __packed_ws__(S_UpdateClientConfig_BB_04, 0x30);
|
|
||||||
|
|
||||||
// 05: Disconnect
|
// 05: Disconnect
|
||||||
// Internal name: SndLogout
|
// Internal name: SndLogout
|
||||||
@@ -1053,13 +1053,13 @@ struct PlayerRecordsEntry_BB {
|
|||||||
|
|
||||||
struct C_CharacterData_DCv1_61_98 {
|
struct C_CharacterData_DCv1_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */
|
/* 041C */
|
||||||
} __packed_ws__(C_CharacterData_DCv1_61_98, 0x041C);
|
} __packed_ws__(C_CharacterData_DCv1_61_98, 0x041C);
|
||||||
|
|
||||||
struct C_CharacterData_DCv2_61_98 {
|
struct C_CharacterData_DCv2_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */ PlayerRecordsEntry_DC records;
|
/* 041C */ PlayerRecordsEntry_DC records;
|
||||||
/* 04D8 */ ChoiceSearchConfig choice_search_config;
|
/* 04D8 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 04F0 */
|
/* 04F0 */
|
||||||
@@ -1067,7 +1067,7 @@ struct C_CharacterData_DCv2_61_98 {
|
|||||||
|
|
||||||
struct C_CharacterData_PC_61_98 {
|
struct C_CharacterData_PC_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */ PlayerRecordsEntry_PC records;
|
/* 041C */ PlayerRecordsEntry_PC records;
|
||||||
/* 0510 */ ChoiceSearchConfig choice_search_config;
|
/* 0510 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 0528 */ parray<le_uint32_t, 0x1E> blocked_senders;
|
/* 0528 */ parray<le_uint32_t, 0x1E> blocked_senders;
|
||||||
@@ -1079,7 +1079,7 @@ struct C_CharacterData_PC_61_98 {
|
|||||||
|
|
||||||
struct C_CharacterData_GCNTE_61_98 {
|
struct C_CharacterData_GCNTE_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */ PlayerRecordsEntry_DC records;
|
/* 041C */ PlayerRecordsEntry_DC records;
|
||||||
/* 04D8 */ ChoiceSearchConfig choice_search_config;
|
/* 04D8 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 04F0 */ parray<le_uint32_t, 0x1E> blocked_senders;
|
/* 04F0 */ parray<le_uint32_t, 0x1E> blocked_senders;
|
||||||
@@ -1091,7 +1091,7 @@ struct C_CharacterData_GCNTE_61_98 {
|
|||||||
|
|
||||||
struct C_CharacterData_V3_61_98 {
|
struct C_CharacterData_V3_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */ PlayerRecordsEntry_V3 records;
|
/* 041C */ PlayerRecordsEntry_V3 records;
|
||||||
/* 0538 */ ChoiceSearchConfig choice_search_config;
|
/* 0538 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 0550 */ pstring<TextEncoding::MARKED, 0xAC> info_board;
|
/* 0550 */ pstring<TextEncoding::MARKED, 0xAC> info_board;
|
||||||
@@ -1104,7 +1104,7 @@ struct C_CharacterData_V3_61_98 {
|
|||||||
|
|
||||||
struct C_CharacterData_Ep3_61_98 {
|
struct C_CharacterData_Ep3_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */ PlayerRecordsEntry_V3 records;
|
/* 041C */ PlayerRecordsEntry_V3 records;
|
||||||
/* 0538 */ ChoiceSearchConfig choice_search_config;
|
/* 0538 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 0550 */ pstring<TextEncoding::MARKED, 0xAC> info_board;
|
/* 0550 */ pstring<TextEncoding::MARKED, 0xAC> info_board;
|
||||||
@@ -1117,7 +1117,7 @@ struct C_CharacterData_Ep3_61_98 {
|
|||||||
|
|
||||||
struct C_CharacterData_BB_61_98 {
|
struct C_CharacterData_BB_61_98 {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataBB disp;
|
/* 034C */ PlayerDispDataV4 disp;
|
||||||
/* 04DC */ PlayerRecordsEntry_BB records;
|
/* 04DC */ PlayerRecordsEntry_BB records;
|
||||||
/* 0638 */ ChoiceSearchConfig choice_search_config;
|
/* 0638 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 0650 */ pstring<TextEncoding::UTF16, 0xAC> info_board;
|
/* 0650 */ pstring<TextEncoding::UTF16, 0xAC> info_board;
|
||||||
@@ -1139,17 +1139,37 @@ struct C_CharacterData_BB_61_98 {
|
|||||||
// 64 (S->C): Join game
|
// 64 (S->C): Join game
|
||||||
// Internal name: RcvStartGame3
|
// Internal name: RcvStartGame3
|
||||||
|
|
||||||
// This is sent to the joining player; the other players get a 65 instead. Note that (except on Episode 3) this command
|
|
||||||
// does not include the player's disp or inventory data. The clients in the game are responsible for sending that data
|
|
||||||
// to each other during the join process with 60/62/6C/6D commands.
|
|
||||||
|
|
||||||
// After receiving a 64 command, the client starts the game loading procedure, during which it will completely ignore
|
|
||||||
// other 64 or 65 commands, and will delay processing of all other commands except 1D until loading is done. If more
|
|
||||||
// than 0x10000 bytes of commands are sent during loading, any commands that don't fit in the buffer are lost.
|
|
||||||
|
|
||||||
// Curiously, this command is named RcvStartGame3 internally, while 0E is named RcvStartGame. The string RcvStartGame2
|
// Curiously, this command is named RcvStartGame3 internally, while 0E is named RcvStartGame. The string RcvStartGame2
|
||||||
// appears in the DC versions, but it seems the relevant code was deleted - there are no references to the string.
|
// appears in the DC versions, but it seems the relevant code was deleted - there are no references to the string.
|
||||||
|
|
||||||
|
// The game joining procedure goes as follows:
|
||||||
|
// 1. The server sends 64 to the joining player, and 65 to all other players in the game. This pauses the game and
|
||||||
|
// brings up the "please wait" message box for all players. The joining player unloads the lobby assets and begins
|
||||||
|
// loading Pioneer 2 assets. On v2 and later, the joining player sends 8A near the beginning of this procedure.
|
||||||
|
// During this time, the joining player will completely ignore other 64 or 65 commands, and will delay processing of
|
||||||
|
// all other commands except 1D until loading is done. If more than 0x10000 bytes of commands are sent during
|
||||||
|
// loading, any commands that don't fit in the buffer are lost.
|
||||||
|
// 2. If the joining player is not the only player in the game:
|
||||||
|
// a. If the leader is DC v1 or later, the leader sends 6x6D (item state).
|
||||||
|
// b. The leader sends 6x6B (enemy state).
|
||||||
|
// c. The leader sends 6x6C (object state).
|
||||||
|
// d. If the leader is DC NTE or DC 11/2000, the leader sends 6x6D (item state).
|
||||||
|
// e. The leader sends 6x6E (set flag state).
|
||||||
|
// f. If the leader is DC v1 or later, the leader sends 6x6F (quest flag state).
|
||||||
|
// g. If the leader is DC v1 or later, the leader sends 6x71 (construct player).
|
||||||
|
// h. All players except the joining player send 6x70 to the joining player. (This is not synchronized; non-leader
|
||||||
|
// players do not wait for any of the above things to happen, so their 6x70 commands may be interleaved with the
|
||||||
|
// preceding commands from the leader, or may arrive after the following 6x72.) Character data is sent in a
|
||||||
|
// different format here (6x70 instead of 65) because it contains ephemeral fields that the server doesn't know
|
||||||
|
// about - things like current HP, state, game flags, player flags, etc. which are not present in 65.
|
||||||
|
// i. If the leader is not BB, the leader sends 6x72 to all players, which resumes the game. If the leader is BB,
|
||||||
|
// the server is responsible for sending 6x72, and should do so here. (There is no sequence-breaking risk here,
|
||||||
|
// since the 6x72 sent to other players will always be ordered after the 65 from the server, so they will always
|
||||||
|
// send a 6x70 containing their player state at the time the game was paused.)
|
||||||
|
// 3. Once the joining player has fully loaded, the player processes all the commands sent during loading, which sets
|
||||||
|
// up the game state and constructs all the other players. Once the local player is constructed, the joining player
|
||||||
|
// sends 6F, which notifies the server that it can unlock the game and allow more players to join.
|
||||||
|
|
||||||
// Header flag = entry count
|
// Header flag = entry count
|
||||||
template <typename LobbyDataT>
|
template <typename LobbyDataT>
|
||||||
struct S_JoinGameT_DC_PC {
|
struct S_JoinGameT_DC_PC {
|
||||||
@@ -1195,7 +1215,7 @@ struct S_JoinGame_Ep3_64 : S_JoinGame_GC_64 {
|
|||||||
// four of these are always present and they are filled in in slot positions.
|
// four of these are always present and they are filled in in slot positions.
|
||||||
struct Ep3PlayerEntry {
|
struct Ep3PlayerEntry {
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
PlayerDispDataDCPCV3 disp;
|
PlayerDispDataV123 disp;
|
||||||
} __packed_ws__(Ep3PlayerEntry, 0x41C);
|
} __packed_ws__(Ep3PlayerEntry, 0x41C);
|
||||||
parray<Ep3PlayerEntry, 4> players_ep3;
|
parray<Ep3PlayerEntry, 4> players_ep3;
|
||||||
} __packed_ws__(S_JoinGame_Ep3_64, 0x1180);
|
} __packed_ws__(S_JoinGame_Ep3_64, 0x1180);
|
||||||
@@ -1272,10 +1292,10 @@ struct S_JoinLobbyT {
|
|||||||
return offsetof(S_JoinLobbyT, entries) + used_entries * sizeof(Entry);
|
return offsetof(S_JoinLobbyT, entries) + used_entries * sizeof(Entry);
|
||||||
}
|
}
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT<LobbyFlagsDCNTE, PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>;
|
using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT<LobbyFlagsDCNTE, PlayerLobbyDataDCGC, PlayerDispDataV123>;
|
||||||
using S_JoinLobby_PC_65_67_68 = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataPC, PlayerDispDataDCPCV3>;
|
using S_JoinLobby_PC_65_67_68 = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataPC, PlayerDispDataV123>;
|
||||||
using S_JoinLobby_DC_GC_65_67_68_Ep3_EB = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>;
|
using S_JoinLobby_DC_GC_65_67_68_Ep3_EB = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataDCGC, PlayerDispDataV123>;
|
||||||
using S_JoinLobby_BB_65_67_68 = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataBB, PlayerDispDataBB>;
|
using S_JoinLobby_BB_65_67_68 = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataBB, PlayerDispDataV4>;
|
||||||
check_struct_size(S_JoinLobby_DCNTE_65_67_68, 0x32D4);
|
check_struct_size(S_JoinLobby_DCNTE_65_67_68, 0x32D4);
|
||||||
check_struct_size(S_JoinLobby_PC_65_67_68, 0x339C);
|
check_struct_size(S_JoinLobby_PC_65_67_68, 0x339C);
|
||||||
check_struct_size(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, 0x32DC);
|
check_struct_size(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, 0x32DC);
|
||||||
@@ -1287,7 +1307,7 @@ struct S_JoinLobby_XB_65_67_68 {
|
|||||||
struct Entry {
|
struct Entry {
|
||||||
PlayerLobbyDataXB lobby_data;
|
PlayerLobbyDataXB lobby_data;
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
PlayerDispDataDCPCV3 disp;
|
PlayerDispDataV123 disp;
|
||||||
} __packed_ws__(Entry, 0x468);
|
} __packed_ws__(Entry, 0x468);
|
||||||
// Note: not all of these will be filled in and sent if the lobby isn't full (the command size will be shorter than
|
// Note: not all of these will be filled in and sent if the lobby isn't full (the command size will be shorter than
|
||||||
// this struct's size)
|
// this struct's size)
|
||||||
@@ -1521,10 +1541,10 @@ struct C_ConnectionInfo_DCNTE_8A {
|
|||||||
// header.flag is a success flag. If it's zero, the client shows an error message and disconnects. Otherwise, the
|
// header.flag is a success flag. If it's zero, the client shows an error message and disconnects. Otherwise, the
|
||||||
// client responds with an 8B command.
|
// client responds with an 8B command.
|
||||||
|
|
||||||
// 8A (C->S): Request lobby/game name (except DC NTE)
|
// 8A (C->S): Request lobby/game name (DCv2 and later)
|
||||||
// No arguments.
|
// No arguments.
|
||||||
|
|
||||||
// 8A (S->C): Lobby/game name (except DC NTE)
|
// 8A (S->C): Lobby/game name (DCv2 and later)
|
||||||
// Contents is a string containing the lobby or game name. All versions after DCv1 send an 8A command to request the
|
// Contents is a string containing the lobby or game name. All versions after DCv1 send an 8A command to request the
|
||||||
// team name after joining a game. The response is used to handle the team_name token in quest strings, and appears in
|
// team name after joining a game. The response is used to handle the team_name token in quest strings, and appears in
|
||||||
// some Challenge Mode information windows.
|
// some Challenge Mode information windows.
|
||||||
@@ -2623,7 +2643,7 @@ struct C_GuildCardDataRequest_BB_03DC {
|
|||||||
// DD (S->C): Send quest state to joining player (BB)
|
// DD (S->C): Send quest state to joining player (BB)
|
||||||
// When a player joins a game with a quest already in progress, the server should send this command to the leader.
|
// When a player joins a game with a quest already in progress, the server should send this command to the leader.
|
||||||
// No arguments except header.flag, which is the client ID that the leader should send quest state to. The leader will
|
// No arguments except header.flag, which is the client ID that the leader should send quest state to. The leader will
|
||||||
// then send a series of target commands (62/6D) that the server can forward to the joining player.
|
// then send 6x6D, 6x6B, 6x6C, and 6x6E, in that order, targeted at the client specified in header.flag.
|
||||||
|
|
||||||
// DE (S->C): Rare monster list (BB)
|
// DE (S->C): Rare monster list (BB)
|
||||||
|
|
||||||
@@ -2928,7 +2948,7 @@ struct S_CardBattleTableConfirmation_Ep3_E5 {
|
|||||||
|
|
||||||
struct SC_PlayerPreview_CreateCharacter_BB_00E5 {
|
struct SC_PlayerPreview_CreateCharacter_BB_00E5 {
|
||||||
le_int32_t character_index = 0;
|
le_int32_t character_index = 0;
|
||||||
PlayerDispDataBBPreview preview;
|
PlayerDispDataV4Preview preview;
|
||||||
} __packed_ws__(SC_PlayerPreview_CreateCharacter_BB_00E5, 0x80);
|
} __packed_ws__(SC_PlayerPreview_CreateCharacter_BB_00E5, 0x80);
|
||||||
|
|
||||||
// E6 (C->S): Spectator team control (Episode 3)
|
// E6 (C->S): Spectator team control (Episode 3)
|
||||||
@@ -3014,7 +3034,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 {
|
|||||||
struct PlayerEntry {
|
struct PlayerEntry {
|
||||||
/* 0000 */ PlayerLobbyDataDCGC lobby_data;
|
/* 0000 */ PlayerLobbyDataDCGC lobby_data;
|
||||||
/* 0020 */ PlayerInventory inventory;
|
/* 0020 */ PlayerInventory inventory;
|
||||||
/* 036C */ PlayerDispDataDCPCV3 disp;
|
/* 036C */ PlayerDispDataV123 disp;
|
||||||
/* 043C */
|
/* 043C */
|
||||||
} __packed_ws__(PlayerEntry, 0x43C);
|
} __packed_ws__(PlayerEntry, 0x43C);
|
||||||
/* 0080 */ parray<PlayerEntry, 4> players;
|
/* 0080 */ parray<PlayerEntry, 4> players;
|
||||||
@@ -4741,7 +4761,7 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 {
|
|||||||
/* 0054 */ PlayerHoldState_DCProtos hold_state;
|
/* 0054 */ PlayerHoldState_DCProtos hold_state;
|
||||||
/* 0064 */ le_uint32_t area = 0;
|
/* 0064 */ le_uint32_t area = 0;
|
||||||
/* 0068 */ le_uint32_t player_flags = 0;
|
/* 0068 */ le_uint32_t player_flags = 0;
|
||||||
/* 006C */ PlayerVisualConfig visual;
|
/* 006C */ PlayerVisualConfigV123 visual;
|
||||||
/* 00BC */ PlayerStats stats;
|
/* 00BC */ PlayerStats stats;
|
||||||
/* 00E0 */ le_uint32_t num_items = 0;
|
/* 00E0 */ le_uint32_t num_items = 0;
|
||||||
/* 00E4 */ parray<PlayerInventoryItem, 0x1E> items;
|
/* 00E4 */ parray<PlayerInventoryItem, 0x1E> items;
|
||||||
@@ -4759,7 +4779,7 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 {
|
|||||||
/* 0060 */ PlayerHoldState_DCProtos hold_state;
|
/* 0060 */ PlayerHoldState_DCProtos hold_state;
|
||||||
/* 0070 */ le_uint32_t area = 0;
|
/* 0070 */ le_uint32_t area = 0;
|
||||||
/* 0074 */ le_uint32_t player_flags = 0;
|
/* 0074 */ le_uint32_t player_flags = 0;
|
||||||
/* 0078 */ PlayerVisualConfig visual;
|
/* 0078 */ PlayerVisualConfigV123 visual;
|
||||||
/* 00C8 */ PlayerStats stats;
|
/* 00C8 */ PlayerStats stats;
|
||||||
/* 00EC */ le_uint32_t num_items = 0;
|
/* 00EC */ le_uint32_t num_items = 0;
|
||||||
/* 00F0 */ parray<PlayerInventoryItem, 0x1E> items;
|
/* 00F0 */ parray<PlayerInventoryItem, 0x1E> items;
|
||||||
@@ -4790,13 +4810,13 @@ struct G_6x70_Base_V1 {
|
|||||||
/* 00AC */ le_uint32_t area = 0;
|
/* 00AC */ le_uint32_t area = 0;
|
||||||
/* 00B0 */ le_uint32_t player_flags = 0;
|
/* 00B0 */ le_uint32_t player_flags = 0;
|
||||||
/* 00B4 */ parray<uint8_t, 0x14> technique_levels_v1 = 0xFF; // Last byte is uninitialized
|
/* 00B4 */ parray<uint8_t, 0x14> technique_levels_v1 = 0xFF; // Last byte is uninitialized
|
||||||
/* 00C8 */ PlayerVisualConfig visual;
|
/* 00C8 */
|
||||||
/* 0118 */
|
} __packed_ws__(G_6x70_Base_V1, 0xC8);
|
||||||
} __packed_ws__(G_6x70_Base_V1, 0x118);
|
|
||||||
|
|
||||||
struct G_SyncPlayerDispAndInventory_DC_PC_6x70 {
|
struct G_SyncPlayerDispAndInventory_DC_PC_6x70 {
|
||||||
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)};
|
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)};
|
||||||
/* 0008 */ G_6x70_Base_V1 base;
|
/* 0008 */ G_6x70_Base_V1 base;
|
||||||
|
/* 00D0 */ PlayerVisualConfigV123 visual;
|
||||||
/* 0120 */ PlayerStats stats;
|
/* 0120 */ PlayerStats stats;
|
||||||
/* 0144 */ le_uint32_t num_items = 0;
|
/* 0144 */ le_uint32_t num_items = 0;
|
||||||
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
|
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
|
||||||
@@ -4807,6 +4827,7 @@ struct G_SyncPlayerDispAndInventory_DC_PC_6x70 {
|
|||||||
struct G_SyncPlayerDispAndInventory_GC_6x70 {
|
struct G_SyncPlayerDispAndInventory_GC_6x70 {
|
||||||
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)};
|
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)};
|
||||||
/* 0008 */ G_6x70_Base_V1 base;
|
/* 0008 */ G_6x70_Base_V1 base;
|
||||||
|
/* 00D0 */ PlayerVisualConfigV123 visual;
|
||||||
/* 0120 */ PlayerStats stats;
|
/* 0120 */ PlayerStats stats;
|
||||||
/* 0144 */ le_uint32_t num_items = 0;
|
/* 0144 */ le_uint32_t num_items = 0;
|
||||||
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
|
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
|
||||||
@@ -4817,6 +4838,7 @@ struct G_SyncPlayerDispAndInventory_GC_6x70 {
|
|||||||
struct G_SyncPlayerDispAndInventory_XB_6x70 {
|
struct G_SyncPlayerDispAndInventory_XB_6x70 {
|
||||||
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)};
|
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)};
|
||||||
/* 0008 */ G_6x70_Base_V1 base;
|
/* 0008 */ G_6x70_Base_V1 base;
|
||||||
|
/* 00D0 */ PlayerVisualConfigV123 visual;
|
||||||
/* 0120 */ PlayerStats stats;
|
/* 0120 */ PlayerStats stats;
|
||||||
/* 0144 */ le_uint32_t num_items = 0;
|
/* 0144 */ le_uint32_t num_items = 0;
|
||||||
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
|
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
|
||||||
@@ -4830,7 +4852,7 @@ struct G_SyncPlayerDispAndInventory_XB_6x70 {
|
|||||||
struct G_SyncPlayerDispAndInventory_BB_6x70 {
|
struct G_SyncPlayerDispAndInventory_BB_6x70 {
|
||||||
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)};
|
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)};
|
||||||
/* 0008 */ G_6x70_Base_V1 base;
|
/* 0008 */ G_6x70_Base_V1 base;
|
||||||
/* 0120 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
|
/* 00D0 */ PlayerVisualConfigV4 visual;
|
||||||
/* 0140 */ PlayerStats stats;
|
/* 0140 */ PlayerStats stats;
|
||||||
/* 0164 */ le_uint32_t num_items = 0;
|
/* 0164 */ le_uint32_t num_items = 0;
|
||||||
/* 0168 */ parray<PlayerInventoryItem, 0x1E> items;
|
/* 0168 */ parray<PlayerInventoryItem, 0x1E> items;
|
||||||
@@ -5200,10 +5222,10 @@ struct G_SetChallengeTime_6x95 {
|
|||||||
|
|
||||||
struct G_SelectChallengeModeFailureOption_6x97 {
|
struct G_SelectChallengeModeFailureOption_6x97 {
|
||||||
G_UnusedHeader header;
|
G_UnusedHeader header;
|
||||||
le_uint32_t unused1 = 0;
|
le_uint32_t client_id = 0;
|
||||||
le_uint32_t is_retry = 0;
|
le_uint32_t is_retry = 0;
|
||||||
|
le_uint32_t unused1 = 0;
|
||||||
le_uint32_t unused2 = 0;
|
le_uint32_t unused2 = 0;
|
||||||
le_uint32_t unused3 = 0;
|
|
||||||
} __packed_ws__(G_SelectChallengeModeFailureOption_6x97, 0x14);
|
} __packed_ws__(G_SelectChallengeModeFailureOption_6x97, 0x14);
|
||||||
|
|
||||||
// 6x98: Unknown
|
// 6x98: Unknown
|
||||||
@@ -5747,7 +5769,7 @@ struct G_ChangeLobbyMusic_Ep3_6xBF {
|
|||||||
// 6xBF: Give EXP (BB) (server->client only)
|
// 6xBF: Give EXP (BB) (server->client only)
|
||||||
// newserv implements an extension that causes this command to show the purple EXP numbers which are normally generated
|
// newserv implements an extension that causes this command to show the purple EXP numbers which are normally generated
|
||||||
// by the client instead. This requires the server to also send the enemy ID that generated the EXP, hence the
|
// by the client instead. This requires the server to also send the enemy ID that generated the EXP, hence the
|
||||||
// extension struct here. See ServerEXPDisplay.59NL.patch.s for details.
|
// extension struct here. See ServerEXPDisplay.s for details.
|
||||||
|
|
||||||
struct G_GiveExperience_BB_6xBF {
|
struct G_GiveExperience_BB_6xBF {
|
||||||
G_ClientIDHeader header;
|
G_ClientIDHeader header;
|
||||||
|
|||||||
+42
-44
@@ -6,8 +6,6 @@
|
|||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
#include "Types.hh"
|
#include "Types.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
template <typename IntT, size_t Count>
|
template <typename IntT, size_t Count>
|
||||||
phosg::JSON to_json(const parray<IntT, Count>& v) {
|
phosg::JSON to_json(const parray<IntT, Count>& v) {
|
||||||
auto ret = phosg::JSON::list();
|
auto ret = phosg::JSON::list();
|
||||||
@@ -20,7 +18,7 @@ phosg::JSON to_json(const parray<IntT, Count>& v) {
|
|||||||
template <typename IntT, size_t Count>
|
template <typename IntT, size_t Count>
|
||||||
void from_json_into(const phosg::JSON& json, parray<IntT, Count>& ret) {
|
void from_json_into(const phosg::JSON& json, parray<IntT, Count>& ret) {
|
||||||
if (json.size() != Count) {
|
if (json.size() != Count) {
|
||||||
throw runtime_error("incorrect array length");
|
throw std::runtime_error("incorrect array length");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < Count; z++) {
|
for (size_t z = 0; z < Count; z++) {
|
||||||
ret[z] = json.at(z).as_int();
|
ret[z] = json.at(z).as_int();
|
||||||
@@ -39,7 +37,7 @@ phosg::JSON to_json(const parray<CommonItemSet::Table::Range<IntT>, Count>& v) {
|
|||||||
template <typename IntT, size_t Count>
|
template <typename IntT, size_t Count>
|
||||||
void from_json_into(const phosg::JSON& json, parray<CommonItemSet::Table::Range<IntT>, Count>& ret) {
|
void from_json_into(const phosg::JSON& json, parray<CommonItemSet::Table::Range<IntT>, Count>& ret) {
|
||||||
if (json.size() != Count) {
|
if (json.size() != Count) {
|
||||||
throw runtime_error("incorrect array length");
|
throw std::runtime_error("incorrect array length");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < Count; z++) {
|
for (size_t z = 0; z < Count; z++) {
|
||||||
from_json_into(json.at(z), ret[z]);
|
from_json_into(json.at(z), ret[z]);
|
||||||
@@ -60,7 +58,7 @@ void from_json_into(const phosg::JSON& json, CommonItemSet::Table::Range<IntT>&
|
|||||||
} else {
|
} else {
|
||||||
const auto& l = json.as_list();
|
const auto& l = json.as_list();
|
||||||
if (l.size() != 2) {
|
if (l.size() != 2) {
|
||||||
throw runtime_error("incorrect range list length");
|
throw std::runtime_error("incorrect range list length");
|
||||||
}
|
}
|
||||||
ret.min = l.at(0)->as_int();
|
ret.min = l.at(0)->as_int();
|
||||||
ret.max = l.at(1)->as_int();
|
ret.max = l.at(1)->as_int();
|
||||||
@@ -79,7 +77,7 @@ phosg::JSON to_json(const parray<parray<IntT, Count2>, Count1>& v) {
|
|||||||
template <typename IntT, size_t Count1, size_t Count2>
|
template <typename IntT, size_t Count1, size_t Count2>
|
||||||
void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1>& ret) {
|
void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1>& ret) {
|
||||||
if (json.size() != Count1) {
|
if (json.size() != Count1) {
|
||||||
throw runtime_error("incorrect array length");
|
throw std::runtime_error("incorrect array length");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < Count1; z++) {
|
for (size_t z = 0; z < Count1; z++) {
|
||||||
from_json_into(json.at(z), ret[z]);
|
from_json_into(json.at(z), ret[z]);
|
||||||
@@ -89,7 +87,7 @@ void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1
|
|||||||
template <typename IntT, size_t Count1, size_t Count2>
|
template <typename IntT, size_t Count1, size_t Count2>
|
||||||
void from_json_into(const phosg::JSON& json, parray<parray<CommonItemSet::Table::Range<IntT>, Count2>, Count1>& ret) {
|
void from_json_into(const phosg::JSON& json, parray<parray<CommonItemSet::Table::Range<IntT>, Count2>, Count1>& ret) {
|
||||||
if (json.size() != Count1) {
|
if (json.size() != Count1) {
|
||||||
throw runtime_error("incorrect array length");
|
throw std::runtime_error("incorrect array length");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < Count1; z++) {
|
for (size_t z = 0; z < Count1; z++) {
|
||||||
from_json_into(json.at(z), ret[z]);
|
from_json_into(json.at(z), ret[z]);
|
||||||
@@ -139,7 +137,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
|||||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||||
try {
|
try {
|
||||||
from_json_into(*dict.at(phosg::name_for_enum(enemy_type)), this->enemy_type_meseta_ranges[enemy_type]);
|
from_json_into(*dict.at(phosg::name_for_enum(enemy_type)), this->enemy_type_meseta_ranges[enemy_type]);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -151,7 +149,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
|||||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||||
try {
|
try {
|
||||||
this->enemy_type_drop_probs[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
this->enemy_type_drop_probs[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -163,7 +161,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
|||||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||||
try {
|
try {
|
||||||
this->enemy_type_item_classes[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
this->enemy_type_item_classes[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -208,13 +206,13 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
|||||||
def.rt_index, phosg::name_for_enum(enemy_type),
|
def.rt_index, phosg::name_for_enum(enemy_type),
|
||||||
meseta_range.min, meseta_range.max, drop_prob, item_class,
|
meseta_range.min, meseta_range.max, drop_prob, item_class,
|
||||||
name_for_common_item_class(item_class));
|
name_for_common_item_class(item_class));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
phosg::fwrite_fmt(stream, " {:02X}:{:<23} ----- ----- ---- --:-------\n",
|
phosg::fwrite_fmt(stream, " {:02X}:{:<23} ----- ----- ---- --:-------\n",
|
||||||
def.rt_index, phosg::name_for_enum(enemy_type));
|
def.rt_index, phosg::name_for_enum(enemy_type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<const char*, 12> base_weapon_type_names = {
|
static const std::array<const char*, 12> base_weapon_type_names = {
|
||||||
"SABER", "SWORD", "DAGGER", "PARTISAN", "SLICER", "HANDGUN", "RIFLE", "MECHGUN", "SHOT", "CANE", "ROD", "WAND"};
|
"SABER", "SWORD", "DAGGER", "PARTISAN", "SLICER", "HANDGUN", "RIFLE", "MECHGUN", "SHOT", "CANE", "ROD", "WAND"};
|
||||||
phosg::fwrite_fmt(stream, "Base weapon config:\n");
|
phosg::fwrite_fmt(stream, "Base weapon config:\n");
|
||||||
phosg::fwrite_fmt(stream, " TYPE PROB [SB AL] FLOORS\n");
|
phosg::fwrite_fmt(stream, " TYPE PROB [SB AL] FLOORS\n");
|
||||||
@@ -295,7 +293,7 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
|||||||
fputc('\n', stream);
|
fputc('\n', stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<const char*, 19> technique_names = {
|
static const std::array<const char*, 19> technique_names = {
|
||||||
"FOIE ",
|
"FOIE ",
|
||||||
"GIFOIE ",
|
"GIFOIE ",
|
||||||
"RAFOIE ",
|
"RAFOIE ",
|
||||||
@@ -392,7 +390,7 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto format_enemy_range_table = [&](const std::unordered_map<EnemyType, Range<uint16_t>>& table) -> std::string {
|
auto format_enemy_range_table = [&](const std::unordered_map<EnemyType, Range<uint16_t>>& table) -> std::string {
|
||||||
string ret = "";
|
std::string ret = "";
|
||||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||||
try {
|
try {
|
||||||
const auto& range = table.at(enemy_type);
|
const auto& range = table.at(enemy_type);
|
||||||
@@ -400,13 +398,13 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
|||||||
ret += ",";
|
ret += ",";
|
||||||
}
|
}
|
||||||
ret += std::format("{}=[{},{}]", phosg::name_for_enum(enemy_type), range.min, range.max);
|
ret += std::format("{}=[{},{}]", phosg::name_for_enum(enemy_type), range.min, range.max);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
auto format_enemy_u8_table = [&](const std::unordered_map<EnemyType, uint8_t>& table) -> std::string {
|
auto format_enemy_u8_table = [&](const std::unordered_map<EnemyType, uint8_t>& table) -> std::string {
|
||||||
string ret = "";
|
std::string ret = "";
|
||||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||||
try {
|
try {
|
||||||
uint8_t value = table.at(enemy_type);
|
uint8_t value = table.at(enemy_type);
|
||||||
@@ -414,7 +412,7 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
|||||||
ret += ",";
|
ret += ",";
|
||||||
}
|
}
|
||||||
ret += std::format("{}={}", phosg::name_for_enum(enemy_type), value);
|
ret += std::format("{}={}", phosg::name_for_enum(enemy_type), value);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -620,7 +618,7 @@ phosg::JSON CommonItemSet::json() const {
|
|||||||
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
||||||
auto table = this->get_table(episode, mode, difficulty, section_id);
|
auto table = this->get_table(episode, mode, difficulty, section_id);
|
||||||
ret.emplace(json_key, table->json(prev_table));
|
ret.emplace(json_key, table->json(prev_table));
|
||||||
} catch (const runtime_error&) {
|
} catch (const std::runtime_error&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,7 +640,7 @@ void CommonItemSet::print(FILE* stream) const {
|
|||||||
name_for_difficulty(difficulty),
|
name_for_difficulty(difficulty),
|
||||||
name_for_section_id(section_id));
|
name_for_section_id(section_id));
|
||||||
table->print(stream);
|
table->print(stream);
|
||||||
} catch (const runtime_error&) {
|
} catch (const std::runtime_error&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,15 +653,15 @@ void CommonItemSet::print_diff(FILE* stream, const CommonItemSet& other) const {
|
|||||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||||
for (const auto& difficulty : ALL_DIFFICULTIES_V234) {
|
for (const auto& difficulty : ALL_DIFFICULTIES_V234) {
|
||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
shared_ptr<const Table> this_table;
|
std::shared_ptr<const Table> this_table;
|
||||||
shared_ptr<const Table> other_table;
|
std::shared_ptr<const Table> other_table;
|
||||||
try {
|
try {
|
||||||
this_table = this->get_table(episode, mode, difficulty, section_id);
|
this_table = this->get_table(episode, mode, difficulty, section_id);
|
||||||
} catch (const runtime_error&) {
|
} catch (const std::runtime_error&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
other_table = other.get_table(episode, mode, difficulty, section_id);
|
other_table = other.get_table(episode, mode, difficulty, section_id);
|
||||||
} catch (const runtime_error&) {
|
} catch (const std::runtime_error&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this_table && !other_table) {
|
if (!this_table && !other_table) {
|
||||||
@@ -789,17 +787,17 @@ std::string CommonItemSet::json_key_for_table(
|
|||||||
token_name_for_difficulty(difficulty), name_for_section_id(section_id));
|
token_name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
std::shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
||||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
||||||
try {
|
try {
|
||||||
return this->tables.at(this->key_for_table(episode, mode, difficulty, section_id));
|
return this->tables.at(this->key_for_table(episode, mode, difficulty, section_id));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
|
throw std::runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
|
||||||
name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), section_id));
|
name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), section_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CommonItemSet::Table> CommonItemSet::get_prev_table(
|
std::shared_ptr<const CommonItemSet::Table> CommonItemSet::get_prev_table(
|
||||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
||||||
if (section_id != 0) {
|
if (section_id != 0) {
|
||||||
// All section IDs are based on the previous, except Viridia
|
// All section IDs are based on the previous, except Viridia
|
||||||
@@ -837,7 +835,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
|||||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||||
auto entry = pt_afs.get(static_cast<size_t>(difficulty) * 10 + section_id);
|
auto entry = pt_afs.get(static_cast<size_t>(difficulty) * 10 + section_id);
|
||||||
phosg::StringReader r(entry.first, entry.second);
|
phosg::StringReader r(entry.first, entry.second);
|
||||||
auto table = make_shared<Table>(r, false, false, Episode::EP1);
|
auto table = std::make_shared<Table>(r, false, false, Episode::EP1);
|
||||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table);
|
||||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table);
|
||||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table);
|
||||||
@@ -861,7 +859,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto r = ct_afs.get_reader(static_cast<size_t>(difficulty) * 10);
|
auto r = ct_afs.get_reader(static_cast<size_t>(difficulty) * 10);
|
||||||
auto table = make_shared<Table>(r, false, false, Episode::EP1);
|
auto table = std::make_shared<Table>(r, false, false, Episode::EP1);
|
||||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table);
|
||||||
}
|
}
|
||||||
@@ -872,7 +870,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
|||||||
GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gsl_data, bool is_big_endian) {
|
GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gsl_data, bool is_big_endian) {
|
||||||
GSLArchive gsl(gsl_data, is_big_endian);
|
GSLArchive gsl(gsl_data, is_big_endian);
|
||||||
|
|
||||||
auto filename_for_table = +[](Episode episode, Difficulty difficulty, uint8_t section_id, bool is_challenge) -> string {
|
auto filename_for_table = +[](Episode episode, Difficulty difficulty, uint8_t section_id, bool is_challenge) -> std::string {
|
||||||
const char* episode_token = "";
|
const char* episode_token = "";
|
||||||
switch (episode) {
|
switch (episode) {
|
||||||
case Episode::EP1:
|
case Episode::EP1:
|
||||||
@@ -885,7 +883,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
|||||||
episode_token = "s";
|
episode_token = "s";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid episode");
|
throw std::runtime_error("invalid episode");
|
||||||
}
|
}
|
||||||
return std::format(
|
return std::format(
|
||||||
"ItemPT{}{}{}{}.rel",
|
"ItemPT{}{}{}{}.rel",
|
||||||
@@ -901,7 +899,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
|||||||
phosg::StringReader r;
|
phosg::StringReader r;
|
||||||
try {
|
try {
|
||||||
r = gsl.get_reader(filename_for_table(episode, difficulty, section_id, false));
|
r = gsl.get_reader(filename_for_table(episode, difficulty, section_id, false));
|
||||||
} catch (const exception&) {
|
} catch (const std::exception&) {
|
||||||
// Fall back to Episode 1 if Episode 4 data is missing
|
// Fall back to Episode 1 if Episode 4 data is missing
|
||||||
if (episode == Episode::EP4) {
|
if (episode == Episode::EP4) {
|
||||||
auto ep1_table = this->tables.at(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id));
|
auto ep1_table = this->tables.at(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id));
|
||||||
@@ -913,7 +911,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto table = make_shared<Table>(r, is_big_endian, true, episode);
|
auto table = std::make_shared<Table>(r, is_big_endian, true, episode);
|
||||||
this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), table);
|
||||||
this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), table);
|
||||||
this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), table);
|
||||||
@@ -923,11 +921,11 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
|||||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||||
try {
|
try {
|
||||||
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
|
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
|
||||||
auto table = make_shared<Table>(r, is_big_endian, true, episode);
|
auto table = std::make_shared<Table>(r, is_big_endian, true, episode);
|
||||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||||
this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table);
|
this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table);
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
// GC NTE doesn't have Ep2 challenge; just skip adding the table
|
// GC NTE doesn't have Ep2 challenge; just skip adding the table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -943,9 +941,9 @@ JSONCommonItemSet::JSONCommonItemSet(const phosg::JSON& json) {
|
|||||||
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
||||||
auto json_key = this->json_key_for_table(episode, mode, difficulty, section_id);
|
auto json_key = this->json_key_for_table(episode, mode, difficulty, section_id);
|
||||||
auto key = this->key_for_table(episode, mode, difficulty, section_id);
|
auto key = this->key_for_table(episode, mode, difficulty, section_id);
|
||||||
this->tables.emplace(key, make_shared<Table>(prev_table, json.at(json_key), episode));
|
this->tables.emplace(key, std::make_shared<Table>(prev_table, json.at(json_key), episode));
|
||||||
} catch (const runtime_error&) {
|
} catch (const std::runtime_error&) {
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,21 +983,21 @@ ToolRandomSet::ToolRandomSet(std::shared_ptr<const std::string> data) : RELFileS
|
|||||||
this->tech_disk_level_table_spec = &r.pget<TableSpec>(r.pget_u32b(specs_offset + 2 * sizeof(uint32_t)));
|
this->tech_disk_level_table_spec = &r.pget<TableSpec>(r.pget_u32b(specs_offset + 2 * sizeof(uint32_t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const uint8_t*, size_t> ToolRandomSet::get_common_recovery_table(size_t index) const {
|
std::pair<const uint8_t*, size_t> ToolRandomSet::get_common_recovery_table(size_t index) const {
|
||||||
return this->get_table<uint8_t>(*this->common_recovery_table_spec, index);
|
return this->get_table<uint8_t>(*this->common_recovery_table_spec, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
std::pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||||
ToolRandomSet::get_rare_recovery_table(size_t index) const {
|
ToolRandomSet::get_rare_recovery_table(size_t index) const {
|
||||||
return this->get_table<WeightTableEntry8>(*this->rare_recovery_table_spec, index);
|
return this->get_table<WeightTableEntry8>(*this->rare_recovery_table_spec, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
std::pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||||
ToolRandomSet::get_tech_disk_table(size_t index) const {
|
ToolRandomSet::get_tech_disk_table(size_t index) const {
|
||||||
return this->get_table<WeightTableEntry8>(*this->tech_disk_table_spec, index);
|
return this->get_table<WeightTableEntry8>(*this->tech_disk_table_spec, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const ToolRandomSet::TechDiskLevelEntry*, size_t>
|
std::pair<const ToolRandomSet::TechDiskLevelEntry*, size_t>
|
||||||
ToolRandomSet::get_tech_disk_level_table(size_t index) const {
|
ToolRandomSet::get_tech_disk_level_table(size_t index) const {
|
||||||
return this->get_table<TechDiskLevelEntry>(*this->tech_disk_level_table_spec, index);
|
return this->get_table<TechDiskLevelEntry>(*this->tech_disk_level_table_spec, index);
|
||||||
}
|
}
|
||||||
@@ -1013,7 +1011,7 @@ std::pair<const WeaponRandomSet::WeightTableEntry8*, size_t>
|
|||||||
WeaponRandomSet::get_weapon_type_table(size_t index) const {
|
WeaponRandomSet::get_weapon_type_table(size_t index) const {
|
||||||
const auto& spec = this->r.pget<TableSpec>(this->offsets->weapon_type_table + index * sizeof(TableSpec));
|
const auto& spec = this->r.pget<TableSpec>(this->offsets->weapon_type_table + index * sizeof(TableSpec));
|
||||||
const auto* data = &this->r.pget<WeightTableEntry8>(spec.offset, spec.entries_per_table * sizeof(WeightTableEntry8));
|
const auto* data = &this->r.pget<WeightTableEntry8>(spec.offset, spec.entries_per_table * sizeof(WeightTableEntry8));
|
||||||
return make_pair(data, spec.entries_per_table);
|
return std::make_pair(data, spec.entries_per_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parray<WeaponRandomSet::WeightTableEntry32, 6>*
|
const parray<WeaponRandomSet::WeightTableEntry32, 6>*
|
||||||
@@ -1055,7 +1053,7 @@ const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_table(
|
|||||||
bool favored,
|
bool favored,
|
||||||
uint8_t section_id) const {
|
uint8_t section_id) const {
|
||||||
if (section_id >= 10) {
|
if (section_id >= 10) {
|
||||||
throw runtime_error("invalid section ID");
|
throw std::runtime_error("invalid section ID");
|
||||||
}
|
}
|
||||||
ProbabilityTable<uint8_t, 100>& table = favored ? tables_favored[section_id] : tables_default[section_id];
|
ProbabilityTable<uint8_t, 100>& table = favored ? tables_favored[section_id] : tables_default[section_id];
|
||||||
if (table.count == 0) {
|
if (table.count == 0) {
|
||||||
|
|||||||
+45
-48
@@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
const char* phosg::name_for_enum<CompressPhase>(CompressPhase v) {
|
const char* phosg::name_for_enum<CompressPhase>(CompressPhase v) {
|
||||||
switch (v) {
|
switch (v) {
|
||||||
@@ -34,13 +32,13 @@ struct WindowIndex {
|
|||||||
const uint8_t* data;
|
const uint8_t* data;
|
||||||
size_t size;
|
size_t size;
|
||||||
size_t offset;
|
size_t offset;
|
||||||
set<size_t, function<bool(size_t, size_t)>> index;
|
std::set<size_t, std::function<bool(size_t, size_t)>> index;
|
||||||
|
|
||||||
WindowIndex(const void* data, size_t size)
|
WindowIndex(const void* data, size_t size)
|
||||||
: data(reinterpret_cast<const uint8_t*>(data)),
|
: data(reinterpret_cast<const uint8_t*>(data)),
|
||||||
size(size),
|
size(size),
|
||||||
offset(0),
|
offset(0),
|
||||||
index(bind(&WindowIndex::set_comparator, this, placeholders::_1, placeholders::_2)) {}
|
index(std::bind(&WindowIndex::set_comparator, this, std::placeholders::_1, std::placeholders::_2)) {}
|
||||||
|
|
||||||
void advance() {
|
void advance() {
|
||||||
if (this->offset >= WindowLength) {
|
if (this->offset >= WindowLength) {
|
||||||
@@ -69,7 +67,7 @@ struct WindowIndex {
|
|||||||
// strings far too much. We can solve this by instead storing the offset of each string as keys in a set and using a
|
// strings far too much. We can solve this by instead storing the offset of each string as keys in a set and using a
|
||||||
// custom comparator to treat them as references to binary strings within the data.
|
// custom comparator to treat them as references to binary strings within the data.
|
||||||
bool set_comparator(size_t a, size_t b) const {
|
bool set_comparator(size_t a, size_t b) const {
|
||||||
size_t max_length = min<size_t>(MaxMatchLength, this->size - max<size_t>(a, b));
|
size_t max_length = std::min<size_t>(MaxMatchLength, this->size - std::max<size_t>(a, b));
|
||||||
size_t end_a = a + max_length;
|
size_t end_a = a + max_length;
|
||||||
for (; a < end_a; a++, b++) {
|
for (; a < end_a; a++, b++) {
|
||||||
uint8_t data_a = static_cast<uint8_t>(this->data[a]);
|
uint8_t data_a = static_cast<uint8_t>(this->data[a]);
|
||||||
@@ -83,7 +81,7 @@ struct WindowIndex {
|
|||||||
return a < b; // Maximum-length match; order them by offset
|
return a < b; // Maximum-length match; order them by offset
|
||||||
};
|
};
|
||||||
|
|
||||||
pair<size_t, size_t> get_best_match() const {
|
std::pair<size_t, size_t> get_best_match() const {
|
||||||
// Find the best match from the index. It's unlikely that we'll get an exact match, so check the entry before the
|
// Find the best match from the index. It's unlikely that we'll get an exact match, so check the entry before the
|
||||||
// upper_bound result too. Note: We use upper_bound rather than lower_bound because in PRS, a backreference can be
|
// upper_bound result too. Note: We use upper_bound rather than lower_bound because in PRS, a backreference can be
|
||||||
// encoded with fewer bits if it's close to the decompression offset, and this makes us pick the latest match by
|
// encoded with fewer bits if it's close to the decompression offset, and this makes us pick the latest match by
|
||||||
@@ -108,7 +106,7 @@ struct WindowIndex {
|
|||||||
match_size = new_match_size;
|
match_size = new_match_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return make_pair(match_offset, match_size);
|
return std::make_pair(match_offset, match_size);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -140,7 +138,7 @@ struct LZSSInterleavedWriter {
|
|||||||
|
|
||||||
void write_control(bool v) {
|
void write_control(bool v) {
|
||||||
if (this->next_control_bit == 0) {
|
if (this->next_control_bit == 0) {
|
||||||
throw logic_error("write_control called with no space to write");
|
throw std::logic_error("write_control called with no space to write");
|
||||||
}
|
}
|
||||||
if (v) {
|
if (v) {
|
||||||
this->buf[0] |= this->next_control_bit;
|
this->buf[0] |= this->next_control_bit;
|
||||||
@@ -208,15 +206,15 @@ struct PRSPathNode {
|
|||||||
size_t to_offset = 0;
|
size_t to_offset = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
std::string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||||
|
|
||||||
vector<PRSPathNode> nodes;
|
std::vector<PRSPathNode> nodes;
|
||||||
nodes.resize(in_size + 1);
|
nodes.resize(in_size + 1);
|
||||||
nodes[0].bits_used = 18; // Stop command: 2 control bits and 2 data bytes
|
nodes[0].bits_used = 18; // Stop command: 2 control bits and 2 data bytes
|
||||||
|
|
||||||
size_t copy_progress_max = 3 * in_size;
|
size_t copy_progress_max = 3 * in_size;
|
||||||
atomic<size_t> copy_progress = 0;
|
std::atomic<size_t> copy_progress = 0;
|
||||||
|
|
||||||
// Populate all possible short copies
|
// Populate all possible short copies
|
||||||
std::thread short_window_thread([&]() -> void {
|
std::thread short_window_thread([&]() -> void {
|
||||||
@@ -361,14 +359,14 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
switch (next_node.from_command_type) {
|
switch (next_node.from_command_type) {
|
||||||
case PRSPathNode::CommandType::LITERAL:
|
case PRSPathNode::CommandType::LITERAL:
|
||||||
if (copy_size != 1) {
|
if (copy_size != 1) {
|
||||||
throw logic_error("incorrect size for LITERAL copy type");
|
throw std::logic_error("incorrect size for LITERAL copy type");
|
||||||
}
|
}
|
||||||
w.write_control(true);
|
w.write_control(true);
|
||||||
w.write_data(in_data[offset]);
|
w.write_data(in_data[offset]);
|
||||||
break;
|
break;
|
||||||
case PRSPathNode::CommandType::SHORT_COPY: {
|
case PRSPathNode::CommandType::SHORT_COPY: {
|
||||||
if (copy_size < 2 || copy_size > 5) {
|
if (copy_size < 2 || copy_size > 5) {
|
||||||
throw logic_error("incorrect size for SHORT_COPY copy type");
|
throw std::logic_error("incorrect size for SHORT_COPY copy type");
|
||||||
}
|
}
|
||||||
uint8_t encoded_size = copy_size - 2;
|
uint8_t encoded_size = copy_size - 2;
|
||||||
w.write_control(false);
|
w.write_control(false);
|
||||||
@@ -383,7 +381,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
}
|
}
|
||||||
case PRSPathNode::CommandType::LONG_COPY: {
|
case PRSPathNode::CommandType::LONG_COPY: {
|
||||||
if (copy_size < 2 || copy_size > 9) {
|
if (copy_size < 2 || copy_size > 9) {
|
||||||
throw logic_error("incorrect size for LONG_COPY copy type");
|
throw std::logic_error("incorrect size for LONG_COPY copy type");
|
||||||
}
|
}
|
||||||
w.write_control(false);
|
w.write_control(false);
|
||||||
w.flush_if_ready();
|
w.flush_if_ready();
|
||||||
@@ -395,7 +393,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
}
|
}
|
||||||
case PRSPathNode::CommandType::EXTENDED_COPY: {
|
case PRSPathNode::CommandType::EXTENDED_COPY: {
|
||||||
if (copy_size < 1 || copy_size > 0x100) {
|
if (copy_size < 1 || copy_size > 0x100) {
|
||||||
throw logic_error("incorrect size for EXTENDED_COPY copy type");
|
throw std::logic_error("incorrect size for EXTENDED_COPY copy type");
|
||||||
}
|
}
|
||||||
w.write_control(false);
|
w.write_control(false);
|
||||||
w.flush_if_ready();
|
w.flush_if_ready();
|
||||||
@@ -407,7 +405,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid copy type in shortest path");
|
throw std::logic_error("invalid copy type in shortest path");
|
||||||
}
|
}
|
||||||
w.flush_if_ready();
|
w.flush_if_ready();
|
||||||
|
|
||||||
@@ -424,11 +422,11 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
return std::move(w.close());
|
return std::move(w.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_compress_optimal(const string& data, ProgressCallback progress_fn) {
|
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn) {
|
||||||
return prs_compress_optimal(data.data(), data.size(), progress_fn);
|
return prs_compress_optimal(data.data(), data.size(), progress_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_compress_pessimal(const void* vdata, size_t size) {
|
std::string prs_compress_pessimal(const void* vdata, size_t size) {
|
||||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(vdata);
|
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(vdata);
|
||||||
|
|
||||||
// The worst possible encoding we can do is a literal byte when no byte with the same value is within the window, or
|
// The worst possible encoding we can do is a literal byte when no byte with the same value is within the window, or
|
||||||
@@ -479,7 +477,7 @@ PRSCompressor::PRSCompressor(
|
|||||||
|
|
||||||
void PRSCompressor::add(const void* data, size_t size) {
|
void PRSCompressor::add(const void* data, size_t size) {
|
||||||
if (this->closed) {
|
if (this->closed) {
|
||||||
throw logic_error("compressor is closed");
|
throw std::logic_error("compressor is closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader r(data, size);
|
phosg::StringReader r(data, size);
|
||||||
@@ -488,7 +486,7 @@ void PRSCompressor::add(const void* data, size_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PRSCompressor::add(const string& data) {
|
void PRSCompressor::add(const std::string& data) {
|
||||||
this->add(data.data(), data.size());
|
this->add(data.data(), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +571,7 @@ void PRSCompressor::advance() {
|
|||||||
this->advance_extended_copy(backreference_offset, best_match_size);
|
this->advance_extended_copy(backreference_offset, best_match_size);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("invalid best match");
|
throw std::logic_error("invalid best match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +619,7 @@ void PRSCompressor::advance_extended_copy(ssize_t offset, size_t size) {
|
|||||||
this->move_forward_data_to_reverse_log(size);
|
this->move_forward_data_to_reverse_log(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
string& PRSCompressor::close() {
|
std::string& PRSCompressor::close() {
|
||||||
if (!this->closed) {
|
if (!this->closed) {
|
||||||
// Advance until all input is consumed
|
// Advance until all input is consumed
|
||||||
while (this->reverse_log.end_offset() < this->input_bytes) {
|
while (this->reverse_log.end_offset() < this->input_bytes) {
|
||||||
@@ -658,23 +656,23 @@ void PRSCompressor::flush_control() {
|
|||||||
this->output.pput_u8(this->control_byte_offset, this->pending_control_bits & 0xFF);
|
this->output.pput_u8(this->control_byte_offset, this->pending_control_bits & 0xFF);
|
||||||
} else {
|
} else {
|
||||||
if (this->control_byte_offset != this->output.size() - 1) {
|
if (this->control_byte_offset != this->output.size() - 1) {
|
||||||
throw logic_error("data written without control bits");
|
throw std::logic_error("data written without control bits");
|
||||||
}
|
}
|
||||||
this->output.str().resize(this->output.str().size() - 1);
|
this->output.str().resize(this->output.str().size() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_compress(const void* vdata, size_t size, ssize_t compression_level, ProgressCallback progress_fn) {
|
std::string prs_compress(const void* vdata, size_t size, ssize_t compression_level, ProgressCallback progress_fn) {
|
||||||
PRSCompressor prs(compression_level, progress_fn);
|
PRSCompressor prs(compression_level, progress_fn);
|
||||||
prs.add(vdata, size);
|
prs.add(vdata, size);
|
||||||
return std::move(prs.close());
|
return std::move(prs.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_compress(const string& data, ssize_t compression_level, ProgressCallback progress_fn) {
|
std::string prs_compress(const std::string& data, ssize_t compression_level, ProgressCallback progress_fn) {
|
||||||
return prs_compress(data.data(), data.size(), compression_level, progress_fn);
|
return prs_compress(data.data(), data.size(), compression_level, progress_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
std::string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||||
|
|
||||||
LZSSInterleavedWriter w;
|
LZSSInterleavedWriter w;
|
||||||
@@ -779,12 +777,12 @@ string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
}
|
}
|
||||||
case PRSPathNode::CommandType::NONE:
|
case PRSPathNode::CommandType::NONE:
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid command type");
|
throw std::logic_error("invalid command type");
|
||||||
}
|
}
|
||||||
w.flush_if_ready();
|
w.flush_if_ready();
|
||||||
|
|
||||||
if (bytes_consumed == 0) {
|
if (bytes_consumed == 0) {
|
||||||
throw logic_error("no input data was consumed");
|
throw std::logic_error("no input data was consumed");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t z = 0; z < bytes_consumed; z++) {
|
for (size_t z = 0; z < bytes_consumed; z++) {
|
||||||
@@ -804,7 +802,7 @@ string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallb
|
|||||||
return std::move(w.close());
|
return std::move(w.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_compress_indexed(const string& data, ProgressCallback progress_fn) {
|
std::string prs_compress_indexed(const std::string& data, ProgressCallback progress_fn) {
|
||||||
return prs_compress_indexed(data.data(), data.size(), progress_fn);
|
return prs_compress_indexed(data.data(), data.size(), progress_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -846,7 +844,7 @@ PRSDecompressResult prs_decompress_with_meta(
|
|||||||
if (allow_unterminated) {
|
if (allow_unterminated) {
|
||||||
return {std::move(w.str()), r.where()};
|
return {std::move(w.str()), r.where()};
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("maximum output size exceeded");
|
throw std::runtime_error("maximum output size exceeded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.put_u8(r.get_u8());
|
w.put_u8(r.get_u8());
|
||||||
@@ -882,14 +880,14 @@ PRSDecompressResult prs_decompress_with_meta(
|
|||||||
// support ranges that cover the current end of the output.
|
// support ranges that cover the current end of the output.
|
||||||
size_t read_offset = w.size() + offset;
|
size_t read_offset = w.size() + offset;
|
||||||
if (read_offset >= w.size()) {
|
if (read_offset >= w.size()) {
|
||||||
throw runtime_error("backreference offset beyond beginning of output");
|
throw std::runtime_error("backreference offset beyond beginning of output");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < count; z++) {
|
for (size_t z = 0; z < count; z++) {
|
||||||
if (max_output_size && w.size() == max_output_size) {
|
if (max_output_size && w.size() == max_output_size) {
|
||||||
if (allow_unterminated) {
|
if (allow_unterminated) {
|
||||||
return {std::move(w.str()), r.where()};
|
return {std::move(w.str()), r.where()};
|
||||||
} else {
|
} else {
|
||||||
throw out_of_range("maximum output size exceeded");
|
throw std::out_of_range("maximum output size exceeded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.put_u8(w.str()[read_offset + z]);
|
w.put_u8(w.str()[read_offset + z]);
|
||||||
@@ -900,16 +898,16 @@ PRSDecompressResult prs_decompress_with_meta(
|
|||||||
return {std::move(w.str()), r.where()};
|
return {std::move(w.str()), r.where()};
|
||||||
}
|
}
|
||||||
|
|
||||||
PRSDecompressResult prs_decompress_with_meta(const string& data, size_t max_output_size, bool allow_unterminated) {
|
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size, bool allow_unterminated) {
|
||||||
return prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
|
return prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_decompress(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
|
std::string prs_decompress(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
|
||||||
auto ret = prs_decompress_with_meta(data, size, max_output_size, allow_unterminated);
|
auto ret = prs_decompress_with_meta(data, size, max_output_size, allow_unterminated);
|
||||||
return std::move(ret.data);
|
return std::move(ret.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
string prs_decompress(const string& data, size_t max_output_size, bool allow_unterminated) {
|
std::string prs_decompress(const std::string& data, size_t max_output_size, bool allow_unterminated) {
|
||||||
auto ret = prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
|
auto ret = prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
|
||||||
return std::move(ret.data);
|
return std::move(ret.data);
|
||||||
}
|
}
|
||||||
@@ -945,7 +943,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
|
|||||||
|
|
||||||
size_t read_offset = ret + offset;
|
size_t read_offset = ret + offset;
|
||||||
if (read_offset >= ret) {
|
if (read_offset >= ret) {
|
||||||
throw runtime_error("backreference offset beyond beginning of output");
|
throw std::runtime_error("backreference offset beyond beginning of output");
|
||||||
}
|
}
|
||||||
ret += count;
|
ret += count;
|
||||||
}
|
}
|
||||||
@@ -954,7 +952,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
|
|||||||
if (allow_unterminated) {
|
if (allow_unterminated) {
|
||||||
return max_output_size;
|
return max_output_size;
|
||||||
} else {
|
} else {
|
||||||
throw out_of_range("maximum output size exceeded");
|
throw std::out_of_range("maximum output size exceeded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -962,7 +960,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t prs_decompress_size(const string& data, size_t max_output_size, bool allow_unterminated) {
|
size_t prs_decompress_size(const std::string& data, size_t max_output_size, bool allow_unterminated) {
|
||||||
return prs_decompress_size(data.data(), data.size(), max_output_size, allow_unterminated);
|
return prs_decompress_size(data.data(), data.size(), max_output_size, allow_unterminated);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,7 +1013,7 @@ void prs_disassemble(FILE* stream, const void* data, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (read_offset >= output_bytes) {
|
if (read_offset >= output_bytes) {
|
||||||
throw runtime_error("backreference offset beyond beginning of output");
|
throw std::runtime_error("backreference offset beyond beginning of output");
|
||||||
}
|
}
|
||||||
output_bytes += count;
|
output_bytes += count;
|
||||||
}
|
}
|
||||||
@@ -1043,11 +1041,10 @@ struct BC0PathNode {
|
|||||||
size_t to_offset = 0;
|
size_t to_offset = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
string bc0_compress_optimal(
|
std::string bc0_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||||
const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
|
||||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||||
|
|
||||||
vector<BC0PathNode> nodes;
|
std::vector<BC0PathNode> nodes;
|
||||||
nodes.resize(in_size + 1);
|
nodes.resize(in_size + 1);
|
||||||
nodes[0].bits_used = 0;
|
nodes[0].bits_used = 0;
|
||||||
|
|
||||||
@@ -1140,11 +1137,11 @@ string bc0_compress_optimal(
|
|||||||
return std::move(w.close());
|
return std::move(w.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
string bc0_compress(const string& data, ProgressCallback progress_fn) {
|
std::string bc0_compress(const std::string& data, ProgressCallback progress_fn) {
|
||||||
return bc0_compress(data.data(), data.size(), progress_fn);
|
return bc0_compress(data.data(), data.size(), progress_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
std::string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||||
|
|
||||||
LZSSInterleavedWriter w;
|
LZSSInterleavedWriter w;
|
||||||
@@ -1180,7 +1177,7 @@ string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback prog
|
|||||||
return std::move(w.close());
|
return std::move(w.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
string bc0_encode(const void* in_data_v, size_t in_size) {
|
std::string bc0_encode(const void* in_data_v, size_t in_size) {
|
||||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||||
|
|
||||||
LZSSInterleavedWriter w;
|
LZSSInterleavedWriter w;
|
||||||
@@ -1197,11 +1194,11 @@ string bc0_encode(const void* in_data_v, size_t in_size) {
|
|||||||
// the output buffer. It is unlikely that this can be usefully exploited (e.g. for RCE) because the output pointer is
|
// the output buffer. It is unlikely that this can be usefully exploited (e.g. for RCE) because the output pointer is
|
||||||
// loaded from memory before every byte is written, so we cannot change the output pointer to any arbitrary address.
|
// loaded from memory before every byte is written, so we cannot change the output pointer to any arbitrary address.
|
||||||
|
|
||||||
string bc0_decompress(const string& data) {
|
std::string bc0_decompress(const std::string& data) {
|
||||||
return bc0_decompress(data.data(), data.size());
|
return bc0_decompress(data.data(), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
string bc0_decompress(const void* data, size_t size) {
|
std::string bc0_decompress(const void* data, size_t size) {
|
||||||
phosg::StringReader r(data, size);
|
phosg::StringReader r(data, size);
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
|
|
||||||
@@ -1265,7 +1262,7 @@ string bc0_decompress(const void* data, size_t size) {
|
|||||||
return std::move(w.str());
|
return std::move(w.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void bc0_disassemble(FILE* stream, const string& data) {
|
void bc0_disassemble(FILE* stream, const std::string& data) {
|
||||||
bc0_disassemble(stream, data.data(), data.size());
|
bc0_disassemble(stream, data.data(), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-28
@@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static const uint32_t primes1[] = {
|
static const uint32_t primes1[] = {
|
||||||
0x65, 0x67, 0x6B, 0x6D, 0x71, 0x7F, 0x83, 0x89, 0x8B, 0x95, 0x97, 0x9D, 0xA3, 0xA7, 0xAD, 0xB3, 0xB5, 0xBF, 0xC1,
|
0x65, 0x67, 0x6B, 0x6D, 0x71, 0x7F, 0x83, 0x89, 0x8B, 0x95, 0x97, 0x9D, 0xA3, 0xA7, 0xAD, 0xB3, 0xB5, 0xBF, 0xC1,
|
||||||
0xC5, 0xC7, 0xD3, 0xDF, 0xE3, 0xE5, 0xE9, 0xEF, 0xF1, 0xFB, 0x101, 0x107, 0x10D, 0x10F, 0x115, 0x119, 0x11B, 0x125,
|
0xC5, 0xC7, 0xD3, 0xDF, 0xE3, 0xE5, 0xE9, 0xEF, 0xF1, 0xFB, 0x101, 0x107, 0x10D, 0x10F, 0x115, 0x119, 0x11B, 0x125,
|
||||||
@@ -735,10 +733,10 @@ static const uint32_t primes3[] = {
|
|||||||
static constexpr size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
|
static constexpr size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
|
||||||
|
|
||||||
static bool check_prime3(uint64_t prime3) {
|
static bool check_prime3(uint64_t prime3) {
|
||||||
static vector<bool> primes3_set;
|
static std::vector<bool> primes3_set;
|
||||||
static mutex primes3_init_mutex;
|
static std::mutex primes3_init_mutex;
|
||||||
if (primes3_set.empty()) {
|
if (primes3_set.empty()) {
|
||||||
lock_guard g(primes3_init_mutex);
|
std::lock_guard g(primes3_init_mutex);
|
||||||
if (primes3_set.empty()) {
|
if (primes3_set.empty()) {
|
||||||
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
|
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
|
||||||
primes3_set.resize(primes3_set_size, false);
|
primes3_set.resize(primes3_set_size, false);
|
||||||
@@ -790,7 +788,7 @@ static char replace_char_reverse(char ch) {
|
|||||||
|
|
||||||
static constexpr uint64_t INVALID_PRODUCT = 0xFFFFFFFFFFFFFFFF;
|
static constexpr uint64_t INVALID_PRODUCT = 0xFFFFFFFFFFFFFFFF;
|
||||||
|
|
||||||
static uint64_t decode_dc_serial_number_str(const string& s) {
|
static uint64_t decode_dc_serial_number_str(const std::string& s) {
|
||||||
if (s.size() != 8) {
|
if (s.size() != 8) {
|
||||||
return INVALID_PRODUCT;
|
return INVALID_PRODUCT;
|
||||||
}
|
}
|
||||||
@@ -828,20 +826,20 @@ static uint32_t encode_dc_serial_number_int(uint32_t v) {
|
|||||||
(replace_nybble_reverse(v));
|
(replace_nybble_reverse(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
static pair<size_t, size_t> compute_offset1_and_limit1(uint8_t domain, uint8_t subdomain) {
|
static std::pair<size_t, size_t> compute_offset1_and_limit1(uint8_t domain, uint8_t subdomain) {
|
||||||
if (domain > 2) {
|
if (domain > 2) {
|
||||||
return make_pair(0, 0);
|
return std::make_pair(0, 0);
|
||||||
}
|
}
|
||||||
size_t domain_base = domain * 30;
|
size_t domain_base = domain * 30;
|
||||||
if (subdomain != 0xFF) {
|
if (subdomain != 0xFF) {
|
||||||
size_t subdomain_base = domain_base + (subdomain % 3);
|
size_t subdomain_base = domain_base + (subdomain % 3);
|
||||||
return make_pair(subdomain_base, subdomain_base + 1);
|
return std::make_pair(subdomain_base, subdomain_base + 1);
|
||||||
} else {
|
} else {
|
||||||
return make_pair(domain_base, domain_base + 3);
|
return std::make_pair(domain_base, domain_base + 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dc_serial_number_is_valid_slow(const string& s, uint8_t domain, uint8_t subdomain) {
|
bool dc_serial_number_is_valid_slow(const std::string& s, uint8_t domain, uint8_t subdomain) {
|
||||||
uint64_t serial_number = decode_dc_serial_number_str(s);
|
uint64_t serial_number = decode_dc_serial_number_str(s);
|
||||||
if (serial_number == INVALID_PRODUCT) {
|
if (serial_number == INVALID_PRODUCT) {
|
||||||
return false;
|
return false;
|
||||||
@@ -890,7 +888,7 @@ bool decoded_dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t doma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dc_serial_number_is_valid_fast(const string& s, uint8_t domain, uint8_t subdomain) {
|
bool dc_serial_number_is_valid_fast(const std::string& s, uint8_t domain, uint8_t subdomain) {
|
||||||
uint64_t serial_number = decode_dc_serial_number_str(s);
|
uint64_t serial_number = decode_dc_serial_number_str(s);
|
||||||
if (serial_number == INVALID_PRODUCT) {
|
if (serial_number == INVALID_PRODUCT) {
|
||||||
return false;
|
return false;
|
||||||
@@ -902,7 +900,7 @@ bool dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t domain, uint
|
|||||||
return decoded_dc_serial_number_is_valid_fast(decode_dc_serial_number_int(serial_number), domain, subdomain);
|
return decoded_dc_serial_number_is_valid_fast(decode_dc_serial_number_int(serial_number), domain, subdomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
std::string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||||
size_t offset1, limit1;
|
size_t offset1, limit1;
|
||||||
if (domain == 0) {
|
if (domain == 0) {
|
||||||
offset1 = 0x00;
|
offset1 = 0x00;
|
||||||
@@ -914,7 +912,7 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
|||||||
offset1 = 0x3C;
|
offset1 = 0x3C;
|
||||||
limit1 = 0x3F;
|
limit1 = 0x3F;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid domain");
|
throw std::runtime_error("invalid domain");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t det1 = (subdomain == 0xFF) ? phosg::random_object<uint32_t>() : subdomain;
|
size_t det1 = (subdomain == 0xFF) ? phosg::random_object<uint32_t>() : subdomain;
|
||||||
@@ -922,23 +920,23 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
|||||||
size_t index2 = phosg::random_object<uint32_t>() % num_primes2;
|
size_t index2 = phosg::random_object<uint32_t>() % num_primes2;
|
||||||
size_t index3 = phosg::random_object<uint32_t>() % num_primes3;
|
size_t index3 = phosg::random_object<uint32_t>() % num_primes3;
|
||||||
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
|
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
|
||||||
string s = std::format("{:08X}", value);
|
std::string s = std::format("{:08X}", value);
|
||||||
|
|
||||||
string ret;
|
std::string ret;
|
||||||
for (char ch : s) {
|
for (char ch : s) {
|
||||||
ret.push_back(replace_char_reverse(ch));
|
ret.push_back(replace_char_reverse(ch));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) {
|
std::unordered_map<uint32_t, std::string> generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) {
|
||||||
DCSerialNumberIterator iter;
|
DCSerialNumberIterator iter;
|
||||||
|
|
||||||
if (domain < 3) {
|
if (domain < 3) {
|
||||||
iter.domain = domain;
|
iter.domain = domain;
|
||||||
iter.end_domain = domain + 1;
|
iter.end_domain = domain + 1;
|
||||||
} else if (domain != 0xFF) {
|
} else if (domain != 0xFF) {
|
||||||
throw runtime_error("invalid domain");
|
throw std::runtime_error("invalid domain");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subdomain < 3) {
|
if (subdomain < 3) {
|
||||||
@@ -946,11 +944,11 @@ unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, u
|
|||||||
iter.start_subdomain = subdomain;
|
iter.start_subdomain = subdomain;
|
||||||
iter.end_subdomain = subdomain + 1;
|
iter.end_subdomain = subdomain + 1;
|
||||||
} else if (subdomain != 0xFF) {
|
} else if (subdomain != 0xFF) {
|
||||||
throw runtime_error("invalid subdomain");
|
throw std::runtime_error("invalid subdomain");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t serial_number;
|
uint32_t serial_number;
|
||||||
unordered_map<uint32_t, string> ret;
|
std::unordered_map<uint32_t, std::string> ret;
|
||||||
while ((serial_number = iter.next()) != 0) {
|
while ((serial_number = iter.next()) != 0) {
|
||||||
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
|
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
|
||||||
if (iter.index3 == 0) {
|
if (iter.index3 == 0) {
|
||||||
@@ -1015,7 +1013,7 @@ void dc_serial_number_speed_test(uint64_t seed) {
|
|||||||
size_t num_disagreements = 0;
|
size_t num_disagreements = 0;
|
||||||
static constexpr size_t count = 0x1000;
|
static constexpr size_t count = 0x1000;
|
||||||
for (size_t z = 0; z < count; z++) {
|
for (size_t z = 0; z < count; z++) {
|
||||||
string s = std::format("{:08X}", crypt.next());
|
std::string s = std::format("{:08X}", crypt.next());
|
||||||
|
|
||||||
uint64_t start = phosg::now();
|
uint64_t start = phosg::now();
|
||||||
bool is_valid_fast = dc_serial_number_is_valid_fast(s, 1, 0xFF);
|
bool is_valid_fast = dc_serial_number_is_valid_fast(s, 1, 0xFF);
|
||||||
@@ -1041,7 +1039,8 @@ void dc_serial_number_speed_test(uint64_t seed) {
|
|||||||
phosg::fwrite_fmt(stderr, "Disagreements: {}\n", num_disagreements);
|
phosg::fwrite_fmt(stderr, "Disagreements: {}\n", num_disagreements);
|
||||||
}
|
}
|
||||||
|
|
||||||
string decrypt_dp_address_jpn(const string& executable, const string& values, const string& indexes) {
|
std::string decrypt_dp_address_jpn(
|
||||||
|
const std::string& executable, const std::string& values, const std::string& indexes) {
|
||||||
phosg::StringReader values_r(values);
|
phosg::StringReader values_r(values);
|
||||||
phosg::StringReader indexes_r(indexes);
|
phosg::StringReader indexes_r(indexes);
|
||||||
|
|
||||||
@@ -1056,7 +1055,7 @@ string decrypt_dp_address_jpn(const string& executable, const string& values, co
|
|||||||
fixup_offset += (fixup_steps_r.get_u8() << 2);
|
fixup_offset += (fixup_steps_r.get_u8() << 2);
|
||||||
fixup_steps_r.skip(1);
|
fixup_steps_r.skip(1);
|
||||||
if (fixup_offset + 4 > decrypted.compressed_data.size()) {
|
if (fixup_offset + 4 > decrypted.compressed_data.size()) {
|
||||||
throw runtime_error("fixup beyond end of compressed data");
|
throw std::runtime_error("fixup beyond end of compressed data");
|
||||||
}
|
}
|
||||||
*reinterpret_cast<le_uint32_t*>(decrypted.compressed_data.data() + fixup_offset) = fixup_values_r.get_u32l();
|
*reinterpret_cast<le_uint32_t*>(decrypted.compressed_data.data() + fixup_offset) = fixup_values_r.get_u32l();
|
||||||
}
|
}
|
||||||
@@ -1064,10 +1063,10 @@ string decrypt_dp_address_jpn(const string& executable, const string& values, co
|
|||||||
return prs_decompress(decrypted.compressed_data);
|
return prs_decompress(decrypted.compressed_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptedDCv2Executables encrypt_dp_address_jpn(const string& executable, const string& indexes) {
|
EncryptedDCv2Executables encrypt_dp_address_jpn(const std::string& executable, const std::string& indexes) {
|
||||||
EncryptedDCv2Executables ret;
|
EncryptedDCv2Executables ret;
|
||||||
|
|
||||||
string compressed = prs_compress(executable);
|
std::string compressed = prs_compress(executable);
|
||||||
ret.executable = encrypt_pr2_data<false>(compressed, executable.size(), phosg::random_object<uint32_t>() & 0x7FFFFF7F);
|
ret.executable = encrypt_pr2_data<false>(compressed, executable.size(), phosg::random_object<uint32_t>() & 0x7FFFFF7F);
|
||||||
|
|
||||||
phosg::StringReader indexes_r(indexes);
|
phosg::StringReader indexes_r(indexes);
|
||||||
@@ -1079,12 +1078,12 @@ EncryptedDCv2Executables encrypt_dp_address_jpn(const string& executable, const
|
|||||||
|
|
||||||
std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_key) {
|
std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_key) {
|
||||||
if (data.size() & 3) {
|
if (data.size() & 3) {
|
||||||
throw runtime_error("size is not a multiple of 4");
|
throw std::runtime_error("size is not a multiple of 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader r(data);
|
phosg::StringReader r(data);
|
||||||
if (mask_key < 0) {
|
if (mask_key < 0) {
|
||||||
unordered_map<uint32_t, size_t> key_freq;
|
std::unordered_map<uint32_t, size_t> key_freq;
|
||||||
while (!r.eof()) {
|
while (!r.eof()) {
|
||||||
key_freq[r.get_u32l()] += 1;
|
key_freq[r.get_u32l()] += 1;
|
||||||
}
|
}
|
||||||
@@ -1096,7 +1095,7 @@ std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_ke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mask_key < 0) {
|
if (mask_key < 0) {
|
||||||
throw runtime_error("cannot determine mask key");
|
throw std::runtime_error("cannot determine mask key");
|
||||||
}
|
}
|
||||||
phosg::log_info_f("Determined {:08X} to be the most likely mask key", mask_key);
|
phosg::log_info_f("Determined {:08X} to be the most likely mask key", mask_key);
|
||||||
r.go(0);
|
r.go(0);
|
||||||
|
|||||||
+8
-10
@@ -14,9 +14,7 @@
|
|||||||
#include "NetworkAddresses.hh"
|
#include "NetworkAddresses.hh"
|
||||||
#include "ServerState.hh"
|
#include "ServerState.hh"
|
||||||
|
|
||||||
using namespace std;
|
DNSServer::DNSServer(std::shared_ptr<ServerState> state)
|
||||||
|
|
||||||
DNSServer::DNSServer(shared_ptr<ServerState> state)
|
|
||||||
: state(state) {}
|
: state(state) {}
|
||||||
|
|
||||||
void DNSServer::listen(const std::string& addr, int port) {
|
void DNSServer::listen(const std::string& addr, int port) {
|
||||||
@@ -25,15 +23,15 @@ void DNSServer::listen(const std::string& addr, int port) {
|
|||||||
}
|
}
|
||||||
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
||||||
asio::ip::udp::endpoint endpoint(asio_addr, port);
|
asio::ip::udp::endpoint endpoint(asio_addr, port);
|
||||||
auto sock = make_shared<asio::ip::udp::socket>(*this->state->io_context, endpoint);
|
auto sock = std::make_shared<asio::ip::udp::socket>(*this->state->io_context, endpoint);
|
||||||
this->sockets.emplace(sock);
|
this->sockets.emplace(sock);
|
||||||
|
|
||||||
asio::co_spawn(*this->state->io_context, this->dns_server_task(sock), asio::detached);
|
asio::co_spawn(*this->state->io_context, this->dns_server_task(sock), asio::detached);
|
||||||
}
|
}
|
||||||
|
|
||||||
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
|
std::string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
|
||||||
if (size < 0x0C) {
|
if (size < 0x0C) {
|
||||||
throw invalid_argument("query too small");
|
throw std::invalid_argument("query too small");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* data = reinterpret_cast<const char*>(vdata);
|
const char* data = reinterpret_cast<const char*>(vdata);
|
||||||
@@ -41,7 +39,7 @@ string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t re
|
|||||||
|
|
||||||
phosg::be_uint32_t be_resolved_address = resolved_address;
|
phosg::be_uint32_t be_resolved_address = resolved_address;
|
||||||
|
|
||||||
string response;
|
std::string response;
|
||||||
response.append(data, 2);
|
response.append(data, 2);
|
||||||
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
||||||
response.append(&data[12], name_len);
|
response.append(&data[12], name_len);
|
||||||
@@ -50,13 +48,13 @@ string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t re
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
string DNSServer::response_for_query(const string& query, uint32_t resolved_address) {
|
std::string DNSServer::response_for_query(const std::string& query, uint32_t resolved_address) {
|
||||||
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
|
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::socket> sock) {
|
asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::socket> sock) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
string input(2048, 0);
|
std::string input(2048, 0);
|
||||||
asio::ip::udp::endpoint sender_ep;
|
asio::ip::udp::endpoint sender_ep;
|
||||||
size_t bytes = co_await sock->async_receive_from(asio::buffer(input), sender_ep, asio::use_awaitable);
|
size_t bytes = co_await sock->async_receive_from(asio::buffer(input), sender_ep, asio::use_awaitable);
|
||||||
uint32_t sender_addr = ipv4_addr_for_asio_addr(sender_ep.address());
|
uint32_t sender_addr = ipv4_addr_for_asio_addr(sender_ep.address());
|
||||||
@@ -69,7 +67,7 @@ asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::
|
|||||||
uint32_t connect_address = is_local_address(sender_addr)
|
uint32_t connect_address = is_local_address(sender_addr)
|
||||||
? this->state->local_address
|
? this->state->local_address
|
||||||
: this->state->external_address;
|
: this->state->external_address;
|
||||||
string response = this->response_for_query(input, connect_address);
|
std::string response = this->response_for_query(input, connect_address);
|
||||||
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
|
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-13
@@ -18,15 +18,13 @@
|
|||||||
#include "Compression.hh"
|
#include "Compression.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
|
||||||
using namespace std;
|
DOLFileIndex::DOLFileIndex(const std::string& directory) {
|
||||||
|
|
||||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
|
||||||
if (!std::filesystem::is_directory(directory)) {
|
if (!std::filesystem::is_directory(directory)) {
|
||||||
client_functions_log.info_f("DOL file directory is missing");
|
client_functions_log.info_f("DOL file directory is missing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto menu = make_shared<Menu>(MenuID::PROGRAMS, "Programs");
|
auto menu = std::make_shared<Menu>(MenuID::PROGRAMS, "Programs");
|
||||||
this->menu = menu;
|
this->menu = menu;
|
||||||
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||||
|
|
||||||
@@ -43,17 +41,17 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
|||||||
if (!is_dol && !is_compressed_dol) {
|
if (!is_dol && !is_compressed_dol) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4));
|
std::string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto dol = make_shared<File>();
|
auto dol = std::make_shared<File>();
|
||||||
dol->menu_item_id = next_menu_item_id++;
|
dol->menu_item_id = next_menu_item_id++;
|
||||||
dol->name = name;
|
dol->name = name;
|
||||||
|
|
||||||
string path = directory + "/" + filename;
|
std::string path = directory + "/" + filename;
|
||||||
string file_data = phosg::load_file(path);
|
std::string file_data = phosg::load_file(path);
|
||||||
|
|
||||||
string description;
|
std::string description;
|
||||||
if (is_compressed_dol) {
|
if (is_compressed_dol) {
|
||||||
size_t decompressed_size = prs_decompress_size(file_data);
|
size_t decompressed_size = prs_decompress_size(file_data);
|
||||||
|
|
||||||
@@ -66,8 +64,8 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
|||||||
}
|
}
|
||||||
dol->data = std::move(w.str());
|
dol->data = std::move(w.str());
|
||||||
|
|
||||||
string compressed_size_str = phosg::format_size(file_data.size());
|
std::string compressed_size_str = phosg::format_size(file_data.size());
|
||||||
string decompressed_size_str = phosg::format_size(decompressed_size);
|
std::string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||||
client_functions_log.debug_f("Loaded compressed DOL file {} ({} -> {})",
|
client_functions_log.debug_f("Loaded compressed DOL file {} ({} -> {})",
|
||||||
dol->name, compressed_size_str, decompressed_size_str);
|
dol->name, compressed_size_str, decompressed_size_str);
|
||||||
description = std::format("$C6{}$C7\n{}\n{} (orig)", dol->name, compressed_size_str, decompressed_size_str);
|
description = std::format("$C6{}$C7\n{}\n{} (orig)", dol->name, compressed_size_str, decompressed_size_str);
|
||||||
@@ -82,7 +80,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
|||||||
}
|
}
|
||||||
dol->data = std::move(w.str());
|
dol->data = std::move(w.str());
|
||||||
|
|
||||||
string size_str = phosg::format_size(dol->data.size());
|
std::string size_str = phosg::format_size(dol->data.size());
|
||||||
client_functions_log.debug_f("Loaded DOL file {} ({})", filename, size_str);
|
client_functions_log.debug_f("Loaded DOL file {} ({})", filename, size_str);
|
||||||
description = std::format("$C6{}$C7\n{}", dol->name, size_str);
|
description = std::format("$C6{}$C7\n{}", dol->name, size_str);
|
||||||
}
|
}
|
||||||
@@ -90,7 +88,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
|||||||
this->item_id_to_file.emplace_back(dol);
|
this->item_id_to_file.emplace_back(dol);
|
||||||
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
client_functions_log.warning_f("Failed to load DOL file {}: {}", filename, e.what());
|
client_functions_log.warning_f("Failed to load DOL file {}: {}", filename, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-52
@@ -22,12 +22,10 @@
|
|||||||
#include "ReceiveSubcommands.hh"
|
#include "ReceiveSubcommands.hh"
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
static std::string random_name() {
|
||||||
|
std::string ret;
|
||||||
static string random_name() {
|
|
||||||
string ret;
|
|
||||||
size_t length = (phosg::random_object<size_t>() % 12) + 4;
|
size_t length = (phosg::random_object<size_t>() % 12) + 4;
|
||||||
static const string alphabet = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890-+<>:\"\',.";
|
static const std::string alphabet = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890-+<>:\"\',.";
|
||||||
while (ret.size() < length) {
|
while (ret.size() < length) {
|
||||||
ret.push_back(alphabet[phosg::random_object<size_t>() % alphabet.size()]);
|
ret.push_back(alphabet[phosg::random_object<size_t>() % alphabet.size()]);
|
||||||
}
|
}
|
||||||
@@ -87,40 +85,40 @@ DownloadSession::DownloadSession(
|
|||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
case Version::DC_V2:
|
case Version::DC_V2:
|
||||||
if (this->serial_number2 == 0 || this->serial_number == 0 || this->access_key.empty()) {
|
if (this->serial_number2 == 0 || this->serial_number == 0 || this->access_key.empty()) {
|
||||||
throw runtime_error("missing credentials");
|
throw std::runtime_error("missing credentials");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::PC_V2:
|
case Version::PC_V2:
|
||||||
if (this->serial_number == 0 || this->access_key.empty()) {
|
if (this->serial_number == 0 || this->access_key.empty()) {
|
||||||
throw runtime_error("missing credentials");
|
throw std::runtime_error("missing credentials");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::GC_V3:
|
case Version::GC_V3:
|
||||||
if (this->serial_number == 0 || this->access_key.empty() || this->password.empty()) {
|
if (this->serial_number == 0 || this->access_key.empty() || this->password.empty()) {
|
||||||
throw runtime_error("missing credentials");
|
throw std::runtime_error("missing credentials");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::XB_V3:
|
case Version::XB_V3:
|
||||||
if (this->xb_gamertag.empty() || this->xb_user_id == 0 || this->xb_account_id == 0) {
|
if (this->xb_gamertag.empty() || this->xb_user_id == 0 || this->xb_account_id == 0) {
|
||||||
throw runtime_error("missing credentials");
|
throw std::runtime_error("missing credentials");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
if (this->username.empty() || this->password.empty()) {
|
if (this->username.empty() || this->password.empty()) {
|
||||||
throw runtime_error("missing credentials");
|
throw std::runtime_error("missing credentials");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unsupported version");
|
throw std::runtime_error("unsupported version");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->character->inventory.language = language;
|
this->character->inventory.language = language;
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> DownloadSession::run() {
|
asio::awaitable<void> DownloadSession::run() {
|
||||||
string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
std::string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
||||||
this->log.info_f("Connecting to {}", netloc_str);
|
this->log.info_f("Connecting to {}", netloc_str);
|
||||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
||||||
this->channel = SocketChannel::create(
|
this->channel = SocketChannel::create(
|
||||||
this->io_context,
|
this->io_context,
|
||||||
std::move(sock),
|
std::move(sock),
|
||||||
@@ -151,7 +149,7 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
|||||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||||
ret.access_key.encode(this->access_key);
|
ret.access_key.encode(this->access_key);
|
||||||
ret.serial_number2.encode(std::format("{:08X}", this->serial_number2));
|
ret.serial_number2.encode(std::format("{:08X}", this->serial_number2));
|
||||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
ret.login_character_name.encode(this->character->disp.visual.name.decode());
|
||||||
this->channel->send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93));
|
this->channel->send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93));
|
||||||
|
|
||||||
} else if (is_v2(this->version)) {
|
} else if (is_v2(this->version)) {
|
||||||
@@ -166,7 +164,7 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
|||||||
ret.access_key.encode(this->access_key);
|
ret.access_key.encode(this->access_key);
|
||||||
ret.serial_number2 = ret.serial_number;
|
ret.serial_number2 = ret.serial_number;
|
||||||
ret.access_key2 = ret.access_key;
|
ret.access_key2 = ret.access_key;
|
||||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
ret.login_character_name.encode(this->character->disp.visual.name.decode());
|
||||||
size_t data_size = extended
|
size_t data_size = extended
|
||||||
? ((this->version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_GC_9D))
|
? ((this->version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_GC_9D))
|
||||||
: sizeof(C_Login_DC_PC_GC_9D);
|
: sizeof(C_Login_DC_PC_GC_9D);
|
||||||
@@ -184,7 +182,7 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
|||||||
ret.access_key.encode(this->access_key);
|
ret.access_key.encode(this->access_key);
|
||||||
ret.serial_number2 = ret.serial_number;
|
ret.serial_number2 = ret.serial_number;
|
||||||
ret.access_key2 = ret.access_key;
|
ret.access_key2 = ret.access_key;
|
||||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
ret.login_character_name.encode(this->character->disp.visual.name.decode());
|
||||||
ret.client_config = this->client_config;
|
ret.client_config = this->client_config;
|
||||||
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_PC_GC_9E));
|
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_PC_GC_9E));
|
||||||
|
|
||||||
@@ -200,7 +198,7 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
|||||||
ret.access_key.encode(std::format("{:016X}", this->xb_user_id));
|
ret.access_key.encode(std::format("{:016X}", this->xb_user_id));
|
||||||
ret.serial_number2 = ret.serial_number;
|
ret.serial_number2 = ret.serial_number;
|
||||||
ret.access_key2 = ret.access_key;
|
ret.access_key2 = ret.access_key;
|
||||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
ret.login_character_name.encode(this->character->disp.visual.name.decode());
|
||||||
ret.xb_netloc.internal_ipv4_address = phosg::random_object<uint32_t>();
|
ret.xb_netloc.internal_ipv4_address = phosg::random_object<uint32_t>();
|
||||||
ret.xb_netloc.external_ipv4_address = phosg::random_object<uint32_t>();
|
ret.xb_netloc.external_ipv4_address = phosg::random_object<uint32_t>();
|
||||||
ret.xb_netloc.port = 9500;
|
ret.xb_netloc.port = 9500;
|
||||||
@@ -214,7 +212,7 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
|||||||
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_DC_PC_GC_9D));
|
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_DC_PC_GC_9D));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unsupported version");
|
throw std::runtime_error("unsupported version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +222,13 @@ void DownloadSession::send_61_98(bool is_98) {
|
|||||||
if (is_v1(this->version)) {
|
if (is_v1(this->version)) {
|
||||||
C_CharacterData_DCv1_61_98 ret;
|
C_CharacterData_DCv1_61_98 ret;
|
||||||
ret.inventory = this->character->inventory;
|
ret.inventory = this->character->inventory;
|
||||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
ret.disp = convert_player_disp_data<PlayerDispDataV123, PlayerDispDataV4>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
||||||
this->channel->send(command, 0x01, ret);
|
this->channel->send(command, 0x01, ret);
|
||||||
|
|
||||||
} else if (this->version == Version::DC_V2) {
|
} else if (this->version == Version::DC_V2) {
|
||||||
C_CharacterData_DCv2_61_98 ret;
|
C_CharacterData_DCv2_61_98 ret;
|
||||||
ret.inventory = this->character->inventory;
|
ret.inventory = this->character->inventory;
|
||||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
ret.disp = convert_player_disp_data<PlayerDispDataV123, PlayerDispDataV4>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
||||||
ret.records.challenge = this->character->challenge_records;
|
ret.records.challenge = this->character->challenge_records;
|
||||||
ret.records.battle = this->character->battle_records;
|
ret.records.battle = this->character->battle_records;
|
||||||
ret.choice_search_config = this->character->choice_search_config;
|
ret.choice_search_config = this->character->choice_search_config;
|
||||||
@@ -239,7 +237,7 @@ void DownloadSession::send_61_98(bool is_98) {
|
|||||||
} else if (this->version == Version::PC_V2) {
|
} else if (this->version == Version::PC_V2) {
|
||||||
C_CharacterData_PC_61_98 ret;
|
C_CharacterData_PC_61_98 ret;
|
||||||
ret.inventory = this->character->inventory;
|
ret.inventory = this->character->inventory;
|
||||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
ret.disp = convert_player_disp_data<PlayerDispDataV123, PlayerDispDataV4>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
||||||
ret.records.challenge = this->character->challenge_records;
|
ret.records.challenge = this->character->challenge_records;
|
||||||
ret.records.battle = this->character->battle_records;
|
ret.records.battle = this->character->battle_records;
|
||||||
ret.choice_search_config = this->character->choice_search_config;
|
ret.choice_search_config = this->character->choice_search_config;
|
||||||
@@ -248,7 +246,7 @@ void DownloadSession::send_61_98(bool is_98) {
|
|||||||
} else if (is_v3(this->version)) {
|
} else if (is_v3(this->version)) {
|
||||||
C_CharacterData_V3_61_98 ret;
|
C_CharacterData_V3_61_98 ret;
|
||||||
ret.inventory = this->character->inventory;
|
ret.inventory = this->character->inventory;
|
||||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
ret.disp = convert_player_disp_data<PlayerDispDataV123, PlayerDispDataV4>(this->character->disp, Language::ENGLISH, Language::ENGLISH);
|
||||||
ret.records.challenge = this->character->challenge_records;
|
ret.records.challenge = this->character->challenge_records;
|
||||||
ret.records.battle = this->character->battle_records;
|
ret.records.battle = this->character->battle_records;
|
||||||
ret.choice_search_config = this->character->choice_search_config;
|
ret.choice_search_config = this->character->choice_search_config;
|
||||||
@@ -266,7 +264,7 @@ void DownloadSession::send_61_98(bool is_98) {
|
|||||||
this->channel->send(command, 0x04, ret);
|
this->channel->send(command, 0x04, ret);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unsupported version");
|
throw std::runtime_error("unsupported version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,16 +276,16 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case 0x03: {
|
case 0x03: {
|
||||||
if (this->version != Version::BB_V4) {
|
if (this->version != Version::BB_V4) {
|
||||||
throw runtime_error("BB server sent non-BB encryption command");
|
throw std::runtime_error("BB server sent non-BB encryption command");
|
||||||
}
|
}
|
||||||
if (!this->bb_key_file) {
|
if (!this->bb_key_file) {
|
||||||
throw runtime_error("BB encryption requires a key file");
|
throw std::runtime_error("BB encryption requires a key file");
|
||||||
}
|
}
|
||||||
const auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
const auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||||
this->channel->crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
this->channel->crypt_in = std::make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
||||||
this->channel->crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
this->channel->crypt_out = std::make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
||||||
this->log.info_f("Enabled BB encryption");
|
this->log.info_f("Enabled BB encryption");
|
||||||
throw runtime_error("not yet implemented"); // Send 93
|
throw std::runtime_error("not yet implemented"); // Send 93
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,17 +295,17 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
case 0x9B: {
|
case 0x9B: {
|
||||||
const auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
const auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
||||||
if (uses_v3_encryption(this->version)) {
|
if (uses_v3_encryption(this->version)) {
|
||||||
this->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
this->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||||
this->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
this->channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||||
this->log.info_f("Enabled V3 encryption (server key {:08X}, client key {:08X})",
|
this->log.info_f("Enabled V3 encryption (server key {:08X}, client key {:08X})",
|
||||||
cmd.server_key, cmd.client_key);
|
cmd.server_key, cmd.client_key);
|
||||||
} else if (!uses_v4_encryption(this->version)) {
|
} else if (!uses_v4_encryption(this->version)) {
|
||||||
this->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
this->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||||
this->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
this->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||||
this->log.info_f("Enabled V2 encryption (server key {:08X}, client key {:08X})",
|
this->log.info_f("Enabled V2 encryption (server key {:08X}, client key {:08X})",
|
||||||
cmd.server_key, cmd.client_key);
|
cmd.server_key, cmd.client_key);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("BB server sent non-BB encryption command");
|
throw std::runtime_error("BB server sent non-BB encryption command");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.command == 0x02) {
|
if (msg.command == 0x02) {
|
||||||
@@ -346,7 +344,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
this->send_93_9D_9E(true);
|
this->send_93_9D_9E(true);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unsupported version");
|
throw std::runtime_error("unsupported version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,14 +379,14 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
this->channel->send(0x9C, 0x00, ret);
|
this->channel->send(0x9C, 0x00, ret);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unsupported version");
|
throw std::runtime_error("unsupported version");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (msg.flag == 0 || msg.flag == 2) {
|
} else if (msg.flag == 0 || msg.flag == 2) {
|
||||||
this->send_93_9D_9E(true);
|
this->send_93_9D_9E(true);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("login failed");
|
throw std::runtime_error("login failed");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -396,14 +394,14 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
case 0x92:
|
case 0x92:
|
||||||
case 0x9C:
|
case 0x9C:
|
||||||
if (msg.flag == 0) {
|
if (msg.flag == 0) {
|
||||||
throw runtime_error("server rejected login credentials");
|
throw std::runtime_error("server rejected login credentials");
|
||||||
}
|
}
|
||||||
this->send_93_9D_9E(true);
|
this->send_93_9D_9E(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x9F: {
|
case 0x9F: {
|
||||||
if (is_v1_or_v2(this->version)) {
|
if (is_v1_or_v2(this->version)) {
|
||||||
throw runtime_error("invalid command");
|
throw std::runtime_error("invalid command");
|
||||||
}
|
}
|
||||||
this->channel->send(0x9F, 0x00, this->client_config);
|
this->channel->send(0x9F, 0x00, this->client_config);
|
||||||
break;
|
break;
|
||||||
@@ -477,11 +475,11 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
if (this->interactive) {
|
if (this->interactive) {
|
||||||
while (item_index == 0 || item_index > msg.flag) {
|
while (item_index == 0 || item_index > msg.flag) {
|
||||||
this->log.info_f("Choose response index:");
|
this->log.info_f("Choose response index:");
|
||||||
string input = phosg::fgets(stdin);
|
std::string input = phosg::fgets(stdin);
|
||||||
item_index = stoul(input, nullptr, 0);
|
item_index = std::stoul(input, nullptr, 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unhandled menu selection");
|
throw std::runtime_error("unhandled menu selection");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.menu_id = items[item_index].menu_id;
|
ret.menu_id = items[item_index].menu_id;
|
||||||
@@ -521,9 +519,9 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
const auto& cmd = msg.check_size_t<S_Reconnect_19>(sizeof(S_Reconnect_19), 0xFFFF);
|
const auto& cmd = msg.check_size_t<S_Reconnect_19>(sizeof(S_Reconnect_19), 0xFFFF);
|
||||||
|
|
||||||
auto new_ep = make_endpoint_ipv4(cmd.address, cmd.port);
|
auto new_ep = make_endpoint_ipv4(cmd.address, cmd.port);
|
||||||
string netloc_str = str_for_endpoint(new_ep);
|
std::string netloc_str = str_for_endpoint(new_ep);
|
||||||
this->log.info_f("Connecting to {}", netloc_str);
|
this->log.info_f("Connecting to {}", netloc_str);
|
||||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||||
this->channel = SocketChannel::create(
|
this->channel = SocketChannel::create(
|
||||||
this->io_context,
|
this->io_context,
|
||||||
std::move(sock),
|
std::move(sock),
|
||||||
@@ -649,12 +647,12 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
case 0xA6: {
|
case 0xA6: {
|
||||||
auto handle_command = [&]<typename CmdT>() {
|
auto handle_command = [&]<typename CmdT>() {
|
||||||
const auto& cmd = msg.check_size_t<CmdT>(0xFFFF);
|
const auto& cmd = msg.check_size_t<CmdT>(0xFFFF);
|
||||||
string internal_name = cmd.filename.decode();
|
std::string internal_name = cmd.filename.decode();
|
||||||
string filtered_name;
|
std::string filtered_name;
|
||||||
for (char ch : internal_name) {
|
for (char ch : internal_name) {
|
||||||
filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_');
|
filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_');
|
||||||
}
|
}
|
||||||
string local_filename = std::format(
|
std::string local_filename = std::format(
|
||||||
"{}/{:016X}_{}_{}_{:c}_{}",
|
"{}/{:016X}_{}_{}_{:c}_{}",
|
||||||
this->output_dir,
|
this->output_dir,
|
||||||
this->current_request,
|
this->current_request,
|
||||||
@@ -677,7 +675,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
case 0x13:
|
case 0x13:
|
||||||
case 0xA7: {
|
case 0xA7: {
|
||||||
const auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
const auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
||||||
string internal_filename = cmd.filename.decode();
|
std::string internal_filename = cmd.filename.decode();
|
||||||
|
|
||||||
if (!is_v1_or_v2(this->version)) {
|
if (!is_v1_or_v2(this->version)) {
|
||||||
C_WriteFileConfirmation_V3_BB_13_A7 ret;
|
C_WriteFileConfirmation_V3_BB_13_A7 ret;
|
||||||
@@ -692,8 +690,8 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
}
|
}
|
||||||
auto& f = this->open_files.at(cmd.filename.decode());
|
auto& f = this->open_files.at(cmd.filename.decode());
|
||||||
size_t block_offset = msg.flag * 0x400;
|
size_t block_offset = msg.flag * 0x400;
|
||||||
size_t allowed_block_size = (block_offset < f.total_size) ? min<size_t>(f.total_size - block_offset, 0x400) : 0;
|
size_t allowed_block_size = (block_offset < f.total_size) ? std::min<size_t>(f.total_size - block_offset, 0x400) : 0;
|
||||||
size_t data_size = min<size_t>(cmd.data_size, allowed_block_size);
|
size_t data_size = std::min<size_t>(cmd.data_size, allowed_block_size);
|
||||||
size_t block_end_offset = block_offset + data_size;
|
size_t block_end_offset = block_offset + data_size;
|
||||||
if (block_end_offset > f.data.size()) {
|
if (block_end_offset > f.data.size()) {
|
||||||
f.data.resize(block_end_offset);
|
f.data.resize(block_end_offset);
|
||||||
@@ -720,7 +718,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
|||||||
}
|
}
|
||||||
case 0xAC: {
|
case 0xAC: {
|
||||||
if (is_v1_or_v2(this->version)) {
|
if (is_v1_or_v2(this->version)) {
|
||||||
throw runtime_error("unsupported version");
|
throw std::runtime_error("unsupported version");
|
||||||
}
|
}
|
||||||
this->on_request_complete();
|
this->on_request_complete();
|
||||||
break;
|
break;
|
||||||
@@ -745,7 +743,7 @@ void DownloadSession::send_next_request() {
|
|||||||
this->log.info_f("{:016X}: {}", it.first, it.second);
|
this->log.info_f("{:016X}: {}", it.first, it.second);
|
||||||
}
|
}
|
||||||
this->log.info_f("Choose item to expand by ID (q to quit; s to skip to next config):");
|
this->log.info_f("Choose item to expand by ID (q to quit; s to skip to next config):");
|
||||||
string input = phosg::fgets(stdin);
|
std::string input = phosg::fgets(stdin);
|
||||||
if (input.empty() || (input == "q\n")) {
|
if (input.empty() || (input == "q\n")) {
|
||||||
this->channel->disconnect();
|
this->channel->disconnect();
|
||||||
return;
|
return;
|
||||||
@@ -753,7 +751,7 @@ void DownloadSession::send_next_request() {
|
|||||||
this->pending_requests.clear();
|
this->pending_requests.clear();
|
||||||
this->on_request_complete();
|
this->on_request_complete();
|
||||||
} else {
|
} else {
|
||||||
this->current_request = stoull(input, nullptr, 16);
|
this->current_request = std::stoull(input, nullptr, 16);
|
||||||
this->done_requests.emplace(this->current_request);
|
this->done_requests.emplace(this->current_request);
|
||||||
this->pending_requests.erase(this->current_request);
|
this->pending_requests.erase(this->current_request);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-17
@@ -8,8 +8,6 @@
|
|||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static constexpr uint8_t EP1 = EnemyTypeDefinition::Flag::VALID_EP1;
|
static constexpr uint8_t EP1 = EnemyTypeDefinition::Flag::VALID_EP1;
|
||||||
static constexpr uint8_t EP2 = EnemyTypeDefinition::Flag::VALID_EP2;
|
static constexpr uint8_t EP2 = EnemyTypeDefinition::Flag::VALID_EP2;
|
||||||
static constexpr uint8_t EP4 = EnemyTypeDefinition::Flag::VALID_EP4;
|
static constexpr uint8_t EP4 = EnemyTypeDefinition::Flag::VALID_EP4;
|
||||||
@@ -17,7 +15,7 @@ static constexpr uint8_t RARE = EnemyTypeDefinition::Flag::IS_RARE;
|
|||||||
static constexpr uint8_t BOSS = EnemyTypeDefinition::Flag::IS_BOSS;
|
static constexpr uint8_t BOSS = EnemyTypeDefinition::Flag::IS_BOSS;
|
||||||
|
|
||||||
static constexpr uint8_t NONE = 0xFF;
|
static constexpr uint8_t NONE = 0xFF;
|
||||||
static const vector<EnemyTypeDefinition> type_defs{
|
static const std::vector<EnemyTypeDefinition> type_defs{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// TYPE FLAGS RT OLDRT BP-STATS BP-ATTACK BP-RESIST BP-MOVEMENT ENUM NAME IN-GAME NAME ULTIMATE NAME
|
// TYPE FLAGS RT OLDRT BP-STATS BP-ATTACK BP-RESIST BP-MOVEMENT ENUM NAME IN-GAME NAME ULTIMATE NAME
|
||||||
{EnemyType::UNKNOWN, 0, NONE, NONE, {}, {}, {}, {}, "UNKNOWN", "__UNKNOWN__", nullptr},
|
{EnemyType::UNKNOWN, 0, NONE, NONE, {}, {}, {}, {}, "UNKNOWN", "__UNKNOWN__", nullptr},
|
||||||
@@ -48,9 +46,9 @@ static const vector<EnemyTypeDefinition> type_defs{
|
|||||||
{EnemyType::DARK_GUNNER_CONTROL, EP1, NONE, NONE, {}, {}, {}, {}, "DARK_GUNNER_CONTROL", "Dark Gunner (control)", nullptr},
|
{EnemyType::DARK_GUNNER_CONTROL, EP1, NONE, NONE, {}, {}, {}, {}, "DARK_GUNNER_CONTROL", "Dark Gunner (control)", nullptr},
|
||||||
{EnemyType::DARK_GUNNER, EP1, 0x22, 0x22, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DARK_GUNNER", "Dark Gunner", nullptr},
|
{EnemyType::DARK_GUNNER, EP1, 0x22, 0x22, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DARK_GUNNER", "Dark Gunner", nullptr},
|
||||||
{EnemyType::DARVANT, EP1, NONE, NONE, {0x35}, {0x35}, {0x35}, {0x35, 0x39}, "DARVANT", "Darvant", nullptr},
|
{EnemyType::DARVANT, EP1, NONE, NONE, {0x35}, {0x35}, {0x35}, {0x35, 0x39}, "DARVANT", "Darvant", nullptr},
|
||||||
{EnemyType::DE_ROL_LE_BODY, EP1 | BOSS, NONE, NONE, {0x10}, {0x0F}, {0x10}, {0x0F}, "DE_ROL_LE_BODY", "De Rol Le (body)", "Dal Ral Lie (body)"},
|
{EnemyType::DE_ROL_LE_BODY, EP1 | BOSS, NONE, NONE, {0x10}, {0x0F}, {0x10}, {0x0F}, "DE_ROL_LE_BODY", "De Rol Le (body)", "Dal Ra Lie (body)"},
|
||||||
{EnemyType::DE_ROL_LE_MINE, EP1 | BOSS, NONE, NONE, {0x11}, {0x0F}, {0x11}, {0x0F}, "DE_ROL_LE_MINE", "De Rol Le (mine)", "Dal Ral Lie (mine)"},
|
{EnemyType::DE_ROL_LE_MINE, EP1 | BOSS, NONE, NONE, {0x11}, {0x0F}, {0x11}, {0x0F}, "DE_ROL_LE_MINE", "De Rol Le (mine)", "Dal Ra Lie (mine)"},
|
||||||
{EnemyType::DE_ROL_LE, EP1 | BOSS, 0x2D, 0x2D, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DE_ROL_LE", "De Rol Le", "Dal Ral Lie"},
|
{EnemyType::DE_ROL_LE, EP1 | BOSS, 0x2D, 0x2D, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DE_ROL_LE", "De Rol Le", "Dal Ra Lie"},
|
||||||
{EnemyType::DEATH_GUNNER, EP1, 0x23, 0x23, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DEATH_GUNNER", "Death Gunner", nullptr},
|
{EnemyType::DEATH_GUNNER, EP1, 0x23, 0x23, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DEATH_GUNNER", "Death Gunner", nullptr},
|
||||||
{EnemyType::DEL_LILY, EP2, 0x53, 0x53, {0x25}, {0x25}, {0x25}, {0x25}, "DEL_LILY", "Del Lily", nullptr},
|
{EnemyType::DEL_LILY, EP2, 0x53, 0x53, {0x25}, {0x25}, {0x25}, {0x25}, "DEL_LILY", "Del Lily", nullptr},
|
||||||
{EnemyType::DEL_RAPPY_CRATER, EP4, 0x69, 0x57, {0x06}, {0x06}, {0x06}, {0x06}, "DEL_RAPPY_CRATER", "Del Rappy (crater)", nullptr},
|
{EnemyType::DEL_RAPPY_CRATER, EP4, 0x69, 0x57, {0x06}, {0x06}, {0x06}, {0x06}, "DEL_RAPPY_CRATER", "Del Rappy (crater)", nullptr},
|
||||||
@@ -167,19 +165,19 @@ const char* phosg::name_for_enum<EnemyType>(EnemyType type) {
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
|
EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
|
||||||
static unordered_map<string, EnemyType> index;
|
static std::unordered_map<std::string, EnemyType> index;
|
||||||
if (index.empty()) {
|
if (index.empty()) {
|
||||||
for (const auto& def : type_defs) {
|
for (const auto& def : type_defs) {
|
||||||
if (!index.emplace(def.enum_name, def.type).second) {
|
if (!index.emplace(def.enum_name, def.type).second) {
|
||||||
throw logic_error(std::format("duplicate enemy enum name: {}", def.enum_name));
|
throw std::logic_error(std::format("duplicate enemy enum name: {}", def.enum_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return index.at(name);
|
return index.at(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
|
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
|
||||||
static array<vector<vector<EnemyType>>, 5> data;
|
static std::array<std::vector<std::vector<EnemyType>>, 5> data;
|
||||||
auto& ret = data.at(static_cast<size_t>(episode));
|
auto& ret = data.at(static_cast<size_t>(episode));
|
||||||
if (ret.empty()) {
|
if (ret.empty()) {
|
||||||
for (const auto& def : type_defs) {
|
for (const auto& def : type_defs) {
|
||||||
@@ -196,8 +194,8 @@ const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return ret.at(rt_index);
|
return ret.at(rt_index);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
static const vector<EnemyType> empty_vec;
|
static const std::vector<EnemyType> empty_vec;
|
||||||
return empty_vec;
|
return empty_vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,7 +209,7 @@ struct BPIndexCacheEntry {
|
|||||||
|
|
||||||
static const BPIndexCacheEntry& get_bp_index_cache_entry(Episode episode, uint8_t bp_index) {
|
static const BPIndexCacheEntry& get_bp_index_cache_entry(Episode episode, uint8_t bp_index) {
|
||||||
static bool cache_populated = false;
|
static bool cache_populated = false;
|
||||||
static array<vector<BPIndexCacheEntry>, 5> data;
|
static std::array<std::vector<BPIndexCacheEntry>, 5> data;
|
||||||
if (!cache_populated) {
|
if (!cache_populated) {
|
||||||
cache_populated = true;
|
cache_populated = true;
|
||||||
for (const auto& def : type_defs) {
|
for (const auto& def : type_defs) {
|
||||||
@@ -256,7 +254,7 @@ static const std::set<EnemyType> empty_vec;
|
|||||||
const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index) {
|
const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index) {
|
||||||
try {
|
try {
|
||||||
return get_bp_index_cache_entry(episode, bp_index).stats;
|
return get_bp_index_cache_entry(episode, bp_index).stats;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return empty_vec;
|
return empty_vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +262,7 @@ const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode epis
|
|||||||
const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episode episode, uint8_t bp_index) {
|
const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episode episode, uint8_t bp_index) {
|
||||||
try {
|
try {
|
||||||
return get_bp_index_cache_entry(episode, bp_index).attack_data;
|
return get_bp_index_cache_entry(episode, bp_index).attack_data;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return empty_vec;
|
return empty_vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,7 +270,7 @@ const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episod
|
|||||||
const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episode episode, uint8_t bp_index) {
|
const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episode episode, uint8_t bp_index) {
|
||||||
try {
|
try {
|
||||||
return get_bp_index_cache_entry(episode, bp_index).resist_data;
|
return get_bp_index_cache_entry(episode, bp_index).resist_data;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return empty_vec;
|
return empty_vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,7 +278,7 @@ const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episod
|
|||||||
const std::set<EnemyType>& enemy_types_for_battle_param_movement_data_index(Episode episode, uint8_t bp_index) {
|
const std::set<EnemyType>& enemy_types_for_battle_param_movement_data_index(Episode episode, uint8_t bp_index) {
|
||||||
try {
|
try {
|
||||||
return get_bp_index_cache_entry(episode, bp_index).movement_data;
|
return get_bp_index_cache_entry(episode, bp_index).movement_data;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return empty_vec;
|
return empty_vec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,20 @@
|
|||||||
|
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
const vector<uint16_t>& all_assist_card_ids(bool is_nte) {
|
const std::vector<uint16_t>& all_assist_card_ids(bool is_nte) {
|
||||||
// Note: This order matches the order that the cards are defined in the original
|
// Note: This order matches the order that the cards are defined in the original
|
||||||
// code. This is relevant for consistency of results when choosing a random card
|
// code. This is relevant for consistency of results when choosing a random card
|
||||||
// (for God Whim).
|
// (for God Whim).
|
||||||
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
|
static const std::vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
|
||||||
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102,
|
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102,
|
||||||
0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, 0x0121,
|
0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, 0x0121,
|
||||||
0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
|
0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
|
||||||
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x013F, 0x0140,
|
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x013F, 0x0140,
|
||||||
0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240,
|
0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240,
|
||||||
0x0241, 0x0242};
|
0x0241, 0x0242};
|
||||||
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
|
static const std::vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
|
||||||
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
|
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
|
||||||
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D,
|
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D,
|
||||||
0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F,
|
0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F,
|
||||||
@@ -28,12 +26,12 @@ const vector<uint16_t>& all_assist_card_ids(bool is_nte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_nte) {
|
AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_nte) {
|
||||||
static const unordered_map<uint16_t, AssistEffect> card_id_to_effect_final_only({
|
static const std::unordered_map<uint16_t, AssistEffect> card_id_to_effect_final_only({
|
||||||
{0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS},
|
{0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS},
|
||||||
{0x0019, /* 0x004A */ AssistEffect::RICH_PLUS},
|
{0x0019, /* 0x004A */ AssistEffect::RICH_PLUS},
|
||||||
{0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS},
|
{0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS},
|
||||||
});
|
});
|
||||||
static const unordered_map<uint16_t, AssistEffect> card_id_to_effect({
|
static const std::unordered_map<uint16_t, AssistEffect> card_id_to_effect({
|
||||||
{0x00F5, /* 0x0001 */ AssistEffect::DICE_HALF},
|
{0x00F5, /* 0x0001 */ AssistEffect::DICE_HALF},
|
||||||
{0x00F6, /* 0x0002 */ AssistEffect::DICE_PLUS_1},
|
{0x00F6, /* 0x0002 */ AssistEffect::DICE_PLUS_1},
|
||||||
{0x00F7, /* 0x0003 */ AssistEffect::DICE_FEVER},
|
{0x00F7, /* 0x0003 */ AssistEffect::DICE_FEVER},
|
||||||
@@ -109,18 +107,18 @@ AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_nte) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
return card_id_to_effect.at(card_id);
|
return card_id_to_effect.at(card_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
try {
|
try {
|
||||||
return card_id_to_effect_final_only.at(card_id);
|
return card_id_to_effect_final_only.at(card_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AssistEffect::NONE;
|
return AssistEffect::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
AssistServer::AssistServer(shared_ptr<Server> server)
|
AssistServer::AssistServer(std::shared_ptr<Server> server)
|
||||||
: w_server(server),
|
: w_server(server),
|
||||||
assist_effects(AssistEffect::NONE),
|
assist_effects(AssistEffect::NONE),
|
||||||
num_assist_cards_set(0),
|
num_assist_cards_set(0),
|
||||||
@@ -128,18 +126,18 @@ AssistServer::AssistServer(shared_ptr<Server> server)
|
|||||||
active_assist_effects(AssistEffect::NONE),
|
active_assist_effects(AssistEffect::NONE),
|
||||||
num_active_assists(0) {}
|
num_active_assists(0) {}
|
||||||
|
|
||||||
shared_ptr<Server> AssistServer::server() {
|
std::shared_ptr<Server> AssistServer::server() {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Server> AssistServer::server() const {
|
std::shared_ptr<const Server> AssistServer::server() const {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -148,7 +146,7 @@ uint16_t AssistServer::card_id_for_card_ref(uint16_t card_ref) const {
|
|||||||
return this->server()->card_id_for_card_ref(card_ref);
|
return this->server()->card_id_for_card_ref(card_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CardIndex::CardEntry> AssistServer::definition_for_card_id(
|
std::shared_ptr<const CardIndex::CardEntry> AssistServer::definition_for_card_id(
|
||||||
uint16_t card_id) const {
|
uint16_t card_id) const {
|
||||||
return this->server()->definition_for_card_id(card_id);
|
return this->server()->definition_for_card_id(card_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#include "../CommandFormats.hh"
|
#include "../CommandFormats.hh"
|
||||||
#include "../SendCommands.hh"
|
#include "../SendCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
void BattleRecord::PlayerEntry::print(FILE* stream) const {
|
void BattleRecord::PlayerEntry::print(FILE* stream) const {
|
||||||
@@ -41,7 +39,7 @@ BattleRecord::Event::Event(phosg::StringReader& r) {
|
|||||||
this->data = r.read(r.get_u16l());
|
this->data = r.read(r.get_u16l());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown event type");
|
throw std::logic_error("unknown event type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +49,7 @@ void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
|
|||||||
switch (this->type) {
|
switch (this->type) {
|
||||||
case Event::Type::PLAYER_JOIN:
|
case Event::Type::PLAYER_JOIN:
|
||||||
if (this->players.size() != 1) {
|
if (this->players.size() != 1) {
|
||||||
throw logic_error("player join event does not contain 1 player entry");
|
throw std::logic_error("player join event does not contain 1 player entry");
|
||||||
}
|
}
|
||||||
w.put(this->players[0]);
|
w.put(this->players[0]);
|
||||||
break;
|
break;
|
||||||
@@ -75,13 +73,12 @@ void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
|
|||||||
w.write(this->data);
|
w.write(this->data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown event type");
|
throw std::logic_error("unknown event type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleRecord::Event::print(FILE* stream) const {
|
void BattleRecord::Event::print(FILE* stream) const {
|
||||||
string time_str = phosg::format_time(this->timestamp);
|
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, phosg::format_time(this->timestamp));
|
||||||
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, time_str);
|
|
||||||
switch (this->type) {
|
switch (this->type) {
|
||||||
case Type::PLAYER_JOIN:
|
case Type::PLAYER_JOIN:
|
||||||
phosg::fwrite_fmt(stream, "PLAYER_JOIN {:02X}\n", this->players[0].lobby_data.client_id);
|
phosg::fwrite_fmt(stream, "PLAYER_JOIN {:02X}\n", this->players[0].lobby_data.client_id);
|
||||||
@@ -121,7 +118,7 @@ void BattleRecord::Event::print(FILE* stream) const {
|
|||||||
phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown event type in battle record");
|
throw std::runtime_error("unknown event type in battle record");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,11 +128,8 @@ BattleRecord::BattleRecord(uint32_t behavior_flags)
|
|||||||
battle_start_timestamp(0),
|
battle_start_timestamp(0),
|
||||||
battle_end_timestamp(0) {}
|
battle_end_timestamp(0) {}
|
||||||
|
|
||||||
BattleRecord::BattleRecord(const string& data)
|
BattleRecord::BattleRecord(const std::string& data)
|
||||||
: is_writable(false),
|
: is_writable(false), behavior_flags(0), battle_start_timestamp(0), battle_end_timestamp(0) {
|
||||||
behavior_flags(0),
|
|
||||||
battle_start_timestamp(0),
|
|
||||||
battle_end_timestamp(0) {
|
|
||||||
phosg::StringReader r(data);
|
phosg::StringReader r(data);
|
||||||
|
|
||||||
uint64_t signature = r.get_u64l();
|
uint64_t signature = r.get_u64l();
|
||||||
@@ -145,7 +139,7 @@ BattleRecord::BattleRecord(const string& data)
|
|||||||
} else if (signature == this->SIGNATURE_V2) {
|
} else if (signature == this->SIGNATURE_V2) {
|
||||||
has_random_stream = true;
|
has_random_stream = true;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("incorrect battle record signature");
|
throw std::runtime_error("incorrect battle record signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->battle_start_timestamp = r.get_u64l();
|
this->battle_start_timestamp = r.get_u64l();
|
||||||
@@ -159,7 +153,7 @@ BattleRecord::BattleRecord(const string& data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string BattleRecord::serialize() const {
|
std::string BattleRecord::serialize() const {
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
w.put_u64l(this->SIGNATURE_V2);
|
w.put_u64l(this->SIGNATURE_V2);
|
||||||
w.put_u64l(this->battle_start_timestamp);
|
w.put_u64l(this->battle_start_timestamp);
|
||||||
@@ -176,13 +170,13 @@ string BattleRecord::serialize() const {
|
|||||||
void BattleRecord::add_player(
|
void BattleRecord::add_player(
|
||||||
const PlayerLobbyDataDCGC& lobby_data,
|
const PlayerLobbyDataDCGC& lobby_data,
|
||||||
const PlayerInventory& inventory,
|
const PlayerInventory& inventory,
|
||||||
const PlayerDispDataDCPCV3& disp,
|
const PlayerDispDataV123& disp,
|
||||||
uint32_t level) {
|
uint32_t level) {
|
||||||
if (!this->is_writable) {
|
if (!this->is_writable) {
|
||||||
throw logic_error("cannot write to battle record");
|
throw std::logic_error("cannot write to battle record");
|
||||||
}
|
}
|
||||||
if (this->battle_start_timestamp != 0) {
|
if (this->battle_start_timestamp != 0) {
|
||||||
throw runtime_error("cannot add player during battle");
|
throw std::runtime_error("cannot add player during battle");
|
||||||
}
|
}
|
||||||
Event& ev = this->events.emplace_back();
|
Event& ev = this->events.emplace_back();
|
||||||
ev.type = Event::Type::PLAYER_JOIN;
|
ev.type = Event::Type::PLAYER_JOIN;
|
||||||
@@ -196,7 +190,7 @@ void BattleRecord::add_player(
|
|||||||
|
|
||||||
void BattleRecord::delete_player(uint8_t client_id) {
|
void BattleRecord::delete_player(uint8_t client_id) {
|
||||||
if (!this->is_writable) {
|
if (!this->is_writable) {
|
||||||
throw logic_error("cannot write to battle record");
|
throw std::logic_error("cannot write to battle record");
|
||||||
}
|
}
|
||||||
Event& ev = this->events.emplace_back();
|
Event& ev = this->events.emplace_back();
|
||||||
ev.type = Event::Type::PLAYER_LEAVE;
|
ev.type = Event::Type::PLAYER_LEAVE;
|
||||||
@@ -206,7 +200,7 @@ void BattleRecord::delete_player(uint8_t client_id) {
|
|||||||
|
|
||||||
void BattleRecord::add_command(Event::Type type, const void* data, size_t size) {
|
void BattleRecord::add_command(Event::Type type, const void* data, size_t size) {
|
||||||
if (!this->is_writable) {
|
if (!this->is_writable) {
|
||||||
throw logic_error("cannot write to battle record");
|
throw std::logic_error("cannot write to battle record");
|
||||||
}
|
}
|
||||||
Event& ev = this->events.emplace_back();
|
Event& ev = this->events.emplace_back();
|
||||||
ev.type = type;
|
ev.type = type;
|
||||||
@@ -214,9 +208,9 @@ void BattleRecord::add_command(Event::Type type, const void* data, size_t size)
|
|||||||
ev.data.assign(reinterpret_cast<const char*>(data), size);
|
ev.data.assign(reinterpret_cast<const char*>(data), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleRecord::add_command(Event::Type type, string&& data) {
|
void BattleRecord::add_command(Event::Type type, std::string&& data) {
|
||||||
if (!this->is_writable) {
|
if (!this->is_writable) {
|
||||||
throw logic_error("cannot write to battle record");
|
throw std::logic_error("cannot write to battle record");
|
||||||
}
|
}
|
||||||
Event& ev = this->events.emplace_back();
|
Event& ev = this->events.emplace_back();
|
||||||
ev.type = type;
|
ev.type = type;
|
||||||
@@ -224,10 +218,9 @@ void BattleRecord::add_command(Event::Type type, string&& data) {
|
|||||||
ev.data = std::move(data);
|
ev.data = std::move(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleRecord::add_chat_message(
|
void BattleRecord::add_chat_message(uint32_t guild_card_number, std::string&& data) {
|
||||||
uint32_t guild_card_number, string&& data) {
|
|
||||||
if (!this->is_writable) {
|
if (!this->is_writable) {
|
||||||
throw logic_error("cannot write to battle record");
|
throw std::logic_error("cannot write to battle record");
|
||||||
}
|
}
|
||||||
Event& ev = this->events.emplace_back();
|
Event& ev = this->events.emplace_back();
|
||||||
ev.type = Event::Type::CHAT_MESSAGE;
|
ev.type = Event::Type::CHAT_MESSAGE;
|
||||||
@@ -240,7 +233,7 @@ void BattleRecord::add_random_data(const void* data, size_t size) {
|
|||||||
this->random_stream.append(reinterpret_cast<const char*>(data), size);
|
this->random_stream.append(reinterpret_cast<const char*>(data), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
const string& BattleRecord::get_random_stream() const {
|
const std::string& BattleRecord::get_random_stream() const {
|
||||||
return this->random_stream;
|
return this->random_stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +252,7 @@ bool BattleRecord::is_map_definition_event(const Event& ev) {
|
|||||||
|
|
||||||
void BattleRecord::set_battle_start_timestamp() {
|
void BattleRecord::set_battle_start_timestamp() {
|
||||||
if (this->battle_start_timestamp != 0) {
|
if (this->battle_start_timestamp != 0) {
|
||||||
throw logic_error("battle start timestamp is already set");
|
throw std::logic_error("battle start timestamp is already set");
|
||||||
}
|
}
|
||||||
this->battle_start_timestamp = phosg::now();
|
this->battle_start_timestamp = phosg::now();
|
||||||
|
|
||||||
@@ -271,30 +264,30 @@ void BattleRecord::set_battle_start_timestamp() {
|
|||||||
for (auto& ev : this->events) {
|
for (auto& ev : this->events) {
|
||||||
if (ev.type == Event::Type::PLAYER_JOIN) {
|
if (ev.type == Event::Type::PLAYER_JOIN) {
|
||||||
if (ev.players.size() != 1) {
|
if (ev.players.size() != 1) {
|
||||||
throw logic_error("player join event does not contain 1 player entry");
|
throw std::logic_error("player join event does not contain 1 player entry");
|
||||||
}
|
}
|
||||||
auto& player = ev.players[0];
|
auto& player = ev.players[0];
|
||||||
if (player.lobby_data.client_id >= 4) {
|
if (player.lobby_data.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
players[player.lobby_data.client_id] = player;
|
players[player.lobby_data.client_id] = player;
|
||||||
players_present[player.lobby_data.client_id] = true;
|
players_present[player.lobby_data.client_id] = true;
|
||||||
|
|
||||||
} else if (ev.type == Event::Type::PLAYER_LEAVE) {
|
} else if (ev.type == Event::Type::PLAYER_LEAVE) {
|
||||||
if (ev.leaving_client_id >= 4) {
|
if (ev.leaving_client_id >= 4) {
|
||||||
throw logic_error("invalid client ID");
|
throw std::logic_error("invalid client ID");
|
||||||
}
|
}
|
||||||
players_present[ev.leaving_client_id] = false;
|
players_present[ev.leaving_client_id] = false;
|
||||||
|
|
||||||
} else if (ev.type == Event::Type::SET_INITIAL_PLAYERS) {
|
} else if (ev.type == Event::Type::SET_INITIAL_PLAYERS) {
|
||||||
throw logic_error("BattleRecord::set_battle_start_timestamp called twice");
|
throw std::logic_error("BattleRecord::set_battle_start_timestamp called twice");
|
||||||
|
|
||||||
} else if (this->is_map_definition_event(ev)) {
|
} else if (this->is_map_definition_event(ev)) {
|
||||||
num_map_events++;
|
num_map_events++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deque<Event> new_events;
|
std::deque<Event> new_events;
|
||||||
|
|
||||||
// Generate the initial players event
|
// Generate the initial players event
|
||||||
Event initial_ev;
|
Event initial_ev;
|
||||||
@@ -335,32 +328,31 @@ void BattleRecord::set_battle_end_timestamp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BattleRecord::print(FILE* stream) const {
|
void BattleRecord::print(FILE* stream) const {
|
||||||
string start_str = phosg::format_time(this->battle_start_timestamp);
|
|
||||||
string end_str = phosg::format_time(this->battle_end_timestamp);
|
|
||||||
phosg::fwrite_fmt(stream, "BattleRecord {} behavior_flags={:08X} start={:016X} ({}) end={:016X} ({}); {} events\n",
|
phosg::fwrite_fmt(stream, "BattleRecord {} behavior_flags={:08X} start={:016X} ({}) end={:016X} ({}); {} events\n",
|
||||||
this->is_writable ? "writable" : "read-only",
|
this->is_writable ? "writable" : "read-only",
|
||||||
this->behavior_flags,
|
this->behavior_flags,
|
||||||
this->battle_start_timestamp,
|
this->battle_start_timestamp,
|
||||||
start_str,
|
phosg::format_time(this->battle_start_timestamp),
|
||||||
this->battle_end_timestamp,
|
this->battle_end_timestamp,
|
||||||
end_str, this->events.size());
|
phosg::format_time(this->battle_end_timestamp),
|
||||||
|
this->events.size());
|
||||||
for (const auto& event : this->events) {
|
for (const auto& event : this->events) {
|
||||||
event.print(stream);
|
event.print(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BattleRecordPlayer::BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, shared_ptr<const BattleRecord> rec)
|
BattleRecordPlayer::BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, std::shared_ptr<const BattleRecord> rec)
|
||||||
: io_context(io_context),
|
: io_context(io_context),
|
||||||
record(rec),
|
record(rec),
|
||||||
event_it(this->record->events.begin()),
|
event_it(this->record->events.begin()),
|
||||||
play_start_timestamp(0),
|
play_start_timestamp(0),
|
||||||
next_command_timer(*this->io_context) {}
|
next_command_timer(*this->io_context) {}
|
||||||
|
|
||||||
shared_ptr<const BattleRecord> BattleRecordPlayer::get_record() const {
|
std::shared_ptr<const BattleRecord> BattleRecordPlayer::get_record() const {
|
||||||
return this->record;
|
return this->record;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleRecordPlayer::set_lobby(shared_ptr<Lobby> l) {
|
void BattleRecordPlayer::set_lobby(std::shared_ptr<Lobby> l) {
|
||||||
this->lobby = l;
|
this->lobby = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +398,7 @@ asio::awaitable<void> BattleRecordPlayer::play_task() {
|
|||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case BattleRecord::Event::Type::PLAYER_JOIN:
|
case BattleRecord::Event::Type::PLAYER_JOIN:
|
||||||
// Technically we can support this, but it should never happen
|
// Technically we can support this, but it should never happen
|
||||||
throw runtime_error("player join event during battle replay");
|
throw std::runtime_error("player join event during battle replay");
|
||||||
case BattleRecord::Event::Type::PLAYER_LEAVE:
|
case BattleRecord::Event::Type::PLAYER_LEAVE:
|
||||||
send_player_leave_notification(l, ev.leaving_client_id);
|
send_player_leave_notification(l, ev.leaving_client_id);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public:
|
|||||||
struct PlayerEntry {
|
struct PlayerEntry {
|
||||||
PlayerLobbyDataDCGC lobby_data;
|
PlayerLobbyDataDCGC lobby_data;
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
PlayerDispDataDCPCV3 disp;
|
PlayerDispDataV123 disp;
|
||||||
le_uint32_t level;
|
le_uint32_t level;
|
||||||
|
|
||||||
void print(FILE* stream) const;
|
void print(FILE* stream) const;
|
||||||
@@ -85,7 +85,7 @@ public:
|
|||||||
void add_player(
|
void add_player(
|
||||||
const PlayerLobbyDataDCGC& lobby_data,
|
const PlayerLobbyDataDCGC& lobby_data,
|
||||||
const PlayerInventory& inventory,
|
const PlayerInventory& inventory,
|
||||||
const PlayerDispDataDCPCV3& disp,
|
const PlayerDispDataV123& disp,
|
||||||
uint32_t level);
|
uint32_t level);
|
||||||
void delete_player(uint8_t client_id);
|
void delete_player(uint8_t client_id);
|
||||||
void add_command(Event::Type type, const void* data, size_t size);
|
void add_command(Event::Type type, const void* data, size_t size);
|
||||||
|
|||||||
+52
-59
@@ -3,11 +3,9 @@
|
|||||||
#include "../CommandFormats.hh"
|
#include "../CommandFormats.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
Card::Card(uint16_t card_id, uint16_t card_ref, uint16_t client_id, shared_ptr<Server> server)
|
Card::Card(uint16_t card_id, uint16_t card_ref, uint16_t client_id, std::shared_ptr<Server> server)
|
||||||
: w_server(server),
|
: w_server(server),
|
||||||
w_player_state(server->get_player_state(client_id)),
|
w_player_state(server->get_player_state(client_id)),
|
||||||
client_id(client_id),
|
client_id(client_id),
|
||||||
@@ -39,7 +37,7 @@ void Card::init() {
|
|||||||
// Arkz-side. This could break things later on in the battle, and even if it
|
// Arkz-side. This could break things later on in the battle, and even if it
|
||||||
// doesn't, it certainly isn't behavior that the player would expect, so we
|
// doesn't, it certainly isn't behavior that the player would expect, so we
|
||||||
// prevent it instead.
|
// prevent it instead.
|
||||||
throw runtime_error("card definition is missing");
|
throw std::runtime_error("card definition is missing");
|
||||||
}
|
}
|
||||||
this->sc_card_ref = ps->get_sc_card_ref();
|
this->sc_card_ref = ps->get_sc_card_ref();
|
||||||
this->sc_def_entry = s->definition_for_card_id(ps->get_sc_card_id());
|
this->sc_def_entry = s->definition_for_card_id(ps->get_sc_card_id());
|
||||||
@@ -56,7 +54,7 @@ void Card::init() {
|
|||||||
} else {
|
} else {
|
||||||
int16_t rules_char_hp = s->map_and_rules->rules.char_hp;
|
int16_t rules_char_hp = s->map_and_rules->rules.char_hp;
|
||||||
int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp;
|
int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp;
|
||||||
int16_t hp = clamp<int16_t>(base_char_hp + this->def_entry->def.hp.stat, 1, 99);
|
int16_t hp = std::clamp<int16_t>(base_char_hp + this->def_entry->def.hp.stat, 1, 99);
|
||||||
this->max_hp = hp;
|
this->max_hp = hp;
|
||||||
this->current_hp = hp;
|
this->current_hp = hp;
|
||||||
}
|
}
|
||||||
@@ -78,34 +76,34 @@ void Card::init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Server> Card::server() {
|
std::shared_ptr<Server> Card::server() {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Server> Card::server() const {
|
std::shared_ptr<const Server> Card::server() const {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PlayerState> Card::player_state() {
|
std::shared_ptr<PlayerState> Card::player_state() {
|
||||||
auto s = this->w_player_state.lock();
|
auto s = this->w_player_state.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const PlayerState> Card::player_state() const {
|
std::shared_ptr<const PlayerState> Card::player_state() const {
|
||||||
auto s = this->w_player_state.lock();
|
auto s = this->w_player_state.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -158,7 +156,7 @@ ssize_t Card::apply_abnormal_condition(
|
|||||||
int16_t existing_cond_value = 0;
|
int16_t existing_cond_value = 0;
|
||||||
auto& cond = this->action_chain.conditions[cond_index];
|
auto& cond = this->action_chain.conditions[cond_index];
|
||||||
if ((eff.type == ConditionType::MV_BONUS) && (cond.type == ConditionType::MV_BONUS)) {
|
if ((eff.type == ConditionType::MV_BONUS) && (cond.type == ConditionType::MV_BONUS)) {
|
||||||
existing_cond_value = clamp<int16_t>(cond.value, -99, 99);
|
existing_cond_value = std::clamp<int16_t>(cond.value, -99, 99);
|
||||||
log.debug_f("MV_BONUS combines => existing_cond_value = {}", existing_cond_value);
|
log.debug_f("MV_BONUS combines => existing_cond_value = {}", existing_cond_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +174,7 @@ ssize_t Card::apply_abnormal_condition(
|
|||||||
|
|
||||||
switch (eff.arg1.at(0)) {
|
switch (eff.arg1.at(0)) {
|
||||||
case 'a': {
|
case 'a': {
|
||||||
string s = eff.arg1.decode();
|
std::string s = eff.arg1.decode();
|
||||||
cond.a_arg_value = atoi(s.c_str() + 1);
|
cond.a_arg_value = atoi(s.c_str() + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -190,13 +188,12 @@ ssize_t Card::apply_abnormal_condition(
|
|||||||
cond.remaining_turns = 102;
|
cond.remaining_turns = 102;
|
||||||
break;
|
break;
|
||||||
case 't': {
|
case 't': {
|
||||||
string s = eff.arg1.decode();
|
std::string s = eff.arg1.decode();
|
||||||
cond.remaining_turns = atoi(s.c_str() + 1);
|
cond.remaining_turns = atoi(s.c_str() + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string cond_str = cond.str(s);
|
log.debug_f("wrote condition {} => {}", cond_index, cond.str(s));
|
||||||
log.debug_f("wrote condition {} => {}", cond_index, cond_str);
|
|
||||||
|
|
||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
s->card_special->update_condition_orders(this->shared_from_this());
|
s->card_special->update_condition_orders(this->shared_from_this());
|
||||||
@@ -204,8 +201,7 @@ ssize_t Card::apply_abnormal_condition(
|
|||||||
if (this->action_chain.conditions[z].type == ConditionType::NONE) {
|
if (this->action_chain.conditions[z].type == ConditionType::NONE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
string cond_str = cond.str(s);
|
log.debug_f("sorted conditions: [{}] => {}", z, cond.str(s));
|
||||||
log.debug_f("sorted conditions: [{}] => {}", z, cond_str);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +209,7 @@ ssize_t Card::apply_abnormal_condition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Card::apply_ap_and_tp_adjust_assists_to_attack(
|
void Card::apply_ap_and_tp_adjust_assists_to_attack(
|
||||||
shared_ptr<const Card> attacker_card,
|
std::shared_ptr<const Card> attacker_card,
|
||||||
int16_t* inout_attacker_ap,
|
int16_t* inout_attacker_ap,
|
||||||
int16_t* in_defense_power,
|
int16_t* in_defense_power,
|
||||||
int16_t* inout_attacker_tp) const {
|
int16_t* inout_attacker_tp) const {
|
||||||
@@ -226,12 +222,12 @@ void Card::apply_ap_and_tp_adjust_assists_to_attack(
|
|||||||
switch (s->assist_server->get_active_assist_by_index(z)) {
|
switch (s->assist_server->get_active_assist_by_index(z)) {
|
||||||
case AssistEffect::POWERLESS_RAIN:
|
case AssistEffect::POWERLESS_RAIN:
|
||||||
if (is_nte) {
|
if (is_nte) {
|
||||||
*inout_attacker_ap = max<int16_t>(*inout_attacker_ap - 2, 0);
|
*inout_attacker_ap = std::max<int16_t>(*inout_attacker_ap - 2, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AssistEffect::BRAVE_WIND:
|
case AssistEffect::BRAVE_WIND:
|
||||||
if (is_nte) {
|
if (is_nte) {
|
||||||
*inout_attacker_ap = max<int16_t>(*inout_attacker_ap + 2, 0);
|
*inout_attacker_ap = std::max<int16_t>(*inout_attacker_ap + 2, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AssistEffect::SILENT_COLOSSEUM:
|
case AssistEffect::SILENT_COLOSSEUM:
|
||||||
@@ -284,7 +280,7 @@ bool Card::check_card_flag(uint32_t flags) const {
|
|||||||
|
|
||||||
void Card::commit_attack(
|
void Card::commit_attack(
|
||||||
int16_t damage,
|
int16_t damage,
|
||||||
shared_ptr<Card> attacker_card,
|
std::shared_ptr<Card> attacker_card,
|
||||||
G_ApplyConditionEffect_Ep3_6xB4x06* cmd,
|
G_ApplyConditionEffect_Ep3_6xB4x06* cmd,
|
||||||
size_t strike_number,
|
size_t strike_number,
|
||||||
int16_t* out_effective_damage) {
|
int16_t* out_effective_damage) {
|
||||||
@@ -302,7 +298,7 @@ void Card::commit_attack(
|
|||||||
auto eff = s->assist_server->get_active_assist_by_index(z);
|
auto eff = s->assist_server->get_active_assist_by_index(z);
|
||||||
if ((eff == AssistEffect::RANSOM) && (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
|
if ((eff == AssistEffect::RANSOM) && (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
|
||||||
uint8_t team_id = this->player_state()->get_team_id();
|
uint8_t team_id = this->player_state()->get_team_id();
|
||||||
int16_t exp_amount = clamp<int16_t>(s->team_exp[team_id], 0, effective_damage);
|
int16_t exp_amount = std::clamp<int16_t>(s->team_exp[team_id], 0, effective_damage);
|
||||||
s->team_exp[team_id] -= exp_amount;
|
s->team_exp[team_id] -= exp_amount;
|
||||||
effective_damage -= exp_amount;
|
effective_damage -= exp_amount;
|
||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
@@ -326,7 +322,7 @@ void Card::commit_attack(
|
|||||||
this->player_state()->stats.damage_taken += effective_damage;
|
this->player_state()->stats.damage_taken += effective_damage;
|
||||||
log.debug_f("updated stats");
|
log.debug_f("updated stats");
|
||||||
|
|
||||||
this->current_hp = clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp);
|
this->current_hp = std::clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp);
|
||||||
log.debug_f("hp set to {}", this->current_hp);
|
log.debug_f("hp set to {}", this->current_hp);
|
||||||
|
|
||||||
if ((effective_damage > 0) && (attacker_ps->stats.max_attack_damage < effective_damage)) {
|
if ((effective_damage > 0) && (attacker_ps->stats.max_attack_damage < effective_damage)) {
|
||||||
@@ -366,7 +362,7 @@ void Card::commit_attack(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t Card::compute_defense_power_for_attacker_card(shared_ptr<const Card> attacker_card) {
|
int16_t Card::compute_defense_power_for_attacker_card(std::shared_ptr<const Card> attacker_card) {
|
||||||
if (!attacker_card) {
|
if (!attacker_card) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -392,7 +388,7 @@ int16_t Card::compute_defense_power_for_attacker_card(shared_ptr<const Card> att
|
|||||||
return this->action_metadata.defense_power + this->action_metadata.defense_bonus;
|
return this->action_metadata.defense_power + this->action_metadata.defense_bonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Card::destroy_set_card(shared_ptr<Card> attacker_card) {
|
void Card::destroy_set_card(std::shared_ptr<Card> attacker_card) {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
auto ps = this->player_state();
|
auto ps = this->player_state();
|
||||||
|
|
||||||
@@ -490,7 +486,7 @@ int32_t Card::error_code_for_move_to_location(const Location& loc) const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
void Card::execute_attack(std::shared_ptr<Card> attacker_card) {
|
||||||
if (!attacker_card) {
|
if (!attacker_card) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -518,7 +514,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
|||||||
if (attacker_card->action_chain.chain.attack_medium == AttackMedium::UNKNOWN_03) {
|
if (attacker_card->action_chain.chain.attack_medium == AttackMedium::UNKNOWN_03) {
|
||||||
// Probably Resta
|
// Probably Resta
|
||||||
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
|
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
|
||||||
this->current_hp = min<int16_t>(this->current_hp + attacker_card->action_chain.chain.effective_tp, this->max_hp);
|
this->current_hp = std::min<int16_t>(this->current_hp + attacker_card->action_chain.chain.effective_tp, this->max_hp);
|
||||||
}
|
}
|
||||||
this->propagate_shared_hp_if_needed();
|
this->propagate_shared_hp_if_needed();
|
||||||
cmd.effect.tp = attacker_card->action_chain.chain.effective_tp;
|
cmd.effect.tp = attacker_card->action_chain.chain.effective_tp;
|
||||||
@@ -545,7 +541,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
|||||||
log.debug_f("assist adjusts ap={}, defense={}", attack_ap, defense_power);
|
log.debug_f("assist adjusts ap={}, defense={}", attack_ap, defense_power);
|
||||||
|
|
||||||
int16_t raw_damage = attack_ap - defense_power;
|
int16_t raw_damage = attack_ap - defense_power;
|
||||||
int16_t preliminary_damage = max<int16_t>(raw_damage, 0) - attack_tp;
|
int16_t preliminary_damage = std::max<int16_t>(raw_damage, 0) - attack_tp;
|
||||||
this->last_attack_preliminary_damage = preliminary_damage;
|
this->last_attack_preliminary_damage = preliminary_damage;
|
||||||
log.debug_f("raw_damage={}, preliminary_damange={}", raw_damage, preliminary_damage);
|
log.debug_f("raw_damage={}, preliminary_damange={}", raw_damage, preliminary_damage);
|
||||||
|
|
||||||
@@ -571,8 +567,8 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.effect.current_hp = is_nte ? attack_ap : min<int16_t>(attack_ap, 99);
|
cmd.effect.current_hp = is_nte ? attack_ap : std::min<int16_t>(attack_ap, 99);
|
||||||
cmd.effect.ap = is_nte ? defense_power : min<int16_t>(defense_power, 99);
|
cmd.effect.ap = is_nte ? defense_power : std::min<int16_t>(defense_power, 99);
|
||||||
cmd.effect.tp = attack_tp;
|
cmd.effect.tp = attack_tp;
|
||||||
|
|
||||||
auto ps = this->player_state();
|
auto ps = this->player_state();
|
||||||
@@ -583,7 +579,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
|||||||
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
|
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
|
||||||
int16_t final_effective_damage = 0;
|
int16_t final_effective_damage = 0;
|
||||||
target->commit_attack(preliminary_damage, attacker_card, &cmd, strike_num, &final_effective_damage);
|
target->commit_attack(preliminary_damage, attacker_card, &cmd, strike_num, &final_effective_damage);
|
||||||
ps->stats.action_card_negated_damage += max<int16_t>(0, this->current_defense_power - final_effective_damage);
|
ps->stats.action_card_negated_damage += std::max<int16_t>(0, this->current_defense_power - final_effective_damage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug_f("flag 2 set; committing zero-damage attack");
|
log.debug_f("flag 2 set; committing zero-damage attack");
|
||||||
@@ -618,7 +614,7 @@ const Condition* Card::find_condition(ConditionType cond_type) const {
|
|||||||
return const_cast<Card*>(this)->find_condition(cond_type);
|
return const_cast<Card*>(this)->find_condition(cond_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
|
std::shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
|
||||||
return this->def_entry;
|
return this->def_entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,10 +835,10 @@ void Card::set_current_and_max_hp(int16_t hp) {
|
|||||||
void Card::set_current_hp(
|
void Card::set_current_hp(
|
||||||
uint32_t new_hp, bool propagate_shared_hp, bool enforce_max_hp) {
|
uint32_t new_hp, bool propagate_shared_hp, bool enforce_max_hp) {
|
||||||
if (!enforce_max_hp) {
|
if (!enforce_max_hp) {
|
||||||
new_hp = max<int16_t>(new_hp, 0);
|
new_hp = std::max<int16_t>(new_hp, 0);
|
||||||
this->max_hp = max<int16_t>(this->max_hp, new_hp);
|
this->max_hp = std::max<int16_t>(this->max_hp, new_hp);
|
||||||
} else {
|
} else {
|
||||||
new_hp = clamp<int16_t>(new_hp, 0, this->max_hp);
|
new_hp = std::clamp<int16_t>(new_hp, 0, this->max_hp);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->current_hp = new_hp;
|
this->current_hp = new_hp;
|
||||||
@@ -955,11 +951,11 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this->action_chain.check_flag(0x10)) {
|
if (!this->action_chain.check_flag(0x10)) {
|
||||||
this->action_chain.chain.effective_ap = is_nte ? effective_ap : min<int16_t>(effective_ap, 99);
|
this->action_chain.chain.effective_ap = is_nte ? effective_ap : std::min<int16_t>(effective_ap, 99);
|
||||||
log.debug_f("set chain effective_ap = {}", this->action_chain.chain.effective_ap);
|
log.debug_f("set chain effective_ap = {}", this->action_chain.chain.effective_ap);
|
||||||
}
|
}
|
||||||
if (!this->action_chain.check_flag(0x20)) {
|
if (!this->action_chain.check_flag(0x20)) {
|
||||||
this->action_chain.chain.effective_tp = is_nte ? effective_tp : min<int16_t>(effective_tp, 99);
|
this->action_chain.chain.effective_tp = is_nte ? effective_tp : std::min<int16_t>(effective_tp, 99);
|
||||||
log.debug_f("set chain effective_tp = {}", this->action_chain.chain.effective_tp);
|
log.debug_f("set chain effective_tp = {}", this->action_chain.chain.effective_tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1099,7 +1095,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
|||||||
|
|
||||||
this->action_chain.chain.damage = is_nte
|
this->action_chain.chain.damage = is_nte
|
||||||
? (damage * this->action_chain.chain.damage_multiplier)
|
? (damage * this->action_chain.chain.damage_multiplier)
|
||||||
: min<int16_t>(damage * this->action_chain.chain.damage_multiplier, 99);
|
: std::min<int16_t>(damage * this->action_chain.chain.damage_multiplier, 99);
|
||||||
log.debug_f("overall chain damage = {} (base) * {} (mult) = {}",
|
log.debug_f("overall chain damage = {} (base) * {} (mult) = {}",
|
||||||
damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage);
|
damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage);
|
||||||
|
|
||||||
@@ -1108,7 +1104,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
|||||||
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, this_sh, this_sh, 2, nullptr);
|
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, this_sh, this_sh, 2, nullptr);
|
||||||
log.debug_f("applied action conditions (2)");
|
log.debug_f("applied action conditions (2)");
|
||||||
if (!is_nte && this->action_chain.check_flag(0x100)) {
|
if (!is_nte && this->action_chain.check_flag(0x100)) {
|
||||||
this->action_chain.chain.damage = min<int16_t>(this->action_chain.chain.damage + 5, 99);
|
this->action_chain.chain.damage = std::min<int16_t>(this->action_chain.chain.damage + 5, 99);
|
||||||
log.debug_f("(has flag 0x100) chain damage = {}", this->action_chain.chain.damage);
|
log.debug_f("(has flag 0x100) chain damage = {}", this->action_chain.chain.damage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1141,8 +1137,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string chain_str = this->action_chain.str(s);
|
log.debug_f("result computed as {}", this->action_chain.str(s));
|
||||||
log.debug_f("result computed as {}", chain_str);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,20 +1203,19 @@ void Card::move_phase_before() {
|
|||||||
this->server()->card_special->move_phase_before_for_card(this->shared_from_this());
|
this->server()->card_special->move_phase_before_for_card(this->shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Card::unknown_80236374(shared_ptr<Card> other_card, const ActionState* as) {
|
void Card::unknown_80236374(std::shared_ptr<Card> other_card, const ActionState* as) {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
auto log = s->log_stack(std::format("unknown_80236374(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id()));
|
auto log = s->log_stack(std::format("unknown_80236374(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id()));
|
||||||
|
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
if (as) {
|
if (as) {
|
||||||
string as_str = as->str(s);
|
log.debug_f("as = {}", as->str(s));
|
||||||
log.debug_f("as = {}", as_str);
|
|
||||||
} else {
|
} else {
|
||||||
log.debug_f("as = null");
|
log.debug_f("as = null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto check_card = [&](shared_ptr<Card> card) -> void {
|
auto check_card = [&](std::shared_ptr<Card> card) -> void {
|
||||||
if (card) {
|
if (card) {
|
||||||
if (!card->unknown_80236554(other_card, as)) {
|
if (!card->unknown_80236554(other_card, as)) {
|
||||||
log.debug_f("check_card @{:04X} #{:04X} => false", card->get_card_ref(), card->get_card_id());
|
log.debug_f("check_card @{:04X} #{:04X} => false", card->get_card_ref(), card->get_card_id());
|
||||||
@@ -1263,7 +1257,7 @@ void Card::unknown_802379DC(const ActionState& pa) {
|
|||||||
pa.defense_card_ref, this->shared_from_this(), pa.original_attacker_card_ref);
|
pa.defense_card_ref, this->shared_from_this(), pa.original_attacker_card_ref);
|
||||||
this->server()->card_special->unknown_8024A9D8(pa, 0xFFFF);
|
this->server()->card_special->unknown_8024A9D8(pa, 0xFFFF);
|
||||||
for (size_t z = 0; z < this->action_metadata.target_card_ref_count; z++) {
|
for (size_t z = 0; z < this->action_metadata.target_card_ref_count; z++) {
|
||||||
shared_ptr<Card> card = this->server()->card_for_set_card_ref(this->action_metadata.target_card_refs[z]);
|
std::shared_ptr<Card> card = this->server()->card_for_set_card_ref(this->action_metadata.target_card_refs[z]);
|
||||||
if (card) {
|
if (card) {
|
||||||
card->action_chain.set_action_subphase_from_card(this->shared_from_this());
|
card->action_chain.set_action_subphase_from_card(this->shared_from_this());
|
||||||
card->send_6xB4x4E_4C_4D_if_needed();
|
card->send_6xB4x4E_4C_4D_if_needed();
|
||||||
@@ -1302,7 +1296,7 @@ void Card::unknown_80237A90(const ActionState& pa, uint16_t action_card_ref) {
|
|||||||
if (this->action_chain.chain.target_card_ref_count == 0) {
|
if (this->action_chain.chain.target_card_ref_count == 0) {
|
||||||
for (size_t z = 0; (z < 4 * 9) && (pa.target_card_refs[z] != 0xFFFF); z++) {
|
for (size_t z = 0; (z < 4 * 9) && (pa.target_card_refs[z] != 0xFFFF); z++) {
|
||||||
this->action_chain.add_target_card_ref(pa.target_card_refs[z]);
|
this->action_chain.add_target_card_ref(pa.target_card_refs[z]);
|
||||||
shared_ptr<Card> sc_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
std::shared_ptr<Card> sc_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||||
if (sc_card) {
|
if (sc_card) {
|
||||||
sc_card->action_metadata.add_target_card_ref(this->card_ref);
|
sc_card->action_metadata.add_target_card_ref(this->card_ref);
|
||||||
sc_card->card_flags |= 8;
|
sc_card->card_flags |= 8;
|
||||||
@@ -1357,7 +1351,7 @@ bool Card::is_guard_item() const {
|
|||||||
return (this->def_entry->def.card_class() == CardClass::GUARD_ITEM);
|
return (this->def_entry->def.card_class() == CardClass::GUARD_ITEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as) {
|
bool Card::unknown_80236554(std::shared_ptr<Card> other_card, const ActionState* as) {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
auto log = s->log_stack(other_card
|
auto log = s->log_stack(other_card
|
||||||
? std::format("unknown_80236554(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id())
|
? std::format("unknown_80236554(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id())
|
||||||
@@ -1395,7 +1389,7 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->action_metadata.attack_bonus = max<int16_t>(attack_bonus, 0);
|
this->action_metadata.attack_bonus = std::max<int16_t>(attack_bonus, 0);
|
||||||
log.debug_f("attack_bonus = {}", this->action_metadata.attack_bonus);
|
log.debug_f("attack_bonus = {}", this->action_metadata.attack_bonus);
|
||||||
this->last_attack_preliminary_damage = 0;
|
this->last_attack_preliminary_damage = 0;
|
||||||
this->last_attack_final_damage = 0;
|
this->last_attack_final_damage = 0;
|
||||||
@@ -1421,17 +1415,17 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Card::execute_attack_on_all_valid_targets(shared_ptr<Card> attacker_card) {
|
void Card::execute_attack_on_all_valid_targets(std::shared_ptr<Card> attacker_card) {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||||
auto ps = s->player_states[client_id];
|
auto ps = s->player_states[client_id];
|
||||||
if (ps) {
|
if (ps) {
|
||||||
shared_ptr<Card> card = ps->get_sc_card();
|
std::shared_ptr<Card> card = ps->get_sc_card();
|
||||||
if (card) {
|
if (card) {
|
||||||
card->execute_attack(attacker_card);
|
card->execute_attack(attacker_card);
|
||||||
}
|
}
|
||||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||||
shared_ptr<Card> card = ps->get_set_card(set_index);
|
std::shared_ptr<Card> card = ps->get_set_card(set_index);
|
||||||
if (card) {
|
if (card) {
|
||||||
card->execute_attack(attacker_card);
|
card->execute_attack(attacker_card);
|
||||||
}
|
}
|
||||||
@@ -1476,7 +1470,7 @@ void Card::apply_attack_result() {
|
|||||||
temp_chain.chain.target_card_ref_count++;
|
temp_chain.chain.target_card_ref_count++;
|
||||||
} else if ((target_card->get_definition()->def.type == CardType::ITEM) && !this->action_chain.check_flag(0x02)) {
|
} else if ((target_card->get_definition()->def.type == CardType::ITEM) && !this->action_chain.check_flag(0x02)) {
|
||||||
auto target_ps = target_card->player_state();
|
auto target_ps = target_card->player_state();
|
||||||
shared_ptr<Card> candidate_card;
|
std::shared_ptr<Card> candidate_card;
|
||||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||||
auto set_card = target_ps->get_set_card(set_index);
|
auto set_card = target_ps->get_set_card(set_index);
|
||||||
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
|
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
|
||||||
@@ -1555,12 +1549,11 @@ void Card::apply_attack_result() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string as_str = as.str(s);
|
log.debug_f("as constructed as {}", as.str(s));
|
||||||
log.debug_f("as constructed as {}", as_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t z = 0; z < this->action_chain.chain.target_card_ref_count; z++) {
|
for (size_t z = 0; z < this->action_chain.chain.target_card_ref_count; z++) {
|
||||||
shared_ptr<Card> card = s->card_for_set_card_ref(this->action_chain.chain.target_card_refs[z]);
|
std::shared_ptr<Card> card = s->card_for_set_card_ref(this->action_chain.chain.target_card_refs[z]);
|
||||||
if (card) {
|
if (card) {
|
||||||
card->current_defense_power = card->action_metadata.attack_bonus;
|
card->current_defense_power = card->action_metadata.attack_bonus;
|
||||||
if (!this->action_chain.check_flag(0x40)) {
|
if (!this->action_chain.check_flag(0x40)) {
|
||||||
|
|||||||
+217
-230
File diff suppressed because it is too large
Load Diff
+234
-245
File diff suppressed because it is too large
Load Diff
@@ -905,7 +905,7 @@ struct PlayerConfig {
|
|||||||
/* 2270:211C */ be_uint32_t unknown_t3;
|
/* 2270:211C */ be_uint32_t unknown_t3;
|
||||||
// This visual config is copied to the player's main visual config when the player's name or proportions have
|
// This visual config is copied to the player's main visual config when the player's name or proportions have
|
||||||
// changed, or when certain buttons on the controller (L, R, X, Y) are held at game start time.
|
// changed, or when certain buttons on the controller (L, R, X, Y) are held at game start time.
|
||||||
/* 2274:2120 */ PlayerVisualConfig backup_visual;
|
/* 2274:2120 */ PlayerVisualConfigV123 backup_visual;
|
||||||
/* 22C4:2170 */ parray<uint8_t, 0x8C> unknown_a14;
|
/* 22C4:2170 */ parray<uint8_t, 0x8C> unknown_a14;
|
||||||
/* 2350:21FC */
|
/* 2350:21FC */
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
NameEntry::NameEntry() {
|
NameEntry::NameEntry() {
|
||||||
@@ -198,7 +196,7 @@ void DeckState::redraw_initial_hand(bool is_nte) {
|
|||||||
|
|
||||||
auto s = this->server.lock();
|
auto s = this->server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is missing");
|
throw std::runtime_error("server is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shuffle the deck, except the first 5 cards (which are about to be drawn).
|
// Shuffle the deck, except the first 5 cards (which are about to be drawn).
|
||||||
@@ -260,7 +258,7 @@ void DeckState::shuffle() {
|
|||||||
if (this->shuffle_enabled) {
|
if (this->shuffle_enabled) {
|
||||||
auto s = this->server.lock();
|
auto s = this->server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is missing");
|
throw std::runtime_error("server is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t max = this->num_drawable_cards();
|
size_t max = this->num_drawable_cards();
|
||||||
@@ -305,17 +303,17 @@ void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index)
|
|||||||
this->loop_enabled ? "true" : "false");
|
this->loop_enabled ? "true" : "false");
|
||||||
for (size_t z = 0; z < 31; z++) {
|
for (size_t z = 0; z < 31; z++) {
|
||||||
const auto& e = this->entries[z];
|
const auto& e = this->entries[z];
|
||||||
shared_ptr<const CardIndex::CardEntry> ce;
|
std::shared_ptr<const CardIndex::CardEntry> ce;
|
||||||
if (card_index) {
|
if (card_index) {
|
||||||
try {
|
try {
|
||||||
ce = card_index->definition_for_id(e.card_id);
|
ce = card_index->definition_for_id(e.card_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ce) {
|
if (ce) {
|
||||||
string name = ce->def.en_name.decode(Language::ENGLISH);
|
|
||||||
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} \"{}\" {}\n",
|
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} \"{}\" {}\n",
|
||||||
z, e.deck_index, this->card_refs[z], e.card_id, name, name_for_card_state(e.state));
|
z, e.deck_index, this->card_refs[z], e.card_id, ce->def.en_name.decode(Language::ENGLISH),
|
||||||
|
name_for_card_state(e.state));
|
||||||
} else {
|
} else {
|
||||||
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} {}\n",
|
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} {}\n",
|
||||||
z, e.deck_index, this->card_refs[z], e.card_id, name_for_card_state(e.state));
|
z, e.deck_index, this->card_refs[z], e.card_id, name_for_card_state(e.state));
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "MapState.hh"
|
#include "MapState.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
MapState::MapState() {
|
MapState::MapState() {
|
||||||
|
|||||||
+47
-53
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
PlayerState::PlayerState(uint8_t client_id, shared_ptr<Server> server)
|
PlayerState::PlayerState(uint8_t client_id, std::shared_ptr<Server> server)
|
||||||
: w_server(server),
|
: w_server(server),
|
||||||
client_id(client_id),
|
client_id(client_id),
|
||||||
num_hand_redraws_allowed(1),
|
num_hand_redraws_allowed(1),
|
||||||
@@ -46,10 +44,10 @@ void PlayerState::init() {
|
|||||||
if (s->player_states.at(this->client_id).get() != this) {
|
if (s->player_states.at(this->client_id).get() != this) {
|
||||||
// Note: The original code handles this, but we don't. This appears not to
|
// Note: The original code handles this, but we don't. This appears not to
|
||||||
// ever happen, so we didn't bother implementing it.
|
// ever happen, so we didn't bother implementing it.
|
||||||
throw logic_error("replacing a player state object is not permitted");
|
throw std::logic_error("replacing a player state object is not permitted");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->deck_state = make_shared<DeckState>(this->client_id, s->deck_entries[client_id]->card_ids, s);
|
this->deck_state = std::make_shared<DeckState>(this->client_id, s->deck_entries[client_id]->card_ids, s);
|
||||||
if (s->map_and_rules->rules.disable_deck_shuffle) {
|
if (s->map_and_rules->rules.disable_deck_shuffle) {
|
||||||
this->deck_state->disable_shuffle();
|
this->deck_state->disable_shuffle();
|
||||||
}
|
}
|
||||||
@@ -62,7 +60,7 @@ void PlayerState::init() {
|
|||||||
this->team_id = s->deck_entries[this->client_id]->team_id;
|
this->team_id = s->deck_entries[this->client_id]->team_id;
|
||||||
auto sc_ce = s->definition_for_card_ref(this->sc_card_ref);
|
auto sc_ce = s->definition_for_card_ref(this->sc_card_ref);
|
||||||
if (!sc_ce) {
|
if (!sc_ce) {
|
||||||
throw runtime_error("SC card definition is missing");
|
throw std::runtime_error("SC card definition is missing");
|
||||||
}
|
}
|
||||||
if (sc_ce->def.type == CardType::HUNTERS_SC) {
|
if (sc_ce->def.type == CardType::HUNTERS_SC) {
|
||||||
this->sc_card_type = CardType::HUNTERS_SC;
|
this->sc_card_type = CardType::HUNTERS_SC;
|
||||||
@@ -72,13 +70,13 @@ void PlayerState::init() {
|
|||||||
// In the original code, sc_card_type gets left as 0xFFFFFFFF (yes, it's a
|
// In the original code, sc_card_type gets left as 0xFFFFFFFF (yes, it's a
|
||||||
// uint32_t). This probably breaks some things later on, so we instead
|
// uint32_t). This probably breaks some things later on, so we instead
|
||||||
// prevent it upfront.
|
// prevent it upfront.
|
||||||
throw runtime_error("SC card is not a Hunters or Arkz SC");
|
throw std::runtime_error("SC card is not a Hunters or Arkz SC");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->hand_and_equip = make_shared<HandAndEquipState>();
|
this->hand_and_equip = std::make_shared<HandAndEquipState>();
|
||||||
this->card_short_statuses = make_shared<parray<CardShortStatus, 0x10>>();
|
this->card_short_statuses = std::make_shared<parray<CardShortStatus, 0x10>>();
|
||||||
this->set_card_action_chains = make_shared<parray<ActionChainWithConds, 9>>();
|
this->set_card_action_chains = std::make_shared<parray<ActionChainWithConds, 9>>();
|
||||||
this->set_card_action_metadatas = make_shared<parray<ActionMetadata, 9>>();
|
this->set_card_action_metadatas = std::make_shared<parray<ActionMetadata, 9>>();
|
||||||
|
|
||||||
this->hand_and_equip->clear_FF();
|
this->hand_and_equip->clear_FF();
|
||||||
for (size_t z = 0; z < 0x10; z++) {
|
for (size_t z = 0; z < 0x10; z++) {
|
||||||
@@ -89,7 +87,7 @@ void PlayerState::init() {
|
|||||||
this->set_card_action_metadatas->at(z).clear_FF();
|
this->set_card_action_metadatas->at(z).clear_FF();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->sc_card = make_shared<Card>(this->deck_state->sc_card_id(), this->sc_card_ref, this->client_id, s);
|
this->sc_card = std::make_shared<Card>(this->deck_state->sc_card_id(), this->sc_card_ref, this->client_id, s);
|
||||||
this->sc_card->init();
|
this->sc_card->init();
|
||||||
this->draw_initial_hand();
|
this->draw_initial_hand();
|
||||||
if (s->options.is_nte()) {
|
if (s->options.is_nte()) {
|
||||||
@@ -114,18 +112,18 @@ void PlayerState::init() {
|
|||||||
this->god_whim_can_use_hidden_cards = (s->deck_entries[this->client_id]->god_whim_flag != 3);
|
this->god_whim_can_use_hidden_cards = (s->deck_entries[this->client_id]->god_whim_flag != 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Server> PlayerState::server() {
|
std::shared_ptr<Server> PlayerState::server() {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Server> PlayerState::server() const {
|
std::shared_ptr<const Server> PlayerState::server() const {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -151,7 +149,7 @@ bool PlayerState::draw_cards_allowed() const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter_ps) {
|
void PlayerState::apply_assist_card_effect_on_set(std::shared_ptr<PlayerState> setter_ps) {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
|
|
||||||
uint16_t assist_card_id = this->set_assist_card_id;
|
uint16_t assist_card_id = this->set_assist_card_id;
|
||||||
@@ -244,7 +242,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
|||||||
case AssistEffect::LEGACY: {
|
case AssistEffect::LEGACY: {
|
||||||
uint16_t total_cost = 0;
|
uint16_t total_cost = 0;
|
||||||
for (ssize_t z = 7; z >= 0; z--) {
|
for (ssize_t z = 7; z >= 0; z--) {
|
||||||
shared_ptr<const Card> card = this->set_cards[z];
|
std::shared_ptr<const Card> card = this->set_cards[z];
|
||||||
if (card) {
|
if (card) {
|
||||||
auto ce = card->get_definition();
|
auto ce = card->get_definition();
|
||||||
uint8_t card_cost = ce->def.self_cost;
|
uint8_t card_cost = ce->def.self_cost;
|
||||||
@@ -258,7 +256,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
|||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
this->on_cards_destroyed();
|
this->on_cards_destroyed();
|
||||||
}
|
}
|
||||||
this->atk_points = min<uint8_t>(9, this->atk_points + (total_cost >> 1));
|
this->atk_points = std::min<uint8_t>(9, this->atk_points + (total_cost >> 1));
|
||||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
s->send_6xB4x05();
|
s->send_6xB4x05();
|
||||||
@@ -353,7 +351,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
|||||||
if (is_nte
|
if (is_nte
|
||||||
? (other_ps->assist_remaining_turns != 90 && other_ps->assist_remaining_turns != 99)
|
? (other_ps->assist_remaining_turns != 90 && other_ps->assist_remaining_turns != 99)
|
||||||
: (other_ps->assist_remaining_turns < 10)) {
|
: (other_ps->assist_remaining_turns < 10)) {
|
||||||
other_ps->assist_remaining_turns = min<uint8_t>(9, other_ps->assist_remaining_turns << 1);
|
other_ps->assist_remaining_turns = std::min<uint8_t>(9, other_ps->assist_remaining_turns << 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) {
|
for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) {
|
||||||
@@ -369,7 +367,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
|||||||
cond.remaining_turns <<= 1;
|
cond.remaining_turns <<= 1;
|
||||||
}
|
}
|
||||||
} else if (cond.remaining_turns < 10) {
|
} else if (cond.remaining_turns < 10) {
|
||||||
cond.remaining_turns = min<uint8_t>(9, cond.remaining_turns << 1);
|
cond.remaining_turns = std::min<uint8_t>(9, cond.remaining_turns << 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,7 +478,7 @@ void PlayerState::apply_dice_effects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (size_t die_index = 0; die_index < 2; die_index++) {
|
for (size_t die_index = 0; die_index < 2; die_index++) {
|
||||||
this->dice_results[die_index] = min<uint8_t>(this->dice_results[die_index], 9);
|
this->dice_results[die_index] = std::min<uint8_t>(this->dice_results[die_index], 9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,15 +816,14 @@ int32_t PlayerState::error_code_for_client_setting_card(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<uint16_t> PlayerState::get_all_cards_within_range(
|
std::vector<uint16_t> PlayerState::get_all_cards_within_range(
|
||||||
const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const {
|
const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
|
|
||||||
auto log = s->log_stack("get_all_cards_within_range: ");
|
auto log = s->log_stack("get_all_cards_within_range: ");
|
||||||
string loc_str = loc.str();
|
log.debug_f("loc={}, target_team_id={:02X}", loc.str(), target_team_id);
|
||||||
log.debug_f("loc={}, target_team_id={:02X}", loc_str, target_team_id);
|
|
||||||
|
|
||||||
vector<uint16_t> ret;
|
std::vector<uint16_t> ret;
|
||||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||||
auto other_ps = s->player_states[client_id];
|
auto other_ps = s->player_states[client_id];
|
||||||
if (other_ps && ((target_team_id == 0xFF) || (target_team_id == other_ps->get_team_id()))) {
|
if (other_ps && ((target_team_id == 0xFF) || (target_team_id == other_ps->get_team_id()))) {
|
||||||
@@ -845,7 +842,7 @@ void PlayerState::get_short_status_for_card_index_in_hand(size_t hand_index, Car
|
|||||||
stat->card_ref = this->card_refs[hand_index - 1];
|
stat->card_ref = this->card_refs[hand_index - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<DeckState> PlayerState::get_deck() {
|
std::shared_ptr<DeckState> PlayerState::get_deck() {
|
||||||
return this->deck_state;
|
return this->deck_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,11 +868,11 @@ uint16_t PlayerState::get_sc_card_id() const {
|
|||||||
return this->sc_card_id;
|
return this->sc_card_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Card> PlayerState::get_sc_card() {
|
std::shared_ptr<Card> PlayerState::get_sc_card() {
|
||||||
return this->sc_card;
|
return this->sc_card;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Card> PlayerState::get_sc_card() const {
|
std::shared_ptr<const Card> PlayerState::get_sc_card() const {
|
||||||
return this->sc_card;
|
return this->sc_card;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -887,11 +884,11 @@ CardType PlayerState::get_sc_card_type() const {
|
|||||||
return this->sc_card_type;
|
return this->sc_card_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Card> PlayerState::get_set_card(size_t set_index) {
|
std::shared_ptr<Card> PlayerState::get_set_card(size_t set_index) {
|
||||||
return (set_index < 8) ? this->set_cards[set_index] : nullptr;
|
return (set_index < 8) ? this->set_cards[set_index] : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Card> PlayerState::get_set_card(size_t set_index) const {
|
std::shared_ptr<const Card> PlayerState::get_set_card(size_t set_index) const {
|
||||||
return (set_index < 8) ? this->set_cards[set_index] : nullptr;
|
return (set_index < 8) ? this->set_cards[set_index] : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -964,7 +961,7 @@ uint16_t PlayerState::pop_from_discard_log(uint16_t) {
|
|||||||
bool PlayerState::move_card_to_location_by_card_index(size_t card_index, const Location& new_loc) {
|
bool PlayerState::move_card_to_location_by_card_index(size_t card_index, const Location& new_loc) {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
|
|
||||||
shared_ptr<Card> card;
|
std::shared_ptr<Card> card;
|
||||||
if (card_index == 0) {
|
if (card_index == 0) {
|
||||||
card = this->sc_card;
|
card = this->sc_card;
|
||||||
} else {
|
} else {
|
||||||
@@ -1010,7 +1007,7 @@ void PlayerState::move_null_hand_refs_to_end() {
|
|||||||
void PlayerState::on_cards_destroyed() {
|
void PlayerState::on_cards_destroyed() {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
|
|
||||||
unordered_multimap<uint16_t, bool> card_refs_map; // {card_ref: should_return_to_hand}
|
std::unordered_multimap<uint16_t, bool> card_refs_map; // {card_ref: should_return_to_hand}
|
||||||
for (size_t z = 0; z < 8; z++) {
|
for (size_t z = 0; z < 8; z++) {
|
||||||
auto card = this->set_cards[z];
|
auto card = this->set_cards[z];
|
||||||
if (!card || !(card->card_flags & 2)) {
|
if (!card || !(card->card_flags & 2)) {
|
||||||
@@ -1333,7 +1330,7 @@ bool PlayerState::set_card_from_hand(
|
|||||||
}
|
}
|
||||||
this->card_refs[card_index + 1] = card_ref;
|
this->card_refs[card_index + 1] = card_ref;
|
||||||
// Note: NTE doesn't call the destructor on the existing card, if there is one. Is that a bug?
|
// Note: NTE doesn't call the destructor on the existing card, if there is one. Is that a bug?
|
||||||
this->set_cards[card_index - 7] = make_shared<Card>(s->card_id_for_card_ref(card_ref), card_ref, this->client_id, s);
|
this->set_cards[card_index - 7] = std::make_shared<Card>(s->card_id_for_card_ref(card_ref), card_ref, this->client_id, s);
|
||||||
auto new_card = this->set_cards[card_index - 7];
|
auto new_card = this->set_cards[card_index - 7];
|
||||||
new_card->init();
|
new_card->init();
|
||||||
|
|
||||||
@@ -1433,7 +1430,7 @@ void PlayerState::set_initial_location() {
|
|||||||
|
|
||||||
static const uint8_t start_tile_defs_offset_for_team_size[4] = {0, 0, 1, 3};
|
static const uint8_t start_tile_defs_offset_for_team_size[4] = {0, 0, 1, 3};
|
||||||
if (num_team_players >= 4) {
|
if (num_team_players >= 4) {
|
||||||
throw logic_error("too many players on team");
|
throw std::logic_error("too many players on team");
|
||||||
}
|
}
|
||||||
size_t start_tile_def_index = start_tile_defs_offset_for_team_size[num_team_players] + player_index_within_team;
|
size_t start_tile_def_index = start_tile_defs_offset_for_team_size[num_team_players] + player_index_within_team;
|
||||||
uint8_t player_start_tile = mr->map.start_tile_definitions[this->team_id][start_tile_def_index];
|
uint8_t player_start_tile = mr->map.start_tile_definitions[this->team_id][start_tile_def_index];
|
||||||
@@ -1455,11 +1452,11 @@ void PlayerState::set_initial_location() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!start_tile_found) {
|
if (!start_tile_found) {
|
||||||
throw runtime_error("player start location not set");
|
throw std::runtime_error("player start location not set");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(shared_ptr<const Card> card) {
|
void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(std::shared_ptr<const Card> card) {
|
||||||
if (!card) {
|
if (!card) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1524,7 +1521,7 @@ bool PlayerState::subtract_or_check_atk_or_def_points_for_action(const ActionSta
|
|||||||
|
|
||||||
void PlayerState::subtract_atk_points(uint8_t cost) {
|
void PlayerState::subtract_atk_points(uint8_t cost) {
|
||||||
this->atk_points -= cost;
|
this->atk_points -= cost;
|
||||||
this->atk_points2 = min<uint8_t>(this->atk_points, this->atk_points2_max);
|
this->atk_points2 = std::min<uint8_t>(this->atk_points, this->atk_points2_max);
|
||||||
}
|
}
|
||||||
|
|
||||||
G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
|
G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
|
||||||
@@ -1571,7 +1568,7 @@ void PlayerState::set_random_assist_card_from_hand_for_free() {
|
|||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
bool is_nte = s->options.is_nte();
|
bool is_nte = s->options.is_nte();
|
||||||
|
|
||||||
vector<uint16_t> candidate_card_refs;
|
std::vector<uint16_t> candidate_card_refs;
|
||||||
for (size_t hand_index = 0; hand_index < 6; hand_index++) {
|
for (size_t hand_index = 0; hand_index < 6; hand_index++) {
|
||||||
uint16_t card_ref = this->card_refs[hand_index];
|
uint16_t card_ref = this->card_refs[hand_index];
|
||||||
auto ce = s->definition_for_card_ref(card_ref);
|
auto ce = s->definition_for_card_ref(card_ref);
|
||||||
@@ -1636,11 +1633,11 @@ void PlayerState::send_6xB4x04_if_needed(bool always_send) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<uint16_t> PlayerState::get_card_refs_within_range_from_all_players(
|
std::vector<uint16_t> PlayerState::get_card_refs_within_range_from_all_players(
|
||||||
const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const {
|
const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const {
|
||||||
auto s = this->server();
|
auto s = this->server();
|
||||||
|
|
||||||
vector<uint16_t> ret;
|
std::vector<uint16_t> ret;
|
||||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||||
auto other_ps = s->player_states[client_id];
|
auto other_ps = s->player_states[client_id];
|
||||||
if (other_ps && ((other_ps->get_sc_card_type() == type) || (type == CardType::ITEM))) {
|
if (other_ps && ((other_ps->get_sc_card_type() == type) || (type == CardType::ITEM))) {
|
||||||
@@ -1697,7 +1694,7 @@ void PlayerState::handle_before_turn_assist_effects() {
|
|||||||
break;
|
break;
|
||||||
case AssistEffect::ATK_DICE_2:
|
case AssistEffect::ATK_DICE_2:
|
||||||
// Note: This behavior doesn't match the card description. Is it supposed to add 2 or multiply by 2?
|
// Note: This behavior doesn't match the card description. Is it supposed to add 2 or multiply by 2?
|
||||||
this->atk_points = min<int16_t>(this->atk_points + 2, 9);
|
this->atk_points = std::min<int16_t>(this->atk_points + 2, 9);
|
||||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||||
break;
|
break;
|
||||||
case AssistEffect::SKIP_TURN:
|
case AssistEffect::SKIP_TURN:
|
||||||
@@ -1756,8 +1753,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
|||||||
size_t z = 0;
|
size_t z = 0;
|
||||||
do {
|
do {
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
log.debug_f("on action card ref {}", s->debug_str_for_card_ref(pa.action_card_refs[z]));
|
||||||
log.debug_f("on action card ref {}", ref_str);
|
|
||||||
}
|
}
|
||||||
card->unknown_80237A90(pa, pa.action_card_refs[z]);
|
card->unknown_80237A90(pa, pa.action_card_refs[z]);
|
||||||
card->unknown_802379BC(pa.action_card_refs[z]);
|
card->unknown_802379BC(pa.action_card_refs[z]);
|
||||||
@@ -1793,8 +1789,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
|||||||
auto target_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
auto target_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||||
if (target_card) {
|
if (target_card) {
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string ref_str = s->debug_str_for_card_ref(pa.target_card_refs[z]);
|
log.debug_f("on target card ref {}", s->debug_str_for_card_ref(pa.target_card_refs[z]));
|
||||||
log.debug_f("on target card ref {}", ref_str);
|
|
||||||
}
|
}
|
||||||
target_card->unknown_802379DC(pa);
|
target_card->unknown_802379DC(pa);
|
||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
@@ -1823,8 +1818,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
|||||||
}
|
}
|
||||||
for (size_t z = 0; (z < pa.action_card_refs.size()) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
for (size_t z = 0; (z < pa.action_card_refs.size()) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
log.debug_f("discarding {} from hand", s->debug_str_for_card_ref(pa.action_card_refs[z]));
|
||||||
log.debug_f("discarding {} from hand", ref_str);
|
|
||||||
}
|
}
|
||||||
this->discard_ref_from_hand(pa.action_card_refs[z]);
|
this->discard_ref_from_hand(pa.action_card_refs[z]);
|
||||||
}
|
}
|
||||||
@@ -1866,7 +1860,7 @@ void PlayerState::dice_phase_before() {
|
|||||||
this->send_set_card_updates();
|
this->send_set_card_updates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr<Card> card) {
|
void PlayerState::handle_homesick_assist_effect_from_bomb(std::shared_ptr<Card> card) {
|
||||||
if (!card) {
|
if (!card) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1998,13 +1992,13 @@ void PlayerState::roll_main_dice_or_apply_after_effects() {
|
|||||||
}
|
}
|
||||||
this->atk_points += s->team_dice_bonus[this->team_id];
|
this->atk_points += s->team_dice_bonus[this->team_id];
|
||||||
this->def_points += s->team_dice_bonus[this->team_id];
|
this->def_points += s->team_dice_bonus[this->team_id];
|
||||||
this->atk_points = clamp<uint8_t>(this->atk_points, 1, 9);
|
this->atk_points = std::clamp<uint8_t>(this->atk_points, 1, 9);
|
||||||
this->def_points = clamp<uint8_t>(this->def_points, 1, 9);
|
this->def_points = std::clamp<uint8_t>(this->def_points, 1, 9);
|
||||||
if (!s->options.is_nte()) {
|
if (!s->options.is_nte()) {
|
||||||
this->atk_bonuses = this->atk_points - atk_before_bonuses;
|
this->atk_bonuses = this->atk_points - atk_before_bonuses;
|
||||||
this->def_bonuses = this->def_points - def_before_bonuses;
|
this->def_bonuses = this->def_points - def_before_bonuses;
|
||||||
}
|
}
|
||||||
this->atk_points2 = min<uint8_t>(this->atk_points2_max, this->atk_points);
|
this->atk_points2 = std::min<uint8_t>(this->atk_points2_max, this->atk_points);
|
||||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2036,7 +2030,7 @@ void PlayerState::compute_team_dice_bonus_after_draw_phase() {
|
|||||||
uint8_t current_team_turn = s->get_current_team_turn();
|
uint8_t current_team_turn = s->get_current_team_turn();
|
||||||
uint8_t dice_boost = s->get_team_exp(current_team_turn) / (s->team_client_count[current_team_turn] * 12);
|
uint8_t dice_boost = s->get_team_exp(current_team_turn) / (s->team_client_count[current_team_turn] * 12);
|
||||||
s->card_special->adjust_dice_boost_if_team_has_condition_52(current_team_turn, &dice_boost, 0);
|
s->card_special->adjust_dice_boost_if_team_has_condition_52(current_team_turn, &dice_boost, 0);
|
||||||
s->team_dice_bonus[current_team_turn] = clamp<int16_t>(dice_boost, 0, 8);
|
s->team_dice_bonus[current_team_turn] = std::clamp<int16_t>(dice_boost, 0, 8);
|
||||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
Condition::Condition() {
|
Condition::Condition() {
|
||||||
@@ -61,7 +59,7 @@ void Condition::clear_FF() {
|
|||||||
this->unknown_a8 = 0xFF;
|
this->unknown_a8 = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Condition::str(shared_ptr<const Server> s) const {
|
std::string Condition::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
|
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
|
||||||
"def_eff_index={}, ref={}, value={}, giver_ref={} "
|
"def_eff_index={}, ref={}, value={}, giver_ref={} "
|
||||||
@@ -98,7 +96,7 @@ void EffectResult::clear() {
|
|||||||
this->dice_roll_value = 0;
|
this->dice_roll_value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string EffectResult::str(shared_ptr<const Server> s) const {
|
std::string EffectResult::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"EffectResult[att_ref={}, target_ref={}, value={}, cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, cond_index={}, dice={}]",
|
"EffectResult[att_ref={}, target_ref={}, value={}, cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, cond_index={}, dice={}]",
|
||||||
s->debug_str_for_card_ref(this->attacker_card_ref),
|
s->debug_str_for_card_ref(this->attacker_card_ref),
|
||||||
@@ -130,7 +128,7 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const {
|
|||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CardShortStatus::str(shared_ptr<const Server> s) const {
|
std::string CardShortStatus::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, u1={:04X}, max_hp={}, u2={}]",
|
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, u1={:04X}, max_hp={}, u2={}]",
|
||||||
s->debug_str_for_card_ref(this->card_ref),
|
s->debug_str_for_card_ref(this->card_ref),
|
||||||
@@ -178,7 +176,7 @@ void ActionState::clear() {
|
|||||||
this->unused2 = 0xFFFF;
|
this->unused2 = 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ActionState::str(shared_ptr<const Server> s) const {
|
std::string ActionState::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, def_ref={}, target_refs={}, action_refs={}, orig_attacker_ref={}]",
|
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, def_ref={}, target_refs={}, action_refs={}, orig_attacker_ref={}]",
|
||||||
this->client_id,
|
this->client_id,
|
||||||
@@ -222,7 +220,7 @@ bool ActionChain::operator!=(const ActionChain& other) const {
|
|||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ActionChain::str(shared_ptr<const Server> s) const {
|
std::string ActionChain::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, "
|
"ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, "
|
||||||
"attack_action_ref_count={}, medium={}, target_ref_count={}, subphase={}, strikes={}, damage_mult={}, attack_num={}, "
|
"attack_action_ref_count={}, medium={}, target_ref_count={}, subphase={}, strikes={}, damage_mult={}, attack_num={}, "
|
||||||
@@ -309,8 +307,8 @@ bool ActionChainWithConds::operator!=(const ActionChainWithConds& other) const {
|
|||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ActionChainWithConds::str(shared_ptr<const Server> s) const {
|
std::string ActionChainWithConds::str(std::shared_ptr<const Server> s) const {
|
||||||
string ret = "ActionChainWithConds[chain=";
|
std::string ret = "ActionChainWithConds[chain=";
|
||||||
ret += this->chain.str(s);
|
ret += this->chain.str(s);
|
||||||
ret += ", conds=[";
|
ret += ", conds=[";
|
||||||
for (size_t z = 0; z < this->conditions.size(); z++) {
|
for (size_t z = 0; z < this->conditions.size(); z++) {
|
||||||
@@ -383,7 +381,7 @@ void ActionChainWithConds::set_flags(uint32_t flags) {
|
|||||||
this->chain.flags |= flags;
|
this->chain.flags |= flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionChainWithConds::add_attack_action_card_ref(uint16_t card_ref, shared_ptr<Server> server) {
|
void ActionChainWithConds::add_attack_action_card_ref(uint16_t card_ref, std::shared_ptr<Server> server) {
|
||||||
if (card_ref != 0xFFFF) {
|
if (card_ref != 0xFFFF) {
|
||||||
this->chain.attack_action_card_refs[this->chain.attack_action_card_ref_count++] = card_ref;
|
this->chain.attack_action_card_refs[this->chain.attack_action_card_ref_count++] = card_ref;
|
||||||
}
|
}
|
||||||
@@ -397,7 +395,7 @@ void ActionChainWithConds::add_target_card_ref(uint16_t card_ref) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionChainWithConds::compute_attack_medium(shared_ptr<Server> server) {
|
void ActionChainWithConds::compute_attack_medium(std::shared_ptr<Server> server) {
|
||||||
this->chain.attack_medium = AttackMedium::PHYSICAL;
|
this->chain.attack_medium = AttackMedium::PHYSICAL;
|
||||||
for (size_t z = 0; z < this->chain.attack_action_card_ref_count; z++) {
|
for (size_t z = 0; z < this->chain.attack_action_card_ref_count; z++) {
|
||||||
uint16_t card_ref = this->chain.attack_action_card_refs[z];
|
uint16_t card_ref = this->chain.attack_action_card_refs[z];
|
||||||
@@ -437,7 +435,7 @@ bool ActionChainWithConds::get_condition_value(
|
|||||||
return any_found;
|
return any_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionChainWithConds::set_action_subphase_from_card(shared_ptr<const Card> card) {
|
void ActionChainWithConds::set_action_subphase_from_card(std::shared_ptr<const Card> card) {
|
||||||
this->chain.action_subphase = card->server()->get_current_action_subphase();
|
this->chain.action_subphase = card->server()->get_current_action_subphase();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +543,7 @@ bool ActionMetadata::operator!=(const ActionMetadata& other) const {
|
|||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ActionMetadata::str(shared_ptr<const Server> s) const {
|
std::string ActionMetadata::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, subphase={}, def_power={}, def_bonus={}, "
|
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, subphase={}, def_power={}, def_bonus={}, "
|
||||||
"att_bonus={}, flags={:08X}, target_refs={}, defense_refs={}, original_attacker_refs={}]",
|
"att_bonus={}, flags={:08X}, target_refs={}, defense_refs={}, original_attacker_refs={}]",
|
||||||
@@ -621,7 +619,7 @@ void ActionMetadata::add_target_card_ref(uint16_t card_ref) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ActionMetadata::add_defense_card_ref(
|
void ActionMetadata::add_defense_card_ref(
|
||||||
uint16_t defense_card_ref, shared_ptr<Card> card, uint16_t original_attacker_card_ref) {
|
uint16_t defense_card_ref, std::shared_ptr<Card> card, uint16_t original_attacker_card_ref) {
|
||||||
if ((defense_card_ref != 0xFFFF) && (this->defense_card_ref_count < 8)) {
|
if ((defense_card_ref != 0xFFFF) && (this->defense_card_ref_count < 8)) {
|
||||||
this->defense_card_refs[this->defense_card_ref_count] = defense_card_ref;
|
this->defense_card_refs[this->defense_card_ref_count] = defense_card_ref;
|
||||||
this->original_attacker_card_refs[this->defense_card_ref_count] = original_attacker_card_ref;
|
this->original_attacker_card_refs[this->defense_card_ref_count] = original_attacker_card_ref;
|
||||||
@@ -634,7 +632,7 @@ HandAndEquipState::HandAndEquipState() {
|
|||||||
this->clear();
|
this->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
|
std::string HandAndEquipState::str(std::shared_ptr<const Server> s) const {
|
||||||
return std::format(
|
return std::format(
|
||||||
"HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, a1={}, total_set_cost={}, is_cpu={}, assist_flags={:08X}, "
|
"HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, a1={}, total_set_cost={}, is_cpu={}, assist_flags={:08X}, "
|
||||||
"hand_refs={}, assist_ref={}, set_refs={}, sc_ref={}, hand_refs2={}, set_refs2={}, assist_ref2={}, assist_set_num={}, assist_card_id={}, "
|
"hand_refs={}, assist_ref={}, set_refs={}, sc_ref={}, hand_refs2={}, set_refs2={}, assist_ref2={}, assist_set_num={}, assist_card_id={}, "
|
||||||
@@ -777,7 +775,7 @@ uint8_t PlayerBattleStats::rank_for_score(float score) {
|
|||||||
|
|
||||||
const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
|
const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
|
||||||
if (rank >= RANK_THRESHOLD_COUNT + 1) {
|
if (rank >= RANK_THRESHOLD_COUNT + 1) {
|
||||||
throw invalid_argument("invalid rank");
|
throw std::invalid_argument("invalid rank");
|
||||||
}
|
}
|
||||||
return RANK_NAMES[rank];
|
return RANK_NAMES[rank];
|
||||||
}
|
}
|
||||||
@@ -842,12 +840,12 @@ static bool is_card_within_range(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<uint16_t> get_card_refs_within_range(
|
std::vector<uint16_t> get_card_refs_within_range(
|
||||||
const parray<uint8_t, 9 * 9>& range,
|
const parray<uint8_t, 9 * 9>& range,
|
||||||
const Location& loc,
|
const Location& loc,
|
||||||
const parray<CardShortStatus, 0x10>& short_statuses,
|
const parray<CardShortStatus, 0x10>& short_statuses,
|
||||||
phosg::PrefixedLogger* log) {
|
phosg::PrefixedLogger* log) {
|
||||||
vector<uint16_t> ret;
|
std::vector<uint16_t> ret;
|
||||||
if (is_card_within_range(range, loc, short_statuses[0], log)) {
|
if (is_card_within_range(range, loc, short_statuses[0], log)) {
|
||||||
if (log) {
|
if (log) {
|
||||||
log->debug_f("get_card_refs_within_range: sc card @{:04X} within range", short_statuses[0].card_ref);
|
log->debug_f("get_card_refs_within_range: sc card @{:04X} within range", short_statuses[0].card_ref);
|
||||||
|
|||||||
+57
-64
@@ -5,20 +5,17 @@
|
|||||||
#include "DataIndexes.hh"
|
#include "DataIndexes.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
void compute_effective_range(
|
void compute_effective_range(
|
||||||
parray<uint8_t, 9 * 9>& ret,
|
parray<uint8_t, 9 * 9>& ret,
|
||||||
shared_ptr<const CardIndex> card_index,
|
std::shared_ptr<const CardIndex> card_index,
|
||||||
uint16_t card_id,
|
uint16_t card_id,
|
||||||
const Location& loc,
|
const Location& loc,
|
||||||
shared_ptr<const MapAndRulesState> map_and_rules,
|
std::shared_ptr<const MapAndRulesState> map_and_rules,
|
||||||
phosg::PrefixedLogger* log) {
|
phosg::PrefixedLogger* log) {
|
||||||
if (log && log->should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log && log->should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string loc_str = loc.str();
|
log->debug_f("compute_effective_range: card_id=#{:04X}, loc={}", card_id, loc.str());
|
||||||
log->debug_f("compute_effective_range: card_id=#{:04X}, loc={}", card_id, loc_str);
|
|
||||||
log->debug_f("compute_effective_range: map_and_rules->map:");
|
log->debug_f("compute_effective_range: map_and_rules->map:");
|
||||||
map_and_rules->map.print(stderr);
|
map_and_rules->map.print(stderr);
|
||||||
}
|
}
|
||||||
@@ -28,10 +25,10 @@ void compute_effective_range(
|
|||||||
if (card_id == 0xFFFE) { // Heavy Fog: one tile directly in front
|
if (card_id == 0xFFFE) { // Heavy Fog: one tile directly in front
|
||||||
range_def[3] = 0x00000100;
|
range_def[3] = 0x00000100;
|
||||||
} else {
|
} else {
|
||||||
shared_ptr<const CardIndex::CardEntry> ce;
|
std::shared_ptr<const CardIndex::CardEntry> ce;
|
||||||
try {
|
try {
|
||||||
ce = card_index->definition_for_id(card_id);
|
ce = card_index->definition_for_id(card_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < 6; z++) {
|
for (size_t z = 0; z < 6; z++) {
|
||||||
@@ -95,7 +92,7 @@ void compute_effective_range(
|
|||||||
up_y = 9 - y - 1;
|
up_y = 9 - y - 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid direction");
|
throw std::logic_error("invalid direction");
|
||||||
}
|
}
|
||||||
ret[y * 9 + x] = decoded_range[up_y * 9 + up_x];
|
ret[y * 9 + x] = decoded_range[up_y * 9 + up_x];
|
||||||
if (log) {
|
if (log) {
|
||||||
@@ -117,9 +114,9 @@ void compute_effective_range(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool card_linkage_is_valid(
|
bool card_linkage_is_valid(
|
||||||
shared_ptr<const CardIndex::CardEntry> right_ce,
|
std::shared_ptr<const CardIndex::CardEntry> right_ce,
|
||||||
shared_ptr<const CardIndex::CardEntry> left_ce,
|
std::shared_ptr<const CardIndex::CardEntry> left_ce,
|
||||||
shared_ptr<const CardIndex::CardEntry> sc_ce,
|
std::shared_ptr<const CardIndex::CardEntry> sc_ce,
|
||||||
bool has_permission_effect) {
|
bool has_permission_effect) {
|
||||||
if (!right_ce) {
|
if (!right_ce) {
|
||||||
return false;
|
return false;
|
||||||
@@ -173,31 +170,27 @@ bool card_linkage_is_valid(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RulerServer::RulerServer(shared_ptr<Server> server)
|
RulerServer::RulerServer(std::shared_ptr<Server> server)
|
||||||
: w_server(server),
|
: w_server(server), team_id_for_client_id(0xFF), error_code1(0), error_code2(0), error_code3(0) {}
|
||||||
team_id_for_client_id(0xFF),
|
|
||||||
error_code1(0),
|
|
||||||
error_code2(0),
|
|
||||||
error_code3(0) {}
|
|
||||||
|
|
||||||
shared_ptr<Server> RulerServer::server() {
|
std::shared_ptr<Server> RulerServer::server() {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Server> RulerServer::server() const {
|
std::shared_ptr<const Server> RulerServer::server() const {
|
||||||
auto s = this->w_server.lock();
|
auto s = this->w_server.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw runtime_error("server is deleted");
|
throw std::runtime_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) {
|
ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) {
|
||||||
return const_cast<ActionChainWithConds*>(as_const(*this).action_chain_with_conds_for_card_ref(card_ref));
|
return const_cast<ActionChainWithConds*>(std::as_const(*this).action_chain_with_conds_for_card_ref(card_ref));
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) const {
|
const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) const {
|
||||||
@@ -398,7 +391,7 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
|
|||||||
last_action_card_index = z;
|
last_action_card_index = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto check_chain = [&]() -> optional<bool> {
|
auto check_chain = [&]() -> std::optional<bool> {
|
||||||
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref);
|
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref);
|
||||||
if (chain) {
|
if (chain) {
|
||||||
for (ssize_t cond_index = 8; cond_index >= 0; cond_index--) {
|
for (ssize_t cond_index = 8; cond_index >= 0; cond_index--) {
|
||||||
@@ -415,7 +408,7 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullopt;
|
return std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (is_nte) {
|
if (is_nte) {
|
||||||
@@ -767,7 +760,7 @@ bool RulerServer::check_move_path_and_get_cost(
|
|||||||
if (max_dist < 1) {
|
if (max_dist < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
max_dist = min<uint8_t>(max_dist, 9);
|
max_dist = std::min<uint8_t>(max_dist, 9);
|
||||||
|
|
||||||
const auto* short_status = this->short_status_for_card_ref(card_ref);
|
const auto* short_status = this->short_status_for_card_ref(card_ref);
|
||||||
if (!short_status) {
|
if (!short_status) {
|
||||||
@@ -1030,7 +1023,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CriterionCode::HUNTER_NON_ANDROID_SC: {
|
case CriterionCode::HUNTER_NON_ANDROID_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0001, // Orland
|
0x0001, // Orland
|
||||||
0x0002, // Kranz
|
0x0002, // Kranz
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
@@ -1059,7 +1052,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_HU_CLASS_MALE_SC: {
|
case CriterionCode::HUNTER_HU_CLASS_MALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0001, // Orland
|
0x0001, // Orland
|
||||||
0x0113, // Teifu
|
0x0113, // Teifu
|
||||||
0x02AA, // H-HUmar
|
0x02AA, // H-HUmar
|
||||||
@@ -1070,7 +1063,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_FEMALE_SC: {
|
case CriterionCode::HUNTER_FEMALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
0x0004, // Sil'fer
|
0x0004, // Sil'fer
|
||||||
0x0006, // Kylria
|
0x0006, // Kylria
|
||||||
@@ -1094,7 +1087,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_NON_RA_CLASS_HUMAN_SC: {
|
case CriterionCode::HUNTER_NON_RA_CLASS_HUMAN_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0001, // Orland
|
0x0001, // Orland
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
0x0004, // Sil'fer
|
0x0004, // Sil'fer
|
||||||
@@ -1117,7 +1110,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_HU_CLASS_ANDROID_SC: {
|
case CriterionCode::HUNTER_HU_CLASS_ANDROID_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0110, // Saligun
|
0x0110, // Saligun
|
||||||
0x0113, // Teifu
|
0x0113, // Teifu
|
||||||
0x02AC, // H-HUcast
|
0x02AC, // H-HUcast
|
||||||
@@ -1128,7 +1121,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_NON_RA_CLASS_NON_NEWMAN_SC: {
|
case CriterionCode::HUNTER_NON_RA_CLASS_NON_NEWMAN_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0001, // Orland
|
0x0001, // Orland
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
0x0110, // Saligun
|
0x0110, // Saligun
|
||||||
@@ -1148,7 +1141,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC: {
|
case CriterionCode::HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0001, // Orland
|
0x0001, // Orland
|
||||||
0x0002, // Kranz
|
0x0002, // Kranz
|
||||||
0x0005, // Guykild
|
0x0005, // Guykild
|
||||||
@@ -1166,7 +1159,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_HUNEWEARL_CLASS_SC: {
|
case CriterionCode::HUNTER_HUNEWEARL_CLASS_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0004, // Sil'fer
|
0x0004, // Sil'fer
|
||||||
0x02AB, // H-HUnewearl
|
0x02AB, // H-HUnewearl
|
||||||
0x02CF, // H-HUnewearl
|
0x02CF, // H-HUnewearl
|
||||||
@@ -1174,7 +1167,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_RA_CLASS_MALE_SC: {
|
case CriterionCode::HUNTER_RA_CLASS_MALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0002, // Kranz
|
0x0002, // Kranz
|
||||||
0x0005, // Guykild
|
0x0005, // Guykild
|
||||||
0x02AE, // H-RAmar
|
0x02AE, // H-RAmar
|
||||||
@@ -1185,7 +1178,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_RA_CLASS_FEMALE_SC: {
|
case CriterionCode::HUNTER_RA_CLASS_FEMALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0006, // Kylria
|
0x0006, // Kylria
|
||||||
0x0114, // Stella
|
0x0114, // Stella
|
||||||
0x02AF, // H-RAmarl
|
0x02AF, // H-RAmarl
|
||||||
@@ -1196,7 +1189,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_RA_OR_FO_CLASS_FEMALE_SC: {
|
case CriterionCode::HUNTER_RA_OR_FO_CLASS_FEMALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
0x0006, // Kylria
|
0x0006, // Kylria
|
||||||
0x0112, // Viviana
|
0x0112, // Viviana
|
||||||
@@ -1213,7 +1206,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_HU_OR_RA_CLASS_HUMAN_SC: {
|
case CriterionCode::HUNTER_HU_OR_RA_CLASS_HUMAN_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0001, // Orland
|
0x0001, // Orland
|
||||||
0x0002, // Kranz
|
0x0002, // Kranz
|
||||||
0x0004, // Sil'fer
|
0x0004, // Sil'fer
|
||||||
@@ -1230,7 +1223,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_RA_CLASS_ANDROID_SC: {
|
case CriterionCode::HUNTER_RA_CLASS_ANDROID_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0005, // Guykild
|
0x0005, // Guykild
|
||||||
0x0114, // Stella
|
0x0114, // Stella
|
||||||
0x02B0, // H-RAcast
|
0x02B0, // H-RAcast
|
||||||
@@ -1241,7 +1234,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_FO_CLASS_FEMALE_SC: {
|
case CriterionCode::HUNTER_FO_CLASS_FEMALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
0x0112, // Viviana
|
0x0112, // Viviana
|
||||||
0x02B3, // H-FOmarl
|
0x02B3, // H-FOmarl
|
||||||
@@ -1252,7 +1245,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_HUMAN_FEMALE_SC: {
|
case CriterionCode::HUNTER_HUMAN_FEMALE_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0003, // Ino'lis
|
0x0003, // Ino'lis
|
||||||
0x0004, // Sil'fer
|
0x0004, // Sil'fer
|
||||||
0x0006, // Kylria
|
0x0006, // Kylria
|
||||||
@@ -1269,7 +1262,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
|||||||
return ret && card_ids.count(card_id2);
|
return ret && card_ids.count(card_id2);
|
||||||
}
|
}
|
||||||
case CriterionCode::HUNTER_ANDROID_SC: {
|
case CriterionCode::HUNTER_ANDROID_SC: {
|
||||||
static const unordered_set<uint16_t> card_ids = {
|
static const std::unordered_set<uint16_t> card_ids = {
|
||||||
0x0005, // Guykild
|
0x0005, // Guykild
|
||||||
0x0110, // Saligun
|
0x0110, // Saligun
|
||||||
0x0113, // Teifu
|
0x0113, // Teifu
|
||||||
@@ -1433,14 +1426,14 @@ uint16_t RulerServer::compute_attack_or_defense_costs(
|
|||||||
final_cost = 0;
|
final_cost = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final_cost = max<int16_t>(final_cost, this->hand_and_equip_states[pa.client_id]->atk_points);
|
final_cost = std::max<int16_t>(final_cost, this->hand_and_equip_states[pa.client_id]->atk_points);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out_ally_cost) {
|
if (out_ally_cost) {
|
||||||
*out_ally_cost = total_ally_cost;
|
*out_ally_cost = total_ally_cost;
|
||||||
}
|
}
|
||||||
return max<int16_t>(final_cost, total_cost + assist_cost_bias);
|
return std::max<int16_t>(final_cost, total_cost + assist_cost_bias);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RulerServer::compute_effective_range_and_target_mode_for_attack(
|
bool RulerServer::compute_effective_range_and_target_mode_for_attack(
|
||||||
@@ -1627,7 +1620,7 @@ bool RulerServer::defense_card_can_apply_to_attack(
|
|||||||
bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionState& pa) const {
|
bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionState& pa) const {
|
||||||
auto ce = this->definition_for_card_ref(pa.action_card_refs[0]);
|
auto ce = this->definition_for_card_ref(pa.action_card_refs[0]);
|
||||||
if (!ce) {
|
if (!ce) {
|
||||||
throw runtime_error("defense card definition is missing");
|
throw std::runtime_error("defense card definition is missing");
|
||||||
}
|
}
|
||||||
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.original_attacker_card_ref);
|
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.original_attacker_card_ref);
|
||||||
if (chain->chain.attack_action_card_ref_count < 1) {
|
if (chain->chain.attack_action_card_ref_count < 1) {
|
||||||
@@ -1646,7 +1639,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionSta
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
|
std::shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
|
||||||
uint16_t card_id = this->card_id_for_card_ref(card_ref);
|
uint16_t card_id = this->card_id_for_card_ref(card_ref);
|
||||||
if (card_id == 0xFFFF) {
|
if (card_id == 0xFFFF) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -1841,7 +1834,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
|
|||||||
return -0x7E;
|
return -0x7E;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int16_t diff = max<int16_t>(summon_area_size - summon_cost, 0);
|
int16_t diff = std::max<int16_t>(summon_area_size - summon_cost, 0);
|
||||||
if (x_offset > 0) {
|
if (x_offset > 0) {
|
||||||
if (loc->x < summon_area_loc.x) {
|
if (loc->x < summon_area_loc.x) {
|
||||||
return -0x7E;
|
return -0x7E;
|
||||||
@@ -1860,7 +1853,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
|
|||||||
return -0x7E;
|
return -0x7E;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int16_t diff = max<int16_t>(summon_area_size - summon_cost, 0);
|
int16_t diff = std::max<int16_t>(summon_area_size - summon_cost, 0);
|
||||||
if (y_offset > 0) {
|
if (y_offset > 0) {
|
||||||
if (loc->y < summon_area_loc.y) {
|
if (loc->y < summon_area_loc.y) {
|
||||||
return -0x7E;
|
return -0x7E;
|
||||||
@@ -1974,7 +1967,7 @@ bool RulerServer::flood_fill_move_path(
|
|||||||
Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)};
|
Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)};
|
||||||
for (size_t dir_index = 0; dir_index < 3; dir_index++) {
|
for (size_t dir_index = 0; dir_index < 3; dir_index++) {
|
||||||
if (static_cast<uint8_t>(dirs[dir_index]) > 3) {
|
if (static_cast<uint8_t>(dirs[dir_index]) > 3) {
|
||||||
throw logic_error("invalid direction");
|
throw std::logic_error("invalid direction");
|
||||||
}
|
}
|
||||||
ret |= this->flood_fill_move_path(
|
ret |= this->flood_fill_move_path(
|
||||||
chain,
|
chain,
|
||||||
@@ -2017,7 +2010,7 @@ uint16_t RulerServer::get_ally_sc_card_ref(uint16_t card_ref) const {
|
|||||||
return 0xFFFF;
|
return 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint32_t card_id) const {
|
std::shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint32_t card_id) const {
|
||||||
return this->server()->definition_for_card_id(card_id);
|
return this->server()->definition_for_card_id(card_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2130,11 +2123,11 @@ bool RulerServer::get_creature_summon_area(uint8_t client_id, Location* out_loc,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) {
|
std::shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) {
|
||||||
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
|
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) const {
|
std::shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) const {
|
||||||
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
|
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2171,7 +2164,7 @@ ssize_t RulerServer::get_path_cost(
|
|||||||
path_length *= cond.value;
|
path_length *= cond.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return clamp<ssize_t>(path_length + cost_penalty, 0, 99);
|
return std::clamp<ssize_t>(path_length + cost_penalty, 0, 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionType RulerServer::get_pending_action_type(const ActionState& pa) const {
|
ActionType RulerServer::get_pending_action_type(const ActionState& pa) const {
|
||||||
@@ -2440,9 +2433,9 @@ bool RulerServer::is_defense_valid(const ActionState& pa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RulerServer::link_objects(
|
void RulerServer::link_objects(
|
||||||
shared_ptr<MapAndRulesState> map_and_rules,
|
std::shared_ptr<MapAndRulesState> map_and_rules,
|
||||||
shared_ptr<StateFlags> state_flags,
|
std::shared_ptr<StateFlags> state_flags,
|
||||||
shared_ptr<AssistServer> assist_server) {
|
std::shared_ptr<AssistServer> assist_server) {
|
||||||
this->map_and_rules = map_and_rules;
|
this->map_and_rules = map_and_rules;
|
||||||
this->state_flags = state_flags;
|
this->state_flags = state_flags;
|
||||||
this->assist_server = assist_server;
|
this->assist_server = assist_server;
|
||||||
@@ -2491,7 +2484,7 @@ size_t RulerServer::max_move_distance_for_card_ref(uint32_t card_ref) const {
|
|||||||
if (this->find_condition_on_card_ref(card_ref, ConditionType::SET_MV, &cond, nullptr, true)) {
|
if (this->find_condition_on_card_ref(card_ref, ConditionType::SET_MV, &cond, nullptr, true)) {
|
||||||
ret = cond.value;
|
ret = cond.value;
|
||||||
}
|
}
|
||||||
ret = max<ssize_t>(0, ret);
|
ret = std::max<ssize_t>(0, ret);
|
||||||
|
|
||||||
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id);
|
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id);
|
||||||
bool has_stamina_effect = false;
|
bool has_stamina_effect = false;
|
||||||
@@ -2505,7 +2498,7 @@ size_t RulerServer::max_move_distance_for_card_ref(uint32_t card_ref) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return has_stamina_effect ? 9 : min<ssize_t>(9, ret);
|
return has_stamina_effect ? 9 : std::min<ssize_t>(9, ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2566,11 +2559,11 @@ void RulerServer::offsets_for_direction(
|
|||||||
|
|
||||||
void RulerServer::register_player(
|
void RulerServer::register_player(
|
||||||
uint8_t client_id,
|
uint8_t client_id,
|
||||||
shared_ptr<HandAndEquipState> hes,
|
std::shared_ptr<HandAndEquipState> hes,
|
||||||
shared_ptr<parray<CardShortStatus, 0x10>> short_statuses,
|
std::shared_ptr<parray<CardShortStatus, 0x10>> short_statuses,
|
||||||
shared_ptr<DeckEntry> deck_entry,
|
std::shared_ptr<DeckEntry> deck_entry,
|
||||||
shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
|
std::shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
|
||||||
shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas) {
|
std::shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas) {
|
||||||
this->hand_and_equip_states[client_id] = hes;
|
this->hand_and_equip_states[client_id] = hes;
|
||||||
this->short_statuses[client_id] = short_statuses;
|
this->short_statuses[client_id] = short_statuses;
|
||||||
this->deck_entries[client_id] = deck_entry;
|
this->deck_entries[client_id] = deck_entry;
|
||||||
@@ -2656,7 +2649,7 @@ int32_t RulerServer::set_cost_for_card(uint8_t client_id, uint16_t card_ref) con
|
|||||||
// In NTE, Land Price is apparently 2x rather than 1.5x
|
// In NTE, Land Price is apparently 2x rather than 1.5x
|
||||||
ret = is_nte ? (ret << 1) : (ret + (ret >> 1));
|
ret = is_nte ? (ret << 1) : (ret + (ret >> 1));
|
||||||
} else if (eff == AssistEffect::DEFLATION) {
|
} else if (eff == AssistEffect::DEFLATION) {
|
||||||
ret = max<int32_t>(0, ret - 1);
|
ret = std::max<int32_t>(0, ret - 1);
|
||||||
} else if (eff == AssistEffect::INFLATION) {
|
} else if (eff == AssistEffect::INFLATION) {
|
||||||
ret++;
|
ret++;
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-119
@@ -7,11 +7,9 @@
|
|||||||
#include "../Revision.hh"
|
#include "../Revision.hh"
|
||||||
#include "../SendCommands.hh"
|
#include "../SendCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
// This is (obviously) not the original string. The original string is:
|
// This is (obviously) not the original std::string. The original std::string is:
|
||||||
// NTE: "03/05/29 18:00 by K.Toya"
|
// NTE: "03/05/29 18:00 by K.Toya"
|
||||||
// Final: "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
|
// Final: "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
|
||||||
static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
|
static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
|
||||||
@@ -27,7 +25,7 @@ void Server::PresenceEntry::clear() {
|
|||||||
this->is_cpu_player = 0;
|
this->is_cpu_player = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
Server::Server(std::shared_ptr<Lobby> lobby, Options&& options)
|
||||||
: lobby(lobby),
|
: lobby(lobby),
|
||||||
battle_record(lobby ? lobby->battle_record : nullptr),
|
battle_record(lobby ? lobby->battle_record : nullptr),
|
||||||
has_lobby(lobby != nullptr),
|
has_lobby(lobby != nullptr),
|
||||||
@@ -81,7 +79,7 @@ Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
|||||||
|
|
||||||
Server::~Server() noexcept(false) {
|
Server::~Server() noexcept(false) {
|
||||||
if (this->logger_stack.size() != 1) {
|
if (this->logger_stack.size() != 1) {
|
||||||
throw logic_error(std::format("incorrect logger stack size: expected 1, received {}", this->logger_stack.size()));
|
throw std::logic_error(std::format("incorrect logger stack size: expected 1, received {}", this->logger_stack.size()));
|
||||||
}
|
}
|
||||||
delete this->logger_stack.back();
|
delete this->logger_stack.back();
|
||||||
}
|
}
|
||||||
@@ -89,31 +87,31 @@ Server::~Server() noexcept(false) {
|
|||||||
void Server::init() {
|
void Server::init() {
|
||||||
this->log().info_f("Creating server with random seed {:08X}", this->options.rand_crypt->seed());
|
this->log().info_f("Creating server with random seed {:08X}", this->options.rand_crypt->seed());
|
||||||
|
|
||||||
this->map_and_rules = make_shared<MapAndRulesState>();
|
this->map_and_rules = std::make_shared<MapAndRulesState>();
|
||||||
this->num_clients_present = 0;
|
this->num_clients_present = 0;
|
||||||
this->overlay_state.clear();
|
this->overlay_state.clear();
|
||||||
for (size_t z = 0; z < 4; z++) {
|
for (size_t z = 0; z < 4; z++) {
|
||||||
this->presence_entries[z].clear();
|
this->presence_entries[z].clear();
|
||||||
this->deck_entries[z] = make_shared<DeckEntry>();
|
this->deck_entries[z] = std::make_shared<DeckEntry>();
|
||||||
this->name_entries[z].clear();
|
this->name_entries[z].clear();
|
||||||
this->name_entries_valid[z] = false;
|
this->name_entries_valid[z] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->card_special = make_shared<CardSpecial>(this->shared_from_this());
|
this->card_special = std::make_shared<CardSpecial>(this->shared_from_this());
|
||||||
|
|
||||||
// Note: The original implementation calls the default PSOV2Encryption constructor for random_crypt, which just uses
|
// Note: The original implementation calls the default PSOV2Encryption constructor for random_crypt, which just uses
|
||||||
// 0 as the seed. It then re-seeds the generator later. We instead expect the caller to provide a seeded generator,
|
// 0 as the seed. It then re-seeds the generator later. We instead expect the caller to provide a seeded generator,
|
||||||
// and we don't re-seed it at all.
|
// and we don't re-seed it at all.
|
||||||
// this->random_crypt = make_shared<PSOV2Encryption>(0);
|
// this->random_crypt = std::make_shared<PSOV2Encryption>(0);
|
||||||
|
|
||||||
this->state_flags = make_shared<StateFlags>();
|
this->state_flags = std::make_shared<StateFlags>();
|
||||||
|
|
||||||
this->clear_player_flags_after_dice_phase();
|
this->clear_player_flags_after_dice_phase();
|
||||||
|
|
||||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
||||||
|
|
||||||
this->assist_server = make_shared<AssistServer>(this->shared_from_this());
|
this->assist_server = std::make_shared<AssistServer>(this->shared_from_this());
|
||||||
this->ruler_server = make_shared<RulerServer>(this->shared_from_this());
|
this->ruler_server = std::make_shared<RulerServer>(this->shared_from_this());
|
||||||
this->ruler_server->link_objects(this->map_and_rules, this->state_flags, this->assist_server);
|
this->ruler_server->link_objects(this->map_and_rules, this->state_flags, this->assist_server);
|
||||||
|
|
||||||
this->send_6xB4x46();
|
this->send_6xB4x46();
|
||||||
@@ -135,7 +133,7 @@ Server::StackLogger::StackLogger(StackLogger&& other)
|
|||||||
: PrefixedLogger(std::move(other)),
|
: PrefixedLogger(std::move(other)),
|
||||||
server(other.server) {
|
server(other.server) {
|
||||||
if (this->server->logger_stack.back() != &other) {
|
if (this->server->logger_stack.back() != &other) {
|
||||||
throw logic_error("cannot move StackLogger unless it is the last one");
|
throw std::logic_error("cannot move StackLogger unless it is the last one");
|
||||||
}
|
}
|
||||||
this->server->logger_stack.back() = this;
|
this->server->logger_stack.back() = this;
|
||||||
}
|
}
|
||||||
@@ -144,7 +142,7 @@ Server::StackLogger& Server::StackLogger::operator=(StackLogger&& other) {
|
|||||||
this->PrefixedLogger::operator=(std::move(other));
|
this->PrefixedLogger::operator=(std::move(other));
|
||||||
this->server = other.server;
|
this->server = other.server;
|
||||||
if (this->server->logger_stack.back() != &other) {
|
if (this->server->logger_stack.back() != &other) {
|
||||||
throw logic_error("cannot move StackLogger unless it is the last one");
|
throw std::logic_error("cannot move StackLogger unless it is the last one");
|
||||||
}
|
}
|
||||||
this->server->logger_stack.back() = this;
|
this->server->logger_stack.back() = this;
|
||||||
return *this;
|
return *this;
|
||||||
@@ -152,7 +150,7 @@ Server::StackLogger& Server::StackLogger::operator=(StackLogger&& other) {
|
|||||||
|
|
||||||
Server::StackLogger::~StackLogger() noexcept(false) {
|
Server::StackLogger::~StackLogger() noexcept(false) {
|
||||||
if (this->server->logger_stack.back() != this) {
|
if (this->server->logger_stack.back() != this) {
|
||||||
throw logic_error("incorrect logger stack unwind order");
|
throw std::logic_error("incorrect logger stack unwind order");
|
||||||
}
|
}
|
||||||
this->server->logger_stack.pop_back();
|
this->server->logger_stack.pop_back();
|
||||||
}
|
}
|
||||||
@@ -171,8 +169,7 @@ std::string Server::debug_str_for_card_ref(uint16_t card_ref) const {
|
|||||||
}
|
}
|
||||||
auto ce = this->definition_for_card_ref(card_ref);
|
auto ce = this->definition_for_card_ref(card_ref);
|
||||||
if (ce) {
|
if (ce) {
|
||||||
string name = ce->def.en_name.decode();
|
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, ce->def.en_name.decode());
|
||||||
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, name);
|
|
||||||
} else {
|
} else {
|
||||||
return std::format("@{:04X} (missing)", card_ref);
|
return std::format("@{:04X} (missing)", card_ref);
|
||||||
}
|
}
|
||||||
@@ -184,8 +181,7 @@ std::string Server::debug_str_for_card_id(uint16_t card_id) const {
|
|||||||
}
|
}
|
||||||
auto ce = this->definition_for_card_id(card_id);
|
auto ce = this->definition_for_card_id(card_id);
|
||||||
if (ce) {
|
if (ce) {
|
||||||
string name = ce->def.en_name.decode();
|
return std::format("#{:04X} ({})", card_id, ce->def.en_name.decode());
|
||||||
return std::format("#{:04X} ({})", card_id, name);
|
|
||||||
} else {
|
} else {
|
||||||
return std::format("#{:04X} (missing)", card_id);
|
return std::format("#{:04X} (missing)", card_id);
|
||||||
}
|
}
|
||||||
@@ -209,17 +205,17 @@ int8_t Server::get_winner_team_id() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!team_player_counts[0] || !team_player_counts[1]) {
|
if (!team_player_counts[0] || !team_player_counts[1]) {
|
||||||
throw logic_error("at least one team has no players");
|
throw std::logic_error("at least one team has no players");
|
||||||
}
|
}
|
||||||
if (team_win_flag_counts[0] && team_win_flag_counts[1]) {
|
if (team_win_flag_counts[0] && team_win_flag_counts[1]) {
|
||||||
throw logic_error("both teams have winning players");
|
throw std::logic_error("both teams have winning players");
|
||||||
}
|
}
|
||||||
for (int8_t z = 0; z < 2; z++) {
|
for (int8_t z = 0; z < 2; z++) {
|
||||||
if (!team_win_flag_counts[z]) {
|
if (!team_win_flag_counts[z]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (team_win_flag_counts[z] != team_player_counts[z]) {
|
if (team_win_flag_counts[z] != team_player_counts[z]) {
|
||||||
throw logic_error("only some players on team have won");
|
throw std::logic_error("only some players on team have won");
|
||||||
}
|
}
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
@@ -236,10 +232,10 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
|||||||
if (this->has_lobby) {
|
if (this->has_lobby) {
|
||||||
auto l = this->lobby.lock();
|
auto l = this->lobby.lock();
|
||||||
if (!l) {
|
if (!l) {
|
||||||
throw runtime_error("lobby is deleted");
|
throw std::runtime_error("lobby is deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
string masked_data;
|
std::string masked_data;
|
||||||
if (enable_masking &&
|
if (enable_masking &&
|
||||||
!this->options.is_nte() &&
|
!this->options.is_nte() &&
|
||||||
!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING) &&
|
!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING) &&
|
||||||
@@ -267,7 +263,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
|||||||
|
|
||||||
void Server::send_6xB4x46() const {
|
void Server::send_6xB4x46() const {
|
||||||
// Note: This function is not part of the original implementation; it was factored out from its callsites in this
|
// Note: This function is not part of the original implementation; it was factored out from its callsites in this
|
||||||
// file and the strings were changed.
|
// file and the std::strings were changed.
|
||||||
|
|
||||||
// NTE doesn't have the date_str2 field, but we send it anyway to make debugging easier.
|
// NTE doesn't have the date_str2 field, but we send it anyway to make debugging easier.
|
||||||
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
||||||
@@ -275,12 +271,13 @@ void Server::send_6xB4x46() const {
|
|||||||
cmd.date_str1.encode(
|
cmd.date_str1.encode(
|
||||||
std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()),
|
std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()),
|
||||||
Language::ENGLISH);
|
Language::ENGLISH);
|
||||||
string build_date = phosg::format_time(BUILD_TIMESTAMP);
|
std::string build_date = phosg::format_time(BUILD_TIMESTAMP);
|
||||||
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
|
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
|
||||||
this->send(cmd);
|
this->send(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> map, Language language, bool is_nte) {
|
std::string Server::prepare_6xB6x41_map_definition(
|
||||||
|
std::shared_ptr<const MapIndex::Map> map, Language language, bool is_nte) {
|
||||||
auto vm = map->version(language);
|
auto vm = map->version(language);
|
||||||
|
|
||||||
const auto& compressed = vm->compressed(is_nte);
|
const auto& compressed = vm->compressed(is_nte);
|
||||||
@@ -295,7 +292,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
|
|||||||
|
|
||||||
void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) const {
|
void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) const {
|
||||||
if (this->last_chosen_map) {
|
if (this->last_chosen_map) {
|
||||||
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte());
|
std::string data = this->prepare_6xB6x41_map_definition(
|
||||||
|
this->last_chosen_map, ch->language, this->options.is_nte());
|
||||||
this->log().info_f(
|
this->log().info_f(
|
||||||
"Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
|
"Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
|
||||||
ch->send(0x6C, 0x00, data);
|
ch->send(0x6C, 0x00, data);
|
||||||
@@ -366,11 +364,12 @@ void Server::add_team_exp(uint8_t team_id, int32_t exp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->team_exp[team_id] = clamp<int16_t>(this->team_exp[team_id] + exp, 0, this->team_client_count[team_id] * 96);
|
this->team_exp[team_id] = std::clamp<int16_t>(
|
||||||
|
this->team_exp[team_id] + exp, 0, this->team_client_count[team_id] * 96);
|
||||||
|
|
||||||
uint8_t dice_boost = this->team_exp[team_id] / (this->team_client_count[team_id] * 12);
|
uint8_t dice_boost = this->team_exp[team_id] / (this->team_client_count[team_id] * 12);
|
||||||
this->card_special->adjust_dice_boost_if_team_has_condition_52(team_id, &dice_boost, 0);
|
this->card_special->adjust_dice_boost_if_team_has_condition_52(team_id, &dice_boost, 0);
|
||||||
this->team_dice_bonus[team_id] = min<uint8_t>(dice_boost, 8);
|
this->team_dice_bonus[team_id] = std::min<uint8_t>(dice_boost, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Server::advance_battle_phase() {
|
bool Server::advance_battle_phase() {
|
||||||
@@ -396,7 +395,7 @@ bool Server::advance_battle_phase() {
|
|||||||
this->dice_phase_before();
|
this->dice_phase_before();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid battle phase");
|
throw std::logic_error("invalid battle phase");
|
||||||
}
|
}
|
||||||
return this->check_for_battle_end();
|
return this->check_for_battle_end();
|
||||||
}
|
}
|
||||||
@@ -414,15 +413,15 @@ void Server::draw_phase_before() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
|
std::shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
|
||||||
try {
|
try {
|
||||||
return this->options.card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
|
return this->options.card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
std::shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
||||||
if (card_ref == 0xFFFF) {
|
if (card_ref == 0xFFFF) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -447,7 +446,7 @@ shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
std::shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
||||||
return const_cast<Server*>(this)->card_for_set_card_ref(card_ref);
|
return const_cast<Server*>(this)->card_for_set_card_ref(card_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,7 +583,7 @@ bool Server::check_for_battle_end() {
|
|||||||
void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
|
void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
|
||||||
auto ps = this->player_states.at(client_id);
|
auto ps = this->player_states.at(client_id);
|
||||||
if (!ps) {
|
if (!ps) {
|
||||||
throw runtime_error("player does not exist");
|
throw std::runtime_error("player does not exist");
|
||||||
}
|
}
|
||||||
if (card_id == 0xFFFF) {
|
if (card_id == 0xFFFF) {
|
||||||
ps->discard_set_assist_card();
|
ps->discard_set_assist_card();
|
||||||
@@ -599,12 +598,12 @@ void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
|
|||||||
void Server::force_destroy_field_character(uint8_t client_id, size_t visible_index) {
|
void Server::force_destroy_field_character(uint8_t client_id, size_t visible_index) {
|
||||||
auto ps = this->player_states.at(client_id);
|
auto ps = this->player_states.at(client_id);
|
||||||
if (!ps) {
|
if (!ps) {
|
||||||
throw runtime_error("player does not exist");
|
throw std::runtime_error("player does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Is it possible for there to be gaps in the set cards array? If not, we could just do a direct array lookup
|
// TODO: Is it possible for there to be gaps in the set cards array? If not, we could just do a direct array lookup
|
||||||
// here instead of this loop
|
// here instead of this loop
|
||||||
shared_ptr<Card> set_card = nullptr;
|
std::shared_ptr<Card> set_card = nullptr;
|
||||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||||
if (!ps->set_cards[set_index]) {
|
if (!ps->set_cards[set_index]) {
|
||||||
continue;
|
continue;
|
||||||
@@ -681,7 +680,7 @@ void Server::compute_all_map_occupied_bits() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Server::compute_team_dice_bonus(uint8_t team_id) {
|
void Server::compute_team_dice_bonus(uint8_t team_id) {
|
||||||
this->team_dice_bonus[team_id] = clamp<int16_t>(
|
this->team_dice_bonus[team_id] = std::clamp<int16_t>(
|
||||||
this->team_exp[team_id] / (this->team_client_count[team_id] * 12), 0, 8);
|
this->team_exp[team_id] / (this->team_client_count[team_id] * 12), 0, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,10 +699,10 @@ void Server::copy_player_states_to_prev_states() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
|
std::shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
|
||||||
try {
|
try {
|
||||||
return this->options.card_index->definition_for_id(card_id);
|
return this->options.card_index->definition_for_id(card_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -731,7 +730,7 @@ void Server::determine_first_team_turn() {
|
|||||||
this->team_client_count[0] = this->map_and_rules->num_team0_players;
|
this->team_client_count[0] = this->map_and_rules->num_team0_players;
|
||||||
this->team_client_count[1] = this->map_and_rules->num_players - this->team_client_count[0];
|
this->team_client_count[1] = this->map_and_rules->num_players - this->team_client_count[0];
|
||||||
if (this->team_client_count[0] == 0 || this->team_client_count[1] == 0) {
|
if (this->team_client_count[0] == 0 || this->team_client_count[1] == 0) {
|
||||||
throw runtime_error("one or both teams have no players");
|
throw std::runtime_error("one or both teams have no players");
|
||||||
}
|
}
|
||||||
this->first_team_turn = 0xFF;
|
this->first_team_turn = 0xFF;
|
||||||
while (this->first_team_turn == 0xFF) {
|
while (this->first_team_turn == 0xFF) {
|
||||||
@@ -927,7 +926,7 @@ void Server::end_action_phase() {
|
|||||||
bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
||||||
auto log = this->log_stack("enqueue_attack_or_defense: ");
|
auto log = this->log_stack("enqueue_attack_or_defense: ");
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string s = pa->str(this->shared_from_this());
|
std::string s = pa->str(this->shared_from_this());
|
||||||
log.debug_f("input: {}", s);
|
log.debug_f("input: {}", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -977,8 +976,8 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
|||||||
size_t attack_index = this->num_pending_attacks++;
|
size_t attack_index = this->num_pending_attacks++;
|
||||||
this->pending_attacks[attack_index] = *pa;
|
this->pending_attacks[attack_index] = *pa;
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string pa_str = this->pending_attacks[attack_index].str(this->shared_from_this());
|
log.debug_f("set pending attack {}: {}",
|
||||||
log.debug_f("set pending attack {}: {}", attack_index, pa_str);
|
attack_index, this->pending_attacks[attack_index].str(this->shared_from_this()));
|
||||||
}
|
}
|
||||||
ps->set_action_cards_for_action_state(*pa);
|
ps->set_action_cards_for_action_state(*pa);
|
||||||
log.debug_f("set action cards");
|
log.debug_f("set action cards");
|
||||||
@@ -1013,14 +1012,14 @@ uint8_t Server::get_current_team_turn() const {
|
|||||||
return this->current_team_turn1;
|
return this->current_team_turn1;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PlayerState> Server::get_player_state(uint8_t client_id) {
|
std::shared_ptr<PlayerState> Server::get_player_state(uint8_t client_id) {
|
||||||
if (client_id >= 4) {
|
if (client_id >= 4) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return this->player_states[client_id];
|
return this->player_states[client_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const PlayerState> Server::get_player_state(uint8_t client_id) const {
|
std::shared_ptr<const PlayerState> Server::get_player_state(uint8_t client_id) const {
|
||||||
if (client_id >= 4) {
|
if (client_id >= 4) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -1094,19 +1093,19 @@ void Server::move_phase_after() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<vector<uint16_t>, 5> DEFAULT_TRAP_CARD_IDS = {
|
static const std::array<std::vector<uint16_t>, 5> DEFAULT_TRAP_CARD_IDS = {
|
||||||
// Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
// Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
||||||
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
|
std::vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
|
||||||
// Blue: Gold Rush, Charity, Requiem
|
// Blue: Gold Rush, Charity, Requiem
|
||||||
vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
std::vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
||||||
// Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
// Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||||
vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
std::vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
||||||
// Green: Brave Wind, Homesick, Fly
|
// Green: Brave Wind, Homesick, Fly
|
||||||
vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
std::vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
||||||
// Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
// Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||||
vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
std::vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||||
|
|
||||||
const vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
|
const std::vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
|
||||||
if (trap_card_ids->empty()) {
|
if (trap_card_ids->empty()) {
|
||||||
trap_card_ids = &DEFAULT_TRAP_CARD_IDS.at(trap_type);
|
trap_card_ids = &DEFAULT_TRAP_CARD_IDS.at(trap_type);
|
||||||
}
|
}
|
||||||
@@ -1201,7 +1200,7 @@ int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) {
|
|||||||
bool has_ally_cost = false;
|
bool has_ally_cost = false;
|
||||||
uint8_t ally_cost = 0;
|
uint8_t ally_cost = 0;
|
||||||
uint8_t setter_client_id = 0xFF;
|
uint8_t setter_client_id = 0xFF;
|
||||||
shared_ptr<PlayerState> setter_ps = nullptr;
|
std::shared_ptr<PlayerState> setter_ps = nullptr;
|
||||||
cmd.card_ref = 0xFFFF;
|
cmd.card_ref = 0xFFFF;
|
||||||
for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||||
auto ce = this->definition_for_card_ref(pa.action_card_refs[z]);
|
auto ce = this->definition_for_card_ref(pa.action_card_refs[z]);
|
||||||
@@ -1509,7 +1508,7 @@ void Server::setup_and_start_battle() {
|
|||||||
this->name_entries[z].clear();
|
this->name_entries[z].clear();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this->player_states[z] = make_shared<PlayerState>(z, this->shared_from_this());
|
this->player_states[z] = std::make_shared<PlayerState>(z, this->shared_from_this());
|
||||||
this->player_states[z]->init();
|
this->player_states[z]->init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1523,7 +1522,7 @@ void Server::setup_and_start_battle() {
|
|||||||
}
|
}
|
||||||
auto card = ps->get_sc_card();
|
auto card = ps->get_sc_card();
|
||||||
if (card) {
|
if (card) {
|
||||||
team_hp[ps->get_team_id()] = min<int16_t>(team_hp[ps->get_team_id()], card->get_current_hp());
|
team_hp[ps->get_team_id()] = std::min<int16_t>(team_hp[ps->get_team_id()], card->get_current_hp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1739,7 +1738,7 @@ bool Server::update_registration_phase() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
const std::unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
||||||
{0x0B, &Server::handle_CAx0B_redraw_initial_hand},
|
{0x0B, &Server::handle_CAx0B_redraw_initial_hand},
|
||||||
{0x0C, &Server::handle_CAx0C_end_redraw_initial_hand_phase},
|
{0x0C, &Server::handle_CAx0C_end_redraw_initial_hand_phase},
|
||||||
{0x0D, &Server::handle_CAx0D_end_non_action_phase},
|
{0x0D, &Server::handle_CAx0D_end_non_action_phase},
|
||||||
@@ -1765,23 +1764,23 @@ const unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
|||||||
{0x49, &Server::handle_CAx49_card_counts},
|
{0x49, &Server::handle_CAx49_card_counts},
|
||||||
});
|
});
|
||||||
|
|
||||||
void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& data) {
|
void Server::on_server_data_input(std::shared_ptr<Client> sender_c, const std::string& data) {
|
||||||
auto header = check_size_t<G_CardBattleCommandHeader>(data, 0xFFFF);
|
auto header = check_size_t<G_CardBattleCommandHeader>(data, 0xFFFF);
|
||||||
size_t expected_size = header.size * 4;
|
size_t expected_size = header.size * 4;
|
||||||
if (expected_size < data.size()) {
|
if (expected_size < data.size()) {
|
||||||
phosg::print_data(stderr, data);
|
phosg::print_data(stderr, data);
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size()));
|
"command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size()));
|
||||||
}
|
}
|
||||||
if (header.subcommand != 0xB3) {
|
if (header.subcommand != 0xB3) {
|
||||||
throw runtime_error("server data command is not 6xB3");
|
throw std::runtime_error("server data command is not 6xB3");
|
||||||
}
|
}
|
||||||
|
|
||||||
handler_t handler = nullptr;
|
handler_t handler = nullptr;
|
||||||
try {
|
try {
|
||||||
handler = this->subcommand_handlers.at(header.subsubcommand);
|
handler = this->subcommand_handlers.at(header.subsubcommand);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error("unknown CAx subsubcommand");
|
throw std::runtime_error("unknown CAx subsubcommand");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->battle_record && this->battle_record->writable()) {
|
if (this->battle_record && this->battle_record->writable()) {
|
||||||
@@ -1791,17 +1790,17 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
|||||||
if ((sender_c && (sender_c->version() == Version::GC_EP3_NTE)) || !header.mask_key) {
|
if ((sender_c && (sender_c->version() == Version::GC_EP3_NTE)) || !header.mask_key) {
|
||||||
(this->*handler)(sender_c, data);
|
(this->*handler)(sender_c, data);
|
||||||
} else {
|
} else {
|
||||||
string unmasked_data = data;
|
std::string unmasked_data = data;
|
||||||
set_mask_for_ep3_game_command(unmasked_data.data(), unmasked_data.size(), 0);
|
set_mask_for_ep3_game_command(unmasked_data.data(), unmasked_data.size(), 0);
|
||||||
(this->*handler)(sender_c, unmasked_data);
|
(this->*handler)(sender_c, unmasked_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx0B_redraw_initial_hand(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx0B_redraw_initial_hand(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_RedrawInitialHand_Ep3_CAx0B>(data);
|
const auto& in_cmd = check_size_t<G_RedrawInitialHand_Ep3_CAx0B>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -1829,11 +1828,11 @@ void Server::handle_CAx0B_redraw_initial_hand(shared_ptr<Client>, const string&
|
|||||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx0C_end_redraw_initial_hand_phase(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EndInitialRedrawPhase_Ep3_CAx0C>(data);
|
const auto& in_cmd = check_size_t<G_EndInitialRedrawPhase_Ep3_CAx0C>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "HAND READY");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "HAND READY");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -1887,11 +1886,11 @@ void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, cons
|
|||||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx0D_end_non_action_phase(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx0D_end_non_action_phase(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EndNonAttackPhase_Ep3_CAx0D>(data);
|
const auto& in_cmd = check_size_t<G_EndNonAttackPhase_Ep3_CAx0D>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->options.is_nte()) {
|
if (!this->options.is_nte()) {
|
||||||
@@ -1909,11 +1908,11 @@ void Server::handle_CAx0D_end_non_action_phase(shared_ptr<Client>, const string&
|
|||||||
this->send(out_cmd_fin);
|
this->send(out_cmd_fin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx0E_discard_card_from_hand(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_DiscardCardFromHand_Ep3_CAx0E>(data);
|
const auto& in_cmd = check_size_t<G_DiscardCardFromHand_Ep3_CAx0E>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -1948,11 +1947,11 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const strin
|
|||||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx0F_set_card_from_hand(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_SetCardFromHand_Ep3_CAx0F>(data);
|
const auto& in_cmd = check_size_t<G_SetCardFromHand_Ep3_CAx0F>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -1994,11 +1993,11 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& d
|
|||||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code1);
|
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx10_move_fc_to_location(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_MoveFieldCharacter_Ep3_CAx10>(data);
|
const auto& in_cmd = check_size_t<G_MoveFieldCharacter_Ep3_CAx10>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -2034,11 +2033,11 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string&
|
|||||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code2);
|
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx11_enqueue_attack_or_defense(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EnqueueAttackOrDefense_Ep3_CAx11>(data);
|
const auto& in_cmd = check_size_t<G_EnqueueAttackOrDefense_Ep3_CAx11>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -2072,11 +2071,11 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const st
|
|||||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code3);
|
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx12_end_attack_list(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EndAttackList_Ep3_CAx12>(data);
|
const auto& in_cmd = check_size_t<G_EndAttackList_Ep3_CAx12>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t error_code = 0;
|
int32_t error_code = 0;
|
||||||
@@ -2097,7 +2096,7 @@ void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const string& data) {
|
void Server::handle_CAx13_update_map_during_setup_t(std::shared_ptr<Client> c, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<CmdT>(data);
|
const auto& in_cmd = check_size_t<CmdT>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "UPDATE MAP");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "UPDATE MAP");
|
||||||
|
|
||||||
@@ -2157,7 +2156,7 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client> c, const string& data) {
|
void Server::handle_CAx13_update_map_during_setup(std::shared_ptr<Client> c, const std::string& data) {
|
||||||
if (this->options.is_nte()) {
|
if (this->options.is_nte()) {
|
||||||
this->handle_CAx13_update_map_during_setup_t<G_SetMapState_Ep3NTE_CAx13>(c, data);
|
this->handle_CAx13_update_map_during_setup_t<G_SetMapState_Ep3NTE_CAx13>(c, data);
|
||||||
} else {
|
} else {
|
||||||
@@ -2165,7 +2164,7 @@ void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client> c, const st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx14_update_deck_during_setup(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_SetPlayerDeck_Ep3_CAx14>(data);
|
const auto& in_cmd = check_size_t<G_SetPlayerDeck_Ep3_CAx14>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK");
|
||||||
|
|
||||||
@@ -2188,7 +2187,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (verify_error) {
|
if (verify_error) {
|
||||||
throw runtime_error(std::format("invalid deck: -0x{:X}", verify_error));
|
throw std::runtime_error(std::format("invalid deck: -0x{:X}", verify_error));
|
||||||
}
|
}
|
||||||
if (!this->options.is_nte() && !(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
if (!this->options.is_nte() && !(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
||||||
this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids);
|
this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids);
|
||||||
@@ -2211,7 +2210,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx15_unused_hard_reset_server_state(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data);
|
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
|
||||||
|
|
||||||
@@ -2224,10 +2223,10 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, con
|
|||||||
// this->send_all_state_updates();
|
// this->send_all_state_updates();
|
||||||
// this->update_registration_phase();
|
// this->update_registration_phase();
|
||||||
// this->setup_and_start_battle();
|
// this->setup_and_start_battle();
|
||||||
throw runtime_error("hard reset command received");
|
throw std::runtime_error("hard reset command received");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx1B_update_player_name(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_SetPlayerName_Ep3_CAx1B>(data);
|
const auto& in_cmd = check_size_t<G_SetPlayerName_Ep3_CAx1B>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME");
|
this->send_debug_command_received_message(in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME");
|
||||||
|
|
||||||
@@ -2258,7 +2257,7 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
|
|||||||
this->send(out_cmd);
|
this->send(out_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx1D_start_battle(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_StartBattle_Ep3_CAx1D>(data);
|
const auto& in_cmd = check_size_t<G_StartBattle_Ep3_CAx1D>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "START BATTLE");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "START BATTLE");
|
||||||
|
|
||||||
@@ -2304,7 +2303,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx21_end_battle(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EndBattle_Ep3_CAx21>(data);
|
const auto& in_cmd = check_size_t<G_EndBattle_Ep3_CAx21>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "END BATTLE");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "END BATTLE");
|
||||||
if (this->setup_phase == SetupPhase::BATTLE_ENDED) {
|
if (this->setup_phase == SetupPhase::BATTLE_ENDED) {
|
||||||
@@ -2318,11 +2317,11 @@ void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx28_end_defense_list(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EndDefenseList_Ep3_CAx28>(data);
|
const auto& in_cmd = check_size_t<G_EndDefenseList_Ep3_CAx28>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
G_ActionResult_Ep3_6xB4x1E out_cmd_ack;
|
G_ActionResult_Ep3_6xB4x1E out_cmd_ack;
|
||||||
@@ -2369,13 +2368,13 @@ void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& dat
|
|||||||
this->send(out_cmd_fin);
|
this->send(out_cmd_fin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx2B_legacy_set_card(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx2B_legacy_set_card(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_ExecLegacyCard_Ep3_CAx2B>(data);
|
const auto& in_cmd = check_size_t<G_ExecLegacyCard_Ep3_CAx2B>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "EXEC LEGACY");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "EXEC LEGACY");
|
||||||
// Sega's original implementation does nothing here, so we do nothing as well.
|
// Sega's original implementation does nothing here, so we do nothing as well.
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx34_subtract_ally_atk_points(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_PhotonBlastRequest_Ep3_CAx34>(data);
|
const auto& in_cmd = check_size_t<G_PhotonBlastRequest_Ep3_CAx34>(data);
|
||||||
|
|
||||||
uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref);
|
uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref);
|
||||||
@@ -2450,11 +2449,11 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_AdvanceFromStartingRollsPhase_Ep3_CAx37>(data);
|
const auto& in_cmd = check_size_t<G_AdvanceFromStartingRollsPhase_Ep3_CAx37>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "CHOOSE ORDER");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "CHOOSE ORDER");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ps = this->player_states[in_cmd.client_id];
|
auto ps = this->player_states[in_cmd.client_id];
|
||||||
@@ -2479,19 +2478,19 @@ void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx3A_time_limit_expired(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(data);
|
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED");
|
||||||
// We don't need to do anything here because the overall time limit is tracked server-side instead.
|
// We don't need to do anything here because the overall time limit is tracked server-side instead.
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
|
void Server::handle_CAx40_map_list_request(std::shared_ptr<Client> sender_c, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_MapListRequest_Ep3_CAx40>(data);
|
const auto& in_cmd = check_size_t<G_MapListRequest_Ep3_CAx40>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "MAP LIST");
|
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "MAP LIST");
|
||||||
|
|
||||||
auto l = this->lobby.lock();
|
auto l = this->lobby.lock();
|
||||||
if (!l) {
|
if (!l) {
|
||||||
throw runtime_error("lobby is deleted");
|
throw std::runtime_error("lobby is deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t num_players = l ? l->count_clients() : 1;
|
size_t num_players = l ? l->count_clients() : 1;
|
||||||
@@ -2512,13 +2511,13 @@ void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const st
|
|||||||
|
|
||||||
void Server::send_6xB6x41_to_all_clients() const {
|
void Server::send_6xB6x41_to_all_clients() const {
|
||||||
if (!this->last_chosen_map) {
|
if (!this->last_chosen_map) {
|
||||||
throw logic_error("cannot send 6xB4x41 without a map chosen");
|
throw std::logic_error("cannot send 6xB4x41 without a map chosen");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto l = this->lobby.lock();
|
auto l = this->lobby.lock();
|
||||||
if (l) {
|
if (l) {
|
||||||
vector<string> map_commands_by_language;
|
std::vector<std::string> map_commands_by_language;
|
||||||
auto send_to_client = [&](shared_ptr<Client> c) -> void {
|
auto send_to_client = [&](std::shared_ptr<Client> c) -> void {
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2545,7 +2544,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
|||||||
if (this->battle_record && this->battle_record->writable()) {
|
if (this->battle_record && this->battle_record->writable()) {
|
||||||
// TODO: It's not great that we just pick the first one; ideally we'd put all of them in the recording and send
|
// TODO: It's not great that we just pick the first one; ideally we'd put all of them in the recording and send
|
||||||
// the appropriate one to the client in the playback lobby
|
// the appropriate one to the client in the playback lobby
|
||||||
for (string& data : map_commands_by_language) {
|
for (std::string& data : map_commands_by_language) {
|
||||||
if (!data.empty()) {
|
if (!data.empty()) {
|
||||||
this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data));
|
this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data));
|
||||||
break;
|
break;
|
||||||
@@ -2559,7 +2558,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx41_map_request(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& cmd = check_size_t<G_MapDataRequest_Ep3_CAx41>(data);
|
const auto& cmd = check_size_t<G_MapDataRequest_Ep3_CAx41>(data);
|
||||||
this->send_debug_command_received_message(cmd.header.subsubcommand, "MAP DATA");
|
this->send_debug_command_received_message(cmd.header.subsubcommand, "MAP DATA");
|
||||||
if (!this->options.tournament || (this->options.tournament->get_map()->map_number == cmd.map_number)) {
|
if (!this->options.tournament || (this->options.tournament->get_map()->map_number == cmd.map_number)) {
|
||||||
@@ -2568,11 +2567,11 @@ void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx48_end_turn(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_EndTurn_Ep3_CAx48>(data);
|
const auto& in_cmd = check_size_t<G_EndTurn_Ep3_CAx48>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
|
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
|
||||||
if (in_cmd.client_id >= 4) {
|
if (in_cmd.client_id >= 4) {
|
||||||
throw runtime_error("invalid client ID");
|
throw std::runtime_error("invalid client ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ps = this->get_player_state(in_cmd.client_id);
|
auto ps = this->get_player_state(in_cmd.client_id);
|
||||||
@@ -2585,7 +2584,7 @@ void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
|||||||
this->send(out_cmd);
|
this->send(out_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_CAx49_card_counts(shared_ptr<Client>, const string& data) {
|
void Server::handle_CAx49_card_counts(std::shared_ptr<Client>, const std::string& data) {
|
||||||
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data);
|
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data);
|
||||||
this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
|
this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
|
||||||
|
|
||||||
@@ -2612,7 +2611,7 @@ void Server::compute_losing_team_id_and_add_winner_flags(uint32_t flags) {
|
|||||||
uint32_t winner_flags = flags | AssistFlag::HAS_WON_BATTLE | AssistFlag::WINNER_DECIDED_BY_DEFEAT;
|
uint32_t winner_flags = flags | AssistFlag::HAS_WON_BATTLE | AssistFlag::WINNER_DECIDED_BY_DEFEAT;
|
||||||
|
|
||||||
int8_t losing_team_id = -1;
|
int8_t losing_team_id = -1;
|
||||||
array<uint32_t, 2> team_counts = {0, 0};
|
std::array<uint32_t, 2> team_counts{0, 0};
|
||||||
|
|
||||||
if (!is_nte) {
|
if (!is_nte) {
|
||||||
// First, check which team has more dead SCs
|
// First, check which team has more dead SCs
|
||||||
@@ -2747,8 +2746,7 @@ void Server::unknown_8023EEF4() {
|
|||||||
ActionState as = this->pending_attacks_with_cards[this->unknown_a14];
|
ActionState as = this->pending_attacks_with_cards[this->unknown_a14];
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
log.debug_f("card @{:04X} #{:04X} can attack", card->get_card_ref(), card->get_card_id());
|
log.debug_f("card @{:04X} #{:04X} can attack", card->get_card_ref(), card->get_card_id());
|
||||||
string as_str = as.str(this->shared_from_this());
|
log.debug_f("as: {}", as.str(this->shared_from_this()));
|
||||||
log.debug_f("as: {}", as_str);
|
|
||||||
}
|
}
|
||||||
if (is_nte) {
|
if (is_nte) {
|
||||||
this->replace_targets_due_to_destruction_nte(&as);
|
this->replace_targets_due_to_destruction_nte(&as);
|
||||||
@@ -2756,8 +2754,7 @@ void Server::unknown_8023EEF4() {
|
|||||||
this->replace_targets_due_to_destruction_or_conditions(&as);
|
this->replace_targets_due_to_destruction_or_conditions(&as);
|
||||||
}
|
}
|
||||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string as_str = as.str(this->shared_from_this());
|
log.debug_f("as after target replacement: {}", as.str(this->shared_from_this()));
|
||||||
log.debug_f("as after target replacement: {}", as_str);
|
|
||||||
}
|
}
|
||||||
if (this->any_target_exists_for_attack(as)) {
|
if (this->any_target_exists_for_attack(as)) {
|
||||||
log.debug_f("as is valid");
|
log.debug_f("as is valid");
|
||||||
@@ -2837,8 +2834,8 @@ void Server::execute_bomb_assist_effect() {
|
|||||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||||
auto card = ps->get_set_card(set_index);
|
auto card = ps->get_set_card(set_index);
|
||||||
if (card && !(card->card_flags & 2)) {
|
if (card && !(card->card_flags & 2)) {
|
||||||
max_hp = max<int16_t>(max_hp, card->get_current_hp());
|
max_hp = std::max<int16_t>(max_hp, card->get_current_hp());
|
||||||
min_hp = min<int16_t>(min_hp, card->get_current_hp());
|
min_hp = std::min<int16_t>(min_hp, card->get_current_hp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2875,7 +2872,7 @@ void Server::replace_targets_due_to_destruction_nte(ActionState* as) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto ps = target_card->player_state();
|
auto ps = target_card->player_state();
|
||||||
shared_ptr<Card> found_guard_item;
|
std::shared_ptr<Card> found_guard_item;
|
||||||
for (size_t z = 0; z < 8; z++) {
|
for (size_t z = 0; z < 8; z++) {
|
||||||
auto set_card = ps->get_set_card(z);
|
auto set_card = ps->get_set_card(z);
|
||||||
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
|
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
|
||||||
@@ -2915,7 +2912,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<uint16_t> phase1_replaced_card_refs;
|
std::vector<uint16_t> phase1_replaced_card_refs;
|
||||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||||
auto ps = this->get_player_state(client_id);
|
auto ps = this->get_player_state(client_id);
|
||||||
if (!attacker_card->action_chain.check_flag(0x200 << client_id)) {
|
if (!attacker_card->action_chain.check_flag(0x200 << client_id)) {
|
||||||
@@ -3005,7 +3002,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
|
|||||||
}
|
}
|
||||||
// as->target_card_refs[phase1_replaced_card_refs.size()] = 0xFFFF;
|
// as->target_card_refs[phase1_replaced_card_refs.size()] = 0xFFFF;
|
||||||
|
|
||||||
vector<uint16_t> phase2_replaced_card_refs;
|
std::vector<uint16_t> phase2_replaced_card_refs;
|
||||||
for (size_t z = 0; (z < 4 * 9) && (as->target_card_refs[z] != 0xFFFF); z++) {
|
for (size_t z = 0; (z < 4 * 9) && (as->target_card_refs[z] != 0xFFFF); z++) {
|
||||||
uint16_t target_card_ref = this->send_6xB4x06_if_card_ref_invalid(as->target_card_refs[z], 7);
|
uint16_t target_card_ref = this->send_6xB4x06_if_card_ref_invalid(as->target_card_refs[z], 7);
|
||||||
auto target_card = this->card_for_set_card_ref(target_card_ref);
|
auto target_card = this->card_for_set_card_ref(target_card_ref);
|
||||||
@@ -3105,13 +3102,14 @@ void Server::unknown_802402F4() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<shared_ptr<Card>> Server::const_cast_set_cards_v(const vector<shared_ptr<const Card>>& cards) {
|
std::vector<std::shared_ptr<Card>> Server::const_cast_set_cards_v(
|
||||||
|
const std::vector<std::shared_ptr<const Card>>& cards) {
|
||||||
// TODO: This is dumb. Figure out a not-dumb way to do this.
|
// TODO: This is dumb. Figure out a not-dumb way to do this.
|
||||||
vector<shared_ptr<Card>> ret;
|
std::vector<std::shared_ptr<Card>> ret;
|
||||||
for (auto const_card : cards) {
|
for (auto const_card : cards) {
|
||||||
auto mutable_card = this->card_for_set_card_ref(const_card->get_card_ref());
|
auto mutable_card = this->card_for_set_card_ref(const_card->get_card_ref());
|
||||||
if (mutable_card.get() != const_card.get()) {
|
if (mutable_card.get() != const_card.get()) {
|
||||||
throw logic_error("inconsistent set cards index");
|
throw std::logic_error("inconsistent set cards index");
|
||||||
}
|
}
|
||||||
ret.emplace_back(mutable_card);
|
ret.emplace_back(mutable_card);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace Episode3 {
|
|||||||
// RulerServer.hh/cc
|
// RulerServer.hh/cc
|
||||||
// Server.hh/cc
|
// Server.hh/cc
|
||||||
|
|
||||||
// Class ownership levels (classes may contain weak_ptrs but not shared_ptrs to classes at the same or higher level):
|
// Class ownership levels (classes may contain weak_ptrs but not std::shared_ptrs to classes at the same or higher level):
|
||||||
// - Server
|
// - Server
|
||||||
// - - RulerServer
|
// - - RulerServer
|
||||||
// - - - AssistServer
|
// - - - AssistServer
|
||||||
|
|||||||
+100
-116
@@ -7,23 +7,18 @@
|
|||||||
#include "../SendCommands.hh"
|
#include "../SendCommands.hh"
|
||||||
#include "../ServerState.hh"
|
#include "../ServerState.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|
||||||
Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const string& player_name)
|
Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const std::string& player_name)
|
||||||
: account_id(account_id),
|
: account_id(account_id), player_name(player_name) {}
|
||||||
player_name(player_name) {}
|
|
||||||
|
|
||||||
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
|
Tournament::PlayerEntry::PlayerEntry(std::shared_ptr<Client> c)
|
||||||
: account_id(c->login->account->account_id),
|
: account_id(c->login->account->account_id),
|
||||||
client(c),
|
client(c),
|
||||||
player_name(c->character_file()->disp.name.decode(c->language())) {}
|
player_name(c->character_file()->disp.visual.name.decode(c->language())) {}
|
||||||
|
|
||||||
Tournament::PlayerEntry::PlayerEntry(
|
Tournament::PlayerEntry::PlayerEntry(std::shared_ptr<const COMDeckDefinition> com_deck)
|
||||||
shared_ptr<const COMDeckDefinition> com_deck)
|
: account_id(0), com_deck(com_deck) {}
|
||||||
: account_id(0),
|
|
||||||
com_deck(com_deck) {}
|
|
||||||
|
|
||||||
bool Tournament::PlayerEntry::is_com() const {
|
bool Tournament::PlayerEntry::is_com() const {
|
||||||
return (this->com_deck != nullptr);
|
return (this->com_deck != nullptr);
|
||||||
@@ -33,8 +28,7 @@ bool Tournament::PlayerEntry::is_human() const {
|
|||||||
return (this->account_id != 0);
|
return (this->account_id != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Tournament::Team::Team(
|
Tournament::Team::Team(std::shared_ptr<Tournament> tournament, size_t index, size_t max_players)
|
||||||
shared_ptr<Tournament> tournament, size_t index, size_t max_players)
|
|
||||||
: tournament(tournament),
|
: tournament(tournament),
|
||||||
index(index),
|
index(index),
|
||||||
max_players(max_players),
|
max_players(max_players),
|
||||||
@@ -43,7 +37,7 @@ Tournament::Team::Team(
|
|||||||
num_rounds_cleared(0),
|
num_rounds_cleared(0),
|
||||||
is_active(true) {}
|
is_active(true) {}
|
||||||
|
|
||||||
string Tournament::Team::str() const {
|
std::string Tournament::Team::str() const {
|
||||||
size_t num_human_players = 0;
|
size_t num_human_players = 0;
|
||||||
size_t num_com_players = 0;
|
size_t num_com_players = 0;
|
||||||
for (const auto& player : this->players) {
|
for (const auto& player : this->players) {
|
||||||
@@ -51,7 +45,7 @@ string Tournament::Team::str() const {
|
|||||||
num_com_players += player.is_com();
|
num_com_players += player.is_com();
|
||||||
}
|
}
|
||||||
|
|
||||||
string ret = std::format("[Team/{} {} {}H/{}C/{}P name={} pass={} rounds={}",
|
std::string ret = std::format("[Team/{} {} {}H/{}C/{}P name={} pass={} rounds={}",
|
||||||
this->index, this->is_active ? "active" : "inactive",
|
this->index, this->is_active ? "active" : "inactive",
|
||||||
num_human_players, num_com_players, this->max_players, this->name,
|
num_human_players, num_com_players, this->max_players, this->name,
|
||||||
this->password, this->num_rounds_cleared);
|
this->password, this->num_rounds_cleared);
|
||||||
@@ -67,26 +61,27 @@ string Tournament::Team::str() const {
|
|||||||
return ret + "]";
|
return ret + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tournament::Team::register_player(shared_ptr<Client> c, const string& team_name, const string& password) {
|
void Tournament::Team::register_player(
|
||||||
|
std::shared_ptr<Client> c, const std::string& team_name, const std::string& password) {
|
||||||
if (this->players.size() >= this->max_players) {
|
if (this->players.size() >= this->max_players) {
|
||||||
throw runtime_error("team is full");
|
throw std::runtime_error("team is full");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->name.empty() && (password != this->password)) {
|
if (!this->name.empty() && (password != this->password)) {
|
||||||
throw runtime_error("incorrect password");
|
throw std::runtime_error("incorrect password");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tournament = this->tournament.lock();
|
auto tournament = this->tournament.lock();
|
||||||
if (!tournament) {
|
if (!tournament) {
|
||||||
throw runtime_error("tournament has been deleted");
|
throw std::runtime_error("tournament has been deleted");
|
||||||
}
|
}
|
||||||
if (!tournament->all_player_account_ids.emplace(c->login->account->account_id).second) {
|
if (!tournament->all_player_account_ids.emplace(c->login->account->account_id).second) {
|
||||||
throw runtime_error("player already registered in same tournament");
|
throw std::runtime_error("player already registered in same tournament");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& player : this->players) {
|
for (const auto& player : this->players) {
|
||||||
if (player.is_human() && (player.account_id == c->login->account->account_id)) {
|
if (player.is_human() && (player.account_id == c->login->account->account_id)) {
|
||||||
throw logic_error("player already registered in team but not in tournament");
|
throw std::logic_error("player already registered in team but not in tournament");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +120,7 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
|
|||||||
// Look through the pending matches to see if this team is involved in any of them
|
// Look through the pending matches to see if this team is involved in any of them
|
||||||
for (auto match : tournament->pending_matches) {
|
for (auto match : tournament->pending_matches) {
|
||||||
if (!match->preceding_a || !match->preceding_b) {
|
if (!match->preceding_a || !match->preceding_b) {
|
||||||
throw logic_error("zero-round match is pending after tournament registration phase");
|
throw std::logic_error("zero-round match is pending after tournament registration phase");
|
||||||
}
|
}
|
||||||
if (match->preceding_a->winner_team.get() == this) {
|
if (match->preceding_a->winner_team.get() == this) {
|
||||||
match->set_winner_team(match->preceding_b->winner_team);
|
match->set_winner_team(match->preceding_b->winner_team);
|
||||||
@@ -139,7 +134,7 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
|
|||||||
} else {
|
} else {
|
||||||
// If the tournament has not started yet, just remove the player from the team
|
// If the tournament has not started yet, just remove the player from the team
|
||||||
if (!tournament->all_player_account_ids.erase(account_id)) {
|
if (!tournament->all_player_account_ids.erase(account_id)) {
|
||||||
throw logic_error("player removed from team but not from tournament");
|
throw std::logic_error("player removed from team but not from tournament");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,27 +171,19 @@ size_t Tournament::Team::num_com_players() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Tournament::Match::Match(
|
Tournament::Match::Match(
|
||||||
shared_ptr<Tournament> tournament, shared_ptr<Match> preceding_a, shared_ptr<Match> preceding_b)
|
std::shared_ptr<Tournament> tournament, std::shared_ptr<Match> preceding_a, std::shared_ptr<Match> preceding_b)
|
||||||
: tournament(tournament),
|
: tournament(tournament), preceding_a(preceding_a), preceding_b(preceding_b), winner_team(nullptr), round_num(0) {
|
||||||
preceding_a(preceding_a),
|
|
||||||
preceding_b(preceding_b),
|
|
||||||
winner_team(nullptr),
|
|
||||||
round_num(0) {
|
|
||||||
if (this->preceding_a->round_num != this->preceding_b->round_num) {
|
if (this->preceding_a->round_num != this->preceding_b->round_num) {
|
||||||
throw logic_error("preceding matches have different round numbers");
|
throw std::logic_error("preceding matches have different round numbers");
|
||||||
}
|
}
|
||||||
this->round_num = this->preceding_a->round_num + 1;
|
this->round_num = this->preceding_a->round_num + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tournament::Match::Match(shared_ptr<Tournament> tournament, shared_ptr<Team> winner_team)
|
Tournament::Match::Match(std::shared_ptr<Tournament> tournament, std::shared_ptr<Team> winner_team)
|
||||||
: tournament(tournament),
|
: tournament(tournament), preceding_a(nullptr), preceding_b(nullptr), winner_team(winner_team), round_num(0) {}
|
||||||
preceding_a(nullptr),
|
|
||||||
preceding_b(nullptr),
|
|
||||||
winner_team(winner_team),
|
|
||||||
round_num(0) {}
|
|
||||||
|
|
||||||
string Tournament::Match::str() const {
|
std::string Tournament::Match::str() const {
|
||||||
string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
|
std::string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
|
||||||
return std::format("[Match round={} winner={}]", this->round_num, winner_str);
|
return std::format("[Match round={} winner={}]", this->round_num, winner_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,12 +249,12 @@ void Tournament::Match::on_winner_team_set() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tournament::Match::set_winner_team_without_triggers(shared_ptr<Team> team) {
|
void Tournament::Match::set_winner_team_without_triggers(std::shared_ptr<Team> team) {
|
||||||
if (!this->preceding_a || !this->preceding_b) {
|
if (!this->preceding_a || !this->preceding_b) {
|
||||||
throw logic_error("set_winner_team called on zero-round match");
|
throw std::logic_error("set_winner_team called on zero-round match");
|
||||||
}
|
}
|
||||||
if ((team != this->preceding_a->winner_team) && (team != this->preceding_b->winner_team)) {
|
if ((team != this->preceding_a->winner_team) && (team != this->preceding_b->winner_team)) {
|
||||||
throw logic_error("winner team did not participate in match");
|
throw std::logic_error("winner team did not participate in match");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->winner_team = team;
|
this->winner_team = team;
|
||||||
@@ -280,29 +267,29 @@ void Tournament::Match::set_winner_team_without_triggers(shared_ptr<Team> team)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tournament::Match::set_winner_team(shared_ptr<Team> team) {
|
void Tournament::Match::set_winner_team(std::shared_ptr<Team> team) {
|
||||||
this->set_winner_team_without_triggers(team);
|
this->set_winner_team_without_triggers(team);
|
||||||
this->on_winner_team_set();
|
this->on_winner_team_set();
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(shared_ptr<Team> team) const {
|
std::shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(std::shared_ptr<Team> team) const {
|
||||||
if (!this->preceding_a || !this->preceding_b) {
|
if (!this->preceding_a || !this->preceding_b) {
|
||||||
throw logic_error("zero-round matches do not have opponents");
|
throw std::logic_error("zero-round matches do not have opponents");
|
||||||
}
|
}
|
||||||
if (team == this->preceding_a->winner_team) {
|
if (team == this->preceding_a->winner_team) {
|
||||||
return this->preceding_b->winner_team;
|
return this->preceding_b->winner_team;
|
||||||
} else if (team == this->preceding_b->winner_team) {
|
} else if (team == this->preceding_b->winner_team) {
|
||||||
return this->preceding_a->winner_team;
|
return this->preceding_a->winner_team;
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("team is not registered for this match");
|
throw std::logic_error("team is not registered for this match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tournament::Tournament(
|
Tournament::Tournament(
|
||||||
shared_ptr<const MapIndex> map_index,
|
std::shared_ptr<const MapIndex> map_index,
|
||||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||||
const string& name,
|
const std::string& name,
|
||||||
shared_ptr<const MapIndex::Map> map,
|
std::shared_ptr<const MapIndex::Map> map,
|
||||||
const Rules& rules,
|
const Rules& rules,
|
||||||
size_t num_teams,
|
size_t num_teams,
|
||||||
uint8_t flags)
|
uint8_t flags)
|
||||||
@@ -317,18 +304,20 @@ Tournament::Tournament(
|
|||||||
current_state(State::REGISTRATION),
|
current_state(State::REGISTRATION),
|
||||||
menu_item_id(0xFFFFFFFF) {
|
menu_item_id(0xFFFFFFFF) {
|
||||||
if (this->num_teams < 4) {
|
if (this->num_teams < 4) {
|
||||||
throw invalid_argument("team count must be 4 or more");
|
throw std::invalid_argument("team count must be 4 or more");
|
||||||
}
|
}
|
||||||
if (this->num_teams > 32) {
|
if (this->num_teams > 32) {
|
||||||
throw invalid_argument("team count must be 32 or fewer");
|
throw std::invalid_argument("team count must be 32 or fewer");
|
||||||
}
|
}
|
||||||
if (this->num_teams & (this->num_teams - 1)) {
|
if (this->num_teams & (this->num_teams - 1)) {
|
||||||
throw invalid_argument("team count must be a power of 2");
|
throw std::invalid_argument("team count must be a power of 2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tournament::Tournament(
|
Tournament::Tournament(
|
||||||
shared_ptr<const MapIndex> map_index, shared_ptr<const COMDeckIndex> com_deck_index, const phosg::JSON& json)
|
std::shared_ptr<const MapIndex> map_index,
|
||||||
|
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||||
|
const phosg::JSON& json)
|
||||||
: log(std::format("[Tournament:{}] ", json.get_string("name"))),
|
: log(std::format("[Tournament:{}] ", json.get_string("name"))),
|
||||||
map_index(map_index),
|
map_index(map_index),
|
||||||
com_deck_index(com_deck_index),
|
com_deck_index(com_deck_index),
|
||||||
@@ -336,7 +325,7 @@ Tournament::Tournament(
|
|||||||
current_state(State::REGISTRATION) {}
|
current_state(State::REGISTRATION) {}
|
||||||
|
|
||||||
void Tournament::init() {
|
void Tournament::init() {
|
||||||
vector<size_t> team_index_to_rounds_cleared;
|
std::vector<size_t> team_index_to_rounds_cleared;
|
||||||
|
|
||||||
bool is_registration_complete;
|
bool is_registration_complete;
|
||||||
if (!this->source_json.is_null()) {
|
if (!this->source_json.is_null()) {
|
||||||
@@ -350,7 +339,7 @@ void Tournament::init() {
|
|||||||
is_registration_complete = this->source_json.get_bool("is_registration_complete");
|
is_registration_complete = this->source_json.get_bool("is_registration_complete");
|
||||||
|
|
||||||
for (const auto& team_json : this->source_json.get_list("teams")) {
|
for (const auto& team_json : this->source_json.get_list("teams")) {
|
||||||
auto& team = this->teams.emplace_back(make_shared<Team>(
|
auto& team = this->teams.emplace_back(std::make_shared<Team>(
|
||||||
this->shared_from_this(), this->teams.size(), team_json->get_int("max_players")));
|
this->shared_from_this(), this->teams.size(), team_json->get_int("max_players")));
|
||||||
team->name = team_json->get_string("name");
|
team->name = team_json->get_string("name");
|
||||||
team->password = team_json->get_string("password");
|
team->password = team_json->get_string("password");
|
||||||
@@ -367,7 +356,7 @@ void Tournament::init() {
|
|||||||
} else if (player_json->is_string()) {
|
} else if (player_json->is_string()) {
|
||||||
team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
|
team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid player spec");
|
throw std::runtime_error("invalid player spec");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,7 +367,7 @@ void Tournament::init() {
|
|||||||
} else {
|
} else {
|
||||||
// Create empty teams
|
// Create empty teams
|
||||||
while (this->teams.size() < this->num_teams) {
|
while (this->teams.size() < this->num_teams) {
|
||||||
auto t = make_shared<Team>(this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
|
auto t = std::make_shared<Team>(this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
|
||||||
this->teams.emplace_back(t);
|
this->teams.emplace_back(t);
|
||||||
}
|
}
|
||||||
is_registration_complete = false;
|
is_registration_complete = false;
|
||||||
@@ -390,12 +379,12 @@ void Tournament::init() {
|
|||||||
this->create_bracket_matches();
|
this->create_bracket_matches();
|
||||||
|
|
||||||
// Start with all zero-round matches in the match queue
|
// Start with all zero-round matches in the match queue
|
||||||
unordered_set<shared_ptr<Match>> match_queue;
|
std::unordered_set<std::shared_ptr<Match>> match_queue;
|
||||||
for (auto match : this->zero_round_matches) {
|
for (auto match : this->zero_round_matches) {
|
||||||
match_queue.emplace(match->following.lock());
|
match_queue.emplace(match->following.lock());
|
||||||
}
|
}
|
||||||
if (match_queue.count(nullptr)) {
|
if (match_queue.count(nullptr)) {
|
||||||
throw logic_error("null match in match queue");
|
throw std::logic_error("null match in match queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each match in the queue, either resolve it from the previous state or
|
// For each match in the queue, either resolve it from the previous state or
|
||||||
@@ -406,12 +395,12 @@ void Tournament::init() {
|
|||||||
match_queue.erase(match_it);
|
match_queue.erase(match_it);
|
||||||
|
|
||||||
if (!match->preceding_a->winner_team || !match->preceding_b->winner_team) {
|
if (!match->preceding_a->winner_team || !match->preceding_b->winner_team) {
|
||||||
throw logic_error("preceding matches are not resolved");
|
throw std::logic_error("preceding matches are not resolved");
|
||||||
}
|
}
|
||||||
size_t& a_rounds_cleared = team_index_to_rounds_cleared[match->preceding_a->winner_team->index];
|
size_t& a_rounds_cleared = team_index_to_rounds_cleared[match->preceding_a->winner_team->index];
|
||||||
size_t& b_rounds_cleared = team_index_to_rounds_cleared[match->preceding_b->winner_team->index];
|
size_t& b_rounds_cleared = team_index_to_rounds_cleared[match->preceding_b->winner_team->index];
|
||||||
if (a_rounds_cleared && b_rounds_cleared) {
|
if (a_rounds_cleared && b_rounds_cleared) {
|
||||||
throw runtime_error("both teams won the same match");
|
throw std::runtime_error("both teams won the same match");
|
||||||
}
|
}
|
||||||
if (!a_rounds_cleared && !b_rounds_cleared) {
|
if (!a_rounds_cleared && !b_rounds_cleared) {
|
||||||
this->pending_matches.emplace(match); // Neither team has won yet
|
this->pending_matches.emplace(match); // Neither team has won yet
|
||||||
@@ -434,7 +423,7 @@ void Tournament::init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this->final_match->winner_team == this->pending_matches.empty()) {
|
if (!this->final_match->winner_team == this->pending_matches.empty()) {
|
||||||
throw logic_error("there must be pending matches if and only if the final match is not resolved");
|
throw std::logic_error("there must be pending matches if and only if the final match is not resolved");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all matches are resolved, then the tournament is complete
|
// If all matches are resolved, then the tournament is complete
|
||||||
@@ -449,19 +438,19 @@ void Tournament::init() {
|
|||||||
|
|
||||||
void Tournament::create_bracket_matches() {
|
void Tournament::create_bracket_matches() {
|
||||||
if (this->teams.size() < 4) {
|
if (this->teams.size() < 4) {
|
||||||
throw logic_error("tournaments must have at least 4 teams");
|
throw std::logic_error("tournaments must have at least 4 teams");
|
||||||
}
|
}
|
||||||
if (this->teams.size() > 32) {
|
if (this->teams.size() > 32) {
|
||||||
throw logic_error("tournaments must have at most 32 teams");
|
throw std::logic_error("tournaments must have at most 32 teams");
|
||||||
}
|
}
|
||||||
if (this->teams.size() & (this->teams.size() - 1)) {
|
if (this->teams.size() & (this->teams.size() - 1)) {
|
||||||
throw logic_error("tournaments team count is not a power of 2");
|
throw std::logic_error("tournaments team count is not a power of 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the zero-round matches, and make them all pending if registration is still open
|
// Create the zero-round matches, and make them all pending if registration is still open
|
||||||
this->zero_round_matches.clear();
|
this->zero_round_matches.clear();
|
||||||
for (const auto& team : this->teams) {
|
for (const auto& team : this->teams) {
|
||||||
auto m = make_shared<Match>(this->shared_from_this(), team);
|
auto m = std::make_shared<Match>(this->shared_from_this(), team);
|
||||||
this->zero_round_matches.emplace_back(m);
|
this->zero_round_matches.emplace_back(m);
|
||||||
if (this->current_state == State::REGISTRATION) {
|
if (this->current_state == State::REGISTRATION) {
|
||||||
this->pending_matches.emplace(m);
|
this->pending_matches.emplace(m);
|
||||||
@@ -469,11 +458,11 @@ void Tournament::create_bracket_matches() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the bracket matches
|
// Create the bracket matches
|
||||||
vector<shared_ptr<Match>> current_round_matches = this->zero_round_matches;
|
std::vector<std::shared_ptr<Match>> current_round_matches = this->zero_round_matches;
|
||||||
while (current_round_matches.size() > 1) {
|
while (current_round_matches.size() > 1) {
|
||||||
vector<shared_ptr<Match>> next_round_matches;
|
std::vector<std::shared_ptr<Match>> next_round_matches;
|
||||||
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
|
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
|
||||||
auto m = make_shared<Match>(this->shared_from_this(), current_round_matches[z], current_round_matches[z + 1]);
|
auto m = std::make_shared<Match>(this->shared_from_this(), current_round_matches[z], current_round_matches[z + 1]);
|
||||||
current_round_matches[z]->following = m;
|
current_round_matches[z]->following = m;
|
||||||
current_round_matches[z + 1]->following = m;
|
current_round_matches[z + 1]->following = m;
|
||||||
next_round_matches.emplace_back(std::move(m));
|
next_round_matches.emplace_back(std::move(m));
|
||||||
@@ -516,26 +505,26 @@ phosg::JSON Tournament::json() const {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
|
std::shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
|
||||||
if (this->current_state != State::COMPLETE) {
|
if (this->current_state != State::COMPLETE) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (!this->final_match) {
|
if (!this->final_match) {
|
||||||
throw logic_error("tournament is complete but final match is missing");
|
throw std::logic_error("tournament is complete but final match is missing");
|
||||||
}
|
}
|
||||||
if (!this->final_match->winner_team) {
|
if (!this->final_match->winner_team) {
|
||||||
throw logic_error("tournament is complete but winner is not set");
|
throw std::logic_error("tournament is complete but winner is not set");
|
||||||
}
|
}
|
||||||
return this->final_match->winner_team;
|
return this->final_match->winner_team;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> team) const {
|
std::shared_ptr<Tournament::Match> Tournament::next_match_for_team(std::shared_ptr<Team> team) const {
|
||||||
if (this->current_state == Tournament::State::REGISTRATION) {
|
if (this->current_state == Tournament::State::REGISTRATION) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
for (auto match : this->pending_matches) {
|
for (auto match : this->pending_matches) {
|
||||||
if (!match->preceding_a || !match->preceding_b) {
|
if (!match->preceding_a || !match->preceding_b) {
|
||||||
throw logic_error("zero-round match is pending after tournament registration phase");
|
throw std::logic_error("zero-round match is pending after tournament registration phase");
|
||||||
}
|
}
|
||||||
if ((team == match->preceding_a->winner_team) || (team == match->preceding_b->winner_team)) {
|
if ((team == match->preceding_a->winner_team) || (team == match->preceding_b->winner_team)) {
|
||||||
return match;
|
return match;
|
||||||
@@ -544,11 +533,11 @@ shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> t
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament::Match> Tournament::get_final_match() const {
|
std::shared_ptr<Tournament::Match> Tournament::get_final_match() const {
|
||||||
return this->final_match;
|
return this->final_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id) const {
|
std::shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id) const {
|
||||||
if (!this->all_player_account_ids.count(account_id)) {
|
if (!this->all_player_account_ids.count(account_id)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -561,16 +550,16 @@ shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw logic_error("account ID registered in tournament but not in any team");
|
throw std::logic_error("account ID registered in tournament but not in any team");
|
||||||
}
|
}
|
||||||
|
|
||||||
const set<uint32_t>& Tournament::get_all_player_account_ids() const {
|
const std::set<uint32_t>& Tournament::get_all_player_account_ids() const {
|
||||||
return this->all_player_account_ids;
|
return this->all_player_account_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tournament::start() {
|
void Tournament::start() {
|
||||||
if (this->current_state != State::REGISTRATION) {
|
if (this->current_state != State::REGISTRATION) {
|
||||||
throw runtime_error("tournament has already started");
|
throw std::runtime_error("tournament has already started");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_com_teams = (this->flags & Flag::HAS_COM_TEAMS);
|
bool has_com_teams = (this->flags & Flag::HAS_COM_TEAMS);
|
||||||
@@ -584,7 +573,7 @@ void Tournament::start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (num_human_teams < (has_com_teams ? 1 : 2)) {
|
if (num_human_teams < (has_com_teams ? 1 : 2)) {
|
||||||
throw runtime_error("not enough registrants to start tournament");
|
throw std::runtime_error("not enough registrants to start tournament");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this->flags & Flag::SHUFFLE_ENTRIES) && (this->flags & Flag::RESIZE_ON_START)) {
|
if ((this->flags & Flag::SHUFFLE_ENTRIES) && (this->flags & Flag::RESIZE_ON_START)) {
|
||||||
@@ -642,11 +631,11 @@ void Tournament::start() {
|
|||||||
}
|
}
|
||||||
for (const auto& player : t->players) {
|
for (const auto& player : t->players) {
|
||||||
if (player.is_com()) {
|
if (player.is_com()) {
|
||||||
throw logic_error("non-human player on team before tournament start");
|
throw std::logic_error("non-human player on team before tournament start");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
|
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
|
||||||
throw runtime_error("not enough COM decks to complete team");
|
throw std::runtime_error("not enough COM decks to complete team");
|
||||||
}
|
}
|
||||||
// If we allow all-COM teams, or this is a 2v2 tournament and the team has only one human on it, add a COM
|
// If we allow all-COM teams, or this is a 2v2 tournament and the team has only one human on it, add a COM
|
||||||
if (has_com_teams || !t->players.empty()) {
|
if (has_com_teams || !t->players.empty()) {
|
||||||
@@ -687,10 +676,10 @@ void Tournament::send_all_state_updates_on_deletion() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string Tournament::bracket_str() const {
|
std::string Tournament::bracket_str() const {
|
||||||
string ret = std::format("Tournament \"{}\"\n", this->name);
|
std::string ret = std::format("Tournament \"{}\"\n", this->name);
|
||||||
|
|
||||||
function<void(shared_ptr<Match>, size_t)> add_match = [&](shared_ptr<Match> m, size_t indent_level) -> void {
|
std::function<void(std::shared_ptr<Match>, size_t)> add_match = [&](std::shared_ptr<Match> m, size_t indent_level) -> void {
|
||||||
ret.append(2 * indent_level, ' ');
|
ret.append(2 * indent_level, ' ');
|
||||||
ret += m->str();
|
ret += m->str();
|
||||||
if (this->pending_matches.count(m)) {
|
if (this->pending_matches.count(m)) {
|
||||||
@@ -707,13 +696,12 @@ string Tournament::bracket_str() const {
|
|||||||
|
|
||||||
auto en_vm = this->map->version(Language::ENGLISH);
|
auto en_vm = this->map->version(Language::ENGLISH);
|
||||||
if (en_vm) {
|
if (en_vm) {
|
||||||
string map_name = en_vm->map->name.decode(en_vm->language);
|
std::string map_name = en_vm->map->name.decode(en_vm->language);
|
||||||
ret += std::format(" Map: {:08X} ({})\n", this->map->map_number, map_name);
|
ret += std::format(" Map: {:08X} ({})\n", this->map->map_number, map_name);
|
||||||
} else {
|
} else {
|
||||||
ret += std::format(" Map: {:08X}\n", this->map->map_number);
|
ret += std::format(" Map: {:08X}\n", this->map->map_number);
|
||||||
}
|
}
|
||||||
string rules_str = this->rules.str();
|
ret += std::format(" Rules: {}\n", this->rules.str());
|
||||||
ret += std::format(" Rules: {}\n", rules_str);
|
|
||||||
ret += std::format(" Structure: {}, {} entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
|
ret += std::format(" Structure: {}, {} entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
|
||||||
ret += std::format(" COM teams: {}\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
|
ret += std::format(" COM teams: {}\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
|
||||||
ret += std::format(" Shuffle entries: {}\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
|
ret += std::format(" Shuffle entries: {}\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
|
||||||
@@ -739,14 +727,12 @@ string Tournament::bracket_str() const {
|
|||||||
if (this->current_state == State::REGISTRATION) {
|
if (this->current_state == State::REGISTRATION) {
|
||||||
ret += " Teams:\n";
|
ret += " Teams:\n";
|
||||||
for (const auto& team : this->teams) {
|
for (const auto& team : this->teams) {
|
||||||
string team_str = team->str();
|
ret += std::format(" {}\n", team->str());
|
||||||
ret += std::format(" {}\n", team_str);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ret += " Pending matches:\n";
|
ret += " Pending matches:\n";
|
||||||
for (const auto& match : this->pending_matches) {
|
for (const auto& match : this->pending_matches) {
|
||||||
string match_str = match->str();
|
ret += std::format(" {}\n", match->str());
|
||||||
ret += std::format(" {}\n", match_str);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,13 +741,11 @@ string Tournament::bracket_str() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TournamentIndex::TournamentIndex(
|
TournamentIndex::TournamentIndex(
|
||||||
shared_ptr<const MapIndex> map_index,
|
std::shared_ptr<const MapIndex> map_index,
|
||||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||||
const string& state_filename,
|
const std::string& state_filename,
|
||||||
bool skip_load_state)
|
bool skip_load_state)
|
||||||
: map_index(map_index),
|
: map_index(map_index), com_deck_index(com_deck_index), state_filename(state_filename) {
|
||||||
com_deck_index(com_deck_index),
|
|
||||||
state_filename(state_filename) {
|
|
||||||
if (this->state_filename.empty() || skip_load_state) {
|
if (this->state_filename.empty() || skip_load_state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -775,14 +759,14 @@ TournamentIndex::TournamentIndex(
|
|||||||
|
|
||||||
if (json.is_list()) {
|
if (json.is_list()) {
|
||||||
if (json.size() > 0x20) {
|
if (json.size() > 0x20) {
|
||||||
throw runtime_error("tournament phosg::JSON list length is incorrect");
|
throw std::runtime_error("tournament phosg::JSON list length is incorrect");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < min<size_t>(json.size(), 0x20); z++) {
|
for (size_t z = 0; z < std::min<size_t>(json.size(), 0x20); z++) {
|
||||||
if (!json.at(z).is_null()) {
|
if (!json.at(z).is_null()) {
|
||||||
auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, json.at(z));
|
auto tourn = std::make_shared<Tournament>(this->map_index, this->com_deck_index, json.at(z));
|
||||||
tourn->init();
|
tourn->init();
|
||||||
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
||||||
throw runtime_error("multiple tournaments have the same name: " + tourn->get_name());
|
throw std::runtime_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||||
}
|
}
|
||||||
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
|
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
|
||||||
this->menu_item_id_to_tournament.emplace_back(tourn);
|
this->menu_item_id_to_tournament.emplace_back(tourn);
|
||||||
@@ -790,20 +774,20 @@ TournamentIndex::TournamentIndex(
|
|||||||
}
|
}
|
||||||
} else if (json.is_dict()) {
|
} else if (json.is_dict()) {
|
||||||
if (json.size() > 0x20) {
|
if (json.size() > 0x20) {
|
||||||
throw runtime_error("tournament phosg::JSON dict length is incorrect");
|
throw std::runtime_error("tournament phosg::JSON dict length is incorrect");
|
||||||
}
|
}
|
||||||
for (const auto& it : json.as_dict()) {
|
for (const auto& it : json.as_dict()) {
|
||||||
auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second);
|
auto tourn = std::make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second);
|
||||||
tourn->init();
|
tourn->init();
|
||||||
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
||||||
// This is logic_error instead of runtime_error because phosg::JSON dicts already have unique keys
|
// This is logic_error instead of runtime_error because phosg::JSON dicts already have unique keys
|
||||||
throw logic_error("multiple tournaments have the same name: " + tourn->get_name());
|
throw std::logic_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||||
}
|
}
|
||||||
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
|
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
|
||||||
this->menu_item_id_to_tournament.emplace_back(tourn);
|
this->menu_item_id_to_tournament.emplace_back(tourn);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("tournament state root phosg::JSON is not a list or dict");
|
throw std::runtime_error("tournament state root phosg::JSON is not a list or dict");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,20 +803,20 @@ void TournamentIndex::save() const {
|
|||||||
phosg::save_file(this->state_filename, json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
phosg::save_file(this->state_filename, json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament> TournamentIndex::create_tournament(
|
std::shared_ptr<Tournament> TournamentIndex::create_tournament(
|
||||||
const string& name,
|
const std::string& name,
|
||||||
shared_ptr<const MapIndex::Map> map,
|
std::shared_ptr<const MapIndex::Map> map,
|
||||||
const Rules& rules,
|
const Rules& rules,
|
||||||
size_t num_teams,
|
size_t num_teams,
|
||||||
uint8_t flags) {
|
uint8_t flags) {
|
||||||
if (this->name_to_tournament.size() >= 0x20) {
|
if (this->name_to_tournament.size() >= 0x20) {
|
||||||
throw runtime_error("there can be at most 32 tournaments at a time");
|
throw std::runtime_error("there can be at most 32 tournaments at a time");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto t = make_shared<Tournament>(this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
|
auto t = std::make_shared<Tournament>(this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
|
||||||
t->init();
|
t->init();
|
||||||
if (!this->name_to_tournament.emplace(t->get_name(), t).second) {
|
if (!this->name_to_tournament.emplace(t->get_name(), t).second) {
|
||||||
throw runtime_error("a tournament with the same name already exists");
|
throw std::runtime_error("a tournament with the same name already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t z;
|
size_t z;
|
||||||
@@ -852,7 +836,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TournamentIndex::delete_tournament(const string& name) {
|
bool TournamentIndex::delete_tournament(const std::string& name) {
|
||||||
auto it = this->name_to_tournament.find(name);
|
auto it = this->name_to_tournament.find(name);
|
||||||
if (it == this->name_to_tournament.end()) {
|
if (it == this->name_to_tournament.end()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -869,7 +853,7 @@ bool TournamentIndex::delete_tournament(const string& name) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Tournament::Team> TournamentIndex::team_for_account_id(uint32_t account_id) const {
|
std::shared_ptr<Tournament::Team> TournamentIndex::team_for_account_id(uint32_t account_id) const {
|
||||||
for (const auto& it : this->name_to_tournament) {
|
for (const auto& it : this->name_to_tournament) {
|
||||||
const auto& tourn = it.second;
|
const auto& tourn = it.second;
|
||||||
auto team = tourn->team_for_account_id(account_id);
|
auto team = tourn->team_for_account_id(account_id);
|
||||||
@@ -880,7 +864,7 @@ shared_ptr<Tournament::Team> TournamentIndex::team_for_account_id(uint32_t accou
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TournamentIndex::link_client(shared_ptr<Client> c) {
|
void TournamentIndex::link_client(std::shared_ptr<Client> c) {
|
||||||
if (!is_ep3(c->version())) {
|
if (!is_ep3(c->version())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -898,7 +882,7 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw logic_error("tournament team found for player, but player not found on team");
|
throw std::logic_error("tournament team found for player, but player not found on team");
|
||||||
} else {
|
} else {
|
||||||
c->ep3_tournament_team.reset();
|
c->ep3_tournament_team.reset();
|
||||||
if (c->version() == Version::GC_EP3) {
|
if (c->version() == Version::GC_EP3) {
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
#include "FileContentsCache.hh"
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <phosg/Filesystem.hh>
|
|
||||||
#include <phosg/Time.hh>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
FileContentsCache::FileContentsCache(uint64_t ttl_usecs) : ttl_usecs(ttl_usecs) {}
|
|
||||||
|
|
||||||
FileContentsCache::File::File(
|
|
||||||
const string& name,
|
|
||||||
string&& data,
|
|
||||||
uint64_t load_time)
|
|
||||||
: name(name),
|
|
||||||
data(make_shared<string>(std::move(data))),
|
|
||||||
load_time(load_time) {}
|
|
||||||
|
|
||||||
shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
|
|
||||||
const string& name, string&& data, uint64_t t) {
|
|
||||||
if (t == 0) {
|
|
||||||
t = phosg::now();
|
|
||||||
}
|
|
||||||
auto new_file = make_shared<File>(name, std::move(data), t);
|
|
||||||
auto emplace_ret = this->name_to_file.emplace(name, new_file);
|
|
||||||
if (!emplace_ret.second) {
|
|
||||||
emplace_ret.first->second = new_file;
|
|
||||||
}
|
|
||||||
return new_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
|
|
||||||
const string& name, const void* data, size_t size, uint64_t t) {
|
|
||||||
string s(reinterpret_cast<const char*>(data), size);
|
|
||||||
return this->replace(name, std::move(s), t);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileContentsCache::GetResult FileContentsCache::get_or_load(const std::string& name) {
|
|
||||||
return this->get(name, phosg::load_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileContentsCache::GetResult FileContentsCache::get_or_load(const char* name) {
|
|
||||||
return this->get_or_load(string(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(const std::string& name) {
|
|
||||||
auto throw_fn = +[](const std::string&) -> string {
|
|
||||||
throw out_of_range("file missing from cache");
|
|
||||||
};
|
|
||||||
return this->get(name, throw_fn).file;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(const char* name) {
|
|
||||||
return this->get_or_throw(string(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
FileContentsCache::GetResult FileContentsCache::get(const std::string& name,
|
|
||||||
std::function<std::string(const std::string&)> generate) {
|
|
||||||
uint64_t t = phosg::now();
|
|
||||||
try {
|
|
||||||
auto& entry = this->name_to_file.at(name);
|
|
||||||
if (this->ttl_usecs && (t - entry->load_time < this->ttl_usecs)) {
|
|
||||||
return {entry, false};
|
|
||||||
}
|
|
||||||
} catch (const out_of_range& e) {
|
|
||||||
}
|
|
||||||
return {this->replace(name, generate(name)), true};
|
|
||||||
}
|
|
||||||
|
|
||||||
FileContentsCache::GetResult FileContentsCache::get(const char* name,
|
|
||||||
std::function<std::string(const std::string&)> generate) {
|
|
||||||
return this->get(string(name), generate);
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const string> ThreadSafeFileCache::get(
|
|
||||||
const string& name, std::function<shared_ptr<const string>(const std::string&)> generate) {
|
|
||||||
try {
|
|
||||||
shared_lock g(this->lock);
|
|
||||||
return this->name_to_file.at(name);
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
unique_lock g(this->lock);
|
|
||||||
auto it = this->name_to_file.find(name);
|
|
||||||
if (it == this->name_to_file.end()) {
|
|
||||||
it = this->name_to_file.emplace(name, generate(name)).first;
|
|
||||||
}
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include <phosg/Time.hh>
|
|
||||||
|
|
||||||
class FileContentsCache {
|
|
||||||
public:
|
|
||||||
struct File {
|
|
||||||
std::string name;
|
|
||||||
std::shared_ptr<const std::string> data;
|
|
||||||
uint64_t load_time;
|
|
||||||
|
|
||||||
File() = delete;
|
|
||||||
File(const std::string& name, std::string&& contents, uint64_t load_time);
|
|
||||||
File(const File&) = delete;
|
|
||||||
File(File&&) = delete;
|
|
||||||
File& operator=(const File&) = delete;
|
|
||||||
File& operator=(File&&) = delete;
|
|
||||||
~File() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit FileContentsCache(uint64_t ttl_usecs);
|
|
||||||
FileContentsCache(const FileContentsCache&) = delete;
|
|
||||||
FileContentsCache(FileContentsCache&&) = delete;
|
|
||||||
FileContentsCache& operator=(const FileContentsCache&) = delete;
|
|
||||||
FileContentsCache& operator=(FileContentsCache&&) = delete;
|
|
||||||
~FileContentsCache() = default;
|
|
||||||
|
|
||||||
template <typename NameT>
|
|
||||||
bool delete_key(NameT key) {
|
|
||||||
return this->name_to_file.erase(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<const File> replace(const std::string& name, std::string&& data, uint64_t t = 0);
|
|
||||||
std::shared_ptr<const File> replace(const std::string& name, const void* data, size_t size, uint64_t t = 0);
|
|
||||||
|
|
||||||
struct GetResult {
|
|
||||||
std::shared_ptr<const File> file;
|
|
||||||
bool generate_called;
|
|
||||||
};
|
|
||||||
|
|
||||||
GetResult get_or_load(const std::string& name);
|
|
||||||
GetResult get_or_load(const char* name);
|
|
||||||
std::shared_ptr<const File> get_or_throw(const std::string& name);
|
|
||||||
std::shared_ptr<const File> get_or_throw(const char* name);
|
|
||||||
|
|
||||||
GetResult get(const std::string& name, std::function<std::string(const std::string&)> generate);
|
|
||||||
GetResult get(const char* name, std::function<std::string(const std::string&)> generate);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct GetObjResult {
|
|
||||||
const T& obj;
|
|
||||||
std::shared_ptr<const File> data;
|
|
||||||
bool generate_called;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T, typename NameT>
|
|
||||||
GetObjResult<T> get_obj_or_load(NameT name) {
|
|
||||||
auto res = this->get_or_load(name);
|
|
||||||
if (res.file->data->size() != sizeof(T)) {
|
|
||||||
throw std::runtime_error("cached string size is incorrect");
|
|
||||||
}
|
|
||||||
return {*reinterpret_cast<const T*>(res.file->data->data()), res.file, res.generate_called};
|
|
||||||
}
|
|
||||||
template <typename T, typename NameT>
|
|
||||||
GetObjResult<T> get_obj_or_throw(NameT name) {
|
|
||||||
auto res = this->get_or_throw(name);
|
|
||||||
if (res.file->data->size() != sizeof(T)) {
|
|
||||||
throw std::runtime_error("cached string size is incorrect");
|
|
||||||
}
|
|
||||||
return {*reinterpret_cast<const T*>(res.file->data->data()), res.file, res.generate_called};
|
|
||||||
}
|
|
||||||
template <typename T, typename NameT>
|
|
||||||
GetObjResult<T> get_obj(NameT name, std::function<T(const std::string&)> generate) {
|
|
||||||
uint64_t t = phosg::now();
|
|
||||||
try {
|
|
||||||
auto& f = this->name_to_file.at(name);
|
|
||||||
if (f->data->size() != sizeof(T)) {
|
|
||||||
throw std::runtime_error("cached string size is incorrect");
|
|
||||||
}
|
|
||||||
if (this->ttl_usecs && (t - f->load_time < this->ttl_usecs)) {
|
|
||||||
return {*reinterpret_cast<const T*>(f->data->data()), f, false};
|
|
||||||
}
|
|
||||||
} catch (const std::out_of_range& e) {
|
|
||||||
}
|
|
||||||
T value = generate(name);
|
|
||||||
auto ret = this->replace_obj(name, value);
|
|
||||||
ret.generate_called = true;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
template <typename T, typename NameT>
|
|
||||||
GetObjResult<T> replace_obj(NameT name, const T& value) {
|
|
||||||
auto cached_value = this->replace(name, &value, sizeof(value));
|
|
||||||
return {*reinterpret_cast<const T*>(cached_value->data->data()), cached_value, false};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
|
|
||||||
uint64_t ttl_usecs;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ThreadSafeFileCache {
|
|
||||||
public:
|
|
||||||
explicit ThreadSafeFileCache() = default;
|
|
||||||
ThreadSafeFileCache(const ThreadSafeFileCache&) = delete;
|
|
||||||
ThreadSafeFileCache(ThreadSafeFileCache&&) = delete;
|
|
||||||
ThreadSafeFileCache& operator=(const ThreadSafeFileCache&) = delete;
|
|
||||||
ThreadSafeFileCache& operator=(ThreadSafeFileCache&&) = delete;
|
|
||||||
~ThreadSafeFileCache() = default;
|
|
||||||
|
|
||||||
// generate() is called while the lock is held for writing, so it will block other threads.
|
|
||||||
std::shared_ptr<const std::string> get(
|
|
||||||
const std::string& name, std::function<std::shared_ptr<const std::string>(const std::string&)> generate);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_mutex lock;
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> name_to_file;
|
|
||||||
};
|
|
||||||
+15
-17
@@ -7,8 +7,6 @@
|
|||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
#include "Types.hh"
|
#include "Types.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
struct GSLHeaderEntryT {
|
struct GSLHeaderEntryT {
|
||||||
pstring<TextEncoding::ASCII, 0x20> filename;
|
pstring<TextEncoding::ASCII, 0x20> filename;
|
||||||
@@ -30,13 +28,13 @@ void GSLArchive::load_t() {
|
|||||||
}
|
}
|
||||||
uint64_t offset = static_cast<uint64_t>(entry.offset) * 0x800;
|
uint64_t offset = static_cast<uint64_t>(entry.offset) * 0x800;
|
||||||
if (offset + entry.size > this->data->size()) {
|
if (offset + entry.size > this->data->size()) {
|
||||||
throw runtime_error("GSL entry extends beyond end of data");
|
throw std::runtime_error("GSL entry extends beyond end of data");
|
||||||
}
|
}
|
||||||
this->entries.emplace(entry.filename.decode(), Entry{offset, entry.size});
|
this->entries.emplace(entry.filename.decode(), Entry{offset, entry.size});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GSLArchive::GSLArchive(shared_ptr<const string> data, bool big_endian) : data(data) {
|
GSLArchive::GSLArchive(std::shared_ptr<const std::string> data, bool big_endian) : data(data) {
|
||||||
if (big_endian) {
|
if (big_endian) {
|
||||||
this->load_t<true>();
|
this->load_t<true>();
|
||||||
} else {
|
} else {
|
||||||
@@ -44,43 +42,43 @@ GSLArchive::GSLArchive(shared_ptr<const string> data, bool big_endian) : data(da
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unordered_map<string, GSLArchive::Entry> GSLArchive::all_entries() const {
|
const std::unordered_map<std::string, GSLArchive::Entry> GSLArchive::all_entries() const {
|
||||||
return this->entries;
|
return this->entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<const void*, size_t> GSLArchive::get(const std::string& name) const {
|
std::pair<const void*, size_t> GSLArchive::get(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
return std::make_pair(this->data->data() + entry.offset, entry.size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("GSL does not contain file: " + name);
|
throw std::out_of_range("GSL does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string GSLArchive::get_copy(const string& name) const {
|
std::string GSLArchive::get_copy(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return this->data->substr(entry.offset, entry.size);
|
return this->data->substr(entry.offset, entry.size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("GSL does not contain file: " + name);
|
throw std::out_of_range("GSL does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader GSLArchive::get_reader(const string& name) const {
|
phosg::StringReader GSLArchive::get_reader(const std::string& name) const {
|
||||||
try {
|
try {
|
||||||
const auto& entry = this->entries.at(name);
|
const auto& entry = this->entries.at(name);
|
||||||
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw out_of_range("GSL does not contain file: " + name);
|
throw std::out_of_range("GSL does not contain file: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string GSLArchive::generate(const unordered_map<string, string>& files, bool big_endian) {
|
std::string GSLArchive::generate(const std::unordered_map<std::string, std::string>& files, bool big_endian) {
|
||||||
return big_endian ? GSLArchive::generate_t<true>(files) : GSLArchive::generate_t<false>(files);
|
return big_endian ? GSLArchive::generate_t<true>(files) : GSLArchive::generate_t<false>(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
string GSLArchive::generate_t(const unordered_map<string, string>& files) {
|
std::string GSLArchive::generate_t(const std::unordered_map<std::string, std::string>& files) {
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
|
|
||||||
// Make sure there's enough space for a blank header entry before any file's data pages begin
|
// Make sure there's enough space for a blank header entry before any file's data pages begin
|
||||||
|
|||||||
+25
-27
@@ -18,14 +18,11 @@
|
|||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "ReceiveCommands.hh"
|
#include "ReceiveCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
GameServer::GameServer(std::shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
|
||||||
using namespace std::placeholders;
|
|
||||||
|
|
||||||
GameServer::GameServer(shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
|
|
||||||
|
|
||||||
void GameServer::listen(
|
void GameServer::listen(
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const string& addr,
|
const std::string& addr,
|
||||||
uint16_t port,
|
uint16_t port,
|
||||||
Version version,
|
Version version,
|
||||||
ServerBehavior behavior) {
|
ServerBehavior behavior) {
|
||||||
@@ -34,7 +31,7 @@ void GameServer::listen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
||||||
auto sock = make_shared<GameServerSocket>();
|
auto sock = std::make_shared<GameServerSocket>();
|
||||||
sock->name = name;
|
sock->name = name;
|
||||||
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
||||||
sock->version = version;
|
sock->version = version;
|
||||||
@@ -42,8 +39,8 @@ void GameServer::listen(
|
|||||||
this->add_socket(std::move(sock));
|
this->add_socket(std::move(sock));
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Client> GameServer::connect_channel(shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state) {
|
std::shared_ptr<Client> GameServer::connect_channel(std::shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state) {
|
||||||
auto c = make_shared<Client>(this->shared_from_this(), ch, initial_state);
|
auto c = std::make_shared<Client>(this->shared_from_this(), ch, initial_state);
|
||||||
c->listener_port = port;
|
c->listener_port = port;
|
||||||
|
|
||||||
this->log.info_f("Client connected: C-{:X} via TSI-{}-{}-{}",
|
this->log.info_f("Client connected: C-{:X} via TSI-{}-{}-{}",
|
||||||
@@ -53,31 +50,31 @@ shared_ptr<Client> GameServer::connect_channel(shared_ptr<Channel> ch, uint16_t
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Client> GameServer::get_client() const {
|
std::shared_ptr<Client> GameServer::get_client() const {
|
||||||
if (this->clients.empty()) {
|
if (this->clients.empty()) {
|
||||||
throw runtime_error("no clients on game server");
|
throw std::runtime_error("no clients on game server");
|
||||||
}
|
}
|
||||||
if (this->clients.size() > 1) {
|
if (this->clients.size() > 1) {
|
||||||
throw runtime_error("multiple clients on game server");
|
throw std::runtime_error("multiple clients on game server");
|
||||||
}
|
}
|
||||||
return *this->clients.begin();
|
return *this->clients.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& ident) const {
|
std::vector<std::shared_ptr<Client>> GameServer::get_clients_by_identifier(const std::string& ident) const {
|
||||||
int64_t account_id_hex = -1;
|
int64_t account_id_hex = -1;
|
||||||
int64_t account_id_dec = -1;
|
int64_t account_id_dec = -1;
|
||||||
try {
|
try {
|
||||||
account_id_dec = stoul(ident, nullptr, 10);
|
account_id_dec = stoul(ident, nullptr, 10);
|
||||||
} catch (const invalid_argument&) {
|
} catch (const std::invalid_argument&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
account_id_hex = stoul(ident, nullptr, 16);
|
account_id_hex = stoul(ident, nullptr, 16);
|
||||||
} catch (const invalid_argument&) {
|
} catch (const std::invalid_argument&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: It's kind of not great that we do a linear search here, but this is only used in the shell, so it should be
|
// TODO: It's kind of not great that we do a linear search here, but this is only used in the shell, so it should be
|
||||||
// pretty rare.
|
// pretty rare.
|
||||||
vector<shared_ptr<Client>> results;
|
std::vector<std::shared_ptr<Client>> results;
|
||||||
for (const auto& c : this->clients) {
|
for (const auto& c : this->clients) {
|
||||||
if (c->login && c->login->account->account_id == account_id_hex) {
|
if (c->login && c->login->account->account_id == account_id_hex) {
|
||||||
results.emplace_back(c);
|
results.emplace_back(c);
|
||||||
@@ -97,7 +94,7 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->character_file(false, false);
|
auto p = c->character_file(false, false);
|
||||||
if (p && p->disp.name.eq(ident, p->inventory.language)) {
|
if (p && p->disp.visual.name.eq(ident, p->inventory.language)) {
|
||||||
results.emplace_back(c);
|
results.emplace_back(c);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -115,8 +112,8 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Client> GameServer::create_client(
|
std::shared_ptr<Client> GameServer::create_client(
|
||||||
shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
|
std::shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
|
||||||
uint32_t addr = ipv4_addr_for_asio_addr(client_sock.remote_endpoint().address());
|
uint32_t addr = ipv4_addr_for_asio_addr(client_sock.remote_endpoint().address());
|
||||||
if (this->state->banned_ipv4_ranges->check(addr)) {
|
if (this->state->banned_ipv4_ranges->check(addr)) {
|
||||||
if (client_sock.is_open()) {
|
if (client_sock.is_open()) {
|
||||||
@@ -127,7 +124,7 @@ shared_ptr<Client> GameServer::create_client(
|
|||||||
|
|
||||||
auto channel = SocketChannel::create(
|
auto channel = SocketChannel::create(
|
||||||
this->io_context,
|
this->io_context,
|
||||||
make_unique<asio::ip::tcp::socket>(std::move(client_sock)),
|
std::make_unique<asio::ip::tcp::socket>(std::move(client_sock)),
|
||||||
listen_sock->version,
|
listen_sock->version,
|
||||||
Language::ENGLISH,
|
Language::ENGLISH,
|
||||||
"",
|
"",
|
||||||
@@ -135,28 +132,29 @@ shared_ptr<Client> GameServer::create_client(
|
|||||||
phosg::TerminalFormat::FG_GREEN,
|
phosg::TerminalFormat::FG_GREEN,
|
||||||
this->state->censor_credentials,
|
this->state->censor_credentials,
|
||||||
false);
|
false);
|
||||||
auto c = make_shared<Client>(this->shared_from_this(), channel, listen_sock->behavior);
|
auto c = std::make_shared<Client>(this->shared_from_this(), channel, listen_sock->behavior);
|
||||||
c->listener_port = listen_sock->endpoint.port();
|
c->listener_port = listen_sock->endpoint.port();
|
||||||
this->log.info_f("Client connected: C-{:X} via {}", c->id, listen_sock->name);
|
this->log.info_f("Client connected: C-{:X} via {}", c->id, listen_sock->name);
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> GameServer::handle_client_command(shared_ptr<Client> c, unique_ptr<Channel::Message> msg) {
|
asio::awaitable<void> GameServer::handle_client_command(
|
||||||
|
std::shared_ptr<Client> c, std::unique_ptr<Channel::Message> msg) {
|
||||||
try {
|
try {
|
||||||
co_await on_command(c, std::move(msg));
|
co_await on_command(c, std::move(msg));
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.warning_f("Error processing client command: {}", e.what());
|
this->log.warning_f("Error processing client command: {}", e.what());
|
||||||
c->channel->disconnect();
|
c->channel->disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> GameServer::handle_client(shared_ptr<Client> c) {
|
asio::awaitable<void> GameServer::handle_client(std::shared_ptr<Client> c) {
|
||||||
auto g = phosg::on_close_scope(std::bind(&Client::cancel_pending_promises, c.get()));
|
auto g = phosg::on_close_scope(std::bind(&Client::cancel_pending_promises, c.get()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
co_await on_connect(c);
|
co_await on_connect(c);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.warning_f("Error in client initialization: {}", e.what());
|
this->log.warning_f("Error in client initialization: {}", e.what());
|
||||||
c->channel->disconnect();
|
c->channel->disconnect();
|
||||||
}
|
}
|
||||||
@@ -183,17 +181,17 @@ asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> c) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
co_await on_disconnect(c);
|
co_await on_disconnect(c);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.warning_f("Error during client disconnect cleanup: {}", e.what());
|
this->log.warning_f("Error during client disconnect cleanup: {}", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: It's important to move the disconnect hooks out of the client here because the hooks could modify
|
// Note: It's important to move the disconnect hooks out of the client here because the hooks could modify
|
||||||
// c->disconnect_hooks while it's being iterated here, which would invalidate these iterators.
|
// c->disconnect_hooks while it's being iterated here, which would invalidate these iterators.
|
||||||
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
|
std::unordered_map<std::string, std::function<void()>> hooks = std::move(c->disconnect_hooks);
|
||||||
for (auto h_it : hooks) {
|
for (auto h_it : hooks) {
|
||||||
try {
|
try {
|
||||||
h_it.second();
|
h_it.second();
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.warning_f("Disconnect hook {} failed: {}", h_it.first, e.what());
|
c->log.warning_f("Disconnect hook {} failed: {}", h_it.first, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-84
@@ -15,9 +15,7 @@
|
|||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
#include "ShellCommands.hh"
|
#include "ShellCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
|
||||||
|
|
||||||
HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|
||||||
: AsyncHTTPServer(state->io_context, "[HTTPServer] "), state(state) {
|
: AsyncHTTPServer(state->io_context, "[HTTPServer] "), state(state) {
|
||||||
using RouterRetT = std::variant<RawResponse, std::shared_ptr<const phosg::JSON>>;
|
using RouterRetT = std::variant<RawResponse, std::shared_ptr<const phosg::JSON>>;
|
||||||
using RetT = asio::awaitable<RouterRetT>;
|
using RetT = asio::awaitable<RouterRetT>;
|
||||||
@@ -33,15 +31,15 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
};
|
};
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/", [generate_server_version_json](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/", [generate_server_version_json](ArgsT&&) -> RetT {
|
||||||
co_return make_shared<phosg::JSON>(generate_server_version_json());
|
co_return std::make_shared<phosg::JSON>(generate_server_version_json());
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::POST, "/y/shell-exec", [this](ArgsT&& args) -> RetT {
|
this->router.add(HTTPRequest::Method::POST, "/y/shell-exec", [this](ArgsT&& args) -> RetT {
|
||||||
auto command = args.post_data.get_string("command");
|
auto command = args.post_data.get_string("command");
|
||||||
try {
|
try {
|
||||||
auto dispatch_res = co_await ShellCommand::dispatch_str(this->state, command);
|
auto dispatch_res = co_await ShellCommand::dispatch_str(this->state, command);
|
||||||
co_return make_shared<phosg::JSON>(phosg::JSON::dict({{"result", phosg::join(dispatch_res, "\n")}}));
|
co_return std::make_shared<phosg::JSON>(phosg::JSON::dict({{"result", phosg::join(dispatch_res, "\n")}}));
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
throw HTTPError(400, e.what());
|
throw HTTPError(400, e.what());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -70,8 +68,8 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto escape_label = +[](const string& in) -> string {
|
auto escape_label = +[](const std::string& in) -> std::string {
|
||||||
string out;
|
std::string out;
|
||||||
for (char ch : in) {
|
for (char ch : in) {
|
||||||
if (ch == '\\') {
|
if (ch == '\\') {
|
||||||
out += "\\\\";
|
out += "\\\\";
|
||||||
@@ -86,14 +84,14 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto add_metric = [](string& out, const string& name, uint64_t value) -> void {
|
auto add_metric = [](std::string& out, const std::string& name, uint64_t value) -> void {
|
||||||
out += name;
|
out += name;
|
||||||
out += " ";
|
out += " ";
|
||||||
out += std::to_string(value);
|
out += std::to_string(value);
|
||||||
out += "\n";
|
out += "\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
auto add_metric_1label = [](string& out, const string& name, const string& label_name, const string& label_value, uint64_t value) -> void {
|
auto add_metric_1label = [](std::string& out, const std::string& name, const std::string& label_name, const std::string& label_value, uint64_t value) -> void {
|
||||||
out += name;
|
out += name;
|
||||||
out += "{";
|
out += "{";
|
||||||
out += label_name;
|
out += label_name;
|
||||||
@@ -104,9 +102,9 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
out += "\n";
|
out += "\n";
|
||||||
};
|
};
|
||||||
|
|
||||||
map<string, uint64_t> connected_by_version;
|
std::map<std::string, uint64_t> connected_by_version;
|
||||||
map<string, uint64_t> lobby_players_by_version;
|
std::map<std::string, uint64_t> lobby_players_by_version;
|
||||||
map<string, uint64_t> game_players_by_version;
|
std::map<std::string, uint64_t> game_players_by_version;
|
||||||
|
|
||||||
uint64_t connected_total = 0;
|
uint64_t connected_total = 0;
|
||||||
uint64_t lobbies_total = 0;
|
uint64_t lobbies_total = 0;
|
||||||
@@ -143,10 +141,10 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string server_name = escape_label(this->state->name);
|
std::string server_name = escape_label(this->state->name);
|
||||||
string revision = escape_label(GIT_REVISION_HASH);
|
std::string revision = escape_label(GIT_REVISION_HASH);
|
||||||
|
|
||||||
string out;
|
std::string out;
|
||||||
out += "# HELP pso_newserv_up Whether this newserv HTTP metrics endpoint is reachable\n";
|
out += "# HELP pso_newserv_up Whether this newserv HTTP metrics endpoint is reachable\n";
|
||||||
out += "# TYPE pso_newserv_up gauge\n";
|
out += "# TYPE pso_newserv_up gauge\n";
|
||||||
add_metric(out, "pso_newserv_up", 1);
|
add_metric(out, "pso_newserv_up", 1);
|
||||||
@@ -206,7 +204,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/clients", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/clients", [this](ArgsT&&) -> RetT {
|
||||||
auto res = make_shared<phosg::JSON>(phosg::JSON::list());
|
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||||
for (const auto& c : this->state->game_server->all_clients()) {
|
for (const auto& c : this->state->game_server->all_clients()) {
|
||||||
auto item_name_index = this->state->item_name_index_opt(c->version());
|
auto item_name_index = this->state->item_name_index_opt(c->version());
|
||||||
|
|
||||||
@@ -313,22 +311,22 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
client_json.emplace("TechniqueLevels", std::move(tech_levels_json));
|
client_json.emplace("TechniqueLevels", std::move(tech_levels_json));
|
||||||
}
|
}
|
||||||
client_json.emplace("Level", p->disp.stats.level.load() + 1);
|
client_json.emplace("Level", p->disp.stats.level.load() + 1);
|
||||||
client_json.emplace("NameColor", p->disp.visual.name_color.load());
|
client_json.emplace("NameColor", p->disp.visual.sh.name_color.load());
|
||||||
client_json.emplace("ExtraModel", (p->disp.visual.validation_flags & 2) ? p->disp.visual.extra_model : phosg::JSON(nullptr));
|
client_json.emplace("ExtraModel", (p->disp.visual.sh.validation_flags & 2) ? p->disp.visual.sh.extra_model : phosg::JSON(nullptr));
|
||||||
client_json.emplace("SectionID", name_for_section_id(p->disp.visual.section_id));
|
client_json.emplace("SectionID", name_for_section_id(p->disp.visual.sh.section_id));
|
||||||
client_json.emplace("CharClass", name_for_char_class(p->disp.visual.char_class));
|
client_json.emplace("CharClass", name_for_char_class(p->disp.visual.sh.char_class));
|
||||||
client_json.emplace("Costume", p->disp.visual.costume.load());
|
client_json.emplace("Costume", p->disp.visual.sh.costume.load());
|
||||||
client_json.emplace("Skin", p->disp.visual.skin.load());
|
client_json.emplace("Skin", p->disp.visual.sh.skin.load());
|
||||||
client_json.emplace("Face", p->disp.visual.face.load());
|
client_json.emplace("Face", p->disp.visual.sh.face.load());
|
||||||
client_json.emplace("Head", p->disp.visual.head.load());
|
client_json.emplace("Head", p->disp.visual.sh.head.load());
|
||||||
client_json.emplace("Hair", p->disp.visual.hair.load());
|
client_json.emplace("Hair", p->disp.visual.sh.hair.load());
|
||||||
client_json.emplace("HairR", p->disp.visual.hair_r.load());
|
client_json.emplace("HairR", p->disp.visual.sh.hair_r.load());
|
||||||
client_json.emplace("HairG", p->disp.visual.hair_g.load());
|
client_json.emplace("HairG", p->disp.visual.sh.hair_g.load());
|
||||||
client_json.emplace("HairB", p->disp.visual.hair_b.load());
|
client_json.emplace("HairB", p->disp.visual.sh.hair_b.load());
|
||||||
client_json.emplace("ProportionX", p->disp.visual.proportion_x.load());
|
client_json.emplace("ProportionX", p->disp.visual.sh.proportion_x.load());
|
||||||
client_json.emplace("ProportionY", p->disp.visual.proportion_y.load());
|
client_json.emplace("ProportionY", p->disp.visual.sh.proportion_y.load());
|
||||||
|
|
||||||
client_json.emplace("Name", p->disp.name.decode(c->language()));
|
client_json.emplace("Name", p->disp.visual.name.decode(c->language()));
|
||||||
client_json.emplace("PlayTimeSeconds", p->play_time_seconds.load());
|
client_json.emplace("PlayTimeSeconds", p->play_time_seconds.load());
|
||||||
|
|
||||||
client_json.emplace("AutoReply", p->auto_reply.decode(c->language()));
|
client_json.emplace("AutoReply", p->auto_reply.decode(c->language()));
|
||||||
@@ -366,7 +364,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
uint8_t minute = p->challenge_records.grave_time & 0xFF;
|
uint8_t minute = p->challenge_records.grave_time & 0xFF;
|
||||||
client_json.emplace("ChallengeGraveTime", std::format("{:04}-{:02}-{:02} {:02}:{:02}:00", year, month, day, hour, minute));
|
client_json.emplace("ChallengeGraveTime", std::format("{:04}-{:02}-{:02} {:02}:{:02}:00", year, month, day, hour, minute));
|
||||||
}
|
}
|
||||||
string grave_enemy_types;
|
std::string grave_enemy_types;
|
||||||
if (p->challenge_records.grave_defeated_by_enemy_rt_index) {
|
if (p->challenge_records.grave_defeated_by_enemy_rt_index) {
|
||||||
for (EnemyType type : enemy_types_for_rare_table_index(
|
for (EnemyType type : enemy_types_for_rare_table_index(
|
||||||
p->challenge_records.grave_is_ep2 ? Episode::EP2 : Episode::EP1,
|
p->challenge_records.grave_is_ep2 ? Episode::EP2 : Episode::EP1,
|
||||||
@@ -448,7 +446,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/lobbies", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/lobbies", [this](ArgsT&&) -> RetT {
|
||||||
auto res = make_shared<phosg::JSON>(phosg::JSON::list());
|
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||||
for (const auto& [_, l] : this->state->id_to_lobby) {
|
for (const auto& [_, l] : this->state->id_to_lobby) {
|
||||||
auto leader = l->clients[l->leader_id];
|
auto leader = l->clients[l->leader_id];
|
||||||
Version v = leader ? leader->version() : Version::BB_V4;
|
Version v = leader ? leader->version() : Version::BB_V4;
|
||||||
@@ -581,7 +579,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
name = ce->def.jp_short_name.decode();
|
name = ce->def.jp_short_name.decode();
|
||||||
}
|
}
|
||||||
cards_json.emplace_back(name);
|
cards_json.emplace_back(name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
cards_json.emplace_back(deck_entry->card_ids[w].load());
|
cards_json.emplace_back(deck_entry->card_ids[w].load());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,7 +654,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/accounts", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/accounts", [this](ArgsT&&) -> RetT {
|
||||||
auto res = make_shared<phosg::JSON>(phosg::JSON::list());
|
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||||
for (const auto& it : this->state->account_index->all()) {
|
for (const auto& it : this->state->account_index->all()) {
|
||||||
res->emplace_back(it->json());
|
res->emplace_back(it->json());
|
||||||
}
|
}
|
||||||
@@ -666,14 +664,14 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
this->router.add(HTTPRequest::Method::GET, "/y/account/:account_id", [this](ArgsT&& args) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/account/:account_id", [this](ArgsT&& args) -> RetT {
|
||||||
uint32_t account_id = args.get_param<uint32_t>("account_id");
|
uint32_t account_id = args.get_param<uint32_t>("account_id");
|
||||||
try {
|
try {
|
||||||
co_return make_shared<phosg::JSON>(this->state->account_index->from_account_id(account_id)->json());
|
co_return std::make_shared<phosg::JSON>(this->state->account_index->from_account_id(account_id)->json());
|
||||||
} catch (const AccountIndex::missing_account&) {
|
} catch (const AccountIndex::missing_account&) {
|
||||||
throw HTTPError(404, "Account does not exist");
|
throw HTTPError(404, "Account does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/teams", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/teams", [this](ArgsT&&) -> RetT {
|
||||||
auto res = make_shared<phosg::JSON>(phosg::JSON::dict());
|
auto res = std::make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||||
for (const auto& it : this->state->team_index->all()) {
|
for (const auto& it : this->state->team_index->all()) {
|
||||||
res->emplace(std::format("{}", it->team_id), it->json());
|
res->emplace(std::format("{}", it->team_id), it->json());
|
||||||
}
|
}
|
||||||
@@ -686,7 +684,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
if (!team) {
|
if (!team) {
|
||||||
throw HTTPError(404, "Team does not exist");
|
throw HTTPError(404, "Team does not exist");
|
||||||
}
|
}
|
||||||
co_return make_shared<phosg::JSON>(team->json());
|
co_return std::make_shared<phosg::JSON>(team->json());
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/team/:team_id/flag", [this](ArgsT&& args) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/team/:team_id/flag", [this](ArgsT&& args) -> RetT {
|
||||||
@@ -727,7 +725,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
};
|
};
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/server", [generate_server_info_json](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/server", [generate_server_info_json](ArgsT&&) -> RetT {
|
||||||
co_return make_shared<phosg::JSON>(generate_server_info_json());
|
co_return std::make_shared<phosg::JSON>(generate_server_info_json());
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/config", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/config", [this](ArgsT&&) -> RetT {
|
||||||
@@ -742,12 +740,12 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
clients_json.emplace_back(phosg::JSON::dict({
|
clients_json.emplace_back(phosg::JSON::dict({
|
||||||
{"ID", c->id},
|
{"ID", c->id},
|
||||||
{"AccountID", c->login ? c->login->account->account_id : phosg::JSON(nullptr)},
|
{"AccountID", c->login ? c->login->account->account_id : phosg::JSON(nullptr)},
|
||||||
{"Name", p ? p->disp.name.decode(c->language()) : phosg::JSON(nullptr)},
|
{"Name", p ? p->disp.visual.name.decode(c->language()) : phosg::JSON(nullptr)},
|
||||||
{"Version", phosg::name_for_enum(c->version())},
|
{"Version", phosg::name_for_enum(c->version())},
|
||||||
{"Language", name_for_language(c->language())},
|
{"Language", name_for_language(c->language())},
|
||||||
{"Level", p ? (p->disp.stats.level + 1) : phosg::JSON(nullptr)},
|
{"Level", p ? (p->disp.stats.level + 1) : phosg::JSON(nullptr)},
|
||||||
{"Class", p ? name_for_char_class(p->disp.visual.char_class) : phosg::JSON(nullptr)},
|
{"Class", p ? name_for_char_class(p->disp.visual.sh.char_class) : phosg::JSON(nullptr)},
|
||||||
{"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : phosg::JSON(nullptr)},
|
{"SectionID", p ? name_for_section_id(p->disp.visual.sh.section_id) : phosg::JSON(nullptr)},
|
||||||
{"LobbyID", l ? l->lobby_id : phosg::JSON(nullptr)},
|
{"LobbyID", l ? l->lobby_id : phosg::JSON(nullptr)},
|
||||||
{"IsOnProxy", c->proxy_session ? true : false},
|
{"IsOnProxy", c->proxy_session ? true : false},
|
||||||
}));
|
}));
|
||||||
@@ -789,7 +787,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return make_shared<phosg::JSON>(phosg::JSON::dict({
|
co_return std::make_shared<phosg::JSON>(phosg::JSON::dict({
|
||||||
{"Clients", std::move(clients_json)},
|
{"Clients", std::move(clients_json)},
|
||||||
{"Games", std::move(games_json)},
|
{"Games", std::move(games_json)},
|
||||||
{"Server", generate_server_info_json()},
|
{"Server", generate_server_info_json()},
|
||||||
@@ -798,8 +796,8 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/cards", [this](ArgsT&& args) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/cards", [this](ArgsT&& args) -> RetT {
|
||||||
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
||||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||||
return make_shared<phosg::JSON>(index->definitions_json());
|
return std::make_shared<phosg::JSON>(index->definitions_json());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -807,15 +805,15 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
||||||
uint32_t card_id = args.get_param<uint32_t>("card_id");
|
uint32_t card_id = args.get_param<uint32_t>("card_id");
|
||||||
try {
|
try {
|
||||||
co_return make_shared<phosg::JSON>(index->definition_for_id(card_id)->def.json());
|
co_return std::make_shared<phosg::JSON>(index->definition_for_id(card_id)->def.json());
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw HTTPError(404, "Card definition does not exist");
|
throw HTTPError(404, "Card definition does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/maps", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/maps", [this](ArgsT&&) -> RetT {
|
||||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::dict());
|
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||||
for (const auto& [map_number, map] : this->state->ep3_map_index->all_maps()) {
|
for (const auto& [map_number, map] : this->state->ep3_map_index->all_maps()) {
|
||||||
auto languages_json = phosg::JSON::list();
|
auto languages_json = phosg::JSON::list();
|
||||||
for (const auto& vm : map->all_versions()) {
|
for (const auto& vm : map->all_versions()) {
|
||||||
@@ -835,11 +833,11 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language", [this](ArgsT&& args) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language", [this](ArgsT&& args) -> RetT {
|
||||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||||
try {
|
try {
|
||||||
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number", true));
|
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number", true));
|
||||||
auto vm = map->version(language_for_name(args.params.at("language")));
|
auto vm = map->version(language_for_name(args.params.at("language")));
|
||||||
return make_shared<phosg::JSON>(vm->map->json(vm->language));
|
return std::make_shared<phosg::JSON>(vm->map->json(vm->language));
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw HTTPError(404, "Map version does not exist");
|
throw HTTPError(404, "Map version does not exist");
|
||||||
}
|
}
|
||||||
@@ -851,7 +849,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
try {
|
try {
|
||||||
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number"));
|
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number"));
|
||||||
auto vm = map->version(language_for_name(args.params.at("language")));
|
auto vm = map->version(language_for_name(args.params.at("language")));
|
||||||
string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
|
std::string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
|
||||||
return RawResponse{.content_type = "application/octet-stream", .data = std::move(data)};
|
return RawResponse{.content_type = "application/octet-stream", .data = std::move(data)};
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw HTTPError(404, "Map version does not exist");
|
throw HTTPError(404, "Map version does not exist");
|
||||||
@@ -860,7 +858,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/common-tables", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/common-tables", [this](ArgsT&&) -> RetT {
|
||||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
|
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||||
for (const auto& it : this->state->common_item_sets) {
|
for (const auto& it : this->state->common_item_sets) {
|
||||||
ret->emplace_back(it.first);
|
ret->emplace_back(it.first);
|
||||||
}
|
}
|
||||||
@@ -870,16 +868,16 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/common-table/:table_name", [this](ArgsT&& args) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/common-table/:table_name", [this](ArgsT&& args) -> RetT {
|
||||||
try {
|
try {
|
||||||
const auto& table = this->state->common_item_sets.at(args.params.at("table_name"));
|
const auto& table = this->state->common_item_sets.at(args.params.at("table_name"));
|
||||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||||
return make_shared<phosg::JSON>(table->json());
|
return std::make_shared<phosg::JSON>(table->json());
|
||||||
});
|
});
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw HTTPError(404, "Table does not exist");
|
throw HTTPError(404, "Table does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/rare-tables", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/rare-tables", [this](ArgsT&&) -> RetT {
|
||||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
|
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||||
for (const auto& it : this->state->rare_item_sets) {
|
for (const auto& it : this->state->rare_item_sets) {
|
||||||
ret->emplace_back(it.first);
|
ret->emplace_back(it.first);
|
||||||
}
|
}
|
||||||
@@ -890,7 +888,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
try {
|
try {
|
||||||
const auto& table_name = args.params.at("table_name");
|
const auto& table_name = args.params.at("table_name");
|
||||||
const auto& table = this->state->rare_item_sets.at(table_name);
|
const auto& table = this->state->rare_item_sets.at(table_name);
|
||||||
shared_ptr<const ItemNameIndex> name_index;
|
std::shared_ptr<const ItemNameIndex> name_index;
|
||||||
if (table_name.ends_with("-v1")) {
|
if (table_name.ends_with("-v1")) {
|
||||||
name_index = this->state->item_name_index_opt(Version::DC_V1);
|
name_index = this->state->item_name_index_opt(Version::DC_V1);
|
||||||
} else if (table_name.ends_with("-v2")) {
|
} else if (table_name.ends_with("-v2")) {
|
||||||
@@ -900,17 +898,17 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
} else if (table_name.ends_with("-v4")) {
|
} else if (table_name.ends_with("-v4")) {
|
||||||
name_index = this->state->item_name_index_opt(Version::BB_V4);
|
name_index = this->state->item_name_index_opt(Version::BB_V4);
|
||||||
}
|
}
|
||||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||||
return make_shared<phosg::JSON>(table->json(name_index));
|
return std::make_shared<phosg::JSON>(table->json(name_index));
|
||||||
});
|
});
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw HTTPError(404, "Table does not exist");
|
throw HTTPError(404, "Table does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this->router.add(HTTPRequest::Method::GET, "/y/data/quests", [this](ArgsT&&) -> RetT {
|
this->router.add(HTTPRequest::Method::GET, "/y/data/quests", [this](ArgsT&&) -> RetT {
|
||||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||||
return make_shared<phosg::JSON>(this->state->quest_index->json());
|
return std::make_shared<phosg::JSON>(this->state->quest_index->json());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -920,21 +918,21 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
|||||||
if (!q) {
|
if (!q) {
|
||||||
throw HTTPError(404, "Quest does not exist");
|
throw HTTPError(404, "Quest does not exist");
|
||||||
}
|
}
|
||||||
co_return make_shared<phosg::JSON>(q->json());
|
co_return std::make_shared<phosg::JSON>(q->json());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> HTTPServer::send_rare_drop_notification(shared_ptr<const phosg::JSON> message) {
|
asio::awaitable<void> HTTPServer::send_rare_drop_notification(std::shared_ptr<const phosg::JSON> message) {
|
||||||
if (!this->rare_drop_subscribers.empty()) {
|
if (!this->rare_drop_subscribers.empty()) {
|
||||||
string data = message->serialize();
|
std::string data = message->serialize();
|
||||||
|
|
||||||
// Make a copy of the rare drop subscribers set, so we can guarantee that the client objects are all valid until
|
// Make a copy of the rare drop subscribers set, so we can guarantee that the client objects are all valid until
|
||||||
// this coroutine returns
|
// this coroutine returns
|
||||||
unordered_set<shared_ptr<HTTPClient>> subscribers = this->rare_drop_subscribers;
|
std::unordered_set<std::shared_ptr<HTTPClient>> subscribers = this->rare_drop_subscribers;
|
||||||
|
|
||||||
size_t expected_results = subscribers.size();
|
size_t expected_results = subscribers.size();
|
||||||
AsyncPromise<void> complete_promise;
|
AsyncPromise<void> complete_promise;
|
||||||
auto fn = [this, &data, &expected_results, &complete_promise](shared_ptr<HTTPClient> c) -> asio::awaitable<void> {
|
auto fn = [this, &data, &expected_results, &complete_promise](std::shared_ptr<HTTPClient> c) -> asio::awaitable<void> {
|
||||||
try {
|
try {
|
||||||
co_await c->send_websocket_message(data);
|
co_await c->send_websocket_message(data);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@@ -954,14 +952,14 @@ asio::awaitable<void> HTTPServer::send_rare_drop_notification(shared_ptr<const p
|
|||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared_ptr<HTTPClient> c, HTTPRequest&& req) {
|
asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(std::shared_ptr<HTTPClient> c, HTTPRequest&& req) {
|
||||||
variant<RawResponse, shared_ptr<const phosg::JSON>> ret;
|
std::variant<RawResponse, std::shared_ptr<const phosg::JSON>> ret;
|
||||||
uint32_t serialize_options = phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY;
|
uint32_t serialize_options = phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY;
|
||||||
uint64_t start_time = phosg::now();
|
uint64_t start_time = phosg::now();
|
||||||
|
|
||||||
this->log.info_f("{} ...", req.path);
|
this->log.info_f("{} ...", req.path);
|
||||||
|
|
||||||
auto resp = make_unique<HTTPResponse>();
|
auto resp = std::make_unique<HTTPResponse>();
|
||||||
resp->http_version = req.http_version;
|
resp->http_version = req.http_version;
|
||||||
resp->response_code = 200;
|
resp->response_code = 200;
|
||||||
resp->headers.emplace("Server", "newserv");
|
resp->headers.emplace("Server", "newserv");
|
||||||
@@ -981,39 +979,41 @@ asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared
|
|||||||
ret = co_await this->router.call_handler(c, req);
|
ret = co_await this->router.call_handler(c, req);
|
||||||
|
|
||||||
} catch (const HTTPError& e) {
|
} catch (const HTTPError& e) {
|
||||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
ret = std::make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
||||||
resp->response_code = e.code;
|
resp->response_code = e.code;
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
ret = std::make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
||||||
resp->response_code = 500;
|
resp->response_code = 500;
|
||||||
}
|
}
|
||||||
uint64_t handler_end = phosg::now();
|
uint64_t handler_end = phosg::now();
|
||||||
|
|
||||||
if (holds_alternative<shared_ptr<const phosg::JSON>>(ret)) {
|
if (holds_alternative<std::shared_ptr<const phosg::JSON>>(ret)) {
|
||||||
// If the handler returns nullptr (not JSON null), assume it called enable_websockets and send no response
|
// If the handler returns nullptr (not JSON null), assume it called enable_websockets and send no response
|
||||||
auto& json = get<shared_ptr<const phosg::JSON>>(ret);
|
auto& json = get<std::shared_ptr<const phosg::JSON>>(ret);
|
||||||
if (!json) {
|
if (!json) {
|
||||||
co_return nullptr;
|
co_return nullptr;
|
||||||
}
|
}
|
||||||
resp->headers.emplace("Content-Type", "application/json");
|
resp->headers.emplace("Content-Type", "application/json");
|
||||||
resp->data = co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> string {
|
resp->data = co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::string {
|
||||||
return json->serialize(serialize_options, 0);
|
return json->serialize(serialize_options, 0);
|
||||||
});
|
});
|
||||||
uint64_t serialize_end = phosg::now();
|
uint64_t serialize_end = phosg::now();
|
||||||
|
|
||||||
string handler_time = phosg::format_duration(handler_end - start_time);
|
this->log.info_f("{} in [handler: {}, serialize: {}, size: {}]",
|
||||||
string serialize_time = phosg::format_duration(serialize_end - handler_end);
|
req.path,
|
||||||
string size_str = phosg::format_size(resp->data.size());
|
phosg::format_duration(handler_end - start_time),
|
||||||
this->log.info_f("{} in [handler: {}, serialize: {}, size: {}]", req.path, handler_time, serialize_time, size_str);
|
phosg::format_duration(serialize_end - handler_end),
|
||||||
|
phosg::format_size(resp->data.size()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
auto& raw_resp = get<RawResponse>(ret);
|
auto& raw_resp = get<RawResponse>(ret);
|
||||||
resp->headers.emplace("Content-Type", std::move(raw_resp.content_type));
|
resp->headers.emplace("Content-Type", std::move(raw_resp.content_type));
|
||||||
resp->data = std::move(raw_resp.data);
|
resp->data = std::move(raw_resp.data);
|
||||||
|
|
||||||
string handler_time = phosg::format_duration(handler_end - start_time);
|
this->log.info_f("{} in [handler: {}, size: {}]",
|
||||||
string size_str = phosg::format_size(resp->data.size());
|
req.path,
|
||||||
this->log.info_f("{} in [handler: {}, size: {}]", req.path, handler_time, size_str);
|
phosg::format_duration(handler_end - start_time),
|
||||||
|
phosg::format_size(resp->data.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return resp;
|
co_return resp;
|
||||||
|
|||||||
+14
-16
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static inline uint16_t collapse_checksum(uint32_t sum) {
|
static inline uint16_t collapse_checksum(uint32_t sum) {
|
||||||
// It's impossible for this to be necessary more than twice: the first addition can carry out at most a single bit.
|
// It's impossible for this to be necessary more than twice: the first addition can carry out at most a single bit.
|
||||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||||
@@ -65,13 +63,13 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid link type");
|
throw std::logic_error("invalid link type");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse inner protocol headers
|
// Parse inner protocol headers
|
||||||
switch (proto) {
|
switch (proto) {
|
||||||
case Protocol::NONE:
|
case Protocol::NONE:
|
||||||
throw runtime_error("unknown protocol");
|
throw std::runtime_error("unknown protocol");
|
||||||
case Protocol::LCP:
|
case Protocol::LCP:
|
||||||
this->payload_size -= sizeof(LCPHeader);
|
this->payload_size -= sizeof(LCPHeader);
|
||||||
this->lcp = &r.get<LCPHeader>();
|
this->lcp = &r.get<LCPHeader>();
|
||||||
@@ -87,7 +85,7 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
|||||||
case Protocol::IPV4:
|
case Protocol::IPV4:
|
||||||
this->ipv4 = &r.get<IPv4Header>();
|
this->ipv4 = &r.get<IPv4Header>();
|
||||||
if (this->payload_size < this->ipv4->size) {
|
if (this->payload_size < this->ipv4->size) {
|
||||||
throw invalid_argument("ipv4 header specifies size larger than frame");
|
throw std::invalid_argument("ipv4 header specifies size larger than frame");
|
||||||
}
|
}
|
||||||
this->payload_size = this->ipv4->size - sizeof(IPv4Header);
|
this->payload_size = this->ipv4->size - sizeof(IPv4Header);
|
||||||
|
|
||||||
@@ -95,7 +93,7 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
|||||||
this->tcp = &r.get<TCPHeader>();
|
this->tcp = &r.get<TCPHeader>();
|
||||||
size_t tcp_header_size = (this->tcp->flags >> 12) * 4;
|
size_t tcp_header_size = (this->tcp->flags >> 12) * 4;
|
||||||
if (tcp_header_size < sizeof(TCPHeader) || tcp_header_size > this->payload_size) {
|
if (tcp_header_size < sizeof(TCPHeader) || tcp_header_size > this->payload_size) {
|
||||||
throw invalid_argument("frame is too small for tcp4 header with options");
|
throw std::invalid_argument("frame is too small for tcp4 header with options");
|
||||||
}
|
}
|
||||||
this->tcp_options_size = tcp_header_size - sizeof(TCPHeader);
|
this->tcp_options_size = tcp_header_size - sizeof(TCPHeader);
|
||||||
this->payload_size -= tcp_header_size;
|
this->payload_size -= tcp_header_size;
|
||||||
@@ -115,12 +113,12 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
|||||||
this->payload = r.getv(this->payload_size);
|
this->payload = r.getv(this->payload_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
string FrameInfo::header_str() const {
|
std::string FrameInfo::header_str() const {
|
||||||
if (!this->ether && !this->hdlc) {
|
if (!this->ether && !this->hdlc) {
|
||||||
return "<invalid-frame-info>";
|
return "<invalid-frame-info>";
|
||||||
}
|
}
|
||||||
|
|
||||||
string ret;
|
std::string ret;
|
||||||
if (this->ether) {
|
if (this->ether) {
|
||||||
ret = std::format(
|
ret = std::format(
|
||||||
"ETHER:{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}->{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
"ETHER:{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}->{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
||||||
@@ -187,10 +185,10 @@ string FrameInfo::header_str() const {
|
|||||||
|
|
||||||
void FrameInfo::truncate(size_t new_total_size) {
|
void FrameInfo::truncate(size_t new_total_size) {
|
||||||
if (new_total_size > this->total_size) {
|
if (new_total_size > this->total_size) {
|
||||||
throw logic_error("truncate call expands frame size");
|
throw std::logic_error("truncate call expands frame size");
|
||||||
}
|
}
|
||||||
if (new_total_size < this->payload_size) {
|
if (new_total_size < this->payload_size) {
|
||||||
throw logic_error("truncate call destroys part of header");
|
throw std::logic_error("truncate call destroys part of header");
|
||||||
}
|
}
|
||||||
size_t delta_bytes = this->total_size - new_total_size;
|
size_t delta_bytes = this->total_size - new_total_size;
|
||||||
this->total_size -= delta_bytes;
|
this->total_size -= delta_bytes;
|
||||||
@@ -222,7 +220,7 @@ uint16_t FrameInfo::computed_ipv4_header_checksum(const IPv4Header& ipv4) {
|
|||||||
|
|
||||||
uint16_t FrameInfo::computed_ipv4_header_checksum() const {
|
uint16_t FrameInfo::computed_ipv4_header_checksum() const {
|
||||||
if (!this->ipv4) {
|
if (!this->ipv4) {
|
||||||
throw logic_error("cannot compute ipv4 header checksum for non-ipv4 frame");
|
throw std::logic_error("cannot compute ipv4 header checksum for non-ipv4 frame");
|
||||||
}
|
}
|
||||||
return this->computed_ipv4_header_checksum(*this->ipv4);
|
return this->computed_ipv4_header_checksum(*this->ipv4);
|
||||||
}
|
}
|
||||||
@@ -252,10 +250,10 @@ uint16_t FrameInfo::computed_udp4_checksum(
|
|||||||
|
|
||||||
uint16_t FrameInfo::computed_udp4_checksum() const {
|
uint16_t FrameInfo::computed_udp4_checksum() const {
|
||||||
if (!this->ipv4) {
|
if (!this->ipv4) {
|
||||||
throw logic_error("cannot compute udp header checksum for non-ipv4 frame");
|
throw std::logic_error("cannot compute udp header checksum for non-ipv4 frame");
|
||||||
}
|
}
|
||||||
if (!this->udp) {
|
if (!this->udp) {
|
||||||
throw logic_error("cannot compute udp header checksum for non-udp frame");
|
throw std::logic_error("cannot compute udp header checksum for non-udp frame");
|
||||||
}
|
}
|
||||||
return this->computed_udp4_checksum(
|
return this->computed_udp4_checksum(
|
||||||
*this->ipv4, *this->udp, this->payload, this->payload_size);
|
*this->ipv4, *this->udp, this->payload, this->payload_size);
|
||||||
@@ -293,10 +291,10 @@ uint16_t FrameInfo::computed_tcp4_checksum(
|
|||||||
|
|
||||||
uint16_t FrameInfo::computed_tcp4_checksum() const {
|
uint16_t FrameInfo::computed_tcp4_checksum() const {
|
||||||
if (!this->ipv4) {
|
if (!this->ipv4) {
|
||||||
throw logic_error("cannot compute tcp header checksum for non-ipv4 frame");
|
throw std::logic_error("cannot compute tcp header checksum for non-ipv4 frame");
|
||||||
}
|
}
|
||||||
if (!this->tcp) {
|
if (!this->tcp) {
|
||||||
throw logic_error("cannot compute tcp header checksum for non-tcp frame");
|
throw std::logic_error("cannot compute tcp header checksum for non-tcp frame");
|
||||||
}
|
}
|
||||||
return this->computed_tcp4_checksum(
|
return this->computed_tcp4_checksum(
|
||||||
*this->ipv4, *this->tcp, this->tcp + 1,
|
*this->ipv4, *this->tcp, this->tcp + 1,
|
||||||
@@ -317,7 +315,7 @@ uint16_t FrameInfo::computed_hdlc_checksum(const void* vdata, size_t size) {
|
|||||||
|
|
||||||
uint16_t FrameInfo::computed_hdlc_checksum() const {
|
uint16_t FrameInfo::computed_hdlc_checksum() const {
|
||||||
if (!this->hdlc) {
|
if (!this->hdlc) {
|
||||||
throw logic_error("cannot compute HDLC checksum for non-HDLC frame");
|
throw std::logic_error("cannot compute HDLC checksum for non-HDLC frame");
|
||||||
}
|
}
|
||||||
return this->computed_hdlc_checksum(&this->hdlc->address, this->total_size - 4);
|
return this->computed_hdlc_checksum(&this->hdlc->address, this->total_size - 4);
|
||||||
}
|
}
|
||||||
|
|||||||
+132
-132
@@ -13,18 +13,16 @@
|
|||||||
#include "IPFrameInfo.hh"
|
#include "IPFrameInfo.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
|
static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
|
||||||
uint8_t* data = reinterpret_cast<uint8_t*>(vdata);
|
uint8_t* data = reinterpret_cast<uint8_t*>(vdata);
|
||||||
if (size < 2) {
|
if (size < 2) {
|
||||||
throw runtime_error("escaped HDLC frame is too small");
|
throw std::runtime_error("escaped HDLC frame is too small");
|
||||||
}
|
}
|
||||||
if (data[0] != 0x7E) {
|
if (data[0] != 0x7E) {
|
||||||
throw runtime_error("HDLC frame does not begin with 7E");
|
throw std::runtime_error("HDLC frame does not begin with 7E");
|
||||||
}
|
}
|
||||||
if (data[size - 1] != 0x7E) {
|
if (data[size - 1] != 0x7E) {
|
||||||
throw runtime_error("HDLC frame does not end with 7E");
|
throw std::runtime_error("HDLC frame does not end with 7E");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t read_offset = 1;
|
size_t read_offset = 1;
|
||||||
@@ -33,33 +31,34 @@ static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
|
|||||||
uint8_t ch = data[read_offset++];
|
uint8_t ch = data[read_offset++];
|
||||||
if (ch == 0x7D) {
|
if (ch == 0x7D) {
|
||||||
if (read_offset >= size - 1) {
|
if (read_offset >= size - 1) {
|
||||||
throw runtime_error("abort sequence received");
|
throw std::runtime_error("abort sequence received");
|
||||||
}
|
}
|
||||||
ch = data[read_offset++] ^ 0x20;
|
ch = data[read_offset++] ^ 0x20;
|
||||||
}
|
}
|
||||||
data[write_offset++] = ch;
|
data[write_offset++] = ch;
|
||||||
}
|
}
|
||||||
if (write_offset > size - 1) {
|
if (write_offset > size - 1) {
|
||||||
throw logic_error("unescaping HDLC frame resulted in longer data string");
|
throw std::logic_error("unescaping HDLC frame resulted in longer data string");
|
||||||
}
|
}
|
||||||
data[write_offset++] = 0x7E;
|
data[write_offset++] = 0x7E;
|
||||||
return write_offset;
|
return write_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string escape_hdlc_frame(const void* data, size_t size, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
static std::string escape_hdlc_frame(
|
||||||
|
const void* data, size_t size, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
||||||
if (size < 2) {
|
if (size < 2) {
|
||||||
throw runtime_error("HDLC frame too small for start and end sentinels");
|
throw std::runtime_error("HDLC frame too small for start and end sentinels");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader r(data, size);
|
phosg::StringReader r(data, size);
|
||||||
if (r.pget_u8(size - 1) != 0x7E) {
|
if (r.pget_u8(size - 1) != 0x7E) {
|
||||||
throw runtime_error("HDLC frame does not end with 7E");
|
throw std::runtime_error("HDLC frame does not end with 7E");
|
||||||
}
|
}
|
||||||
r.truncate(size - 1);
|
r.truncate(size - 1);
|
||||||
if (r.get_u8() != 0x7E) {
|
if (r.get_u8() != 0x7E) {
|
||||||
throw runtime_error("HDLC frame does not begin with 7E");
|
throw std::runtime_error("HDLC frame does not begin with 7E");
|
||||||
}
|
}
|
||||||
string ret("\x7E", 1);
|
std::string ret("\x7E", 1);
|
||||||
|
|
||||||
while (!r.eof()) {
|
while (!r.eof()) {
|
||||||
uint8_t ch = r.get_u8();
|
uint8_t ch = r.get_u8();
|
||||||
@@ -74,7 +73,7 @@ static string escape_hdlc_frame(const void* data, size_t size, uint32_t escape_c
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string escape_hdlc_frame(const string& data, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
static std::string escape_hdlc_frame(const std::string& data, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
||||||
return escape_hdlc_frame(data.data(), data.size(), escape_control_character_flags);
|
return escape_hdlc_frame(data.data(), data.size(), escape_control_character_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +113,7 @@ void IPSSClient::TCPConnection::drain_outbound_data(size_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
throw logic_error("attempted to drain more outbound data than was present");
|
throw std::logic_error("attempted to drain more outbound data than was present");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ void IPSSClient::TCPConnection::linearize_outbound_data(size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IPSSClient::IPSSClient(
|
IPSSClient::IPSSClient(
|
||||||
shared_ptr<IPStackSimulator> sim, uint64_t network_id, VirtualNetworkProtocol protocol, asio::ip::tcp::socket&& sock)
|
std::shared_ptr<IPStackSimulator> sim, uint64_t network_id, VirtualNetworkProtocol protocol, asio::ip::tcp::socket&& sock)
|
||||||
: io_context(sim->get_io_context()),
|
: io_context(sim->get_io_context()),
|
||||||
sim(sim),
|
sim(sim),
|
||||||
network_id(network_id),
|
network_id(network_id),
|
||||||
@@ -143,7 +142,7 @@ IPSSClient::IPSSClient(
|
|||||||
void IPSSClient::reschedule_idle_timeout() {
|
void IPSSClient::reschedule_idle_timeout() {
|
||||||
auto sim = this->sim.lock();
|
auto sim = this->sim.lock();
|
||||||
if (!sim) {
|
if (!sim) {
|
||||||
throw runtime_error("cannot reschedule idle timeout when simulator is missing");
|
throw std::runtime_error("cannot reschedule idle timeout when simulator is missing");
|
||||||
}
|
}
|
||||||
this->idle_timeout_timer.cancel();
|
this->idle_timeout_timer.cancel();
|
||||||
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->client_idle_timeout_usecs));
|
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->client_idle_timeout_usecs));
|
||||||
@@ -204,7 +203,7 @@ void IPSSChannel::add_inbound_data(const void* data, size_t size) {
|
|||||||
// If recv_buf is not null, there is a coroutine waiting to receive data, and inbound_data must be empty. Copy the
|
// If recv_buf is not null, there is a coroutine waiting to receive data, and inbound_data must be empty. Copy the
|
||||||
// data directly to the waiting coroutine's buffer, and put the rest in this->inbound_data if needed.
|
// data directly to the waiting coroutine's buffer, and put the rest in this->inbound_data if needed.
|
||||||
if (this->recv_buf) {
|
if (this->recv_buf) {
|
||||||
size_t direct_size = min<size_t>(this->recv_buf_size, size);
|
size_t direct_size = std::min<size_t>(this->recv_buf_size, size);
|
||||||
memcpy(this->recv_buf, data, direct_size);
|
memcpy(this->recv_buf, data, direct_size);
|
||||||
data = reinterpret_cast<const uint8_t*>(data) + direct_size;
|
data = reinterpret_cast<const uint8_t*>(data) + direct_size;
|
||||||
size -= direct_size;
|
size -= direct_size;
|
||||||
@@ -221,7 +220,7 @@ void IPSSChannel::add_inbound_data(const void* data, size_t size) {
|
|||||||
this->data_available_signal.set();
|
this->data_available_signal.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPSSChannel::send_raw(string&& data) {
|
void IPSSChannel::send_raw(std::string&& data) {
|
||||||
auto c = this->ipss_client.lock();
|
auto c = this->ipss_client.lock();
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return;
|
return;
|
||||||
@@ -249,7 +248,7 @@ void IPSSChannel::send_raw(string&& data) {
|
|||||||
|
|
||||||
asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
||||||
if (this->recv_buf) {
|
if (this->recv_buf) {
|
||||||
throw logic_error("recv_raw called again when it was already pending");
|
throw std::logic_error("recv_raw called again when it was already pending");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive as much data as possible from the pending inbound data buffer
|
// Receive as much data as possible from the pending inbound data buffer
|
||||||
@@ -274,7 +273,7 @@ asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
|||||||
this->recv_buf_size = size;
|
this->recv_buf_size = size;
|
||||||
while (this->recv_buf) {
|
while (this->recv_buf) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
throw runtime_error("IPSS channel closed");
|
throw std::runtime_error("IPSS channel closed");
|
||||||
}
|
}
|
||||||
this->data_available_signal.clear();
|
this->data_available_signal.clear();
|
||||||
co_await this->data_available_signal.wait();
|
co_await this->data_available_signal.wait();
|
||||||
@@ -282,18 +281,18 @@ asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IPStackSimulator::IPStackSimulator(shared_ptr<ServerState> state)
|
IPStackSimulator::IPStackSimulator(std::shared_ptr<ServerState> state)
|
||||||
: Server(state->io_context, "[IPStackSimulator] "), state(state) {
|
: Server(state->io_context, "[IPStackSimulator] "), state(state) {
|
||||||
this->host_mac_address_bytes.clear(0x90);
|
this->host_mac_address_bytes.clear(0x90);
|
||||||
this->broadcast_mac_address_bytes.clear(0xFF);
|
this->broadcast_mac_address_bytes.clear(0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPStackSimulator::listen(const std::string& name, const string& addr, int port, VirtualNetworkProtocol protocol) {
|
void IPStackSimulator::listen(const std::string& name, const std::string& addr, int port, VirtualNetworkProtocol protocol) {
|
||||||
if (port == 0) {
|
if (port == 0) {
|
||||||
throw std::runtime_error("Listening port cannot be zero");
|
throw std::runtime_error("Listening port cannot be zero");
|
||||||
}
|
}
|
||||||
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
||||||
auto sock = make_shared<IPSSSocket>();
|
auto sock = std::make_shared<IPSSSocket>();
|
||||||
sock->name = name;
|
sock->name = name;
|
||||||
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
||||||
sock->protocol = protocol;
|
sock->protocol = protocol;
|
||||||
@@ -320,12 +319,12 @@ uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const IPv4Header& ipv4,
|
|||||||
|
|
||||||
uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const FrameInfo& fi) {
|
uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const FrameInfo& fi) {
|
||||||
if (!fi.ipv4 || !fi.tcp) {
|
if (!fi.ipv4 || !fi.tcp) {
|
||||||
throw logic_error("tcp_conn_key_for_frame called on non-TCP frame");
|
throw std::logic_error("tcp_conn_key_for_frame called on non-TCP frame");
|
||||||
}
|
}
|
||||||
return IPStackSimulator::tcp_conn_key_for_client_frame(*fi.ipv4, *fi.tcp);
|
return IPStackSimulator::tcp_conn_key_for_client_frame(*fi.ipv4, *fi.tcp);
|
||||||
}
|
}
|
||||||
|
|
||||||
string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
std::string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
||||||
be_uint32_t be_addr = addr;
|
be_uint32_t be_addr = addr;
|
||||||
char addr_str[INET_ADDRSTRLEN];
|
char addr_str[INET_ADDRSTRLEN];
|
||||||
memset(addr_str, 0, sizeof(addr_str));
|
memset(addr_str, 0, sizeof(addr_str));
|
||||||
@@ -336,16 +335,16 @@ string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string IPStackSimulator::str_for_tcp_connection(
|
std::string IPStackSimulator::str_for_tcp_connection(
|
||||||
shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> conn) {
|
std::shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> conn) {
|
||||||
uint64_t key = IPStackSimulator::tcp_conn_key_for_connection(conn);
|
uint64_t key = IPStackSimulator::tcp_conn_key_for_connection(conn);
|
||||||
string server_netloc_str = str_for_ipv4_netloc(conn->server_addr, conn->server_port);
|
std::string server_netloc_str = str_for_ipv4_netloc(conn->server_addr, conn->server_port);
|
||||||
string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn->client_port);
|
std::string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn->client_port);
|
||||||
return std::format("{:016X} ({} -> {})", key, client_netloc_str, server_netloc_str);
|
return std::format("{:016X} ({} -> {})", key, client_netloc_str, server_netloc_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
||||||
shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
||||||
|
|
||||||
struct TapServerEthernetHeader {
|
struct TapServerEthernetHeader {
|
||||||
phosg::le_uint16_t frame_size;
|
phosg::le_uint16_t frame_size;
|
||||||
@@ -358,9 +357,9 @@ asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
|||||||
header.ether.src_mac = this->host_mac_address_bytes;
|
header.ether.src_mac = this->host_mac_address_bytes;
|
||||||
switch (proto) {
|
switch (proto) {
|
||||||
case FrameInfo::Protocol::NONE:
|
case FrameInfo::Protocol::NONE:
|
||||||
throw logic_error("layer 3 protocol not specified");
|
throw std::logic_error("layer 3 protocol not specified");
|
||||||
case FrameInfo::Protocol::LCP:
|
case FrameInfo::Protocol::LCP:
|
||||||
throw logic_error("cannot send LCP frame over Ethernet");
|
throw std::logic_error("cannot send LCP frame over Ethernet");
|
||||||
case FrameInfo::Protocol::IPV4:
|
case FrameInfo::Protocol::IPV4:
|
||||||
header.ether.protocol = 0x0800;
|
header.ether.protocol = 0x0800;
|
||||||
break;
|
break;
|
||||||
@@ -368,16 +367,17 @@ asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
|||||||
header.ether.protocol = 0x0806;
|
header.ether.protocol = 0x0806;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown layer 3 protocol");
|
throw std::logic_error("unknown layer 3 protocol");
|
||||||
}
|
}
|
||||||
header.frame_size = size + sizeof(EthernetHeader);
|
header.frame_size = size + sizeof(EthernetHeader);
|
||||||
|
|
||||||
array<asio::const_buffer, 2> bufs{asio::buffer(static_cast<const void*>(&header), sizeof(header)), asio::buffer(data, size)};
|
std::array<asio::const_buffer, 2> bufs{
|
||||||
|
asio::buffer(static_cast<const void*>(&header), sizeof(header)), asio::buffer(data, size)};
|
||||||
co_await asio::async_write(c->sock, bufs, asio::use_awaitable);
|
co_await asio::async_write(c->sock, bufs, asio::use_awaitable);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
||||||
shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size, bool is_raw) const {
|
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size, bool is_raw) const {
|
||||||
|
|
||||||
HDLCHeader hdlc;
|
HDLCHeader hdlc;
|
||||||
hdlc.start_sentinel1 = 0x7E;
|
hdlc.start_sentinel1 = 0x7E;
|
||||||
@@ -385,7 +385,7 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
|||||||
hdlc.control = 0x03;
|
hdlc.control = 0x03;
|
||||||
switch (proto) {
|
switch (proto) {
|
||||||
case FrameInfo::Protocol::NONE:
|
case FrameInfo::Protocol::NONE:
|
||||||
throw logic_error("layer 3 protocol not specified");
|
throw std::logic_error("layer 3 protocol not specified");
|
||||||
case FrameInfo::Protocol::LCP:
|
case FrameInfo::Protocol::LCP:
|
||||||
hdlc.protocol = 0xC021;
|
hdlc.protocol = 0xC021;
|
||||||
break;
|
break;
|
||||||
@@ -399,9 +399,9 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
|||||||
hdlc.protocol = 0x0021;
|
hdlc.protocol = 0x0021;
|
||||||
break;
|
break;
|
||||||
case FrameInfo::Protocol::ARP:
|
case FrameInfo::Protocol::ARP:
|
||||||
throw runtime_error("cannot send ARP packets over HDLC");
|
throw std::runtime_error("cannot send ARP packets over HDLC");
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown layer 3 protocol");
|
throw std::logic_error("unknown layer 3 protocol");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
@@ -410,14 +410,14 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
|||||||
w.put_u16l(FrameInfo::computed_hdlc_checksum(w.str().data() + 1, w.size() - 1));
|
w.put_u16l(FrameInfo::computed_hdlc_checksum(w.str().data() + 1, w.size() - 1));
|
||||||
w.put_u8(0x7E);
|
w.put_u8(0x7E);
|
||||||
|
|
||||||
string escaped = escape_hdlc_frame(w.str(), c->hdlc_escape_control_character_flags);
|
std::string escaped = escape_hdlc_frame(w.str(), c->hdlc_escape_control_character_flags);
|
||||||
if (this->log.debug_f("Sending HDLC frame to virtual network (escaped to {:X} bytes)", escaped.size())) {
|
if (this->log.debug_f("Sending HDLC frame to virtual network (escaped to {:X} bytes)", escaped.size())) {
|
||||||
phosg::print_data(stderr, w.str());
|
phosg::print_data(stderr, w.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_raw) {
|
if (!is_raw) {
|
||||||
phosg::le_uint16_t frame_size = escaped.size();
|
phosg::le_uint16_t frame_size = escaped.size();
|
||||||
array<asio::const_buffer, 2> bufs{
|
std::array<asio::const_buffer, 2> bufs{
|
||||||
asio::buffer(static_cast<const void*>(&frame_size), sizeof(frame_size)),
|
asio::buffer(static_cast<const void*>(&frame_size), sizeof(frame_size)),
|
||||||
asio::buffer(escaped.data(), escaped.size())};
|
asio::buffer(escaped.data(), escaped.size())};
|
||||||
co_await asio::async_write(c->sock, bufs, asio::use_awaitable);
|
co_await asio::async_write(c->sock, bufs, asio::use_awaitable);
|
||||||
@@ -427,7 +427,7 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::send_layer3_frame(
|
asio::awaitable<void> IPStackSimulator::send_layer3_frame(
|
||||||
shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
||||||
switch (c->protocol) {
|
switch (c->protocol) {
|
||||||
case VirtualNetworkProtocol::ETHERNET_TAPSERVER:
|
case VirtualNetworkProtocol::ETHERNET_TAPSERVER:
|
||||||
co_await this->send_ethernet_tapserver_frame(c, proto, data, size);
|
co_await this->send_ethernet_tapserver_frame(c, proto, data, size);
|
||||||
@@ -439,11 +439,11 @@ asio::awaitable<void> IPStackSimulator::send_layer3_frame(
|
|||||||
co_await this->send_hdlc_frame(c, proto, data, size, true);
|
co_await this->send_hdlc_frame(c, proto, data, size, true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown link type");
|
throw std::logic_error("unknown link type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c, const void* data, size_t size) {
|
asio::awaitable<void> IPStackSimulator::on_client_frame(std::shared_ptr<IPSSClient> c, const void* data, size_t size) {
|
||||||
FrameInfo::LinkType link_type = (c->protocol == VirtualNetworkProtocol::ETHERNET_TAPSERVER)
|
FrameInfo::LinkType link_type = (c->protocol == VirtualNetworkProtocol::ETHERNET_TAPSERVER)
|
||||||
? FrameInfo::LinkType::ETHERNET
|
? FrameInfo::LinkType::ETHERNET
|
||||||
: FrameInfo::LinkType::HDLC;
|
: FrameInfo::LinkType::HDLC;
|
||||||
@@ -461,17 +461,17 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
|
|||||||
if (c->mac_addr.is_filled_with(0)) {
|
if (c->mac_addr.is_filled_with(0)) {
|
||||||
c->mac_addr = fi.ether->src_mac;
|
c->mac_addr = fi.ether->src_mac;
|
||||||
} else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) {
|
} else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) {
|
||||||
throw runtime_error("client sent IPv4 packet from different MAC address");
|
throw std::runtime_error("client sent IPv4 packet from different MAC address");
|
||||||
}
|
}
|
||||||
} else if (fi.hdlc) {
|
} else if (fi.hdlc) {
|
||||||
uint16_t expected_checksum = fi.computed_hdlc_checksum();
|
uint16_t expected_checksum = fi.computed_hdlc_checksum();
|
||||||
uint16_t stored_checksum = fi.stored_hdlc_checksum();
|
uint16_t stored_checksum = fi.stored_hdlc_checksum();
|
||||||
if (expected_checksum != stored_checksum) {
|
if (expected_checksum != stored_checksum) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"HDLC checksum is incorrect ({:04X} expected, {:04X} received)", expected_checksum, stored_checksum));
|
"HDLC checksum is incorrect ({:04X} expected, {:04X} received)", expected_checksum, stored_checksum));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("frame is not Ethernet or HDLC");
|
throw std::runtime_error("frame is not Ethernet or HDLC");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fi.lcp) {
|
if (fi.lcp) {
|
||||||
@@ -489,18 +489,18 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
|
|||||||
} else if (fi.ipv4) {
|
} else if (fi.ipv4) {
|
||||||
uint16_t expected_ipv4_checksum = fi.computed_ipv4_header_checksum();
|
uint16_t expected_ipv4_checksum = fi.computed_ipv4_header_checksum();
|
||||||
if (fi.ipv4->checksum != expected_ipv4_checksum) {
|
if (fi.ipv4->checksum != expected_ipv4_checksum) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"IPv4 header checksum is incorrect ({:04X} expected, {:04X} received)", expected_ipv4_checksum, fi.ipv4->checksum));
|
"IPv4 header checksum is incorrect ({:04X} expected, {:04X} received)", expected_ipv4_checksum, fi.ipv4->checksum));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((fi.ipv4->src_addr != c->ipv4_addr) && (fi.ipv4->src_addr != 0)) {
|
if ((fi.ipv4->src_addr != c->ipv4_addr) && (fi.ipv4->src_addr != 0)) {
|
||||||
throw runtime_error("client sent IPv4 packet from different IPv4 address");
|
throw std::runtime_error("client sent IPv4 packet from different IPv4 address");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fi.udp) {
|
if (fi.udp) {
|
||||||
uint16_t expected_udp_checksum = fi.computed_udp4_checksum();
|
uint16_t expected_udp_checksum = fi.computed_udp4_checksum();
|
||||||
if (fi.udp->checksum != expected_udp_checksum) {
|
if (fi.udp->checksum != expected_udp_checksum) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"UDP checksum is incorrect ({:04X} expected, {:04X} received)", expected_udp_checksum, fi.udp->checksum));
|
"UDP checksum is incorrect ({:04X} expected, {:04X} received)", expected_udp_checksum, fi.udp->checksum));
|
||||||
}
|
}
|
||||||
co_await this->on_client_udp_frame(c, fi);
|
co_await this->on_client_udp_frame(c, fi);
|
||||||
@@ -508,27 +508,27 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
|
|||||||
} else if (fi.tcp) {
|
} else if (fi.tcp) {
|
||||||
uint16_t expected_tcp_checksum = fi.computed_tcp4_checksum();
|
uint16_t expected_tcp_checksum = fi.computed_tcp4_checksum();
|
||||||
if (fi.tcp->checksum != expected_tcp_checksum) {
|
if (fi.tcp->checksum != expected_tcp_checksum) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"TCP checksum is incorrect ({:04X} expected, {:04X} received)", expected_tcp_checksum, fi.tcp->checksum));
|
"TCP checksum is incorrect ({:04X} expected, {:04X} received)", expected_tcp_checksum, fi.tcp->checksum));
|
||||||
}
|
}
|
||||||
co_await this->on_client_tcp_frame(c, fi);
|
co_await this->on_client_tcp_frame(c, fi);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("frame uses unsupported IPv4 protocol");
|
throw std::runtime_error("frame uses unsupported IPv4 protocol");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("frame is not IPv4");
|
throw std::runtime_error("frame is not IPv4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||||
switch (fi.lcp->command) {
|
switch (fi.lcp->command) {
|
||||||
case 0x01: { // Configure-Request
|
case 0x01: { // Configure-Request
|
||||||
auto opts_r = fi.read_payload();
|
auto opts_r = fi.read_payload();
|
||||||
while (!opts_r.eof()) {
|
while (!opts_r.eof()) {
|
||||||
uint8_t opt = opts_r.get_u8();
|
uint8_t opt = opts_r.get_u8();
|
||||||
string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
std::string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||||
phosg::StringReader opt_data_r(opt_data);
|
phosg::StringReader opt_data_r(opt_data);
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 0x01: // Maximum receive unit
|
case 0x01: // Maximum receive unit
|
||||||
@@ -546,9 +546,9 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
|||||||
case 0x04: // Quality protocol
|
case 0x04: // Quality protocol
|
||||||
case 0x07: // Protocol field compression
|
case 0x07: // Protocol field compression
|
||||||
case 0x08: // Address and control field compression
|
case 0x08: // Address and control field compression
|
||||||
throw runtime_error(std::format("unimplemented LCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
throw std::runtime_error(std::format("unimplemented LCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown LCP option");
|
throw std::runtime_error("unknown LCP option");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Technically, we should implement the LCP state machine, but I'm too lazy to do this right now. In our
|
// Technically, we should implement the LCP state machine, but I'm too lazy to do this right now. In our
|
||||||
@@ -591,14 +591,14 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
|||||||
case 0x05: { // Terminate-Request
|
case 0x05: { // Terminate-Request
|
||||||
c->ipv4_addr = 0;
|
c->ipv4_addr = 0;
|
||||||
c->tcp_connections.clear();
|
c->tcp_connections.clear();
|
||||||
string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
std::string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||||
response.at(0) = 0x06; // Terminate-Ack
|
response.at(0) = 0x06; // Terminate-Ack
|
||||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x09: { // Echo-Request
|
case 0x09: { // Echo-Request
|
||||||
string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
std::string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||||
response.at(0) = 0x0A; // Echo-Reply
|
response.at(0) = 0x0A; // Echo-Reply
|
||||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
||||||
break;
|
break;
|
||||||
@@ -614,23 +614,23 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
|||||||
case 0x07: // Code-Reject
|
case 0x07: // Code-Reject
|
||||||
case 0x08: // Protocol-Reject
|
case 0x08: // Protocol-Reject
|
||||||
case 0x0A: // Echo-Reply
|
case 0x0A: // Echo-Reply
|
||||||
throw runtime_error("unimplemented LCP command");
|
throw std::runtime_error("unimplemented LCP command");
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown LCP command");
|
throw std::runtime_error("unknown LCP command");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_pap_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
asio::awaitable<void> IPStackSimulator::on_client_pap_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||||
if (fi.pap->command != 0x01) { // Authenticate-Request
|
if (fi.pap->command != 0x01) { // Authenticate-Request
|
||||||
throw runtime_error("client sent incorrect PAP command");
|
throw std::runtime_error("client sent incorrect PAP command");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto r = fi.read_payload();
|
auto r = fi.read_payload();
|
||||||
string username = r.read(r.get_u8());
|
std::string username = r.read(r.get_u8());
|
||||||
string password = r.read(r.get_u8());
|
std::string password = r.read(r.get_u8());
|
||||||
this->log.info_f("Client logged in with username \"{}\" and password", username);
|
this->log.info_f("Client logged in with username \"{}\" and password", username);
|
||||||
|
|
||||||
static const string login_message = "newserv PPP simulator";
|
static const std::string login_message = "newserv PPP simulator";
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
w.put<PAPHeader>(PAPHeader{
|
w.put<PAPHeader>(PAPHeader{
|
||||||
.command = 0x02, // Authenticate-Ack
|
.command = 0x02, // Authenticate-Ack
|
||||||
@@ -642,7 +642,7 @@ asio::awaitable<void> IPStackSimulator::on_client_pap_frame(shared_ptr<IPSSClien
|
|||||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::PAP, w.str());
|
co_await this->send_layer3_frame(c, FrameInfo::Protocol::PAP, w.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||||
switch (fi.ipcp->command) {
|
switch (fi.ipcp->command) {
|
||||||
case 0x01: { // Configure-Request
|
case 0x01: { // Configure-Request
|
||||||
auto opts_r = fi.read_payload();
|
auto opts_r = fi.read_payload();
|
||||||
@@ -653,11 +653,11 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
|||||||
phosg::StringWriter rejected_opts_w;
|
phosg::StringWriter rejected_opts_w;
|
||||||
while (!opts_r.eof()) {
|
while (!opts_r.eof()) {
|
||||||
uint8_t opt = opts_r.get_u8();
|
uint8_t opt = opts_r.get_u8();
|
||||||
string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
std::string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||||
phosg::StringReader opt_data_r(opt_data);
|
phosg::StringReader opt_data_r(opt_data);
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 0x01: // IP addresses (deprecated as of 1992; we don't support it at all)
|
case 0x01: // IP addresses (deprecated as of 1992; we don't support it at all)
|
||||||
throw runtime_error("IPCP client sent IP-Addresses option");
|
throw std::runtime_error("IPCP client sent IP-Addresses option");
|
||||||
case 0x02: // IP compression protocol
|
case 0x02: // IP compression protocol
|
||||||
rejected_opts_w.put_u8(0x02);
|
rejected_opts_w.put_u8(0x02);
|
||||||
rejected_opts_w.put_u8(opt_data_r.size() + 2);
|
rejected_opts_w.put_u8(opt_data_r.size() + 2);
|
||||||
@@ -675,9 +675,9 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
|||||||
case 0x82: // Primary NBNS server address
|
case 0x82: // Primary NBNS server address
|
||||||
case 0x84: // Secondary NBNS server address
|
case 0x84: // Secondary NBNS server address
|
||||||
case 0x04: // Mobile IP address
|
case 0x04: // Mobile IP address
|
||||||
throw runtime_error(std::format("unimplemented IPCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
throw std::runtime_error(std::format("unimplemented IPCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown IPCP option");
|
throw std::runtime_error("unknown IPCP option");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,7 +755,7 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
|||||||
case 0x05: { // Terminate-Request
|
case 0x05: { // Terminate-Request
|
||||||
c->ipv4_addr = 0;
|
c->ipv4_addr = 0;
|
||||||
c->tcp_connections.clear();
|
c->tcp_connections.clear();
|
||||||
string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
std::string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||||
response.at(0) = 0x06; // Terminate-Ack
|
response.at(0) = 0x06; // Terminate-Ack
|
||||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
||||||
break;
|
break;
|
||||||
@@ -768,21 +768,21 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
|||||||
case 0x04: // Configure-Reject
|
case 0x04: // Configure-Reject
|
||||||
case 0x06: // Terminate-Ack
|
case 0x06: // Terminate-Ack
|
||||||
case 0x07: // Code-Reject
|
case 0x07: // Code-Reject
|
||||||
throw runtime_error("unimplemented IPCP command");
|
throw std::runtime_error("unimplemented IPCP command");
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown LCP command");
|
throw std::runtime_error("unknown LCP command");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_arp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
asio::awaitable<void> IPStackSimulator::on_client_arp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||||
if (fi.arp->hwaddr_len != 6 ||
|
if (fi.arp->hwaddr_len != 6 ||
|
||||||
fi.arp->paddr_len != 4 ||
|
fi.arp->paddr_len != 4 ||
|
||||||
fi.arp->hardware_type != 0x0001 ||
|
fi.arp->hardware_type != 0x0001 ||
|
||||||
fi.arp->protocol_type != 0x0800) {
|
fi.arp->protocol_type != 0x0800) {
|
||||||
throw runtime_error("unsupported ARP parameters");
|
throw std::runtime_error("unsupported ARP parameters");
|
||||||
}
|
}
|
||||||
if (fi.payload_size < 20) {
|
if (fi.payload_size < 20) {
|
||||||
throw runtime_error("ARP payload too small");
|
throw std::runtime_error("ARP payload too small");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->ipv4_addr == 0) {
|
if (c->ipv4_addr == 0) {
|
||||||
@@ -816,7 +816,7 @@ asio::awaitable<void> IPStackSimulator::on_client_arp_frame(shared_ptr<IPSSClien
|
|||||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::ARP, w.str());
|
co_await this->send_layer3_frame(c, FrameInfo::Protocol::ARP, w.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
asio::awaitable<void> IPStackSimulator::on_client_udp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||||
// We only implement DHCP and newserv's DNS server here.
|
// We only implement DHCP and newserv's DNS server here.
|
||||||
|
|
||||||
// Every received UDP packet will elicit exactly one UDP response from newserv, so we prepare the headers in advance
|
// Every received UDP packet will elicit exactly one UDP response from newserv, so we prepare the headers in advance
|
||||||
@@ -838,24 +838,24 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
|||||||
// r_udp.size filled in later
|
// r_udp.size filled in later
|
||||||
// r_udp.checksum filled in later
|
// r_udp.checksum filled in later
|
||||||
|
|
||||||
string r_data;
|
std::string r_data;
|
||||||
if (fi.udp->dest_port == 67) { // DHCP
|
if (fi.udp->dest_port == 67) { // DHCP
|
||||||
auto r = fi.read_payload();
|
auto r = fi.read_payload();
|
||||||
const auto& dhcp = r.get<DHCPHeader>();
|
const auto& dhcp = r.get<DHCPHeader>();
|
||||||
if (dhcp.hardware_type != 1) {
|
if (dhcp.hardware_type != 1) {
|
||||||
throw runtime_error("unknown DHCP hardware type");
|
throw std::runtime_error("unknown DHCP hardware type");
|
||||||
}
|
}
|
||||||
if (dhcp.hardware_address_length != 6) {
|
if (dhcp.hardware_address_length != 6) {
|
||||||
throw runtime_error("unknown DHCP hardware address length");
|
throw std::runtime_error("unknown DHCP hardware address length");
|
||||||
}
|
}
|
||||||
if (dhcp.magic != 0x63825363) {
|
if (dhcp.magic != 0x63825363) {
|
||||||
throw runtime_error("incorrect DHCP magic cookie");
|
throw std::runtime_error("incorrect DHCP magic cookie");
|
||||||
}
|
}
|
||||||
if (dhcp.opcode != 1) { // Request
|
if (dhcp.opcode != 1) { // Request
|
||||||
throw runtime_error("DHCP packet is not a request");
|
throw std::runtime_error("DHCP packet is not a request");
|
||||||
}
|
}
|
||||||
|
|
||||||
unordered_map<uint8_t, string> option_data;
|
std::unordered_map<uint8_t, std::string> option_data;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t option = r.get_u8();
|
uint8_t option = r.get_u8();
|
||||||
if (option == 0xFF) {
|
if (option == 0xFF) {
|
||||||
@@ -868,8 +868,8 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
|||||||
uint8_t command = 0;
|
uint8_t command = 0;
|
||||||
try {
|
try {
|
||||||
command = option_data.at(53).at(0);
|
command = option_data.at(53).at(0);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error("client did not send a DHCP command option");
|
throw std::runtime_error("client did not send a DHCP command option");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command == 7) {
|
if (command == 7) {
|
||||||
@@ -885,7 +885,7 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
|||||||
r_ipv4.dest_addr = c->ipv4_addr;
|
r_ipv4.dest_addr = c->ipv4_addr;
|
||||||
|
|
||||||
if ((command != 1) && (command != 3)) {
|
if ((command != 1) && (command != 3)) {
|
||||||
throw runtime_error("client sent unknown DHCP command option");
|
throw std::runtime_error("client sent unknown DHCP command option");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
@@ -951,19 +951,19 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
|||||||
r_data = std::move(w.str());
|
r_data = std::move(w.str());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("client sent unknown DHCP command");
|
throw std::runtime_error("client sent unknown DHCP command");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (fi.udp->dest_port == 53) { // DNS
|
} else if (fi.udp->dest_port == 53) { // DNS
|
||||||
if (fi.payload_size < 0x0C) {
|
if (fi.payload_size < 0x0C) {
|
||||||
throw runtime_error("DNS payload too small");
|
throw std::runtime_error("DNS payload too small");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr);
|
uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr);
|
||||||
r_data = DNSServer::response_for_query(fi.payload, fi.payload_size, resolved_address);
|
r_data = DNSServer::response_for_query(fi.payload, fi.payload_size, resolved_address);
|
||||||
|
|
||||||
} else { // Not DHCP or DNS
|
} else { // Not DHCP or DNS
|
||||||
throw runtime_error("UDP packet is not DHCP or DNS");
|
throw std::runtime_error("UDP packet is not DHCP or DNS");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!r_data.empty()) {
|
if (!r_data.empty()) {
|
||||||
@@ -973,7 +973,7 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
|||||||
r_udp.checksum = FrameInfo::computed_udp4_checksum(r_ipv4, r_udp, r_data.data(), r_data.size());
|
r_udp.checksum = FrameInfo::computed_udp4_checksum(r_ipv4, r_udp, r_data.data(), r_data.size());
|
||||||
|
|
||||||
if (this->log.should_log(phosg::LogLevel::L_DEBUG)) {
|
if (this->log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||||
string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
|
std::string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
|
||||||
this->log.debug_f("Sending UDP response to {}", remote_str);
|
this->log.debug_f("Sending UDP response to {}", remote_str);
|
||||||
phosg::print_data(stderr, r_data);
|
phosg::print_data(stderr, r_data);
|
||||||
}
|
}
|
||||||
@@ -987,19 +987,19 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||||
this->log.debug_f("Virtual network sent TCP frame (seq={:08X}, ack={:08X})",
|
this->log.debug_f("Virtual network sent TCP frame (seq={:08X}, ack={:08X})",
|
||||||
fi.tcp->seq_num, fi.tcp->ack_num);
|
fi.tcp->seq_num, fi.tcp->ack_num);
|
||||||
|
|
||||||
if (fi.tcp->flags & (TCPHeader::Flag::NS | TCPHeader::Flag::CWR | TCPHeader::Flag::ECE | TCPHeader::Flag::URG)) {
|
if (fi.tcp->flags & (TCPHeader::Flag::NS | TCPHeader::Flag::CWR | TCPHeader::Flag::ECE | TCPHeader::Flag::URG)) {
|
||||||
throw runtime_error("unsupported flag in TCP packet");
|
throw std::runtime_error("unsupported flag in TCP packet");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fi.tcp->flags & TCPHeader::Flag::SYN) {
|
if (fi.tcp->flags & TCPHeader::Flag::SYN) {
|
||||||
// We never make connections back to the client, so we should never receive a SYN+ACK. Essentially, no other flags
|
// We never make connections back to the client, so we should never receive a SYN+ACK. Essentially, no other flags
|
||||||
// should be set in any received SYN.
|
// should be set in any received SYN.
|
||||||
if ((fi.tcp->flags & 0x0FFF) != TCPHeader::Flag::SYN) {
|
if ((fi.tcp->flags & 0x0FFF) != TCPHeader::Flag::SYN) {
|
||||||
throw runtime_error("TCP SYN contains extra flags");
|
throw std::runtime_error("TCP SYN contains extra flags");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringReader options_r(fi.tcp + 1, fi.tcp_options_size);
|
phosg::StringReader options_r(fi.tcp + 1, fi.tcp_options_size);
|
||||||
@@ -1015,19 +1015,19 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
break;
|
break;
|
||||||
case 2: // Max segment size
|
case 2: // Max segment size
|
||||||
if (option_size != 4) {
|
if (option_size != 4) {
|
||||||
throw runtime_error("incorrect size for TCP max frame size option");
|
throw std::runtime_error("incorrect size for TCP max frame size option");
|
||||||
}
|
}
|
||||||
max_frame_size = options_r.get_u16b();
|
max_frame_size = options_r.get_u16b();
|
||||||
break;
|
break;
|
||||||
case 3: // Window scale (ignored)
|
case 3: // Window scale (ignored)
|
||||||
if (option_size != 3) {
|
if (option_size != 3) {
|
||||||
throw runtime_error("incorrect size for TCP window scale option");
|
throw std::runtime_error("incorrect size for TCP window scale option");
|
||||||
}
|
}
|
||||||
options_r.skip(option_size);
|
options_r.skip(option_size);
|
||||||
break;
|
break;
|
||||||
case 4: // Selective ACK supported (ignored)
|
case 4: // Selective ACK supported (ignored)
|
||||||
if (option_size != 2) {
|
if (option_size != 2) {
|
||||||
throw runtime_error("incorrect size for TCP selective ACK supported option");
|
throw std::runtime_error("incorrect size for TCP selective ACK supported option");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 5: // Selective ACK (ignored)
|
case 5: // Selective ACK (ignored)
|
||||||
@@ -1035,21 +1035,21 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
break;
|
break;
|
||||||
case 8: // Timestamps (ignored)
|
case 8: // Timestamps (ignored)
|
||||||
if (option_size != 10) {
|
if (option_size != 10) {
|
||||||
throw runtime_error("incorrect size for TCP timestamps option");
|
throw std::runtime_error("incorrect size for TCP timestamps option");
|
||||||
}
|
}
|
||||||
options_r.skip(8);
|
options_r.skip(8);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid TCP option");
|
throw std::runtime_error("invalid TCP option");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<IPSSClient::TCPConnection> conn;
|
std::shared_ptr<IPSSClient::TCPConnection> conn;
|
||||||
string conn_str;
|
std::string conn_str;
|
||||||
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
|
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
|
||||||
auto conn_it = c->tcp_connections.find(key);
|
auto conn_it = c->tcp_connections.find(key);
|
||||||
if (conn_it == c->tcp_connections.end()) {
|
if (conn_it == c->tcp_connections.end()) {
|
||||||
conn = make_shared<IPSSClient::TCPConnection>(c);
|
conn = std::make_shared<IPSSClient::TCPConnection>(c);
|
||||||
c->tcp_connections.emplace(key, conn);
|
c->tcp_connections.emplace(key, conn);
|
||||||
conn->server_addr = fi.ipv4->dest_addr;
|
conn->server_addr = fi.ipv4->dest_addr;
|
||||||
conn->server_port = fi.tcp->dest_port;
|
conn->server_port = fi.tcp->dest_port;
|
||||||
@@ -1070,7 +1070,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
|
|
||||||
// Connection is NOT new; this is probably a resend of an earlier SYN
|
// Connection is NOT new; this is probably a resend of an earlier SYN
|
||||||
if (!conn->awaiting_first_ack) {
|
if (!conn->awaiting_first_ack) {
|
||||||
throw logic_error("SYN received on already-open connection after initial phase");
|
throw std::logic_error("SYN received on already-open connection after initial phase");
|
||||||
}
|
}
|
||||||
// TODO: We should check the syn/ack numbers here instead of just assuming they're correct
|
// TODO: We should check the syn/ack numbers here instead of just assuming they're correct
|
||||||
conn_str = this->str_for_tcp_connection(c, conn);
|
conn_str = this->str_for_tcp_connection(c, conn);
|
||||||
@@ -1100,7 +1100,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
this->log.debug_f("Client sent ACK {:08X}", fi.tcp->ack_num);
|
this->log.debug_f("Client sent ACK {:08X}", fi.tcp->ack_num);
|
||||||
if (conn->awaiting_first_ack) {
|
if (conn->awaiting_first_ack) {
|
||||||
if (fi.tcp->ack_num != conn->acked_server_seq + 1) {
|
if (fi.tcp->ack_num != conn->acked_server_seq + 1) {
|
||||||
throw runtime_error("first ack_num was not acked_server_seq + 1");
|
throw std::runtime_error("first ack_num was not acked_server_seq + 1");
|
||||||
}
|
}
|
||||||
conn->acked_server_seq++;
|
conn->acked_server_seq++;
|
||||||
conn->awaiting_first_ack = false;
|
conn->awaiting_first_ack = false;
|
||||||
@@ -1111,7 +1111,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
this->log.debug_f("Advancing acked_server_seq from {:08X}", conn->acked_server_seq);
|
this->log.debug_f("Advancing acked_server_seq from {:08X}", conn->acked_server_seq);
|
||||||
uint32_t ack_delta = fi.tcp->ack_num - conn->acked_server_seq;
|
uint32_t ack_delta = fi.tcp->ack_num - conn->acked_server_seq;
|
||||||
if (conn->outbound_data_bytes < ack_delta) {
|
if (conn->outbound_data_bytes < ack_delta) {
|
||||||
throw runtime_error("client acknowledged beyond end of sent data");
|
throw std::runtime_error("client acknowledged beyond end of sent data");
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->drain_outbound_data(ack_delta);
|
conn->drain_outbound_data(ack_delta);
|
||||||
@@ -1125,7 +1125,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
ack_delta, conn->acked_server_seq);
|
ack_delta, conn->acked_server_seq);
|
||||||
|
|
||||||
} else if (seq_num_less(fi.tcp->ack_num, conn->acked_server_seq)) {
|
} else if (seq_num_less(fi.tcp->ack_num, conn->acked_server_seq)) {
|
||||||
throw runtime_error("client sent lower ack num than previous frame");
|
throw std::runtime_error("client sent lower ack num than previous frame");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,10 +1137,10 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
if (fi.tcp->flags & (TCPHeader::Flag::RST | TCPHeader::Flag::FIN)) {
|
if (fi.tcp->flags & (TCPHeader::Flag::RST | TCPHeader::Flag::FIN)) {
|
||||||
bool is_rst = (fi.tcp->flags & TCPHeader::Flag::RST);
|
bool is_rst = (fi.tcp->flags & TCPHeader::Flag::RST);
|
||||||
if (is_rst && (fi.tcp->flags & TCPHeader::Flag::FIN)) {
|
if (is_rst && (fi.tcp->flags & TCPHeader::Flag::FIN)) {
|
||||||
throw runtime_error("client sent TCP FIN+RST");
|
throw std::runtime_error("client sent TCP FIN+RST");
|
||||||
}
|
}
|
||||||
|
|
||||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
std::string conn_str = this->str_for_tcp_connection(c, conn);
|
||||||
this->log.info_f("Client closed TCP connection {}", conn_str);
|
this->log.info_f("Client closed TCP connection {}", conn_str);
|
||||||
if (conn->server_channel) {
|
if (conn->server_channel) {
|
||||||
conn->server_channel->disconnect();
|
conn->server_channel->disconnect();
|
||||||
@@ -1162,7 +1162,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
// newserv can handle incomplete commands, so we just ignore the PSH flag and forward any data to the server
|
// newserv can handle incomplete commands, so we just ignore the PSH flag and forward any data to the server
|
||||||
// immediately (hence the lack of a flag check in the above condition).
|
// immediately (hence the lack of a flag check in the above condition).
|
||||||
|
|
||||||
string conn_str = this->log.should_log(phosg::LogLevel::L_WARNING)
|
std::string conn_str = this->log.should_log(phosg::LogLevel::L_WARNING)
|
||||||
? this->str_for_tcp_connection(c, conn)
|
? this->str_for_tcp_connection(c, conn)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
@@ -1190,7 +1190,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (payload_skip_bytes > fi.payload_size) {
|
if (payload_skip_bytes > fi.payload_size) {
|
||||||
throw logic_error("payload skip bytes too large");
|
throw std::logic_error("payload skip bytes too large");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload_skip_bytes < fi.payload_size) {
|
if (payload_skip_bytes < fi.payload_size) {
|
||||||
@@ -1239,9 +1239,9 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPStackSimulator::schedule_send_pending_push_frame(shared_ptr<IPSSClient::TCPConnection> conn, uint64_t delay_usecs) {
|
void IPStackSimulator::schedule_send_pending_push_frame(std::shared_ptr<IPSSClient::TCPConnection> conn, uint64_t delay_usecs) {
|
||||||
conn->resend_push_timer.expires_after(std::chrono::microseconds(delay_usecs));
|
conn->resend_push_timer.expires_after(std::chrono::microseconds(delay_usecs));
|
||||||
conn->resend_push_timer.async_wait([wconn = weak_ptr<IPSSClient::TCPConnection>(conn)](std::error_code ec) {
|
conn->resend_push_timer.async_wait([wconn = std::weak_ptr<IPSSClient::TCPConnection>(conn)](std::error_code ec) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1262,7 +1262,7 @@ void IPStackSimulator::schedule_send_pending_push_frame(shared_ptr<IPSSClient::T
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||||
if (!conn->outbound_data_bytes) {
|
if (!conn->outbound_data_bytes) {
|
||||||
if (!conn->server_channel || !conn->server_channel->connected()) {
|
if (!conn->server_channel || !conn->server_channel->connected()) {
|
||||||
co_await this->close_tcp_connection(c, conn);
|
co_await this->close_tcp_connection(c, conn);
|
||||||
@@ -1270,11 +1270,11 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
|||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t bytes_to_send = min<size_t>(conn->outbound_data_bytes, conn->next_push_max_frame_size);
|
size_t bytes_to_send = std::min<size_t>(conn->outbound_data_bytes, conn->next_push_max_frame_size);
|
||||||
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
||||||
// There is a bug in Dolphin's modem implementation (which I wrote, so it's my fault) that causes commands to be
|
// There is a bug in Dolphin's modem implementation (which I wrote, so it's my fault) that causes commands to be
|
||||||
// dropped when too much data is sent. To work around this, we only send up to 200 bytes in each push frame.
|
// dropped when too much data is sent. To work around this, we only send up to 200 bytes in each push frame.
|
||||||
bytes_to_send = min<size_t>(bytes_to_send, 200);
|
bytes_to_send = std::min<size_t>(bytes_to_send, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->log.debug_f("Sending PSH frame with seq_num {:08X}, 0x{:X}/0x{:X} data bytes",
|
this->log.debug_f("Sending PSH frame with seq_num {:08X}, 0x{:X}/0x{:X} data bytes",
|
||||||
@@ -1284,7 +1284,7 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
|||||||
if (conn->outbound_data.empty() || conn->outbound_data.front().size() < bytes_to_send) {
|
if (conn->outbound_data.empty() || conn->outbound_data.front().size() < bytes_to_send) {
|
||||||
// This should never happen because bytes_to_send should always be less than or equal to conn->outbound_data_bytes,
|
// This should never happen because bytes_to_send should always be less than or equal to conn->outbound_data_bytes,
|
||||||
// which itself should be equal to the number of bytes that can be linearized
|
// which itself should be equal to the number of bytes that can be linearized
|
||||||
throw logic_error("failed to linearize enough bytes before sending TCP PSH");
|
throw std::logic_error("failed to linearize enough bytes before sending TCP PSH");
|
||||||
}
|
}
|
||||||
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn->outbound_data.front().data(), bytes_to_send);
|
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn->outbound_data.front().data(), bytes_to_send);
|
||||||
conn->awaiting_ack = true;
|
conn->awaiting_ack = true;
|
||||||
@@ -1300,17 +1300,17 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
|||||||
if (conn->resend_push_usecs > 5000000) {
|
if (conn->resend_push_usecs > 5000000) {
|
||||||
conn->resend_push_usecs = 5000000;
|
conn->resend_push_usecs = 5000000;
|
||||||
}
|
}
|
||||||
conn->next_push_max_frame_size = max<size_t>(0x100, conn->next_push_max_frame_size - 0x100);
|
conn->next_push_max_frame_size = std::max<size_t>(0x100, conn->next_push_max_frame_size - 0x100);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::send_tcp_frame(
|
asio::awaitable<void> IPStackSimulator::send_tcp_frame(
|
||||||
shared_ptr<IPSSClient> c,
|
std::shared_ptr<IPSSClient> c,
|
||||||
shared_ptr<IPSSClient::TCPConnection> conn,
|
std::shared_ptr<IPSSClient::TCPConnection> conn,
|
||||||
uint16_t flags,
|
uint16_t flags,
|
||||||
const void* payload_data,
|
const void* payload_data,
|
||||||
size_t payload_size) {
|
size_t payload_size) {
|
||||||
if (!payload_data != !(flags & TCPHeader::Flag::PSH)) {
|
if (!payload_data != !(flags & TCPHeader::Flag::PSH)) {
|
||||||
throw logic_error("data should be given if and only if PSH is given");
|
throw std::logic_error("data should be given if and only if PSH is given");
|
||||||
}
|
}
|
||||||
|
|
||||||
IPv4Header ipv4;
|
IPv4Header ipv4;
|
||||||
@@ -1350,12 +1350,12 @@ asio::awaitable<void> IPStackSimulator::send_tcp_frame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::open_server_connection(
|
asio::awaitable<void> IPStackSimulator::open_server_connection(
|
||||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||||
if (conn->server_channel) {
|
if (conn->server_channel) {
|
||||||
throw logic_error("server connection is already open");
|
throw std::logic_error("server connection is already open");
|
||||||
}
|
}
|
||||||
|
|
||||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
std::string conn_str = this->str_for_tcp_connection(c, conn);
|
||||||
|
|
||||||
// Figure out which logical port the connection should go to
|
// Figure out which logical port the connection should go to
|
||||||
uint16_t effective_server_port = conn->server_port;
|
uint16_t effective_server_port = conn->server_port;
|
||||||
@@ -1381,7 +1381,7 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
|
|||||||
}
|
}
|
||||||
const auto& port_config = port_config_it->second;
|
const auto& port_config = port_config_it->second;
|
||||||
|
|
||||||
conn->server_channel = make_shared<IPSSChannel>(
|
conn->server_channel = std::make_shared<IPSSChannel>(
|
||||||
this->shared_from_this(),
|
this->shared_from_this(),
|
||||||
c,
|
c,
|
||||||
conn,
|
conn,
|
||||||
@@ -1404,13 +1404,13 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::close_tcp_connection(
|
asio::awaitable<void> IPStackSimulator::close_tcp_connection(
|
||||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||||
// Send an RST to the client. This is kind of rude (we really should use FIN) but the PSO network stack always sends
|
// Send an RST to the client. This is kind of rude (we really should use FIN) but the PSO network stack always sends
|
||||||
// an RST to us when disconnecting, so whatever
|
// an RST to us when disconnecting, so whatever
|
||||||
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::RST);
|
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::RST);
|
||||||
|
|
||||||
// Delete the connection object
|
// Delete the connection object
|
||||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
std::string conn_str = this->str_for_tcp_connection(c, conn);
|
||||||
this->log.info_f("Server closed TCP connection {}", conn_str);
|
this->log.info_f("Server closed TCP connection {}", conn_str);
|
||||||
c->tcp_connections.erase(this->tcp_conn_key_for_connection(conn));
|
c->tcp_connections.erase(this->tcp_conn_key_for_connection(conn));
|
||||||
}
|
}
|
||||||
@@ -1427,14 +1427,14 @@ std::shared_ptr<IPSSClient> IPStackSimulator::create_client(
|
|||||||
|
|
||||||
uint64_t network_id = this->next_network_id++;
|
uint64_t network_id = this->next_network_id++;
|
||||||
this->log.info_f("Virtual network N-{:X} connected via {}", network_id, listen_sock->name);
|
this->log.info_f("Virtual network N-{:X} connected via {}", network_id, listen_sock->name);
|
||||||
return make_shared<IPSSClient>(this->shared_from_this(), network_id, listen_sock->protocol, std::move(client_sock));
|
return std::make_shared<IPSSClient>(this->shared_from_this(), network_id, listen_sock->protocol, std::move(client_sock));
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> IPStackSimulator::handle_tapserver_client(std::shared_ptr<IPSSClient> c) {
|
asio::awaitable<void> IPStackSimulator::handle_tapserver_client(std::shared_ptr<IPSSClient> c) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
le_uint16_t frame_size;
|
le_uint16_t frame_size;
|
||||||
co_await asio::async_read(c->sock, asio::buffer(&frame_size, sizeof(frame_size)), asio::use_awaitable);
|
co_await asio::async_read(c->sock, asio::buffer(&frame_size, sizeof(frame_size)), asio::use_awaitable);
|
||||||
string frame(frame_size, '\0');
|
std::string frame(frame_size, '\0');
|
||||||
co_await asio::async_read(c->sock, asio::buffer(frame.data(), frame.size()), asio::use_awaitable);
|
co_await asio::async_read(c->sock, asio::buffer(frame.data(), frame.size()), asio::use_awaitable);
|
||||||
|
|
||||||
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
||||||
@@ -1443,7 +1443,7 @@ asio::awaitable<void> IPStackSimulator::handle_tapserver_client(std::shared_ptr<
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
co_await this->on_client_frame(c, frame.data(), frame.size());
|
co_await this->on_client_frame(c, frame.data(), frame.size());
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
if (this->log.warning_f("Failed to process frame: {}", e.what())) {
|
if (this->log.warning_f("Failed to process frame: {}", e.what())) {
|
||||||
phosg::print_data(stderr, frame);
|
phosg::print_data(stderr, frame);
|
||||||
}
|
}
|
||||||
@@ -1469,10 +1469,10 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
|
|||||||
size_t frame_start_offset = 0;
|
size_t frame_start_offset = 0;
|
||||||
while (buffer.size() > frame_start_offset) {
|
while (buffer.size() > frame_start_offset) {
|
||||||
if (buffer[frame_start_offset] != 0x7E) {
|
if (buffer[frame_start_offset] != 0x7E) {
|
||||||
throw runtime_error("HDLC frame does not begin with 7E");
|
throw std::runtime_error("HDLC frame does not begin with 7E");
|
||||||
}
|
}
|
||||||
size_t frame_end_offset = buffer.find(0x7E, frame_start_offset + 1);
|
size_t frame_end_offset = buffer.find(0x7E, frame_start_offset + 1);
|
||||||
if (frame_end_offset == string::npos) {
|
if (frame_end_offset == std::string::npos) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
frame_end_offset++;
|
frame_end_offset++;
|
||||||
@@ -1483,7 +1483,7 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
co_await this->on_client_frame(c, frame_data, unescaped_size);
|
co_await this->on_client_frame(c, frame_data, unescaped_size);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
if (this->log.warning_f("Failed to process frame: {}", e.what())) {
|
if (this->log.warning_f("Failed to process frame: {}", e.what())) {
|
||||||
phosg::print_data(stderr, frame_data, unescaped_size);
|
phosg::print_data(stderr, frame_data, unescaped_size);
|
||||||
}
|
}
|
||||||
@@ -1494,7 +1494,7 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
|
|||||||
|
|
||||||
// Delete the processed packets from the beginning of the buffer
|
// Delete the processed packets from the beginning of the buffer
|
||||||
if (frame_start_offset > buffer_bytes) {
|
if (frame_start_offset > buffer_bytes) {
|
||||||
throw logic_error("frame start offset is beyond buffer bounds");
|
throw std::logic_error("frame start offset is beyond buffer bounds");
|
||||||
} else if (frame_start_offset == buffer_bytes) {
|
} else if (frame_start_offset == buffer_bytes) {
|
||||||
buffer_bytes = 0;
|
buffer_bytes = 0;
|
||||||
} else if (frame_start_offset > 0) {
|
} else if (frame_start_offset > 0) {
|
||||||
|
|||||||
+4
-6
@@ -1,7 +1,5 @@
|
|||||||
#include "IPV4RangeSet.hh"
|
#include "IPV4RangeSet.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
||||||
for (const auto& it : json.as_list()) {
|
for (const auto& it : json.as_list()) {
|
||||||
// String should be of the form a.b.c.d or a.b.c.d/e
|
// String should be of the form a.b.c.d or a.b.c.d/e
|
||||||
@@ -13,22 +11,22 @@ IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
|||||||
} else if (tokens.size() == 2) {
|
} else if (tokens.size() == 2) {
|
||||||
mask_bits = stoul(tokens[1], nullptr, 10);
|
mask_bits = stoul(tokens[1], nullptr, 10);
|
||||||
if (mask_bits > 32) {
|
if (mask_bits > 32) {
|
||||||
throw runtime_error("invalid IPv4 address range");
|
throw std::runtime_error("invalid IPv4 address range");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid IPv4 address range");
|
throw std::runtime_error("invalid IPv4 address range");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto addr_tokens = phosg::split(tokens[0], '.');
|
auto addr_tokens = phosg::split(tokens[0], '.');
|
||||||
if (addr_tokens.size() != 4) {
|
if (addr_tokens.size() != 4) {
|
||||||
throw runtime_error("invalid IPv4 address");
|
throw std::runtime_error("invalid IPv4 address");
|
||||||
}
|
}
|
||||||
uint32_t addr = 0;
|
uint32_t addr = 0;
|
||||||
for (size_t z = 0; z < 4; z++) {
|
for (size_t z = 0; z < 4; z++) {
|
||||||
size_t end_pos = 0;
|
size_t end_pos = 0;
|
||||||
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
|
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
|
||||||
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
|
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
|
||||||
throw runtime_error("invalid IPv4 address");
|
throw std::runtime_error("invalid IPv4 address");
|
||||||
}
|
}
|
||||||
addr = (addr << 8) | new_byte;
|
addr = (addr << 8) | new_byte;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-11
@@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
struct GVMFileEntry {
|
struct GVMFileEntry {
|
||||||
be_uint16_t file_num;
|
be_uint16_t file_num;
|
||||||
pstring<TextEncoding::ASCII, 0x1C> name;
|
pstring<TextEncoding::ASCII, 0x1C> name;
|
||||||
@@ -35,21 +33,25 @@ struct GVRHeader {
|
|||||||
be_uint16_t height;
|
be_uint16_t height;
|
||||||
} __packed_ws__(GVRHeader, 0x10);
|
} __packed_ws__(GVRHeader, 0x10);
|
||||||
|
|
||||||
string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, const string& internal_name, uint32_t global_index) {
|
std::string encode_gvm(
|
||||||
|
const phosg::ImageRGBA8888N& img,
|
||||||
|
GVRDataFormat data_format,
|
||||||
|
const std::string& internal_name,
|
||||||
|
uint32_t global_index) {
|
||||||
int8_t dimensions_field = -2;
|
int8_t dimensions_field = -2;
|
||||||
{
|
{
|
||||||
size_t h = img.get_height();
|
size_t h = img.get_height();
|
||||||
size_t w = img.get_width();
|
size_t w = img.get_width();
|
||||||
if ((h != w) || (w & (w - 1)) || (h & (h - 1))) {
|
if ((h != w) || (w & (w - 1)) || (h & (h - 1))) {
|
||||||
throw runtime_error("image must be square and dimensions must be powers of 2");
|
throw std::runtime_error("image must be square and dimensions must be powers of 2");
|
||||||
}
|
}
|
||||||
for (w >>= 1; w; w >>= 1, dimensions_field++) {
|
for (w >>= 1; w; w >>= 1, dimensions_field++) {
|
||||||
}
|
}
|
||||||
if (dimensions_field < 1) {
|
if (dimensions_field < 1) {
|
||||||
throw runtime_error("image is too small");
|
throw std::runtime_error("image is too small");
|
||||||
}
|
}
|
||||||
if (dimensions_field > 0xF) {
|
if (dimensions_field > 0xF) {
|
||||||
throw runtime_error("image is too large");
|
throw std::runtime_error("image is too large");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c
|
|||||||
pixel_bytes = pixel_count * 2;
|
pixel_bytes = pixel_count * 2;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("cannot encode pixel format");
|
throw std::invalid_argument("cannot encode pixel format");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
@@ -102,7 +104,7 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c
|
|||||||
w.put_u32b(phosg::argb8888_for_rgba8888(c));
|
w.put_u32b(phosg::argb8888_for_rgba8888(c));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("cannot encode pixel format");
|
throw std::logic_error("cannot encode pixel format");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,9 +114,9 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c
|
|||||||
return std::move(w.str());
|
return std::move(w.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<uint32_t, 4> fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF};
|
static const std::array<uint32_t, 4> fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF};
|
||||||
|
|
||||||
phosg::ImageRGB888 decode_fon(const string& data, size_t width) {
|
phosg::ImageRGB888 decode_fon(const std::string& data, size_t width) {
|
||||||
size_t num_pixels = data.size() * 4;
|
size_t num_pixels = data.size() * 4;
|
||||||
size_t height = num_pixels / width;
|
size_t height = num_pixels / width;
|
||||||
phosg::ImageRGB888 ret(width, height);
|
phosg::ImageRGB888 ret(width, height);
|
||||||
@@ -132,7 +134,7 @@ constexpr size_t uabs(size_t a, size_t b) {
|
|||||||
return (a > b) ? (a - b) : (b - a);
|
return (a > b) ? (a - b) : (b - a);
|
||||||
}
|
}
|
||||||
|
|
||||||
string encode_fon(const phosg::ImageRGB888& img) {
|
std::string encode_fon(const phosg::ImageRGB888& img) {
|
||||||
phosg::BitWriter w;
|
phosg::BitWriter w;
|
||||||
for (size_t y = 0; y < img.get_height(); y++) {
|
for (size_t y = 0; y < img.get_height(); y++) {
|
||||||
for (size_t x = 0; x < img.get_width(); x++) {
|
for (size_t x = 0; x < img.get_width(); x++) {
|
||||||
|
|||||||
+57
-63
@@ -19,22 +19,17 @@
|
|||||||
#include "SaveFileFormats.hh"
|
#include "SaveFileFormats.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
IntegralExpression::IntegralExpression(const std::string& text) : root(this->parse_expr(text)) {}
|
||||||
|
|
||||||
IntegralExpression::IntegralExpression(const string& text)
|
|
||||||
: root(this->parse_expr(text)) {}
|
|
||||||
|
|
||||||
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
|
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
|
||||||
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right)
|
||||||
: type(type),
|
: type(type), left(std::move(left)), right(std::move(right)) {}
|
||||||
left(std::move(left)),
|
|
||||||
right(std::move(right)) {}
|
|
||||||
|
|
||||||
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
||||||
try {
|
try {
|
||||||
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
|
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
|
||||||
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
|
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
|
||||||
} catch (const bad_cast&) {
|
} catch (const std::bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,11 +73,11 @@ int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
|
|||||||
case Type::MODULUS:
|
case Type::MODULUS:
|
||||||
return this->left->evaluate(env) % this->right->evaluate(env);
|
return this->left->evaluate(env) % this->right->evaluate(env);
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid binary operator type");
|
throw std::logic_error("invalid binary operator type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::BinaryOperatorNode::str() const {
|
std::string IntegralExpression::BinaryOperatorNode::str() const {
|
||||||
switch (this->type) {
|
switch (this->type) {
|
||||||
case Type::LOGICAL_OR:
|
case Type::LOGICAL_OR:
|
||||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||||
@@ -121,19 +116,18 @@ string IntegralExpression::BinaryOperatorNode::str() const {
|
|||||||
case Type::MODULUS:
|
case Type::MODULUS:
|
||||||
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
|
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid binary operator type");
|
throw std::logic_error("invalid binary operator type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
|
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub)
|
||||||
: type(type),
|
: type(type), sub(std::move(sub)) {}
|
||||||
sub(std::move(sub)) {}
|
|
||||||
|
|
||||||
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
|
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
|
||||||
try {
|
try {
|
||||||
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
||||||
return other_un.type == this->type && *other_un.sub == *this->sub;
|
return other_un.type == this->type && *other_un.sub == *this->sub;
|
||||||
} catch (const bad_cast&) {
|
} catch (const std::bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,11 +141,11 @@ int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
|
|||||||
case Type::NEGATIVE:
|
case Type::NEGATIVE:
|
||||||
return -this->sub->evaluate(env);
|
return -this->sub->evaluate(env);
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid unary operator type");
|
throw std::logic_error("invalid unary operator type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::UnaryOperatorNode::str() const {
|
std::string IntegralExpression::UnaryOperatorNode::str() const {
|
||||||
switch (this->type) {
|
switch (this->type) {
|
||||||
case Type::LOGICAL_NOT:
|
case Type::LOGICAL_NOT:
|
||||||
return "!(" + this->sub->str() + ")";
|
return "!(" + this->sub->str() + ")";
|
||||||
@@ -160,7 +154,7 @@ string IntegralExpression::UnaryOperatorNode::str() const {
|
|||||||
case Type::NEGATIVE:
|
case Type::NEGATIVE:
|
||||||
return "-(" + this->sub->str() + ")";
|
return "-(" + this->sub->str() + ")";
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid unary operator type");
|
throw std::logic_error("invalid unary operator type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,19 +164,19 @@ bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
|
|||||||
try {
|
try {
|
||||||
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
|
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
|
||||||
return other_flag.flag_index == this->flag_index;
|
return other_flag.flag_index == this->flag_index;
|
||||||
} catch (const bad_cast&) {
|
} catch (const std::bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||||
if (!env.flags) {
|
if (!env.flags) {
|
||||||
throw runtime_error("quest flags not available");
|
throw std::runtime_error("quest flags not available");
|
||||||
}
|
}
|
||||||
return env.flags->get(this->flag_index) ? 1 : 0;
|
return env.flags->get(this->flag_index) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::FlagLookupNode::str() const {
|
std::string IntegralExpression::FlagLookupNode::str() const {
|
||||||
return std::format("F_{:04X}", this->flag_index);
|
return std::format("F_{:04X}", this->flag_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,14 +187,14 @@ bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& o
|
|||||||
try {
|
try {
|
||||||
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
|
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
|
||||||
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
|
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
|
||||||
} catch (const bad_cast&) {
|
} catch (const std::bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
|
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
|
||||||
if (!env.challenge_records) {
|
if (!env.challenge_records) {
|
||||||
throw runtime_error("challenge records not available");
|
throw std::runtime_error("challenge records not available");
|
||||||
}
|
}
|
||||||
if (this->episode == Episode::EP1) {
|
if (this->episode == Episode::EP1) {
|
||||||
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
|
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
|
||||||
@@ -210,17 +204,17 @@ int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& e
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
std::string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||||
return std::format("CC_{}_{}", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
return std::format("CC_{}_{}", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name) : reward_name(reward_name) {}
|
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const std::string& reward_name) : reward_name(reward_name) {}
|
||||||
|
|
||||||
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
||||||
try {
|
try {
|
||||||
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
|
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
|
||||||
return other_team_reward.reward_name == this->reward_name;
|
return other_team_reward.reward_name == this->reward_name;
|
||||||
} catch (const bad_cast&) {
|
} catch (const std::bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,7 +223,7 @@ int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const
|
|||||||
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::TeamRewardLookupNode::str() const {
|
std::string IntegralExpression::TeamRewardLookupNode::str() const {
|
||||||
return "T_" + this->reward_name;
|
return "T_" + this->reward_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +237,7 @@ int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const
|
|||||||
return env.num_players;
|
return env.num_players;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::NumPlayersLookupNode::str() const {
|
std::string IntegralExpression::NumPlayersLookupNode::str() const {
|
||||||
return "V_NumPlayers";
|
return "V_NumPlayers";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +251,7 @@ int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
|
|||||||
return env.event;
|
return env.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::EventLookupNode::str() const {
|
std::string IntegralExpression::EventLookupNode::str() const {
|
||||||
return "V_Event";
|
return "V_Event";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +265,7 @@ int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const
|
|||||||
return env.v1_present ? 1 : 0;
|
return env.v1_present ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::V1PresenceLookupNode::str() const {
|
std::string IntegralExpression::V1PresenceLookupNode::str() const {
|
||||||
return "V_V1Present";
|
return "V_V1Present";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +276,7 @@ bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
|
|||||||
try {
|
try {
|
||||||
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
|
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
|
||||||
return other_const.value == this->value;
|
return other_const.value == this->value;
|
||||||
} catch (const bad_cast&) {
|
} catch (const std::bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,11 +285,11 @@ int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
|
|||||||
return this->value;
|
return this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
string IntegralExpression::ConstantNode::str() const {
|
std::string IntegralExpression::ConstantNode::str() const {
|
||||||
return std::format("{}", this->value);
|
return std::format("{}", this->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
|
std::unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(std::string_view text) {
|
||||||
// Strip off spaces and fully-enclosing parentheses
|
// Strip off spaces and fully-enclosing parentheses
|
||||||
for (;;) {
|
for (;;) {
|
||||||
size_t starting_size = text.size();
|
size_t starting_size = text.size();
|
||||||
@@ -329,22 +323,22 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (text.empty()) {
|
if (text.empty()) {
|
||||||
throw runtime_error("invalid expression");
|
throw std::runtime_error("invalid expression");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for binary operators at the root level
|
// Check for binary operators at the root level
|
||||||
using BinType = BinaryOperatorNode::Type;
|
using BinType = BinaryOperatorNode::Type;
|
||||||
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
static const std::vector<std::vector<std::pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||||
{{make_pair("||", BinType::LOGICAL_OR)}},
|
{{std::make_pair("||", BinType::LOGICAL_OR)}},
|
||||||
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
{{std::make_pair("&&", BinType::LOGICAL_AND)}},
|
||||||
{{make_pair("|", BinType::BITWISE_OR)}},
|
{{std::make_pair("|", BinType::BITWISE_OR)}},
|
||||||
{{make_pair("^", BinType::BITWISE_XOR)}},
|
{{std::make_pair("^", BinType::BITWISE_XOR)}},
|
||||||
{{make_pair("&", BinType::BITWISE_AND)}},
|
{{std::make_pair("&", BinType::BITWISE_AND)}},
|
||||||
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
{{std::make_pair("==", BinType::EQUAL)}, {std::make_pair("!=", BinType::NOT_EQUAL)}},
|
||||||
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
{{std::make_pair("<=", BinType::LESS_OR_EQUAL)}, {std::make_pair(">=", BinType::GREATER_OR_EQUAL)}, {std::make_pair("<", BinType::LESS_THAN)}, {std::make_pair(">", BinType::GREATER_THAN)}},
|
||||||
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
{{std::make_pair("<<", BinType::LEFT_SHIFT)}, {std::make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||||
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
{{std::make_pair("+", BinType::ADD)}, {std::make_pair("-", BinType::SUBTRACT)}},
|
||||||
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
{{std::make_pair("*", BinType::MULTIPLY)}, {std::make_pair("/", BinType::DIVIDE)}, {std::make_pair("%", BinType::MODULUS)}},
|
||||||
};
|
};
|
||||||
for (const auto& operators : binary_operator_levels) {
|
for (const auto& operators : binary_operator_levels) {
|
||||||
size_t paren_level = 0;
|
size_t paren_level = 0;
|
||||||
@@ -390,12 +384,12 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
|||||||
char* endptr = nullptr;
|
char* endptr = nullptr;
|
||||||
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||||
if (endptr != text.data() + text.size()) {
|
if (endptr != text.data() + text.size()) {
|
||||||
throw runtime_error("invalid flag lookup token");
|
throw std::runtime_error("invalid flag lookup token");
|
||||||
}
|
}
|
||||||
if (flag >= 0x400) {
|
if (flag >= 0x400) {
|
||||||
throw runtime_error("invalid flag index");
|
throw std::runtime_error("invalid flag index");
|
||||||
}
|
}
|
||||||
return make_unique<FlagLookupNode>(flag);
|
return std::make_unique<FlagLookupNode>(flag);
|
||||||
}
|
}
|
||||||
if (text.starts_with("CC_")) {
|
if (text.starts_with("CC_")) {
|
||||||
Episode episode;
|
Episode episode;
|
||||||
@@ -404,45 +398,45 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
|||||||
} else if (text.starts_with("CC_Ep2_")) {
|
} else if (text.starts_with("CC_Ep2_")) {
|
||||||
episode = Episode::EP2;
|
episode = Episode::EP2;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid challenge episode");
|
throw std::runtime_error("invalid challenge episode");
|
||||||
}
|
}
|
||||||
char* endptr = nullptr;
|
char* endptr = nullptr;
|
||||||
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
|
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
|
||||||
if (endptr != text.data() + text.size()) {
|
if (endptr != text.data() + text.size()) {
|
||||||
throw runtime_error("invalid challenge completion lookup token");
|
throw std::runtime_error("invalid challenge completion lookup token");
|
||||||
}
|
}
|
||||||
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
|
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
|
||||||
throw runtime_error("invalid challenge stage index");
|
throw std::runtime_error("invalid challenge stage index");
|
||||||
}
|
}
|
||||||
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
|
return std::make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
|
||||||
}
|
}
|
||||||
if (text.starts_with("T_")) {
|
if (text.starts_with("T_")) {
|
||||||
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
return std::make_unique<TeamRewardLookupNode>(std::string(text.substr(2)));
|
||||||
}
|
}
|
||||||
if (text == "V_NumPlayers") {
|
if (text == "V_NumPlayers") {
|
||||||
return make_unique<NumPlayersLookupNode>();
|
return std::make_unique<NumPlayersLookupNode>();
|
||||||
}
|
}
|
||||||
if (text == "V_Event") {
|
if (text == "V_Event") {
|
||||||
return make_unique<EventLookupNode>();
|
return std::make_unique<EventLookupNode>();
|
||||||
}
|
}
|
||||||
if (text == "V_V1Present") {
|
if (text == "V_V1Present") {
|
||||||
return make_unique<V1PresenceLookupNode>();
|
return std::make_unique<V1PresenceLookupNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for constants
|
// Check for constants
|
||||||
if (text == "true") {
|
if (text == "true") {
|
||||||
return make_unique<ConstantNode>(1);
|
return std::make_unique<ConstantNode>(1);
|
||||||
}
|
}
|
||||||
if (text == "false") {
|
if (text == "false") {
|
||||||
return make_unique<ConstantNode>(0);
|
return std::make_unique<ConstantNode>(0);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
size_t endpos;
|
size_t endpos;
|
||||||
int64_t v = stoll(string(text), &endpos, 0);
|
int64_t v = std::stoll(std::string(text), &endpos, 0);
|
||||||
if (endpos == text.size()) {
|
if (endpos == text.size()) {
|
||||||
return make_unique<ConstantNode>(v);
|
return std::make_unique<ConstantNode>(v);
|
||||||
}
|
}
|
||||||
} catch (const exception&) {
|
} catch (const std::exception&) {
|
||||||
}
|
}
|
||||||
throw runtime_error("unparseable expression");
|
throw std::runtime_error("unparseable expression");
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-67
@@ -6,8 +6,6 @@
|
|||||||
#include "EnemyType.hh"
|
#include "EnemyType.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
// The favored weapon type table is hardcoded in the game client. The table is:
|
// The favored weapon type table is hardcoded in the game client. The table is:
|
||||||
// Viridia shots
|
// Viridia shots
|
||||||
// Greennill rifles
|
// Greennill rifles
|
||||||
@@ -19,23 +17,23 @@ using namespace std;
|
|||||||
// Oran daggers
|
// Oran daggers
|
||||||
// Yellowboze (none)
|
// Yellowboze (none)
|
||||||
// Whitill slicers
|
// Whitill slicers
|
||||||
static const array<uint8_t, 10> favored_weapon_by_section_id = {
|
static const std::array<uint8_t, 10> favored_weapon_by_section_id = {
|
||||||
0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05};
|
0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05};
|
||||||
|
|
||||||
ItemCreator::ItemCreator(
|
ItemCreator::ItemCreator(
|
||||||
shared_ptr<const CommonItemSet> common_item_set,
|
std::shared_ptr<const CommonItemSet> common_item_set,
|
||||||
shared_ptr<const RareItemSet> rare_item_set,
|
std::shared_ptr<const RareItemSet> rare_item_set,
|
||||||
shared_ptr<const ArmorRandomSet> armor_random_set,
|
std::shared_ptr<const ArmorRandomSet> armor_random_set,
|
||||||
shared_ptr<const ToolRandomSet> tool_random_set,
|
std::shared_ptr<const ToolRandomSet> tool_random_set,
|
||||||
shared_ptr<const WeaponRandomSet> weapon_random_set,
|
std::shared_ptr<const WeaponRandomSet> weapon_random_set,
|
||||||
shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
|
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
|
||||||
shared_ptr<const ItemParameterTable> item_parameter_table,
|
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||||
std::shared_ptr<const ItemData::StackLimits> stack_limits,
|
std::shared_ptr<const ItemData::StackLimits> stack_limits,
|
||||||
GameMode mode,
|
GameMode mode,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
uint8_t section_id,
|
uint8_t section_id,
|
||||||
std::shared_ptr<RandomGenerator> rand_crypt,
|
std::shared_ptr<RandomGenerator> rand_crypt,
|
||||||
shared_ptr<const BattleRules> restrictions)
|
std::shared_ptr<const BattleRules> restrictions)
|
||||||
: log(std::format("[ItemCreator:{}/{}/{}/{}] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
: log(std::format("[ItemCreator:{}/{}/{}/{}] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||||
logic_version(stack_limits->version),
|
logic_version(stack_limits->version),
|
||||||
is_legacy_replay(false),
|
is_legacy_replay(false),
|
||||||
@@ -175,7 +173,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
|
|||||||
case 6: // Nothing
|
case 6: // Nothing
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("this should be impossible");
|
throw std::logic_error("this should be impossible");
|
||||||
}
|
}
|
||||||
if (item_class < 6) {
|
if (item_class < 6) {
|
||||||
this->generate_common_item_variances(res.item, area);
|
this->generate_common_item_variances(res.item, area);
|
||||||
@@ -183,7 +181,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.error_f("Exception in item creation: {}", e.what());
|
this->log.error_f("Exception in item creation: {}", e.what());
|
||||||
return DropResult();
|
return DropResult();
|
||||||
}
|
}
|
||||||
@@ -233,13 +231,13 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
|||||||
case 2:
|
case 2:
|
||||||
try {
|
try {
|
||||||
item_class = pt->enemy_type_item_classes.at(enemy_type);
|
item_class = pt->enemy_type_item_classes.at(enemy_type);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
this->log.info_f("Item class is not set for this enemy type");
|
this->log.info_f("Item class is not set for this enemy type");
|
||||||
item_class = 0xFF;
|
item_class = 0xFF;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid item class determinant");
|
throw std::logic_error("invalid item class determinant");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->log.info_f(
|
this->log.info_f(
|
||||||
@@ -265,7 +263,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
|||||||
res.item.data1[0] = 0x04;
|
res.item.data1[0] = 0x04;
|
||||||
try {
|
try {
|
||||||
res.item.data2d = this->choose_meseta_amount(pt->enemy_type_meseta_ranges.at(enemy_type)) & 0xFFFF;
|
res.item.data2d = this->choose_meseta_amount(pt->enemy_type_meseta_ranges.at(enemy_type)) & 0xFFFF;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
this->log.info_f("Meseta range is not set for this enemy type");
|
this->log.info_f("Meseta range is not set for this enemy type");
|
||||||
return DropResult();
|
return DropResult();
|
||||||
}
|
}
|
||||||
@@ -281,7 +279,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
this->log.error_f("Exception in item creation: {}", e.what());
|
this->log.error_f("Exception in item creation: {}", e.what());
|
||||||
return DropResult();
|
return DropResult();
|
||||||
}
|
}
|
||||||
@@ -408,7 +406,7 @@ ItemData ItemCreator::create_rare_item(const ItemData& drop_item, uint8_t area)
|
|||||||
case 4:
|
case 4:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid item class");
|
throw std::logic_error("invalid item class");
|
||||||
}
|
}
|
||||||
this->set_item_kill_count_if_unsealable(item);
|
this->set_item_kill_count_if_unsealable(item);
|
||||||
}
|
}
|
||||||
@@ -424,7 +422,7 @@ void ItemCreator::generate_rare_weapon_bonuses(ItemData& item, Episode episode,
|
|||||||
|
|
||||||
auto pt = this->pt(episode);
|
auto pt = this->pt(episode);
|
||||||
if (!pt->has_rare_bonus_value_prob_table) {
|
if (!pt->has_rare_bonus_value_prob_table) {
|
||||||
throw logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table");
|
throw std::logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t z = 0; z < 6; z += 2) {
|
for (size_t z = 0; z < 6; z += 2) {
|
||||||
@@ -557,7 +555,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
|||||||
item.clear();
|
item.clear();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid weapon and armor mode");
|
throw std::logic_error("invalid weapon and armor mode");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
@@ -588,7 +586,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid tech disk mode");
|
throw std::logic_error("invalid tech disk mode");
|
||||||
}
|
}
|
||||||
} else if ((item.data1[1] == 9) && this->restrictions->forbid_scape_dolls) {
|
} else if ((item.data1[1] == 9) && this->restrictions->forbid_scape_dolls) {
|
||||||
this->log.info_f("Restricted: scape dolls not allowed");
|
this->log.info_f("Restricted: scape dolls not allowed");
|
||||||
@@ -602,7 +600,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid item");
|
throw std::logic_error("invalid item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,7 +640,7 @@ void ItemCreator::generate_common_item_variances(ItemData& item, uint8_t area) {
|
|||||||
// Note: The original code does the following here:
|
// Note: The original code does the following here:
|
||||||
// item.clear();
|
// item.clear();
|
||||||
// item.data1[0] = 0x05;
|
// item.data1[0] = 0x05;
|
||||||
throw logic_error("invalid item class");
|
throw std::logic_error("invalid item class");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->clear_item_if_restricted(item);
|
this->clear_item_if_restricted(item);
|
||||||
@@ -706,7 +704,7 @@ void ItemCreator::generate_common_tool_variances(ItemData& item, uint8_t area) {
|
|||||||
item.data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.data1[1] = data.first;
|
item.data1[1] = data.first;
|
||||||
item.data1[2] = data.second;
|
item.data1[2] = data.second;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
this->log.info_f("Tool class is missing; skipping item generation");
|
this->log.info_f("Tool class is missing; skipping item generation");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -795,7 +793,7 @@ void ItemCreator::generate_common_weapon_variances(ItemData& item, uint8_t area)
|
|||||||
|
|
||||||
void ItemCreator::generate_common_weapon_grind(ItemData& item, uint8_t area, uint8_t offset_within_subtype_range) {
|
void ItemCreator::generate_common_weapon_grind(ItemData& item, uint8_t area, uint8_t offset_within_subtype_range) {
|
||||||
if (item.data1[0] == 0) {
|
if (item.data1[0] == 0) {
|
||||||
uint8_t offset = clamp<uint8_t>(offset_within_subtype_range, 0, 3);
|
uint8_t offset = std::clamp<uint8_t>(offset_within_subtype_range, 0, 3);
|
||||||
item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt(area)->grind_prob_table, offset);
|
item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt(area)->grind_prob_table, offset);
|
||||||
this->log.info_f("Generated grind {:02X} from offset within subtype range {:02X}", item.data1[3], offset_within_subtype_range);
|
this->log.info_f("Generated grind {:02X} from offset within subtype range {:02X}", item.data1[3], offset_within_subtype_range);
|
||||||
}
|
}
|
||||||
@@ -860,7 +858,7 @@ void ItemCreator::generate_unit_stars_tables() {
|
|||||||
case Version::BB_PATCH:
|
case Version::BB_PATCH:
|
||||||
case Version::GC_EP3_NTE:
|
case Version::GC_EP3_NTE:
|
||||||
case Version::GC_EP3:
|
case Version::GC_EP3:
|
||||||
throw logic_error("ItemCreator cannot be created for Episode 3 games");
|
throw std::logic_error("ItemCreator cannot be created for Episode 3 games");
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
star_base_index = 0x124;
|
star_base_index = 0x124;
|
||||||
num_units = 0x43;
|
num_units = 0x43;
|
||||||
@@ -890,7 +888,7 @@ void ItemCreator::generate_unit_stars_tables() {
|
|||||||
num_units = 0x64;
|
num_units = 0x64;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid game version");
|
throw std::logic_error("invalid game version");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& vec : this->unit_results_by_star_count) {
|
for (auto& vec : this->unit_results_by_star_count) {
|
||||||
@@ -944,7 +942,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
|
|||||||
rand_max += tables[x * stride + offset];
|
rand_max += tables[x * stride + offset];
|
||||||
}
|
}
|
||||||
if (rand_max == 0) {
|
if (rand_max == 0) {
|
||||||
throw runtime_error("weighted table is empty");
|
throw std::runtime_error("weighted table is empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t x = this->rand_int(rand_max);
|
uint32_t x = this->rand_int(rand_max);
|
||||||
@@ -955,7 +953,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
|
|||||||
}
|
}
|
||||||
x -= table_value;
|
x -= table_value;
|
||||||
}
|
}
|
||||||
throw logic_error("selector was not less than rand_max");
|
throw std::logic_error("selector was not less than rand_max");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename IntT, size_t X>
|
template <typename IntT, size_t X>
|
||||||
@@ -968,8 +966,8 @@ IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical(const parray<parray<
|
|||||||
return ItemCreator::get_rand_from_weighted_tables<IntT>(tables[0].data(), offset, Y, X);
|
return ItemCreator::get_rand_from_weighted_tables<IntT>(tables[0].data(), offset, Y, X);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
|
std::vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
|
||||||
vector<ItemData> shop;
|
std::vector<ItemData> shop;
|
||||||
this->generate_armor_shop_armors(shop, episode, player_level);
|
this->generate_armor_shop_armors(shop, episode, player_level);
|
||||||
this->generate_armor_shop_shields(shop, player_level);
|
this->generate_armor_shop_shields(shop, player_level);
|
||||||
this->generate_armor_shop_units(shop, player_level);
|
this->generate_armor_shop_units(shop, player_level);
|
||||||
@@ -992,7 +990,7 @@ size_t ItemCreator::get_table_index_for_armor_shop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ItemCreator::shop_does_not_contain_duplicate_armor(
|
bool ItemCreator::shop_does_not_contain_duplicate_armor(
|
||||||
const vector<ItemData>& shop, const ItemData& item) {
|
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||||
for (const auto& shop_item : shop) {
|
for (const auto& shop_item : shop) {
|
||||||
if ((shop_item.data1[0] == item.data1[0]) &&
|
if ((shop_item.data1[0] == item.data1[0]) &&
|
||||||
(shop_item.data1[1] == item.data1[1]) &&
|
(shop_item.data1[1] == item.data1[1]) &&
|
||||||
@@ -1005,7 +1003,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_armor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
|
bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
|
||||||
const vector<ItemData>& shop, const ItemData& item) {
|
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||||
for (const auto& shop_item : shop) {
|
for (const auto& shop_item : shop) {
|
||||||
if ((shop_item.data1[0] == item.data1[0]) &&
|
if ((shop_item.data1[0] == item.data1[0]) &&
|
||||||
(shop_item.data1[1] == item.data1[1]) &&
|
(shop_item.data1[1] == item.data1[1]) &&
|
||||||
@@ -1018,7 +1016,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
|
bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
|
||||||
const vector<ItemData>& shop, const ItemData& item) {
|
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||||
size_t similar_items = 0;
|
size_t similar_items = 0;
|
||||||
for (const auto& shop_item : shop) {
|
for (const auto& shop_item : shop) {
|
||||||
// Disallow exact matches
|
// Disallow exact matches
|
||||||
@@ -1037,7 +1035,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
|
bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
|
||||||
const vector<ItemData>& shop, const ItemData& item) {
|
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||||
for (const auto& shop_item : shop) {
|
for (const auto& shop_item : shop) {
|
||||||
if ((shop_item.data1[0] == item.data1[0]) &&
|
if ((shop_item.data1[0] == item.data1[0]) &&
|
||||||
(shop_item.data1[1] == item.data1[1]) &&
|
(shop_item.data1[1] == item.data1[1]) &&
|
||||||
@@ -1048,7 +1046,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, Episode episode, size_t player_level) {
|
void ItemCreator::generate_armor_shop_armors(std::vector<ItemData>& shop, Episode episode, size_t player_level) {
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
num_items = 4;
|
num_items = 4;
|
||||||
@@ -1092,7 +1090,7 @@ void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, Episode epi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t player_level) {
|
void ItemCreator::generate_armor_shop_shields(std::vector<ItemData>& shop, size_t player_level) {
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
num_items = 4;
|
num_items = 4;
|
||||||
@@ -1135,7 +1133,7 @@ void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t pla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t player_level) {
|
void ItemCreator::generate_armor_shop_units(std::vector<ItemData>& shop, size_t player_level) {
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
return; // num_items = 0
|
return; // num_items = 0
|
||||||
@@ -1169,8 +1167,8 @@ void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t playe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
|
std::vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
|
||||||
vector<ItemData> shop;
|
std::vector<ItemData> shop;
|
||||||
this->generate_common_tool_shop_recovery_items(shop, player_level);
|
this->generate_common_tool_shop_recovery_items(shop, player_level);
|
||||||
this->generate_rare_tool_shop_recovery_items(shop, player_level);
|
this->generate_rare_tool_shop_recovery_items(shop, player_level);
|
||||||
this->generate_tool_shop_tech_disks(shop, player_level);
|
this->generate_tool_shop_tech_disks(shop, player_level);
|
||||||
@@ -1192,11 +1190,11 @@ size_t ItemCreator::get_table_index_for_tool_shop(size_t player_level) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const vector<pair<uint8_t, uint8_t>> tool_item_defs{
|
static const std::vector<std::pair<uint8_t, uint8_t>> tool_item_defs{
|
||||||
{0x00, 0x00}, {0x00, 0x01}, {0x00, 0x02}, {0x01, 0x00}, {0x01, 0x01}, {0x01, 0x02}, {0x06, 0x00}, {0x06, 0x01},
|
{0x00, 0x00}, {0x00, 0x01}, {0x00, 0x02}, {0x01, 0x00}, {0x01, 0x01}, {0x01, 0x02}, {0x06, 0x00}, {0x06, 0x01},
|
||||||
{0x03, 0x00}, {0x04, 0x00}, {0x05, 0x00}, {0x07, 0x00}, {0x08, 0x00}, {0x09, 0x00}, {0x0A, 0x00}, {0xFF, 0xFF}};
|
{0x03, 0x00}, {0x04, 0x00}, {0x05, 0x00}, {0x07, 0x00}, {0x08, 0x00}, {0x09, 0x00}, {0x0A, 0x00}, {0xFF, 0xFF}};
|
||||||
|
|
||||||
void ItemCreator::generate_common_tool_shop_recovery_items(vector<ItemData>& shop, size_t player_level) {
|
void ItemCreator::generate_common_tool_shop_recovery_items(std::vector<ItemData>& shop, size_t player_level) {
|
||||||
size_t table_index;
|
size_t table_index;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
table_index = 0;
|
table_index = 0;
|
||||||
@@ -1226,7 +1224,7 @@ void ItemCreator::generate_common_tool_shop_recovery_items(vector<ItemData>& sho
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_rare_tool_shop_recovery_items(vector<ItemData>& shop, size_t player_level) {
|
void ItemCreator::generate_rare_tool_shop_recovery_items(std::vector<ItemData>& shop, size_t player_level) {
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1264,7 +1262,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(vector<ItemData>& shop,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t player_level) {
|
void ItemCreator::generate_tool_shop_tech_disks(std::vector<ItemData>& shop, size_t player_level) {
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
num_items = 4;
|
num_items = 4;
|
||||||
@@ -1286,7 +1284,7 @@ void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t p
|
|||||||
}
|
}
|
||||||
pt.shuffle(this->rand_crypt);
|
pt.shuffle(this->rand_crypt);
|
||||||
|
|
||||||
static const array<uint8_t, 0x13> tech_num_map = {
|
static const std::array<uint8_t, 0x13> tech_num_map = {
|
||||||
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
|
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
|
||||||
0x0E, 0x11, 0x02, 0x05, 0x08, 0x09, 0x12};
|
0x0E, 0x11, 0x02, 0x05, 0x08, 0x09, 0x12};
|
||||||
|
|
||||||
@@ -1309,7 +1307,7 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
|
|||||||
size_t table_index = this->get_table_index_for_tool_shop(player_level);
|
size_t table_index = this->get_table_index_for_tool_shop(player_level);
|
||||||
auto table = this->tool_random_set->get_tech_disk_level_table(table_index);
|
auto table = this->tool_random_set->get_tech_disk_level_table(table_index);
|
||||||
if (tech_num_index >= table.second) {
|
if (tech_num_index >= table.second) {
|
||||||
throw runtime_error("technique number out of range");
|
throw std::runtime_error("technique number out of range");
|
||||||
}
|
}
|
||||||
const auto& e = table.first[tech_num_index];
|
const auto& e = table.first[tech_num_index];
|
||||||
|
|
||||||
@@ -1318,23 +1316,23 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
|
|||||||
item.data1[2] = 0;
|
item.data1[2] = 0;
|
||||||
break;
|
break;
|
||||||
case ToolRandomSet::TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR:
|
case ToolRandomSet::TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR:
|
||||||
item.data1[2] = clamp<ssize_t>(
|
item.data1[2] = std::clamp<ssize_t>(
|
||||||
(min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
|
(std::min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
|
||||||
break;
|
break;
|
||||||
case ToolRandomSet::TechDiskLevelEntry::Mode::RANDOM_IN_RANGE: {
|
case ToolRandomSet::TechDiskLevelEntry::Mode::RANDOM_IN_RANGE: {
|
||||||
// Note: This logic does not give a uniform distribution - if the minimumlevel is not zero (level 1), then the
|
// Note: This logic does not give a uniform distribution - if the minimumlevel is not zero (level 1), then the
|
||||||
// minimum level is more likely than all the other levels. This behavior matches the client's logic, though it's
|
// minimum level is more likely than all the other levels. This behavior matches the client's logic, though it's
|
||||||
// unclear if this nonuniformity was intentional.
|
// unclear if this nonuniformity was intentional.
|
||||||
int16_t min_level = max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
|
int16_t min_level = std::max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
|
||||||
item.data1[2] = clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
|
item.data1[2] = std::clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid tech disk level mode");
|
throw std::logic_error("invalid tech disk level mode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level) {
|
std::vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level) {
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
num_items = 10;
|
num_items = 10;
|
||||||
@@ -1385,13 +1383,13 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
|||||||
}
|
}
|
||||||
pt.shuffle(this->rand_crypt);
|
pt.shuffle(this->rand_crypt);
|
||||||
|
|
||||||
vector<ItemData> shop;
|
std::vector<ItemData> shop;
|
||||||
while (shop.size() < num_items) {
|
while (shop.size() < num_items) {
|
||||||
ItemData item;
|
ItemData item;
|
||||||
|
|
||||||
uint8_t which = pt.pop();
|
uint8_t which = pt.pop();
|
||||||
if (which == 0x39) {
|
if (which == 0x39) {
|
||||||
static const vector<pair<uint8_t, uint8_t>> defs{
|
static const std::vector<std::pair<uint8_t, uint8_t>> defs{
|
||||||
{0x28, 0x00}, {0x2A, 0x00}, {0x2B, 0x00}, {0x35, 0x00}, {0x52, 0x00}, {0x48, 0x00}, {0x64, 0x00},
|
{0x28, 0x00}, {0x2A, 0x00}, {0x2B, 0x00}, {0x35, 0x00}, {0x52, 0x00}, {0x48, 0x00}, {0x64, 0x00},
|
||||||
{0x59, 0x00}, {0x8A, 0x00}, {0x99, 0x00}};
|
{0x59, 0x00}, {0x8A, 0x00}, {0x99, 0x00}};
|
||||||
const auto& def = defs.at(this->section_id);
|
const auto& def = defs.at(this->section_id);
|
||||||
@@ -1400,7 +1398,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
|||||||
item.data1[2] = def.second;
|
item.data1[2] = def.second;
|
||||||
|
|
||||||
} else if (which == 0x3A) {
|
} else if (which == 0x3A) {
|
||||||
static const vector<pair<uint8_t, uint8_t>> defs{
|
static const std::vector<std::pair<uint8_t, uint8_t>> defs{
|
||||||
{0x99, 0x00}, {0x64, 0x00}, {0x8A, 0x00}, {0x28, 0x00}, {0x59, 0x00}, {0x2B, 0x00}, {0x52, 0x00},
|
{0x99, 0x00}, {0x64, 0x00}, {0x8A, 0x00}, {0x28, 0x00}, {0x59, 0x00}, {0x2B, 0x00}, {0x52, 0x00},
|
||||||
{0x2A, 0x00}, {0x48, 0x00}, {0x35, 0x00}};
|
{0x2A, 0x00}, {0x48, 0x00}, {0x35, 0x00}};
|
||||||
const auto& def = defs.at(this->section_id);
|
const auto& def = defs.at(this->section_id);
|
||||||
@@ -1409,7 +1407,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
|||||||
item.data1[2] = def.second;
|
item.data1[2] = def.second;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
static const vector<pair<uint8_t, uint8_t>> defs({
|
static const std::vector<std::pair<uint8_t, uint8_t>> defs({
|
||||||
/* 00 */ {0x01, 0x00},
|
/* 00 */ {0x01, 0x00},
|
||||||
/* 01 */ {0x01, 0x01},
|
/* 01 */ {0x01, 0x01},
|
||||||
/* 02 */ {0x01, 0x02},
|
/* 02 */ {0x01, 0x02},
|
||||||
@@ -1528,7 +1526,7 @@ void ItemCreator::generate_weapon_shop_item_grind(ItemData& item, size_t player_
|
|||||||
: this->weapon_random_set->get_standard_grind_range(table_index);
|
: this->weapon_random_set->get_standard_grind_range(table_index);
|
||||||
|
|
||||||
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
||||||
item.data1[3] = clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
|
item.data1[3] = std::clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t player_level) {
|
void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t player_level) {
|
||||||
@@ -1574,11 +1572,11 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe
|
|||||||
item.data1[4] = this->choose_weapon_special(1);
|
item.data1[4] = this->choose_weapon_special(1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid special mode");
|
throw std::runtime_error("invalid special mode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<int8_t, 20> bonus_values = {
|
static const std::array<int8_t, 20> bonus_values = {
|
||||||
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
|
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
|
||||||
|
|
||||||
void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player_level) {
|
void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player_level) {
|
||||||
@@ -1619,7 +1617,7 @@ void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player
|
|||||||
item.data1[7] = 0;
|
item.data1[7] = 0;
|
||||||
} else {
|
} else {
|
||||||
const auto* range = this->weapon_random_set->get_bonus_range(0, table_index);
|
const auto* range = this->weapon_random_set->get_bonus_range(0, table_index);
|
||||||
item.data1[7] = bonus_values.at(max<size_t>(this->rand_int(range->max + 1), range->min));
|
item.data1[7] = bonus_values.at(std::max<size_t>(this->rand_int(range->max + 1), range->min));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1663,7 +1661,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
|
|||||||
item.data1[9] = 0;
|
item.data1[9] = 0;
|
||||||
} else {
|
} else {
|
||||||
const auto* range = this->weapon_random_set->get_bonus_range(1, table_index);
|
const auto* range = this->weapon_random_set->get_bonus_range(1, table_index);
|
||||||
item.data1[9] = bonus_values.at(max<size_t>(this->rand_int(range->max + 1), range->min));
|
item.data1[9] = bonus_values.at(std::max<size_t>(this->rand_int(range->max + 1), range->min));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1715,7 +1713,7 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t param4, uint32_t pa
|
|||||||
item.data2d = ((param5 >> 0x10) & 0xFFFF) * 10;
|
item.data2d = ((param5 >> 0x10) & 0xFFFF) * 10;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item class");
|
throw std::runtime_error("invalid item class");
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@@ -1723,10 +1721,10 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t param4, uint32_t pa
|
|||||||
|
|
||||||
ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||||
if (item.data1[0] != 0) {
|
if (item.data1[0] != 0) {
|
||||||
throw runtime_error("tekker deltas can only be applied to weapons");
|
throw std::runtime_error("tekker deltas can only be applied to weapons");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
|
static const std::array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
|
||||||
|
|
||||||
bool favored = item.data1[1] == favored_weapon_by_section_id[section_id];
|
bool favored = item.data1[1] == favored_weapon_by_section_id[section_id];
|
||||||
ssize_t luck = 0;
|
ssize_t luck = 0;
|
||||||
@@ -1759,7 +1757,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
|||||||
this->log.info_f("(Special) Delta canceled because it would change special category");
|
this->log.info_f("(Special) Delta canceled because it would change special category");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
// Invalid special number passed to get_special; just ignore it
|
// Invalid special number passed to get_special; just ignore it
|
||||||
}
|
}
|
||||||
luck += this->tekker_adjustment_set->get_luck_for_special_upgrade(delta_index);
|
luck += this->tekker_adjustment_set->get_luck_for_special_upgrade(delta_index);
|
||||||
@@ -1774,7 +1772,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
|||||||
int8_t delta = delta_table.at(delta_index);
|
int8_t delta = delta_table.at(delta_index);
|
||||||
this->log.info_f("(Grind) Delta index {}, delta {}", delta_index, delta);
|
this->log.info_f("(Grind) Delta index {}, delta {}", delta_index, delta);
|
||||||
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
|
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
|
||||||
item.data1[3] = clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
|
item.data1[3] = std::clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
|
||||||
luck += this->tekker_adjustment_set->get_luck_for_grind_delta(delta_index);
|
luck += this->tekker_adjustment_set->get_luck_for_grind_delta(delta_index);
|
||||||
this->log.info_f("(Grind) Luck is now {}", luck);
|
this->log.info_f("(Grind) Luck is now {}", luck);
|
||||||
} else {
|
} else {
|
||||||
@@ -1793,7 +1791,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
|||||||
// to check here if each bonus is actually present.
|
// to check here if each bonus is actually present.
|
||||||
for (size_t z = 6; z <= 10; z += 2) {
|
for (size_t z = 6; z <= 10; z += 2) {
|
||||||
if (item.data1[z] >= 1 && item.data1[z] <= 5) {
|
if (item.data1[z] >= 1 && item.data1[z] <= 5) {
|
||||||
item.data1[z + 1] = min<int8_t>(item.data1[z + 1] + delta, 100);
|
item.data1[z + 1] = std::min<int8_t>(item.data1[z + 1] + delta, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
luck += this->tekker_adjustment_set->get_luck_for_bonus_delta(delta_index);
|
luck += this->tekker_adjustment_set->get_luck_for_bonus_delta(delta_index);
|
||||||
|
|||||||
+30
-32
@@ -6,11 +6,9 @@
|
|||||||
#include "ItemParameterTable.hh"
|
#include "ItemParameterTable.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE({10});
|
||||||
|
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2({10, 10, 1, 10, 10, 10, 10, 10, 10, 1});
|
||||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE({10});
|
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4(
|
||||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2({10, 10, 1, 10, 10, 10, 10, 10, 10, 1});
|
|
||||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4(
|
|
||||||
{10, 10, 1, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 99, 1});
|
{10, 10, 1, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 99, 1});
|
||||||
|
|
||||||
const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE(
|
const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE(
|
||||||
@@ -21,7 +19,7 @@ const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_V3_V4(
|
|||||||
Version::GC_V3, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4, 999999);
|
Version::GC_V3, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4, 999999);
|
||||||
|
|
||||||
ItemData::StackLimits::StackLimits(
|
ItemData::StackLimits::StackLimits(
|
||||||
Version version, const vector<uint8_t>& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size)
|
Version version, const std::vector<uint8_t>& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size)
|
||||||
: version(version),
|
: version(version),
|
||||||
max_tool_stack_sizes_by_data1_1(max_tool_stack_sizes_by_data1_1),
|
max_tool_stack_sizes_by_data1_1(max_tool_stack_sizes_by_data1_1),
|
||||||
max_meseta_stack_size(max_meseta_stack_size) {}
|
max_meseta_stack_size(max_meseta_stack_size) {}
|
||||||
@@ -40,7 +38,7 @@ uint8_t ItemData::StackLimits::get(uint8_t data1_0, uint8_t data1_1) const {
|
|||||||
}
|
}
|
||||||
if (data1_0 == 3) {
|
if (data1_0 == 3) {
|
||||||
const auto& vec = this->max_tool_stack_sizes_by_data1_1;
|
const auto& vec = this->max_tool_stack_sizes_by_data1_1;
|
||||||
return vec.at(min<size_t>(data1_1, vec.size() - 1));
|
return vec.at(std::min<size_t>(data1_1, vec.size() - 1));
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -181,7 +179,7 @@ bool ItemData::is_wrapped(const StackLimits& limits) const {
|
|||||||
case 4:
|
case 4:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item data");
|
throw std::runtime_error("invalid item data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +204,7 @@ void ItemData::wrap(const StackLimits& limits, uint8_t present_color) {
|
|||||||
case 4:
|
case 4:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item data");
|
throw std::runtime_error("invalid item data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +225,7 @@ void ItemData::unwrap(const StackLimits& limits) {
|
|||||||
case 4:
|
case 4:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item data");
|
throw std::runtime_error("invalid item data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +307,7 @@ uint16_t ItemData::compute_mag_strength_flags() const {
|
|||||||
ret |= 0x020;
|
ret |= 0x020;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t highest = max<uint16_t>(dex, max<uint16_t>(pow, mind));
|
uint16_t highest = std::max<uint16_t>(dex, std::max<uint16_t>(pow, mind));
|
||||||
if ((pow == highest) + (dex == highest) + (mind == highest) > 1) {
|
if ((pow == highest) + (dex == highest) + (mind == highest) > 1) {
|
||||||
ret |= 0x100;
|
ret |= 0x100;
|
||||||
}
|
}
|
||||||
@@ -343,10 +341,10 @@ uint8_t ItemData::mag_photon_blast_for_slot(uint8_t slot) const {
|
|||||||
left_pb_num--;
|
left_pb_num--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw logic_error("failed to find unused photon blast number");
|
throw std::logic_error("failed to find unused photon blast number");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("invalid slot index");
|
throw std::logic_error("invalid slot index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +385,7 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) {
|
|||||||
pb_num--;
|
pb_num--;
|
||||||
}
|
}
|
||||||
if (pb_num >= 4) {
|
if (pb_num >= 4) {
|
||||||
throw runtime_error("left photon blast number is too high");
|
throw std::runtime_error("left photon blast number is too high");
|
||||||
}
|
}
|
||||||
pb_nums |= (pb_num << 6);
|
pb_nums |= (pb_num << 6);
|
||||||
flags |= 4;
|
flags |= 4;
|
||||||
@@ -471,11 +469,11 @@ void ItemData::decode_for_version(Version from_version) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item class");
|
throw std::runtime_error("invalid item class");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParameterTable> item_parameter_table) {
|
void ItemData::encode_for_version(Version to_version, std::shared_ptr<const ItemParameterTable> item_parameter_table) {
|
||||||
bool should_encode_v2_data = item_parameter_table &&
|
bool should_encode_v2_data = item_parameter_table &&
|
||||||
(is_v1(to_version) || is_v2(to_version)) &&
|
(is_v1(to_version) || is_v2(to_version)) &&
|
||||||
(to_version != Version::GC_NTE) &&
|
(to_version != Version::GC_NTE) &&
|
||||||
@@ -501,7 +499,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x01: {
|
case 0x01: {
|
||||||
static const array<uint8_t, 4> armor_limits = {0x00, 0x29, 0x27, 0x44};
|
static const std::array<uint8_t, 4> armor_limits = {0x00, 0x29, 0x27, 0x44};
|
||||||
if (should_encode_v2_data && (this->data1[2] >= armor_limits[this->data1[1]])) {
|
if (should_encode_v2_data && (this->data1[2] >= armor_limits[this->data1[1]])) {
|
||||||
this->data1[3] = this->data1[2];
|
this->data1[3] = this->data1[2];
|
||||||
this->data1[2] = 0x00;
|
this->data1[2] = 0x00;
|
||||||
@@ -554,7 +552,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item class");
|
throw std::runtime_error("invalid item class");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +656,7 @@ bool ItemData::has_bonuses() const {
|
|||||||
case 3:
|
case 3:
|
||||||
return (this->get_unit_bonus() > 0);
|
return (this->get_unit_bonus() > 0);
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
if (this->data1[1] < 0x23) {
|
if (this->data1[1] < 0x23) {
|
||||||
@@ -670,7 +668,7 @@ bool ItemData::has_bonuses() const {
|
|||||||
case 4:
|
case 4:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -703,7 +701,7 @@ EquipSlot ItemData::default_equip_slot() const {
|
|||||||
case 0x02:
|
case 0x02:
|
||||||
return EquipSlot::MAG;
|
return EquipSlot::MAG;
|
||||||
}
|
}
|
||||||
throw runtime_error("item cannot be equipped");
|
throw std::runtime_error("item cannot be equipped");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
|
bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
|
||||||
@@ -722,7 +720,7 @@ bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
|
|||||||
case EquipSlot::WEAPON:
|
case EquipSlot::WEAPON:
|
||||||
return (this->data1[0] == 0x00);
|
return (this->data1[0] == 0x00);
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid equip slot");
|
throw std::runtime_error("invalid equip slot");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,23 +746,23 @@ bool ItemData::compare_for_sort(const ItemData& a, const ItemData& b) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemData ItemData::from_data(const string& data) {
|
ItemData ItemData::from_data(const std::string& data) {
|
||||||
if (data.size() < 2) {
|
if (data.size() < 2) {
|
||||||
throw runtime_error("data is too short");
|
throw std::runtime_error("data is too short");
|
||||||
}
|
}
|
||||||
if (data.size() > 0x10) {
|
if (data.size() > 0x10) {
|
||||||
throw runtime_error("data is too long");
|
throw std::runtime_error("data is too long");
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemData ret;
|
ItemData ret;
|
||||||
for (size_t z = 0; z < min<size_t>(data.size(), 12); z++) {
|
for (size_t z = 0; z < std::min<size_t>(data.size(), 12); z++) {
|
||||||
ret.data1[z] = data[z];
|
ret.data1[z] = data[z];
|
||||||
}
|
}
|
||||||
for (size_t z = 12; z < min<size_t>(data.size(), 16); z++) {
|
for (size_t z = 12; z < std::min<size_t>(data.size(), 16); z++) {
|
||||||
ret.data2[z - 12] = data[z];
|
ret.data2[z - 12] = data[z];
|
||||||
}
|
}
|
||||||
if (ret.data1[0] > 4) {
|
if (ret.data1[0] > 4) {
|
||||||
throw runtime_error("invalid item class");
|
throw std::runtime_error("invalid item class");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -772,7 +770,7 @@ ItemData ItemData::from_data(const string& data) {
|
|||||||
ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t primary_identifier) {
|
ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t primary_identifier) {
|
||||||
ItemData ret;
|
ItemData ret;
|
||||||
if (primary_identifier > 0x04000000) {
|
if (primary_identifier > 0x04000000) {
|
||||||
throw runtime_error("invalid item class");
|
throw std::runtime_error("invalid item class");
|
||||||
}
|
}
|
||||||
ret.data1[0] = (primary_identifier >> 24) & 0xFF;
|
ret.data1[0] = (primary_identifier >> 24) & 0xFF;
|
||||||
ret.data1[1] = (primary_identifier >> 16) & 0xFF;
|
ret.data1[1] = (primary_identifier >> 16) & 0xFF;
|
||||||
@@ -786,16 +784,16 @@ ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t p
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
string ItemData::hex() const {
|
std::string ItemData::hex() const {
|
||||||
return std::format("{:08X} {:08X} {:08X} ({:08X}) {:08X}",
|
return std::format("{:08X} {:08X} {:08X} ({:08X}) {:08X}",
|
||||||
this->data1db[0], this->data1db[1], this->data1db[2], this->id, this->data2db);
|
this->data1db[0], this->data1db[1], this->data1db[2], this->id, this->data2db);
|
||||||
}
|
}
|
||||||
|
|
||||||
string ItemData::short_hex() const {
|
std::string ItemData::short_hex() const {
|
||||||
auto ret = std::format("{:08X}{:08X}{:08X}{:08X}",
|
auto ret = std::format("{:08X}{:08X}{:08X}{:08X}",
|
||||||
this->data1db[0], this->data1db[1], this->data1db[2], this->data2db);
|
this->data1db[0], this->data1db[1], this->data1db[2], this->data2db);
|
||||||
size_t offset = ret.find_last_not_of('0');
|
size_t offset = ret.find_last_not_of('0');
|
||||||
if (offset != string::npos) {
|
if (offset != std::string::npos) {
|
||||||
offset += (offset & 1) ? 1 : 2;
|
offset += (offset & 1) ? 1 : 2;
|
||||||
offset = std::max<size_t>(offset, 6);
|
offset = std::max<size_t>(offset, 6);
|
||||||
if (offset < ret.size()) {
|
if (offset < ret.size()) {
|
||||||
|
|||||||
+59
-62
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
ItemNameIndex::ItemNameIndex(
|
ItemNameIndex::ItemNameIndex(
|
||||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||||
std::shared_ptr<const ItemData::StackLimits> limits,
|
std::shared_ptr<const ItemData::StackLimits> limits,
|
||||||
@@ -19,17 +17,17 @@ ItemNameIndex::ItemNameIndex(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string* name = nullptr;
|
const std::string* name = nullptr;
|
||||||
bool is_es_weapon = false;
|
bool is_es_weapon = false;
|
||||||
try {
|
try {
|
||||||
ItemData item = ItemData::from_primary_identifier(*this->limits, primary_identifier);
|
ItemData item = ItemData::from_primary_identifier(*this->limits, primary_identifier);
|
||||||
is_es_weapon = item.is_s_rank_weapon();
|
is_es_weapon = item.is_s_rank_weapon();
|
||||||
name = &name_coll.at(item_parameter_table->get_item_id(item));
|
name = &name_coll.at(item_parameter_table->get_item_id(item));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
auto meta = make_shared<ItemMetadata>();
|
auto meta = std::make_shared<ItemMetadata>();
|
||||||
meta->primary_identifier = primary_identifier;
|
meta->primary_identifier = primary_identifier;
|
||||||
meta->name = *name;
|
meta->name = *name;
|
||||||
this->primary_identifier_index.emplace(meta->primary_identifier, meta);
|
this->primary_identifier_index.emplace(meta->primary_identifier, meta);
|
||||||
@@ -44,7 +42,7 @@ ItemNameIndex::ItemNameIndex(
|
|||||||
static std::string s_rank_name_characters("\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", 0x20);
|
static std::string s_rank_name_characters("\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", 0x20);
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const array<const char*, 0x29> name_for_weapon_special = {
|
static const std::array<const char*, 0x29> name_for_weapon_special = {
|
||||||
nullptr,
|
nullptr,
|
||||||
"Draw", // Type: 0001, amount: 0005
|
"Draw", // Type: 0001, amount: 0005
|
||||||
"Drain", // Type: 0001, amount: 0009
|
"Drain", // Type: 0001, amount: 0009
|
||||||
@@ -89,7 +87,7 @@ static const array<const char*, 0x29> name_for_weapon_special = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
const array<const char*, 0x11> name_for_s_rank_special = {
|
const std::array<const char*, 0x11> name_for_s_rank_special = {
|
||||||
nullptr,
|
nullptr,
|
||||||
"Jellen",
|
"Jellen",
|
||||||
"Zalure",
|
"Zalure",
|
||||||
@@ -117,7 +115,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
return std::format("{}{} Meseta", include_color_escapes ? "$C7" : "", item.data2d);
|
return std::format("{}{} Meseta", include_color_escapes ? "$C7" : "", item.data2d);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> ret_tokens;
|
std::vector<std::string> ret_tokens;
|
||||||
|
|
||||||
// For weapons, specials appear before the weapon name
|
// For weapons, specials appear before the weapon name
|
||||||
bool is_unidentified = false;
|
bool is_unidentified = false;
|
||||||
@@ -135,7 +133,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
if (special_id) {
|
if (special_id) {
|
||||||
try {
|
try {
|
||||||
ret_tokens.emplace_back(name_for_weapon_special.at(special_id));
|
ret_tokens.emplace_back(name_for_weapon_special.at(special_id));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
ret_tokens.emplace_back(std::format("!SP:{:02X}", special_id));
|
ret_tokens.emplace_back(std::format("!SP:{:02X}", special_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +141,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
if (!name_only && (item.data1[0] == 0x00) && (item.data1[2] != 0x00) && item.is_s_rank_weapon()) {
|
if (!name_only && (item.data1[0] == 0x00) && (item.data1[2] != 0x00) && item.is_s_rank_weapon()) {
|
||||||
try {
|
try {
|
||||||
ret_tokens.emplace_back(name_for_s_rank_special.at(item.data1[2]));
|
ret_tokens.emplace_back(name_for_s_rank_special.at(item.data1[2]));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
ret_tokens.emplace_back(std::format("!SSP:{:02X}", item.data1[2]));
|
ret_tokens.emplace_back(std::format("!SSP:{:02X}", item.data1[2]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,11 +158,11 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
// Add the item name
|
// Add the item name
|
||||||
uint32_t primary_identifier = item.primary_identifier();
|
uint32_t primary_identifier = item.primary_identifier();
|
||||||
if ((primary_identifier & 0xFFFF0000) == 0x03020000) {
|
if ((primary_identifier & 0xFFFF0000) == 0x03020000) {
|
||||||
string technique_name;
|
std::string technique_name;
|
||||||
try {
|
try {
|
||||||
technique_name = tech_id_to_name.at(item.data1[4]);
|
technique_name = tech_id_to_name.at(item.data1[4]);
|
||||||
technique_name[0] = toupper(technique_name[0]);
|
technique_name[0] = toupper(technique_name[0]);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
technique_name = std::format("!TD:{:02X}", item.data1[4]);
|
technique_name = std::format("!TD:{:02X}", item.data1[4]);
|
||||||
}
|
}
|
||||||
// Hide the level for Reverser and Ryuker, unless the level isn't 1
|
// Hide the level for Reverser and Ryuker, unless the level isn't 1
|
||||||
@@ -177,7 +175,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
try {
|
try {
|
||||||
auto meta = this->primary_identifier_index.at(primary_identifier);
|
auto meta = this->primary_identifier_index.at(primary_identifier);
|
||||||
ret_tokens.emplace_back(meta->name);
|
ret_tokens.emplace_back(meta->name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
ret_tokens.emplace_back(std::format("!ID:{:08X}", primary_identifier));
|
ret_tokens.emplace_back(std::format("!ID:{:08X}", primary_identifier));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +206,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
static_cast<uint8_t>(be_data1w5 & 0x1F),
|
static_cast<uint8_t>(be_data1w5 & 0x1F),
|
||||||
};
|
};
|
||||||
|
|
||||||
string name;
|
std::string name;
|
||||||
for (size_t x = 0; x < 8; x++) {
|
for (size_t x = 0; x < 8; x++) {
|
||||||
char ch = s_rank_name_characters.at(char_indexes[x]);
|
char ch = s_rank_name_characters.at(char_indexes[x]);
|
||||||
if (ch == 0) {
|
if (ch == 0) {
|
||||||
@@ -306,7 +304,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
uint16_t pow = item.data1w[3];
|
uint16_t pow = item.data1w[3];
|
||||||
uint16_t dex = item.data1w[4];
|
uint16_t dex = item.data1w[4];
|
||||||
uint16_t mind = item.data1w[5];
|
uint16_t mind = item.data1w[5];
|
||||||
auto format_stat = +[](uint16_t stat) -> string {
|
auto format_stat = +[](uint16_t stat) -> std::string {
|
||||||
uint16_t level = stat / 100;
|
uint16_t level = stat / 100;
|
||||||
uint8_t partial = stat % 100;
|
uint8_t partial = stat % 100;
|
||||||
if (partial == 0) {
|
if (partial == 0) {
|
||||||
@@ -323,7 +321,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
|
|
||||||
uint8_t flags = item.data2[2];
|
uint8_t flags = item.data2[2];
|
||||||
if (flags & 7) {
|
if (flags & 7) {
|
||||||
static const vector<const char*> pb_shortnames = {"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
static const std::vector<const char*> pb_shortnames = {"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
||||||
|
|
||||||
const char* pb_names[3] = {nullptr, nullptr, nullptr};
|
const char* pb_names[3] = {nullptr, nullptr, nullptr};
|
||||||
uint8_t left_pb = item.mag_photon_blast_for_slot(2);
|
uint8_t left_pb = item.mag_photon_blast_for_slot(2);
|
||||||
@@ -339,7 +337,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
pb_names[2] = pb_shortnames[right_pb];
|
pb_names[2] = pb_shortnames[right_pb];
|
||||||
}
|
}
|
||||||
|
|
||||||
string token = "PB:";
|
std::string token = "PB:";
|
||||||
for (size_t x = 0; x < 3; x++) {
|
for (size_t x = 0; x < 3; x++) {
|
||||||
if (pb_names[x] == nullptr) {
|
if (pb_names[x] == nullptr) {
|
||||||
continue;
|
continue;
|
||||||
@@ -354,7 +352,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
ret_tokens.emplace_back(std::format("({})", name_for_mag_color.at(item.data2[3])));
|
ret_tokens.emplace_back(std::format("({})", name_for_mag_color.at(item.data2[3])));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
ret_tokens.emplace_back(std::format("(!CL:{:02X})", item.data2[3]));
|
ret_tokens.emplace_back(std::format("(!CL:{:02X})", item.data2[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +363,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string ret = phosg::join(ret_tokens, " ");
|
std::string ret = phosg::join(ret_tokens, " ");
|
||||||
if (include_color_escapes) {
|
if (include_color_escapes) {
|
||||||
if (is_unidentified) {
|
if (is_unidentified) {
|
||||||
return "$C3" + ret;
|
return "$C3" + ret;
|
||||||
@@ -387,18 +385,18 @@ ItemData ItemNameIndex::parse_item_description(const std::string& desc) const {
|
|||||||
ItemData ret;
|
ItemData ret;
|
||||||
try {
|
try {
|
||||||
ret = this->parse_item_description_phase(desc, false);
|
ret = this->parse_item_description_phase(desc, false);
|
||||||
} catch (const exception& e1) {
|
} catch (const std::exception& e1) {
|
||||||
try {
|
try {
|
||||||
ret = this->parse_item_description_phase(desc, true);
|
ret = this->parse_item_description_phase(desc, true);
|
||||||
} catch (const exception& e2) {
|
} catch (const std::exception& e2) {
|
||||||
try {
|
try {
|
||||||
ret = ItemData::from_data(phosg::parse_data_string(desc));
|
ret = ItemData::from_data(phosg::parse_data_string(desc));
|
||||||
} catch (const exception& ed) {
|
} catch (const std::exception& ed) {
|
||||||
if (strcmp(e1.what(), e2.what())) {
|
if (strcmp(e1.what(), e2.what())) {
|
||||||
throw runtime_error(std::format("cannot parse item description \"{}\" (as text 1: {}) (as text 2: {}) (as data: {})",
|
throw std::runtime_error(std::format("cannot parse item description \"{}\" (as text 1: {}) (as text 2: {}) (as data: {})",
|
||||||
desc, e1.what(), e2.what(), ed.what()));
|
desc, e1.what(), e2.what(), ed.what()));
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error(std::format("cannot parse item description \"{}\" (as text: {}) (as data: {})",
|
throw std::runtime_error(std::format("cannot parse item description \"{}\" (as text: {}) (as data: {})",
|
||||||
desc, e1.what(), ed.what()));
|
desc, e1.what(), ed.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,17 +412,17 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
ret.id = 0xFFFFFFFF;
|
ret.id = 0xFFFFFFFF;
|
||||||
ret.data2d = 0;
|
ret.data2d = 0;
|
||||||
|
|
||||||
string desc = phosg::tolower(description);
|
std::string desc = phosg::tolower(description);
|
||||||
if (desc.ends_with(" meseta")) {
|
if (desc.ends_with(" meseta")) {
|
||||||
ret.data1[0] = 0x04;
|
ret.data1[0] = 0x04;
|
||||||
ret.data2d = stol(desc, nullptr, 10);
|
ret.data2d = std::stol(desc, nullptr, 10);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.starts_with("es ")) {
|
if (desc.starts_with("es ")) {
|
||||||
auto parse_name = [&](const std::string& token) -> void {
|
auto parse_name = [&](const std::string& token) -> void {
|
||||||
if (token.size() > 8) {
|
if (token.size() > 8) {
|
||||||
throw runtime_error("s-rank name too long");
|
throw std::runtime_error("s-rank name too long");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t char_indexes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
uint8_t char_indexes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
@@ -432,7 +430,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
char ch = toupper(token[z]);
|
char ch = toupper(token[z]);
|
||||||
size_t pos = s_rank_name_characters.find(ch);
|
size_t pos = s_rank_name_characters.find(ch);
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
throw runtime_error(std::format("s-rank name contains invalid character {:02X} ({})", ch, ch));
|
throw std::runtime_error(std::format("s-rank name contains invalid character {:02X} ({})", ch, ch));
|
||||||
}
|
}
|
||||||
char_indexes[z] = pos;
|
char_indexes[z] = pos;
|
||||||
}
|
}
|
||||||
@@ -538,10 +536,10 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
ret.data1[4] = tech;
|
ret.data1[4] = tech;
|
||||||
} else {
|
} else {
|
||||||
if (tokens.size() != 2) {
|
if (tokens.size() != 2) {
|
||||||
throw runtime_error("invalid tech disk format");
|
throw std::runtime_error("invalid tech disk format");
|
||||||
}
|
}
|
||||||
if (!tokens[1].starts_with("lv.")) {
|
if (!tokens[1].starts_with("lv.")) {
|
||||||
throw runtime_error("invalid tech disk level");
|
throw std::runtime_error("invalid tech disk level");
|
||||||
}
|
}
|
||||||
uint8_t tech = technique_for_name(tokens[0]);
|
uint8_t tech = technique_for_name(tokens[0]);
|
||||||
uint8_t level = stoul(tokens[1].substr(3), nullptr, 10) - 1;
|
uint8_t level = stoul(tokens[1].substr(3), nullptr, 10) - 1;
|
||||||
@@ -574,7 +572,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
if (!name_for_weapon_special[z]) {
|
if (!name_for_weapon_special[z]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
string prefix = phosg::tolower(name_for_weapon_special[z]);
|
std::string prefix = phosg::tolower(name_for_weapon_special[z]);
|
||||||
prefix += ' ';
|
prefix += ' ';
|
||||||
if (desc.starts_with(prefix)) {
|
if (desc.starts_with(prefix)) {
|
||||||
weapon_special = z;
|
weapon_special = z;
|
||||||
@@ -592,14 +590,14 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
if (name_it != this->name_index.end() && desc.starts_with(name_it->first)) {
|
if (name_it != this->name_index.end() && desc.starts_with(name_it->first)) {
|
||||||
break;
|
break;
|
||||||
} else if (name_it == this->name_index.begin()) {
|
} else if (name_it == this->name_index.begin()) {
|
||||||
throw runtime_error("no such item");
|
throw std::runtime_error("no such item");
|
||||||
} else {
|
} else {
|
||||||
name_it--;
|
name_it--;
|
||||||
lookback++;
|
lookback++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lookback >= 4) {
|
if (lookback >= 4) {
|
||||||
throw runtime_error("item not found: " + desc);
|
throw std::runtime_error("item not found: " + desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
desc = desc.substr(name_it->first.size());
|
desc = desc.substr(name_it->first.size());
|
||||||
@@ -637,7 +635,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
} else {
|
} else {
|
||||||
auto p_tokens = phosg::split(token, '/');
|
auto p_tokens = phosg::split(token, '/');
|
||||||
if (p_tokens.size() > 5) {
|
if (p_tokens.size() > 5) {
|
||||||
throw runtime_error("invalid bonuses token");
|
throw std::runtime_error("invalid bonuses token");
|
||||||
}
|
}
|
||||||
uint8_t max_bonuses = this->item_parameter_table->is_unsealable_item(ret) ? 2 : 3;
|
uint8_t max_bonuses = this->item_parameter_table->is_unsealable_item(ret) ? 2 : 3;
|
||||||
uint8_t bonus_index = 0;
|
uint8_t bonus_index = 0;
|
||||||
@@ -647,7 +645,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (bonus_index >= max_bonuses) {
|
if (bonus_index >= max_bonuses) {
|
||||||
throw runtime_error("weapon has too many bonuses");
|
throw std::runtime_error("weapon has too many bonuses");
|
||||||
}
|
}
|
||||||
ret.data1[6 + (2 * bonus_index)] = z + 1;
|
ret.data1[6 + (2 * bonus_index)] = z + 1;
|
||||||
ret.data1[7 + (2 * bonus_index)] = static_cast<uint8_t>(bonus_value);
|
ret.data1[7 + (2 * bonus_index)] = static_cast<uint8_t>(bonus_value);
|
||||||
@@ -662,7 +660,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
|
|
||||||
} else if (ret.data1[0] == 0x01) {
|
} else if (ret.data1[0] == 0x01) {
|
||||||
if (ret.data1[1] == 0x03) { // Unit
|
if (ret.data1[1] == 0x03) { // Unit
|
||||||
static const unordered_map<string, uint16_t> modifiers({
|
static const std::unordered_map<std::string, uint16_t> modifiers({
|
||||||
{"--", 0xFFFC},
|
{"--", 0xFFFC},
|
||||||
{"-", 0xFFFE},
|
{"-", 0xFFFE},
|
||||||
{"", 0x0000},
|
{"", 0x0000},
|
||||||
@@ -698,7 +696,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
if (token.empty()) {
|
if (token.empty()) {
|
||||||
continue;
|
continue;
|
||||||
} else if (!token.starts_with("+")) {
|
} else if (!token.starts_with("+")) {
|
||||||
throw runtime_error("invalid armor/shield modifier");
|
throw std::runtime_error("invalid armor/shield modifier");
|
||||||
}
|
}
|
||||||
if (token.ends_with("def")) {
|
if (token.ends_with("def")) {
|
||||||
ret.data1w[3] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
ret.data1w[3] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
||||||
@@ -721,9 +719,9 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
} else if (token.starts_with("pb:")) { // Photon blasts
|
} else if (token.starts_with("pb:")) { // Photon blasts
|
||||||
auto pb_tokens = phosg::split(token.substr(3), ',');
|
auto pb_tokens = phosg::split(token.substr(3), ',');
|
||||||
if (pb_tokens.size() > 3) {
|
if (pb_tokens.size() > 3) {
|
||||||
throw runtime_error("too many photon blasts specified");
|
throw std::runtime_error("too many photon blasts specified");
|
||||||
}
|
}
|
||||||
static const unordered_map<string, uint8_t> name_to_pb_num(
|
static const std::unordered_map<std::string, uint8_t> name_to_pb_num(
|
||||||
{{"f", 0}, {"e", 1}, {"g", 2}, {"p", 3}, {"l", 4}, {"m", 5}, {"my", 5}, {"m&y", 5}});
|
{{"f", 0}, {"e", 1}, {"g", 2}, {"p", 3}, {"l", 4}, {"m", 5}, {"my", 5}, {"m&y", 5}});
|
||||||
for (const auto& pb_token : pb_tokens) {
|
for (const auto& pb_token : pb_tokens) {
|
||||||
ret.add_mag_photon_blast(name_to_pb_num.at(pb_token));
|
ret.add_mag_photon_blast(name_to_pb_num.at(pb_token));
|
||||||
@@ -735,12 +733,12 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
} else if (!token.empty() && isdigit(token[0])) { // Stats
|
} else if (!token.empty() && isdigit(token[0])) { // Stats
|
||||||
auto s_tokens = phosg::split(token, '/');
|
auto s_tokens = phosg::split(token, '/');
|
||||||
if (s_tokens.size() != 4) {
|
if (s_tokens.size() != 4) {
|
||||||
throw runtime_error("incorrect stat count");
|
throw std::runtime_error("incorrect stat count");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < 4; z++) {
|
for (size_t z = 0; z < 4; z++) {
|
||||||
auto n_tokens = phosg::split(s_tokens[z], '.');
|
auto n_tokens = phosg::split(s_tokens[z], '.');
|
||||||
if (n_tokens.size() == 0 || n_tokens.size() > 2) {
|
if (n_tokens.size() == 0 || n_tokens.size() > 2) {
|
||||||
throw logic_error("incorrect stats argument format");
|
throw std::logic_error("incorrect stats argument format");
|
||||||
} else if ((n_tokens.size() == 1) || (n_tokens[1].size() == 0)) {
|
} else if ((n_tokens.size() == 1) || (n_tokens[1].size() == 0)) {
|
||||||
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100;
|
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100;
|
||||||
} else if (n_tokens[1].size() == 1) {
|
} else if (n_tokens[1].size() == 1) {
|
||||||
@@ -748,7 +746,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
} else if (n_tokens[1].size() == 2) {
|
} else if (n_tokens[1].size() == 2) {
|
||||||
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100 + stoul(n_tokens[1], nullptr, 10);
|
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100 + stoul(n_tokens[1], nullptr, 10);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("incorrect stat format");
|
throw std::runtime_error("incorrect stat format");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.data1[2] = ret.compute_mag_level();
|
ret.data1[2] = ret.compute_mag_level();
|
||||||
@@ -768,18 +766,18 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
|||||||
ret.data1[5] = 1;
|
ret.data1[5] = 1;
|
||||||
}
|
}
|
||||||
} else if (!desc.empty()) {
|
} else if (!desc.empty()) {
|
||||||
throw runtime_error("item cannot be stacked");
|
throw std::runtime_error("item cannot be stacked");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_wrapped) {
|
if (is_wrapped) {
|
||||||
if (ret.is_stackable(*this->limits)) {
|
if (ret.is_stackable(*this->limits)) {
|
||||||
throw runtime_error("stackable items cannot be wrapped");
|
throw std::runtime_error("stackable items cannot be wrapped");
|
||||||
} else {
|
} else {
|
||||||
ret.data1[3] |= 0x40;
|
ret.data1[3] |= 0x40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("invalid item class");
|
throw std::logic_error("invalid item class");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -793,7 +791,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes(); data1_1++) {
|
for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes(); data1_1++) {
|
||||||
uint8_t weapon_class = pmt->get_weapon_kind(data1_1);
|
uint8_t weapon_class = pmt->get_weapon_kind(data1_1);
|
||||||
float sale_divisor = pmt->get_sale_divisor(0x00, data1_1);
|
float sale_divisor = pmt->get_sale_divisor(0x00, data1_1);
|
||||||
string divisor_str = std::format("{:g}", sale_divisor);
|
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||||
divisor_str.resize(13, ' ');
|
divisor_str.resize(13, ' ');
|
||||||
|
|
||||||
size_t data1_2_limit = pmt->num_weapons_in_class(data1_1);
|
size_t data1_2_limit = pmt->num_weapons_in_class(data1_1);
|
||||||
@@ -806,11 +804,11 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
item.data1[0] = 0x00;
|
item.data1[0] = 0x00;
|
||||||
item.data1[1] = data1_1;
|
item.data1[1] = data1_1;
|
||||||
item.data1[2] = data1_2;
|
item.data1[2] = data1_2;
|
||||||
string name = this->describe_item(item);
|
std::string name = this->describe_item(item);
|
||||||
|
|
||||||
const auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index);
|
const auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index);
|
||||||
|
|
||||||
string tech_boost_str;
|
std::string tech_boost_str;
|
||||||
if (w.tech_boost_entry_index < pmt->num_tech_boosts()) {
|
if (w.tech_boost_entry_index < pmt->num_tech_boosts()) {
|
||||||
const auto& tech_boost = pmt->get_tech_boost(w.tech_boost_entry_index);
|
const auto& tech_boost = pmt->get_tech_boost(w.tech_boost_entry_index);
|
||||||
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
|
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
|
||||||
@@ -874,7 +872,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB(TN:FL:AMOUNT, ... ) FT A4 ST* ---DIVISOR--- NAME\n");
|
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB(TN:FL:AMOUNT, ... ) FT A4 ST* ---DIVISOR--- NAME\n");
|
||||||
for (size_t data1_1 = 1; data1_1 < 3; data1_1++) {
|
for (size_t data1_1 = 1; data1_1 < 3; data1_1++) {
|
||||||
float sale_divisor = pmt->get_sale_divisor(0x01, data1_1);
|
float sale_divisor = pmt->get_sale_divisor(0x01, data1_1);
|
||||||
string divisor_str = std::format("{:g}", sale_divisor);
|
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||||
divisor_str.resize(13, ' ');
|
divisor_str.resize(13, ' ');
|
||||||
|
|
||||||
size_t data1_2_limit = pmt->num_armors_or_shields_in_class(data1_1);
|
size_t data1_2_limit = pmt->num_armors_or_shields_in_class(data1_1);
|
||||||
@@ -886,11 +884,11 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
item.data1[0] = 0x01;
|
item.data1[0] = 0x01;
|
||||||
item.data1[1] = data1_1;
|
item.data1[1] = data1_1;
|
||||||
item.data1[2] = data1_2;
|
item.data1[2] = data1_2;
|
||||||
string name = this->describe_item(item);
|
std::string name = this->describe_item(item);
|
||||||
|
|
||||||
auto& stat_boost = pmt->get_stat_boost(a.stat_boost_entry_index);
|
auto& stat_boost = pmt->get_stat_boost(a.stat_boost_entry_index);
|
||||||
|
|
||||||
string tech_boost_str;
|
std::string tech_boost_str;
|
||||||
if (a.tech_boost_entry_index < pmt->num_tech_boosts()) {
|
if (a.tech_boost_entry_index < pmt->num_tech_boosts()) {
|
||||||
const auto& tech_boost = pmt->get_tech_boost(a.tech_boost_entry_index);
|
const auto& tech_boost = pmt->get_tech_boost(a.tech_boost_entry_index);
|
||||||
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
|
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
|
||||||
@@ -938,7 +936,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS STAT COUNT ST-MOD ST* ---DIVISOR--- NAME\n");
|
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS STAT COUNT ST-MOD ST* ---DIVISOR--- NAME\n");
|
||||||
{
|
{
|
||||||
float sale_divisor = pmt->get_sale_divisor(0x01, 0x03);
|
float sale_divisor = pmt->get_sale_divisor(0x01, 0x03);
|
||||||
string divisor_str = std::format("{:g}", sale_divisor);
|
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||||
divisor_str.resize(13, ' ');
|
divisor_str.resize(13, ' ');
|
||||||
|
|
||||||
size_t data1_2_limit = pmt->num_units();
|
size_t data1_2_limit = pmt->num_units();
|
||||||
@@ -950,7 +948,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
item.data1[0] = 0x01;
|
item.data1[0] = 0x01;
|
||||||
item.data1[1] = 0x03;
|
item.data1[1] = 0x03;
|
||||||
item.data1[2] = data1_2;
|
item.data1[2] = data1_2;
|
||||||
string name = this->describe_item(item);
|
std::string name = this->describe_item(item);
|
||||||
|
|
||||||
phosg::fwrite_fmt(stream, " 0103{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:6} {:2}* {} {}\n",
|
phosg::fwrite_fmt(stream, " 0103{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:6} {:2}* {} {}\n",
|
||||||
data1_2,
|
data1_2,
|
||||||
@@ -975,14 +973,14 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
const auto& m = pmt->get_mag(data1_1);
|
const auto& m = pmt->get_mag(data1_1);
|
||||||
|
|
||||||
float sale_divisor = pmt->get_sale_divisor(0x02, data1_1);
|
float sale_divisor = pmt->get_sale_divisor(0x02, data1_1);
|
||||||
string divisor_str = std::format("{:g}", sale_divisor);
|
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||||
divisor_str.resize(13, ' ');
|
divisor_str.resize(13, ' ');
|
||||||
|
|
||||||
ItemData item;
|
ItemData item;
|
||||||
item.data1[0] = 0x02;
|
item.data1[0] = 0x02;
|
||||||
item.data1[1] = data1_1;
|
item.data1[1] = data1_1;
|
||||||
item.data1[2] = 0x00;
|
item.data1[2] = 0x00;
|
||||||
string name = this->describe_item(item);
|
std::string name = this->describe_item(item);
|
||||||
|
|
||||||
phosg::fwrite_fmt(stream, " 02{:02X}00 => {:08X} {:04X} {:04X} {:6} {:04X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:04X} {} {}\n",
|
phosg::fwrite_fmt(stream, " 02{:02X}00 => {:08X} {:04X} {:04X} {:6} {:04X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:04X} {} {}\n",
|
||||||
data1_1,
|
data1_1,
|
||||||
@@ -1011,7 +1009,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS COUNT TECH -COST- ITEMFLAG ---DIVISOR--- NAME\n");
|
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS COUNT TECH -COST- ITEMFLAG ---DIVISOR--- NAME\n");
|
||||||
for (size_t data1_1 = 0; data1_1 < pmt->num_tool_classes(); data1_1++) {
|
for (size_t data1_1 = 0; data1_1 < pmt->num_tool_classes(); data1_1++) {
|
||||||
float sale_divisor = pmt->get_sale_divisor(0x03, data1_1);
|
float sale_divisor = pmt->get_sale_divisor(0x03, data1_1);
|
||||||
string divisor_str = std::format("{:g}", sale_divisor);
|
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||||
divisor_str.resize(13, ' ');
|
divisor_str.resize(13, ' ');
|
||||||
|
|
||||||
size_t data1_2_limit = pmt->num_tools_in_class(data1_1);
|
size_t data1_2_limit = pmt->num_tools_in_class(data1_1);
|
||||||
@@ -1023,7 +1021,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
item.data1[1] = data1_1;
|
item.data1[1] = data1_1;
|
||||||
item.data1[(data1_1 == 0x02) ? 4 : 2] = data1_2;
|
item.data1[(data1_1 == 0x02) ? 4 : 2] = data1_2;
|
||||||
item.set_tool_item_amount(*this->limits, 1);
|
item.set_tool_item_amount(*this->limits, 1);
|
||||||
string name = this->describe_item(item);
|
std::string name = this->describe_item(item);
|
||||||
|
|
||||||
phosg::fwrite_fmt(stream, " 03{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:04X} {:6} {:08X} {} {}\n",
|
phosg::fwrite_fmt(stream, " 03{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:04X} {:6} {:08X} {} {}\n",
|
||||||
data1_1,
|
data1_1,
|
||||||
@@ -1078,7 +1076,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
if (index) {
|
if (index) {
|
||||||
try {
|
try {
|
||||||
name = name_for_weapon_special.at(index);
|
name = name_for_weapon_special.at(index);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name);
|
phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name);
|
||||||
@@ -1191,7 +1189,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
static constexpr std::array<const char*, 0x10> stat_names{
|
static constexpr std::array<const char*, 0x10> stat_names{
|
||||||
"ATP+", "ATA+", "EVP+", "DFP+", "MST+", "HP+", "LCK+", "ALL+",
|
"ATP+", "ATA+", "EVP+", "DFP+", "MST+", "HP+", "LCK+", "ALL+",
|
||||||
"ATP-", "ATA-", "EVP-", "DFP-", "MST-", "HP-", "LCK-", "ALL-"};
|
"ATP-", "ATA-", "EVP-", "DFP-", "MST-", "HP-", "LCK-", "ALL-"};
|
||||||
string s;
|
std::string s;
|
||||||
if (sb.stat1 > 0x10) {
|
if (sb.stat1 > 0x10) {
|
||||||
s = std::format("[{:02X}:{:04X}]", sb.stat1, sb.amount1);
|
s = std::format("[{:02X}:{:04X}]", sb.stat1, sb.amount1);
|
||||||
} else if (sb.stat1 > 0) {
|
} else if (sb.stat1 > 0) {
|
||||||
@@ -1244,7 +1242,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
phosg::fwrite_fmt(stream, " ## => BOOSTS\n");
|
phosg::fwrite_fmt(stream, " ## => BOOSTS\n");
|
||||||
for (size_t z = 0; z < pmt->num_tech_boosts(); z++) {
|
for (size_t z = 0; z < pmt->num_tech_boosts(); z++) {
|
||||||
const auto& tb = pmt->get_tech_boost(z);
|
const auto& tb = pmt->get_tech_boost(z);
|
||||||
string s;
|
std::string s;
|
||||||
if (tb.amount1) {
|
if (tb.amount1) {
|
||||||
s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num1, tb.flags1, tb.amount1);
|
s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num1, tb.flags1, tb.amount1);
|
||||||
}
|
}
|
||||||
@@ -1269,8 +1267,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
|||||||
item.data1[0] = item_code >> 16;
|
item.data1[0] = item_code >> 16;
|
||||||
item.data1[1] = item_code >> 8;
|
item.data1[1] = item_code >> 8;
|
||||||
item.data1[2] = item_code;
|
item.data1[2] = item_code;
|
||||||
string name = this->describe_item(item);
|
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, this->describe_item(item));
|
||||||
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::fwrite_fmt(stream, "RANGED SPECIALS\n");
|
phosg::fwrite_fmt(stream, "RANGED SPECIALS\n");
|
||||||
|
|||||||
+42
-44
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "CommonFileFormats.hh"
|
#include "CommonFileFormats.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
/* General notes on the ItemPMT formats:
|
/* General notes on the ItemPMT formats:
|
||||||
*
|
*
|
||||||
* Sega apparently serialized the fields in this order, so we do the same in BinaryItemParameterTableT::serialize.
|
* Sega apparently serialized the fields in this order, so we do the same in BinaryItemParameterTableT::serialize.
|
||||||
@@ -91,7 +89,7 @@ ServerDropMode phosg::enum_for_name<ServerDropMode>(const char* name) {
|
|||||||
} else if (!strcmp(name, "SERVER_DUPLICATE")) {
|
} else if (!strcmp(name, "SERVER_DUPLICATE")) {
|
||||||
return ServerDropMode::SERVER_DUPLICATE;
|
return ServerDropMode::SERVER_DUPLICATE;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid drop mode");
|
throw std::runtime_error("invalid drop mode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +107,7 @@ const char* phosg::name_for_enum<ServerDropMode>(ServerDropMode value) {
|
|||||||
case ServerDropMode::SERVER_DUPLICATE:
|
case ServerDropMode::SERVER_DUPLICATE:
|
||||||
return "SERVER_DUPLICATE";
|
return "SERVER_DUPLICATE";
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid drop mode");
|
throw std::runtime_error("invalid drop mode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,11 +1025,11 @@ public:
|
|||||||
case 3:
|
case 3:
|
||||||
return this->unit_sale_divisor;
|
return this->unit_sale_divisor;
|
||||||
}
|
}
|
||||||
throw runtime_error("invalid defensive item type");
|
throw std::runtime_error("invalid defensive item type");
|
||||||
case 2:
|
case 2:
|
||||||
return this->mag_sale_divisor;
|
return this->mag_sale_divisor;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("item type does not have a sale divisor");
|
throw std::runtime_error("item type does not have a sale divisor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2088,7 +2086,7 @@ struct HeaderV4 : HeaderV3V4Base<false> {
|
|||||||
// Reader implementation
|
// Reader implementation
|
||||||
|
|
||||||
std::set<uint32_t> ItemParameterTable::compute_all_valid_primary_identifiers() const {
|
std::set<uint32_t> ItemParameterTable::compute_all_valid_primary_identifiers() const {
|
||||||
set<uint32_t> ret;
|
std::set<uint32_t> ret;
|
||||||
|
|
||||||
auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t {
|
auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t {
|
||||||
ItemData item(data1, 0);
|
ItemData item(data1, 0);
|
||||||
@@ -2096,7 +2094,7 @@ std::set<uint32_t> ItemParameterTable::compute_all_valid_primary_identifiers() c
|
|||||||
item.data1[position] = x;
|
item.data1[position] = x;
|
||||||
try {
|
try {
|
||||||
this->get_item_id(item);
|
this->get_item_id(item);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
ret.emplace(item.primary_identifier());
|
ret.emplace(item.primary_identifier());
|
||||||
@@ -2143,7 +2141,7 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie
|
|||||||
case 3:
|
case 3:
|
||||||
return &this->get_unit(data1_2);
|
return &this->get_unit(data1_2);
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid primary identifier");
|
throw std::runtime_error("invalid primary identifier");
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
return &this->get_mag(data1_1);
|
return &this->get_mag(data1_1);
|
||||||
@@ -2152,7 +2150,7 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie
|
|||||||
// 0302XXYY here
|
// 0302XXYY here
|
||||||
return &this->get_tool(data1_1, data1_2);
|
return &this->get_tool(data1_1, data1_2);
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid primary identifier");
|
throw std::runtime_error("invalid primary identifier");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2166,7 +2164,7 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
|
|||||||
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
||||||
return this->get_armor_or_shield(item.data1[1], item.data1[2]).id;
|
return this->get_armor_or_shield(item.data1[1], item.data1[2]).id;
|
||||||
}
|
}
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
case 2:
|
case 2:
|
||||||
return this->get_mag(item.data1[1]).id;
|
return this->get_mag(item.data1[1]).id;
|
||||||
case 3:
|
case 3:
|
||||||
@@ -2175,11 +2173,11 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
|
|||||||
} else {
|
} else {
|
||||||
return this->get_tool(item.data1[1], item.data1[2]).id;
|
return this->get_tool(item.data1[1], item.data1[2]).id;
|
||||||
}
|
}
|
||||||
throw logic_error("this should be impossible");
|
throw std::logic_error("this should be impossible");
|
||||||
case 4:
|
case 4:
|
||||||
throw runtime_error("item is meseta and therefore has no definition");
|
throw std::runtime_error("item is meseta and therefore has no definition");
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2193,7 +2191,7 @@ uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const {
|
|||||||
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
||||||
return this->get_armor_or_shield(item.data1[1], item.data1[2]).team_points;
|
return this->get_armor_or_shield(item.data1[1], item.data1[2]).team_points;
|
||||||
}
|
}
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
case 2:
|
case 2:
|
||||||
return this->get_mag(item.data1[1]).team_points;
|
return this->get_mag(item.data1[1]).team_points;
|
||||||
case 3:
|
case 3:
|
||||||
@@ -2202,11 +2200,11 @@ uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const {
|
|||||||
} else {
|
} else {
|
||||||
return this->get_tool(item.data1[1], item.data1[2]).team_points;
|
return this->get_tool(item.data1[1], item.data1[2]).team_points;
|
||||||
}
|
}
|
||||||
throw logic_error("this should be impossible");
|
throw std::logic_error("this should be impossible");
|
||||||
case 4:
|
case 4:
|
||||||
throw runtime_error("item is meseta and therefore has no definition");
|
throw std::runtime_error("item is meseta and therefore has no definition");
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2249,7 +2247,7 @@ uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item, bool i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return min<uint8_t>(ret, 12);
|
return std::min<uint8_t>(ret, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ItemParameterTable::get_star_value_table() const {
|
std::string ItemParameterTable::get_star_value_table() const {
|
||||||
@@ -2288,7 +2286,7 @@ std::string ItemParameterTable::get_shield_stat_boost_index_table() const {
|
|||||||
bool ItemParameterTable::is_item_rare(const ItemData& item) const {
|
bool ItemParameterTable::is_item_rare(const ItemData& item) const {
|
||||||
try {
|
try {
|
||||||
return (this->get_item_base_stars(item) >= 9);
|
return (this->get_item_base_stars(item) >= 9);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2317,8 +2315,8 @@ const std::vector<ItemParameterTable::ItemCombination>& ItemParameterTable::all_
|
|||||||
try {
|
try {
|
||||||
return this->item_combinations_index().at(item_code_to_u32(
|
return this->item_combinations_index().at(item_code_to_u32(
|
||||||
used_item.data1[0], used_item.data1[1], used_item.data1[2]));
|
used_item.data1[0], used_item.data1[1], used_item.data1[2]));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
static const vector<ItemCombination> ret;
|
static const std::vector<ItemCombination> ret;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2332,7 +2330,7 @@ const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combinat
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw out_of_range("no item combination applies");
|
throw std::out_of_range("no item combination applies");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||||
@@ -2347,7 +2345,7 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
|||||||
|
|
||||||
float sale_divisor = this->get_sale_divisor(0, item.data1[1]);
|
float sale_divisor = this->get_sale_divisor(0, item.data1[1]);
|
||||||
if (sale_divisor == 0.0) {
|
if (sale_divisor == 0.0) {
|
||||||
throw runtime_error("item sale divisor is zero");
|
throw std::runtime_error("item sale divisor is zero");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& def = this->get_weapon(item.data1[1], item.data1[2]);
|
const auto& def = this->get_weapon(item.data1[1], item.data1[2]);
|
||||||
@@ -2380,7 +2378,7 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
|||||||
|
|
||||||
double sale_divisor = (double)this->get_sale_divisor(1, item.data1[1]);
|
double sale_divisor = (double)this->get_sale_divisor(1, item.data1[1]);
|
||||||
if (sale_divisor == 0.0) {
|
if (sale_divisor == 0.0) {
|
||||||
throw runtime_error("item sale divisor is zero");
|
throw std::runtime_error("item sale divisor is zero");
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t def_bonus = item.get_armor_or_shield_defense_bonus();
|
int16_t def_bonus = item.get_armor_or_shield_defense_bonus();
|
||||||
@@ -2406,9 +2404,9 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
|||||||
return item.data2d;
|
return item.data2d;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid item");
|
throw std::runtime_error("invalid item");
|
||||||
}
|
}
|
||||||
throw logic_error("this should be impossible");
|
throw std::logic_error("this should be impossible");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
@@ -2442,7 +2440,7 @@ public:
|
|||||||
const T& indirect_lookup_2d(size_t base_offset, size_t co_index, size_t item_index) const {
|
const T& indirect_lookup_2d(size_t base_offset, size_t co_index, size_t item_index) const {
|
||||||
const auto& co = this->r.pget<ArrayRefT<BE>>(base_offset + sizeof(ArrayRefT<BE>) * co_index);
|
const auto& co = this->r.pget<ArrayRefT<BE>>(base_offset + sizeof(ArrayRefT<BE>) * co_index);
|
||||||
if (item_index >= (co.count + HasImplicitPlaceholders)) {
|
if (item_index >= (co.count + HasImplicitPlaceholders)) {
|
||||||
throw out_of_range("2-D array index out of range");
|
throw std::out_of_range("2-D array index out of range");
|
||||||
}
|
}
|
||||||
return this->r.pget<T>(co.offset + sizeof(T) * item_index);
|
return this->r.pget<T>(co.offset + sizeof(T) * item_index);
|
||||||
}
|
}
|
||||||
@@ -2470,14 +2468,14 @@ public:
|
|||||||
|
|
||||||
virtual size_t num_weapons_in_class(uint8_t data1_1) const {
|
virtual size_t num_weapons_in_class(uint8_t data1_1) const {
|
||||||
if (data1_1 >= this->num_weapon_classes()) {
|
if (data1_1 >= this->num_weapon_classes()) {
|
||||||
throw out_of_range("weapon ID out of range");
|
throw std::out_of_range("weapon ID out of range");
|
||||||
}
|
}
|
||||||
return this->indirect_lookup_2d_count(this->root->weapon_table, data1_1);
|
return this->indirect_lookup_2d_count(this->root->weapon_table, data1_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const {
|
virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const {
|
||||||
if (data1_1 >= this->num_weapon_classes()) {
|
if (data1_1 >= this->num_weapon_classes()) {
|
||||||
throw out_of_range("weapon ID out of range");
|
throw std::out_of_range("weapon ID out of range");
|
||||||
}
|
}
|
||||||
uint16_t key = (data1_1 << 8) | data1_2;
|
uint16_t key = (data1_1 << 8) | data1_2;
|
||||||
auto it = this->weapons.find(key);
|
auto it = this->weapons.find(key);
|
||||||
@@ -2490,14 +2488,14 @@ public:
|
|||||||
|
|
||||||
virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const {
|
virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const {
|
||||||
if ((data1_1 < 1) || (data1_1 > 2)) {
|
if ((data1_1 < 1) || (data1_1 > 2)) {
|
||||||
throw out_of_range("armor/shield class ID out of range");
|
throw std::out_of_range("armor/shield class ID out of range");
|
||||||
}
|
}
|
||||||
return this->indirect_lookup_2d_count(this->root->armor_table, data1_1 - 1) + HasImplicitPlaceholders;
|
return this->indirect_lookup_2d_count(this->root->armor_table, data1_1 - 1) + HasImplicitPlaceholders;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const {
|
virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const {
|
||||||
if ((data1_1 < 1) || (data1_1 > 2)) {
|
if ((data1_1 < 1) || (data1_1 > 2)) {
|
||||||
throw out_of_range("armor/shield class ID out of range");
|
throw std::out_of_range("armor/shield class ID out of range");
|
||||||
}
|
}
|
||||||
auto& vec = (data1_1 == 1) ? this->armors : this->shields;
|
auto& vec = (data1_1 == 1) ? this->armors : this->shields;
|
||||||
return this->add_to_vector_cache_2d_indirect<ArmorOrShieldT>(vec, this->root->armor_table, data1_1 - 1, data1_2);
|
return this->add_to_vector_cache_2d_indirect<ArmorOrShieldT>(vec, this->root->armor_table, data1_1 - 1, data1_2);
|
||||||
@@ -2517,14 +2515,14 @@ public:
|
|||||||
|
|
||||||
virtual size_t num_tools_in_class(uint8_t data1_1) const {
|
virtual size_t num_tools_in_class(uint8_t data1_1) const {
|
||||||
if (data1_1 >= this->num_tool_classes()) {
|
if (data1_1 >= this->num_tool_classes()) {
|
||||||
throw out_of_range("tool class ID out of range");
|
throw std::out_of_range("tool class ID out of range");
|
||||||
}
|
}
|
||||||
return this->indirect_lookup_2d_count(this->root->tool_table, data1_1);
|
return this->indirect_lookup_2d_count(this->root->tool_table, data1_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const {
|
virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const {
|
||||||
if (data1_1 >= this->num_tool_classes()) {
|
if (data1_1 >= this->num_tool_classes()) {
|
||||||
throw out_of_range("tool class ID out of range");
|
throw std::out_of_range("tool class ID out of range");
|
||||||
}
|
}
|
||||||
uint16_t key = (data1_1 << 8) | data1_2;
|
uint16_t key = (data1_1 << 8) | data1_2;
|
||||||
auto it = this->tools.find(key);
|
auto it = this->tools.find(key);
|
||||||
@@ -2543,11 +2541,11 @@ public:
|
|||||||
const auto* defs = &this->r.pget<ToolT>(co.offset, sizeof(ToolT) * co.count);
|
const auto* defs = &this->r.pget<ToolT>(co.offset, sizeof(ToolT) * co.count);
|
||||||
for (size_t y = 0; y < co.count; y++) {
|
for (size_t y = 0; y < co.count; y++) {
|
||||||
if (defs[y].base.id == id) {
|
if (defs[y].base.id == id) {
|
||||||
return make_pair(z, y);
|
return std::make_pair(z, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw out_of_range(std::format("invalid tool class {:08X}", id));
|
throw std::out_of_range(std::format("invalid tool class {:08X}", id));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual size_t num_mags() const {
|
virtual size_t num_mags() const {
|
||||||
@@ -2636,10 +2634,10 @@ public:
|
|||||||
|
|
||||||
virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const {
|
virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const {
|
||||||
if (table_index >= 8) {
|
if (table_index >= 8) {
|
||||||
throw out_of_range("invalid mag feed table index");
|
throw std::out_of_range("invalid mag feed table index");
|
||||||
}
|
}
|
||||||
if (item_index >= 11) {
|
if (item_index >= 11) {
|
||||||
throw out_of_range("invalid mag feed item index");
|
throw std::out_of_range("invalid mag feed item index");
|
||||||
}
|
}
|
||||||
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsetsT<BE>>(this->root->mag_feed_table);
|
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsetsT<BE>>(this->root->mag_feed_table);
|
||||||
return this->r.pget<MagFeedResultsList>(table_offsets[table_index])[item_index];
|
return this->r.pget<MagFeedResultsList>(table_offsets[table_index])[item_index];
|
||||||
@@ -2681,7 +2679,7 @@ public:
|
|||||||
virtual const Special& get_special(uint8_t special) const {
|
virtual const Special& get_special(uint8_t special) const {
|
||||||
special &= 0x3F;
|
special &= 0x3F;
|
||||||
if (special >= this->num_specials()) {
|
if (special >= this->num_specials()) {
|
||||||
throw out_of_range("invalid special index");
|
throw std::out_of_range("invalid special index");
|
||||||
}
|
}
|
||||||
while (this->specials.size() <= special) {
|
while (this->specials.size() <= special) {
|
||||||
this->specials.emplace_back(this->r.pget<SpecialT<BE>>(
|
this->specials.emplace_back(this->r.pget<SpecialT<BE>>(
|
||||||
@@ -2772,10 +2770,10 @@ public:
|
|||||||
|
|
||||||
virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
|
virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
|
||||||
if (char_class >= 12) {
|
if (char_class >= 12) {
|
||||||
throw out_of_range("invalid character class");
|
throw std::out_of_range("invalid character class");
|
||||||
}
|
}
|
||||||
if (tech_num >= 19) {
|
if (tech_num >= 19) {
|
||||||
throw out_of_range("invalid technique number");
|
throw std::out_of_range("invalid technique number");
|
||||||
}
|
}
|
||||||
if constexpr (requires { this->root->max_tech_level_table; }) {
|
if constexpr (requires { this->root->max_tech_level_table; }) {
|
||||||
return r.pget_u8(this->root->max_tech_level_table + tech_num * 12 + char_class);
|
return r.pget_u8(this->root->max_tech_level_table + tech_num * 12 + char_class);
|
||||||
@@ -2862,13 +2860,13 @@ public:
|
|||||||
if constexpr (requires { this->root->unwrap_table; }) {
|
if constexpr (requires { this->root->unwrap_table; }) {
|
||||||
const auto& co = this->r.pget<ArrayRefT<BE>>(this->root->unwrap_table);
|
const auto& co = this->r.pget<ArrayRefT<BE>>(this->root->unwrap_table);
|
||||||
if (event_number >= co.count) {
|
if (event_number >= co.count) {
|
||||||
throw out_of_range("invalid event number");
|
throw std::out_of_range("invalid event number");
|
||||||
}
|
}
|
||||||
const auto& event_co = this->r.pget<ArrayRefT<BE>>(co.offset + sizeof(ArrayRefT<BE>) * event_number);
|
const auto& event_co = this->r.pget<ArrayRefT<BE>>(co.offset + sizeof(ArrayRefT<BE>) * event_number);
|
||||||
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(EventItem));
|
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(EventItem));
|
||||||
return make_pair(defs, event_co.count);
|
return std::make_pair(defs, event_co.count);
|
||||||
} else {
|
} else {
|
||||||
return make_pair(nullptr, 0);
|
return std::make_pair(nullptr, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-12
@@ -1,7 +1,5 @@
|
|||||||
#include "ItemTranslationTable.hh"
|
#include "ItemTranslationTable.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static constexpr bool is_canonical(uint32_t id) {
|
static constexpr bool is_canonical(uint32_t id) {
|
||||||
return !(id & 0x80000000);
|
return !(id & 0x80000000);
|
||||||
}
|
}
|
||||||
@@ -23,12 +21,12 @@ ItemTranslationTable::ItemTranslationTable(
|
|||||||
if (is_canonical(id)) {
|
if (is_canonical(id)) {
|
||||||
has_any_canonical_id = true;
|
has_any_canonical_id = true;
|
||||||
if (!this->entry_index_for_version[v_s].emplace(id, z).second) {
|
if (!this->entry_index_for_version[v_s].emplace(id, z).second) {
|
||||||
throw runtime_error(std::format("(row {}) duplicate canonical ID {:08X}", z, id));
|
throw std::runtime_error(std::format("(row {}) duplicate canonical ID {:08X}", z, id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!has_any_canonical_id) {
|
if (!has_any_canonical_id) {
|
||||||
throw runtime_error(std::format("(row {}) no canonical ID present in row", z));
|
throw std::runtime_error(std::format("(row {}) no canonical ID present in row", z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,27 +44,27 @@ ItemTranslationTable::ItemTranslationTable(
|
|||||||
uint32_t e_id = this->entries[z].id_for_version[v_s];
|
uint32_t e_id = this->entries[z].id_for_version[v_s];
|
||||||
if (is_canonical(e_id)) {
|
if (is_canonical(e_id)) {
|
||||||
if (!entry_index.count(e_id)) {
|
if (!entry_index.count(e_id)) {
|
||||||
throw logic_error(std::format("(row {} version {}) canonical ID {:08X} is missing from the index", z, phosg::name_for_enum(v), e_id));
|
throw std::logic_error(std::format("(row {} version {}) canonical ID {:08X} is missing from the index", z, phosg::name_for_enum(v), e_id));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
item_parameter_table->definition_for_primary_identifier(e_id);
|
item_parameter_table->definition_for_primary_identifier(e_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error(std::format("(row {} version {}) ID {:08X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
throw std::runtime_error(std::format("(row {} version {}) ID {:08X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
||||||
}
|
}
|
||||||
if (!remaining_identifiers.erase(e_id)) {
|
if (!remaining_identifiers.erase(e_id)) {
|
||||||
throw runtime_error(std::format("(row {} version {}) ID {:08X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
|
throw std::runtime_error(std::format("(row {} version {}) ID {:08X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
|
||||||
}
|
}
|
||||||
} else if (!entry_index.count(make_canonical(e_id))) {
|
} else if (!entry_index.count(make_canonical(e_id))) {
|
||||||
throw runtime_error(std::format("(row {} version {}) ID {:08X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
throw std::runtime_error(std::format("(row {} version {}) ID {:08X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!remaining_identifiers.empty()) {
|
if (!remaining_identifiers.empty()) {
|
||||||
string missing_str = std::format("(version {}) not all identifiers in the item parameter table are defined in the translation table; missing:", phosg::name_for_enum(v));
|
std::string missing_str = std::format("(version {}) not all identifiers in the item parameter table are defined in the translation table; missing:", phosg::name_for_enum(v));
|
||||||
for (uint32_t id : remaining_identifiers) {
|
for (uint32_t id : remaining_identifiers) {
|
||||||
missing_str += std::format(" {:08X}", id);
|
missing_str += std::format(" {:08X}", id);
|
||||||
}
|
}
|
||||||
throw runtime_error(missing_str);
|
throw std::runtime_error(missing_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +90,7 @@ uint32_t ItemTranslationTable::translate(uint32_t primary_identifier, Version fr
|
|||||||
ItemTranslationTable::Entry::Entry(const phosg::JSON& json) {
|
ItemTranslationTable::Entry::Entry(const phosg::JSON& json) {
|
||||||
const auto& l = json.as_list();
|
const auto& l = json.as_list();
|
||||||
if (l.size() != NUM_NON_PATCH_VERSIONS + 1) {
|
if (l.size() != NUM_NON_PATCH_VERSIONS + 1) {
|
||||||
throw runtime_error("list length is incorrect");
|
throw std::runtime_error("list length is incorrect");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < NUM_NON_PATCH_VERSIONS; z++) {
|
for (size_t z = 0; z < NUM_NON_PATCH_VERSIONS; z++) {
|
||||||
this->id_for_version[z] = l[z]->as_int();
|
this->id_for_version[z] = l[z]->as_int();
|
||||||
|
|||||||
+33
-35
@@ -4,9 +4,7 @@
|
|||||||
|
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<RandomGenerator> rand_crypt) {
|
||||||
|
|
||||||
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomGenerator> rand_crypt) {
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
// On PC (and presumably DC), the client sends a 6x29 after this to delete the used item. On GC and later versions,
|
// On PC (and presumably DC), the client sends a 6x29 after this to delete the used item. On GC and later versions,
|
||||||
@@ -24,15 +22,15 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
|
|
||||||
} else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk
|
} else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk
|
||||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||||
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
|
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.sh.char_class, item.data.data1[4]);
|
||||||
if (item.data.data1[2] > max_level) {
|
if (item.data.data1[2] > max_level) {
|
||||||
throw runtime_error("technique level too high");
|
throw std::runtime_error("technique level too high");
|
||||||
}
|
}
|
||||||
player->set_technique_level(item.data.data1[4], item.data.data1[2]);
|
player->set_technique_level(item.data.data1[4], item.data.data1[2]);
|
||||||
|
|
||||||
} else if ((primary_identifier & 0xFFFF0000) == 0x030A0000) { // Grinder
|
} else if ((primary_identifier & 0xFFFF0000) == 0x030A0000) { // Grinder
|
||||||
if (item.data.data1[2] > 2) {
|
if (item.data.data1[2] > 2) {
|
||||||
throw runtime_error("incorrect grinder value");
|
throw std::runtime_error("incorrect grinder value");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& weapon = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::WEAPON)];
|
auto& weapon = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::WEAPON)];
|
||||||
@@ -40,9 +38,9 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||||
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
|
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
|
||||||
if (is_v4 && (weapon.data.data1[3] >= weapon_def.max_grind)) {
|
if (is_v4 && (weapon.data.data1[3] >= weapon_def.max_grind)) {
|
||||||
throw runtime_error("weapon already at maximum grind");
|
throw std::runtime_error("weapon already at maximum grind");
|
||||||
}
|
}
|
||||||
weapon.data.data1[3] = min<uint8_t>(weapon.data.data1[3] + item.data.data1[2] + 1, weapon_def.max_grind);
|
weapon.data.data1[3] = std::min<uint8_t>(weapon.data.data1[3] + item.data.data1[2] + 1, weapon_def.max_grind);
|
||||||
|
|
||||||
} else if ((primary_identifier & 0xFFFF0000) == 0x030B0000) { // Material
|
} else if ((primary_identifier & 0xFFFF0000) == 0x030B0000) { // Material
|
||||||
auto p = c->character_file();
|
auto p = c->character_file();
|
||||||
@@ -85,11 +83,11 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
if (!is_v3_or_later && (c->version() != Version::GC_NTE)) {
|
if (!is_v3_or_later && (c->version() != Version::GC_NTE)) {
|
||||||
p->disp.stats.char_stats.lck += 2;
|
p->disp.stats.char_stats.lck += 2;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unknown material used");
|
throw std::runtime_error("unknown material used");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("unknown material used");
|
throw std::runtime_error("unknown material used");
|
||||||
}
|
}
|
||||||
if (is_v3_or_later || (type == Type::HP) || (type == Type::TP)) {
|
if (is_v3_or_later || (type == Type::HP) || (type == Type::TP)) {
|
||||||
p->set_material_usage(type, p->get_material_usage(type) + 1);
|
p->set_material_usage(type, p->get_material_usage(type) + 1);
|
||||||
@@ -98,7 +96,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
} else if ((primary_identifier & 0xFFFF0000) == 0x030F0000) { // AddSlot
|
} else if ((primary_identifier & 0xFFFF0000) == 0x030F0000) { // AddSlot
|
||||||
auto& armor = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::ARMOR)];
|
auto& armor = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::ARMOR)];
|
||||||
if (armor.data.data1[5] >= 4) {
|
if (armor.data.data1[5] >= 4) {
|
||||||
throw runtime_error("armor already at maximum slot count");
|
throw std::runtime_error("armor already at maximum slot count");
|
||||||
}
|
}
|
||||||
armor.data.data1[5]++;
|
armor.data.data1[5]++;
|
||||||
|
|
||||||
@@ -137,10 +135,10 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
if (evolution_number < 4) {
|
if (evolution_number < 4) {
|
||||||
switch (item.data.data1[2]) {
|
switch (item.data.data1[2]) {
|
||||||
case 0x00: // Cell of MAG 502
|
case 0x00: // Cell of MAG 502
|
||||||
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21;
|
mag.data.data1[1] = (player->disp.visual.sh.section_id & 1) ? 0x1D : 0x21;
|
||||||
break;
|
break;
|
||||||
case 0x01: // Cell of MAG 213
|
case 0x01: // Cell of MAG 213
|
||||||
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x27 : 0x22;
|
mag.data.data1[1] = (player->disp.visual.sh.section_id & 1) ? 0x27 : 0x22;
|
||||||
break;
|
break;
|
||||||
case 0x02: // Parts of RoboChao
|
case 0x02: // Parts of RoboChao
|
||||||
mag.data.data1[1] = 0x28;
|
mag.data.data1[1] = 0x28;
|
||||||
@@ -155,7 +153,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
mag.data.data1[1] = 0x2B;
|
mag.data.data1[1] = 0x2B;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid mag cell used");
|
throw std::runtime_error("invalid mag cell used");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +166,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
sum += table.first[z].probability;
|
sum += table.first[z].probability;
|
||||||
}
|
}
|
||||||
if (sum == 0) {
|
if (sum == 0) {
|
||||||
throw runtime_error("no unwrap results available for event");
|
throw std::runtime_error("no unwrap results available for event");
|
||||||
}
|
}
|
||||||
// TODO: It seems that on non-BB, clients don't synchronize this at all, so they could end up thinking the
|
// TODO: It seems that on non-BB, clients don't synchronize this at all, so they could end up thinking the
|
||||||
// unwrapped item is something completely different. (They don't even use a fixed random seed, like for rares; they
|
// unwrapped item is something completely different. (They don't even use a fixed random seed, like for rares; they
|
||||||
@@ -217,31 +215,31 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
try {
|
try {
|
||||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||||
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
|
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
|
||||||
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.char_class) {
|
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.sh.char_class) {
|
||||||
throw runtime_error("item combination requires specific char_class");
|
throw std::runtime_error("item combination requires specific char_class");
|
||||||
}
|
}
|
||||||
if (combo.mag_level != 0xFF) {
|
if (combo.mag_level != 0xFF) {
|
||||||
if (inv_item.data.data1[0] != 2) {
|
if (inv_item.data.data1[0] != 2) {
|
||||||
throw runtime_error("item combination applies with mag level requirement, but equipped item is not a mag");
|
throw std::runtime_error("item combination applies with mag level requirement, but equipped item is not a mag");
|
||||||
}
|
}
|
||||||
if (inv_item.data.compute_mag_level() < combo.mag_level) {
|
if (inv_item.data.compute_mag_level() < combo.mag_level) {
|
||||||
throw runtime_error("item combination applies with mag level requirement, but equipped mag level is too low");
|
throw std::runtime_error("item combination applies with mag level requirement, but equipped mag level is too low");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (combo.grind != 0xFF) {
|
if (combo.grind != 0xFF) {
|
||||||
if (inv_item.data.data1[0] != 0) {
|
if (inv_item.data.data1[0] != 0) {
|
||||||
throw runtime_error("item combination applies with grind requirement, but equipped item is not a weapon");
|
throw std::runtime_error("item combination applies with grind requirement, but equipped item is not a weapon");
|
||||||
}
|
}
|
||||||
if (inv_item.data.data1[3] < combo.grind) {
|
if (inv_item.data.data1[3] < combo.grind) {
|
||||||
throw runtime_error("item combination applies with grind requirement, but equipped weapon grind is too low");
|
throw std::runtime_error("item combination applies with grind requirement, but equipped weapon grind is too low");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (combo.level != 0xFF && player->disp.stats.level + 1 < combo.level) {
|
if (combo.level != 0xFF && player->disp.stats.level + 1 < combo.level) {
|
||||||
throw runtime_error("item combination applies with level requirement, but player level is too low");
|
throw std::runtime_error("item combination applies with level requirement, but player level is too low");
|
||||||
}
|
}
|
||||||
// If we get here, then the combo applies
|
// If we get here, then the combo applies
|
||||||
if (combo_applied) {
|
if (combo_applied) {
|
||||||
throw runtime_error("multiple combinations apply");
|
throw std::runtime_error("multiple combinations apply");
|
||||||
}
|
}
|
||||||
combo_applied = true;
|
combo_applied = true;
|
||||||
|
|
||||||
@@ -254,7 +252,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
inv_item.data.data1[4] = 0; // Flags + special
|
inv_item.data.data1[4] = 0; // Flags + special
|
||||||
}
|
}
|
||||||
inv_item.flags &= (~8); // Unequip it
|
inv_item.flags &= (~8); // Unequip it
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,13 +267,13 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
|||||||
void apply_mag_feed_result(
|
void apply_mag_feed_result(
|
||||||
ItemData& mag_item,
|
ItemData& mag_item,
|
||||||
const ItemData& fed_item,
|
const ItemData& fed_item,
|
||||||
shared_ptr<const ItemParameterTable> item_parameter_table,
|
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||||
shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
||||||
uint8_t char_class,
|
uint8_t char_class,
|
||||||
uint8_t section_id,
|
uint8_t section_id,
|
||||||
bool version_has_rare_mags) {
|
bool version_has_rare_mags) {
|
||||||
|
|
||||||
static const unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
static const std::unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
||||||
{0x03000000, 0}, // Monomate
|
{0x03000000, 0}, // Monomate
|
||||||
{0x03000100, 1}, // Dimate
|
{0x03000100, 1}, // Dimate
|
||||||
{0x03000200, 2}, // Trimate
|
{0x03000200, 2}, // Trimate
|
||||||
@@ -298,7 +296,7 @@ void apply_mag_feed_result(
|
|||||||
if ((delta > 0) || ((delta < 0) && (-delta < existing_stat))) {
|
if ((delta > 0) || ((delta < 0) && (-delta < existing_stat))) {
|
||||||
uint16_t level = data.compute_mag_level();
|
uint16_t level = data.compute_mag_level();
|
||||||
if (level > 200) {
|
if (level > 200) {
|
||||||
throw runtime_error("mag level is too high");
|
throw std::runtime_error("mag level is too high");
|
||||||
}
|
}
|
||||||
if ((level == 200) && ((99 - existing_stat) < delta)) {
|
if ((level == 200) && ((99 - existing_stat) < delta)) {
|
||||||
delta = 99 - existing_stat;
|
delta = 99 - existing_stat;
|
||||||
@@ -311,8 +309,8 @@ void apply_mag_feed_result(
|
|||||||
update_stat(mag_item, 3, feed_result.pow);
|
update_stat(mag_item, 3, feed_result.pow);
|
||||||
update_stat(mag_item, 4, feed_result.dex);
|
update_stat(mag_item, 4, feed_result.dex);
|
||||||
update_stat(mag_item, 5, feed_result.mind);
|
update_stat(mag_item, 5, feed_result.mind);
|
||||||
mag_item.data2[0] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[0]) + feed_result.synchro, 0, 120);
|
mag_item.data2[0] = std::clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[0]) + feed_result.synchro, 0, 120);
|
||||||
mag_item.data2[1] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[1]) + feed_result.iq, 0, 200);
|
mag_item.data2[1] = std::clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[1]) + feed_result.iq, 0, 200);
|
||||||
|
|
||||||
uint8_t mag_level = mag_item.compute_mag_level();
|
uint8_t mag_level = mag_item.compute_mag_level();
|
||||||
mag_item.data1[2] = mag_level;
|
mag_item.data1[2] = mag_level;
|
||||||
@@ -347,7 +345,7 @@ void apply_mag_feed_result(
|
|||||||
mag_item.data1[1] = 0x19; // Vritra
|
mag_item.data1[1] = 0x19; // Vritra
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid character class");
|
throw std::runtime_error("invalid character class");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +399,7 @@ void apply_mag_feed_result(
|
|||||||
} else if (is_ranger) {
|
} else if (is_ranger) {
|
||||||
table_index += 6;
|
table_index += 6;
|
||||||
} else if (!is_hunter) {
|
} else if (!is_hunter) {
|
||||||
throw logic_error("char class is not any of the top-level classes");
|
throw std::logic_error("char class is not any of the top-level classes");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The original code checks the class (hunter/ranger/force) again here, and goes into 3 branches that
|
// Note: The original code checks the class (hunter/ranger/force) again here, and goes into 3 branches that
|
||||||
@@ -433,7 +431,7 @@ void apply_mag_feed_result(
|
|||||||
bool is_ranger = char_class_is_ranger(char_class);
|
bool is_ranger = char_class_is_ranger(char_class);
|
||||||
bool is_force = char_class_is_force(char_class);
|
bool is_force = char_class_is_force(char_class);
|
||||||
if (is_hunter + is_ranger + is_force != 1) {
|
if (is_hunter + is_ranger + is_force != 1) {
|
||||||
throw logic_error("char class is not exactly one of the top-level classes");
|
throw std::logic_error("char class is not exactly one of the top-level classes");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_hunter) {
|
if (is_hunter) {
|
||||||
@@ -492,7 +490,7 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
|||||||
player->inventory.items[fed_item_index].data,
|
player->inventory.items[fed_item_index].data,
|
||||||
s->item_parameter_table(c->version()),
|
s->item_parameter_table(c->version()),
|
||||||
s->mag_evolution_table(c->version()),
|
s->mag_evolution_table(c->version()),
|
||||||
player->disp.visual.char_class,
|
player->disp.visual.sh.char_class,
|
||||||
player->disp.visual.section_id,
|
player->disp.visual.sh.section_id,
|
||||||
!is_v1_or_v2(c->version()));
|
!is_v1_or_v2(c->version()));
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-9
@@ -7,8 +7,6 @@
|
|||||||
#include "CommonFileFormats.hh"
|
#include "CommonFileFormats.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
||||||
stats.level = 0;
|
stats.level = 0;
|
||||||
stats.exp = 0;
|
stats.exp = 0;
|
||||||
@@ -82,7 +80,7 @@ const LevelStatsDelta& JSONLevelTable::stats_delta_for_level(uint8_t char_class,
|
|||||||
return this->level_deltas.at(char_class).at(level);
|
return this->level_deltas.at(char_class).at(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
LevelTableV2::LevelTableV2(const string& data) {
|
LevelTableV2::LevelTableV2(const std::string& data) {
|
||||||
struct Root {
|
struct Root {
|
||||||
// The overall format of this file on V2 has much more data than we actually use. This table is sorted by the
|
// The overall format of this file on V2 has much more data than we actually use. This table is sorted by the
|
||||||
// offset in the PlayerTable.prs file; note that the offset fields in this structure do not match that order.
|
// offset in the PlayerTable.prs file; note that the offset fields in this structure do not match that order.
|
||||||
@@ -165,7 +163,7 @@ size_t LevelTableV3::num_char_classes() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) const {
|
const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) const {
|
||||||
static const array<CharacterStats, 12> data = {
|
static const std::array<CharacterStats, 12> data = {
|
||||||
// ATP MST EVP HP DFP ATA LCK
|
// ATP MST EVP HP DFP ATA LCK
|
||||||
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
||||||
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
|
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
|
||||||
@@ -183,7 +181,7 @@ const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) con
|
|||||||
return data.at(char_class);
|
return data.at(char_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const array<PlayerStats, 12> max_stats_v3_v4 = {
|
static const std::array<PlayerStats, 12> max_stats_v3_v4 = {
|
||||||
// ATP MST EVP HP DFP ATA LCK ESP PRX PRY L E M
|
// ATP MST EVP HP DFP ATA LCK ESP PRX PRY L E M
|
||||||
PlayerStats{{0x056B, 0x02DC, 0x02F4, 0x0265, 0x0243, 0x054B, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
PlayerStats{{0x056B, 0x02DC, 0x02F4, 0x0265, 0x0243, 0x054B, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
||||||
PlayerStats{{0x04CB, 0x0499, 0x032B, 0x0254, 0x024D, 0x056C, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
PlayerStats{{0x04CB, 0x0499, 0x032B, 0x0254, 0x024D, 0x056C, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
||||||
@@ -208,7 +206,7 @@ const LevelStatsDelta& LevelTableV3::stats_delta_for_level(uint8_t char_class, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const string& data) {
|
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const std::string& data) {
|
||||||
// The V3 format is very simple:
|
// The V3 format is very simple:
|
||||||
// root:
|
// root:
|
||||||
// u32 offset:
|
// u32 offset:
|
||||||
@@ -225,11 +223,11 @@ void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& delt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LevelTableGC::LevelTableGC(const string& data) {
|
LevelTableGC::LevelTableGC(const std::string& data) {
|
||||||
parse_level_deltas_t<true>(this->level_deltas, data);
|
parse_level_deltas_t<true>(this->level_deltas, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
LevelTableXB::LevelTableXB(const string& data) {
|
LevelTableXB::LevelTableXB(const std::string& data) {
|
||||||
parse_level_deltas_t<false>(this->level_deltas, data);
|
parse_level_deltas_t<false>(this->level_deltas, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +272,7 @@ std::string LevelTable::serialize_binary_v4() const {
|
|||||||
return rel.finalize(root_offset);
|
return rel.finalize(root_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
LevelTableV4::LevelTableV4(const string& data) {
|
LevelTableV4::LevelTableV4(const std::string& data) {
|
||||||
|
|
||||||
phosg::StringReader r(data);
|
phosg::StringReader r(data);
|
||||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||||
|
|||||||
+53
-55
@@ -10,8 +10,6 @@
|
|||||||
#include "ServerState.hh"
|
#include "ServerState.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
|
bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
|
||||||
return this->flags & (1 << client_id);
|
return this->flags & (1 << client_id);
|
||||||
}
|
}
|
||||||
@@ -24,17 +22,17 @@ bool Lobby::FloorItemManager::exists(uint32_t item_id) const {
|
|||||||
return this->items.count(item_id);
|
return this->items.count(item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) const {
|
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) const {
|
||||||
return this->items.at(item_id);
|
return this->items.at(item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::FloorItemManager::add(
|
void Lobby::FloorItemManager::add(
|
||||||
const ItemData& item,
|
const ItemData& item,
|
||||||
const VectorXZF& pos,
|
const VectorXZF& pos,
|
||||||
shared_ptr<const MapState::ObjectState> from_obj,
|
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||||
shared_ptr<const MapState::EnemyState> from_ene,
|
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||||
uint16_t flags) {
|
uint16_t flags) {
|
||||||
auto fi = make_shared<FloorItem>();
|
auto fi = std::make_shared<FloorItem>();
|
||||||
fi->data = item;
|
fi->data = item;
|
||||||
fi->pos = pos;
|
fi->pos = pos;
|
||||||
fi->drop_number = this->next_drop_number++;
|
fi->drop_number = this->next_drop_number++;
|
||||||
@@ -44,14 +42,14 @@ void Lobby::FloorItemManager::add(
|
|||||||
this->add(fi);
|
this->add(fi);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
void Lobby::FloorItemManager::add(std::shared_ptr<Lobby::FloorItem> fi) {
|
||||||
if (fi->flags == 0) {
|
if (fi->flags == 0) {
|
||||||
throw logic_error("floor item is not visible to any player");
|
throw std::logic_error("floor item is not visible to any player");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto emplace_ret = this->items.emplace(fi->data.id, fi);
|
auto emplace_ret = this->items.emplace(fi->data.id, fi);
|
||||||
if (!emplace_ret.second) {
|
if (!emplace_ret.second) {
|
||||||
throw runtime_error("floor item already exists with the same ID");
|
throw std::runtime_error("floor item already exists with the same ID");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < 12; z++) {
|
for (size_t z = 0; z < 12; z++) {
|
||||||
if (fi->visible_to_client(z)) {
|
if (fi->visible_to_client(z)) {
|
||||||
@@ -65,15 +63,15 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
|||||||
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
|
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
|
||||||
auto item_it = this->items.find(item_id);
|
auto item_it = this->items.find(item_id);
|
||||||
if (item_it == this->items.end()) {
|
if (item_it == this->items.end()) {
|
||||||
throw out_of_range("item not present");
|
throw std::out_of_range("item not present");
|
||||||
}
|
}
|
||||||
auto fi = item_it->second;
|
auto fi = item_it->second;
|
||||||
if ((client_id != 0xFF) && !fi->visible_to_client(client_id)) {
|
if ((client_id != 0xFF) && !fi->visible_to_client(client_id)) {
|
||||||
throw runtime_error("client does not have access to item");
|
throw std::runtime_error("client does not have access to item");
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < 12; z++) {
|
for (size_t z = 0; z < 12; z++) {
|
||||||
if (fi->visible_to_client(z) && !this->queue_for_client[z].erase(fi->drop_number)) {
|
if (fi->visible_to_client(z) && !this->queue_for_client[z].erase(fi->drop_number)) {
|
||||||
throw logic_error("item queue for client is inconsistent");
|
throw std::logic_error("item queue for client is inconsistent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->items.erase(item_it);
|
this->items.erase(item_it);
|
||||||
@@ -83,7 +81,7 @@ std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::evict() {
|
std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::evict() {
|
||||||
unordered_set<shared_ptr<FloorItem>> ret;
|
std::unordered_set<std::shared_ptr<FloorItem>> ret;
|
||||||
for (size_t z = 0; z < 12; z++) {
|
for (size_t z = 0; z < 12; z++) {
|
||||||
while (this->queue_for_client[z].size() > 48) {
|
while (this->queue_for_client[z].size() > 48) {
|
||||||
ret.emplace(this->remove(this->queue_for_client[z].begin()->second->data.id, 0xFF));
|
ret.emplace(this->remove(this->queue_for_client[z].begin()->second->data.id, 0xFF));
|
||||||
@@ -94,7 +92,7 @@ std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::e
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask) {
|
void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask) {
|
||||||
unordered_set<uint32_t> item_ids_to_delete;
|
std::unordered_set<uint32_t> item_ids_to_delete;
|
||||||
for (const auto& it : this->items) {
|
for (const auto& it : this->items) {
|
||||||
if ((it.second->flags & remaining_clients_mask) == 0) {
|
if ((it.second->flags & remaining_clients_mask) == 0) {
|
||||||
item_ids_to_delete.emplace(it.first);
|
item_ids_to_delete.emplace(it.first);
|
||||||
@@ -107,7 +105,7 @@ void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::FloorItemManager::clear_private() {
|
void Lobby::FloorItemManager::clear_private() {
|
||||||
unordered_set<uint32_t> item_ids_to_delete;
|
std::unordered_set<uint32_t> item_ids_to_delete;
|
||||||
for (const auto& it : this->items) {
|
for (const auto& it : this->items) {
|
||||||
if ((it.second->flags & 0x00F) != 0x00F) {
|
if ((it.second->flags & 0x00F) != 0x00F) {
|
||||||
item_ids_to_delete.emplace(it.first);
|
item_ids_to_delete.emplace(it.first);
|
||||||
@@ -130,7 +128,7 @@ void Lobby::FloorItemManager::clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
||||||
::map<uint32_t, shared_ptr<FloorItem>> old_items;
|
std::map<uint32_t, std::shared_ptr<FloorItem>> old_items;
|
||||||
old_items.swap(this->items);
|
old_items.swap(this->items);
|
||||||
for (auto& queue : this->queue_for_client) {
|
for (auto& queue : this->queue_for_client) {
|
||||||
queue.clear();
|
queue.clear();
|
||||||
@@ -142,13 +140,13 @@ uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
|||||||
return next_item_id;
|
return next_item_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
Lobby::Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||||
: server_state(s),
|
: server_state(s),
|
||||||
log(std::format("[{}:{:X}] ", is_game ? "Game" : "Lobby", id), lobby_log.min_level),
|
log(std::format("[{}:{:X}] ", is_game ? "Game" : "Lobby", id), lobby_log.min_level),
|
||||||
creation_time(phosg::now()),
|
creation_time(phosg::now()),
|
||||||
lobby_id(id),
|
lobby_id(id),
|
||||||
random_seed(phosg::random_object<uint32_t>()),
|
random_seed(phosg::random_object<uint32_t>()),
|
||||||
rand_crypt(make_shared<DisabledRandomGenerator>()),
|
rand_crypt(std::make_shared<DisabledRandomGenerator>()),
|
||||||
drop_mode(ServerDropMode::CLIENT),
|
drop_mode(ServerDropMode::CLIENT),
|
||||||
idle_timeout_timer(*s->io_context) {
|
idle_timeout_timer(*s->io_context) {
|
||||||
this->log.info_f("Created");
|
this->log.info_f("Created");
|
||||||
@@ -178,17 +176,17 @@ uint8_t Lobby::area_for_floor(Version version, uint8_t floor) const {
|
|||||||
return sdt->default_floor_to_area(this->episode).at(floor);
|
return sdt->default_floor_to_area(this->episode).at(floor);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<ServerState> Lobby::require_server_state() const {
|
std::shared_ptr<ServerState> Lobby::require_server_state() const {
|
||||||
auto s = this->server_state.lock();
|
auto s = this->server_state.lock();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
throw logic_error("server is deleted");
|
throw std::logic_error("server is deleted");
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
|
std::shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
|
||||||
if (!this->challenge_params) {
|
if (!this->challenge_params) {
|
||||||
throw runtime_error("challenge params are missing");
|
throw std::runtime_error("challenge params are missing");
|
||||||
}
|
}
|
||||||
return this->challenge_params;
|
return this->challenge_params;
|
||||||
}
|
}
|
||||||
@@ -206,17 +204,17 @@ void Lobby::create_item_creator(Version logic_version) {
|
|||||||
logic_version = leader_c ? leader_c->version() : Version::BB_V4;
|
logic_version = leader_c ? leader_c->version() : Version::BB_V4;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<RandomGenerator> rand_crypt;
|
std::shared_ptr<RandomGenerator> rand_crypt;
|
||||||
if (s->use_psov2_rand_crypt) {
|
if (s->use_psov2_rand_crypt) {
|
||||||
rand_crypt = make_shared<PSOV2Encryption>(this->rand_crypt->seed());
|
rand_crypt = std::make_shared<PSOV2Encryption>(this->rand_crypt->seed());
|
||||||
} else {
|
} else {
|
||||||
rand_crypt = make_shared<MT19937Generator>(this->rand_crypt->seed());
|
rand_crypt = std::make_shared<MT19937Generator>(this->rand_crypt->seed());
|
||||||
}
|
}
|
||||||
uint8_t effective_section_id = this->effective_section_id();
|
uint8_t effective_section_id = this->effective_section_id();
|
||||||
if (effective_section_id >= 10) {
|
if (effective_section_id >= 10) {
|
||||||
effective_section_id = 0x00;
|
effective_section_id = 0x00;
|
||||||
}
|
}
|
||||||
this->item_creator = make_shared<ItemCreator>(
|
this->item_creator = std::make_shared<ItemCreator>(
|
||||||
s->common_item_set(logic_version, this->quest),
|
s->common_item_set(logic_version, this->quest),
|
||||||
s->rare_item_set(logic_version, this->quest),
|
s->rare_item_set(logic_version, this->quest),
|
||||||
s->armor_random_set,
|
s->armor_random_set,
|
||||||
@@ -249,7 +247,7 @@ uint8_t Lobby::effective_section_id() const {
|
|||||||
}
|
}
|
||||||
auto leader = this->clients.at(this->leader_id);
|
auto leader = this->clients.at(this->leader_id);
|
||||||
if (leader) {
|
if (leader) {
|
||||||
return leader->character_file()->disp.visual.section_id;
|
return leader->character_file()->disp.visual.sh.section_id;
|
||||||
}
|
}
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
@@ -279,13 +277,13 @@ void Lobby::load_maps() {
|
|||||||
if (this->quest) {
|
if (this->quest) {
|
||||||
this->log.info_f("Loading quest supermap");
|
this->log.info_f("Loading quest supermap");
|
||||||
auto supermap = this->quest->get_supermap(this->random_seed);
|
auto supermap = this->quest->get_supermap(this->random_seed);
|
||||||
this->map_state = make_shared<MapState>(
|
this->map_state = std::make_shared<MapState>(
|
||||||
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermap);
|
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermap);
|
||||||
} else {
|
} else {
|
||||||
this->log.info_f("Loading free play supermaps");
|
this->log.info_f("Loading free play supermaps");
|
||||||
auto s = this->require_server_state();
|
auto s = this->require_server_state();
|
||||||
auto supermaps = s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations);
|
auto supermaps = s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations);
|
||||||
this->map_state = make_shared<MapState>(
|
this->map_state = std::make_shared<MapState>(
|
||||||
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermaps);
|
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +326,7 @@ void Lobby::create_ep3_server() {
|
|||||||
} else {
|
} else {
|
||||||
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
|
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
|
||||||
}
|
}
|
||||||
this->ep3_server = make_shared<Episode3::Server>(this->shared_from_this(), std::move(options));
|
this->ep3_server = std::make_shared<Episode3::Server>(this->shared_from_this(), std::move(options));
|
||||||
this->ep3_server->init();
|
this->ep3_server->init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,9 +384,9 @@ bool Lobby::any_v1_clients_present() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
void Lobby::add_client(std::shared_ptr<Client> c, ssize_t required_client_id) {
|
||||||
if (!c->login) {
|
if (!c->login) {
|
||||||
throw runtime_error("client is not logged in");
|
throw std::runtime_error("client is not logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t index;
|
ssize_t index;
|
||||||
@@ -396,7 +394,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
|||||||
|
|
||||||
if (required_client_id >= 0) {
|
if (required_client_id >= 0) {
|
||||||
if (this->clients.at(required_client_id).get()) {
|
if (this->clients.at(required_client_id).get()) {
|
||||||
throw out_of_range("required slot is in use");
|
throw std::out_of_range("required slot is in use");
|
||||||
}
|
}
|
||||||
this->clients[required_client_id] = c;
|
this->clients[required_client_id] = c;
|
||||||
index = required_client_id;
|
index = required_client_id;
|
||||||
@@ -409,7 +407,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index < min_client_id) {
|
if (index < min_client_id) {
|
||||||
throw out_of_range("no space left in lobby");
|
throw std::out_of_range("no space left in lobby");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (index = min_client_id; index < this->max_clients; index++) {
|
for (index = min_client_id; index < this->max_clients; index++) {
|
||||||
@@ -419,7 +417,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index >= this->max_clients) {
|
if (index >= this->max_clients) {
|
||||||
throw out_of_range("no space left in lobby");
|
throw std::out_of_range("no space left in lobby");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,11 +464,11 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
|||||||
PlayerLobbyDataDCGC lobby_data;
|
PlayerLobbyDataDCGC lobby_data;
|
||||||
lobby_data.player_tag = 0x00010000;
|
lobby_data.player_tag = 0x00010000;
|
||||||
lobby_data.guild_card_number = c->login->account->account_id;
|
lobby_data.guild_card_number = c->login->account->account_id;
|
||||||
lobby_data.name.encode(p->disp.name.decode(c->language()), c->language());
|
lobby_data.name.encode(p->disp.visual.name.decode(c->language()), c->language());
|
||||||
this->battle_record->add_player(
|
this->battle_record->add_player(
|
||||||
lobby_data,
|
lobby_data,
|
||||||
p->inventory,
|
p->inventory,
|
||||||
p->disp.to_dcpcv3<false>(c->language(), c->language()),
|
p->disp.to_v123<false>(c->language(), c->language()),
|
||||||
c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0);
|
c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,10 +490,10 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::remove_client(shared_ptr<Client> c) {
|
void Lobby::remove_client(std::shared_ptr<Client> c) {
|
||||||
if (this->clients.at(c->lobby_client_id) != c) {
|
if (this->clients.at(c->lobby_client_id) != c) {
|
||||||
auto other_c = this->clients[c->lobby_client_id].get();
|
auto other_c = this->clients[c->lobby_client_id].get();
|
||||||
throw logic_error(std::format(
|
throw std::logic_error(std::format(
|
||||||
"client\'s lobby client id ({}) does not match client list ({})",
|
"client\'s lobby client id ({}) does not match client list ({})",
|
||||||
c->lobby_client_id,
|
c->lobby_client_id,
|
||||||
static_cast<uint8_t>(other_c ? other_c->lobby_client_id : 0xFF)));
|
static_cast<uint8_t>(other_c ? other_c->lobby_client_id : 0xFF)));
|
||||||
@@ -568,20 +566,20 @@ void Lobby::remove_client(shared_ptr<Client> c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::move_client_to_lobby(shared_ptr<Lobby> dest_lobby, shared_ptr<Client> c, ssize_t required_client_id) {
|
void Lobby::move_client_to_lobby(std::shared_ptr<Lobby> dest_lobby, std::shared_ptr<Client> c, ssize_t required_client_id) {
|
||||||
if (dest_lobby.get() == this) {
|
if (dest_lobby.get() == this) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (required_client_id >= 0) {
|
if (required_client_id >= 0) {
|
||||||
if (dest_lobby->clients.at(required_client_id)) {
|
if (dest_lobby->clients.at(required_client_id)) {
|
||||||
throw out_of_range("required slot is in use");
|
throw std::out_of_range("required slot is in use");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0;
|
ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0;
|
||||||
size_t available_slots = dest_lobby->max_clients - min_client_id;
|
size_t available_slots = dest_lobby->max_clients - min_client_id;
|
||||||
if (dest_lobby->count_clients() >= available_slots) {
|
if (dest_lobby->count_clients() >= available_slots) {
|
||||||
throw out_of_range("no space left in lobby");
|
throw std::out_of_range("no space left in lobby");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,7 +587,7 @@ void Lobby::move_client_to_lobby(shared_ptr<Lobby> dest_lobby, shared_ptr<Client
|
|||||||
dest_lobby->add_client(c, required_client_id);
|
dest_lobby->add_client(c, required_client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t account_id) {
|
std::shared_ptr<Client> Lobby::find_client(const std::string* identifier, uint64_t account_id) {
|
||||||
for (size_t x = 0; x < this->max_clients; x++) {
|
for (size_t x = 0; x < this->max_clients; x++) {
|
||||||
auto lc = this->clients[x];
|
auto lc = this->clients[x];
|
||||||
if (!lc) {
|
if (!lc) {
|
||||||
@@ -598,12 +596,12 @@ shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t account
|
|||||||
if (account_id && lc->login && (lc->login->account->account_id == account_id)) {
|
if (account_id && lc->login && (lc->login->account->account_id == account_id)) {
|
||||||
return lc;
|
return lc;
|
||||||
}
|
}
|
||||||
if (identifier && (lc->character_file()->disp.name.eq(*identifier, lc->language()))) {
|
if (identifier && (lc->character_file()->disp.visual.name.eq(*identifier, lc->language()))) {
|
||||||
return lc;
|
return lc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw out_of_range("client not found");
|
throw std::out_of_range("client not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const {
|
Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const {
|
||||||
@@ -667,7 +665,7 @@ bool Lobby::item_exists(uint8_t floor, uint32_t item_id) const {
|
|||||||
return this->floor_item_managers.at(floor).exists(item_id);
|
return this->floor_item_managers.at(floor).exists(item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) const {
|
std::shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) const {
|
||||||
return this->floor_item_managers.at(floor).find(item_id);
|
return this->floor_item_managers.at(floor).find(item_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +681,7 @@ void Lobby::add_item(
|
|||||||
this->evict_items_from_floor(floor);
|
this->evict_items_from_floor(floor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::add_item(uint8_t floor, shared_ptr<FloorItem> fi) {
|
void Lobby::add_item(uint8_t floor, std::shared_ptr<FloorItem> fi) {
|
||||||
auto& m = this->floor_item_managers.at(floor);
|
auto& m = this->floor_item_managers.at(floor);
|
||||||
m.add(fi);
|
m.add(fi);
|
||||||
this->evict_items_from_floor(floor);
|
this->evict_items_from_floor(floor);
|
||||||
@@ -705,7 +703,7 @@ void Lobby::evict_items_from_floor(uint8_t floor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Lobby::FloorItem> Lobby::remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id) {
|
std::shared_ptr<Lobby::FloorItem> Lobby::remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id) {
|
||||||
return this->floor_item_managers.at(floor).remove(item_id, requesting_client_id);
|
return this->floor_item_managers.at(floor).remove(item_id, requesting_client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +724,7 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consume_ids) {
|
void Lobby::assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids) {
|
||||||
auto p = c->character_file();
|
auto p = c->character_file();
|
||||||
uint32_t orig_next_item_id = this->next_item_id_for_client.at(c->lobby_client_id);
|
uint32_t orig_next_item_id = this->next_item_id_for_client.at(c->lobby_client_id);
|
||||||
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
||||||
@@ -751,8 +749,8 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consum
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_account_id() const {
|
std::unordered_map<uint32_t, std::shared_ptr<Client>> Lobby::clients_by_account_id() const {
|
||||||
unordered_map<uint32_t, shared_ptr<Client>> ret;
|
std::unordered_map<uint32_t, std::shared_ptr<Client>> ret;
|
||||||
for (auto c : this->clients) {
|
for (auto c : this->clients) {
|
||||||
if (c && c->login) {
|
if (c && c->login) {
|
||||||
ret.emplace(c->login->account->account_id, c);
|
ret.emplace(c->login->account->account_id, c);
|
||||||
@@ -764,7 +762,7 @@ unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_account_id() const
|
|||||||
QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||||
size_t num_players = this->count_clients();
|
size_t num_players = this->count_clients();
|
||||||
bool v1_present = this->any_v1_clients_present();
|
bool v1_present = this->any_v1_clients_present();
|
||||||
return [this, num_players, v1_present](shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
return [this, num_players, v1_present](std::shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
||||||
bool is_enabled = true;
|
bool is_enabled = true;
|
||||||
for (const auto& lc : this->clients) {
|
for (const auto& lc : this->clients) {
|
||||||
auto this_sh = this->shared_from_this();
|
auto this_sh = this->shared_from_this();
|
||||||
@@ -779,7 +777,7 @@ QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<const Lobby>& b) {
|
bool Lobby::compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b) {
|
||||||
// Sort keys:
|
// Sort keys:
|
||||||
// 1. Priority class: has free space < empty (persistent) < full < non-joinable (in quest/battle)
|
// 1. Priority class: has free space < empty (persistent) < full < non-joinable (in quest/battle)
|
||||||
// 2. Password: public < locked
|
// 2. Password: public < locked
|
||||||
@@ -787,7 +785,7 @@ bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<co
|
|||||||
// 4. Episode: 1 < 2 < 4
|
// 4. Episode: 1 < 2 < 4
|
||||||
// 5. Difficulty: Normal < Hard < Very Hard < Ultimate
|
// 5. Difficulty: Normal < Hard < Very Hard < Ultimate
|
||||||
// 6. Game name
|
// 6. Game name
|
||||||
static auto get_priority = +[](const shared_ptr<const Lobby>& l) -> size_t {
|
static auto get_priority = +[](const std::shared_ptr<const Lobby>& l) -> size_t {
|
||||||
if (l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS) ||
|
if (l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS) ||
|
||||||
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) ||
|
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) ||
|
||||||
l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
|
l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
|
||||||
@@ -869,6 +867,6 @@ const char* phosg::name_for_enum<Lobby::JoinError>(Lobby::JoinError value) {
|
|||||||
case Lobby::JoinError::NO_ACCESS_TO_QUEST:
|
case Lobby::JoinError::NO_ACCESS_TO_QUEST:
|
||||||
return "NO_ACCESS_TO_QUEST";
|
return "NO_ACCESS_TO_QUEST";
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid drop mode");
|
throw std::runtime_error("invalid drop mode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
|||||||
struct FloorItemManager {
|
struct FloorItemManager {
|
||||||
phosg::PrefixedLogger log;
|
phosg::PrefixedLogger log;
|
||||||
uint64_t next_drop_number;
|
uint64_t next_drop_number;
|
||||||
// It's important that this is a map and not an unordered_map. See the comment in send_game_item_state for details.
|
// It's important that this is a map and not an std::unordered_map. See the comment in send_game_item_state for details.
|
||||||
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
|
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
|
||||||
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
|
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
phosg::PrefixedLogger channel_exceptions_log("[Channel] ", phosg::LogLevel::L_USE_DEFAULT);
|
phosg::PrefixedLogger channel_exceptions_log("[Channel] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||||
phosg::PrefixedLogger client_functions_log("[ClientFunctionIndex] ", phosg::LogLevel::L_USE_DEFAULT);
|
phosg::PrefixedLogger client_functions_log("[ClientFunctionIndex] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||||
phosg::PrefixedLogger client_log("", phosg::LogLevel::L_USE_DEFAULT);
|
phosg::PrefixedLogger client_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||||
@@ -22,9 +20,9 @@ phosg::PrefixedLogger static_game_data_log("[StaticGameData] ", phosg::LogLevel:
|
|||||||
static void set_log_level_from_json(
|
static void set_log_level_from_json(
|
||||||
phosg::PrefixedLogger& log, const phosg::JSON& d, const char* json_key) {
|
phosg::PrefixedLogger& log, const phosg::JSON& d, const char* json_key) {
|
||||||
try {
|
try {
|
||||||
string name = phosg::toupper(d.at(json_key).as_string());
|
std::string name = phosg::toupper(d.at(json_key).as_string());
|
||||||
log.min_level = phosg::enum_for_name<phosg::LogLevel>(name);
|
log.min_level = phosg::enum_for_name<phosg::LogLevel>(name);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "CommonFileFormats.hh"
|
#include "CommonFileFormats.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
struct MotionReferenceTables {
|
struct MotionReferenceTables {
|
||||||
// It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and later,
|
// It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and later,
|
||||||
@@ -125,7 +123,7 @@ static uint8_t get_v1_mag_evolution_number(uint8_t data1_1) {
|
|||||||
/* 10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 4, 3, 3,
|
/* 10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 4, 3, 3,
|
||||||
/* 20 */ 3, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4};
|
/* 20 */ 3, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4};
|
||||||
if (data1_1 >= v1_evolution_number_table.size()) {
|
if (data1_1 >= v1_evolution_number_table.size()) {
|
||||||
throw runtime_error("invalid mag number");
|
throw std::runtime_error("invalid mag number");
|
||||||
}
|
}
|
||||||
return v1_evolution_number_table[data1_1];
|
return v1_evolution_number_table[data1_1];
|
||||||
}
|
}
|
||||||
@@ -196,7 +194,7 @@ public:
|
|||||||
|
|
||||||
virtual const VectorXYZTF& get_color_rgba(size_t index) const {
|
virtual const VectorXYZTF& get_color_rgba(size_t index) const {
|
||||||
if (index >= NumColors) {
|
if (index >= NumColors) {
|
||||||
throw runtime_error("invalid mag color index");
|
throw std::runtime_error("invalid mag color index");
|
||||||
}
|
}
|
||||||
return this->add_to_vector_cache<ColorEntry<BE>>(this->colors, this->root->color_table, index);
|
return this->add_to_vector_cache<ColorEntry<BE>>(this->colors, this->root->color_table, index);
|
||||||
}
|
}
|
||||||
@@ -241,29 +239,29 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
virtual const MotionReference& get_motion_reference(bool, size_t) const {
|
virtual const MotionReference& get_motion_reference(bool, size_t) const {
|
||||||
throw runtime_error("Mag tables not available on DC NTE");
|
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::pair<uint8_t, uint8_t> get_unknown_a2(size_t) const {
|
virtual std::pair<uint8_t, uint8_t> get_unknown_a2(size_t) const {
|
||||||
throw runtime_error("Mag tables not available on DC NTE");
|
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual size_t num_unknown_a3_entries() const {
|
virtual size_t num_unknown_a3_entries() const {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
virtual const UnknownA3Entry& get_unknown_a3(size_t) const {
|
virtual const UnknownA3Entry& get_unknown_a3(size_t) const {
|
||||||
throw runtime_error("Mag tables not available on DC NTE");
|
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual uint8_t get_unknown_a4(size_t) const {
|
virtual uint8_t get_unknown_a4(size_t) const {
|
||||||
throw runtime_error("Mag tables not available on DC NTE");
|
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual size_t num_colors() const {
|
virtual size_t num_colors() const {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
virtual const VectorXYZTF& get_color_rgba(size_t) const {
|
virtual const VectorXYZTF& get_color_rgba(size_t) const {
|
||||||
throw runtime_error("Mag tables not available on DC NTE");
|
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual uint8_t get_evolution_number(uint8_t data1_1) const {
|
virtual uint8_t get_evolution_number(uint8_t data1_1) const {
|
||||||
|
|||||||
+514
-551
File diff suppressed because it is too large
Load Diff
+330
-336
File diff suppressed because it is too large
Load Diff
+24
-5
@@ -456,7 +456,7 @@ public:
|
|||||||
return this->has_any_random_sections;
|
return this->has_any_random_sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the map file has no random sections, does nothing and returns a shared_ptr to this. If it has any random
|
// If the map file has no random sections, does nothing and returns a std::shared_ptr to this. If it has any random
|
||||||
// sections, returns a new map with all non-random sections copied verbatim, and random sections replaced with
|
// sections, returns a new map with all non-random sections copied verbatim, and random sections replaced with
|
||||||
// non-random sections according to the challenge mode enemy generation algorithm.
|
// non-random sections according to the challenge mode enemy generation algorithm.
|
||||||
std::shared_ptr<MapFile> materialize_random_sections(uint32_t random_seed);
|
std::shared_ptr<MapFile> materialize_random_sections(uint32_t random_seed);
|
||||||
@@ -766,10 +766,12 @@ public:
|
|||||||
std::shared_ptr<const SuperMap::Enemy> super_ene;
|
std::shared_ptr<const SuperMap::Enemy> super_ene;
|
||||||
enum Flag {
|
enum Flag {
|
||||||
LAST_HIT_MASK = 0x0003,
|
LAST_HIT_MASK = 0x0003,
|
||||||
EXP_GIVEN = 0x0004,
|
ITEM_DROPPED = 0x0004,
|
||||||
ITEM_DROPPED = 0x0008,
|
SHARED_EXP_GIVEN = 0x0008,
|
||||||
ALL_HITS_MASK_FIRST = 0x0010,
|
FULL_EXP_GIVEN_MASK_FIRST = 0x0010,
|
||||||
ALL_HITS_MASK = 0x00F0,
|
FULL_EXP_GIVEN_MASK = 0x00F0,
|
||||||
|
ALL_HITS_MASK_FIRST = 0x0100,
|
||||||
|
ALL_HITS_MASK = 0x0F00,
|
||||||
};
|
};
|
||||||
size_t e_id = 0;
|
size_t e_id = 0;
|
||||||
size_t set_id = 0;
|
size_t set_id = 0;
|
||||||
@@ -817,6 +819,23 @@ public:
|
|||||||
return this->super_ene->type;
|
return this->super_ene->type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inline bool should_give_shared_exp() {
|
||||||
|
if (this->server_flags & Flag::SHARED_EXP_GIVEN) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this->server_flags |= Flag::SHARED_EXP_GIVEN;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline bool should_give_full_exp_for_client_id(uint8_t client_id) {
|
||||||
|
uint16_t flag = (Flag::FULL_EXP_GIVEN_MASK_FIRST << client_id);
|
||||||
|
if (this->server_flags & flag) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this->server_flags |= flag;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
inline bool ever_hit_by_client_id(uint8_t client_id) const {
|
inline bool ever_hit_by_client_id(uint8_t client_id) const {
|
||||||
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
|
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-20
@@ -1,28 +1,13 @@
|
|||||||
#include "Menu.hh"
|
#include "Menu.hh"
|
||||||
|
|
||||||
using namespace std;
|
MenuItem::MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags)
|
||||||
|
: item_id(item_id), name(name), description(description), get_description(nullptr), flags(flags) {}
|
||||||
|
|
||||||
MenuItem::MenuItem(
|
MenuItem::MenuItem(
|
||||||
uint32_t item_id,
|
uint32_t item_id,
|
||||||
const string& name,
|
const std::string& name,
|
||||||
const string& description,
|
|
||||||
uint32_t flags)
|
|
||||||
: item_id(item_id),
|
|
||||||
name(name),
|
|
||||||
description(description),
|
|
||||||
get_description(nullptr),
|
|
||||||
flags(flags) {}
|
|
||||||
|
|
||||||
MenuItem::MenuItem(uint32_t item_id,
|
|
||||||
const string& name,
|
|
||||||
std::function<std::string()> get_description,
|
std::function<std::string()> get_description,
|
||||||
uint32_t flags)
|
uint32_t flags)
|
||||||
: item_id(item_id),
|
: item_id(item_id), name(name), description(), get_description(std::move(get_description)), flags(flags) {}
|
||||||
name(name),
|
|
||||||
description(),
|
|
||||||
get_description(std::move(get_description)),
|
|
||||||
flags(flags) {}
|
|
||||||
|
|
||||||
Menu::Menu(uint32_t menu_id, const std::string& name)
|
Menu::Menu(uint32_t menu_id, const std::string& name) : menu_id(menu_id), name(name) {}
|
||||||
: menu_id(menu_id),
|
|
||||||
name(name) {}
|
|
||||||
|
|||||||
@@ -17,18 +17,16 @@
|
|||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
using namespace std;
|
std::map<std::string, uint32_t> get_local_addresses() {
|
||||||
|
std::map<std::string, uint32_t> ret;
|
||||||
map<string, uint32_t> get_local_addresses() {
|
|
||||||
map<string, uint32_t> ret;
|
|
||||||
|
|
||||||
#ifndef PHOSG_WINDOWS
|
#ifndef PHOSG_WINDOWS
|
||||||
struct ifaddrs* ifa_raw;
|
struct ifaddrs* ifa_raw;
|
||||||
if (getifaddrs(&ifa_raw)) {
|
if (getifaddrs(&ifa_raw)) {
|
||||||
auto s = phosg::string_for_error(errno);
|
auto s = phosg::string_for_error(errno);
|
||||||
throw runtime_error(std::format("failed to get interface addresses: {}", s));
|
throw std::runtime_error(std::format("failed to get interface addresses: {}", s));
|
||||||
}
|
}
|
||||||
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
std::unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
||||||
|
|
||||||
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
|
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
|
||||||
if (!i->ifa_addr) {
|
if (!i->ifa_addr) {
|
||||||
@@ -56,7 +54,7 @@ map<string, uint32_t> get_local_addresses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result != NO_ERROR) {
|
if (result != NO_ERROR) {
|
||||||
throw runtime_error(std::format("GetAdaptersAddresses failed: {}", result));
|
throw std::runtime_error(std::format("GetAdaptersAddresses failed: {}", result));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IP_ADAPTER_ADDRESSES* adapter = adapters; adapter != nullptr; adapter = adapter->Next) {
|
for (IP_ADAPTER_ADDRESSES* adapter = adapters; adapter != nullptr; adapter = adapter->Next) {
|
||||||
@@ -92,7 +90,7 @@ bool is_local_address(const sockaddr_storage& daddr) {
|
|||||||
return is_local_address(ntohl(sin->sin_addr.s_addr));
|
return is_local_address(ntohl(sin->sin_addr.s_addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
string string_for_address(uint32_t address) {
|
std::string string_for_address(uint32_t address) {
|
||||||
return std::format("{}.{}.{}.{}",
|
return std::format("{}.{}.{}.{}",
|
||||||
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
|
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
|
||||||
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
|
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
|
||||||
|
|||||||
+7
-9
@@ -10,8 +10,6 @@
|
|||||||
#include "Compression.hh"
|
#include "Compression.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
pstring<TextEncoding::ASCII, 0x80> filename;
|
pstring<TextEncoding::ASCII, 0x80> filename;
|
||||||
le_uint32_t unknown_a1;
|
le_uint32_t unknown_a1;
|
||||||
@@ -24,7 +22,7 @@ struct Entry {
|
|||||||
|
|
||||||
static void decrypt_ppk_data(std::string& data, const std::string& filename, const std::string& password) {
|
static void decrypt_ppk_data(std::string& data, const std::string& filename, const std::string& password) {
|
||||||
if (password.size() > 0xFF) {
|
if (password.size() > 0xFF) {
|
||||||
throw runtime_error("password is too long");
|
throw std::runtime_error("password is too long");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t key[0x100];
|
uint8_t key[0x100];
|
||||||
@@ -47,27 +45,27 @@ std::unordered_map<std::string, std::string> decode_ppk_file(const std::string&
|
|||||||
|
|
||||||
uint32_t signature = r.get_u32b();
|
uint32_t signature = r.get_u32b();
|
||||||
if (signature != 0x50503130 && signature != 0x4D5A5000) { // 'PP10' or 'MZP\0'
|
if (signature != 0x50503130 && signature != 0x4D5A5000) { // 'PP10' or 'MZP\0'
|
||||||
throw runtime_error("file is not a ppk archive");
|
throw std::runtime_error("file is not a ppk archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
unordered_map<string, string> ret;
|
std::unordered_map<std::string, std::string> ret;
|
||||||
for (size_t offset = r.size() - 4; offset >= 4;) {
|
for (size_t offset = r.size() - 4; offset >= 4;) {
|
||||||
uint32_t size = r.pget_u32l(offset) ^ 0x12345678;
|
uint32_t size = r.pget_u32l(offset) ^ 0x12345678;
|
||||||
uint32_t entry_offset = offset - size;
|
uint32_t entry_offset = offset - size;
|
||||||
const auto& entry = r.pget<Entry>(entry_offset);
|
const auto& entry = r.pget<Entry>(entry_offset);
|
||||||
string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
|
std::string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
|
||||||
string filename = entry.filename.decode();
|
std::string filename = entry.filename.decode();
|
||||||
decrypt_ppk_data(data, phosg::tolower(filename), password);
|
decrypt_ppk_data(data, phosg::tolower(filename), password);
|
||||||
uint32_t checksum = phosg::crc32(data.data(), data.size());
|
uint32_t checksum = phosg::crc32(data.data(), data.size());
|
||||||
if (checksum != entry.checksum) {
|
if (checksum != entry.checksum) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"incorrect checksum for file {} (expected {:08X}; received {:08X})", filename, entry.checksum, checksum));
|
"incorrect checksum for file {} (expected {:08X}; received {:08X})", filename, entry.checksum, checksum));
|
||||||
}
|
}
|
||||||
if (entry.compressed_size < entry.decompressed_size) {
|
if (entry.compressed_size < entry.decompressed_size) {
|
||||||
data = prs_decompress(data);
|
data = prs_decompress(data);
|
||||||
}
|
}
|
||||||
if (!ret.emplace(filename, data).second) {
|
if (!ret.emplace(filename, data).second) {
|
||||||
throw runtime_error(std::format("archive contains multiple files with the same name ({})", filename));
|
throw std::runtime_error(std::format("archive contains multiple files with the same name ({})", filename));
|
||||||
}
|
}
|
||||||
offset = entry_offset - 4;
|
offset = entry_offset - 4;
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-35
@@ -9,8 +9,6 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
RandomGenerator::RandomGenerator(uint32_t seed) : initial_seed(seed) {}
|
RandomGenerator::RandomGenerator(uint32_t seed) : initial_seed(seed) {}
|
||||||
|
|
||||||
DisabledRandomGenerator::DisabledRandomGenerator() : RandomGenerator(0) {}
|
DisabledRandomGenerator::DisabledRandomGenerator() : RandomGenerator(0) {}
|
||||||
@@ -62,7 +60,7 @@ void PSOLFGEncryption::encrypt_big_endian_minus(void* vdata, size_t size) {
|
|||||||
|
|
||||||
void PSOLFGEncryption::encrypt_both_endian(void* le_vdata, void* be_vdata, size_t size) {
|
void PSOLFGEncryption::encrypt_both_endian(void* le_vdata, void* be_vdata, size_t size) {
|
||||||
if (size & 3) {
|
if (size & 3) {
|
||||||
throw invalid_argument("size must be a multiple of 4");
|
throw std::invalid_argument("size must be a multiple of 4");
|
||||||
}
|
}
|
||||||
size >>= 2;
|
size >>= 2;
|
||||||
|
|
||||||
@@ -185,7 +183,7 @@ PSOBBEncryption::PSOBBEncryption(const KeyFile& key, const void* original_seed,
|
|||||||
void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
||||||
if (this->state.subtype == Subtype::TFS1) {
|
if (this->state.subtype == Subtype::TFS1) {
|
||||||
if (size & 7) {
|
if (size & 7) {
|
||||||
throw invalid_argument("size must be a multiple of 8");
|
throw std::invalid_argument("size must be a multiple of 8");
|
||||||
}
|
}
|
||||||
|
|
||||||
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||||
@@ -212,7 +210,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
|||||||
|
|
||||||
} else if (this->state.subtype == Subtype::JSD1) {
|
} else if (this->state.subtype == Subtype::JSD1) {
|
||||||
if (size & 1) {
|
if (size & 1) {
|
||||||
throw invalid_argument("size must be a multiple of 2");
|
throw std::invalid_argument("size must be a multiple of 2");
|
||||||
}
|
}
|
||||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
||||||
for (size_t z = 0; z < size; z++) {
|
for (size_t z = 0; z < size; z++) {
|
||||||
@@ -230,7 +228,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
|||||||
|
|
||||||
} else { // STANDARD or MOCB1
|
} else { // STANDARD or MOCB1
|
||||||
if (size & 7) {
|
if (size & 7) {
|
||||||
throw invalid_argument("size must be a multiple of 8");
|
throw std::invalid_argument("size must be a multiple of 8");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t num_dwords = size >> 2;
|
size_t num_dwords = size >> 2;
|
||||||
@@ -274,7 +272,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
|||||||
void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
||||||
if (this->state.subtype == Subtype::TFS1) {
|
if (this->state.subtype == Subtype::TFS1) {
|
||||||
if (size & 7) {
|
if (size & 7) {
|
||||||
throw invalid_argument("size must be a multiple of 8");
|
throw std::invalid_argument("size must be a multiple of 8");
|
||||||
}
|
}
|
||||||
|
|
||||||
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||||
@@ -301,7 +299,7 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
|||||||
|
|
||||||
} else if (this->state.subtype == Subtype::JSD1) {
|
} else if (this->state.subtype == Subtype::JSD1) {
|
||||||
if (size & 1) {
|
if (size & 1) {
|
||||||
throw invalid_argument("size must be a multiple of 2");
|
throw std::invalid_argument("size must be a multiple of 2");
|
||||||
}
|
}
|
||||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
||||||
for (size_t z = 0; z < size; z += 2) {
|
for (size_t z = 0; z < size; z += 2) {
|
||||||
@@ -318,7 +316,7 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
|||||||
|
|
||||||
} else { // STANDARD or MOCB1
|
} else { // STANDARD or MOCB1
|
||||||
if (size & 7) {
|
if (size & 7) {
|
||||||
throw invalid_argument("size must be a multiple of 8");
|
throw std::invalid_argument("size must be a multiple of 8");
|
||||||
}
|
}
|
||||||
size_t num_dwords = size >> 2;
|
size_t num_dwords = size >> 2;
|
||||||
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||||
@@ -385,7 +383,7 @@ void PSOBBEncryption::tfs1_scramble(uint32_t* out1, uint32_t* out2) const {
|
|||||||
void PSOBBEncryption::apply_seed(const void* original_seed, size_t seed_size) {
|
void PSOBBEncryption::apply_seed(const void* original_seed, size_t seed_size) {
|
||||||
// Note: This part is done in the 03 command handler in the BB client, and isn't actually part of the encryption
|
// Note: This part is done in the 03 command handler in the BB client, and isn't actually part of the encryption
|
||||||
// library. (Why did they do this?)
|
// library. (Why did they do this?)
|
||||||
string seed;
|
std::string seed;
|
||||||
const uint8_t* original_seed_data = reinterpret_cast<const uint8_t*>(original_seed);
|
const uint8_t* original_seed_data = reinterpret_cast<const uint8_t*>(original_seed);
|
||||||
for (size_t x = 0; x < seed_size; x += 3) {
|
for (size_t x = 0; x < seed_size; x += 3) {
|
||||||
seed.push_back(original_seed_data[x] ^ 0x19);
|
seed.push_back(original_seed_data[x] ^ 0x19);
|
||||||
@@ -431,7 +429,7 @@ void PSOBBEncryption::apply_seed(const void* original_seed, size_t seed_size) {
|
|||||||
|
|
||||||
} else { // STANDARD or MOCB1 (they share most of their logic)
|
} else { // STANDARD or MOCB1 (they share most of their logic)
|
||||||
if (seed_size % 3) {
|
if (seed_size % 3) {
|
||||||
throw invalid_argument("seed size must be divisible by 3");
|
throw std::invalid_argument("seed size must be divisible by 3");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->state.subtype == Subtype::MOCB1) {
|
if (this->state.subtype == Subtype::MOCB1) {
|
||||||
@@ -641,27 +639,27 @@ PSOV2OrV3DetectorEncryption::PSOV2OrV3DetectorEncryption(
|
|||||||
void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size) {
|
void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size) {
|
||||||
if (!this->active_crypt) {
|
if (!this->active_crypt) {
|
||||||
if (size != 4) {
|
if (size != 4) {
|
||||||
throw logic_error("initial detector decrypt size must be 4");
|
throw std::logic_error("initial detector decrypt size must be 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
le_uint32_t encrypted = *reinterpret_cast<le_uint32_t*>(data);
|
le_uint32_t encrypted = *reinterpret_cast<le_uint32_t*>(data);
|
||||||
|
|
||||||
le_uint32_t decrypted_v2 = encrypted;
|
le_uint32_t decrypted_v2 = encrypted;
|
||||||
auto v2_crypt = make_unique<PSOV2Encryption>(this->key);
|
auto v2_crypt = std::make_unique<PSOV2Encryption>(this->key);
|
||||||
v2_crypt->decrypt(&decrypted_v2, sizeof(decrypted_v2));
|
v2_crypt->decrypt(&decrypted_v2, sizeof(decrypted_v2));
|
||||||
|
|
||||||
le_uint32_t decrypted_v3 = encrypted;
|
le_uint32_t decrypted_v3 = encrypted;
|
||||||
auto v3_crypt = make_unique<PSOV3Encryption>(this->key);
|
auto v3_crypt = std::make_unique<PSOV3Encryption>(this->key);
|
||||||
v3_crypt->decrypt(&decrypted_v3, sizeof(decrypted_v3));
|
v3_crypt->decrypt(&decrypted_v3, sizeof(decrypted_v3));
|
||||||
|
|
||||||
bool v2_match = this->v2_matches.count(decrypted_v2);
|
bool v2_match = this->v2_matches.count(decrypted_v2);
|
||||||
bool v3_match = this->v3_matches.count(decrypted_v3);
|
bool v3_match = this->v3_matches.count(decrypted_v3);
|
||||||
if (!v2_match && !v3_match) {
|
if (!v2_match && !v3_match) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"unable to determine crypt version (input={:08X}, v2={:08X}, v3={:08X})",
|
"unable to determine crypt version (input={:08X}, v2={:08X}, v3={:08X})",
|
||||||
encrypted, decrypted_v2, decrypted_v3));
|
encrypted, decrypted_v2, decrypted_v3));
|
||||||
} else if (v2_match && v3_match) {
|
} else if (v2_match && v3_match) {
|
||||||
throw runtime_error(std::format("ambiguous crypt version (v2={:08X}, v3={:08X})", decrypted_v2, decrypted_v3));
|
throw std::runtime_error(std::format("ambiguous crypt version (v2={:08X}, v3={:08X})", decrypted_v2, decrypted_v3));
|
||||||
} else if (v2_match) {
|
} else if (v2_match) {
|
||||||
this->active_crypt = std::move(v2_crypt);
|
this->active_crypt = std::move(v2_crypt);
|
||||||
*reinterpret_cast<le_uint32_t*>(data) = decrypted_v2;
|
*reinterpret_cast<le_uint32_t*>(data) = decrypted_v2;
|
||||||
@@ -676,7 +674,7 @@ void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size) {
|
|||||||
|
|
||||||
PSOEncryption::Type PSOV2OrV3DetectorEncryption::type() const {
|
PSOEncryption::Type PSOV2OrV3DetectorEncryption::type() const {
|
||||||
if (!this->active_crypt) {
|
if (!this->active_crypt) {
|
||||||
throw logic_error("detector encryption state is indeterminate");
|
throw std::logic_error("detector encryption state is indeterminate");
|
||||||
}
|
}
|
||||||
return this->active_crypt->type();
|
return this->active_crypt->type();
|
||||||
}
|
}
|
||||||
@@ -689,11 +687,11 @@ void PSOV2OrV3ImitatorEncryption::encrypt(void* data, size_t size) {
|
|||||||
if (!this->active_crypt) {
|
if (!this->active_crypt) {
|
||||||
auto t = this->detector_crypt->type();
|
auto t = this->detector_crypt->type();
|
||||||
if (t == Type::V2) {
|
if (t == Type::V2) {
|
||||||
this->active_crypt = make_shared<PSOV2Encryption>(this->key);
|
this->active_crypt = std::make_shared<PSOV2Encryption>(this->key);
|
||||||
} else if (t == Type::V3) {
|
} else if (t == Type::V3) {
|
||||||
this->active_crypt = make_shared<PSOV3Encryption>(this->key);
|
this->active_crypt = std::make_shared<PSOV3Encryption>(this->key);
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("detector crypt is not V2 or V3");
|
throw std::logic_error("detector crypt is not V2 or V3");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->active_crypt->encrypt(data, size);
|
this->active_crypt->encrypt(data, size);
|
||||||
@@ -707,8 +705,8 @@ PSOEncryption::Type PSOV2OrV3ImitatorEncryption::type() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
||||||
const vector<shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||||
const unordered_set<string>& expected_first_data,
|
const std::unordered_set<std::string>& expected_first_data,
|
||||||
const void* seed,
|
const void* seed,
|
||||||
size_t seed_size)
|
size_t seed_size)
|
||||||
: possible_keys(possible_keys),
|
: possible_keys(possible_keys),
|
||||||
@@ -717,7 +715,7 @@ PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
|||||||
|
|
||||||
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size) {
|
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size) {
|
||||||
if (!this->active_crypt.get()) {
|
if (!this->active_crypt.get()) {
|
||||||
throw logic_error("PSOBB multi-key encryption requires client input first");
|
throw std::logic_error("PSOBB multi-key encryption requires client input first");
|
||||||
}
|
}
|
||||||
this->active_crypt->encrypt(data, size);
|
this->active_crypt->encrypt(data, size);
|
||||||
}
|
}
|
||||||
@@ -729,13 +727,13 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (size != 8) {
|
if (size != 8) {
|
||||||
throw logic_error("initial decryption size does not match expected first data size");
|
throw std::logic_error("initial decryption size does not match expected first data size");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& key : this->possible_keys) {
|
for (const auto& key : this->possible_keys) {
|
||||||
this->active_key = key;
|
this->active_key = key;
|
||||||
this->active_crypt = make_shared<PSOBBEncryption>(*this->active_key, this->seed.data(), this->seed.size());
|
this->active_crypt = std::make_shared<PSOBBEncryption>(*this->active_key, this->seed.data(), this->seed.size());
|
||||||
string test_data(reinterpret_cast<const char*>(data), size);
|
std::string test_data(reinterpret_cast<const char*>(data), size);
|
||||||
this->active_crypt->decrypt(test_data.data(), test_data.size());
|
this->active_crypt->decrypt(test_data.data(), test_data.size());
|
||||||
if (this->expected_first_data.count(test_data)) {
|
if (this->expected_first_data.count(test_data)) {
|
||||||
memcpy(data, test_data.data(), size);
|
memcpy(data, test_data.data(), size);
|
||||||
@@ -744,7 +742,7 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
|
|||||||
this->active_key.reset();
|
this->active_key.reset();
|
||||||
this->active_crypt.reset();
|
this->active_crypt.reset();
|
||||||
}
|
}
|
||||||
throw runtime_error("none of the registered private keys are valid for this client");
|
throw std::runtime_error("none of the registered private keys are valid for this client");
|
||||||
}
|
}
|
||||||
|
|
||||||
PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
|
PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
|
||||||
@@ -752,7 +750,7 @@ PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption(
|
PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption(
|
||||||
shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||||
const void* seed,
|
const void* seed,
|
||||||
size_t seed_size,
|
size_t seed_size,
|
||||||
bool jsd1_use_detector_seed)
|
bool jsd1_use_detector_seed)
|
||||||
@@ -772,19 +770,19 @@ PSOEncryption::Type PSOBBMultiKeyImitatorEncryption::type() const {
|
|||||||
return Type::BB;
|
return Type::BB;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
|
std::shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
|
||||||
if (!this->active_crypt.get()) {
|
if (!this->active_crypt.get()) {
|
||||||
auto key = this->detector_crypt->get_active_key();
|
auto key = this->detector_crypt->get_active_key();
|
||||||
if (!key.get()) {
|
if (!key.get()) {
|
||||||
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
|
throw std::logic_error("server crypt cannot be initialized because client crypt is not ready");
|
||||||
}
|
}
|
||||||
// Hack: JSD1 uses the client seed for both ends of the connection and ignores the server seed (though each end has
|
// Hack: JSD1 uses the client seed for both ends of the connection and ignores the server seed (though each end has
|
||||||
// its own state after that). To handle this, we use the other crypt's seed if the type is JSD1.
|
// its own state after that). To handle this, we use the other crypt's seed if the type is JSD1.
|
||||||
if ((key->subtype == PSOBBEncryption::Subtype::JSD1) && this->jsd1_use_detector_seed) {
|
if ((key->subtype == PSOBBEncryption::Subtype::JSD1) && this->jsd1_use_detector_seed) {
|
||||||
const auto& detector_seed = this->detector_crypt->get_seed();
|
const auto& detector_seed = this->detector_crypt->get_seed();
|
||||||
this->active_crypt = make_shared<PSOBBEncryption>(*key, detector_seed.data(), detector_seed.size());
|
this->active_crypt = std::make_shared<PSOBBEncryption>(*key, detector_seed.data(), detector_seed.size());
|
||||||
} else {
|
} else {
|
||||||
this->active_crypt = make_shared<PSOBBEncryption>(*key, this->seed.data(), this->seed.size());
|
this->active_crypt = std::make_shared<PSOBBEncryption>(*key, this->seed.data(), this->seed.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this->active_crypt;
|
return this->active_crypt;
|
||||||
@@ -836,7 +834,7 @@ static uint8_t count_one_bits(uint16_t v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t encrypt_challenge_time(uint16_t value) {
|
uint32_t encrypt_challenge_time(uint16_t value) {
|
||||||
vector<uint8_t> available_bits({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
|
std::vector<uint8_t> available_bits({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
|
||||||
|
|
||||||
uint16_t mask = 0;
|
uint16_t mask = 0;
|
||||||
uint8_t num_one_bits = (phosg::random_object<uint8_t>() % 9) + 4; // Range [4, 12]
|
uint8_t num_one_bits = (phosg::random_object<uint8_t>() % 9) + 4; // Range [4, 12]
|
||||||
@@ -856,8 +854,8 @@ uint16_t decrypt_challenge_time(uint32_t value) {
|
|||||||
return ((mask_one_bits < 4) || (mask_one_bits > 12)) ? 0xFFFF : ((mask ^ value) & 0xFFFF);
|
return ((mask_one_bits < 4) || (mask_one_bits > 12)) ? 0xFFFF : ((mask ^ value) & 0xFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
string decrypt_v2_registry_value(const void* data, size_t size) {
|
std::string decrypt_v2_registry_value(const void* data, size_t size) {
|
||||||
string ret(reinterpret_cast<const char*>(data), size);
|
std::string ret(reinterpret_cast<const char*>(data), size);
|
||||||
PSOV2Encryption crypt(0x66);
|
PSOV2Encryption crypt(0x66);
|
||||||
for (size_t z = 0; z < size; z++) {
|
for (size_t z = 0; z < size; z++) {
|
||||||
ret[z] ^= (crypt.next() & 0x7F);
|
ret[z] ^= (crypt.next() & 0x7F);
|
||||||
|
|||||||
@@ -421,12 +421,11 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size,
|
|||||||
w.put<U32T<BE>>(seed);
|
w.put<U32T<BE>>(seed);
|
||||||
w.write(data);
|
w.write(data);
|
||||||
|
|
||||||
std::string ret = std::move(w.str());
|
|
||||||
PSOV2Encryption crypt(seed);
|
PSOV2Encryption crypt(seed);
|
||||||
if (BE) {
|
if (BE) {
|
||||||
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
|
crypt.encrypt_big_endian(w.str().data() + 8, w.str().size() - 8);
|
||||||
} else {
|
} else {
|
||||||
crypt.decrypt(ret.data() + 8, ret.size() - 8);
|
crypt.decrypt(w.str().data() + 8, w.str().size() - 8);
|
||||||
}
|
}
|
||||||
return ret;
|
return std::move(w.str());
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-10
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
struct TObjectVTable {
|
struct TObjectVTable {
|
||||||
phosg::be_uint32_t unused_a1;
|
phosg::be_uint32_t unused_a1;
|
||||||
phosg::be_uint32_t unused_a2;
|
phosg::be_uint32_t unused_a2;
|
||||||
@@ -24,19 +22,19 @@ struct TObject {
|
|||||||
phosg::be_uint32_t vtable_addr;
|
phosg::be_uint32_t vtable_addr;
|
||||||
} __packed_ws__(TObject, 0x1C);
|
} __packed_ws__(TObject, 0x1C);
|
||||||
|
|
||||||
PSOGCObjectGraph::PSOGCObjectGraph(const string& memory_data, uint32_t root_address) {
|
PSOGCObjectGraph::PSOGCObjectGraph(const std::string& memory_data, uint32_t root_address) {
|
||||||
phosg::StringReader r(memory_data);
|
phosg::StringReader r(memory_data);
|
||||||
this->root = this->parse_object_memo(r, root_address);
|
this->root = this->parse_object_memo(r, root_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::StringReader& r, uint32_t addr) {
|
std::shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::StringReader& r, uint32_t addr) {
|
||||||
try {
|
try {
|
||||||
return this->all_vtables.at(addr);
|
return this->all_vtables.at(addr);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& vt = r.pget<TObjectVTable>(addr & 0x01FFFFFF);
|
const auto& vt = r.pget<TObjectVTable>(addr & 0x01FFFFFF);
|
||||||
auto ret = this->all_vtables.emplace(addr, make_shared<VTable>()).first->second;
|
auto ret = this->all_vtables.emplace(addr, std::make_shared<VTable>()).first->second;
|
||||||
ret->address = addr;
|
ret->address = addr;
|
||||||
ret->destroy_addr = vt.destroy;
|
ret->destroy_addr = vt.destroy;
|
||||||
ret->update_addr = vt.update;
|
ret->update_addr = vt.update;
|
||||||
@@ -45,16 +43,16 @@ shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOGCObjectGraph::Object> PSOGCObjectGraph::parse_object_memo(phosg::StringReader& r, uint32_t addr) {
|
std::shared_ptr<PSOGCObjectGraph::Object> PSOGCObjectGraph::parse_object_memo(phosg::StringReader& r, uint32_t addr) {
|
||||||
try {
|
try {
|
||||||
return this->all_objects.at(addr);
|
return this->all_objects.at(addr);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& obj = r.pget<TObject>(addr & 0x01FFFFFF);
|
const auto& obj = r.pget<TObject>(addr & 0x01FFFFFF);
|
||||||
string type_name = r.pget_cstr(obj.type_name_addr & 0x01FFFFFF);
|
std::string type_name = r.pget_cstr(obj.type_name_addr & 0x01FFFFFF);
|
||||||
|
|
||||||
auto ret = this->all_objects.emplace(addr, make_shared<Object>()).first->second;
|
auto ret = this->all_objects.emplace(addr, std::make_shared<Object>()).first->second;
|
||||||
ret->address = addr;
|
ret->address = addr;
|
||||||
ret->flags = obj.flags;
|
ret->flags = obj.flags;
|
||||||
ret->type_name = std::move(type_name);
|
ret->type_name = std::move(type_name);
|
||||||
|
|||||||
+7
-9
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
extern bool use_terminal_colors;
|
extern bool use_terminal_colors;
|
||||||
|
|
||||||
PSOCommandHeader::PSOCommandHeader() {
|
PSOCommandHeader::PSOCommandHeader() {
|
||||||
@@ -37,7 +35,7 @@ uint16_t PSOCommandHeader::command(Version version) const {
|
|||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
return this->bb.command;
|
return this->bb.command;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown game version");
|
throw std::logic_error("unknown game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +66,7 @@ void PSOCommandHeader::set_command(Version version, uint16_t command) {
|
|||||||
this->bb.command = command;
|
this->bb.command = command;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown game version");
|
throw std::logic_error("unknown game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +92,7 @@ uint16_t PSOCommandHeader::size(Version version) const {
|
|||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
return this->bb.size;
|
return this->bb.size;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown game version");
|
throw std::logic_error("unknown game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ void PSOCommandHeader::set_size(Version version, uint32_t size) {
|
|||||||
this->bb.size = size;
|
this->bb.size = size;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown game version");
|
throw std::logic_error("unknown game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +149,7 @@ uint32_t PSOCommandHeader::flag(Version version) const {
|
|||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
return this->bb.flag;
|
return this->bb.flag;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown game version");
|
throw std::logic_error("unknown game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +180,7 @@ void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
|
|||||||
this->bb.flag = flag;
|
this->bb.flag = flag;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unknown game version");
|
throw std::logic_error("unknown game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +253,7 @@ std::string prepend_command_header(
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw logic_error("unimplemented game version in prepend_command_header");
|
throw std::logic_error("unimplemented game version in prepend_command_header");
|
||||||
}
|
}
|
||||||
ret.write(data);
|
ret.write(data);
|
||||||
return std::move(ret.str());
|
return std::move(ret.str());
|
||||||
|
|||||||
@@ -22,8 +22,6 @@
|
|||||||
#include "ReceiveSubcommands.hh"
|
#include "ReceiveSubcommands.hh"
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
PatchDownloadSession::PatchDownloadSession(
|
PatchDownloadSession::PatchDownloadSession(
|
||||||
std::shared_ptr<asio::io_context> io_context,
|
std::shared_ptr<asio::io_context> io_context,
|
||||||
const std::string& remote_host,
|
const std::string& remote_host,
|
||||||
@@ -54,9 +52,9 @@ PatchDownloadSession::PatchDownloadSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> PatchDownloadSession::run() {
|
asio::awaitable<void> PatchDownloadSession::run() {
|
||||||
string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
std::string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
||||||
this->log.info_f("Connecting to {}", netloc_str);
|
this->log.info_f("Connecting to {}", netloc_str);
|
||||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
||||||
this->channel = SocketChannel::create(
|
this->channel = SocketChannel::create(
|
||||||
this->io_context,
|
this->io_context,
|
||||||
std::move(sock),
|
std::move(sock),
|
||||||
@@ -79,14 +77,14 @@ void PatchDownloadSession::check_path_token(const std::string& token) {
|
|||||||
if (token == "..") {
|
if (token == "..") {
|
||||||
throw std::runtime_error("parent directory token is not allowed");
|
throw std::runtime_error("parent directory token is not allowed");
|
||||||
}
|
}
|
||||||
if ((token.find('/') != string::npos) || (token.find('\\') != string::npos)) {
|
if ((token.find('/') != std::string::npos) || (token.find('\\') != std::string::npos)) {
|
||||||
throw std::runtime_error("directory token contains path separator");
|
throw std::runtime_error("directory token contains path separator");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PatchDownloadSession::resolve_filename(const std::string& filename) const {
|
std::string PatchDownloadSession::resolve_filename(const std::string& filename) const {
|
||||||
check_path_token(filename);
|
check_path_token(filename);
|
||||||
string path = this->output_dir;
|
std::string path = this->output_dir;
|
||||||
for (const auto& dir_name : this->dir_path) {
|
for (const auto& dir_name : this->dir_path) {
|
||||||
path.push_back('/');
|
path.push_back('/');
|
||||||
path += dir_name;
|
path += dir_name;
|
||||||
@@ -105,8 +103,8 @@ asio::awaitable<void> PatchDownloadSession::on_message(Channel::Message& msg) {
|
|||||||
if (cmd.copyright.decode() != "Patch Server. Copyright SonicTeam, LTD. 2001") {
|
if (cmd.copyright.decode() != "Patch Server. Copyright SonicTeam, LTD. 2001") {
|
||||||
throw std::runtime_error("incorrect copyright message");
|
throw std::runtime_error("incorrect copyright message");
|
||||||
}
|
}
|
||||||
this->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
this->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||||
this->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
this->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||||
this->channel->send(0x02);
|
this->channel->send(0x02);
|
||||||
this->log.info_f("Enabled encryption");
|
this->log.info_f("Enabled encryption");
|
||||||
break;
|
break;
|
||||||
@@ -181,7 +179,7 @@ asio::awaitable<void> PatchDownloadSession::on_message(Channel::Message& msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto& cmd = msg.check_size_t<S_EnterDirectory_Patch_09>();
|
const auto& cmd = msg.check_size_t<S_EnterDirectory_Patch_09>();
|
||||||
string dirname = cmd.name.decode();
|
std::string dirname = cmd.name.decode();
|
||||||
check_path_token(dirname);
|
check_path_token(dirname);
|
||||||
this->dir_path.emplace_back(std::move(dirname));
|
this->dir_path.emplace_back(std::move(dirname));
|
||||||
std::filesystem::create_directories(this->resolve_filename(""));
|
std::filesystem::create_directories(this->resolve_filename(""));
|
||||||
@@ -257,9 +255,9 @@ asio::awaitable<void> PatchDownloadSession::on_message(Channel::Message& msg) {
|
|||||||
const auto& cmd = msg.check_size_t<S_Reconnect_Patch_14>();
|
const auto& cmd = msg.check_size_t<S_Reconnect_Patch_14>();
|
||||||
|
|
||||||
auto new_ep = make_endpoint_ipv4(cmd.address, cmd.port);
|
auto new_ep = make_endpoint_ipv4(cmd.address, cmd.port);
|
||||||
string netloc_str = str_for_endpoint(new_ep);
|
std::string netloc_str = str_for_endpoint(new_ep);
|
||||||
this->log.info_f("Connecting to {}", netloc_str);
|
this->log.info_f("Connecting to {}", netloc_str);
|
||||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||||
|
|
||||||
auto old_channel = this->channel;
|
auto old_channel = this->channel;
|
||||||
auto new_channel = SocketChannel::create(
|
auto new_channel = SocketChannel::create(
|
||||||
|
|||||||
+24
-31
@@ -12,8 +12,6 @@
|
|||||||
|
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
int64_t file_mtime_int(const std::string& path) {
|
int64_t file_mtime_int(const std::string& path) {
|
||||||
auto mtime = std::filesystem::last_write_time(path);
|
auto mtime = std::filesystem::last_write_time(path);
|
||||||
auto sctp = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
auto sctp = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||||
@@ -21,32 +19,27 @@ int64_t file_mtime_int(const std::string& path) {
|
|||||||
return sctp.time_since_epoch().count();
|
return sctp.time_since_epoch().count();
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchFileIndex::File::File(PatchFileIndex* index)
|
PatchFileIndex::File::File(PatchFileIndex* index) : index(index), crc32(0), size(0) {}
|
||||||
: index(index),
|
|
||||||
crc32(0),
|
|
||||||
size(0) {}
|
|
||||||
|
|
||||||
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
|
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
|
||||||
if (!this->loaded_data) {
|
if (!this->loaded_data) {
|
||||||
string relative_path = phosg::join(this->path_directories, "/") + "/" + this->name;
|
std::string relative_path = phosg::join(this->path_directories, "/") + "/" + this->name;
|
||||||
string full_path = this->index->root_dir + "/" + relative_path;
|
std::string full_path = this->index->root_dir + "/" + relative_path;
|
||||||
patch_index_log.debug_f("Loading data for {}", relative_path);
|
patch_index_log.debug_f("Loading data for {}", relative_path);
|
||||||
this->loaded_data = make_shared<string>(phosg::load_file(full_path));
|
this->loaded_data = std::make_shared<std::string>(phosg::load_file(full_path));
|
||||||
this->size = this->loaded_data->size();
|
this->size = this->loaded_data->size();
|
||||||
}
|
}
|
||||||
return this->loaded_data;
|
return this->loaded_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchFileIndex::PatchFileIndex(const string& root_dir)
|
PatchFileIndex::PatchFileIndex(const std::string& root_dir) : root_dir(root_dir) {
|
||||||
: root_dir(root_dir) {
|
std::string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
||||||
|
|
||||||
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
|
||||||
phosg::JSON metadata_cache_json;
|
phosg::JSON metadata_cache_json;
|
||||||
try {
|
try {
|
||||||
string metadata_text = phosg::load_file(metadata_cache_filename);
|
std::string metadata_text = phosg::load_file(metadata_cache_filename);
|
||||||
metadata_cache_json = phosg::JSON::parse(metadata_text);
|
metadata_cache_json = phosg::JSON::parse(metadata_text);
|
||||||
patch_index_log.debug_f("Loaded patch metadata cache from {}", metadata_cache_filename);
|
patch_index_log.debug_f("Loaded patch metadata cache from {}", metadata_cache_filename);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
metadata_cache_json = phosg::JSON::dict();
|
metadata_cache_json = phosg::JSON::dict();
|
||||||
patch_index_log.warning_f("Cannot load patch metadata cache from {}: {}", metadata_cache_filename, e.what());
|
patch_index_log.warning_f("Cannot load patch metadata cache from {}: {}", metadata_cache_filename, e.what());
|
||||||
}
|
}
|
||||||
@@ -56,45 +49,45 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
|||||||
bool should_write_metadata_cache = false;
|
bool should_write_metadata_cache = false;
|
||||||
phosg::JSON new_metadata_cache_json = phosg::JSON::dict();
|
phosg::JSON new_metadata_cache_json = phosg::JSON::dict();
|
||||||
|
|
||||||
vector<string> path_directories;
|
std::vector<std::string> path_directories;
|
||||||
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
|
std::function<void(const std::string&)> collect_dir = [&](const std::string& dir) -> void {
|
||||||
path_directories.emplace_back(dir);
|
path_directories.emplace_back(dir);
|
||||||
|
|
||||||
string relative_dirs = phosg::join(path_directories, "/");
|
std::string relative_dirs = phosg::join(path_directories, "/");
|
||||||
string full_dir_path = root_dir + '/' + relative_dirs;
|
std::string full_dir_path = root_dir + '/' + relative_dirs;
|
||||||
patch_index_log.debug_f("Listing directory {}", full_dir_path);
|
patch_index_log.debug_f("Listing directory {}", full_dir_path);
|
||||||
|
|
||||||
for (const auto& dir_item : std::filesystem::directory_iterator(full_dir_path)) {
|
for (const auto& dir_item : std::filesystem::directory_iterator(full_dir_path)) {
|
||||||
string item = dir_item.path().filename().string();
|
std::string item = dir_item.path().filename().string();
|
||||||
|
|
||||||
// Skip invisible files (e.g. .DS_Store on macOS)
|
// Skip invisible files (e.g. .DS_Store on macOS)
|
||||||
if (item.starts_with(".")) {
|
if (item.starts_with(".")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string relative_item_path = relative_dirs + '/' + item;
|
std::string relative_item_path = relative_dirs + '/' + item;
|
||||||
string full_item_path = root_dir + '/' + relative_item_path;
|
std::string full_item_path = root_dir + '/' + relative_item_path;
|
||||||
if (std::filesystem::is_directory(full_item_path)) {
|
if (std::filesystem::is_directory(full_item_path)) {
|
||||||
collect_dir(item);
|
collect_dir(item);
|
||||||
} else if (std::filesystem::is_regular_file(full_item_path)) {
|
} else if (std::filesystem::is_regular_file(full_item_path)) {
|
||||||
|
|
||||||
auto f = make_shared<File>(this);
|
auto f = std::make_shared<File>(this);
|
||||||
f->path_directories = path_directories;
|
f->path_directories = path_directories;
|
||||||
f->name = item;
|
f->name = item;
|
||||||
|
|
||||||
int64_t file_mtime = file_mtime_int(full_item_path);
|
int64_t file_mtime = file_mtime_int(full_item_path);
|
||||||
|
|
||||||
string compute_crc32s_message; // If not empty, should compute crc32s
|
std::string compute_crc32s_message; // If not empty, should compute crc32s
|
||||||
phosg::JSON cache_item_json;
|
phosg::JSON cache_item_json;
|
||||||
try {
|
try {
|
||||||
cache_item_json = metadata_cache_json.at(relative_item_path);
|
cache_item_json = metadata_cache_json.at(relative_item_path);
|
||||||
uint64_t cached_size = cache_item_json.get_int(0);
|
uint64_t cached_size = cache_item_json.get_int(0);
|
||||||
int64_t cached_mtime = cache_item_json.get_int(1);
|
int64_t cached_mtime = cache_item_json.get_int(1);
|
||||||
if (file_mtime != cached_mtime) {
|
if (file_mtime != cached_mtime) {
|
||||||
throw runtime_error("file has been modified");
|
throw std::runtime_error("file has been modified");
|
||||||
}
|
}
|
||||||
if (std::filesystem::file_size(full_item_path) != cached_size) {
|
if (std::filesystem::file_size(full_item_path) != cached_size) {
|
||||||
throw runtime_error("file size has changed");
|
throw std::runtime_error("file size has changed");
|
||||||
}
|
}
|
||||||
f->size = cached_size;
|
f->size = cached_size;
|
||||||
f->crc32 = cache_item_json.get_int(2);
|
f->crc32 = cache_item_json.get_int(2);
|
||||||
@@ -102,7 +95,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
|||||||
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
|
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
compute_crc32s_message = e.what();
|
compute_crc32s_message = e.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +103,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
|||||||
auto data = f->load_data(); // Sets f->size
|
auto data = f->load_data(); // Sets f->size
|
||||||
f->crc32 = phosg::crc32(data->data(), f->size);
|
f->crc32 = phosg::crc32(data->data(), f->size);
|
||||||
for (size_t x = 0; x < data->size(); x += this->CHUNK_SIZE) {
|
for (size_t x = 0; x < data->size(); x += this->CHUNK_SIZE) {
|
||||||
size_t chunk_bytes = min<size_t>(f->size - x, this->CHUNK_SIZE);
|
size_t chunk_bytes = std::min<size_t>(f->size - x, this->CHUNK_SIZE);
|
||||||
f->chunk_crcs.emplace_back(phosg::crc32(data->data() + x, chunk_bytes));
|
f->chunk_crcs.emplace_back(phosg::crc32(data->data() + x, chunk_bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +142,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
|||||||
try {
|
try {
|
||||||
phosg::save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
phosg::save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
||||||
patch_index_log.debug_f("Saved patch metadata cache to {}", metadata_cache_filename);
|
patch_index_log.debug_f("Saved patch metadata cache to {}", metadata_cache_filename);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
patch_index_log.warning_f("Cannot save patch metadata cache to {}: {}", metadata_cache_filename, e.what());
|
patch_index_log.warning_f("Cannot save patch metadata cache to {}: {}", metadata_cache_filename, e.what());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -157,10 +150,10 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const vector<shared_ptr<PatchFileIndex::File>>& PatchFileIndex::all_files() const {
|
const std::vector<std::shared_ptr<PatchFileIndex::File>>& PatchFileIndex::all_files() const {
|
||||||
return this->files_by_patch_order;
|
return this->files_by_patch_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PatchFileIndex::File> PatchFileIndex::get(const string& filename) const {
|
std::shared_ptr<PatchFileIndex::File> PatchFileIndex::get(const std::string& filename) const {
|
||||||
return this->files_by_name.at(filename);
|
return this->files_by_name.at(filename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct PatchFileIndex {
|
class PatchFileIndex {
|
||||||
|
public:
|
||||||
static constexpr size_t CHUNK_SIZE = 0x6000;
|
static constexpr size_t CHUNK_SIZE = 0x6000;
|
||||||
|
|
||||||
explicit PatchFileIndex(const std::string& root_dir);
|
explicit PatchFileIndex(const std::string& root_dir);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "ChoiceSearch.hh"
|
#include "ChoiceSearch.hh"
|
||||||
#include "FileContentsCache.hh"
|
|
||||||
#include "ItemData.hh"
|
#include "ItemData.hh"
|
||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|||||||
+41
-43
@@ -20,28 +20,26 @@
|
|||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
|
|
||||||
using namespace std;
|
void PlayerVisualConfigV4::apply_dressing_room(const PlayerVisualConfigV4& new_visual) {
|
||||||
|
this->sh.name_color = new_visual.sh.name_color;
|
||||||
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
|
this->sh.extra_model = new_visual.sh.extra_model;
|
||||||
this->visual.name_color = pre.visual.name_color;
|
this->sh.name_color_checksum = new_visual.sh.name_color_checksum;
|
||||||
this->visual.extra_model = pre.visual.extra_model;
|
this->sh.section_id = new_visual.sh.section_id;
|
||||||
this->visual.name_color_checksum = pre.visual.name_color_checksum;
|
this->sh.char_class = new_visual.sh.char_class;
|
||||||
this->visual.section_id = pre.visual.section_id;
|
this->sh.validation_flags = new_visual.sh.validation_flags;
|
||||||
this->visual.char_class = pre.visual.char_class;
|
this->sh.version = new_visual.sh.version;
|
||||||
this->visual.validation_flags = pre.visual.validation_flags;
|
this->sh.class_flags = new_visual.sh.class_flags;
|
||||||
this->visual.version = pre.visual.version;
|
this->sh.costume = new_visual.sh.costume;
|
||||||
this->visual.class_flags = pre.visual.class_flags;
|
this->sh.skin = new_visual.sh.skin;
|
||||||
this->visual.costume = pre.visual.costume;
|
this->sh.face = new_visual.sh.face;
|
||||||
this->visual.skin = pre.visual.skin;
|
this->sh.head = new_visual.sh.head;
|
||||||
this->visual.face = pre.visual.face;
|
this->sh.hair = new_visual.sh.hair;
|
||||||
this->visual.head = pre.visual.head;
|
this->sh.hair_r = new_visual.sh.hair_r;
|
||||||
this->visual.hair = pre.visual.hair;
|
this->sh.hair_g = new_visual.sh.hair_g;
|
||||||
this->visual.hair_r = pre.visual.hair_r;
|
this->sh.hair_b = new_visual.sh.hair_b;
|
||||||
this->visual.hair_g = pre.visual.hair_g;
|
this->sh.proportion_x = new_visual.sh.proportion_x;
|
||||||
this->visual.hair_b = pre.visual.hair_b;
|
this->sh.proportion_y = new_visual.sh.proportion_y;
|
||||||
this->visual.proportion_x = pre.visual.proportion_x;
|
this->name = new_visual.name;
|
||||||
this->visual.proportion_y = pre.visual.proportion_y;
|
|
||||||
this->name = pre.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuildCardBB::clear() {
|
void GuildCardBB::clear() {
|
||||||
@@ -414,7 +412,7 @@ const char* phosg::name_for_enum<BattleRules::TechDiskMode>(BattleRules::TechDis
|
|||||||
case BattleRules::TechDiskMode::LIMIT_LEVEL:
|
case BattleRules::TechDiskMode::LIMIT_LEVEL:
|
||||||
return "LIMIT_LEVEL";
|
return "LIMIT_LEVEL";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::TechDiskMode value");
|
throw std::invalid_argument("invalid BattleRules::TechDiskMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -426,7 +424,7 @@ BattleRules::TechDiskMode phosg::enum_for_name<BattleRules::TechDiskMode>(const
|
|||||||
} else if (!strcmp(name, "LIMIT_LEVEL")) {
|
} else if (!strcmp(name, "LIMIT_LEVEL")) {
|
||||||
return BattleRules::TechDiskMode::LIMIT_LEVEL;
|
return BattleRules::TechDiskMode::LIMIT_LEVEL;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::TechDiskMode name");
|
throw std::invalid_argument("invalid BattleRules::TechDiskMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,7 +440,7 @@ const char* phosg::name_for_enum<BattleRules::WeaponAndArmorMode>(BattleRules::W
|
|||||||
case BattleRules::WeaponAndArmorMode::FORBID_RARES:
|
case BattleRules::WeaponAndArmorMode::FORBID_RARES:
|
||||||
return "FORBID_RARES";
|
return "FORBID_RARES";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::WeaponAndArmorMode value");
|
throw std::invalid_argument("invalid BattleRules::WeaponAndArmorMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -456,7 +454,7 @@ BattleRules::WeaponAndArmorMode phosg::enum_for_name<BattleRules::WeaponAndArmor
|
|||||||
} else if (!strcmp(name, "FORBID_RARES")) {
|
} else if (!strcmp(name, "FORBID_RARES")) {
|
||||||
return BattleRules::WeaponAndArmorMode::FORBID_RARES;
|
return BattleRules::WeaponAndArmorMode::FORBID_RARES;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::WeaponAndArmorMode name");
|
throw std::invalid_argument("invalid BattleRules::WeaponAndArmorMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +466,7 @@ const char* phosg::name_for_enum<BattleRules::MagMode>(BattleRules::MagMode v) {
|
|||||||
case BattleRules::MagMode::FORBID_ALL:
|
case BattleRules::MagMode::FORBID_ALL:
|
||||||
return "FORBID_ALL";
|
return "FORBID_ALL";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::MagMode value");
|
throw std::invalid_argument("invalid BattleRules::MagMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -478,7 +476,7 @@ BattleRules::MagMode phosg::enum_for_name<BattleRules::MagMode>(const char* name
|
|||||||
} else if (!strcmp(name, "FORBID_ALL")) {
|
} else if (!strcmp(name, "FORBID_ALL")) {
|
||||||
return BattleRules::MagMode::FORBID_ALL;
|
return BattleRules::MagMode::FORBID_ALL;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::MagMode name");
|
throw std::invalid_argument("invalid BattleRules::MagMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +490,7 @@ const char* phosg::name_for_enum<BattleRules::ToolMode>(BattleRules::ToolMode v)
|
|||||||
case BattleRules::ToolMode::FORBID_ALL:
|
case BattleRules::ToolMode::FORBID_ALL:
|
||||||
return "FORBID_ALL";
|
return "FORBID_ALL";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::ToolMode value");
|
throw std::invalid_argument("invalid BattleRules::ToolMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -504,7 +502,7 @@ BattleRules::ToolMode phosg::enum_for_name<BattleRules::ToolMode>(const char* na
|
|||||||
} else if (!strcmp(name, "FORBID_ALL")) {
|
} else if (!strcmp(name, "FORBID_ALL")) {
|
||||||
return BattleRules::ToolMode::FORBID_ALL;
|
return BattleRules::ToolMode::FORBID_ALL;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::ToolMode name");
|
throw std::invalid_argument("invalid BattleRules::ToolMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,7 +514,7 @@ const char* phosg::name_for_enum<BattleRules::TrapMode>(BattleRules::TrapMode v)
|
|||||||
case BattleRules::TrapMode::ALL_PLAYERS:
|
case BattleRules::TrapMode::ALL_PLAYERS:
|
||||||
return "ALL_PLAYERS";
|
return "ALL_PLAYERS";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::TrapMode value");
|
throw std::invalid_argument("invalid BattleRules::TrapMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -526,7 +524,7 @@ BattleRules::TrapMode phosg::enum_for_name<BattleRules::TrapMode>(const char* na
|
|||||||
} else if (!strcmp(name, "ALL_PLAYERS")) {
|
} else if (!strcmp(name, "ALL_PLAYERS")) {
|
||||||
return BattleRules::TrapMode::ALL_PLAYERS;
|
return BattleRules::TrapMode::ALL_PLAYERS;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::TrapMode name");
|
throw std::invalid_argument("invalid BattleRules::TrapMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +538,7 @@ const char* phosg::name_for_enum<BattleRules::MesetaMode>(BattleRules::MesetaMod
|
|||||||
case BattleRules::MesetaMode::CLEAR_AND_ALLOW:
|
case BattleRules::MesetaMode::CLEAR_AND_ALLOW:
|
||||||
return "CLEAR_AND_ALLOW";
|
return "CLEAR_AND_ALLOW";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::MesetaDropMode value");
|
throw std::invalid_argument("invalid BattleRules::MesetaDropMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -552,7 +550,7 @@ BattleRules::MesetaMode phosg::enum_for_name<BattleRules::MesetaMode>(const char
|
|||||||
} else if (!strcmp(name, "CLEAR_AND_ALLOW")) {
|
} else if (!strcmp(name, "CLEAR_AND_ALLOW")) {
|
||||||
return BattleRules::MesetaMode::CLEAR_AND_ALLOW;
|
return BattleRules::MesetaMode::CLEAR_AND_ALLOW;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::MesetaDropMode name");
|
throw std::invalid_argument("invalid BattleRules::MesetaDropMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +564,7 @@ const char* phosg::name_for_enum<BattleRules::RespawnMode>(BattleRules::RespawnM
|
|||||||
case BattleRules::RespawnMode::LIMIT_LIVES:
|
case BattleRules::RespawnMode::LIMIT_LIVES:
|
||||||
return "LIMIT_LIVES";
|
return "LIMIT_LIVES";
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("invalid BattleRules::MesetaDropMode value");
|
throw std::invalid_argument("invalid BattleRules::MesetaDropMode value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
@@ -578,7 +576,7 @@ BattleRules::RespawnMode phosg::enum_for_name<BattleRules::RespawnMode>(const ch
|
|||||||
} else if (!strcmp(name, "LIMIT_LIVES")) {
|
} else if (!strcmp(name, "LIMIT_LIVES")) {
|
||||||
return BattleRules::RespawnMode::LIMIT_LIVES;
|
return BattleRules::RespawnMode::LIMIT_LIVES;
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid BattleRules::MesetaDropMode name");
|
throw std::invalid_argument("invalid BattleRules::MesetaDropMode name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,7 +596,7 @@ static PlayerInventoryItem v3_item(bool equipped, uint64_t first_data, uint64_t
|
|||||||
|
|
||||||
const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index) {
|
const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const vector<ChallengeTemplateDefinition> v2_hunter_templates({
|
static const std::vector<ChallengeTemplateDefinition> v2_hunter_templates({
|
||||||
{0, {v2_item(true, 0x0001000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{0, {v2_item(true, 0x0001000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{4, {v2_item(true, 0x0001000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02010D002003F501, 0x0100010000002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{4, {v2_item(true, 0x0001000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02010D002003F501, 0x0100010000002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{6, {v2_item(true, 0x0002000000000000, 0x0000000000000000), v2_item(true, 0x0101020000000000, 0x0000000000000000), v2_item(true, 0x0102010000000000, 0x0000000000000000), v2_item(true, 0x0201100020032103, 0x0100010000002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{6, {v2_item(true, 0x0002000000000000, 0x0000000000000000), v2_item(true, 0x0101020000000000, 0x0000000000000000), v2_item(true, 0x0102010000000000, 0x0000000000000000), v2_item(true, 0x0201100020032103, 0x0100010000002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
@@ -616,7 +614,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
|||||||
{50, {v2_item(true, 0x02058200A00F8913, 0xD107D10700002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{50, {v2_item(true, 0x02058200A00F8913, 0xD107D10700002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{99, {v2_item(true, 0x0205BE007017591B, 0xB90BB90B00002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{99, {v2_item(true, 0x0205BE007017591B, 0xB90BB90B00002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
});
|
});
|
||||||
static const vector<ChallengeTemplateDefinition> v2_ranger_templates({
|
static const std::vector<ChallengeTemplateDefinition> v2_ranger_templates({
|
||||||
{0, {v2_item(true, 0x0006000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{0, {v2_item(true, 0x0006000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{4, {v2_item(true, 0x0006000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x020D0C00F401C900, 0xF501010000002800), v2_item(false, 0x0300000000050000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0308000000050000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{4, {v2_item(true, 0x0006000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x020D0C00F401C900, 0xF501010000002800), v2_item(false, 0x0300000000050000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0308000000050000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{5, {v2_item(true, 0x0006000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102010000000000, 0x0000000000000000), v2_item(true, 0x020D0E00F401C900, 0xBD02010000002800), v2_item(false, 0x0300000000050000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0308000000050000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{5, {v2_item(true, 0x0006000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102010000000000, 0x0000000000000000), v2_item(true, 0x020D0E00F401C900, 0xBD02010000002800), v2_item(false, 0x0300000000050000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0308000000050000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
@@ -634,7 +632,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
|||||||
{50, {v2_item(true, 0x020C8C00B80BC509, 0x7117C50900002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{50, {v2_item(true, 0x020C8C00B80BC509, 0x7117C50900002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{99, {v2_item(true, 0x0206B400B80BB90B, 0x2923B90B00002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{99, {v2_item(true, 0x0206B400B80BB90B, 0x2923B90B00002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
});
|
});
|
||||||
static const vector<ChallengeTemplateDefinition> v2_force_templates({
|
static const std::vector<ChallengeTemplateDefinition> v2_force_templates({
|
||||||
{0, {v2_item(true, 0x000A000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000040000, 0x0000000000000000), v2_item(false, 0x0301000000040000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}}},
|
{0, {v2_item(true, 0x000A000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000040000, 0x0000000000000000), v2_item(false, 0x0301000000040000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}}},
|
||||||
{4, {v2_item(true, 0x000A000500000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02190D0020036500, 0x0100910100002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0301000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
{4, {v2_item(true, 0x000A000500000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02190D0020036500, 0x0100910100002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0301000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||||
{6, {v2_item(true, 0x000B000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02190F002003C900, 0x0100F50100002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0301000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
{6, {v2_item(true, 0x000B000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02190F002003C900, 0x0100F50100002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0301000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||||
@@ -653,7 +651,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
|||||||
{99, {v2_item(true, 0x021CB400AC0DD107, 0xC509112700002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}} },
|
{99, {v2_item(true, 0x021CB400AC0DD107, 0xC509112700002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}} },
|
||||||
});
|
});
|
||||||
|
|
||||||
static const vector<ChallengeTemplateDefinition> v3_hunter_templates({
|
static const std::vector<ChallengeTemplateDefinition> v3_hunter_templates({
|
||||||
{0, {v3_item(true, 0x0001000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{0, {v3_item(true, 0x0001000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{4, {v3_item(true, 0x0001000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02010D002003F401, 0x0000000028000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{4, {v3_item(true, 0x0001000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02010D002003F401, 0x0000000028000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{6, {v3_item(true, 0x0002000000000000, 0x0000000000000000), v3_item(true, 0x0101020000000000, 0x0000000000000000), v3_item(true, 0x0102010000000000, 0x0000000000000000), v3_item(true, 0x0201100020032003, 0x0000000028000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{6, {v3_item(true, 0x0002000000000000, 0x0000000000000000), v3_item(true, 0x0101020000000000, 0x0000000000000000), v3_item(true, 0x0102010000000000, 0x0000000000000000), v3_item(true, 0x0201100020032003, 0x0000000028000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
@@ -671,7 +669,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
|||||||
{50, {v3_item(true, 0x02058200A00F8813, 0xD007D00728000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{50, {v3_item(true, 0x02058200A00F8813, 0xD007D00728000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{99, {v3_item(true, 0x0205BE007017581B, 0xB80BB80B28000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{99, {v3_item(true, 0x0205BE007017581B, 0xB80BB80B28000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
});
|
});
|
||||||
static const vector<ChallengeTemplateDefinition> v3_ranger_templates({
|
static const std::vector<ChallengeTemplateDefinition> v3_ranger_templates({
|
||||||
{0, {v3_item(true, 0x0006000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{0, {v3_item(true, 0x0006000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{4, {v3_item(true, 0x0006000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x020D0C00F401C800, 0xF401000028000012), v3_item(false, 0x0300000000050000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0308000000050000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{4, {v3_item(true, 0x0006000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x020D0C00F401C800, 0xF401000028000012), v3_item(false, 0x0300000000050000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0308000000050000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{5, {v3_item(true, 0x0006000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102010000000000, 0x0000000000000000), v3_item(true, 0x020D0E00F401C800, 0xBC02000028000012), v3_item(false, 0x0300000000050000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0308000000050000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{5, {v3_item(true, 0x0006000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102010000000000, 0x0000000000000000), v3_item(true, 0x020D0E00F401C800, 0xBC02000028000012), v3_item(false, 0x0300000000050000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0308000000050000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
@@ -689,7 +687,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
|||||||
{50, {v3_item(true, 0x020C8C00B80BC409, 0x7017C40928000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{50, {v3_item(true, 0x020C8C00B80BC409, 0x7017C40928000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
{99, {v3_item(true, 0x0206B400B80BB80B, 0x2823B80B28000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
{99, {v3_item(true, 0x0206B400B80BB80B, 0x2823B80B28000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||||
});
|
});
|
||||||
static const vector<ChallengeTemplateDefinition> v3_force_templates({
|
static const std::vector<ChallengeTemplateDefinition> v3_force_templates({
|
||||||
{0, {v3_item(true, 0x000A000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000040000, 0x0000000000000000), v3_item(false, 0x0301000000040000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}}},
|
{0, {v3_item(true, 0x000A000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000040000, 0x0000000000000000), v3_item(false, 0x0301000000040000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}}},
|
||||||
{4, {v3_item(true, 0x000A000500000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02190D0020036400, 0x0000900128000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0301000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
{4, {v3_item(true, 0x000A000500000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02190D0020036400, 0x0000900128000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0301000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||||
{6, {v3_item(true, 0x000B000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02190F002003C800, 0x0000F40128000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0301000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
{6, {v3_item(true, 0x000B000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02190F002003C800, 0x0000F40128000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0301000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||||
@@ -716,7 +714,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
|||||||
} else if ((class_flags & 0xE0) == 0x80) {
|
} else if ((class_flags & 0xE0) == 0x80) {
|
||||||
return is_v1_or_v2(version) ? v2_force_templates.at(index) : v3_force_templates.at(index);
|
return is_v1_or_v2(version) ? v2_force_templates.at(index) : v3_force_templates.at(index);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid class flags on original player");
|
throw std::runtime_error("invalid class flags on original player");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+120
-82
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
#include "ChoiceSearch.hh"
|
#include "ChoiceSearch.hh"
|
||||||
#include "CommonFileFormats.hh"
|
#include "CommonFileFormats.hh"
|
||||||
#include "FileContentsCache.hh"
|
|
||||||
#include "ItemData.hh"
|
#include "ItemData.hh"
|
||||||
#include "LevelTable.hh"
|
#include "LevelTable.hh"
|
||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
@@ -24,64 +23,64 @@
|
|||||||
class Client;
|
class Client;
|
||||||
class ItemParameterTable;
|
class ItemParameterTable;
|
||||||
|
|
||||||
struct PlayerDispDataBB;
|
struct PlayerDispDataV4;
|
||||||
|
struct PlayerVisualConfigV4;
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
struct PlayerVisualConfigT {
|
struct PlayerVisualConfigSharedT {
|
||||||
/* 00 */ pstring<TextEncoding::ASCII, 0x10> name;
|
/* 00 */ parray<uint8_t, 8> unknown_a2;
|
||||||
/* 10 */ parray<uint8_t, 8> unknown_a2;
|
/* 08 */ U32T<BE> name_color = 0xFFFFFFFF; // ARGB
|
||||||
/* 18 */ U32T<BE> name_color = 0xFFFFFFFF; // ARGB
|
/* 0C */ uint8_t extra_model = 0;
|
||||||
/* 1C */ uint8_t extra_model = 0;
|
|
||||||
// Some NPCs can crash the client if the character's class is incorrect. To handle this, we save the affected fields
|
// Some NPCs can crash the client if the character's class is incorrect. To handle this, we save the affected fields
|
||||||
// in the unused bytes after extra_model. This is a newserv-specific extension; it appears the following 15 bytes
|
// in the unused bytes after extra_model. This is a newserv-specific extension; it appears the following 15 bytes
|
||||||
// were simply never used by Sega.
|
// were simply never used by Sega.
|
||||||
/* 1D */ uint8_t npc_saved_data_type = 0;
|
/* 0D */ uint8_t npc_saved_data_type = 0;
|
||||||
/* 1E */ uint8_t npc_saved_costume = 0;
|
/* 0E */ uint8_t npc_saved_costume = 0;
|
||||||
/* 1F */ uint8_t npc_saved_skin = 0;
|
/* 0F */ uint8_t npc_saved_skin = 0;
|
||||||
/* 20 */ uint8_t npc_saved_face = 0;
|
/* 10 */ uint8_t npc_saved_face = 0;
|
||||||
/* 21 */ uint8_t npc_saved_head = 0;
|
/* 11 */ uint8_t npc_saved_head = 0;
|
||||||
/* 22 */ uint8_t npc_saved_hair = 0;
|
/* 12 */ uint8_t npc_saved_hair = 0;
|
||||||
/* 23 */ uint8_t npc_saved_hair_r = 0;
|
/* 13 */ uint8_t npc_saved_hair_r = 0;
|
||||||
/* 24 */ uint8_t npc_saved_hair_g = 0;
|
/* 14 */ uint8_t npc_saved_hair_g = 0;
|
||||||
/* 25 */ uint8_t npc_saved_hair_b = 0;
|
/* 15 */ uint8_t npc_saved_hair_b = 0;
|
||||||
/* 26 */ parray<uint8_t, 2> unused;
|
/* 16 */ parray<uint8_t, 2> unused;
|
||||||
/* 28 */ F32T<BE> npc_saved_proportion_y = 0.0;
|
/* 18 */ F32T<BE> npc_saved_proportion_y = 0.0;
|
||||||
// See compute_name_color_checksum for details on how this is computed. If the value is incorrect, V1 and V2 will
|
// See compute_name_color_checksum for details on how this is computed. If the value is incorrect, V1 and V2 will
|
||||||
// ignore the name_color field and use the default color instead. This field is ignored on GC; on BB (and presumably
|
// ignore the name_color field and use the default color instead. This field is ignored on GC; on BB (and presumably
|
||||||
// Xbox), if this has a nonzero value, the "Change Name" option appears in the character selection menu.
|
// Xbox), if this has a nonzero value, the "Change Name" option appears in the character selection menu.
|
||||||
/* 2C */ U32T<BE> name_color_checksum = 0;
|
/* 1C */ U32T<BE> name_color_checksum = 0;
|
||||||
/* 30 */ uint8_t section_id = 0;
|
/* 20 */ uint8_t section_id = 0;
|
||||||
/* 31 */ uint8_t char_class = 0;
|
/* 21 */ uint8_t char_class = 0;
|
||||||
// validation_flags specifies that some parts of this structure are not valid and should be ignored. The bits are:
|
// validation_flags specifies that some parts of this structure are not valid and should be ignored. The bits are:
|
||||||
// -----FCS
|
// -----FCS
|
||||||
// F = class_flags is incorrect for the character's char_class value
|
// F = class_flags is incorrect for the character's char_class value
|
||||||
// C = char_class is out of range
|
// C = char_class is out of range
|
||||||
// S = section_id is out of range
|
// S = section_id is out of range
|
||||||
/* 32 */ uint8_t validation_flags = 0;
|
/* 22 */ uint8_t validation_flags = 0;
|
||||||
/* 33 */ uint8_t version = 0;
|
/* 23 */ uint8_t version = 0;
|
||||||
// class_flags specifies features of the character's class. The bits are:
|
// class_flags specifies features of the character's class. The bits are:
|
||||||
// -------- -------- -------- FRHANMfm
|
// -------- -------- -------- FRHANMfm
|
||||||
// F = force, R = ranger, H = hunter
|
// F = force, R = ranger, H = hunter
|
||||||
// A = android, N = newman, M = human
|
// A = android, N = newman, M = human
|
||||||
// f = female, m = male
|
// f = female, m = male
|
||||||
// Enemies also have a class_flags field, though it isn't part of PlayerVisualConfig. The bits for enemies are:
|
// Enemies also have a class_flags field, though it isn't part of PlayerVisualConfigV123T. The bits for enemies are:
|
||||||
// -------- -------- -------- ----DMAN
|
// -------- -------- -------- ----DMAN
|
||||||
// D = Dark attribute
|
// D = Dark attribute
|
||||||
// M = Machine attribute
|
// M = Machine attribute
|
||||||
// A = Altered Beast attribute
|
// A = Altered Beast attribute
|
||||||
// N = Native attribute
|
// N = Native attribute
|
||||||
/* 34 */ U32T<BE> class_flags = 0;
|
/* 24 */ U32T<BE> class_flags = 0;
|
||||||
/* 38 */ U16T<BE> costume = 0;
|
/* 28 */ U16T<BE> costume = 0;
|
||||||
/* 3A */ U16T<BE> skin = 0;
|
/* 2A */ U16T<BE> skin = 0;
|
||||||
/* 3C */ U16T<BE> face = 0;
|
/* 2C */ U16T<BE> face = 0;
|
||||||
/* 3E */ U16T<BE> head = 0;
|
/* 2E */ U16T<BE> head = 0;
|
||||||
/* 40 */ U16T<BE> hair = 0;
|
/* 30 */ U16T<BE> hair = 0;
|
||||||
/* 42 */ U16T<BE> hair_r = 0;
|
/* 32 */ U16T<BE> hair_r = 0;
|
||||||
/* 44 */ U16T<BE> hair_g = 0;
|
/* 34 */ U16T<BE> hair_g = 0;
|
||||||
/* 46 */ U16T<BE> hair_b = 0;
|
/* 36 */ U16T<BE> hair_b = 0;
|
||||||
/* 48 */ F32T<BE> proportion_x = 0.0;
|
/* 38 */ F32T<BE> proportion_x = 0.0;
|
||||||
/* 4C */ F32T<BE> proportion_y = 0.0;
|
/* 3C */ F32T<BE> proportion_y = 0.0;
|
||||||
/* 50 */
|
/* 40 */
|
||||||
|
|
||||||
static uint32_t compute_name_color_checksum(uint32_t name_color) {
|
static uint32_t compute_name_color_checksum(uint32_t name_color) {
|
||||||
uint8_t x = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
uint8_t x = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||||
@@ -284,12 +283,10 @@ struct PlayerVisualConfigT {
|
|||||||
this->name_color_checksum = 0;
|
this->name_color_checksum = 0;
|
||||||
}
|
}
|
||||||
this->class_flags = class_flags_for_class(this->char_class);
|
this->class_flags = class_flags_for_class(this->char_class);
|
||||||
this->name.clear_after_bytes(0x0C);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator PlayerVisualConfigT<!BE>() const {
|
operator PlayerVisualConfigSharedT<!BE>() const {
|
||||||
PlayerVisualConfigT<!BE> ret;
|
PlayerVisualConfigSharedT<!BE> ret;
|
||||||
ret.name = this->name;
|
|
||||||
ret.unknown_a2 = this->unknown_a2;
|
ret.unknown_a2 = this->unknown_a2;
|
||||||
ret.name_color = this->name_color;
|
ret.name_color = this->name_color;
|
||||||
ret.extra_model = this->extra_model;
|
ret.extra_model = this->extra_model;
|
||||||
@@ -312,14 +309,65 @@ struct PlayerVisualConfigT {
|
|||||||
ret.proportion_y = this->proportion_y;
|
ret.proportion_y = this->proportion_y;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
} __packed_ws_be__(PlayerVisualConfigT, 0x50);
|
} __packed_ws_be__(PlayerVisualConfigSharedT, 0x40);
|
||||||
using PlayerVisualConfig = PlayerVisualConfigT<false>;
|
|
||||||
using PlayerVisualConfigBE = PlayerVisualConfigT<true>;
|
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
struct PlayerDispDataDCPCV3T {
|
struct PlayerVisualConfigV123T {
|
||||||
|
/* 00 */ pstring<TextEncoding::ASCII, 0x10> name;
|
||||||
|
/* 10 */ PlayerVisualConfigSharedT<BE> sh;
|
||||||
|
/* 50 */
|
||||||
|
|
||||||
|
operator PlayerVisualConfigV123T<!BE>() const {
|
||||||
|
PlayerVisualConfigV123T<!BE> ret;
|
||||||
|
ret.name = this->name;
|
||||||
|
ret.sh = this->sh;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enforce_lobby_join_limits_for_version(Version v) {
|
||||||
|
this->sh.enforce_lobby_join_limits_for_version(v);
|
||||||
|
this->name.clear_after_bytes(0x0C);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerVisualConfigV4 to_v4(Language to_language, Language from_language) const;
|
||||||
|
} __packed_ws_be__(PlayerVisualConfigV123T, 0x50);
|
||||||
|
using PlayerVisualConfigV123 = PlayerVisualConfigV123T<false>;
|
||||||
|
using PlayerVisualConfigV123BE = PlayerVisualConfigV123T<true>;
|
||||||
|
|
||||||
|
struct PlayerVisualConfigV4 {
|
||||||
|
/* 00 */ pstring<TextEncoding::ASCII, 0x10> guild_card_number;
|
||||||
|
/* 10 */ PlayerVisualConfigSharedT<false> sh;
|
||||||
|
/* 50 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
|
||||||
|
/* 70 */
|
||||||
|
|
||||||
|
void enforce_lobby_join_limits_for_version(Version v) {
|
||||||
|
this->sh.enforce_lobby_join_limits_for_version(v);
|
||||||
|
this->guild_card_number.clear_after_bytes(0x0C);
|
||||||
|
}
|
||||||
|
void apply_dressing_room(const PlayerVisualConfigV4& new_visual);
|
||||||
|
|
||||||
|
template <bool BE>
|
||||||
|
PlayerVisualConfigV123T<BE> to_v123(Language to_language, Language from_language) const {
|
||||||
|
PlayerVisualConfigV123T<BE> ret;
|
||||||
|
ret.name.encode(this->name.decode(from_language), to_language);
|
||||||
|
ret.sh = this->sh;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} __packed_ws__(PlayerVisualConfigV4, 0x70);
|
||||||
|
|
||||||
|
template <bool BE>
|
||||||
|
PlayerVisualConfigV4 PlayerVisualConfigV123T<BE>::to_v4(Language to_language, Language from_language) const {
|
||||||
|
PlayerVisualConfigV4 ret;
|
||||||
|
ret.guild_card_number.encode(" 0", Language::ENGLISH);
|
||||||
|
ret.sh = this->sh;
|
||||||
|
ret.name.encode(this->name.decode(from_language), to_language);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <bool BE>
|
||||||
|
struct PlayerDispDataV123T {
|
||||||
/* 00 */ PlayerStatsT<BE> stats;
|
/* 00 */ PlayerStatsT<BE> stats;
|
||||||
/* 24 */ PlayerVisualConfigT<BE> visual;
|
/* 24 */ PlayerVisualConfigV123T<BE> visual;
|
||||||
/* 74 */ parray<uint8_t, 0x48> config;
|
/* 74 */ parray<uint8_t, 0x48> config;
|
||||||
/* BC */ parray<uint8_t, 0x14> technique_levels_v1;
|
/* BC */ parray<uint8_t, 0x14> technique_levels_v1;
|
||||||
/* D0 */
|
/* D0 */
|
||||||
@@ -328,60 +376,50 @@ struct PlayerDispDataDCPCV3T {
|
|||||||
this->visual.enforce_lobby_join_limits_for_version(v);
|
this->visual.enforce_lobby_join_limits_for_version(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerDispDataBB to_bb(Language to_language, Language from_language) const;
|
PlayerDispDataV4 to_v4(Language to_language, Language from_language) const;
|
||||||
} __packed_ws_be__(PlayerDispDataDCPCV3T, 0xD0);
|
} __packed_ws_be__(PlayerDispDataV123T, 0xD0);
|
||||||
using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T<false>;
|
using PlayerDispDataV123 = PlayerDispDataV123T<false>;
|
||||||
using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T<true>;
|
using PlayerDispDataV123BE = PlayerDispDataV123T<true>;
|
||||||
|
|
||||||
struct PlayerDispDataBBPreview {
|
struct PlayerDispDataV4Preview {
|
||||||
/* 00 */ le_uint32_t exp = 0;
|
/* 00 */ le_uint32_t exp = 0;
|
||||||
/* 04 */ le_uint32_t level = 0;
|
/* 04 */ le_uint32_t level = 0;
|
||||||
// The name field in this structure is used for the player's Guild Card number, apparently (possibly because it's a
|
// The name field in this structure is used for the player's Guild Card number, apparently (possibly because it's a
|
||||||
// char array and this is BB)
|
// char array and this is BB)
|
||||||
/* 08 */ PlayerVisualConfig visual;
|
/* 08 */ PlayerVisualConfigV4 visual;
|
||||||
/* 58 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
|
|
||||||
/* 78 */ uint32_t play_time_seconds = 0;
|
/* 78 */ uint32_t play_time_seconds = 0;
|
||||||
/* 7C */
|
/* 7C */
|
||||||
} __packed_ws__(PlayerDispDataBBPreview, 0x7C);
|
} __packed_ws__(PlayerDispDataV4Preview, 0x7C);
|
||||||
|
|
||||||
// BB player appearance and stats data
|
// BB player appearance and stats data
|
||||||
struct PlayerDispDataBB {
|
struct PlayerDispDataV4 {
|
||||||
/* 0000 */ PlayerStats stats;
|
/* 0000 */ PlayerStats stats;
|
||||||
/* 0024 */ PlayerVisualConfig visual;
|
/* 0024 */ PlayerVisualConfigV4 visual;
|
||||||
/* 0074 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
|
|
||||||
/* 0094 */ parray<uint8_t, 0xE8> config;
|
/* 0094 */ parray<uint8_t, 0xE8> config;
|
||||||
/* 017C */ parray<uint8_t, 0x14> technique_levels_v1;
|
/* 017C */ parray<uint8_t, 0x14> technique_levels_v1;
|
||||||
/* 0190 */
|
/* 0190 */
|
||||||
|
|
||||||
void enforce_lobby_join_limits_for_version(Version v) {
|
void enforce_lobby_join_limits_for_version(Version v) {
|
||||||
this->visual.enforce_lobby_join_limits_for_version(v);
|
this->visual.sh.enforce_lobby_join_limits_for_version(v);
|
||||||
this->name.clear_after_bytes(0x18); // 12 characters
|
this->visual.name.clear_after_bytes(0x18); // 12 characters
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
PlayerDispDataDCPCV3T<BE> to_dcpcv3(Language to_language, Language from_language) const {
|
PlayerDispDataV123T<BE> to_v123(Language to_language, Language from_language) const {
|
||||||
PlayerDispDataDCPCV3T<BE> ret;
|
PlayerDispDataV123T<BE> ret;
|
||||||
ret.stats = this->stats;
|
ret.stats = this->stats;
|
||||||
ret.visual = this->visual;
|
ret.visual = this->visual.to_v123<BE>(to_language, from_language);
|
||||||
std::string decoded_name = this->name.decode(from_language);
|
|
||||||
ret.visual.name.encode(decoded_name, to_language);
|
|
||||||
ret.config = this->config;
|
ret.config = this->config;
|
||||||
ret.technique_levels_v1 = this->technique_levels_v1;
|
ret.technique_levels_v1 = this->technique_levels_v1;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
} __packed_ws__(PlayerDispDataV4, 0x190);
|
||||||
void apply_preview(const PlayerDispDataBBPreview&);
|
|
||||||
void apply_dressing_room(const PlayerDispDataBBPreview&);
|
|
||||||
} __packed_ws__(PlayerDispDataBB, 0x190);
|
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
PlayerDispDataBB PlayerDispDataDCPCV3T<BE>::to_bb(Language to_language, Language from_language) const {
|
PlayerDispDataV4 PlayerDispDataV123T<BE>::to_v4(Language to_language, Language from_language) const {
|
||||||
PlayerDispDataBB bb;
|
PlayerDispDataV4 bb;
|
||||||
bb.stats = this->stats;
|
bb.stats = this->stats;
|
||||||
bb.visual = this->visual;
|
bb.visual = this->visual.to_v4(to_language, from_language);
|
||||||
bb.visual.name.encode(" 0");
|
|
||||||
std::string decoded_name = this->visual.name.decode(from_language);
|
|
||||||
bb.name.encode(decoded_name, to_language);
|
|
||||||
bb.config = this->config;
|
bb.config = this->config;
|
||||||
bb.technique_levels_v1 = this->technique_levels_v1;
|
bb.technique_levels_v1 = this->technique_levels_v1;
|
||||||
return bb;
|
return bb;
|
||||||
@@ -835,25 +873,25 @@ DestT convert_player_disp_data(const SrcT&, Language, Language) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3>(
|
inline PlayerDispDataV123 convert_player_disp_data<PlayerDispDataV123>(
|
||||||
const PlayerDispDataDCPCV3& src, Language, Language) {
|
const PlayerDispDataV123& src, Language, Language) {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(
|
inline PlayerDispDataV123 convert_player_disp_data<PlayerDispDataV123, PlayerDispDataV4>(
|
||||||
const PlayerDispDataBB& src, Language to_language, Language from_language) {
|
const PlayerDispDataV4& src, Language to_language, Language from_language) {
|
||||||
return src.to_dcpcv3<false>(to_language, from_language);
|
return src.to_v123<false>(to_language, from_language);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataDCPCV3>(
|
inline PlayerDispDataV4 convert_player_disp_data<PlayerDispDataV4, PlayerDispDataV123>(
|
||||||
const PlayerDispDataDCPCV3& src, Language to_language, Language from_language) {
|
const PlayerDispDataV123& src, Language to_language, Language from_language) {
|
||||||
return src.to_bb(to_language, from_language);
|
return src.to_v4(to_language, from_language);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(const PlayerDispDataBB& src, Language, Language) {
|
inline PlayerDispDataV4 convert_player_disp_data<PlayerDispDataV4>(const PlayerDispDataV4& src, Language, Language) {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+143
-144
@@ -29,17 +29,15 @@
|
|||||||
#include "ReceiveSubcommands.hh"
|
#include "ReceiveSubcommands.hh"
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
enum class HandlerResult {
|
enum class HandlerResult {
|
||||||
FORWARD = 0,
|
FORWARD = 0,
|
||||||
SUPPRESS,
|
SUPPRESS,
|
||||||
MODIFIED,
|
MODIFIED,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef asio::awaitable<HandlerResult> (*MessageHandler)(shared_ptr<Client> c, Channel::Message& msg);
|
typedef asio::awaitable<HandlerResult> (*MessageHandler)(std::shared_ptr<Client> c, Channel::Message& msg);
|
||||||
|
|
||||||
static void forward_command(shared_ptr<Client> c, bool to_server, const Channel::Message& msg, bool print_contents = true) {
|
static void forward_command(std::shared_ptr<Client> c, bool to_server, const Channel::Message& msg, bool print_contents = true) {
|
||||||
auto ch = to_server ? (c->proxy_session ? c->proxy_session->server_channel : nullptr) : c->channel;
|
auto ch = to_server ? (c->proxy_session ? c->proxy_session->server_channel : nullptr) : c->channel;
|
||||||
if (!ch || !ch->connected()) {
|
if (!ch || !ch->connected()) {
|
||||||
proxy_server_log.warning_f("No endpoint is present; dropping command");
|
proxy_server_log.warning_f("No endpoint is present; dropping command");
|
||||||
@@ -55,20 +53,20 @@ static void forward_command(shared_ptr<Client> c, bool to_server, const Channel:
|
|||||||
// versions), and COMMAND-NUMBERS are the hexadecimal value in the command header field that this handler is called
|
// versions), and COMMAND-NUMBERS are the hexadecimal value in the command header field that this handler is called
|
||||||
// for. If VERSIONS is omitted, the command handler is for all versions (for example, the 97 handler is like this).
|
// for. If VERSIONS is omitted, the command handler is for all versions (for example, the 97 handler is like this).
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> default_handler(shared_ptr<Client>, Channel::Message&) {
|
static asio::awaitable<HandlerResult> default_handler(std::shared_ptr<Client>, Channel::Message&) {
|
||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_invalid(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_invalid(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
c->log.error_f("Server sent invalid command");
|
c->log.error_f("Server sent invalid command");
|
||||||
string error_str = is_v4(c->version())
|
std::string error_str = is_v4(c->version())
|
||||||
? std::format("Server sent invalid\ncommand: {:04X} {:08X}", msg.command, msg.flag)
|
? std::format("Server sent invalid\ncommand: {:04X} {:08X}", msg.command, msg.flag)
|
||||||
: std::format("Server sent invalid\ncommand: {:02X} {:02X}", msg.command, msg.flag);
|
: std::format("Server sent invalid\ncommand: {:02X} {:02X}", msg.command, msg.flag);
|
||||||
c->proxy_session->server_channel->disconnect();
|
c->proxy_session->server_channel->disconnect();
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_1D(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> C_1D(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
if (c->ping_start_time) {
|
if (c->ping_start_time) {
|
||||||
uint64_t ping_usecs = phosg::now() - c->ping_start_time;
|
uint64_t ping_usecs = phosg::now() - c->ping_start_time;
|
||||||
c->ping_start_time = 0;
|
c->ping_start_time = 0;
|
||||||
@@ -83,11 +81,11 @@ static asio::awaitable<HandlerResult> C_1D(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_1D(shared_ptr<Client>, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_1D(std::shared_ptr<Client>, Channel::Message&) {
|
||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_97(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_97(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
// We always assume a 97 has already been received by the client - we should have sent 97 01 before sending the
|
// We always assume a 97 has already been received by the client - we should have sent 97 01 before sending the
|
||||||
// client to the proxy server.
|
// client to the proxy server.
|
||||||
c->proxy_session->server_channel->send(0xB1, 0x00);
|
c->proxy_session->server_channel->send(0xB1, 0x00);
|
||||||
@@ -207,7 +205,7 @@ static void send_9E_XB_to_server(std::shared_ptr<Client> c) {
|
|||||||
c->proxy_session->server_channel->send(0x9E, 0x01, &cmd, sizeof(C_LoginExtended_XB_9E));
|
c->proxy_session->server_channel->send(0x9E, 0x01, &cmd, sizeof(C_LoginExtended_XB_9E));
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_9A(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_G_9A(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
// TODO: Either delete this handler or finish implementing it (flag=00/02 should do the below, 01 should send 9C,
|
// TODO: Either delete this handler or finish implementing it (flag=00/02 should do the below, 01 should send 9C,
|
||||||
// anything else should end the session)
|
// anything else should end the session)
|
||||||
C_LoginExtended_GC_9E cmd;
|
C_LoginExtended_GC_9E cmd;
|
||||||
@@ -239,9 +237,9 @@ static asio::awaitable<HandlerResult> S_G_9A(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_V123U_02_17(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (is_patch(c->version()) && msg.command == 0x17) {
|
if (is_patch(c->version()) && msg.command == 0x17) {
|
||||||
throw invalid_argument("patch server sent 17 server init");
|
throw std::invalid_argument("patch server sent 17 server init");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Most servers don't include after_message or have a shorter after_message than newserv does, so don't require it
|
// Most servers don't include after_message or have a shorter after_message than newserv does, so don't require it
|
||||||
@@ -249,11 +247,11 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
|||||||
|
|
||||||
// This isn't forwarded to the client, so don't recreate the client's crypts
|
// This isn't forwarded to the client, so don't recreate the client's crypts
|
||||||
if (uses_v3_encryption(c->version())) {
|
if (uses_v3_encryption(c->version())) {
|
||||||
c->proxy_session->server_channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||||
c->proxy_session->server_channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||||
} else {
|
} else {
|
||||||
c->proxy_session->server_channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||||
c->proxy_session->server_channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with an appropriate login command. We don't let the client do this because it believes it already did
|
// Respond with an appropriate login command. We don't let the client do this because it believes it already did
|
||||||
@@ -267,7 +265,7 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
|||||||
|
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
// TODO
|
// TODO
|
||||||
throw runtime_error("DC NTE proxy is not implemented");
|
throw std::runtime_error("DC NTE proxy is not implemented");
|
||||||
|
|
||||||
case Version::DC_11_2000:
|
case Version::DC_11_2000:
|
||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
@@ -304,7 +302,7 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
|||||||
// For command 02, send the same as if we had received 9A from the server
|
// For command 02, send the same as if we had received 9A from the server
|
||||||
co_return co_await S_G_9A(c, msg);
|
co_return co_await S_G_9A(c, msg);
|
||||||
}
|
}
|
||||||
throw logic_error("GC init command not handled");
|
throw std::logic_error("GC init command not handled");
|
||||||
|
|
||||||
case Version::XB_V3: {
|
case Version::XB_V3: {
|
||||||
send_9E_XB_to_server(c);
|
send_9E_XB_to_server(c);
|
||||||
@@ -312,13 +310,13 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
throw logic_error("v1/v2/v3 server init handler should not be called on BB");
|
throw std::logic_error("v1/v2/v3 server init handler should not be called on BB");
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid game version in server init handler");
|
throw std::logic_error("invalid game version in server init handler");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_U_04(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_U_04(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
C_Login_Patch_04 ret;
|
C_Login_Patch_04 ret;
|
||||||
ret.username.encode(c->username);
|
ret.username.encode(c->username);
|
||||||
ret.password.encode(c->password);
|
ret.password.encode(c->password);
|
||||||
@@ -327,7 +325,7 @@ static asio::awaitable<HandlerResult> S_U_04(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_03(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_03(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// Most servers don't include after_message or have a shorter after_message than newserv does, so don't require it
|
// Most servers don't include after_message or have a shorter after_message than newserv does, so don't require it
|
||||||
const auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
const auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||||
|
|
||||||
@@ -335,11 +333,11 @@ static asio::awaitable<HandlerResult> S_B_03(shared_ptr<Client> c, Channel::Mess
|
|||||||
// the server has the luxury of being able to try all the crypts it knows to detect what type the client uses, but
|
// the server has the luxury of being able to try all the crypts it knows to detect what type the client uses, but
|
||||||
// the client can't do this since it sends the first encrypted data on the connection.
|
// the client can't do this since it sends the first encrypted data on the connection.
|
||||||
if (!c->bb_detector_crypt) {
|
if (!c->bb_detector_crypt) {
|
||||||
throw logic_error("Client proxy session started with missing detector crypt");
|
throw std::logic_error("Client proxy session started with missing detector crypt");
|
||||||
}
|
}
|
||||||
c->proxy_session->server_channel->crypt_in = make_shared<PSOBBMultiKeyImitatorEncryption>(
|
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOBBMultiKeyImitatorEncryption>(
|
||||||
c->bb_detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false);
|
c->bb_detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false);
|
||||||
c->proxy_session->server_channel->crypt_out = make_shared<PSOBBMultiKeyImitatorEncryption>(
|
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOBBMultiKeyImitatorEncryption>(
|
||||||
c->bb_detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false);
|
c->bb_detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false);
|
||||||
|
|
||||||
C_LoginWithHardwareInfo_BB_93 resp;
|
C_LoginWithHardwareInfo_BB_93 resp;
|
||||||
@@ -363,7 +361,7 @@ static asio::awaitable<HandlerResult> S_B_03(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_E6(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_E6(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
const auto& cmd = msg.check_size_t<S_ClientInit_BB_00E6>(0xFFFF);
|
const auto& cmd = msg.check_size_t<S_ClientInit_BB_00E6>(0xFFFF);
|
||||||
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
|
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
|
||||||
c->bb_security_token = cmd.security_token;
|
c->bb_security_token = cmd.security_token;
|
||||||
@@ -379,7 +377,7 @@ static asio::awaitable<HandlerResult> S_B_E6(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_V123_04(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// Suppress extremely short commands from the server instead of disconnecting
|
// Suppress extremely short commands from the server instead of disconnecting
|
||||||
if (msg.data.size() < offsetof(S_UpdateClientConfig_V3_04, client_config)) {
|
if (msg.data.size() < offsetof(S_UpdateClientConfig_V3_04, client_config)) {
|
||||||
le_uint64_t checksum = phosg::random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
|
le_uint64_t checksum = phosg::random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
|
||||||
@@ -399,7 +397,7 @@ static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::M
|
|||||||
if (c->proxy_session->remote_guild_card_number != cmd.guild_card_number) {
|
if (c->proxy_session->remote_guild_card_number != cmd.guild_card_number) {
|
||||||
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
|
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
|
||||||
c->log.info_f("Remote guild card number set to {}", c->proxy_session->remote_guild_card_number);
|
c->log.info_f("Remote guild card number set to {}", c->proxy_session->remote_guild_card_number);
|
||||||
string message = std::format(
|
std::string message = std::format(
|
||||||
"The remote server\nhas assigned your\nGuild Card number:\n$C6{}", c->proxy_session->remote_guild_card_number);
|
"The remote server\nhas assigned your\nGuild Card number:\n$C6{}", c->proxy_session->remote_guild_card_number);
|
||||||
send_ship_info(c->channel, message);
|
send_ship_info(c->channel, message);
|
||||||
}
|
}
|
||||||
@@ -414,7 +412,7 @@ static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::M
|
|||||||
memcpy(c->proxy_session->remote_client_config_data.data(),
|
memcpy(c->proxy_session->remote_client_config_data.data(),
|
||||||
had_guild_card_number ? "t Lobby Server. Copyright SEGA E" : "t Port Map. Copyright SEGA Enter", 0x20);
|
had_guild_card_number ? "t Lobby Server. Copyright SEGA E" : "t Port Map. Copyright SEGA Enter", 0x20);
|
||||||
memcpy(c->proxy_session->remote_client_config_data.data(), &cmd.client_config,
|
memcpy(c->proxy_session->remote_client_config_data.data(), &cmd.client_config,
|
||||||
min<size_t>(msg.data.size() - offsetof(S_UpdateClientConfig_V3_04, client_config),
|
std::min<size_t>(msg.data.size() - offsetof(S_UpdateClientConfig_V3_04, client_config),
|
||||||
c->proxy_session->remote_client_config_data.bytes()));
|
c->proxy_session->remote_client_config_data.bytes()));
|
||||||
|
|
||||||
// If the guild card number was not set, pretend (to the server) that this is the first 04 command the client has
|
// If the guild card number was not set, pretend (to the server) that this is the first 04 command the client has
|
||||||
@@ -427,7 +425,7 @@ static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::M
|
|||||||
co_return c->login ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return c->login ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_V123_06(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_V123_06(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
auto& cmd = msg.check_size_t<SC_TextHeader_01_06_11_B0_EE>(0xFFFF);
|
auto& cmd = msg.check_size_t<SC_TextHeader_01_06_11_B0_EE>(0xFFFF);
|
||||||
@@ -452,7 +450,7 @@ static asio::awaitable<HandlerResult> S_V123_06(shared_ptr<Client> c, Channel::M
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
static asio::awaitable<HandlerResult> S_41(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_41(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->login) {
|
if (c->login) {
|
||||||
auto& cmd = msg.check_size_t<CmdT>();
|
auto& cmd = msg.check_size_t<CmdT>();
|
||||||
if ((cmd.searcher_guild_card_number == c->proxy_session->remote_guild_card_number) &&
|
if ((cmd.searcher_guild_card_number == c->proxy_session->remote_guild_card_number) &&
|
||||||
@@ -488,7 +486,7 @@ constexpr MessageHandler S_P_41 = &S_41<S_GuildCardSearchResult_PC_41>;
|
|||||||
constexpr MessageHandler S_B_41 = &S_41<S_GuildCardSearchResult_BB_41>;
|
constexpr MessageHandler S_B_41 = &S_41<S_GuildCardSearchResult_BB_41>;
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
static asio::awaitable<HandlerResult> S_81(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_81(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
auto& cmd = msg.check_size_t<CmdT>();
|
auto& cmd = msg.check_size_t<CmdT>();
|
||||||
@@ -508,7 +506,7 @@ constexpr MessageHandler S_DGX_81 = &S_81<SC_SimpleMail_DC_V3_81>;
|
|||||||
constexpr MessageHandler S_P_81 = &S_81<SC_SimpleMail_PC_81>;
|
constexpr MessageHandler S_P_81 = &S_81<SC_SimpleMail_PC_81>;
|
||||||
constexpr MessageHandler S_B_81 = &S_81<SC_SimpleMail_BB_81>;
|
constexpr MessageHandler S_B_81 = &S_81<SC_SimpleMail_BB_81>;
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_88(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_88(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// If the client isn't in the lobby, suppress the command (Ep3 can crash if it receives this while loading; other
|
// If the client isn't in the lobby, suppress the command (Ep3 can crash if it receives this while loading; other
|
||||||
// versions probably also will crash)
|
// versions probably also will crash)
|
||||||
if (!c->proxy_session->is_in_lobby) {
|
if (!c->proxy_session->is_in_lobby) {
|
||||||
@@ -529,19 +527,19 @@ static asio::awaitable<HandlerResult> S_88(shared_ptr<Client> c, Channel::Messag
|
|||||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B1(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_B1(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
// Block all time updates from the remote server, so client's time remains consistent
|
// Block all time updates from the remote server, so client's time remains consistent
|
||||||
c->proxy_session->server_channel->send(0x99, 0x00);
|
c->proxy_session->server_channel->send(0x99, 0x00);
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool BE>
|
template <bool BE>
|
||||||
static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B2(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
const auto& cmd = msg.check_size_t<S_ExecuteCode_B2>(0xFFFF);
|
const auto& cmd = msg.check_size_t<S_ExecuteCode_B2>(0xFFFF);
|
||||||
|
|
||||||
if (cmd.code_size && c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (cmd.code_size && c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
uint64_t filename_timestamp = phosg::now();
|
uint64_t filename_timestamp = phosg::now();
|
||||||
string code = msg.data.substr(sizeof(S_ExecuteCode_B2));
|
std::string code = msg.data.substr(sizeof(S_ExecuteCode_B2));
|
||||||
|
|
||||||
if (c->check_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL)) {
|
if (c->check_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL)) {
|
||||||
phosg::StringReader r(code);
|
phosg::StringReader r(code);
|
||||||
@@ -550,7 +548,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l();
|
uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l();
|
||||||
|
|
||||||
PSOV2Encryption crypt(key);
|
PSOV2Encryption crypt(key);
|
||||||
string decrypted_data;
|
std::string decrypted_data;
|
||||||
if (is_big_endian) {
|
if (is_big_endian) {
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
while (!r.eof()) {
|
while (!r.eof()) {
|
||||||
@@ -566,7 +564,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
if (decompressed_size < code.size()) {
|
if (decompressed_size < code.size()) {
|
||||||
code.resize(decompressed_size);
|
code.resize(decompressed_size);
|
||||||
} else if (decompressed_size > code.size()) {
|
} else if (decompressed_size > code.size()) {
|
||||||
throw runtime_error("decompressed code smaller than expected");
|
throw std::runtime_error("decompressed code smaller than expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -576,7 +574,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string output_filename = std::format("code.{}.bin", filename_timestamp);
|
std::string output_filename = std::format("code.{}.bin", filename_timestamp);
|
||||||
phosg::save_file(output_filename, msg.data);
|
phosg::save_file(output_filename, msg.data);
|
||||||
c->log.info_f("Wrote code from server to file {}", output_filename);
|
c->log.info_f("Wrote code from server to file {}", output_filename);
|
||||||
|
|
||||||
@@ -588,7 +586,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
if (is_ppc || is_x86 || is_sh4) {
|
if (is_ppc || is_x86 || is_sh4) {
|
||||||
try {
|
try {
|
||||||
if (code.size() < sizeof(FooterT)) {
|
if (code.size() < sizeof(FooterT)) {
|
||||||
throw runtime_error("code section is too small");
|
throw std::runtime_error("code section is too small");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t footer_offset = code.size() - sizeof(FooterT);
|
size_t footer_offset = code.size() - sizeof(FooterT);
|
||||||
@@ -596,7 +594,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
phosg::StringReader r(code.data(), code.size());
|
phosg::StringReader r(code.data(), code.size());
|
||||||
const auto& footer = r.pget<FooterT>(footer_offset);
|
const auto& footer = r.pget<FooterT>(footer_offset);
|
||||||
|
|
||||||
multimap<uint32_t, string> labels;
|
std::multimap<uint32_t, std::string> labels;
|
||||||
r.go(footer.relocations_offset);
|
r.go(footer.relocations_offset);
|
||||||
uint32_t reloc_offset = 0;
|
uint32_t reloc_offset = 0;
|
||||||
for (size_t x = 0; x < footer.num_relocations; x++) {
|
for (size_t x = 0; x < footer.num_relocations; x++) {
|
||||||
@@ -607,7 +605,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
labels.emplace(footer_offset, "footer");
|
labels.emplace(footer_offset, "footer");
|
||||||
labels.emplace(r.pget<U32T<BE>>(footer.root_offset), "start");
|
labels.emplace(r.pget<U32T<BE>>(footer.root_offset), "start");
|
||||||
|
|
||||||
string disassembly;
|
std::string disassembly;
|
||||||
if (is_ppc) {
|
if (is_ppc) {
|
||||||
disassembly = ResourceDASM::PPC32Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
|
disassembly = ResourceDASM::PPC32Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
|
||||||
} else if (is_x86) {
|
} else if (is_x86) {
|
||||||
@@ -616,7 +614,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
disassembly = ResourceDASM::SH4Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
|
disassembly = ResourceDASM::SH4Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
|
||||||
} else {
|
} else {
|
||||||
// We shouldn't have entered the outer if statement if this happens
|
// We shouldn't have entered the outer if statement if this happens
|
||||||
throw logic_error("unsupported architecture");
|
throw std::logic_error("unsupported architecture");
|
||||||
}
|
}
|
||||||
|
|
||||||
output_filename = std::format("code.{}.txt", filename_timestamp);
|
output_filename = std::format("code.{}.txt", filename_timestamp);
|
||||||
@@ -629,7 +627,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
c->log.info_f("Wrote disassembly to file {}", output_filename);
|
c->log.info_f("Wrote disassembly to file {}", output_filename);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.info_f("Failed to disassemble code from server: {}", e.what());
|
c->log.info_f("Failed to disassemble code from server: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,10 +646,10 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_B3(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_B3(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
auto cmd = msg.check_size_t<C_ExecuteCodeResult_B3>();
|
auto cmd = msg.check_size_t<C_ExecuteCodeResult_B3>();
|
||||||
|
|
||||||
shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>> promise;
|
std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>> promise;
|
||||||
if (!c->function_call_response_queue.empty()) {
|
if (!c->function_call_response_queue.empty()) {
|
||||||
promise = std::move(c->function_call_response_queue.front());
|
promise = std::move(c->function_call_response_queue.front());
|
||||||
c->function_call_response_queue.pop_front();
|
c->function_call_response_queue.pop_front();
|
||||||
@@ -665,18 +663,18 @@ static asio::awaitable<HandlerResult> C_B3(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_B_E0(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> C_B_E0(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
auto ret = c->proxy_session->bb_client_sent_E0 ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
auto ret = c->proxy_session->bb_client_sent_E0 ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
||||||
c->proxy_session->bb_client_sent_E0 = true;
|
c->proxy_session->bb_client_sent_E0 = true;
|
||||||
co_return ret;
|
co_return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_E2(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_E2(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
const auto& cmd = msg.check_size_t<S_SyncSystemFile_BB_E2>();
|
const auto& cmd = msg.check_size_t<S_SyncSystemFile_BB_E2>();
|
||||||
uint64_t ts = phosg::now();
|
uint64_t ts = phosg::now();
|
||||||
string system_filename = std::format("system.{}.psosys", ts);
|
std::string system_filename = std::format("system.{}.psosys", ts);
|
||||||
string team_membership_filename = std::format("system.{}.psosysteam", ts);
|
std::string team_membership_filename = std::format("system.{}.psosysteam", ts);
|
||||||
phosg::save_object_file(system_filename, cmd.system_file);
|
phosg::save_object_file(system_filename, cmd.system_file);
|
||||||
phosg::save_object_file(team_membership_filename, cmd.team_membership);
|
phosg::save_object_file(team_membership_filename, cmd.team_membership);
|
||||||
c->log.info_f("Wrote system file to {}", system_filename);
|
c->log.info_f("Wrote system file to {}", system_filename);
|
||||||
@@ -685,9 +683,9 @@ static asio::awaitable<HandlerResult> S_B_E2(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_E7(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_E7(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
string output_filename = std::format("player.{}.psochar", phosg::now());
|
std::string output_filename = std::format("player.{}.psochar", phosg::now());
|
||||||
auto f = phosg::fopen_unique(output_filename, "wb");
|
auto f = phosg::fopen_unique(output_filename, "wb");
|
||||||
PSOCommandHeaderBB header = {msg.data.size() + sizeof(PSOCommandHeaderBB), msg.command, msg.flag};
|
PSOCommandHeaderBB header = {msg.data.size() + sizeof(PSOCommandHeaderBB), msg.command, msg.flag};
|
||||||
phosg::fwritex(f.get(), &header, sizeof(header));
|
phosg::fwritex(f.get(), &header, sizeof(header));
|
||||||
@@ -697,7 +695,7 @@ static asio::awaitable<HandlerResult> S_B_E7(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_DC(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_DC(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES) && (msg.command == 0x02DC)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES) && (msg.command == 0x02DC)) {
|
||||||
const auto& cmd = msg.check_size_t<S_GuildCardFileChunk_02DC>(8, sizeof(S_GuildCardFileChunk_02DC));
|
const auto& cmd = msg.check_size_t<S_GuildCardFileChunk_02DC>(8, sizeof(S_GuildCardFileChunk_02DC));
|
||||||
size_t chunk_size = msg.data.size() - 8;
|
size_t chunk_size = msg.data.size() - 8;
|
||||||
@@ -719,11 +717,11 @@ static asio::awaitable<HandlerResult> S_B_DC(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_B_DC(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_B_DC(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES) && (msg.command == 0x03DC)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES) && (msg.command == 0x03DC)) {
|
||||||
const auto& cmd = msg.check_size_t<C_GuildCardDataRequest_BB_03DC>();
|
const auto& cmd = msg.check_size_t<C_GuildCardDataRequest_BB_03DC>();
|
||||||
if ((cmd.cont == 0) && c->proxy_session->bb_guild_card_data) {
|
if ((cmd.cont == 0) && c->proxy_session->bb_guild_card_data) {
|
||||||
string output_filename = std::format("guildcard.{}.psocard", phosg::now());
|
std::string output_filename = std::format("guildcard.{}.psocard", phosg::now());
|
||||||
phosg::save_object_file(output_filename, *c->proxy_session->bb_guild_card_data);
|
phosg::save_object_file(output_filename, *c->proxy_session->bb_guild_card_data);
|
||||||
c->log.info_f("Wrote Guild Card data to {}", output_filename);
|
c->log.info_f("Wrote Guild Card data to {}", output_filename);
|
||||||
}
|
}
|
||||||
@@ -731,7 +729,7 @@ static asio::awaitable<HandlerResult> C_B_DC(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_EB(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_EB(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
if (msg.command == 0x01EB) {
|
if (msg.command == 0x01EB) {
|
||||||
const auto* entries = &msg.check_size_t<S_StreamFileIndexEntry_BB_01EB>(
|
const auto* entries = &msg.check_size_t<S_StreamFileIndexEntry_BB_01EB>(
|
||||||
@@ -761,7 +759,7 @@ static asio::awaitable<HandlerResult> S_B_EB(shared_ptr<Client> c, Channel::Mess
|
|||||||
memcpy(c->proxy_session->bb_stream_file_data.data() + chunk_offset, cmd.data.data(), chunk_size);
|
memcpy(c->proxy_session->bb_stream_file_data.data() + chunk_offset, cmd.data.data(), chunk_size);
|
||||||
c->proxy_session->bb_stream_file_data_received += chunk_size;
|
c->proxy_session->bb_stream_file_data_received += chunk_size;
|
||||||
if (c->proxy_session->bb_stream_file_data_received == c->proxy_session->bb_stream_file_data.size()) {
|
if (c->proxy_session->bb_stream_file_data_received == c->proxy_session->bb_stream_file_data.size()) {
|
||||||
string output_prefix = std::format("streamfile.{}.", phosg::now());
|
std::string output_prefix = std::format("streamfile.{}.", phosg::now());
|
||||||
for (const auto& entry : c->proxy_session->bb_stream_file_entries) {
|
for (const auto& entry : c->proxy_session->bb_stream_file_entries) {
|
||||||
std::string filename = entry.filename.decode();
|
std::string filename = entry.filename.decode();
|
||||||
std::string sanitized_filename = filename;
|
std::string sanitized_filename = filename;
|
||||||
@@ -788,7 +786,7 @@ static asio::awaitable<HandlerResult> S_B_EB(shared_ptr<Client> c, Channel::Mess
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
static asio::awaitable<HandlerResult> S_C4(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_C4(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
size_t expected_size = sizeof(CmdT) * msg.flag;
|
size_t expected_size = sizeof(CmdT) * msg.flag;
|
||||||
@@ -808,7 +806,7 @@ constexpr MessageHandler S_DGX_C4 = &S_C4<S_ChoiceSearchResultEntry_DC_V3_C4>;
|
|||||||
constexpr MessageHandler S_P_C4 = &S_C4<S_ChoiceSearchResultEntry_PC_C4>;
|
constexpr MessageHandler S_P_C4 = &S_C4<S_ChoiceSearchResultEntry_PC_C4>;
|
||||||
constexpr MessageHandler S_B_C4 = &S_C4<S_ChoiceSearchResultEntry_BB_C4>;
|
constexpr MessageHandler S_B_C4 = &S_C4<S_ChoiceSearchResultEntry_BB_C4>;
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_E4(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_G_E4(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
auto& cmd = msg.check_size_t<S_CardBattleTableState_Ep3_E4>();
|
auto& cmd = msg.check_size_t<S_CardBattleTableState_Ep3_E4>();
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
@@ -822,7 +820,7 @@ static asio::awaitable<HandlerResult> S_G_E4(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_22(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_B_22(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// We use this command (which is sent before the init encryption command) to detect a particular server behavior that
|
// We use this command (which is sent before the init encryption command) to detect a particular server behavior that
|
||||||
// we'll have to work around later. It looks like this command's existence is an anti-proxy measure, since this
|
// we'll have to work around later. It looks like this command's existence is an anti-proxy measure, since this
|
||||||
// command is 0x34 bytes in total, and the logic that adds padding bytes when the command size isn't a multiple of 8
|
// command is 0x34 bytes in total, and the logic that adds padding bytes when the command size isn't a multiple of 8
|
||||||
@@ -836,7 +834,7 @@ static asio::awaitable<HandlerResult> S_B_22(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_19_U_14(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_19_U_14(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// If the command is shorter than 6 bytes, use the previous server command to fill it in. This simulates a behavior
|
// If the command is shorter than 6 bytes, use the previous server command to fill it in. This simulates a behavior
|
||||||
// used by some private servers where a longer previous command is used to fill part of the client's receive buffer
|
// used by some private servers where a longer previous command is used to fill part of the client's receive buffer
|
||||||
// with meaningful data, then an intentionally undersize 19 command is sent which results in the client using the
|
// with meaningful data, then an intentionally undersize 19 command is sent which results in the client using the
|
||||||
@@ -872,9 +870,9 @@ static asio::awaitable<HandlerResult> S_19_U_14(shared_ptr<Client> c, Channel::M
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace the server channel with a new channel to the new endpoint
|
// Replace the server channel with a new channel to the new endpoint
|
||||||
string netloc_str = str_for_endpoint(new_ep);
|
std::string netloc_str = str_for_endpoint(new_ep);
|
||||||
c->log.info_f("Connecting to {}", netloc_str);
|
c->log.info_f("Connecting to {}", netloc_str);
|
||||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||||
|
|
||||||
// Close the old channel only after replacing it with the new one
|
// Close the old channel only after replacing it with the new one
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
@@ -898,7 +896,7 @@ static asio::awaitable<HandlerResult> S_19_U_14(shared_ptr<Client> c, Channel::M
|
|||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_V3_1A_D5(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_V3_1A_D5(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
// If the client is a version that sends close confirmations and the client has the no-close-confirmation flag set in
|
// If the client is a version that sends close confirmations and the client has the no-close-confirmation flag set in
|
||||||
// its newserv client config, send a fake confirmation to the remote server immediately.
|
// its newserv client config, send a fake confirmation to the remote server immediately.
|
||||||
if (is_v3(c->version()) && c->check_flag(Client::Flag::NO_D6)) {
|
if (is_v3(c->version()) && c->check_flag(Client::Flag::NO_D6)) {
|
||||||
@@ -907,7 +905,7 @@ static asio::awaitable<HandlerResult> S_V3_1A_D5(shared_ptr<Client> c, Channel::
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_V3_BB_DA(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_V3_BB_DA(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// This command is supported on all V3 and V4 versions except Ep1&2 Trial
|
// This command is supported on all V3 and V4 versions except Ep1&2 Trial
|
||||||
if (c->version() == Version::GC_NTE) {
|
if (c->version() == Version::GC_NTE) {
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
@@ -919,7 +917,7 @@ static asio::awaitable<HandlerResult> S_V3_BB_DA(shared_ptr<Client> c, Channel::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> SC_6x60_6xA2(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (!c->proxy_session->is_in_game) {
|
if (!c->proxy_session->is_in_game) {
|
||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
@@ -947,7 +945,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
|||||||
case ProxyDropMode::INTERCEPT:
|
case ProxyDropMode::INTERCEPT:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid drop mode");
|
throw std::logic_error("invalid drop mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->proxy_session->item_creator) {
|
if (!c->proxy_session->item_creator) {
|
||||||
@@ -985,7 +983,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
|||||||
c->log.info_f("No item was created");
|
c->log.info_f("No item was created");
|
||||||
} else {
|
} else {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
string name = s->describe_item(c->version(), res.item);
|
std::string name = s->describe_item(c->version(), res.item);
|
||||||
c->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
|
c->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
|
||||||
res.item.id = c->proxy_session->next_item_id++;
|
res.item.id = c->proxy_session->next_item_id++;
|
||||||
c->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for all clients",
|
c->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for all clients",
|
||||||
@@ -997,7 +995,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
|||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_6x(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
if (msg.data.size() < 4) {
|
if (msg.data.size() < 4) {
|
||||||
@@ -1054,7 +1052,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
|||||||
|
|
||||||
case 0x46: {
|
case 0x46: {
|
||||||
const auto& header = msg.check_size_t<G_AttackFinished_Header_6x46>(0xFFFF);
|
const auto& header = msg.check_size_t<G_AttackFinished_Header_6x46>(0xFFFF);
|
||||||
if (header.target_count > min<size_t>(header.header.size - sizeof(G_AttackFinished_Header_6x46) / 4, 10)) {
|
if (header.target_count > std::min<size_t>(header.header.size - sizeof(G_AttackFinished_Header_6x46) / 4, 10)) {
|
||||||
c->log.warning_f("Blocking subcommand 6x46 with invalid count");
|
c->log.warning_f("Blocking subcommand 6x46 with invalid count");
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
@@ -1063,7 +1061,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
|||||||
|
|
||||||
case 0x47: {
|
case 0x47: {
|
||||||
const auto& header = msg.check_size_t<G_CastTechnique_Header_6x47>(0xFFFF);
|
const auto& header = msg.check_size_t<G_CastTechnique_Header_6x47>(0xFFFF);
|
||||||
if (header.target_count > min<size_t>(header.header.size - sizeof(G_CastTechnique_Header_6x47) / 4, 10)) {
|
if (header.target_count > std::min<size_t>(header.header.size - sizeof(G_CastTechnique_Header_6x47) / 4, 10)) {
|
||||||
c->log.warning_f("Blocking subcommand 6x47 with invalid count");
|
c->log.warning_f("Blocking subcommand 6x47 with invalid count");
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
@@ -1072,7 +1070,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
|||||||
|
|
||||||
case 0x49: {
|
case 0x49: {
|
||||||
const auto& header = msg.check_size_t<G_ExecutePhotonBlast_Header_6x49>(0xFFFF);
|
const auto& header = msg.check_size_t<G_ExecutePhotonBlast_Header_6x49>(0xFFFF);
|
||||||
if (header.target_count > min<size_t>(header.header.size - sizeof(G_ExecutePhotonBlast_Header_6x49) / 4, 10)) {
|
if (header.target_count > std::min<size_t>(header.header.size - sizeof(G_ExecutePhotonBlast_Header_6x49) / 4, 10)) {
|
||||||
c->log.warning_f("Blocking subcommand 6x49 with invalid count");
|
c->log.warning_f("Blocking subcommand 6x49 with invalid count");
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
@@ -1094,10 +1092,10 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
|||||||
case 0x6A: {
|
case 0x6A: {
|
||||||
auto& cmd = msg.check_size_t<G_SetBossWarpFlags_6x6A>();
|
auto& cmd = msg.check_size_t<G_SetBossWarpFlags_6x6A>();
|
||||||
if (c->proxy_session->map_state) {
|
if (c->proxy_session->map_state) {
|
||||||
shared_ptr<MapState::ObjectState> obj_st;
|
std::shared_ptr<MapState::ObjectState> obj_st;
|
||||||
try {
|
try {
|
||||||
obj_st = c->proxy_session->map_state->object_state_for_index(c->version(), cmd.header.entity_id - 0x4000);
|
obj_st = c->proxy_session->map_state->object_state_for_index(c->version(), cmd.header.entity_id - 0x4000);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.warning_f("Invalid object reference ({})", e.what());
|
c->log.warning_f("Invalid object reference ({})", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,8 +1211,8 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
|||||||
const auto& header = msg.check_size_t<G_MapSubsubcommand_Ep3_6xB6>(0xFFFF);
|
const auto& header = msg.check_size_t<G_MapSubsubcommand_Ep3_6xB6>(0xFFFF);
|
||||||
if (header.subsubcommand == 0x00000041) {
|
if (header.subsubcommand == 0x00000041) {
|
||||||
const auto& cmd = msg.check_size_t<G_MapData_Ep3_6xB6x41>(0xFFFF);
|
const auto& cmd = msg.check_size_t<G_MapData_Ep3_6xB6x41>(0xFFFF);
|
||||||
string filename = std::format("map{:08X}.{}.mnmd", cmd.map_number, phosg::now());
|
std::string filename = std::format("map{:08X}.{}.mnmd", cmd.map_number, phosg::now());
|
||||||
string map_data = prs_decompress(msg.data.data() + sizeof(cmd), msg.data.size() - sizeof(cmd));
|
std::string map_data = prs_decompress(msg.data.data() + sizeof(cmd), msg.data.size() - sizeof(cmd));
|
||||||
phosg::save_file(filename, map_data);
|
phosg::save_file(filename, map_data);
|
||||||
if ((map_data.size() != sizeof(Episode3::MapDefinition)) &&
|
if ((map_data.size() != sizeof(Episode3::MapDefinition)) &&
|
||||||
(map_data.size() != sizeof(Episode3::MapDefinitionTrial))) {
|
(map_data.size() != sizeof(Episode3::MapDefinitionTrial))) {
|
||||||
@@ -1255,7 +1253,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
|||||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_GXB_61(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_GXB_61(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
// TODO: We should check if the info board text was actually modified and return MODIFIED if so.
|
// TODO: We should check if the info board text was actually modified and return MODIFIED if so.
|
||||||
|
|
||||||
@@ -1294,7 +1292,7 @@ static asio::awaitable<HandlerResult> C_GXB_61(shared_ptr<Client> c, Channel::Me
|
|||||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_GX_D9(shared_ptr<Client>, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_GX_D9(std::shared_ptr<Client>, Channel::Message& msg) {
|
||||||
phosg::strip_trailing_zeroes(msg.data);
|
phosg::strip_trailing_zeroes(msg.data);
|
||||||
msg.data = add_color(msg.data);
|
msg.data = add_color(msg.data);
|
||||||
msg.data.push_back(0);
|
msg.data.push_back(0);
|
||||||
@@ -1305,19 +1303,19 @@ static asio::awaitable<HandlerResult> C_GX_D9(shared_ptr<Client>, Channel::Messa
|
|||||||
co_return HandlerResult::MODIFIED;
|
co_return HandlerResult::MODIFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_B_D9(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_B_D9(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
try {
|
try {
|
||||||
phosg::strip_trailing_zeroes(msg.data);
|
phosg::strip_trailing_zeroes(msg.data);
|
||||||
if (msg.data.size() & 1) {
|
if (msg.data.size() & 1) {
|
||||||
msg.data.push_back(0);
|
msg.data.push_back(0);
|
||||||
}
|
}
|
||||||
string decoded = tt_utf16_to_utf8(msg.data.data(), msg.data.size());
|
std::string decoded = tt_utf16_to_utf8(msg.data.data(), msg.data.size());
|
||||||
add_color_inplace(decoded);
|
add_color_inplace(decoded);
|
||||||
msg.data = tt_utf8_to_utf16(decoded.data(), decoded.size());
|
msg.data = tt_utf8_to_utf16(decoded.data(), decoded.size());
|
||||||
while (msg.data.size() & 3) {
|
while (msg.data.size() & 3) {
|
||||||
msg.data.push_back(0);
|
msg.data.push_back(0);
|
||||||
}
|
}
|
||||||
} catch (const runtime_error& e) {
|
} catch (const std::runtime_error& e) {
|
||||||
c->log.warning_f("Failed to decode and unescape D9 command: {}", e.what());
|
c->log.warning_f("Failed to decode and unescape D9 command: {}", e.what());
|
||||||
}
|
}
|
||||||
// TODO: We should check if the info board text was actually modified and return HandlerResult::FORWARD if not.
|
// TODO: We should check if the info board text was actually modified and return HandlerResult::FORWARD if not.
|
||||||
@@ -1325,16 +1323,16 @@ static asio::awaitable<HandlerResult> C_B_D9(shared_ptr<Client> c, Channel::Mess
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static asio::awaitable<HandlerResult> S_44_A6(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_44_A6(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
const auto& cmd = msg.check_size_t<T>();
|
const auto& cmd = msg.check_size_t<T>();
|
||||||
|
|
||||||
string filename = cmd.filename.decode();
|
std::string filename = cmd.filename.decode();
|
||||||
string output_filename;
|
std::string output_filename;
|
||||||
bool is_download = (msg.command == 0xA6);
|
bool is_download = (msg.command == 0xA6);
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
size_t extension_offset = filename.rfind('.');
|
size_t extension_offset = filename.rfind('.');
|
||||||
string basename, extension;
|
std::string basename, extension;
|
||||||
if (extension_offset != string::npos) {
|
if (extension_offset != std::string::npos) {
|
||||||
basename = filename.substr(0, extension_offset);
|
basename = filename.substr(0, extension_offset);
|
||||||
extension = filename.substr(extension_offset);
|
extension = filename.substr(extension_offset);
|
||||||
if (extension == ".bin" && is_ep3(c->version())) {
|
if (extension == ".bin" && is_ep3(c->version())) {
|
||||||
@@ -1378,15 +1376,15 @@ constexpr MessageHandler S_PG_44_A6 = &S_44_A6<S_OpenFile_PC_GC_44_A6>;
|
|||||||
constexpr MessageHandler S_X_44_A6 = &S_44_A6<S_OpenFile_XB_44_A6>;
|
constexpr MessageHandler S_X_44_A6 = &S_44_A6<S_OpenFile_XB_44_A6>;
|
||||||
constexpr MessageHandler S_B_44_A6 = &S_44_A6<S_OpenFile_BB_44_A6>;
|
constexpr MessageHandler S_B_44_A6 = &S_44_A6<S_OpenFile_BB_44_A6>;
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_13_A7(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
|
|
||||||
ProxySession::SavingFile* sf = nullptr;
|
ProxySession::SavingFile* sf = nullptr;
|
||||||
try {
|
try {
|
||||||
sf = &c->proxy_session->saving_files.at(cmd.filename.decode());
|
sf = &c->proxy_session->saving_files.at(cmd.filename.decode());
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
string filename = cmd.filename.decode();
|
std::string filename = cmd.filename.decode();
|
||||||
c->log.warning_f("Received data for non-open file {}", filename);
|
c->log.warning_f("Received data for non-open file {}", filename);
|
||||||
}
|
}
|
||||||
if (!sf) {
|
if (!sf) {
|
||||||
@@ -1396,7 +1394,7 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
|||||||
bool is_last_block = (cmd.data_size != 0x400);
|
bool is_last_block = (cmd.data_size != 0x400);
|
||||||
size_t block_offset = msg.flag * 0x400;
|
size_t block_offset = msg.flag * 0x400;
|
||||||
size_t allowed_block_size = (block_offset < sf->total_size)
|
size_t allowed_block_size = (block_offset < sf->total_size)
|
||||||
? min<size_t>(sf->total_size - block_offset, 0x400)
|
? std::min<size_t>(sf->total_size - block_offset, 0x400)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
if (cmd.data_size > allowed_block_size) {
|
if (cmd.data_size > allowed_block_size) {
|
||||||
@@ -1431,9 +1429,9 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
|||||||
|
|
||||||
if (!sf->is_download) {
|
if (!sf->is_download) {
|
||||||
if (sf->basename.ends_with(".bin")) {
|
if (sf->basename.ends_with(".bin")) {
|
||||||
c->proxy_session->last_bin_contents = make_shared<std::string>(prs_decompress(sf->data));
|
c->proxy_session->last_bin_contents = std::make_shared<std::string>(prs_decompress(sf->data));
|
||||||
} else if (sf->basename.ends_with(".dat")) {
|
} else if (sf->basename.ends_with(".dat")) {
|
||||||
c->proxy_session->last_dat_contents = make_shared<std::string>(prs_decompress(sf->data));
|
c->proxy_session->last_dat_contents = std::make_shared<std::string>(prs_decompress(sf->data));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->proxy_session->last_bin_contents && c->proxy_session->last_dat_contents) {
|
if (c->proxy_session->last_bin_contents && c->proxy_session->last_dat_contents) {
|
||||||
@@ -1446,23 +1444,23 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
|||||||
c->version(),
|
c->version(),
|
||||||
c->language());
|
c->language());
|
||||||
|
|
||||||
auto map_file = make_shared<MapFile>(c->proxy_session->last_dat_contents);
|
auto map_file = std::make_shared<MapFile>(c->proxy_session->last_dat_contents);
|
||||||
auto materialized_map_file = map_file->materialize_random_sections(c->proxy_session->lobby_random_seed);
|
auto materialized_map_file = map_file->materialize_random_sections(c->proxy_session->lobby_random_seed);
|
||||||
|
|
||||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
std::array<std::shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||||
map_files.at(static_cast<size_t>(c->version())) = materialized_map_file;
|
map_files.at(static_cast<size_t>(c->version())) = materialized_map_file;
|
||||||
auto supermap = make_shared<SuperMap>(map_files, meta.get_floor_to_area());
|
auto supermap = std::make_shared<SuperMap>(map_files, meta.get_floor_to_area());
|
||||||
|
|
||||||
c->proxy_session->map_state = make_shared<MapState>(
|
c->proxy_session->map_state = std::make_shared<MapState>(
|
||||||
c->id,
|
c->id,
|
||||||
c->proxy_session->lobby_difficulty,
|
c->proxy_session->lobby_difficulty,
|
||||||
c->proxy_session->lobby_event,
|
c->proxy_session->lobby_event,
|
||||||
c->proxy_session->lobby_random_seed,
|
c->proxy_session->lobby_random_seed,
|
||||||
MapState::DEFAULT_RARE_ENEMIES,
|
MapState::DEFAULT_RARE_ENEMIES,
|
||||||
make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
std::make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
||||||
supermap);
|
supermap);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.warning_f("Failed to load quest map: {}", e.what());
|
c->log.warning_f("Failed to load quest map: {}", e.what());
|
||||||
c->proxy_session->map_state.reset();
|
c->proxy_session->map_state.reset();
|
||||||
}
|
}
|
||||||
@@ -1478,7 +1476,7 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
|||||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_B7(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_G_B7(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (is_ep3(c->version())) {
|
if (is_ep3(c->version())) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||||
auto& cmd = msg.check_size_t<S_RankUpdate_Ep3_B7>();
|
auto& cmd = msg.check_size_t<S_RankUpdate_Ep3_B7>();
|
||||||
@@ -1494,7 +1492,7 @@ static asio::awaitable<HandlerResult> S_G_B7(shared_ptr<Client> c, Channel::Mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_B8(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_G_B8(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
if (msg.data.size() < 4) {
|
if (msg.data.size() < 4) {
|
||||||
c->log.warning_f("Card list data size is too small; not saving file");
|
c->log.warning_f("Card list data size is too small; not saving file");
|
||||||
@@ -1508,7 +1506,7 @@ static asio::awaitable<HandlerResult> S_G_B8(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
string output_filename = std::format("card-definitions.{}.mnr", phosg::now());
|
std::string output_filename = std::format("card-definitions.{}.mnr", phosg::now());
|
||||||
phosg::save_file(output_filename, r.read(size));
|
phosg::save_file(output_filename, r.read(size));
|
||||||
c->log.info_f("Wrote {} bytes to {}", size, output_filename);
|
c->log.info_f("Wrote {} bytes to {}", size, output_filename);
|
||||||
}
|
}
|
||||||
@@ -1520,19 +1518,19 @@ static asio::awaitable<HandlerResult> S_G_B8(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return is_ep3(c->version()) ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
co_return is_ep3(c->version()) ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_B9(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_G_B9(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||||
try {
|
try {
|
||||||
const auto& header = msg.check_size_t<S_UpdateMediaHeader_Ep3_B9>(0xFFFF);
|
const auto& header = msg.check_size_t<S_UpdateMediaHeader_Ep3_B9>(0xFFFF);
|
||||||
|
|
||||||
if (msg.data.size() - sizeof(header) < header.size) {
|
if (msg.data.size() - sizeof(header) < header.size) {
|
||||||
throw runtime_error("Media data size extends beyond end of command; not saving file");
|
throw std::runtime_error("Media data size extends beyond end of command; not saving file");
|
||||||
}
|
}
|
||||||
|
|
||||||
string decompressed_data = prs_decompress(
|
std::string decompressed_data = prs_decompress(
|
||||||
msg.data.data() + sizeof(header), msg.data.size() - sizeof(header));
|
msg.data.data() + sizeof(header), msg.data.size() - sizeof(header));
|
||||||
|
|
||||||
string output_filename = std::format("media-update.{}", phosg::now());
|
std::string output_filename = std::format("media-update.{}", phosg::now());
|
||||||
if (header.type == 1) {
|
if (header.type == 1) {
|
||||||
output_filename += ".gvm";
|
output_filename += ".gvm";
|
||||||
} else if (header.type == 2 || header.type == 3) {
|
} else if (header.type == 2 || header.type == 3) {
|
||||||
@@ -1542,7 +1540,7 @@ static asio::awaitable<HandlerResult> S_G_B9(shared_ptr<Client> c, Channel::Mess
|
|||||||
}
|
}
|
||||||
phosg::save_file(output_filename, decompressed_data);
|
phosg::save_file(output_filename, decompressed_data);
|
||||||
c->log.info_f("Wrote {} bytes to {}", decompressed_data.size(), output_filename);
|
c->log.info_f("Wrote {} bytes to {}", decompressed_data.size(), output_filename);
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.warning_f("Failed to save file: {}", e.what());
|
c->log.warning_f("Failed to save file: {}", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1551,7 +1549,7 @@ static asio::awaitable<HandlerResult> S_G_B9(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return (c->version() == Version::GC_EP3) ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
co_return (c->version() == Version::GC_EP3) ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_G_B9(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> C_G_B9(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
if (c->proxy_session->suppress_next_ep3_media_update_confirmation) {
|
if (c->proxy_session->suppress_next_ep3_media_update_confirmation) {
|
||||||
c->proxy_session->suppress_next_ep3_media_update_confirmation = false;
|
c->proxy_session->suppress_next_ep3_media_update_confirmation = false;
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
@@ -1559,7 +1557,7 @@ static asio::awaitable<HandlerResult> C_G_B9(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_EF(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_G_EF(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (is_ep3(c->version())) {
|
if (is_ep3(c->version())) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||||
auto& cmd = msg.check_size_t<S_StartCardAuction_Ep3_EF>(offsetof(S_StartCardAuction_Ep3_EF, unused), 0xFFFF);
|
auto& cmd = msg.check_size_t<S_StartCardAuction_Ep3_EF>(offsetof(S_StartCardAuction_Ep3_EF, unused), 0xFFFF);
|
||||||
@@ -1574,12 +1572,12 @@ static asio::awaitable<HandlerResult> S_G_EF(shared_ptr<Client> c, Channel::Mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_B_EF(shared_ptr<Client>, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_B_EF(std::shared_ptr<Client>, Channel::Message&) {
|
||||||
// See the comments on EF in CommandFormats.hh for why we unconditionally suppress these.
|
// See the comments on EF in CommandFormats.hh for why we unconditionally suppress these.
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_G_BA(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_G_BA(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||||
auto& cmd = msg.check_size_t<S_MesetaTransaction_Ep3_BA>();
|
auto& cmd = msg.check_size_t<S_MesetaTransaction_Ep3_BA>();
|
||||||
if (cmd.current_meseta != 1000000) {
|
if (cmd.current_meseta != 1000000) {
|
||||||
@@ -1590,7 +1588,7 @@ static asio::awaitable<HandlerResult> S_G_BA(shared_ptr<Client> c, Channel::Mess
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_leader_id(shared_ptr<Client> c, uint8_t leader_id) {
|
static void update_leader_id(std::shared_ptr<Client> c, uint8_t leader_id) {
|
||||||
if (c->proxy_session->leader_client_id != leader_id) {
|
if (c->proxy_session->leader_client_id != leader_id) {
|
||||||
c->proxy_session->leader_client_id = leader_id;
|
c->proxy_session->leader_client_id = leader_id;
|
||||||
c->log.info_f("Changed room leader to {:X}", c->proxy_session->leader_client_id);
|
c->log.info_f("Changed room leader to {:X}", c->proxy_session->leader_client_id);
|
||||||
@@ -1602,7 +1600,7 @@ static void update_leader_id(shared_ptr<Client> c, uint8_t leader_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
static asio::awaitable<HandlerResult> S_65_67_68_EB(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_65_67_68_EB(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (msg.command == 0x67) {
|
if (msg.command == 0x67) {
|
||||||
c->proxy_session->clear_lobby_players(12);
|
c->proxy_session->clear_lobby_players(12);
|
||||||
c->proxy_session->is_in_lobby = true;
|
c->proxy_session->is_in_lobby = true;
|
||||||
@@ -1635,7 +1633,7 @@ static asio::awaitable<HandlerResult> S_65_67_68_EB(shared_ptr<Client> c, Channe
|
|||||||
if (index >= c->proxy_session->lobby_players.size()) {
|
if (index >= c->proxy_session->lobby_players.size()) {
|
||||||
c->log.warning_f("Ignoring invalid player index {} at position {}", index, x);
|
c->log.warning_f("Ignoring invalid player index {} at position {}", index, x);
|
||||||
} else {
|
} else {
|
||||||
string name = escape_player_name(entry.disp.visual.name.decode(entry.inventory.language));
|
std::string name = escape_player_name(entry.disp.visual.name.decode(entry.inventory.language));
|
||||||
if (c->login && (entry.lobby_data.guild_card_number == c->proxy_session->remote_guild_card_number)) {
|
if (c->login && (entry.lobby_data.guild_card_number == c->proxy_session->remote_guild_card_number)) {
|
||||||
num_replacements++;
|
num_replacements++;
|
||||||
if (c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
@@ -1649,8 +1647,8 @@ static asio::awaitable<HandlerResult> S_65_67_68_EB(shared_ptr<Client> c, Channe
|
|||||||
p.guild_card_number = entry.lobby_data.guild_card_number;
|
p.guild_card_number = entry.lobby_data.guild_card_number;
|
||||||
p.name = name;
|
p.name = name;
|
||||||
p.language = entry.inventory.language;
|
p.language = entry.inventory.language;
|
||||||
p.section_id = entry.disp.visual.section_id;
|
p.section_id = entry.disp.visual.sh.section_id;
|
||||||
p.char_class = entry.disp.visual.char_class;
|
p.char_class = entry.disp.visual.sh.char_class;
|
||||||
c->log.info_f("Added lobby player: ({}) {} {}", index, p.guild_card_number, p.name);
|
c->log.info_f("Added lobby player: ({}) {} {}", index, p.guild_card_number, p.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1724,7 +1722,7 @@ Episode get_episode<S_JoinGame_Ep3_64>(const S_JoinGame_Ep3_64&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_64(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
CmdT* cmd;
|
CmdT* cmd;
|
||||||
S_JoinGame_Ep3_64* cmd_ep3 = nullptr;
|
S_JoinGame_Ep3_64* cmd_ep3 = nullptr;
|
||||||
if ((c->sub_version >= 0x40) && is_v3(c->version())) {
|
if ((c->sub_version >= 0x40) && is_v3(c->version())) {
|
||||||
@@ -1775,7 +1773,7 @@ static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Messag
|
|||||||
} else {
|
} else {
|
||||||
c->proxy_session->lobby_event = 0;
|
c->proxy_session->lobby_event = 0;
|
||||||
c->proxy_session->lobby_difficulty = Difficulty::NORMAL;
|
c->proxy_session->lobby_difficulty = Difficulty::NORMAL;
|
||||||
c->proxy_session->lobby_section_id = c->character_file()->disp.visual.section_id;
|
c->proxy_session->lobby_section_id = c->character_file()->disp.visual.sh.section_id;
|
||||||
c->proxy_session->lobby_mode = GameMode::NORMAL;
|
c->proxy_session->lobby_mode = GameMode::NORMAL;
|
||||||
c->proxy_session->lobby_random_seed = phosg::random_object<uint32_t>();
|
c->proxy_session->lobby_random_seed = phosg::random_object<uint32_t>();
|
||||||
}
|
}
|
||||||
@@ -1799,13 +1797,13 @@ static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Messag
|
|||||||
c->proxy_session->lobby_mode,
|
c->proxy_session->lobby_mode,
|
||||||
c->proxy_session->lobby_difficulty,
|
c->proxy_session->lobby_difficulty,
|
||||||
cmd->variations);
|
cmd->variations);
|
||||||
c->proxy_session->map_state = make_shared<MapState>(
|
c->proxy_session->map_state = std::make_shared<MapState>(
|
||||||
c->id,
|
c->id,
|
||||||
c->proxy_session->lobby_difficulty,
|
c->proxy_session->lobby_difficulty,
|
||||||
c->proxy_session->lobby_event,
|
c->proxy_session->lobby_event,
|
||||||
c->proxy_session->lobby_random_seed,
|
c->proxy_session->lobby_random_seed,
|
||||||
MapState::DEFAULT_RARE_ENEMIES,
|
MapState::DEFAULT_RARE_ENEMIES,
|
||||||
make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
std::make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
||||||
supermaps);
|
supermaps);
|
||||||
} else {
|
} else {
|
||||||
c->proxy_session->map_state.reset();
|
c->proxy_session->map_state.reset();
|
||||||
@@ -1826,8 +1824,8 @@ static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Messag
|
|||||||
const auto& p_ep3 = cmd_ep3->players_ep3[x];
|
const auto& p_ep3 = cmd_ep3->players_ep3[x];
|
||||||
p.language = p_ep3.inventory.language;
|
p.language = p_ep3.inventory.language;
|
||||||
p.name = p_ep3.disp.visual.name.decode(p.language);
|
p.name = p_ep3.disp.visual.name.decode(p.language);
|
||||||
p.section_id = p_ep3.disp.visual.section_id;
|
p.section_id = p_ep3.disp.visual.sh.section_id;
|
||||||
p.char_class = p_ep3.disp.visual.char_class;
|
p.char_class = p_ep3.disp.visual.sh.char_class;
|
||||||
} else {
|
} else {
|
||||||
p.name.clear();
|
p.name.clear();
|
||||||
}
|
}
|
||||||
@@ -1844,7 +1842,7 @@ constexpr MessageHandler S_G_64 = &S_64<S_JoinGame_GC_64>;
|
|||||||
constexpr MessageHandler S_X_64 = &S_64<S_JoinGame_XB_64>;
|
constexpr MessageHandler S_X_64 = &S_64<S_JoinGame_XB_64>;
|
||||||
constexpr MessageHandler S_B_64 = &S_64<S_JoinGame_BB_64>;
|
constexpr MessageHandler S_B_64 = &S_64<S_JoinGame_BB_64>;
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_E8(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_E8(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
auto& cmd = msg.check_size_t<S_JoinSpectatorTeam_Ep3_E8>();
|
auto& cmd = msg.check_size_t<S_JoinSpectatorTeam_Ep3_E8>();
|
||||||
|
|
||||||
c->floor = 0;
|
c->floor = 0;
|
||||||
@@ -1885,8 +1883,8 @@ static asio::awaitable<HandlerResult> S_E8(shared_ptr<Client> c, Channel::Messag
|
|||||||
p.guild_card_number = player_entry.lobby_data.guild_card_number;
|
p.guild_card_number = player_entry.lobby_data.guild_card_number;
|
||||||
p.language = player_entry.inventory.language;
|
p.language = player_entry.inventory.language;
|
||||||
p.name = player_entry.disp.visual.name.decode(p.language);
|
p.name = player_entry.disp.visual.name.decode(p.language);
|
||||||
p.section_id = player_entry.disp.visual.section_id;
|
p.section_id = player_entry.disp.visual.sh.section_id;
|
||||||
p.char_class = player_entry.disp.visual.char_class;
|
p.char_class = player_entry.disp.visual.sh.char_class;
|
||||||
c->log.info_f("Added lobby player: ({}) {} {}", x, p.guild_card_number, p.name);
|
c->log.info_f("Added lobby player: ({}) {} {}", x, p.guild_card_number, p.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1906,7 +1904,7 @@ static asio::awaitable<HandlerResult> S_E8(shared_ptr<Client> c, Channel::Messag
|
|||||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_AC(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> S_AC(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
if (!c->proxy_session->is_in_game) {
|
if (!c->proxy_session->is_in_game) {
|
||||||
co_return HandlerResult::SUPPRESS;
|
co_return HandlerResult::SUPPRESS;
|
||||||
} else {
|
} else {
|
||||||
@@ -1915,7 +1913,7 @@ static asio::awaitable<HandlerResult> S_AC(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> S_66_69_E9(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> S_66_69_E9(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
// Schtserv sends a large command here for unknown reasons. The client ignores the extra data, so we allow the large
|
// Schtserv sends a large command here for unknown reasons. The client ignores the extra data, so we allow the large
|
||||||
// command here.
|
// command here.
|
||||||
const auto& cmd = msg.check_size_t<S_LeaveLobby_66_69_Ep3_E9>(0xFFFF);
|
const auto& cmd = msg.check_size_t<S_LeaveLobby_66_69_Ep3_E9>(0xFFFF);
|
||||||
@@ -1924,7 +1922,7 @@ static asio::awaitable<HandlerResult> S_66_69_E9(shared_ptr<Client> c, Channel::
|
|||||||
c->log.warning_f("Lobby leave command references missing position");
|
c->log.warning_f("Lobby leave command references missing position");
|
||||||
} else {
|
} else {
|
||||||
auto& p = c->proxy_session->lobby_players[index];
|
auto& p = c->proxy_session->lobby_players[index];
|
||||||
string name = escape_player_name(p.name);
|
std::string name = escape_player_name(p.name);
|
||||||
if (c->check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED)) {
|
if (c->check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED)) {
|
||||||
send_text_message_fmt(c->channel, "$C4Leave: {}/{}\n{}", index, p.guild_card_number, name);
|
send_text_message_fmt(c->channel, "$C4Leave: {}/{}\n{}", index, p.guild_card_number, name);
|
||||||
}
|
}
|
||||||
@@ -1936,7 +1934,7 @@ static asio::awaitable<HandlerResult> S_66_69_E9(shared_ptr<Client> c, Channel::
|
|||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_98(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_98(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
c->floor = 0x0F;
|
c->floor = 0x0F;
|
||||||
c->proxy_session->is_in_lobby = false;
|
c->proxy_session->is_in_lobby = false;
|
||||||
c->proxy_session->is_in_game = false;
|
c->proxy_session->is_in_game = false;
|
||||||
@@ -1958,11 +1956,11 @@ static asio::awaitable<HandlerResult> C_98(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_06(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (msg.data.size() >= 0x0C) {
|
if (msg.data.size() >= 0x0C) {
|
||||||
const auto& cmd = msg.check_size_t<SC_TextHeader_01_06_11_B0_EE>(0xFFFF);
|
const auto& cmd = msg.check_size_t<SC_TextHeader_01_06_11_B0_EE>(0xFFFF);
|
||||||
|
|
||||||
string text = msg.data.substr(sizeof(cmd));
|
std::string text = msg.data.substr(sizeof(cmd));
|
||||||
phosg::strip_trailing_zeroes(text);
|
phosg::strip_trailing_zeroes(text);
|
||||||
|
|
||||||
uint8_t private_flags = 0;
|
uint8_t private_flags = 0;
|
||||||
@@ -1978,7 +1976,7 @@ static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Messag
|
|||||||
} else {
|
} else {
|
||||||
text = tt_decode_marked(text, c->language(), false);
|
text = tt_decode_marked(text, c->language(), false);
|
||||||
}
|
}
|
||||||
} catch (const runtime_error& e) {
|
} catch (const std::runtime_error& e) {
|
||||||
c->log.warning_f("Failed to decode and unescape chat text: {}", e.what());
|
c->log.warning_f("Failed to decode and unescape chat text: {}", e.what());
|
||||||
text.clear();
|
text.clear();
|
||||||
}
|
}
|
||||||
@@ -2012,7 +2010,7 @@ static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_40(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_40(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
auto& cmd = msg.check_size_t<C_GuildCardSearch_40>();
|
auto& cmd = msg.check_size_t<C_GuildCardSearch_40>();
|
||||||
@@ -2029,7 +2027,7 @@ static asio::awaitable<HandlerResult> C_40(shared_ptr<Client> c, Channel::Messag
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename CmdT>
|
template <typename CmdT>
|
||||||
static asio::awaitable<HandlerResult> C_81(shared_ptr<Client> c, Channel::Message& msg) {
|
static asio::awaitable<HandlerResult> C_81(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
auto& cmd = msg.check_size_t<CmdT>();
|
auto& cmd = msg.check_size_t<CmdT>();
|
||||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||||
if (cmd.from_guild_card_number == c->login->account->account_id) {
|
if (cmd.from_guild_card_number == c->login->account->account_id) {
|
||||||
@@ -2049,7 +2047,7 @@ constexpr MessageHandler C_P_81 = &C_81<SC_SimpleMail_PC_81>;
|
|||||||
constexpr MessageHandler C_B_81 = &C_81<SC_SimpleMail_BB_81>;
|
constexpr MessageHandler C_B_81 = &C_81<SC_SimpleMail_BB_81>;
|
||||||
|
|
||||||
template <typename SendGuildCardCmdT>
|
template <typename SendGuildCardCmdT>
|
||||||
asio::awaitable<HandlerResult> C_6x(shared_ptr<Client> c, Channel::Message& msg) {
|
asio::awaitable<HandlerResult> C_6x(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||||
if (msg.data.size() < 4) {
|
if (msg.data.size() < 4) {
|
||||||
co_return HandlerResult::FORWARD;
|
co_return HandlerResult::FORWARD;
|
||||||
}
|
}
|
||||||
@@ -2224,7 +2222,7 @@ constexpr MessageHandler C_G_6x = &C_6x<G_SendGuildCard_GC_6x06>;
|
|||||||
constexpr MessageHandler C_X_6x = &C_6x<G_SendGuildCard_XB_6x06>;
|
constexpr MessageHandler C_X_6x = &C_6x<G_SendGuildCard_XB_6x06>;
|
||||||
constexpr MessageHandler C_B_6x = &C_6x<G_SendGuildCard_BB_6x06>;
|
constexpr MessageHandler C_B_6x = &C_6x<G_SendGuildCard_BB_6x06>;
|
||||||
|
|
||||||
static asio::awaitable<HandlerResult> C_V123_A0(shared_ptr<Client> c, Channel::Message&) {
|
static asio::awaitable<HandlerResult> C_V123_A0(std::shared_ptr<Client> c, Channel::Message&) {
|
||||||
// A0 is sent after downloading a quest (either successfully, or by backing out of the menu), and when choosing
|
// A0 is sent after downloading a quest (either successfully, or by backing out of the menu), and when choosing
|
||||||
// Change Ship from the lobby counter menu. We override the Change Ship action to end the proxy session, but we only
|
// Change Ship from the lobby counter menu. We override the Change Ship action to end the proxy session, but we only
|
||||||
// do so if the player is in a lobby in order to properly handle the download quest case.
|
// do so if the player is in a lobby in order to properly handle the download quest case.
|
||||||
@@ -2796,13 +2794,14 @@ static MessageHandler get_handler(Version version, bool from_server, uint8_t com
|
|||||||
const auto& handlers = from_server ? server_handlers : client_handlers;
|
const auto& handlers = from_server ? server_handlers : client_handlers;
|
||||||
size_t version_index = static_cast<size_t>(version);
|
size_t version_index = static_cast<size_t>(version);
|
||||||
if (version_index >= handlers[command].size()) {
|
if (version_index >= handlers[command].size()) {
|
||||||
throw logic_error("invalid game version on proxy server");
|
throw std::logic_error("invalid game version on proxy server");
|
||||||
}
|
}
|
||||||
auto ret = handlers[command][version_index];
|
auto ret = handlers[command][version_index];
|
||||||
return ret ? ret : default_handler;
|
return ret ? ret : default_handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> on_proxy_command(shared_ptr<Client> c, bool from_server, unique_ptr<Channel::Message> msg) {
|
asio::awaitable<void> on_proxy_command(
|
||||||
|
std::shared_ptr<Client> c, bool from_server, std::unique_ptr<Channel::Message> msg) {
|
||||||
auto fn = get_handler(c->version(), from_server, msg->command & 0xFF);
|
auto fn = get_handler(c->version(), from_server, msg->command & 0xFF);
|
||||||
try {
|
try {
|
||||||
auto res = co_await fn(c, *msg);
|
auto res = co_await fn(c, *msg);
|
||||||
@@ -2814,9 +2813,9 @@ asio::awaitable<void> on_proxy_command(shared_ptr<Client> c, bool from_server, u
|
|||||||
} else if (res == HandlerResult::SUPPRESS) {
|
} else if (res == HandlerResult::SUPPRESS) {
|
||||||
c->log.info_f("The preceding command from the {} was not forwarded", from_server ? "server" : "client");
|
c->log.info_f("The preceding command from the {} was not forwarded", from_server ? "server" : "client");
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("invalid handler result");
|
throw std::logic_error("invalid handler result");
|
||||||
}
|
}
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.error_f("Error in proxy command handler: {}", e.what());
|
c->log.error_f("Error in proxy command handler: {}", e.what());
|
||||||
if (c->proxy_session && c->proxy_session->server_channel) {
|
if (c->proxy_session && c->proxy_session->server_channel) {
|
||||||
c->proxy_session->server_channel->disconnect();
|
c->proxy_session->server_channel->disconnect();
|
||||||
@@ -2825,13 +2824,13 @@ asio::awaitable<void> on_proxy_command(shared_ptr<Client> c, bool from_server, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<void> handle_proxy_server_commands(
|
asio::awaitable<void> handle_proxy_server_commands(
|
||||||
shared_ptr<Client> c, shared_ptr<ProxySession> ses, shared_ptr<Channel> channel) {
|
std::shared_ptr<Client> c, std::shared_ptr<ProxySession> ses, std::shared_ptr<Channel> channel) {
|
||||||
std::string error_str;
|
std::string error_str;
|
||||||
// server_channel can be changed by receiving a 19 command, hence the exception handler is inside the loop here
|
// server_channel can be changed by receiving a 19 command, hence the exception handler is inside the loop here
|
||||||
while ((c->proxy_session == ses) && (ses->server_channel == channel) && channel->connected()) {
|
while ((c->proxy_session == ses) && (ses->server_channel == channel) && channel->connected()) {
|
||||||
unique_ptr<Channel::Message> msg;
|
std::unique_ptr<Channel::Message> msg;
|
||||||
try {
|
try {
|
||||||
msg = make_unique<Channel::Message>(co_await channel->recv());
|
msg = std::make_unique<Channel::Message>(co_await channel->recv());
|
||||||
if (c->proxy_session == ses) {
|
if (c->proxy_session == ses) {
|
||||||
for (size_t z = 0; z < std::min<size_t>(c->proxy_session->prev_server_command_bytes.size(), msg->data.size()); z++) {
|
for (size_t z = 0; z < std::min<size_t>(c->proxy_session->prev_server_command_bytes.size(), msg->data.size()); z++) {
|
||||||
c->proxy_session->prev_server_command_bytes[z] = msg->data[z];
|
c->proxy_session->prev_server_command_bytes[z] = msg->data[z];
|
||||||
@@ -2854,7 +2853,7 @@ asio::awaitable<void> handle_proxy_server_commands(
|
|||||||
error_str = e.what();
|
error_str = e.what();
|
||||||
}
|
}
|
||||||
channel->disconnect();
|
channel->disconnect();
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
c->log.info_f("Error in proxy server channel handler (command {:04X}): {}", msg ? msg->command : 0, e.what());
|
c->log.info_f("Error in proxy server channel handler (command {:04X}): {}", msg ? msg->command : 0, e.what());
|
||||||
error_str = e.what();
|
error_str = e.what();
|
||||||
channel->disconnect();
|
channel->disconnect();
|
||||||
|
|||||||
+4
-6
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
#include "ServerState.hh"
|
#include "ServerState.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
size_t ProxySession::num_proxy_sessions = 0;
|
size_t ProxySession::num_proxy_sessions = 0;
|
||||||
|
|
||||||
ProxySession::ProxySession(shared_ptr<Channel> server_channel, const PersistentConfig* pc)
|
ProxySession::ProxySession(std::shared_ptr<Channel> server_channel, const PersistentConfig* pc)
|
||||||
: server_channel(server_channel) {
|
: server_channel(server_channel) {
|
||||||
if (pc) {
|
if (pc) {
|
||||||
this->remote_guild_card_number = pc->remote_guild_card_number;
|
this->remote_guild_card_number = pc->remote_guild_card_number;
|
||||||
@@ -22,11 +20,11 @@ ProxySession::~ProxySession() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ProxySession::set_drop_mode(
|
void ProxySession::set_drop_mode(
|
||||||
shared_ptr<ServerState> s, Version version, int64_t override_random_seed, ProxyDropMode new_mode) {
|
std::shared_ptr<ServerState> s, Version version, int64_t override_random_seed, ProxyDropMode new_mode) {
|
||||||
this->drop_mode = new_mode;
|
this->drop_mode = new_mode;
|
||||||
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
|
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
|
||||||
auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
|
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
|
||||||
this->item_creator = make_shared<ItemCreator>(
|
this->item_creator = std::make_shared<ItemCreator>(
|
||||||
s->common_item_set(version, nullptr),
|
s->common_item_set(version, nullptr),
|
||||||
s->rare_item_set(version, nullptr),
|
s->rare_item_set(version, nullptr),
|
||||||
s->armor_random_set,
|
s->armor_random_set,
|
||||||
|
|||||||
+224
-199
File diff suppressed because it is too large
Load Diff
+51
-52
@@ -1,7 +1,5 @@
|
|||||||
#include "QuestMetadata.hh"
|
#include "QuestMetadata.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
phosg::JSON QuestMetadata::FloorAssignment::json() const {
|
phosg::JSON QuestMetadata::FloorAssignment::json() const {
|
||||||
return phosg::JSON::dict({
|
return phosg::JSON::dict({
|
||||||
{"Floor", this->floor},
|
{"Floor", this->floor},
|
||||||
@@ -21,51 +19,51 @@ std::string QuestMetadata::FloorAssignment::str() const {
|
|||||||
void QuestMetadata::apply_json_overrides(const phosg::JSON& json) {
|
void QuestMetadata::apply_json_overrides(const phosg::JSON& json) {
|
||||||
try {
|
try {
|
||||||
this->description_flag = json.at("DescriptionFlag").as_int();
|
this->description_flag = json.at("DescriptionFlag").as_int();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->available_expression = make_shared<IntegralExpression>(json.get_string("AvailableIf"));
|
this->available_expression = std::make_shared<IntegralExpression>(json.get_string("AvailableIf"));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->enabled_expression = make_shared<IntegralExpression>(json.get_string("EnabledIf"));
|
this->enabled_expression = std::make_shared<IntegralExpression>(json.get_string("EnabledIf"));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->allow_start_from_chat_command = json.get_bool("AllowStartFromChatCommand");
|
this->allow_start_from_chat_command = json.get_bool("AllowStartFromChatCommand");
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->joinable = json.get_bool("Joinable");
|
this->joinable = json.get_bool("Joinable");
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->lock_status_register = json.get_int("LockStatusRegister");
|
this->lock_status_register = json.get_int("LockStatusRegister");
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->enemy_exp_overrides = QuestMetadata::parse_enemy_exp_overrides(json.at("EnemyEXPOverrides"));
|
this->enemy_exp_overrides = QuestMetadata::parse_enemy_exp_overrides(json.at("EnemyEXPOverrides"));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->common_item_set_name = json.at("CommonItemSetName").as_string();
|
this->common_item_set_name = json.at("CommonItemSetName").as_string();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->rare_item_set_name = json.at("RareItemSetName").as_string();
|
this->rare_item_set_name = json.at("RareItemSetName").as_string();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->allowed_drop_modes = json.at("AllowedDropModes").as_int();
|
this->allowed_drop_modes = json.at("AllowedDropModes").as_int();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->default_drop_mode = phosg::enum_for_name<ServerDropMode>(json.at("DefaultDropMode").as_string());
|
this->default_drop_mode = phosg::enum_for_name<ServerDropMode>(json.at("DefaultDropMode").as_string());
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this->enable_schtserv_commands = json.at("EnableSchtservCommands").as_bool();
|
this->enable_schtserv_commands = json.at("EnableSchtservCommands").as_bool();
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,51 +80,51 @@ void QuestMetadata::assign_default_floors() {
|
|||||||
|
|
||||||
void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
||||||
if (this->quest_number != other.quest_number) {
|
if (this->quest_number != other.quest_number) {
|
||||||
throw logic_error(std::format(
|
throw std::logic_error(std::format(
|
||||||
"incorrect versioned quest number (existing: {:08X}, new: {:08X})", this->quest_number, other.quest_number));
|
"incorrect versioned quest number (existing: {:08X}, new: {:08X})", this->quest_number, other.quest_number));
|
||||||
}
|
}
|
||||||
if (this->category_id != other.category_id) {
|
if (this->category_id != other.category_id) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version is in a different category (existing: {:08X}, new: {:08X})",
|
"quest version is in a different category (existing: {:08X}, new: {:08X})",
|
||||||
this->category_id, other.category_id));
|
this->category_id, other.category_id));
|
||||||
}
|
}
|
||||||
if (this->episode != other.episode) {
|
if (this->episode != other.episode) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version is in a different episode (existing: {}, new: {})",
|
"quest version is in a different episode (existing: {}, new: {})",
|
||||||
name_for_episode(this->episode), name_for_episode(other.episode)));
|
name_for_episode(this->episode), name_for_episode(other.episode)));
|
||||||
}
|
}
|
||||||
if (this->allow_start_from_chat_command != other.allow_start_from_chat_command) {
|
if (this->allow_start_from_chat_command != other.allow_start_from_chat_command) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different allow_start_from_chat_command state (existing: {}, new: {})",
|
"quest version has a different allow_start_from_chat_command state (existing: {}, new: {})",
|
||||||
this->allow_start_from_chat_command ? "true" : "false",
|
this->allow_start_from_chat_command ? "true" : "false",
|
||||||
other.allow_start_from_chat_command ? "true" : "false"));
|
other.allow_start_from_chat_command ? "true" : "false"));
|
||||||
}
|
}
|
||||||
if (this->joinable != other.joinable) {
|
if (this->joinable != other.joinable) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different joinability state (existing: {}, new: {})",
|
"quest version has a different joinability state (existing: {}, new: {})",
|
||||||
this->joinable ? "true" : "false", other.joinable ? "true" : "false"));
|
this->joinable ? "true" : "false", other.joinable ? "true" : "false"));
|
||||||
}
|
}
|
||||||
bool this_has_player_limit = (this->max_players != 0) && (this->max_players != 4);
|
bool this_has_player_limit = (this->max_players != 0) && (this->max_players != 4);
|
||||||
bool other_has_player_limit = (other.max_players != 0) && (other.max_players != 4);
|
bool other_has_player_limit = (other.max_players != 0) && (other.max_players != 4);
|
||||||
if ((this_has_player_limit || other_has_player_limit) && (this->max_players != other.max_players)) {
|
if ((this_has_player_limit || other_has_player_limit) && (this->max_players != other.max_players)) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different maximum player count (existing: {}, new: {})",
|
"quest version has a different maximum player count (existing: {}, new: {})",
|
||||||
this->max_players, other.max_players));
|
this->max_players, other.max_players));
|
||||||
}
|
}
|
||||||
if (this->lock_status_register != other.lock_status_register) {
|
if (this->lock_status_register != other.lock_status_register) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different lock status register (existing: {:04X}, new: {:04X})",
|
"quest version has a different lock status register (existing: {:04X}, new: {:04X})",
|
||||||
this->lock_status_register, other.lock_status_register));
|
this->lock_status_register, other.lock_status_register));
|
||||||
}
|
}
|
||||||
if (this->enemy_exp_overrides != other.enemy_exp_overrides) {
|
if (this->enemy_exp_overrides != other.enemy_exp_overrides) {
|
||||||
throw runtime_error("quest version has different enemy EXP overrides");
|
throw std::runtime_error("quest version has different enemy EXP overrides");
|
||||||
}
|
}
|
||||||
if (this->solo_unlock_flags != other.solo_unlock_flags) {
|
if (this->solo_unlock_flags != other.solo_unlock_flags) {
|
||||||
throw runtime_error(std::format("quest version has a different set of solo unlock flags"));
|
throw std::runtime_error(std::format("quest version has a different set of solo unlock flags"));
|
||||||
}
|
}
|
||||||
if (!this->create_item_mask_entries.empty() && !other.create_item_mask_entries.empty() &&
|
if (!this->create_item_mask_entries.empty() && !other.create_item_mask_entries.empty() &&
|
||||||
this->create_item_mask_entries != other.create_item_mask_entries) {
|
this->create_item_mask_entries != other.create_item_mask_entries) {
|
||||||
string this_str, other_str;
|
std::string this_str, other_str;
|
||||||
for (const auto& item : this->create_item_mask_entries) {
|
for (const auto& item : this->create_item_mask_entries) {
|
||||||
if (!this_str.empty()) {
|
if (!this_str.empty()) {
|
||||||
this_str += ", ";
|
this_str += ", ";
|
||||||
@@ -139,32 +137,32 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
|||||||
}
|
}
|
||||||
other_str += item.str();
|
other_str += item.str();
|
||||||
}
|
}
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different set of create item masks (existing: {}, new: {})", this_str, other_str));
|
"quest version has a different set of create item masks (existing: {}, new: {})", this_str, other_str));
|
||||||
}
|
}
|
||||||
if (!this->battle_rules != !other.battle_rules) {
|
if (!this->battle_rules != !other.battle_rules) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different battle rules presence state (existing: {}, new: {})",
|
"quest version has a different battle rules presence state (existing: {}, new: {})",
|
||||||
this->battle_rules ? "present" : "absent", other.battle_rules ? "present" : "absent"));
|
this->battle_rules ? "present" : "absent", other.battle_rules ? "present" : "absent"));
|
||||||
}
|
}
|
||||||
if (this->battle_rules && (*this->battle_rules != *other.battle_rules)) {
|
if (this->battle_rules && (*this->battle_rules != *other.battle_rules)) {
|
||||||
string existing_str = this->battle_rules->json().serialize();
|
std::string existing_str = this->battle_rules->json().serialize();
|
||||||
string new_str = other.battle_rules->json().serialize();
|
std::string new_str = other.battle_rules->json().serialize();
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different battle rules (existing: {}, new: {})", existing_str, new_str));
|
"quest version has different battle rules (existing: {}, new: {})", existing_str, new_str));
|
||||||
}
|
}
|
||||||
if (this->challenge_template_index != other.challenge_template_index) {
|
if (this->challenge_template_index != other.challenge_template_index) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different challenge template index (existing: {}, new: {})",
|
"quest version has different challenge template index (existing: {}, new: {})",
|
||||||
this->challenge_template_index, other.challenge_template_index));
|
this->challenge_template_index, other.challenge_template_index));
|
||||||
}
|
}
|
||||||
if (this->challenge_exp_multiplier != other.challenge_exp_multiplier) {
|
if (this->challenge_exp_multiplier != other.challenge_exp_multiplier) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different challenge EXP multiplier (existing: {}, new: {})",
|
"quest version has different challenge EXP multiplier (existing: {}, new: {})",
|
||||||
this->challenge_exp_multiplier, other.challenge_exp_multiplier));
|
this->challenge_exp_multiplier, other.challenge_exp_multiplier));
|
||||||
}
|
}
|
||||||
if (this->challenge_difficulty != other.challenge_difficulty) {
|
if (this->challenge_difficulty != other.challenge_difficulty) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different challenge difficulty (existing: {}, new: {})",
|
"quest version has different challenge difficulty (existing: {}, new: {})",
|
||||||
name_for_difficulty(this->challenge_difficulty), name_for_difficulty(other.challenge_difficulty)));
|
name_for_difficulty(this->challenge_difficulty), name_for_difficulty(other.challenge_difficulty)));
|
||||||
}
|
}
|
||||||
@@ -172,58 +170,59 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
|||||||
const auto& this_fa = this->floor_assignments[z];
|
const auto& this_fa = this->floor_assignments[z];
|
||||||
const auto& other_fa = other.floor_assignments[z];
|
const auto& other_fa = other.floor_assignments[z];
|
||||||
if (this_fa.area != other_fa.area) {
|
if (this_fa.area != other_fa.area) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different area on floor 0x{:02X} (existing: {}, new: {})",
|
"quest version has different area on floor 0x{:02X} (existing: {}, new: {})",
|
||||||
z, this_fa.str(), other_fa.str()));
|
z, this_fa.str(), other_fa.str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->description_flag != other.description_flag) {
|
if (this->description_flag != other.description_flag) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different description flag (existing: {:02X}, new: {:02X})",
|
"quest version has different description flag (existing: {:02X}, new: {:02X})",
|
||||||
this->description_flag, other.description_flag));
|
this->description_flag, other.description_flag));
|
||||||
}
|
}
|
||||||
if (!this->available_expression != !other.available_expression) {
|
if (!this->available_expression != !other.available_expression) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has available expression but root quest does not, or vice versa (existing: {}, new: {})",
|
"quest version has available expression but root quest does not, or vice versa (existing: {}, new: {})",
|
||||||
this->available_expression ? "present" : "absent", other.available_expression ? "present" : "absent"));
|
this->available_expression ? "present" : "absent", other.available_expression ? "present" : "absent"));
|
||||||
}
|
}
|
||||||
if (this->available_expression && *this->available_expression != *other.available_expression) {
|
if (this->available_expression && *this->available_expression != *other.available_expression) {
|
||||||
string existing_str = this->available_expression->str();
|
std::string existing_str = this->available_expression->str();
|
||||||
string new_str = other.available_expression->str();
|
std::string new_str = other.available_expression->str();
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different available expression (existing: {}, new: {})", existing_str, new_str));
|
"quest version has a different available expression (existing: {}, new: {})", existing_str, new_str));
|
||||||
}
|
}
|
||||||
if (!this->enabled_expression != !other.enabled_expression) {
|
if (!this->enabled_expression != !other.enabled_expression) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has enabled expression but root quest does not, or vice versa (existing: {}, new: {})",
|
"quest version has enabled expression but root quest does not, or vice versa (existing: {}, new: {})",
|
||||||
this->enabled_expression ? "present" : "absent", other.enabled_expression ? "present" : "absent"));
|
this->enabled_expression ? "present" : "absent", other.enabled_expression ? "present" : "absent"));
|
||||||
}
|
}
|
||||||
if (this->enabled_expression && *this->enabled_expression != *other.enabled_expression) {
|
if (this->enabled_expression && *this->enabled_expression != *other.enabled_expression) {
|
||||||
string existing_str = this->enabled_expression->str();
|
std::string existing_str = this->enabled_expression->str();
|
||||||
string new_str = other.enabled_expression->str();
|
std::string new_str = other.enabled_expression->str();
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has a different enabled expression (existing: {}, new: {})", existing_str, new_str));
|
"quest version has a different enabled expression (existing: {}, new: {})", existing_str, new_str));
|
||||||
}
|
}
|
||||||
if (this->common_item_set_name != other.common_item_set_name) {
|
if (this->common_item_set_name != other.common_item_set_name) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different common table name (existing: {}, new: {})",
|
"quest version has different common table name (existing: {}, new: {})",
|
||||||
this->common_item_set_name, other.common_item_set_name));
|
this->common_item_set_name, other.common_item_set_name));
|
||||||
}
|
}
|
||||||
if (this->rare_item_set_name != other.rare_item_set_name) {
|
if (this->rare_item_set_name != other.rare_item_set_name) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different rare table name (existing: {}, new: {})",
|
"quest version has different rare table name (existing: {}, new: {})",
|
||||||
this->rare_item_set_name, other.rare_item_set_name));
|
this->rare_item_set_name, other.rare_item_set_name));
|
||||||
}
|
}
|
||||||
if (this->allowed_drop_modes != other.allowed_drop_modes) {
|
if (this->allowed_drop_modes != other.allowed_drop_modes) {
|
||||||
throw runtime_error(format("quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
|
throw std::runtime_error(std::format(
|
||||||
|
"quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
|
||||||
this->allowed_drop_modes, other.allowed_drop_modes));
|
this->allowed_drop_modes, other.allowed_drop_modes));
|
||||||
}
|
}
|
||||||
if (this->default_drop_mode != other.default_drop_mode) {
|
if (this->default_drop_mode != other.default_drop_mode) {
|
||||||
throw runtime_error(format("quest version has different default drop mode (existing: {}, new: {})",
|
throw std::runtime_error(std::format("quest version has different default drop mode (existing: {}, new: {})",
|
||||||
phosg::name_for_enum(this->default_drop_mode), phosg::name_for_enum(other.default_drop_mode)));
|
phosg::name_for_enum(this->default_drop_mode), phosg::name_for_enum(other.default_drop_mode)));
|
||||||
}
|
}
|
||||||
if (this->enable_schtserv_commands != other.enable_schtserv_commands) {
|
if (this->enable_schtserv_commands != other.enable_schtserv_commands) {
|
||||||
throw runtime_error(format(
|
throw std::runtime_error(std::format(
|
||||||
"quest version has different value for enable_schtserv_commands (existing: {}, new: {})",
|
"quest version has different value for enable_schtserv_commands (existing: {}, new: {})",
|
||||||
this->enable_schtserv_commands ? "true" : "false", other.enable_schtserv_commands ? "true" : "false"));
|
this->enable_schtserv_commands ? "true" : "false", other.enable_schtserv_commands ? "true" : "false"));
|
||||||
}
|
}
|
||||||
@@ -354,7 +353,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
|
|||||||
// Key is like "Difficulty:Floor:EnemyType" or "Difficulty:EnemyType"
|
// Key is like "Difficulty:Floor:EnemyType" or "Difficulty:EnemyType"
|
||||||
auto key_tokens = phosg::split(key, ':');
|
auto key_tokens = phosg::split(key, ':');
|
||||||
|
|
||||||
static const unordered_map<string, Difficulty> difficulty_keys(
|
static const std::unordered_map<std::string, Difficulty> difficulty_keys(
|
||||||
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
||||||
|
|
||||||
Difficulty difficulty = Difficulty::NORMAL;
|
Difficulty difficulty = Difficulty::NORMAL;
|
||||||
@@ -366,7 +365,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
|
|||||||
floor = stoul(key_tokens[1], nullptr, 0);
|
floor = stoul(key_tokens[1], nullptr, 0);
|
||||||
enemy_type = phosg::enum_for_name<EnemyType>(key_tokens[2]);
|
enemy_type = phosg::enum_for_name<EnemyType>(key_tokens[2]);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("malformatted key: " + key);
|
throw std::runtime_error("malformatted key: " + key);
|
||||||
}
|
}
|
||||||
difficulty = difficulty_keys.at(key_tokens[0]);
|
difficulty = difficulty_keys.at(key_tokens[0]);
|
||||||
if (floor == 0xFF) {
|
if (floor == 0xFF) {
|
||||||
@@ -379,7 +378,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
throw std::runtime_error(std::format("invalid enemy EXP overrides: ", e.what()));
|
throw std::runtime_error(std::format("invalid enemy EXP overrides: ", e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ struct QuestMetadata {
|
|||||||
std::string str() const;
|
std::string str() const;
|
||||||
};
|
};
|
||||||
uint32_t category_id = 0xFFFFFFFF;
|
uint32_t category_id = 0xFFFFFFFF;
|
||||||
uint32_t quest_number = 0xFFFFFFFF;
|
uint32_t quest_number = 0;
|
||||||
Episode episode = Episode::NONE;
|
Episode episode = Episode::NONE;
|
||||||
std::array<FloorAssignment, 0x12> floor_assignments;
|
std::array<FloorAssignment, 0x12> floor_assignments;
|
||||||
bool joinable = false;
|
bool joinable = false;
|
||||||
|
|||||||
+321
-300
File diff suppressed because it is too large
Load Diff
+66
-71
@@ -9,18 +9,14 @@
|
|||||||
#include "ItemData.hh"
|
#include "ItemData.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
std::string RareItemSet::ExpandedDrop::str() const {
|
||||||
|
|
||||||
string RareItemSet::ExpandedDrop::str() const {
|
|
||||||
auto frac = phosg::reduce_fraction<uint64_t>(this->probability, 0x100000000);
|
auto frac = phosg::reduce_fraction<uint64_t>(this->probability, 0x100000000);
|
||||||
auto hex = this->data.hex();
|
auto hex = this->data.hex();
|
||||||
return std::format(
|
return std::format("({:08X} => {}/{}) {}", this->probability, frac.first, frac.second, hex);
|
||||||
"({:08X} => {}/{}) {}",
|
|
||||||
this->probability, frac.first, frac.second, hex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string RareItemSet::ExpandedDrop::str(shared_ptr<const ItemNameIndex> name_index) const {
|
std::string RareItemSet::ExpandedDrop::str(std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||||
string ret = this->str();
|
std::string ret = this->str();
|
||||||
ret += " (";
|
ret += " (";
|
||||||
ret += name_index->describe_item(this->data);
|
ret += name_index->describe_item(this->data);
|
||||||
ret += ")";
|
ret += ")";
|
||||||
@@ -74,7 +70,7 @@ uint8_t RareItemSet::compress_rate(uint32_t probability) {
|
|||||||
RareItemSet::ParsedRELData::PackedDrop::PackedDrop(const ExpandedDrop& exp)
|
RareItemSet::ParsedRELData::PackedDrop::PackedDrop(const ExpandedDrop& exp)
|
||||||
: probability(RareItemSet::compress_rate(exp.probability)) {
|
: probability(RareItemSet::compress_rate(exp.probability)) {
|
||||||
if (!exp.data.can_be_encoded_in_rel_rare_table()) {
|
if (!exp.data.can_be_encoded_in_rel_rare_table()) {
|
||||||
throw runtime_error("item " + exp.data.short_hex() + " has extended attributes and cannot be encoded in a REL file");
|
throw std::runtime_error("item " + exp.data.short_hex() + " has extended attributes and cannot be encoded in a REL file");
|
||||||
}
|
}
|
||||||
this->item_code[0] = exp.data.data1[0];
|
this->item_code[0] = exp.data.data1[0];
|
||||||
this->item_code[1] = exp.data.data1[1];
|
this->item_code[1] = exp.data.data1[1];
|
||||||
@@ -182,7 +178,7 @@ RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
|
|||||||
for (const auto& [enemy_type, specs] : collection.enemy_specs) {
|
for (const auto& [enemy_type, specs] : collection.enemy_specs) {
|
||||||
const auto& def = type_definition_for_enemy(enemy_type);
|
const auto& def = type_definition_for_enemy(enemy_type);
|
||||||
if (def.rt_index == 0xFF) {
|
if (def.rt_index == 0xFF) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"monster spec for {} has no rt_index and cannot be converted to ItemRT format", def.enum_name));
|
"monster spec for {} has no rt_index and cannot be converted to ItemRT format", def.enum_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,14 +187,14 @@ RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
|
|||||||
if (dest_spec.data.empty()) {
|
if (dest_spec.data.empty()) {
|
||||||
dest_spec = spec;
|
dest_spec = spec;
|
||||||
} else if ((dest_spec.probability != spec.probability) || (dest_spec.data != spec.data)) {
|
} else if ((dest_spec.probability != spec.probability) || (dest_spec.data != spec.data)) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"monster spec for {} contains multiple drops and cannot be converted to ItemRT format", def.enum_name));
|
"monster spec for {} contains multiple drops and cannot be converted to ItemRT format", def.enum_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collection.box_specs.size() > 0xFF) {
|
if (collection.box_specs.size() > 0xFF) {
|
||||||
throw runtime_error("area_norm value too high");
|
throw std::runtime_error("area_norm value too high");
|
||||||
}
|
}
|
||||||
for (uint8_t area_norm = 0; area_norm < collection.box_specs.size(); area_norm++) {
|
for (uint8_t area_norm = 0; area_norm < collection.box_specs.size(); area_norm++) {
|
||||||
for (const auto& spec : collection.box_specs[area_norm]) {
|
for (const auto& spec : collection.box_specs[area_norm]) {
|
||||||
@@ -249,14 +245,14 @@ RareItemSet::RareItemSet(const AFSArchive& afs, bool is_v1) {
|
|||||||
ParsedRELData rel(afs.get_reader(index), false, is_v1);
|
ParsedRELData rel(afs.get_reader(index), false, is_v1);
|
||||||
this->collections.emplace(
|
this->collections.emplace(
|
||||||
this->key_for_params(mode, Episode::EP1, difficulty, section_id), rel.as_collection(Episode::EP1));
|
this->key_for_params(mode, Episode::EP1, difficulty, section_id), rel.as_collection(Episode::EP1));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string RareItemSet::gsl_entry_name_for_table(
|
std::string RareItemSet::gsl_entry_name_for_table(
|
||||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id) {
|
GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id) {
|
||||||
return std::format("ItemRT{}{}{}{}.rel",
|
return std::format("ItemRT{}{}{}{}.rel",
|
||||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||||
@@ -271,11 +267,11 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
|
|||||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||||
try {
|
try {
|
||||||
string filename = this->gsl_entry_name_for_table(mode, episode, difficulty, section_id);
|
std::string filename = this->gsl_entry_name_for_table(mode, episode, difficulty, section_id);
|
||||||
ParsedRELData rel(gsl.get_reader(filename), is_big_endian, false);
|
ParsedRELData rel(gsl.get_reader(filename), is_big_endian, false);
|
||||||
this->collections.emplace(
|
this->collections.emplace(
|
||||||
this->key_for_params(mode, episode, difficulty, section_id), rel.as_collection(episode));
|
this->key_for_params(mode, episode, difficulty, section_id), rel.as_collection(episode));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +279,7 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
|
RareItemSet::RareItemSet(const std::string& rel_data, bool is_big_endian) {
|
||||||
// Tables are 0x280 bytes in size in this format, laid out sequentially
|
// Tables are 0x280 bytes in size in this format, laid out sequentially
|
||||||
phosg::StringReader r(rel_data);
|
phosg::StringReader r(rel_data);
|
||||||
for (Episode episode : ALL_EPISODES_V4) {
|
for (Episode episode : ALL_EPISODES_V4) {
|
||||||
@@ -295,26 +291,26 @@ RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
|
|||||||
ParsedRELData rel(r.sub(0x280 * index, 0x280), is_big_endian, false);
|
ParsedRELData rel(r.sub(0x280 * index, 0x280), is_big_endian, false);
|
||||||
this->collections.emplace(
|
this->collections.emplace(
|
||||||
this->key_for_params(GameMode::NORMAL, episode, difficulty, section_id), rel.as_collection(episode));
|
this->key_for_params(GameMode::NORMAL, episode, difficulty, section_id), rel.as_collection(episode));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex> name_index) {
|
RareItemSet::RareItemSet(const phosg::JSON& json, std::shared_ptr<const ItemNameIndex> name_index) {
|
||||||
for (const auto& mode_it : json.as_dict()) {
|
for (const auto& mode_it : json.as_dict()) {
|
||||||
static const unordered_map<string, GameMode> mode_keys(
|
static const std::unordered_map<std::string, GameMode> mode_keys(
|
||||||
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
||||||
GameMode mode = mode_keys.at(mode_it.first);
|
GameMode mode = mode_keys.at(mode_it.first);
|
||||||
|
|
||||||
for (const auto& episode_it : mode_it.second->as_dict()) {
|
for (const auto& episode_it : mode_it.second->as_dict()) {
|
||||||
static const unordered_map<string, Episode> episode_keys(
|
static const std::unordered_map<std::string, Episode> episode_keys(
|
||||||
{{"Episode1", Episode::EP1}, {"Episode2", Episode::EP2}, {"Episode4", Episode::EP4}});
|
{{"Episode1", Episode::EP1}, {"Episode2", Episode::EP2}, {"Episode4", Episode::EP4}});
|
||||||
Episode episode = episode_keys.at(episode_it.first);
|
Episode episode = episode_keys.at(episode_it.first);
|
||||||
|
|
||||||
for (const auto& difficulty_it : episode_it.second->as_dict()) {
|
for (const auto& difficulty_it : episode_it.second->as_dict()) {
|
||||||
static const unordered_map<string, Difficulty> difficulty_keys(
|
static const std::unordered_map<std::string, Difficulty> difficulty_keys(
|
||||||
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
||||||
Difficulty difficulty = difficulty_keys.at(difficulty_it.first);
|
Difficulty difficulty = difficulty_keys.at(difficulty_it.first);
|
||||||
|
|
||||||
@@ -323,7 +319,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
|||||||
|
|
||||||
auto& collection = this->collections[this->key_for_params(mode, episode, difficulty, section_id)];
|
auto& collection = this->collections[this->key_for_params(mode, episode, difficulty, section_id)];
|
||||||
for (const auto& [enemy_type_name, specs_json] : section_id_it.second->as_dict()) {
|
for (const auto& [enemy_type_name, specs_json] : section_id_it.second->as_dict()) {
|
||||||
vector<ExpandedDrop>* target;
|
std::vector<ExpandedDrop>* target;
|
||||||
if (enemy_type_name.starts_with("Box-")) {
|
if (enemy_type_name.starts_with("Box-")) {
|
||||||
uint8_t area_norm = FloorDefinition::get(episode, enemy_type_name.substr(4)).drop_area_norm;
|
uint8_t area_norm = FloorDefinition::get(episode, enemy_type_name.substr(4)).drop_area_norm;
|
||||||
if (collection.box_specs.size() <= area_norm) {
|
if (collection.box_specs.size() <= area_norm) {
|
||||||
@@ -343,7 +339,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
|||||||
} else if (prob_desc.is_string()) {
|
} else if (prob_desc.is_string()) {
|
||||||
auto tokens = phosg::split(prob_desc.as_string(), '/');
|
auto tokens = phosg::split(prob_desc.as_string(), '/');
|
||||||
if (tokens.size() != 2) {
|
if (tokens.size() != 2) {
|
||||||
throw runtime_error("invalid probability specification");
|
throw std::runtime_error("invalid probability specification");
|
||||||
}
|
}
|
||||||
uint64_t numerator = stoull(tokens[0], nullptr, 0);
|
uint64_t numerator = stoull(tokens[0], nullptr, 0);
|
||||||
uint64_t denominator = stoull(tokens[1], nullptr, 0);
|
uint64_t denominator = stoull(tokens[1], nullptr, 0);
|
||||||
@@ -362,11 +358,11 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
|||||||
d.data.data1[2] = item_code & 0xFF;
|
d.data.data1[2] = item_code & 0xFF;
|
||||||
} else if (item_desc.is_string()) {
|
} else if (item_desc.is_string()) {
|
||||||
if (!name_index) {
|
if (!name_index) {
|
||||||
throw runtime_error("item name index is not available");
|
throw std::runtime_error("item name index is not available");
|
||||||
}
|
}
|
||||||
d.data = name_index->parse_item_description(item_desc.as_string());
|
d.data = name_index->parse_item_description(item_desc.as_string());
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid item description type");
|
throw std::runtime_error("invalid item description type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,7 +373,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string RareItemSet::serialize_afs(bool is_v1) const {
|
std::string RareItemSet::serialize_afs(bool is_v1) const {
|
||||||
vector<string> files;
|
std::vector<std::string> files;
|
||||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||||
if (is_v1 && (difficulty == Difficulty::ULTIMATE)) {
|
if (is_v1 && (difficulty == Difficulty::ULTIMATE)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -391,16 +387,16 @@ std::string RareItemSet::serialize_afs(bool is_v1) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
||||||
unordered_map<string, string> files;
|
std::unordered_map<std::string, std::string> files;
|
||||||
|
|
||||||
for (Episode episode : ALL_EPISODES_V3) {
|
for (Episode episode : ALL_EPISODES_V3) {
|
||||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
try {
|
try {
|
||||||
string filename = this->gsl_entry_name_for_table(GameMode::NORMAL, episode, difficulty, section_id);
|
std::string filename = this->gsl_entry_name_for_table(GameMode::NORMAL, episode, difficulty, section_id);
|
||||||
ParsedRELData rel(this->get_collection(GameMode::NORMAL, episode, difficulty, section_id));
|
ParsedRELData rel(this->get_collection(GameMode::NORMAL, episode, difficulty, section_id));
|
||||||
files.emplace(filename, rel.serialize(big_endian, false));
|
files.emplace(filename, rel.serialize(big_endian, false));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
// Collection does not exist; skip it
|
// Collection does not exist; skip it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,10 +406,10 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
|||||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
try {
|
try {
|
||||||
string filename = this->gsl_entry_name_for_table(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id);
|
std::string filename = this->gsl_entry_name_for_table(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id);
|
||||||
ParsedRELData rel(this->get_collection(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id));
|
ParsedRELData rel(this->get_collection(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id));
|
||||||
files.emplace(filename, rel.serialize(big_endian, false));
|
files.emplace(filename, rel.serialize(big_endian, false));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
// Collection does not exist; skip it
|
// Collection does not exist; skip it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,17 +417,17 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
|||||||
return GSLArchive::generate(files, big_endian);
|
return GSLArchive::generate(files, big_endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
string RareItemSet::serialize_html(
|
std::string RareItemSet::serialize_html(
|
||||||
GameMode mode,
|
GameMode mode,
|
||||||
Episode episode,
|
Episode episode,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
shared_ptr<const ItemNameIndex> name_index,
|
std::shared_ptr<const ItemNameIndex> name_index,
|
||||||
shared_ptr<const CommonItemSet> common_item_set) const {
|
std::shared_ptr<const CommonItemSet> common_item_set) const {
|
||||||
|
|
||||||
struct ZoneTypes {
|
struct ZoneTypes {
|
||||||
const char* name;
|
const char* name;
|
||||||
vector<uint8_t> floors;
|
std::vector<uint8_t> floors;
|
||||||
vector<EnemyType> types;
|
std::vector<EnemyType> types;
|
||||||
};
|
};
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -561,7 +557,7 @@ string RareItemSet::serialize_html(
|
|||||||
return (((r / 8) & 0xFF) << 16) | (((g / 8) & 0xFF) << 8) | ((b / 8) & 0xFF);
|
return (((r / 8) & 0xFF) << 16) | (((g / 8) & 0xFF) << 8) | ((b / 8) & 0xFF);
|
||||||
};
|
};
|
||||||
|
|
||||||
deque<string> blocks;
|
std::deque<std::string> blocks;
|
||||||
blocks.emplace_back(std::format("\
|
blocks.emplace_back(std::format("\
|
||||||
<html>\n\
|
<html>\n\
|
||||||
<head>\n\
|
<head>\n\
|
||||||
@@ -643,7 +639,7 @@ string RareItemSet::serialize_html(
|
|||||||
</style>\n\
|
</style>\n\
|
||||||
</head><body>\n");
|
</head><body>\n");
|
||||||
|
|
||||||
string mode_token;
|
std::string mode_token;
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case GameMode::NORMAL:
|
case GameMode::NORMAL:
|
||||||
mode_token = "";
|
mode_token = "";
|
||||||
@@ -658,7 +654,7 @@ string RareItemSet::serialize_html(
|
|||||||
mode_token = " (solo mode)";
|
mode_token = " (solo mode)";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid game mode");
|
throw std::logic_error("invalid game mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks.emplace_back(std::format(
|
blocks.emplace_back(std::format(
|
||||||
@@ -679,7 +675,7 @@ string RareItemSet::serialize_html(
|
|||||||
blocks.emplace_back("</tr>");
|
blocks.emplace_back("</tr>");
|
||||||
};
|
};
|
||||||
|
|
||||||
auto add_specs_row = [&](const EnemyTypeDefinition* type_def, const char* loc_name, bool is_box, const array<vector<ExpandedDrop>, 10>& specs_lists) -> void {
|
auto add_specs_row = [&](const EnemyTypeDefinition* type_def, const char* loc_name, bool is_box, const std::array<std::vector<ExpandedDrop>, 10>& specs_lists) -> void {
|
||||||
bool any_list_nonempty = false;
|
bool any_list_nonempty = false;
|
||||||
for (const auto& specs_list : specs_lists) {
|
for (const auto& specs_list : specs_lists) {
|
||||||
any_list_nonempty |= !specs_list.empty();
|
any_list_nonempty |= !specs_list.empty();
|
||||||
@@ -691,7 +687,7 @@ string RareItemSet::serialize_html(
|
|||||||
blocks.emplace_back(std::format("<tr><td class=\"loc-{}\">{}</td>", is_box ? "box" : "enemy", loc_name));
|
blocks.emplace_back(std::format("<tr><td class=\"loc-{}\">{}</td>", is_box ? "box" : "enemy", loc_name));
|
||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
blocks.emplace_back(std::format("<td class=\"sec{}-{}\">", section_id, is_box ? "box" : "enemy"));
|
blocks.emplace_back(std::format("<td class=\"sec{}-{}\">", section_id, is_box ? "box" : "enemy"));
|
||||||
vector<string> tokens;
|
std::vector<std::string> tokens;
|
||||||
for (const auto& spec : specs_lists[section_id]) {
|
for (const auto& spec : specs_lists[section_id]) {
|
||||||
if (!tokens.empty()) {
|
if (!tokens.empty()) {
|
||||||
tokens.emplace_back("");
|
tokens.emplace_back("");
|
||||||
@@ -705,7 +701,7 @@ string RareItemSet::serialize_html(
|
|||||||
uint8_t dar = 0;
|
uint8_t dar = 0;
|
||||||
try {
|
try {
|
||||||
dar = table->enemy_type_drop_probs.at(type_def->type);
|
dar = table->enemy_type_drop_probs.at(type_def->type);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
exact_token += std::format(" (DAR: {}%)", dar);
|
exact_token += std::format(" (DAR: {}%)", dar);
|
||||||
frac.first *= dar;
|
frac.first *= dar;
|
||||||
@@ -729,12 +725,12 @@ string RareItemSet::serialize_html(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string hex = example_item.short_hex();
|
std::string hex = example_item.short_hex();
|
||||||
string desc = name_index->describe_item(example_item, ItemNameIndex::Flag::NAME_ONLY);
|
std::string desc = name_index->describe_item(example_item, ItemNameIndex::Flag::NAME_ONLY);
|
||||||
tokens.emplace_back(std::format("<span class=\"item\" title=\"Hex: {}\">{}</span>", hex, desc));
|
tokens.emplace_back(std::format("<span class=\"item\" title=\"Hex: {}\">{}</span>", hex, desc));
|
||||||
|
|
||||||
float denom = static_cast<float>(frac.second) / static_cast<double>(frac.first);
|
float denom = static_cast<float>(frac.second) / static_cast<double>(frac.first);
|
||||||
string denom_token = (floor(denom) == denom)
|
std::string denom_token = (floor(denom) == denom)
|
||||||
? std::format("1 / {:.0f}", denom)
|
? std::format("1 / {:.0f}", denom)
|
||||||
: std::format("1 / {:.02f}", denom);
|
: std::format("1 / {:.02f}", denom);
|
||||||
tokens.emplace_back(std::format("<span class=\"rate\" title=\"{}\">{}</span>", exact_token, denom_token));
|
tokens.emplace_back(std::format("<span class=\"rate\" title=\"{}\">{}</span>", exact_token, denom_token));
|
||||||
@@ -751,7 +747,7 @@ string RareItemSet::serialize_html(
|
|||||||
for (const auto& zone_type : zone_types) {
|
for (const auto& zone_type : zone_types) {
|
||||||
add_location_header(zone_type.name);
|
add_location_header(zone_type.name);
|
||||||
for (EnemyType type : zone_type.types) {
|
for (EnemyType type : zone_type.types) {
|
||||||
array<vector<ExpandedDrop>, 10> specs_lists;
|
std::array<std::vector<ExpandedDrop>, 10> specs_lists;
|
||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
specs_lists[section_id] = this->get_enemy_specs(mode, episode, difficulty, section_id, type);
|
specs_lists[section_id] = this->get_enemy_specs(mode, episode, difficulty, section_id, type);
|
||||||
}
|
}
|
||||||
@@ -762,9 +758,9 @@ string RareItemSet::serialize_html(
|
|||||||
for (uint8_t floor : zone_type.floors) {
|
for (uint8_t floor : zone_type.floors) {
|
||||||
const auto& floor_def = FloorDefinition::get(episode, floor);
|
const auto& floor_def = FloorDefinition::get(episode, floor);
|
||||||
if (floor_def.drop_area_norm == 0xFF) {
|
if (floor_def.drop_area_norm == 0xFF) {
|
||||||
throw runtime_error("zone includes floors with no drop area");
|
throw std::runtime_error("zone includes floors with no drop area");
|
||||||
}
|
}
|
||||||
array<vector<ExpandedDrop>, 10> specs_lists;
|
std::array<std::vector<ExpandedDrop>, 10> specs_lists;
|
||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
specs_lists[section_id] = this->get_box_specs(mode, episode, difficulty, section_id, floor_def.drop_area_norm);
|
specs_lists[section_id] = this->get_box_specs(mode, episode, difficulty, section_id, floor_def.drop_area_norm);
|
||||||
}
|
}
|
||||||
@@ -777,7 +773,7 @@ string RareItemSet::serialize_html(
|
|||||||
return phosg::join(blocks, "");
|
return phosg::join(blocks, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const {
|
phosg::JSON RareItemSet::json(std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||||
auto modes_dict = phosg::JSON::dict();
|
auto modes_dict = phosg::JSON::dict();
|
||||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||||
auto episodes_dict = phosg::JSON::dict();
|
auto episodes_dict = phosg::JSON::dict();
|
||||||
@@ -853,12 +849,12 @@ void RareItemSet::multiply_all_rates(double factor) {
|
|||||||
for (auto& [_, collection] : this->collections) {
|
for (auto& [_, collection] : this->collections) {
|
||||||
for (auto& [_, specs] : collection.enemy_specs) {
|
for (auto& [_, specs] : collection.enemy_specs) {
|
||||||
for (auto& spec : specs) {
|
for (auto& spec : specs) {
|
||||||
spec.probability = min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
spec.probability = std::min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto& specs : collection.box_specs) {
|
for (auto& specs : collection.box_specs) {
|
||||||
for (auto& spec : specs) {
|
for (auto& spec : specs) {
|
||||||
spec.probability = min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
spec.probability = std::min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -870,11 +866,11 @@ void RareItemSet::print_collection(
|
|||||||
Episode episode,
|
Episode episode,
|
||||||
Difficulty difficulty,
|
Difficulty difficulty,
|
||||||
uint8_t section_id,
|
uint8_t section_id,
|
||||||
shared_ptr<const ItemNameIndex> name_index) const {
|
std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||||
const SpecCollection* collection;
|
const SpecCollection* collection;
|
||||||
try {
|
try {
|
||||||
collection = &this->get_collection(mode, episode, difficulty, section_id);
|
collection = &this->get_collection(mode, episode, difficulty, section_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,18 +885,17 @@ void RareItemSet::print_collection(
|
|||||||
try {
|
try {
|
||||||
const auto& def = type_definition_for_enemy(enemy_type);
|
const auto& def = type_definition_for_enemy(enemy_type);
|
||||||
for (const auto& spec : collection->enemy_specs.at(enemy_type)) {
|
for (const auto& spec : collection->enemy_specs.at(enemy_type)) {
|
||||||
string s = name_index ? spec.str(name_index) : spec.str();
|
phosg::fwrite_fmt(stream, " {:<23} {}\n", def.enum_name, (name_index ? spec.str(name_index) : spec.str()));
|
||||||
phosg::fwrite_fmt(stream, " {:<23} {}\n", def.enum_name, s);
|
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
phosg::fwrite_fmt(stream, " Box rares:\n");
|
phosg::fwrite_fmt(stream, " Box rares:\n");
|
||||||
for (size_t area_norm = 0; area_norm < collection->box_specs.size(); area_norm++) {
|
for (size_t area_norm = 0; area_norm < collection->box_specs.size(); area_norm++) {
|
||||||
for (const auto& spec : collection->box_specs[area_norm]) {
|
for (const auto& spec : collection->box_specs[area_norm]) {
|
||||||
string s = name_index ? spec.str(name_index) : spec.str();
|
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n",
|
||||||
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n", area_norm, s);
|
area_norm, (name_index ? spec.str(name_index) : spec.str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -912,7 +907,7 @@ void RareItemSet::print_all_collections(FILE* stream, std::shared_ptr<const Item
|
|||||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||||
try {
|
try {
|
||||||
this->print_collection(stream, mode, episode, difficulty, section_id, name_index);
|
this->print_collection(stream, mode, episode, difficulty, section_id, name_index);
|
||||||
} catch (const out_of_range& e) {
|
} catch (const std::out_of_range& e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -938,11 +933,11 @@ void RareItemSet::SpecCollection::print_diff(FILE* stream, const SpecCollection&
|
|||||||
const std::vector<ExpandedDrop>* other_specs = &empty_specs;
|
const std::vector<ExpandedDrop>* other_specs = &empty_specs;
|
||||||
try {
|
try {
|
||||||
this_specs = &this->enemy_specs.at(enemy_type);
|
this_specs = &this->enemy_specs.at(enemy_type);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
other_specs = &other.enemy_specs.at(enemy_type);
|
other_specs = &other.enemy_specs.at(enemy_type);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
if (*this_specs != *other_specs) {
|
if (*this_specs != *other_specs) {
|
||||||
phosg::fwrite_fmt(stream, " {}: {} -> {}\n",
|
phosg::fwrite_fmt(stream, " {}: {} -> {}\n",
|
||||||
@@ -969,11 +964,11 @@ void RareItemSet::print_diff(FILE* stream, const RareItemSet& other) const {
|
|||||||
const SpecCollection* other_coll = nullptr;
|
const SpecCollection* other_coll = nullptr;
|
||||||
try {
|
try {
|
||||||
this_coll = &this->get_collection(mode, episode, difficulty, section_id);
|
this_coll = &this->get_collection(mode, episode, difficulty, section_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
other_coll = &other.get_collection(mode, episode, difficulty, section_id);
|
other_coll = &other.get_collection(mode, episode, difficulty, section_id);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this_coll && !other_coll) {
|
if (!this_coll && !other_coll) {
|
||||||
@@ -1014,7 +1009,7 @@ std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_enemy_specs(
|
|||||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, EnemyType enemy_type) const {
|
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, EnemyType enemy_type) const {
|
||||||
try {
|
try {
|
||||||
return this->get_collection(mode, episode, difficulty, secid).enemy_specs.at(enemy_type);
|
return this->get_collection(mode, episode, difficulty, secid).enemy_specs.at(enemy_type);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
static const std::vector<ExpandedDrop> empty_vector;
|
static const std::vector<ExpandedDrop> empty_vector;
|
||||||
return empty_vector;
|
return empty_vector;
|
||||||
}
|
}
|
||||||
@@ -1024,7 +1019,7 @@ std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_box_specs(
|
|||||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const {
|
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const {
|
||||||
try {
|
try {
|
||||||
return this->get_collection(mode, episode, difficulty, secid).box_specs.at(area_norm);
|
return this->get_collection(mode, episode, difficulty, secid).box_specs.at(area_norm);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
static const std::vector<ExpandedDrop> empty_vector;
|
static const std::vector<ExpandedDrop> empty_vector;
|
||||||
return empty_vector;
|
return empty_vector;
|
||||||
}
|
}
|
||||||
@@ -1043,7 +1038,7 @@ const RareItemSet::SpecCollection& RareItemSet::get_collection(
|
|||||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const {
|
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const {
|
||||||
try {
|
try {
|
||||||
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid));
|
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid));
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
if (mode == GameMode::BATTLE || mode == GameMode::SOLO) {
|
if (mode == GameMode::BATTLE || mode == GameMode::SOLO) {
|
||||||
return this->collections.at(this->key_for_params(GameMode::NORMAL, episode, difficulty, secid));
|
return this->collections.at(this->key_for_params(GameMode::NORMAL, episode, difficulty, secid));
|
||||||
}
|
}
|
||||||
@@ -1053,10 +1048,10 @@ const RareItemSet::SpecCollection& RareItemSet::get_collection(
|
|||||||
|
|
||||||
uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) {
|
uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) {
|
||||||
if (static_cast<size_t>(difficulty) > 3) {
|
if (static_cast<size_t>(difficulty) > 3) {
|
||||||
throw logic_error("incorrect difficulty");
|
throw std::logic_error("incorrect difficulty");
|
||||||
}
|
}
|
||||||
if (secid > 10) {
|
if (secid > 10) {
|
||||||
throw logic_error("incorrect section id");
|
throw std::logic_error("incorrect section id");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t key = ((static_cast<size_t>(difficulty) & 3) << 4) | (secid & 0x0F);
|
uint16_t key = ((static_cast<size_t>(difficulty) & 3) << 4) | (secid & 0x0F);
|
||||||
@@ -1073,7 +1068,7 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty
|
|||||||
key |= 0x00C0;
|
key |= 0x00C0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid episode in RareItemSet");
|
throw std::logic_error("invalid episode in RareItemSet");
|
||||||
}
|
}
|
||||||
switch (episode) {
|
switch (episode) {
|
||||||
case Episode::EP1:
|
case Episode::EP1:
|
||||||
@@ -1085,7 +1080,7 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty
|
|||||||
key |= 0x0200;
|
key |= 0x0200;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid episode in RareItemSet");
|
throw std::logic_error("invalid episode in RareItemSet");
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|||||||
+509
-516
File diff suppressed because it is too large
Load Diff
+753
-790
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@ public:
|
|||||||
uint32_t player_flags = 0;
|
uint32_t player_flags = 0;
|
||||||
bool player_flags_is_v3 = false;
|
bool player_flags_is_v3 = false;
|
||||||
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
|
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
|
||||||
PlayerVisualConfig visual;
|
PlayerVisualConfigSharedT<false> visual_sh;
|
||||||
std::string name;
|
std::string name;
|
||||||
PlayerStats stats;
|
PlayerStats stats;
|
||||||
uint32_t num_items = 0;
|
uint32_t num_items = 0;
|
||||||
|
|||||||
+77
-85
@@ -8,10 +8,8 @@
|
|||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
|
||||||
using namespace std;
|
static std::string encode_chat_message(Version version, const std::string& message) {
|
||||||
|
std::string encoded_message;
|
||||||
static string encode_chat_message(Version version, const string& message) {
|
|
||||||
string encoded_message;
|
|
||||||
encoded_message.resize(8, 0);
|
encoded_message.resize(8, 0);
|
||||||
encoded_message += uses_utf16(version) ? tt_utf8_to_utf16("\tE" + message) : tt_utf8_to_ascii("\tE" + message);
|
encoded_message += uses_utf16(version) ? tt_utf8_to_utf16("\tE" + message) : tt_utf8_to_ascii("\tE" + message);
|
||||||
encoded_message.resize((encoded_message.size() + 3) & (~3));
|
encoded_message.resize((encoded_message.size() + 3) & (~3));
|
||||||
@@ -19,14 +17,10 @@ static string encode_chat_message(Version version, const string& message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReplaySession::Event::Event(Type type, uint64_t client_id, size_t line_num)
|
ReplaySession::Event::Event(Type type, uint64_t client_id, size_t line_num)
|
||||||
: type(type),
|
: type(type), client_id(client_id), allow_size_disparity(false), complete(false), line_num(line_num) {}
|
||||||
client_id(client_id),
|
|
||||||
allow_size_disparity(false),
|
|
||||||
complete(false),
|
|
||||||
line_num(line_num) {}
|
|
||||||
|
|
||||||
string ReplaySession::Event::str() const {
|
std::string ReplaySession::Event::str() const {
|
||||||
string ret;
|
std::string ret;
|
||||||
if (this->type == Type::CONNECT) {
|
if (this->type == Type::CONNECT) {
|
||||||
ret = std::format("Event[{}, CONNECT", this->client_id);
|
ret = std::format("Event[{}, CONNECT", this->client_id);
|
||||||
} else if (this->type == Type::DISCONNECT) {
|
} else if (this->type == Type::DISCONNECT) {
|
||||||
@@ -46,11 +40,11 @@ string ReplaySession::Event::str() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t id, uint16_t port, Version version)
|
ReplaySession::Client::Client(std::shared_ptr<asio::io_context> io_context, uint64_t id, uint16_t port, Version version)
|
||||||
: id(id),
|
: id(id),
|
||||||
port(port),
|
port(port),
|
||||||
version(version),
|
version(version),
|
||||||
channel(make_shared<PeerChannel>(
|
channel(std::make_shared<PeerChannel>(
|
||||||
io_context,
|
io_context,
|
||||||
this->version,
|
this->version,
|
||||||
Language::ENGLISH,
|
Language::ENGLISH,
|
||||||
@@ -60,12 +54,12 @@ ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t
|
|||||||
false,
|
false,
|
||||||
false)) {}
|
false)) {}
|
||||||
|
|
||||||
string ReplaySession::Client::str() const {
|
std::string ReplaySession::Client::str() const {
|
||||||
return std::format("Client[{}, T-{}, {}]", this->id, this->port, phosg::name_for_enum(this->version));
|
return std::format("Client[{}, T-{}, {}]", this->id, this->port, phosg::name_for_enum(this->version));
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, shared_ptr<Client> c, size_t line_num) {
|
std::shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num) {
|
||||||
auto event = make_shared<Event>(type, c->id, line_num);
|
auto event = std::make_shared<Event>(type, c->id, line_num);
|
||||||
if (!this->last_event.get()) {
|
if (!this->last_event.get()) {
|
||||||
this->first_event = event;
|
this->first_event = event;
|
||||||
} else {
|
} else {
|
||||||
@@ -78,7 +72,7 @@ shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, s
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
void ReplaySession::apply_default_mask(std::shared_ptr<Event> ev) {
|
||||||
auto version = this->clients.at(ev->client_id)->version;
|
auto version = this->clients.at(ev->client_id)->version;
|
||||||
|
|
||||||
void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4);
|
void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4);
|
||||||
@@ -160,7 +154,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
|||||||
mask.variations = Variations();
|
mask.variations = Variations();
|
||||||
mask.random_seed = 0;
|
mask.random_seed = 0;
|
||||||
for (size_t offset = sizeof(S_JoinGame_GC_64) +
|
for (size_t offset = sizeof(S_JoinGame_GC_64) +
|
||||||
offsetof(S_JoinGame_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum);
|
offsetof(S_JoinGame_Ep3_64::Ep3PlayerEntry, disp.visual.sh.name_color_checksum);
|
||||||
offset + 4 <= mask_size;
|
offset + 4 <= mask_size;
|
||||||
offset += sizeof(S_JoinGame_Ep3_64::Ep3PlayerEntry)) {
|
offset += sizeof(S_JoinGame_Ep3_64::Ep3PlayerEntry)) {
|
||||||
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
|
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
|
||||||
@@ -176,7 +170,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
|||||||
case 0x67:
|
case 0x67:
|
||||||
case 0x68: {
|
case 0x68: {
|
||||||
auto update_mask = [&]<typename CmdT>() -> void {
|
auto update_mask = [&]<typename CmdT>() -> void {
|
||||||
for (size_t offset = offsetof(CmdT, entries) + offsetof(typename CmdT::Entry, disp.visual.name_color_checksum);
|
for (size_t offset = offsetof(CmdT, entries) + offsetof(typename CmdT::Entry, disp.visual.sh.name_color_checksum);
|
||||||
offset + 4 <= mask_size;
|
offset + 4 <= mask_size;
|
||||||
offset += sizeof(typename CmdT::Entry)) {
|
offset += sizeof(typename CmdT::Entry)) {
|
||||||
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
|
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
|
||||||
@@ -198,10 +192,10 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
|||||||
auto& mask = check_size_t<S_JoinSpectatorTeam_Ep3_E8>(mask_data, mask_size);
|
auto& mask = check_size_t<S_JoinSpectatorTeam_Ep3_E8>(mask_data, mask_size);
|
||||||
mask.random_seed = 0;
|
mask.random_seed = 0;
|
||||||
for (size_t z = 0; z < 4; z++) {
|
for (size_t z = 0; z < 4; z++) {
|
||||||
mask.players[z].disp.visual.name_color_checksum = 0;
|
mask.players[z].disp.visual.sh.name_color_checksum = 0;
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < 8; z++) {
|
for (size_t z = 0; z < 8; z++) {
|
||||||
mask.spectator_players[z].disp.visual.name_color_checksum = 0;
|
mask.spectator_players[z].disp.visual.sh.name_color_checksum = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -271,19 +265,19 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
|||||||
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
||||||
if (header.subcommand == 0x60) {
|
if (header.subcommand == 0x60) {
|
||||||
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DCNTE_6x70>(mask_data, mask_size, 0xFFFF);
|
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DCNTE_6x70>(mask_data, mask_size, 0xFFFF);
|
||||||
mask.visual.name_color_checksum = 0;
|
mask.visual.sh.name_color_checksum = 0;
|
||||||
}
|
}
|
||||||
} else if (version == Version::DC_11_2000) {
|
} else if (version == Version::DC_11_2000) {
|
||||||
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
||||||
if (header.subcommand == 0x67) {
|
if (header.subcommand == 0x67) {
|
||||||
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DC112000_6x70>(mask_data, mask_size, 0xFFFF);
|
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DC112000_6x70>(mask_data, mask_size, 0xFFFF);
|
||||||
mask.visual.name_color_checksum = 0;
|
mask.visual.sh.name_color_checksum = 0;
|
||||||
}
|
}
|
||||||
} else if (!is_pre_v1(version)) {
|
} else if (!is_pre_v1(version)) {
|
||||||
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
|
||||||
if (header.subcommand == 0x70) {
|
if (header.subcommand == 0x70) {
|
||||||
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DC_PC_6x70>(mask_data, mask_size, 0xFFFF);
|
auto& mask = check_size_t<G_SyncPlayerDispAndInventory_DC_PC_6x70>(mask_data, mask_size, 0xFFFF);
|
||||||
mask.base.visual.name_color_checksum = 0;
|
mask.visual.sh.name_color_checksum = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -325,11 +319,11 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid game version");
|
throw std::logic_error("invalid game version");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
|
||||||
: state(state),
|
: state(state),
|
||||||
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
|
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
|
||||||
commands_sent(0),
|
commands_sent(0),
|
||||||
@@ -338,13 +332,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
bytes_received(0),
|
bytes_received(0),
|
||||||
idle_timeout_timer(*this->state->io_context),
|
idle_timeout_timer(*this->state->io_context),
|
||||||
run_failed(false) {
|
run_failed(false) {
|
||||||
shared_ptr<Event> parsing_command = nullptr;
|
std::shared_ptr<Event> parsing_command = nullptr;
|
||||||
|
|
||||||
size_t line_num = 0;
|
size_t line_num = 0;
|
||||||
size_t num_events = 0;
|
size_t num_events = 0;
|
||||||
while (!feof(input_log)) {
|
while (!feof(input_log)) {
|
||||||
line_num++;
|
line_num++;
|
||||||
string line = phosg::fgets(input_log);
|
std::string line = phosg::fgets(input_log);
|
||||||
if (line.ends_with("\n")) {
|
if (line.ends_with("\n")) {
|
||||||
line.resize(line.size() - 1);
|
line.resize(line.size() - 1);
|
||||||
}
|
}
|
||||||
@@ -353,11 +347,11 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsing_command.get()) {
|
if (parsing_command.get()) {
|
||||||
string expected_start = std::format("{:04X} |", parsing_command->data.size());
|
std::string expected_start = std::format("{:04X} |", parsing_command->data.size());
|
||||||
if (line.starts_with(expected_start)) {
|
if (line.starts_with(expected_start)) {
|
||||||
// Parse out the hex part of the hex/ASCII dump
|
// Parse out the hex part of the hex/ASCII dump
|
||||||
string mask_bytes;
|
std::string mask_bytes;
|
||||||
string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
|
std::string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
|
||||||
parsing_command->data += data_bytes;
|
parsing_command->data += data_bytes;
|
||||||
parsing_command->mask += mask_bytes;
|
parsing_command->mask += mask_bytes;
|
||||||
continue;
|
continue;
|
||||||
@@ -378,35 +372,35 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
if (line.starts_with("### cc ")) {
|
if (line.starts_with("### cc ")) {
|
||||||
// ### cc $<chat command>
|
// ### cc $<chat command>
|
||||||
if (this->clients.size() != 1) {
|
if (this->clients.size() != 1) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
|
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
|
||||||
line_num));
|
line_num));
|
||||||
}
|
}
|
||||||
shared_ptr<Event> event;
|
std::shared_ptr<Event> event;
|
||||||
try {
|
try {
|
||||||
auto c = this->clients.begin()->second;
|
auto c = this->clients.begin()->second;
|
||||||
event = this->create_event(Event::Type::SEND, c, line_num);
|
event = this->create_event(Event::Type::SEND, c, line_num);
|
||||||
event->data = encode_chat_message(c->version, line.substr(7));
|
event->data = encode_chat_message(c->version, line.substr(7));
|
||||||
num_events++;
|
num_events++;
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
} else if (line.starts_with("### on C-")) {
|
} else if (line.starts_with("### on C-")) {
|
||||||
// ### on C-{} cc <chat command>
|
// ### on C-{} cc <chat command>
|
||||||
shared_ptr<Event> event;
|
std::shared_ptr<Event> event;
|
||||||
try {
|
try {
|
||||||
size_t end_offset;
|
size_t end_offset;
|
||||||
auto c = this->clients.at(stoull(line.substr(9), &end_offset, 16));
|
auto c = this->clients.at(stoull(line.substr(9), &end_offset, 16));
|
||||||
if (line.compare(end_offset + 9, 4, " cc ") != 0) {
|
if (line.compare(end_offset + 9, 4, " cc ") != 0) {
|
||||||
throw runtime_error("malformed `on C-X cc $...` shortcut command");
|
throw std::runtime_error("malformed `on C-X cc $...` shortcut command");
|
||||||
}
|
}
|
||||||
event = this->create_event(Event::Type::SEND, c, line_num);
|
event = this->create_event(Event::Type::SEND, c, line_num);
|
||||||
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
|
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
|
||||||
num_events++;
|
num_events++;
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -414,25 +408,25 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
// I <pid/ts> - [GameServer] Client connected: C-1 via TG-9000-GC_V3-gc-jp10-game_server
|
// I <pid/ts> - [GameServer] Client connected: C-1 via TG-9000-GC_V3-gc-jp10-game_server
|
||||||
// I <pid/ts> - [GameServer] Client connected: C-3 via TSI-9000-GC_V3-game_server
|
// I <pid/ts> - [GameServer] Client connected: C-3 via TSI-9000-GC_V3-game_server
|
||||||
size_t offset = line.find(" - [GameServer] Client connected: C-");
|
size_t offset = line.find(" - [GameServer] Client connected: C-");
|
||||||
if (offset != string::npos) {
|
if (offset != std::string::npos) {
|
||||||
auto tokens = phosg::split(line, ' ');
|
auto tokens = phosg::split(line, ' ');
|
||||||
|
|
||||||
if (!tokens[8].starts_with("C-")) {
|
if (!tokens[8].starts_with("C-")) {
|
||||||
throw runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
||||||
}
|
}
|
||||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||||
|
|
||||||
auto listen_tokens = phosg::split(tokens[10], '-');
|
auto listen_tokens = phosg::split(tokens[10], '-');
|
||||||
if (listen_tokens.size() < 4) {
|
if (listen_tokens.size() < 4) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
|
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
|
||||||
}
|
}
|
||||||
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
|
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
|
||||||
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
|
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
|
||||||
|
|
||||||
auto c = make_shared<Client>(state->io_context, client_id, port, version);
|
auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
|
||||||
if (!this->clients.emplace(c->id, c).second) {
|
if (!this->clients.emplace(c->id, c).second) {
|
||||||
throw runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
||||||
}
|
}
|
||||||
this->create_event(Event::Type::CONNECT, c, line_num);
|
this->create_event(Event::Type::CONNECT, c, line_num);
|
||||||
num_events++;
|
num_events++;
|
||||||
@@ -441,24 +435,24 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
|
|
||||||
// I <pid/ts> - [GameServer] Running cleanup tasks for C-{}
|
// I <pid/ts> - [GameServer] Running cleanup tasks for C-{}
|
||||||
offset = line.find(" - [GameServer] Running cleanup tasks for C-");
|
offset = line.find(" - [GameServer] Running cleanup tasks for C-");
|
||||||
if (offset != string::npos) {
|
if (offset != std::string::npos) {
|
||||||
auto tokens = phosg::split(line, ' ');
|
auto tokens = phosg::split(line, ' ');
|
||||||
if (tokens.size() < 11) {
|
if (tokens.size() < 11) {
|
||||||
throw runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
||||||
}
|
}
|
||||||
if (!tokens[10].starts_with("C-")) {
|
if (!tokens[10].starts_with("C-")) {
|
||||||
throw runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
||||||
}
|
}
|
||||||
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
|
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
|
||||||
try {
|
try {
|
||||||
auto& c = this->clients.at(client_id);
|
auto& c = this->clients.at(client_id);
|
||||||
if (c->disconnect_event.get()) {
|
if (c->disconnect_event.get()) {
|
||||||
throw runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
||||||
}
|
}
|
||||||
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
|
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
|
||||||
num_events++;
|
num_events++;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -466,13 +460,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
// I <pid/ts> - [Commands] Sending to C-{:X} (...)
|
// I <pid/ts> - [Commands] Sending to C-{:X} (...)
|
||||||
// I <pid/ts> - [Commands] Received from C-{:X} (...)
|
// I <pid/ts> - [Commands] Received from C-{:X} (...)
|
||||||
offset = line.find(" - [Commands] Sending to C-");
|
offset = line.find(" - [Commands] Sending to C-");
|
||||||
if (offset == string::npos) {
|
if (offset == std::string::npos) {
|
||||||
offset = line.find(" - [Commands] Received from C-");
|
offset = line.find(" - [Commands] Received from C-");
|
||||||
}
|
}
|
||||||
if (offset != string::npos) {
|
if (offset != std::string::npos) {
|
||||||
auto tokens = phosg::split(line, ' ');
|
auto tokens = phosg::split(line, ' ');
|
||||||
if (tokens.size() < 10) {
|
if (tokens.size() < 10) {
|
||||||
throw runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
||||||
}
|
}
|
||||||
bool from_client = (tokens[6] == "Received");
|
bool from_client = (tokens[6] == "Received");
|
||||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||||
@@ -480,8 +474,8 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
parsing_command = this->create_event(
|
parsing_command = this->create_event(
|
||||||
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
|
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
|
||||||
num_events++;
|
num_events++;
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -490,14 +484,12 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
|||||||
|
|
||||||
replay_log.debug_f("{} clients in log", this->clients.size());
|
replay_log.debug_f("{} clients in log", this->clients.size());
|
||||||
for (const auto& it : this->clients) {
|
for (const auto& it : this->clients) {
|
||||||
string client_str = it.second->str();
|
replay_log.debug_f(" {} => {}", it.first, it.second->str());
|
||||||
replay_log.debug_f(" {} => {}", it.first, client_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
replay_log.debug_f("{} events in replay log", num_events);
|
replay_log.debug_f("{} events in replay log", num_events);
|
||||||
for (auto ev = this->first_event; ev != nullptr; ev = ev->next_event) {
|
for (auto ev = this->first_event; ev != nullptr; ev = ev->next_event) {
|
||||||
string ev_str = ev->str();
|
replay_log.debug_f(" {}", ev->str());
|
||||||
replay_log.debug_f(" {}", ev_str);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,25 +504,25 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
switch (this->first_event->type) {
|
switch (this->first_event->type) {
|
||||||
case Event::Type::CONNECT: {
|
case Event::Type::CONNECT: {
|
||||||
if (c->channel->connected()) {
|
if (c->channel->connected()) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
|
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const PortConfiguration> port_config;
|
std::shared_ptr<const PortConfiguration> port_config;
|
||||||
try {
|
try {
|
||||||
port_config = this->state->number_to_port_config.at(c->port);
|
port_config = this->state->number_to_port_config.at(c->port);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
|
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto server_channel = make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
auto server_channel = std::make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
||||||
PeerChannel::link_peers(c->channel, server_channel);
|
PeerChannel::link_peers(c->channel, server_channel);
|
||||||
|
|
||||||
if (this->state->game_server.get()) {
|
if (this->state->game_server.get()) {
|
||||||
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
|
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(ev-line {}) no server available for connection", this->first_event->line_num));
|
"(ev-line {}) no server available for connection", this->first_event->line_num));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -542,7 +534,7 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
|
|
||||||
case Event::Type::SEND:
|
case Event::Type::SEND:
|
||||||
if (!c->channel->connected()) {
|
if (!c->channel->connected()) {
|
||||||
throw runtime_error(std::format(
|
throw std::runtime_error(std::format(
|
||||||
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
|
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
|
||||||
}
|
}
|
||||||
c->channel->send(this->first_event->data);
|
c->channel->send(this->first_event->data);
|
||||||
@@ -552,25 +544,25 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
|
|
||||||
case Event::Type::RECEIVE: {
|
case Event::Type::RECEIVE: {
|
||||||
if (!c->channel->connected()) {
|
if (!c->channel->connected()) {
|
||||||
throw runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
||||||
this->first_event->line_num));
|
this->first_event->line_num));
|
||||||
}
|
}
|
||||||
if (c->receive_events.front() != this->first_event) {
|
if (c->receive_events.front() != this->first_event) {
|
||||||
throw logic_error("Client receive events are out of order");
|
throw std::logic_error("Client receive events are out of order");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->reschedule_idle_timeout();
|
this->reschedule_idle_timeout();
|
||||||
auto msg = co_await c->channel->recv();
|
auto msg = co_await c->channel->recv();
|
||||||
|
|
||||||
// TODO: Use the iovec form of phosg::print_data here instead of prepend_command_header (which copies data)
|
// TODO: Use the iovec form of phosg::print_data here instead of prepend_command_header (which copies data)
|
||||||
string full_command = prepend_command_header(
|
std::string full_command = prepend_command_header(
|
||||||
c->version, (c->channel->crypt_in.get() != nullptr), msg.command, msg.flag, msg.data);
|
c->version, (c->channel->crypt_in.get() != nullptr), msg.command, msg.flag, msg.data);
|
||||||
this->commands_received++;
|
this->commands_received++;
|
||||||
this->bytes_received += full_command.size();
|
this->bytes_received += full_command.size();
|
||||||
|
|
||||||
if (c->receive_events.empty()) {
|
if (c->receive_events.empty()) {
|
||||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||||
throw runtime_error("received unexpected command for client");
|
throw std::runtime_error("received unexpected command for client");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& ev = c->receive_events.front();
|
auto& ev = c->receive_events.front();
|
||||||
@@ -579,15 +571,15 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||||
replay_log.error_f("Received command:");
|
replay_log.error_f("Received command:");
|
||||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||||
throw runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
throw std::runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
||||||
}
|
}
|
||||||
for (size_t x = 0; x < min<size_t>(full_command.size(), ev->data.size()); x++) {
|
for (size_t x = 0; x < std::min<size_t>(full_command.size(), ev->data.size()); x++) {
|
||||||
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
|
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
|
||||||
replay_log.error_f("Expected command:");
|
replay_log.error_f("Expected command:");
|
||||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||||
replay_log.error_f("Received command:");
|
replay_log.error_f("Received command:");
|
||||||
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||||
throw runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
throw std::runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,8 +592,8 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
case Version::BB_PATCH:
|
case Version::BB_PATCH:
|
||||||
if (msg.command == 0x02) {
|
if (msg.command == 0x02) {
|
||||||
auto& cmd = msg.check_size_t<S_ServerInit_Patch_02>();
|
auto& cmd = msg.check_size_t<S_ServerInit_Patch_02>();
|
||||||
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||||
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
@@ -618,11 +610,11 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
if (msg.command == 0x02 || msg.command == 0x17 || msg.command == 0x91 || msg.command == 0x9B) {
|
if (msg.command == 0x02 || msg.command == 0x17 || msg.command == 0x91 || msg.command == 0x9B) {
|
||||||
auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
||||||
if (is_v1_or_v2(c->version)) {
|
if (is_v1_or_v2(c->version)) {
|
||||||
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||||
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||||
} else { // V3
|
} else { // V3
|
||||||
c->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
c->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||||
c->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
c->channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -631,19 +623,19 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||||
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
|
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
|
||||||
// first one here.
|
// first one here.
|
||||||
c->channel->crypt_in = make_shared<PSOBBEncryption>(
|
c->channel->crypt_in = std::make_shared<PSOBBEncryption>(
|
||||||
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
|
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
|
||||||
c->channel->crypt_out = make_shared<PSOBBEncryption>(
|
c->channel->crypt_out = std::make_shared<PSOBBEncryption>(
|
||||||
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
|
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unsupported encryption version");
|
throw std::logic_error("unsupported encryption version");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw logic_error("unhandled event type");
|
throw std::logic_error("unhandled event type");
|
||||||
}
|
}
|
||||||
this->first_event->complete = true;
|
this->first_event->complete = true;
|
||||||
}
|
}
|
||||||
@@ -653,7 +645,7 @@ asio::awaitable<void> ReplaySession::run() {
|
|||||||
this->last_event = nullptr;
|
this->last_event = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
replay_log.error_f("Replay failed: {}", e.what());
|
replay_log.error_f("Replay failed: {}", e.what());
|
||||||
if (this->first_event) {
|
if (this->first_event) {
|
||||||
replay_log.error_f("Next pending event: {}", this->first_event->str());
|
replay_log.error_f("Next pending event: {}", this->first_event->str());
|
||||||
|
|||||||
+104
-157
@@ -7,13 +7,11 @@
|
|||||||
#include "LevelTable.hh"
|
#include "LevelTable.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
struct DefaultSymbolChatEntry {
|
struct DefaultSymbolChatEntry {
|
||||||
array<const char*, 8> language_to_name;
|
std::array<const char*, 8> language_to_name;
|
||||||
uint32_t spec;
|
uint32_t spec;
|
||||||
array<uint16_t, 4> corner_objects;
|
std::array<uint16_t, 4> corner_objects;
|
||||||
array<SymbolChatFacePart, 12> face_parts;
|
std::array<SymbolChatFacePart, 12> face_parts;
|
||||||
|
|
||||||
SaveFileSymbolChatEntryBB to_entry(Language language) const {
|
SaveFileSymbolChatEntryBB to_entry(Language language) const {
|
||||||
SaveFileSymbolChatEntryBB ret;
|
SaveFileSymbolChatEntryBB ret;
|
||||||
@@ -30,7 +28,7 @@ struct DefaultSymbolChatEntry {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static const array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS = {
|
static const std::array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS = {
|
||||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||||
@@ -39,11 +37,11 @@ static const array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS = {
|
|||||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const array<uint16_t, 20> DEFAULT_TECH_MENU_CONFIG = {
|
static const std::array<uint16_t, 20> DEFAULT_TECH_MENU_CONFIG = {
|
||||||
0x0000, 0x0006, 0x0003, 0x0001, 0x0007, 0x0004, 0x0002, 0x0008, 0x0005, 0x0009,
|
0x0000, 0x0006, 0x0003, 0x0001, 0x0007, 0x0004, 0x0002, 0x0008, 0x0005, 0x0009,
|
||||||
0x0012, 0x000F, 0x0010, 0x0011, 0x000D, 0x000A, 0x000B, 0x000C, 0x000E, 0x0000};
|
0x0012, 0x000F, 0x0010, 0x0011, 0x000D, 0x000A, 0x000B, 0x000C, 0x000E, 0x0000};
|
||||||
|
|
||||||
static const array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG = {
|
static const std::array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@@ -68,7 +66,7 @@ static const array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG = {
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
static const array<uint8_t, 0x0038> DEFAULT_JOYSTICK_CONFIG = {
|
static const std::array<uint8_t, 0x0038> DEFAULT_JOYSTICK_CONFIG = {
|
||||||
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00,
|
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||||
0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||||
@@ -88,7 +86,7 @@ ShuffleTables::ShuffleTables(PSOV2Encryption& crypt) {
|
|||||||
while (r28 >= 0) {
|
while (r28 >= 0) {
|
||||||
uint32_t r3 = this->pseudorand(crypt, r28 + 1);
|
uint32_t r3 = this->pseudorand(crypt, r28 + 1);
|
||||||
if (r3 >= 0x100) {
|
if (r3 >= 0x100) {
|
||||||
throw logic_error("bad r3");
|
throw std::logic_error("bad r3");
|
||||||
}
|
}
|
||||||
uint8_t t = this->forward_table[r3];
|
uint8_t t = this->forward_table[r3];
|
||||||
this->forward_table[r3] = *r31;
|
this->forward_table[r3] = *r31;
|
||||||
@@ -144,7 +142,7 @@ bool PSOVMSFileHeader::checksum_correct() const {
|
|||||||
|
|
||||||
void PSOVMSFileHeader::check() const {
|
void PSOVMSFileHeader::check() const {
|
||||||
if (!this->checksum_correct()) {
|
if (!this->checksum_correct()) {
|
||||||
throw runtime_error("VMS file unencrypted header checksum is incorrect");
|
throw std::runtime_error("VMS file unencrypted header checksum is incorrect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,19 +163,19 @@ bool PSOGCIFileHeader::checksum_correct() const {
|
|||||||
|
|
||||||
void PSOGCIFileHeader::check() const {
|
void PSOGCIFileHeader::check() const {
|
||||||
if (!this->checksum_correct()) {
|
if (!this->checksum_correct()) {
|
||||||
throw runtime_error("GCI file unencrypted header checksum is incorrect");
|
throw std::runtime_error("GCI file unencrypted header checksum is incorrect");
|
||||||
}
|
}
|
||||||
if (this->developer_id[0] != '8' || this->developer_id[1] != 'P') {
|
if (this->developer_id[0] != '8' || this->developer_id[1] != 'P') {
|
||||||
throw runtime_error("GCI file is not for a Sega game");
|
throw std::runtime_error("GCI file is not for a Sega game");
|
||||||
}
|
}
|
||||||
if ((this->game_id[0] != 'G') && (this->game_id[0] != 'D')) {
|
if ((this->game_id[0] != 'G') && (this->game_id[0] != 'D')) {
|
||||||
throw runtime_error("GCI file is not for a GameCube game");
|
throw std::runtime_error("GCI file is not for a GameCube game");
|
||||||
}
|
}
|
||||||
if (this->game_id[1] != 'P') {
|
if (this->game_id[1] != 'P') {
|
||||||
throw runtime_error("GCI file is not for Phantasy Star Online");
|
throw std::runtime_error("GCI file is not for Phantasy Star Online");
|
||||||
}
|
}
|
||||||
if ((this->game_id[2] != 'S') && (this->game_id[2] != 'O')) {
|
if ((this->game_id[2] != 'S') && (this->game_id[2] != 'O')) {
|
||||||
throw runtime_error("GCI file is not for Phantasy Star Online");
|
throw std::runtime_error("GCI file is not for Phantasy Star Online");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,10 +213,10 @@ phosg::ImageRGB888 PSOGCSnapshotFile::decode_image() const {
|
|||||||
size_t width = this->width ? this->width.load() : 256;
|
size_t width = this->width ? this->width.load() : 256;
|
||||||
size_t height = this->height ? this->height.load() : 192;
|
size_t height = this->height ? this->height.load() : 192;
|
||||||
if (width != 256) {
|
if (width != 256) {
|
||||||
throw runtime_error("width is incorrect");
|
throw std::runtime_error("width is incorrect");
|
||||||
}
|
}
|
||||||
if (height != 192) {
|
if (height != 192) {
|
||||||
throw runtime_error("height is incorrect");
|
throw std::runtime_error("height is incorrect");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4x4 blocks of pixels
|
// 4x4 blocks of pixels
|
||||||
@@ -307,7 +305,7 @@ bool PSOXBFileHeader::checksum_correct() const {
|
|||||||
|
|
||||||
void PSOXBFileHeader::check() const {
|
void PSOXBFileHeader::check() const {
|
||||||
if (!this->checksum_correct()) {
|
if (!this->checksum_correct()) {
|
||||||
throw runtime_error("Xbox file intermediate header checksum is incorrect");
|
throw std::runtime_error("Xbox file intermediate header checksum is incorrect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +320,7 @@ uint32_t PSOBBGuildCardFile::checksum() const {
|
|||||||
|
|
||||||
void PSOBBGuildCardFile::delete_duplicates() {
|
void PSOBBGuildCardFile::delete_duplicates() {
|
||||||
{
|
{
|
||||||
unordered_set<uint32_t> seen;
|
std::unordered_set<uint32_t> seen;
|
||||||
size_t read_index = 0, write_index = 0;
|
size_t read_index = 0, write_index = 0;
|
||||||
for (read_index = 0; read_index < this->blocked_senders.size(); read_index++) {
|
for (read_index = 0; read_index < this->blocked_senders.size(); read_index++) {
|
||||||
const auto& read_blocked_senders = this->blocked_senders[read_index];
|
const auto& read_blocked_senders = this->blocked_senders[read_index];
|
||||||
@@ -339,7 +337,7 @@ void PSOBBGuildCardFile::delete_duplicates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
unordered_set<uint32_t> seen;
|
std::unordered_set<uint32_t> seen;
|
||||||
size_t read_index = 0, write_index = 0;
|
size_t read_index = 0, write_index = 0;
|
||||||
for (read_index = 0; read_index < this->entries.size(); read_index++) {
|
for (read_index = 0; read_index < this->entries.size(); read_index++) {
|
||||||
const auto& read_entry = this->entries[read_index];
|
const auto& read_entry = this->entries[read_index];
|
||||||
@@ -367,23 +365,21 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const {
|
PlayerDispDataV4Preview PSOBBCharacterFile::to_preview() const {
|
||||||
PlayerDispDataBBPreview pre;
|
PlayerDispDataV4Preview pre;
|
||||||
pre.level = this->disp.stats.level;
|
pre.level = this->disp.stats.level;
|
||||||
pre.exp = this->disp.stats.exp;
|
pre.exp = this->disp.stats.exp;
|
||||||
pre.visual = this->disp.visual;
|
pre.visual = this->disp.visual;
|
||||||
pre.name = this->disp.name;
|
|
||||||
pre.play_time_seconds = this->play_time_seconds;
|
pre.play_time_seconds = this->play_time_seconds;
|
||||||
return pre;
|
return pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||||
uint32_t guild_card_number,
|
uint32_t guild_card_number,
|
||||||
Language language,
|
Language language,
|
||||||
const PlayerVisualConfig& visual,
|
const PlayerVisualConfigV4& visual,
|
||||||
const std::string& name,
|
std::shared_ptr<const LevelTable> level_table) {
|
||||||
shared_ptr<const LevelTable> level_table) {
|
static const std::array<std::array<PlayerInventoryItem, 5>, 12> initial_inventory{{
|
||||||
static const array<array<PlayerInventoryItem, 5>, 12> initial_inventory{{
|
|
||||||
{
|
{
|
||||||
PlayerInventoryItem(ItemData(0x0001000000000000, 0x0000000000000000), true),
|
PlayerInventoryItem(ItemData(0x0001000000000000, 0x0000000000000000), true),
|
||||||
PlayerInventoryItem(ItemData(0x0101000000000000, 0x0000000000000000), true),
|
PlayerInventoryItem(ItemData(0x0101000000000000, 0x0000000000000000), true),
|
||||||
@@ -470,7 +466,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
|||||||
},
|
},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
static const array<uint8_t, 0xE8> config_hunter_ranger{
|
static const std::array<uint8_t, 0xE8> config_hunter_ranger{
|
||||||
{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
|
{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
|
||||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@@ -486,7 +482,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
static const array<uint8_t, 0xE8> config_force{
|
static const std::array<uint8_t, 0xE8> config_force{
|
||||||
{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
|
{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
|
||||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@@ -503,18 +499,17 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
|
||||||
auto ret = make_shared<PSOBBCharacterFile>();
|
auto ret = std::make_shared<PSOBBCharacterFile>();
|
||||||
ret->disp.visual = visual;
|
ret->disp.visual = visual;
|
||||||
ret->disp.name.encode(name, language);
|
|
||||||
|
|
||||||
const auto& initial_items = initial_inventory.at(visual.char_class);
|
const auto& initial_items = initial_inventory.at(visual.sh.char_class);
|
||||||
ret->inventory.num_items = initial_items.size();
|
ret->inventory.num_items = initial_items.size();
|
||||||
for (size_t z = 0; z < initial_items.size(); z++) {
|
for (size_t z = 0; z < initial_items.size(); z++) {
|
||||||
ret->inventory.items[z] = initial_items[z];
|
ret->inventory.items[z] = initial_items[z];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set mag color based on initial costume
|
// Set mag color based on initial costume
|
||||||
static const array<array<uint8_t, 25>, 12> mag_colors = {{
|
static const std::array<std::array<uint8_t, 25>, 12> mag_colors = {{
|
||||||
{0x09, 0x01, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x0B, 0x05, 0x00, 0x07, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
{0x09, 0x01, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x0B, 0x05, 0x00, 0x07, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
{0x00, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x08, 0x11, 0x0D, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
{0x00, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x08, 0x11, 0x0D, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
{0x00, 0x01, 0x02, 0x11, 0x04, 0x0E, 0x06, 0x01, 0x0E, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x04, 0x11, 0x0D, 0x01, 0x0B, 0x11, 0x0D, 0x05, 0x06},
|
{0x00, 0x01, 0x02, 0x11, 0x04, 0x0E, 0x06, 0x01, 0x0E, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x04, 0x11, 0x0D, 0x01, 0x0B, 0x11, 0x0D, 0x05, 0x06},
|
||||||
@@ -528,36 +523,36 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
|||||||
{0x00, 0x01, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0D, 0x07, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
{0x00, 0x01, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x08, 0x0A, 0x0D, 0x07, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
{0x00, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x09, 0x0C, 0x00, 0x01, 0x02, 0x11, 0x0D, 0x05, 0x10, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
{0x00, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x09, 0x0C, 0x00, 0x01, 0x02, 0x11, 0x0D, 0x05, 0x10, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||||
}};
|
}};
|
||||||
uint8_t char_class = (visual.char_class > 0x0B) ? 0 : visual.char_class;
|
uint8_t char_class = (visual.sh.char_class > 0x0B) ? 0 : visual.sh.char_class;
|
||||||
uint8_t mag_color_index;
|
uint8_t mag_color_index;
|
||||||
if (char_class == 2 || char_class == 4 || char_class == 5 || char_class == 9) {
|
if (char_class == 2 || char_class == 4 || char_class == 5 || char_class == 9) {
|
||||||
mag_color_index = (visual.skin >= 25) ? 0 : visual.skin.load();
|
mag_color_index = (visual.sh.skin >= 25) ? 0 : visual.sh.skin.load();
|
||||||
} else {
|
} else {
|
||||||
mag_color_index = (visual.costume >= 18) ? 0 : visual.costume.load();
|
mag_color_index = (visual.sh.costume >= 18) ? 0 : visual.sh.costume.load();
|
||||||
}
|
}
|
||||||
ret->inventory.items[2].data.data2[3] = mag_colors.at(char_class).at(mag_color_index);
|
ret->inventory.items[2].data.data2[3] = mag_colors.at(char_class).at(mag_color_index);
|
||||||
|
|
||||||
ret->inventory.items[13].extension_data2 = 1;
|
ret->inventory.items[13].extension_data2 = 1;
|
||||||
|
|
||||||
const auto& config = (ret->disp.visual.class_flags & 0x80) ? config_force : config_hunter_ranger;
|
const auto& config = (ret->disp.visual.sh.class_flags & 0x80) ? config_force : config_hunter_ranger;
|
||||||
for (size_t z = 0; z < config.size(); z++) {
|
for (size_t z = 0; z < config.size(); z++) {
|
||||||
ret->disp.config[z] = config[z];
|
ret->disp.config[z] = config[z];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (level_table) {
|
if (level_table) {
|
||||||
level_table->reset_to_base(ret->disp.stats, ret->disp.visual.char_class);
|
level_table->reset_to_base(ret->disp.stats, ret->disp.visual.sh.char_class);
|
||||||
}
|
}
|
||||||
ret->disp.technique_levels_v1.clear(0xFF);
|
ret->disp.technique_levels_v1.clear(0xFF);
|
||||||
if (ret->disp.visual.class_flags & 0x80) {
|
if (ret->disp.visual.sh.class_flags & 0x80) {
|
||||||
ret->disp.technique_levels_v1[0] = 0x00; // Forces start with Foie Lv.1
|
ret->disp.technique_levels_v1[0] = 0x00; // Forces start with Foie Lv.1
|
||||||
}
|
}
|
||||||
ret->inventory.language = language;
|
ret->inventory.language = language;
|
||||||
ret->guild_card.guild_card_number = guild_card_number;
|
ret->guild_card.guild_card_number = guild_card_number;
|
||||||
ret->guild_card.name = ret->disp.name;
|
ret->guild_card.name = ret->disp.visual.name;
|
||||||
ret->guild_card.present = 1;
|
ret->guild_card.present = 1;
|
||||||
ret->guild_card.language = ret->inventory.language;
|
ret->guild_card.language = ret->inventory.language;
|
||||||
ret->guild_card.section_id = ret->disp.visual.section_id;
|
ret->guild_card.section_id = ret->disp.visual.sh.section_id;
|
||||||
ret->guild_card.char_class = ret->disp.visual.char_class;
|
ret->guild_card.char_class = ret->disp.visual.sh.char_class;
|
||||||
for (size_t z = 0; z < DEFAULT_SYMBOL_CHATS.size(); z++) {
|
for (size_t z = 0; z < DEFAULT_SYMBOL_CHATS.size(); z++) {
|
||||||
ret->symbol_chats[z] = DEFAULT_SYMBOL_CHATS[z].to_entry(language);
|
ret->symbol_chats[z] = DEFAULT_SYMBOL_CHATS[z].to_entry(language);
|
||||||
}
|
}
|
||||||
@@ -567,26 +562,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) {
|
||||||
uint32_t guild_card_number,
|
|
||||||
Language language,
|
|
||||||
const PlayerDispDataBBPreview& preview,
|
|
||||||
shared_ptr<const LevelTable> level_table) {
|
|
||||||
return PSOBBCharacterFile::create_from_config(
|
|
||||||
guild_card_number, language, preview.visual, preview.name.decode(language), level_table);
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) {
|
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
auto ret = PSOBBCharacterFile::create_from_config(
|
||||||
src.guild_card.guild_card_number,
|
src.guild_card.guild_card_number, Language::JAPANESE, src.disp.visual);
|
||||||
Language::JAPANESE,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
ret->inventory.decode_from_client(Version::DC_V1);
|
ret->inventory.decode_from_client(Version::DC_V1);
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(Language::JAPANESE, Language::JAPANESE);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = 0;
|
ret->validation_flags = 0;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -609,17 +590,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCN
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
ret->inventory.decode_from_client(Version::DC_V1);
|
ret->inventory.decode_from_client(Version::DC_V1);
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = 0;
|
ret->validation_flags = 0;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -652,17 +628,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC1
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
ret->inventory.decode_from_client(Version::DC_V1);
|
ret->inventory.decode_from_client(Version::DC_V1);
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = src.validation_flags;
|
ret->validation_flags = src.validation_flags;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -685,17 +656,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
ret->inventory.decode_from_client(Version::DC_V2);
|
ret->inventory.decode_from_client(Version::DC_V2);
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = src.validation_flags;
|
ret->validation_flags = src.validation_flags;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -725,19 +691,14 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
// Note: We intentionally do not call ret->inventory.decode_from_client here. This is because the GC client byteswaps
|
// Note: We intentionally do not call ret->inventory.decode_from_client here. This is because the GC client byteswaps
|
||||||
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
||||||
// do this, so the data2 fields are already in the correct order here.
|
// do this, so the data2 fields are already in the correct order here.
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = src.validation_flags;
|
ret->validation_flags = src.validation_flags;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -765,19 +726,14 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCN
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
// Note: We intentionally do not call ret->inventory.decode_from_client here. This is because the GC client byteswaps
|
// Note: We intentionally do not call ret->inventory.decode_from_client here. This is because the GC client byteswaps
|
||||||
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
||||||
// do this, so the data2 fields are already in the correct order here.
|
// do this, so the data2 fields are already in the correct order here.
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = src.validation_flags;
|
ret->validation_flags = src.validation_flags;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -815,16 +771,11 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCC
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = src.validation_flags;
|
ret->validation_flags = src.validation_flags;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -863,17 +814,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCE
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
|
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
|
||||||
auto ret = PSOBBCharacterFile::create_from_config(
|
Language language = src.inventory.language;
|
||||||
src.guild_card.guild_card_number,
|
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
|
||||||
src.inventory.language,
|
|
||||||
src.disp.visual,
|
|
||||||
src.disp.visual.name.decode(Language::JAPANESE),
|
|
||||||
nullptr);
|
|
||||||
ret->inventory = src.inventory;
|
ret->inventory = src.inventory;
|
||||||
ret->inventory.decode_from_client(Version::XB_V3);
|
ret->inventory.decode_from_client(Version::XB_V3);
|
||||||
Language language = ret->inventory.language;
|
ret->disp = src.disp.to_v4(language, language);
|
||||||
ret->disp = src.disp.to_bb(language, language);
|
|
||||||
ret->validation_flags = src.validation_flags;
|
ret->validation_flags = src.validation_flags;
|
||||||
ret->creation_timestamp = src.creation_timestamp;
|
ret->creation_timestamp = src.creation_timestamp;
|
||||||
ret->play_time_seconds = src.play_time_seconds;
|
ret->play_time_seconds = src.play_time_seconds;
|
||||||
@@ -920,7 +866,7 @@ PSODCNTECharacterFile::Character PSOBBCharacterFile::as_dc_nte(uint64_t hardware
|
|||||||
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
||||||
// stats in the v2 format
|
// stats in the v2 format
|
||||||
ret.inventory.encode_for_client(Version::DC_NTE, nullptr);
|
ret.inventory.encode_for_client(Version::DC_NTE, nullptr);
|
||||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
ret.disp = this->disp.to_v123<false>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
||||||
ret.masked_creation_timestamp = this->creation_timestamp ^ static_cast<uint32_t>(hardware_id >> 16);
|
ret.masked_creation_timestamp = this->creation_timestamp ^ static_cast<uint32_t>(hardware_id >> 16);
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -946,7 +892,7 @@ PSODC112000CharacterFile::Character PSOBBCharacterFile::as_11_2000(uint64_t hard
|
|||||||
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
||||||
// stats in the v2 format
|
// stats in the v2 format
|
||||||
ret.inventory.encode_for_client(Version::DC_11_2000, nullptr);
|
ret.inventory.encode_for_client(Version::DC_11_2000, nullptr);
|
||||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
ret.disp = this->disp.to_v123<false>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
||||||
ret.masked_creation_timestamp = this->creation_timestamp ^ static_cast<uint32_t>(hardware_id >> 16);
|
ret.masked_creation_timestamp = this->creation_timestamp ^ static_cast<uint32_t>(hardware_id >> 16);
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -983,7 +929,7 @@ PSOBBCharacterFile::operator PSODCV1CharacterFile::Character() const {
|
|||||||
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
||||||
// stats in the v2 format
|
// stats in the v2 format
|
||||||
ret.inventory.encode_for_client(Version::DC_V1, nullptr);
|
ret.inventory.encode_for_client(Version::DC_V1, nullptr);
|
||||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
ret.disp = this->disp.to_v123<false>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
||||||
ret.validation_flags = this->validation_flags;
|
ret.validation_flags = this->validation_flags;
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -1015,7 +961,7 @@ PSOBBCharacterFile::operator PSODCV2CharacterFile::Character() const {
|
|||||||
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
// We don't need to do the v1-compatible encoding (hence it is OK to pass nullptr here) but we do need to encode mag
|
||||||
// stats in the v2 format
|
// stats in the v2 format
|
||||||
ret.inventory.encode_for_client(Version::DC_V2, nullptr);
|
ret.inventory.encode_for_client(Version::DC_V2, nullptr);
|
||||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
ret.disp = this->disp.to_v123<false>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2);
|
||||||
ret.validation_flags = this->validation_flags;
|
ret.validation_flags = this->validation_flags;
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -1056,7 +1002,7 @@ PSOBBCharacterFile::operator PSOGCNTECharacterFileCharacter() const {
|
|||||||
// Note: We intentionally do not call ret.inventory.encode_for_client here. This is because the GC client byteswaps
|
// Note: We intentionally do not call ret.inventory.encode_for_client here. This is because the GC client byteswaps
|
||||||
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
||||||
// do this, so the data2 fields are already in the correct order here.
|
// do this, so the data2 fields are already in the correct order here.
|
||||||
ret.disp = this->disp.to_dcpcv3<true>(language, language);
|
ret.disp = this->disp.to_v123<true>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::GC_V3);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::GC_V3);
|
||||||
ret.validation_flags = this->validation_flags;
|
ret.validation_flags = this->validation_flags;
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -1093,7 +1039,7 @@ PSOBBCharacterFile::operator PSOGCCharacterFile::Character() const {
|
|||||||
// Note: We intentionally do not call ret.inventory.encode_for_client here. This is because the GC client byteswaps
|
// Note: We intentionally do not call ret.inventory.encode_for_client here. This is because the GC client byteswaps
|
||||||
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
// data2 in each item before sending it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does not
|
||||||
// do this, so the data2 fields are already in the correct order here.
|
// do this, so the data2 fields are already in the correct order here.
|
||||||
ret.disp = this->disp.to_dcpcv3<true>(language, language);
|
ret.disp = this->disp.to_v123<true>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::GC_V3);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::GC_V3);
|
||||||
ret.validation_flags = this->validation_flags;
|
ret.validation_flags = this->validation_flags;
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -1138,7 +1084,7 @@ PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const {
|
|||||||
PSOXBCharacterFile::Character ret;
|
PSOXBCharacterFile::Character ret;
|
||||||
ret.inventory = this->inventory;
|
ret.inventory = this->inventory;
|
||||||
ret.inventory.encode_for_client(Version::XB_V3, nullptr);
|
ret.inventory.encode_for_client(Version::XB_V3, nullptr);
|
||||||
ret.disp = this->disp.to_dcpcv3<false>(language, language);
|
ret.disp = this->disp.to_v123<false>(language, language);
|
||||||
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::XB_V3);
|
ret.disp.visual.enforce_lobby_join_limits_for_version(Version::XB_V3);
|
||||||
ret.validation_flags = this->validation_flags;
|
ret.validation_flags = this->validation_flags;
|
||||||
ret.creation_timestamp = this->creation_timestamp;
|
ret.creation_timestamp = this->creation_timestamp;
|
||||||
@@ -1178,24 +1124,24 @@ PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PSOCHARFile::LoadSharedResult PSOCHARFile::load_shared(const string& filename, bool load_system) {
|
PSOCHARFile::LoadSharedResult PSOCHARFile::load_shared(const std::string& filename, bool load_system) {
|
||||||
auto f = phosg::fopen_unique(filename, "rb");
|
auto f = phosg::fopen_unique(filename, "rb");
|
||||||
auto header = phosg::freadx<PSOCommandHeaderBB>(f.get());
|
auto header = phosg::freadx<PSOCommandHeaderBB>(f.get());
|
||||||
if (header.size != 0x399C) {
|
if (header.size != 0x399C) {
|
||||||
throw runtime_error("incorrect size in character file header");
|
throw std::runtime_error("incorrect size in character file header");
|
||||||
}
|
}
|
||||||
if (header.command != 0x00E7) {
|
if (header.command != 0x00E7) {
|
||||||
throw runtime_error("incorrect command in character file header");
|
throw std::runtime_error("incorrect command in character file header");
|
||||||
}
|
}
|
||||||
if (header.flag != 0x00000000) {
|
if (header.flag != 0x00000000) {
|
||||||
throw runtime_error("incorrect flag in character file header");
|
throw std::runtime_error("incorrect flag in character file header");
|
||||||
}
|
}
|
||||||
static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBFullTeamMembership) == 0x3994, ".psochar size is incorrect");
|
static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBFullTeamMembership) == 0x3994, ".psochar size is incorrect");
|
||||||
|
|
||||||
LoadSharedResult ret;
|
LoadSharedResult ret;
|
||||||
ret.character_file = make_shared<PSOBBCharacterFile>(phosg::freadx<PSOBBCharacterFile>(f.get()));
|
ret.character_file = std::make_shared<PSOBBCharacterFile>(phosg::freadx<PSOBBCharacterFile>(f.get()));
|
||||||
if (load_system) {
|
if (load_system) {
|
||||||
ret.system_file = make_shared<PSOBBBaseSystemFile>(phosg::freadx<PSOBBBaseSystemFile>(f.get()));
|
ret.system_file = std::make_shared<PSOBBBaseSystemFile>(phosg::freadx<PSOBBBaseSystemFile>(f.get()));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -1245,7 +1191,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLim
|
|||||||
if (y < this->inventory.num_items) {
|
if (y < this->inventory.num_items) {
|
||||||
size_t new_stack_size = this->inventory.items[y].data.data1[5] + item.data1[5];
|
size_t new_stack_size = this->inventory.items[y].data.data1[5] + item.data1[5];
|
||||||
if (new_stack_size > combine_max) {
|
if (new_stack_size > combine_max) {
|
||||||
throw out_of_range("stack is too large");
|
throw std::out_of_range("stack is too large");
|
||||||
}
|
}
|
||||||
this->inventory.items[y].data.data1[5] = new_stack_size;
|
this->inventory.items[y].data.data1[5] = new_stack_size;
|
||||||
return;
|
return;
|
||||||
@@ -1254,7 +1200,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLim
|
|||||||
|
|
||||||
// If we get here, then it's not meseta and not a combine item, so it needs to go into an empty inventory slot
|
// If we get here, then it's not meseta and not a combine item, so it needs to go into an empty inventory slot
|
||||||
if (this->inventory.num_items >= 30) {
|
if (this->inventory.num_items >= 30) {
|
||||||
throw out_of_range("inventory is full");
|
throw std::out_of_range("inventory is full");
|
||||||
}
|
}
|
||||||
auto& inv_item = this->inventory.items[this->inventory.num_items];
|
auto& inv_item = this->inventory.items[this->inventory.num_items];
|
||||||
inv_item.state = 1;
|
inv_item.state = 1;
|
||||||
@@ -1285,7 +1231,7 @@ ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, cons
|
|||||||
// if amount is nonzero.
|
// if amount is nonzero.
|
||||||
if (amount && (inventory_item.data.stack_size(limits) > 1) && (amount < inventory_item.data.data1[5])) {
|
if (amount && (inventory_item.data.stack_size(limits) > 1) && (amount < inventory_item.data.data1[5])) {
|
||||||
if (is_equipped) {
|
if (is_equipped) {
|
||||||
throw runtime_error("character has a combine item equipped");
|
throw std::runtime_error("character has a combine item equipped");
|
||||||
}
|
}
|
||||||
ret = inventory_item.data;
|
ret = inventory_item.data;
|
||||||
ret.data1[5] = amount;
|
ret.data1[5] = amount;
|
||||||
@@ -1318,7 +1264,7 @@ ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PSOBBCharacterFile::add_meseta(uint32_t amount) {
|
void PSOBBCharacterFile::add_meseta(uint32_t amount) {
|
||||||
this->disp.stats.meseta = min<size_t>(static_cast<size_t>(this->disp.stats.meseta) + amount, 999999);
|
this->disp.stats.meseta = std::min<size_t>(static_cast<size_t>(this->disp.stats.meseta) + amount, 999999);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
|
void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
|
||||||
@@ -1327,7 +1273,7 @@ void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
|
|||||||
} else if (allow_overdraft) {
|
} else if (allow_overdraft) {
|
||||||
this->disp.stats.meseta = 0;
|
this->disp.stats.meseta = 0;
|
||||||
} else {
|
} else {
|
||||||
throw out_of_range("player does not have enough meseta");
|
throw std::out_of_range("player does not have enough meseta");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1363,7 +1309,7 @@ uint8_t PSOBBCharacterFile::get_material_usage(MaterialType which) const {
|
|||||||
case MaterialType::LUCK:
|
case MaterialType::LUCK:
|
||||||
return this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2;
|
return this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid material type");
|
throw std::logic_error("invalid material type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1383,7 +1329,7 @@ void PSOBBCharacterFile::set_material_usage(MaterialType which, uint8_t usage) {
|
|||||||
this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2 = usage;
|
this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2 = usage;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("invalid material type");
|
throw std::logic_error("invalid material type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1408,8 +1354,8 @@ void PSOBBCharacterFile::import_tethealla_material_usage(std::shared_ptr<const L
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlayerStats level_base_stats = this->disp.stats;
|
PlayerStats level_base_stats = this->disp.stats;
|
||||||
level_table->reset_to_base(level_base_stats, this->disp.visual.char_class);
|
level_table->reset_to_base(level_base_stats, this->disp.visual.sh.char_class);
|
||||||
level_table->advance_to_level(level_base_stats, this->disp.stats.level, this->disp.visual.char_class);
|
level_table->advance_to_level(level_base_stats, this->disp.stats.level, this->disp.visual.sh.char_class);
|
||||||
|
|
||||||
uint64_t pow = (this->disp.stats.char_stats.atp - level_base_stats.char_stats.atp) / 2;
|
uint64_t pow = (this->disp.stats.char_stats.atp - level_base_stats.char_stats.atp) / 2;
|
||||||
uint64_t mind = (this->disp.stats.char_stats.mst - level_base_stats.char_stats.mst) / 2;
|
uint64_t mind = (this->disp.stats.char_stats.mst - level_base_stats.char_stats.mst) / 2;
|
||||||
@@ -1430,8 +1376,8 @@ void PSOBBCharacterFile::import_tethealla_material_usage(std::shared_ptr<const L
|
|||||||
void PSOBBCharacterFile::recompute_stats(std::shared_ptr<const LevelTable> level_table, bool reset_exp) {
|
void PSOBBCharacterFile::recompute_stats(std::shared_ptr<const LevelTable> level_table, bool reset_exp) {
|
||||||
uint32_t level = this->disp.stats.level;
|
uint32_t level = this->disp.stats.level;
|
||||||
uint32_t exp = this->disp.stats.exp;
|
uint32_t exp = this->disp.stats.exp;
|
||||||
level_table->reset_to_base(this->disp.stats, this->disp.visual.char_class);
|
level_table->reset_to_base(this->disp.stats, this->disp.visual.sh.char_class);
|
||||||
level_table->advance_to_level(this->disp.stats, level, this->disp.visual.char_class);
|
level_table->advance_to_level(this->disp.stats, level, this->disp.visual.sh.char_class);
|
||||||
if (!reset_exp) {
|
if (!reset_exp) {
|
||||||
this->disp.stats.exp = exp;
|
this->disp.stats.exp = exp;
|
||||||
}
|
}
|
||||||
@@ -1490,29 +1436,30 @@ static uint16_t crc16(const void* data, size_t size) {
|
|||||||
return ret ^ 0xFFFF;
|
return ret ^ 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
string encode_psobb_hangame_credentials(const string& user_id, const string& token, const string& unused) {
|
std::string encode_psobb_hangame_credentials(
|
||||||
|
const std::string& user_id, const std::string& token, const std::string& unused) {
|
||||||
if (user_id.size() < 4) {
|
if (user_id.size() < 4) {
|
||||||
throw runtime_error("user_id must be at least 4 characters");
|
throw std::runtime_error("user_id must be at least 4 characters");
|
||||||
}
|
}
|
||||||
if (user_id.size() > 12) {
|
if (user_id.size() > 12) {
|
||||||
throw runtime_error("user_id must be at most 12 characters");
|
throw std::runtime_error("user_id must be at most 12 characters");
|
||||||
}
|
}
|
||||||
if (!user_id.ends_with("@HG")) {
|
if (!user_id.ends_with("@HG")) {
|
||||||
throw runtime_error("user_id must end with \"@HG\"");
|
throw std::runtime_error("user_id must end with \"@HG\"");
|
||||||
}
|
}
|
||||||
if (token.empty()) {
|
if (token.empty()) {
|
||||||
throw runtime_error("token must not be empty");
|
throw std::runtime_error("token must not be empty");
|
||||||
}
|
}
|
||||||
if (token.size() > 8) {
|
if (token.size() > 8) {
|
||||||
throw runtime_error("token must be at most 8 characters");
|
throw std::runtime_error("token must be at most 8 characters");
|
||||||
}
|
}
|
||||||
for (char ch : token) {
|
for (char ch : token) {
|
||||||
if (!isdigit(ch)) {
|
if (!isdigit(ch)) {
|
||||||
throw runtime_error("token must contain only decimal digits");
|
throw std::runtime_error("token must contain only decimal digits");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (unused.size() > 0xFF) {
|
if (unused.size() > 0xFF) {
|
||||||
throw runtime_error("unused must be at most 255 characters");
|
throw std::runtime_error("unused must be at most 255 characters");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The encoded format is:
|
// The encoded format is:
|
||||||
@@ -1526,7 +1473,7 @@ string encode_psobb_hangame_credentials(const string& user_id, const string& tok
|
|||||||
// uint8_t unused_size;
|
// uint8_t unused_size;
|
||||||
// char unused[unused_size]; // Ignored (possibly email address?)
|
// char unused[unused_size]; // Ignored (possibly email address?)
|
||||||
// We'll fill in mask_key and checksum after all the other fields.
|
// We'll fill in mask_key and checksum after all the other fields.
|
||||||
string data(7, '\0'); // mask_key, checksum, unused
|
std::string data(7, '\0'); // mask_key, checksum, unused
|
||||||
data.push_back(user_id.size());
|
data.push_back(user_id.size());
|
||||||
data += user_id;
|
data += user_id;
|
||||||
data.push_back(token.size());
|
data.push_back(token.size());
|
||||||
|
|||||||
+38
-33
@@ -334,8 +334,8 @@ struct PSOBBMinimalSystemFile {
|
|||||||
} __packed_ws__(PSOBBMinimalSystemFile, 0x114);
|
} __packed_ws__(PSOBBMinimalSystemFile, 0x114);
|
||||||
|
|
||||||
struct PSOBBBaseSystemFile : PSOBBMinimalSystemFile {
|
struct PSOBBBaseSystemFile : PSOBBMinimalSystemFile {
|
||||||
/* 0114 */ parray<uint8_t, 0x016C> key_config;
|
/* 0114 */ parray<uint8_t, 0x16C> key_config;
|
||||||
/* 0280 */ parray<uint8_t, 0x0038> joystick_config;
|
/* 0280 */ parray<uint8_t, 0x38> joystick_config;
|
||||||
/* 02B8 */
|
/* 02B8 */
|
||||||
|
|
||||||
PSOBBBaseSystemFile();
|
PSOBBBaseSystemFile();
|
||||||
@@ -349,7 +349,7 @@ struct PSODCNTECharacterFile {
|
|||||||
struct Character {
|
struct Character {
|
||||||
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
||||||
/* 0000:---- */ PlayerInventory inventory;
|
/* 0000:---- */ PlayerInventory inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
|
/* 034C:---- */ PlayerDispDataV123 disp;
|
||||||
// masked_creation_timestamp is expected to contain the value (creation_timestamp ^ hardware_id_mid), where
|
// masked_creation_timestamp is expected to contain the value (creation_timestamp ^ hardware_id_mid), where
|
||||||
// hardware_id_mid contains the middle 32 bits of the 64-bit hardware ID returned by the SYSINFO_ID syscall (the
|
// hardware_id_mid contains the middle 32 bits of the 64-bit hardware ID returned by the SYSINFO_ID syscall (the
|
||||||
// top and bottom 16 bits are ignored for this purpose).
|
// top and bottom 16 bits are ignored for this purpose).
|
||||||
@@ -385,7 +385,7 @@ struct PSODC112000CharacterFile {
|
|||||||
struct Character {
|
struct Character {
|
||||||
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
||||||
/* 0000:---- */ PlayerInventory inventory;
|
/* 0000:---- */ PlayerInventory inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
|
/* 034C:---- */ PlayerDispDataV123 disp;
|
||||||
/* 041C:0000 */ le_uint32_t masked_creation_timestamp = 0;
|
/* 041C:0000 */ le_uint32_t masked_creation_timestamp = 0;
|
||||||
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
|
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
|
||||||
@@ -417,7 +417,7 @@ struct PSODCV1CharacterFile {
|
|||||||
struct Character {
|
struct Character {
|
||||||
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
||||||
/* 0000:---- */ PlayerInventory inventory;
|
/* 0000:---- */ PlayerInventory inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
|
/* 034C:---- */ PlayerDispDataV123 disp;
|
||||||
/* 041C:0000 */ le_uint32_t validation_flags = 0;
|
/* 041C:0000 */ le_uint32_t validation_flags = 0;
|
||||||
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
|
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
|
||||||
@@ -445,7 +445,7 @@ struct PSODCV2CharacterFile {
|
|||||||
struct Character {
|
struct Character {
|
||||||
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
|
||||||
/* 0000:---- */ PlayerInventory inventory;
|
/* 0000:---- */ PlayerInventory inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
|
/* 034C:---- */ PlayerDispDataV123 disp;
|
||||||
/* 041C:0000 */ le_uint32_t validation_flags = 0;
|
/* 041C:0000 */ le_uint32_t validation_flags = 0;
|
||||||
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
|
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
|
||||||
@@ -491,7 +491,7 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
|
|||||||
/* 0000 */ le_uint32_t checksum = 0;
|
/* 0000 */ le_uint32_t checksum = 0;
|
||||||
struct Character {
|
struct Character {
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataDCPCV3 disp;
|
/* 034C */ PlayerDispDataV123 disp;
|
||||||
/* 041C */ be_uint32_t validation_flags = 0;
|
/* 041C */ be_uint32_t validation_flags = 0;
|
||||||
/* 0420 */ be_uint32_t creation_timestamp = 0;
|
/* 0420 */ be_uint32_t creation_timestamp = 0;
|
||||||
/* 0424 */ be_uint32_t signature = 0x6C5D889E;
|
/* 0424 */ be_uint32_t signature = 0x6C5D889E;
|
||||||
@@ -528,7 +528,7 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
|
|||||||
|
|
||||||
struct PSOGCNTECharacterFileCharacter {
|
struct PSOGCNTECharacterFileCharacter {
|
||||||
/* 0000:---- */ PlayerInventoryBE inventory;
|
/* 0000:---- */ PlayerInventoryBE inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
|
/* 034C:---- */ PlayerDispDataV123BE disp;
|
||||||
/* 041C:0000 */ be_uint32_t validation_flags = 0;
|
/* 041C:0000 */ be_uint32_t validation_flags = 0;
|
||||||
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
|
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
|
||||||
@@ -558,7 +558,7 @@ struct PSOGCCharacterFile {
|
|||||||
// This structure is internally split into two by the game. The offsets here are relative to the start of this
|
// This structure is internally split into two by the game. The offsets here are relative to the start of this
|
||||||
// structure (first column), and relative to the start of the second internal structure (second column).
|
// structure (first column), and relative to the start of the second internal structure (second column).
|
||||||
/* 0000:---- */ PlayerInventoryBE inventory;
|
/* 0000:---- */ PlayerInventoryBE inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
|
/* 034C:---- */ PlayerDispDataV123BE disp;
|
||||||
// Known bits in the validation_flags field:
|
// Known bits in the validation_flags field:
|
||||||
// 00000001: Character was not saved after disconnecting (and the message about items being deleted is shown in
|
// 00000001: Character was not saved after disconnecting (and the message about items being deleted is shown in
|
||||||
// the select menu)
|
// the select menu)
|
||||||
@@ -638,7 +638,7 @@ struct PSOGCEp3NTECharacter {
|
|||||||
// This structure is internally split into two by the game. The offsets here are relative to the start of this
|
// This structure is internally split into two by the game. The offsets here are relative to the start of this
|
||||||
// structure (first column), and relative to the start of the second internal structure (second column).
|
// structure (first column), and relative to the start of the second internal structure (second column).
|
||||||
/* 0000:---- */ PlayerInventoryBE inventory;
|
/* 0000:---- */ PlayerInventoryBE inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
|
/* 034C:---- */ PlayerDispDataV123BE disp;
|
||||||
/* 041C:0000 */ be_uint32_t validation_flags = 0;
|
/* 041C:0000 */ be_uint32_t validation_flags = 0;
|
||||||
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
|
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
|
||||||
@@ -673,7 +673,7 @@ struct PSOGCEp3CharacterFile {
|
|||||||
// This structure is internally split into two by the game. The offsets here are relative to the start of this
|
// This structure is internally split into two by the game. The offsets here are relative to the start of this
|
||||||
// structure (first column), and relative to the start of the second internal structure (second column).
|
// structure (first column), and relative to the start of the second internal structure (second column).
|
||||||
/* 0000:---- */ PlayerInventoryBE inventory;
|
/* 0000:---- */ PlayerInventoryBE inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
|
/* 034C:---- */ PlayerDispDataV123BE disp;
|
||||||
/* 041C:0000 */ be_uint32_t validation_flags = 0;
|
/* 041C:0000 */ be_uint32_t validation_flags = 0;
|
||||||
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
|
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
|
||||||
@@ -735,7 +735,7 @@ struct PSOXBCharacterFile {
|
|||||||
// structure (first column), and relative to the start of the second internal structure (second column). Most
|
// structure (first column), and relative to the start of the second internal structure (second column). Most
|
||||||
// fields have the same meanings as in PSOGCCharacterFile::Character.
|
// fields have the same meanings as in PSOGCCharacterFile::Character.
|
||||||
/* 0000:---- */ PlayerInventory inventory;
|
/* 0000:---- */ PlayerInventory inventory;
|
||||||
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
|
/* 034C:---- */ PlayerDispDataV123 disp;
|
||||||
/* 041C:0000 */ le_uint32_t validation_flags = 0;
|
/* 041C:0000 */ le_uint32_t validation_flags = 0;
|
||||||
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
|
||||||
/* 0424:0008 */ le_uint32_t signature = 0xC87ED5B1;
|
/* 0424:0008 */ le_uint32_t signature = 0xC87ED5B1;
|
||||||
@@ -794,7 +794,7 @@ struct PSOBBCharacterFile {
|
|||||||
// .psochar file. See PSOCHARFile below for the full file format.
|
// .psochar file. See PSOCHARFile below for the full file format.
|
||||||
|
|
||||||
/* 0000 */ PlayerInventory inventory;
|
/* 0000 */ PlayerInventory inventory;
|
||||||
/* 034C */ PlayerDispDataBB disp;
|
/* 034C */ PlayerDispDataV4 disp;
|
||||||
/* 04DC */ le_uint32_t validation_flags = 0;
|
/* 04DC */ le_uint32_t validation_flags = 0;
|
||||||
/* 04E0 */ le_uint32_t creation_timestamp = 0;
|
/* 04E0 */ le_uint32_t creation_timestamp = 0;
|
||||||
/* 04E4 */ le_uint32_t signature = 0xC87ED5B1;
|
/* 04E4 */ le_uint32_t signature = 0xC87ED5B1;
|
||||||
@@ -808,34 +808,39 @@ struct PSOBBCharacterFile {
|
|||||||
/* 1AC8 */ le_uint32_t unknown_a3 = 0;
|
/* 1AC8 */ le_uint32_t unknown_a3 = 0;
|
||||||
/* 1ACC */ parray<SaveFileSymbolChatEntryBB, 0x0C> symbol_chats;
|
/* 1ACC */ parray<SaveFileSymbolChatEntryBB, 0x0C> symbol_chats;
|
||||||
/* 1FAC */ parray<SaveFileShortcutEntryBB, 0x10> shortcuts;
|
/* 1FAC */ parray<SaveFileShortcutEntryBB, 0x10> shortcuts;
|
||||||
/* 29EC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
|
/* 29EC */ pstring<TextEncoding::UTF16, 0xAC> auto_reply;
|
||||||
/* 2B44 */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
|
/* 2B44 */ pstring<TextEncoding::UTF16, 0xAC> info_board;
|
||||||
/* 2C9C */ PlayerRecordsBattle battle_records;
|
/* 2C9C */ PlayerRecordsBattle battle_records;
|
||||||
/* 2CB4 */ parray<uint8_t, 4> unknown_a4;
|
/* 2CB4 */ parray<uint8_t, 4> unknown_a4;
|
||||||
/* 2CB8 */ PlayerRecordsChallengeBB challenge_records;
|
/* 2CB8 */ PlayerRecordsChallengeBB challenge_records;
|
||||||
/* 2DF8 */ parray<le_uint16_t, 0x0014> tech_menu_shortcut_entries;
|
/* 2DF8 */ parray<le_uint16_t, 0x14> tech_menu_shortcut_entries;
|
||||||
/* 2E20 */ ChoiceSearchConfig choice_search_config;
|
/* 2E20 */ ChoiceSearchConfig choice_search_config;
|
||||||
/* 2E38 */ parray<uint8_t, 0x0010> unknown_a6;
|
/* 2E38 */ parray<uint8_t, 0x10> unknown_a6;
|
||||||
/* 2E48 */ parray<le_uint32_t, 0x0010> quest_counters;
|
/* 2E48 */ parray<le_uint32_t, 0x10> quest_counters;
|
||||||
/* 2E88 */ PlayerRecordsBattle offline_battle_records;
|
/* 2E88 */ PlayerRecordsBattle offline_battle_records;
|
||||||
/* 2EA0 */ parray<uint8_t, 4> unknown_a7;
|
/* 2EA0 */ parray<uint8_t, 4> unknown_a7;
|
||||||
/* 2EA4 */
|
/* 2EA4 */
|
||||||
|
|
||||||
PSOBBCharacterFile() = default;
|
PSOBBCharacterFile() = default;
|
||||||
|
|
||||||
PlayerDispDataBBPreview to_preview() const;
|
PlayerDispDataV4Preview to_preview() const;
|
||||||
|
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
|
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
|
||||||
uint32_t guild_card_number,
|
uint32_t guild_card_number,
|
||||||
Language language,
|
Language language,
|
||||||
const PlayerVisualConfig& visual,
|
const PlayerVisualConfigV4& visual,
|
||||||
const std::string& name,
|
std::shared_ptr<const LevelTable> level_table = nullptr);
|
||||||
std::shared_ptr<const LevelTable> level_table);
|
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_preview(
|
template <bool BE>
|
||||||
|
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
|
||||||
uint32_t guild_card_number,
|
uint32_t guild_card_number,
|
||||||
Language language,
|
Language language,
|
||||||
const PlayerDispDataBBPreview& preview,
|
const PlayerVisualConfigV123T<BE>& visual,
|
||||||
std::shared_ptr<const LevelTable> level_table);
|
std::shared_ptr<const LevelTable> level_table = nullptr) {
|
||||||
|
return PSOBBCharacterFile::create_from_config(
|
||||||
|
guild_card_number, language, visual.to_v4(language, language), level_table);
|
||||||
|
}
|
||||||
|
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCNTECharacterFile::Character& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCNTECharacterFile::Character& src);
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODC112000CharacterFile::Character& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODC112000CharacterFile::Character& src);
|
||||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCV1CharacterFile::Character& src);
|
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCV1CharacterFile::Character& src);
|
||||||
@@ -1034,19 +1039,19 @@ struct LegacySavedPlayerDataBB { // .nsc file format
|
|||||||
/* 0000 */ be_uint64_t signature = SIGNATURE_V1;
|
/* 0000 */ be_uint64_t signature = SIGNATURE_V1;
|
||||||
/* 0008 */ parray<uint8_t, 0x20> unused;
|
/* 0008 */ parray<uint8_t, 0x20> unused;
|
||||||
/* 0028 */ PlayerRecordsBattle battle_records;
|
/* 0028 */ PlayerRecordsBattle battle_records;
|
||||||
/* 0040 */ PlayerDispDataBBPreview preview;
|
/* 0040 */ PlayerDispDataV4Preview preview;
|
||||||
/* 00BC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
|
/* 00BC */ pstring<TextEncoding::UTF16, 0xAC> auto_reply;
|
||||||
/* 0214 */ PlayerBank200 bank;
|
/* 0214 */ PlayerBank200 bank;
|
||||||
/* 14DC */ PlayerRecordsChallengeBB challenge_records;
|
/* 14DC */ PlayerRecordsChallengeBB challenge_records;
|
||||||
/* 161C */ PlayerDispDataBB disp;
|
/* 161C */ PlayerDispDataV4 disp;
|
||||||
/* 17AC */ pstring<TextEncoding::UTF16, 0x0058> guild_card_description;
|
/* 17AC */ pstring<TextEncoding::UTF16, 0x58> guild_card_description;
|
||||||
/* 185C */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
|
/* 185C */ pstring<TextEncoding::UTF16, 0xAC> info_board;
|
||||||
/* 19B4 */ PlayerInventory inventory;
|
/* 19B4 */ PlayerInventory inventory;
|
||||||
/* 1D00 */ parray<uint8_t, 4> unknown_a2;
|
/* 1D00 */ parray<uint8_t, 4> unknown_a2;
|
||||||
/* 1D04 */ QuestFlags quest_flags;
|
/* 1D04 */ QuestFlags quest_flags;
|
||||||
/* 1F04 */ le_uint32_t death_count = 0;
|
/* 1F04 */ le_uint32_t death_count = 0;
|
||||||
/* 1F08 */ parray<le_uint32_t, 0x0016> quest_counters;
|
/* 1F08 */ parray<le_uint32_t, 0x16> quest_counters;
|
||||||
/* 1F60 */ parray<le_uint16_t, 0x0014> tech_menu_shortcut_entries;
|
/* 1F60 */ parray<le_uint16_t, 0x14> tech_menu_shortcut_entries;
|
||||||
/* 1F88 */
|
/* 1F88 */
|
||||||
} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88);
|
} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88);
|
||||||
|
|
||||||
@@ -1054,7 +1059,7 @@ struct LegacySavedAccountDataBB { // .nsa file format
|
|||||||
static const char* SIGNATURE;
|
static const char* SIGNATURE;
|
||||||
|
|
||||||
/* 0000 */ pstring<TextEncoding::ASCII, 0x40> signature;
|
/* 0000 */ pstring<TextEncoding::ASCII, 0x40> signature;
|
||||||
/* 0040 */ parray<le_uint32_t, 0x001E> blocked_senders;
|
/* 0040 */ parray<le_uint32_t, 0x1E> blocked_senders;
|
||||||
/* 00B8 */ PSOBBGuildCardFile guild_card_file;
|
/* 00B8 */ PSOBBGuildCardFile guild_card_file;
|
||||||
/* D648 */ PSOBBBaseSystemFile system_file;
|
/* D648 */ PSOBBBaseSystemFile system_file;
|
||||||
/* D880 */ PSOBBFullTeamMembership team_membership;
|
/* D880 */ PSOBBFullTeamMembership team_membership;
|
||||||
|
|||||||
+453
-442
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -24,7 +24,7 @@ extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
|
|||||||
|
|
||||||
constexpr size_t V3_V4_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
|
constexpr size_t V3_V4_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
|
||||||
|
|
||||||
// TODO: Many of these functions should take a shared_ptr<Channel> instead of a shared_ptr<Client>. Refactor functions
|
// TODO: Many of these functions should take a std::shared_ptr<Channel> instead of a std::shared_ptr<Client>. Refactor functions
|
||||||
// appropriately.
|
// appropriately.
|
||||||
|
|
||||||
// Note: There are so many versions of this function for a few reasons:
|
// Note: There are so many versions of this function for a few reasons:
|
||||||
@@ -192,7 +192,7 @@ void send_pc_console_split_reconnect(
|
|||||||
|
|
||||||
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
|
void send_client_init_bb(std::shared_ptr<Client> c, uint32_t error);
|
||||||
void send_system_file_bb(std::shared_ptr<Client> c);
|
void send_system_file_bb(std::shared_ptr<Client> c);
|
||||||
void send_player_preview_bb(std::shared_ptr<Client> c, int8_t character_index, const PlayerDispDataBBPreview* preview);
|
void send_player_preview_bb(std::shared_ptr<Client> c, int8_t character_index, const PlayerDispDataV4Preview* preview);
|
||||||
void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
|
void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
|
||||||
void send_guild_card_header_bb(std::shared_ptr<Client> c);
|
void send_guild_card_header_bb(std::shared_ptr<Client> c);
|
||||||
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
|
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
|
||||||
|
|||||||
+4
-6
@@ -13,9 +13,7 @@
|
|||||||
#include "ShellCommands.hh"
|
#include "ShellCommands.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
ServerShell::ServerShell(std::shared_ptr<ServerState> state)
|
||||||
|
|
||||||
ServerShell::ServerShell(shared_ptr<ServerState> state)
|
|
||||||
: state(state), th(&ServerShell::thread_fn, this) {}
|
: state(state), th(&ServerShell::thread_fn, this) {}
|
||||||
|
|
||||||
ServerShell::~ServerShell() {
|
ServerShell::~ServerShell() {
|
||||||
@@ -28,7 +26,7 @@ void ServerShell::thread_fn() {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
phosg::fwrite_fmt(stdout, "newserv> ");
|
phosg::fwrite_fmt(stdout, "newserv> ");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
string command;
|
std::string command;
|
||||||
uint64_t read_start_usecs = phosg::now();
|
uint64_t read_start_usecs = phosg::now();
|
||||||
try {
|
try {
|
||||||
command = phosg::fgets(stdin);
|
command = phosg::fgets(stdin);
|
||||||
@@ -53,7 +51,7 @@ void ServerShell::thread_fn() {
|
|||||||
phosg::strip_leading_whitespace(command);
|
phosg::strip_leading_whitespace(command);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::promise<deque<string>> promise;
|
std::promise<std::deque<std::string>> promise;
|
||||||
auto future = promise.get_future();
|
auto future = promise.get_future();
|
||||||
|
|
||||||
asio::co_spawn(
|
asio::co_spawn(
|
||||||
@@ -73,7 +71,7 @@ void ServerShell::thread_fn() {
|
|||||||
} catch (const exit_shell&) {
|
} catch (const exit_shell&) {
|
||||||
this->state->io_context->stop();
|
this->state->io_context->stop();
|
||||||
return;
|
return;
|
||||||
} catch (const exception& e) {
|
} catch (const std::exception& e) {
|
||||||
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
|
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+377
-396
File diff suppressed because it is too large
Load Diff
+4
-11
@@ -206,8 +206,6 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
|||||||
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermap_for_free_play_key;
|
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermap_for_free_play_key;
|
||||||
std::shared_ptr<const RoomLayoutIndex> room_layout_index;
|
std::shared_ptr<const RoomLayoutIndex> room_layout_index;
|
||||||
std::shared_ptr<const BBStreamFile> bb_stream_file;
|
std::shared_ptr<const BBStreamFile> bb_stream_file;
|
||||||
std::shared_ptr<FileContentsCache> bb_system_cache;
|
|
||||||
std::shared_ptr<FileContentsCache> gba_files_cache;
|
|
||||||
std::shared_ptr<const DOLFileIndex> dol_file_index;
|
std::shared_ptr<const DOLFileIndex> dol_file_index;
|
||||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
|
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
|
||||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
|
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
|
||||||
@@ -296,15 +294,14 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
|||||||
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
|
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
|
||||||
|
|
||||||
std::shared_ptr<AccountIndex> account_index;
|
std::shared_ptr<AccountIndex> account_index;
|
||||||
bool allow_saving_accounts = true;
|
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||||
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
|
|
||||||
std::shared_ptr<TeamIndex> team_index;
|
std::shared_ptr<TeamIndex> team_index;
|
||||||
phosg::JSON team_reward_defs_json;
|
phosg::JSON team_reward_defs_json;
|
||||||
|
|
||||||
std::shared_ptr<const Menu> information_menu_v2;
|
std::shared_ptr<const Menu> information_menu_v2;
|
||||||
std::shared_ptr<const Menu> information_menu_v3;
|
std::shared_ptr<const Menu> information_menu_v3;
|
||||||
std::shared_ptr<std::vector<std::string>> information_contents_v2;
|
std::shared_ptr<const std::vector<std::string>> information_contents_v2;
|
||||||
std::shared_ptr<std::vector<std::string>> information_contents_v3;
|
std::shared_ptr<const std::vector<std::string>> information_contents_v3;
|
||||||
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
|
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
|
||||||
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
|
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
|
||||||
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
|
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
|
||||||
@@ -418,10 +415,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
|||||||
|
|
||||||
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
|
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
|
||||||
|
|
||||||
std::shared_ptr<const std::string> load_bb_file(
|
std::shared_ptr<const std::string> load_bb_file(const std::string& patch_index_filename) const;
|
||||||
const std::string& patch_index_filename,
|
|
||||||
const std::string& gsl_filename = "",
|
|
||||||
const std::string& bb_directory_filename = "") const;
|
|
||||||
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
|
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
|
||||||
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
|
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
|
||||||
|
|
||||||
@@ -452,7 +446,6 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
|||||||
void load_teams();
|
void load_teams();
|
||||||
void load_patch_indexes();
|
void load_patch_indexes();
|
||||||
void load_maps();
|
void load_maps();
|
||||||
void clear_file_caches();
|
|
||||||
void load_battle_params();
|
void load_battle_params();
|
||||||
void load_level_tables();
|
void load_level_tables();
|
||||||
void load_text_index();
|
void load_text_index();
|
||||||
|
|||||||
+169
-165
@@ -14,48 +14,48 @@
|
|||||||
#include "ServerState.hh"
|
#include "ServerState.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
using namespace std;
|
std::vector<const ShellCommand*> ShellCommand::commands_by_order;
|
||||||
|
std::unordered_map<std::string, const ShellCommand*> ShellCommand::commands_by_name;
|
||||||
vector<const ShellCommand*> ShellCommand::commands_by_order;
|
|
||||||
unordered_map<string, const ShellCommand*> ShellCommand::commands_by_name;
|
|
||||||
|
|
||||||
exit_shell::exit_shell() : runtime_error("shell exited") {}
|
exit_shell::exit_shell() : runtime_error("shell exited") {}
|
||||||
|
|
||||||
shared_ptr<Client> ShellCommand::Args::get_client() const {
|
std::shared_ptr<Client> ShellCommand::Args::get_client() const {
|
||||||
if (!s->game_server) {
|
if (!s->game_server) {
|
||||||
throw logic_error("game server is missing");
|
throw std::logic_error("game server is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Client> c;
|
std::shared_ptr<Client> c;
|
||||||
if (this->session_name.empty()) {
|
if (this->session_name.empty()) {
|
||||||
return this->s->game_server->get_client();
|
return this->s->game_server->get_client();
|
||||||
} else {
|
} else {
|
||||||
auto clients = this->s->game_server->get_clients_by_identifier(this->session_name);
|
auto clients = this->s->game_server->get_clients_by_identifier(this->session_name);
|
||||||
if (clients.empty()) {
|
if (clients.empty()) {
|
||||||
throw runtime_error("no such client");
|
throw std::runtime_error("no such client");
|
||||||
}
|
}
|
||||||
if (clients.size() > 1) {
|
if (clients.size() > 1) {
|
||||||
throw runtime_error("multiple clients found");
|
throw std::runtime_error("multiple clients found");
|
||||||
}
|
}
|
||||||
return clients[0];
|
return clients[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Client> ShellCommand::Args::get_proxy_client() const {
|
std::shared_ptr<Client> ShellCommand::Args::get_proxy_client() const {
|
||||||
auto c = this->get_client();
|
auto c = this->get_client();
|
||||||
if (!c->proxy_session) {
|
if (!c->proxy_session) {
|
||||||
throw runtime_error("client is not in a proxy session");
|
throw std::runtime_error("client is not in a proxy session");
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShellCommand::ShellCommand(const char* name, const char* help_text, asio::awaitable<deque<string>> (*run)(Args&))
|
ShellCommand::ShellCommand(
|
||||||
|
const char* name, const char* help_text, asio::awaitable<std::deque<std::string>> (*run)(Args&))
|
||||||
: name(name), help_text(help_text), run(run) {
|
: name(name), help_text(help_text), run(run) {
|
||||||
ShellCommand::commands_by_order.emplace_back(this);
|
ShellCommand::commands_by_order.emplace_back(this);
|
||||||
ShellCommand::commands_by_name.emplace(this->name, this);
|
ShellCommand::commands_by_name.emplace(this->name, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<deque<string>> ShellCommand::dispatch_str(shared_ptr<ServerState> s, const string& command) {
|
asio::awaitable<std::deque<std::string>> ShellCommand::dispatch_str(
|
||||||
|
std::shared_ptr<ServerState> s, const std::string& command) {
|
||||||
size_t command_end = phosg::skip_non_whitespace(command, 0);
|
size_t command_end = phosg::skip_non_whitespace(command, 0);
|
||||||
size_t args_begin = phosg::skip_whitespace(command, command_end);
|
size_t args_begin = phosg::skip_whitespace(command, command_end);
|
||||||
Args args;
|
Args args;
|
||||||
@@ -65,21 +65,21 @@ asio::awaitable<deque<string>> ShellCommand::dispatch_str(shared_ptr<ServerState
|
|||||||
co_return co_await ShellCommand::dispatch(args);
|
co_return co_await ShellCommand::dispatch(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
asio::awaitable<deque<string>> ShellCommand::dispatch(Args& args) {
|
asio::awaitable<std::deque<std::string>> ShellCommand::dispatch(Args& args) {
|
||||||
const ShellCommand* def = nullptr;
|
const ShellCommand* def = nullptr;
|
||||||
try {
|
try {
|
||||||
def = commands_by_name.at(args.command);
|
def = commands_by_name.at(args.command);
|
||||||
} catch (const out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
}
|
}
|
||||||
if (!def) {
|
if (!def) {
|
||||||
throw runtime_error("no such command; try 'help'");
|
throw std::runtime_error("no such command; try 'help'");
|
||||||
} else {
|
} else {
|
||||||
return def->run(args);
|
return def->run(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static string get_quoted_string(string& s) {
|
static std::string get_quoted_string(std::string& s) {
|
||||||
string ret;
|
std::string ret;
|
||||||
char end_char = (s.at(0) == '\"') ? '\"' : ' ';
|
char end_char = (s.at(0) == '\"') ? '\"' : ' ';
|
||||||
size_t z = (s.at(0) == '\"') ? 1 : 0;
|
size_t z = (s.at(0) == '\"') ? 1 : 0;
|
||||||
for (; (z < s.size()) && (s[z] != end_char); z++) {
|
for (; (z < s.size()) && (s[z] != end_char); z++) {
|
||||||
@@ -87,7 +87,7 @@ static string get_quoted_string(string& s) {
|
|||||||
if (z + 1 < s.size()) {
|
if (z + 1 < s.size()) {
|
||||||
ret.push_back(s[z + 1]);
|
ret.push_back(s[z + 1]);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("incomplete escape sequence");
|
throw std::runtime_error("incomplete escape sequence");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ret.push_back(s[z]);
|
ret.push_back(s[z]);
|
||||||
@@ -95,7 +95,7 @@ static string get_quoted_string(string& s) {
|
|||||||
}
|
}
|
||||||
if (end_char != ' ') {
|
if (end_char != ' ') {
|
||||||
if (z >= s.size()) {
|
if (z >= s.size()) {
|
||||||
throw runtime_error("unterminated quoted string");
|
throw std::runtime_error("unterminated quoted string");
|
||||||
}
|
}
|
||||||
s = s.substr(phosg::skip_whitespace(s, z + 1));
|
s = s.substr(phosg::skip_whitespace(s, z + 1));
|
||||||
} else {
|
} else {
|
||||||
@@ -104,8 +104,8 @@ static string get_quoted_string(string& s) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static asio::awaitable<deque<string>> empty_handler(ShellCommand::Args&) {
|
static asio::awaitable<std::deque<std::string>> empty_handler(ShellCommand::Args&) {
|
||||||
co_return deque<string>();
|
co_return std::deque<std::string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShellCommand c_nop1("", nullptr, empty_handler);
|
ShellCommand c_nop1("", nullptr, empty_handler);
|
||||||
@@ -115,8 +115,8 @@ ShellCommand c_nop3("#", nullptr, empty_handler);
|
|||||||
ShellCommand c_help(
|
ShellCommand c_help(
|
||||||
"help", "help\n\
|
"help", "help\n\
|
||||||
You\'re reading it now.",
|
You\'re reading it now.",
|
||||||
+[](ShellCommand::Args&) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args&) -> asio::awaitable<std::deque<std::string>> {
|
||||||
deque<string> ret({"Commands:"});
|
std::deque<std::string> ret({"Commands:"});
|
||||||
for (const auto& def : ShellCommand::commands_by_order) {
|
for (const auto& def : ShellCommand::commands_by_order) {
|
||||||
if (def->help_text) {
|
if (def->help_text) {
|
||||||
// TODO: It's not great that we copy the text here.
|
// TODO: It's not great that we copy the text here.
|
||||||
@@ -129,7 +129,7 @@ ShellCommand c_help(
|
|||||||
ShellCommand c_exit(
|
ShellCommand c_exit(
|
||||||
"exit", "exit (or ctrl+d)\n\
|
"exit", "exit (or ctrl+d)\n\
|
||||||
Shut down the server.",
|
Shut down the server.",
|
||||||
+[](ShellCommand::Args&) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args&) -> asio::awaitable<std::deque<std::string>> {
|
||||||
throw exit_shell();
|
throw exit_shell();
|
||||||
});
|
});
|
||||||
ShellCommand c_on(
|
ShellCommand c_on(
|
||||||
@@ -141,7 +141,7 @@ ShellCommand c_on(
|
|||||||
gamertag, or a BB account username. For proxy commands, SESSION should be\n\
|
gamertag, or a BB account username. For proxy commands, SESSION should be\n\
|
||||||
the session ID, which generally is the same as the player\'s account ID\n\
|
the session ID, which generally is the same as the player\'s account ID\n\
|
||||||
and appears after \"LinkedSession:\" in the log output.",
|
and appears after \"LinkedSession:\" in the log output.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
size_t session_name_end = phosg::skip_non_whitespace(args.args, 0);
|
size_t session_name_end = phosg::skip_non_whitespace(args.args, 0);
|
||||||
size_t command_begin = phosg::skip_whitespace(args.args, session_name_end);
|
size_t command_begin = phosg::skip_whitespace(args.args, session_name_end);
|
||||||
size_t command_end = phosg::skip_non_whitespace(args.args, command_begin);
|
size_t command_end = phosg::skip_non_whitespace(args.args, command_begin);
|
||||||
@@ -158,7 +158,6 @@ ShellCommand c_reload(
|
|||||||
accounts - reindex user accounts\n\
|
accounts - reindex user accounts\n\
|
||||||
battle-params - reload the BB enemy stats files\n\
|
battle-params - reload the BB enemy stats files\n\
|
||||||
bb-keys - reload BB private keys\n\
|
bb-keys - reload BB private keys\n\
|
||||||
caches - clear all cached files\n\
|
|
||||||
config - reload most fields from config.json\n\
|
config - reload most fields from config.json\n\
|
||||||
dol-files - reindex all DOL files\n\
|
dol-files - reindex all DOL files\n\
|
||||||
drop-tables - reload drop tables\n\
|
drop-tables - reload drop tables\n\
|
||||||
@@ -182,7 +181,7 @@ ShellCommand c_reload(
|
|||||||
disconnect or reload the battle parameters, so if these are changed without\n\
|
disconnect or reload the battle parameters, so if these are changed without\n\
|
||||||
restarting, clients may see (for example) EXP messages inconsistent with\n\
|
restarting, clients may see (for example) EXP messages inconsistent with\n\
|
||||||
the amounts of EXP actually received.",
|
the amounts of EXP actually received.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto types = phosg::split(args.args, ' ');
|
auto types = phosg::split(args.args, ' ');
|
||||||
for (const auto& type : types) {
|
for (const auto& type : types) {
|
||||||
if (type == "all") {
|
if (type == "all") {
|
||||||
@@ -193,8 +192,6 @@ ShellCommand c_reload(
|
|||||||
args.s->load_accounts();
|
args.s->load_accounts();
|
||||||
} else if (type == "maps") {
|
} else if (type == "maps") {
|
||||||
args.s->load_maps();
|
args.s->load_maps();
|
||||||
} else if (type == "caches") {
|
|
||||||
args.s->clear_file_caches();
|
|
||||||
} else if (type == "patch-files") {
|
} else if (type == "patch-files") {
|
||||||
args.s->load_patch_indexes();
|
args.s->load_patch_indexes();
|
||||||
} else if (type == "ep3-cards") {
|
} else if (type == "ep3-cards") {
|
||||||
@@ -234,18 +231,18 @@ ShellCommand c_reload(
|
|||||||
} else if (type == "quests") {
|
} else if (type == "quests") {
|
||||||
args.s->load_quest_index();
|
args.s->load_quest_index();
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid data type: " + type);
|
throw std::runtime_error("invalid data type: " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
ShellCommand c_list_accounts(
|
ShellCommand c_list_accounts(
|
||||||
"list-accounts", "list-accounts\n\
|
"list-accounts", "list-accounts\n\
|
||||||
List all accounts registered on the server.",
|
List all accounts registered on the server.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
deque<string> ret;
|
std::deque<std::string> ret;
|
||||||
auto accounts = args.s->account_index->all();
|
auto accounts = args.s->account_index->all();
|
||||||
if (accounts.empty()) {
|
if (accounts.empty()) {
|
||||||
ret.emplace_back("No accounts registered");
|
ret.emplace_back("No accounts registered");
|
||||||
@@ -257,20 +254,20 @@ ShellCommand c_list_accounts(
|
|||||||
co_return ret;
|
co_return ret;
|
||||||
});
|
});
|
||||||
|
|
||||||
uint32_t parse_account_flags(const string& flags_str) {
|
uint32_t parse_account_flags(const std::string& flags_str) {
|
||||||
try {
|
try {
|
||||||
size_t end_pos = 0;
|
size_t end_pos = 0;
|
||||||
uint32_t ret = stoul(flags_str, &end_pos, 16);
|
uint32_t ret = std::stoul(flags_str, &end_pos, 16);
|
||||||
if (end_pos == flags_str.size()) {
|
if (end_pos == flags_str.size()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
} catch (const exception&) {
|
} catch (const std::exception&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ret = 0;
|
uint32_t ret = 0;
|
||||||
auto tokens = phosg::split(flags_str, ',');
|
auto tokens = phosg::split(flags_str, ',');
|
||||||
for (const auto& token : tokens) {
|
for (const auto& token : tokens) {
|
||||||
string token_upper = phosg::toupper(token);
|
std::string token_upper = phosg::toupper(token);
|
||||||
if (token_upper == "NONE") {
|
if (token_upper == "NONE") {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
} else if (token_upper == "KICK_USER") {
|
} else if (token_upper == "KICK_USER") {
|
||||||
@@ -302,32 +299,32 @@ uint32_t parse_account_flags(const string& flags_str) {
|
|||||||
} else if (token_upper == "IS_SHARED_ACCOUNT") {
|
} else if (token_upper == "IS_SHARED_ACCOUNT") {
|
||||||
ret |= static_cast<uint32_t>(Account::Flag::IS_SHARED_ACCOUNT);
|
ret |= static_cast<uint32_t>(Account::Flag::IS_SHARED_ACCOUNT);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid flag name: " + token_upper);
|
throw std::runtime_error("invalid flag name: " + token_upper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t parse_account_user_flags(const string& user_flags_str) {
|
uint32_t parse_account_user_flags(const std::string& user_flags_str) {
|
||||||
try {
|
try {
|
||||||
size_t end_pos = 0;
|
size_t end_pos = 0;
|
||||||
uint32_t ret = stoul(user_flags_str, &end_pos, 16);
|
uint32_t ret = std::stoul(user_flags_str, &end_pos, 16);
|
||||||
if (end_pos == user_flags_str.size()) {
|
if (end_pos == user_flags_str.size()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
} catch (const exception&) {
|
} catch (const std::exception&) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ret = 0;
|
uint32_t ret = 0;
|
||||||
auto tokens = phosg::split(user_flags_str, ',');
|
auto tokens = phosg::split(user_flags_str, ',');
|
||||||
for (const auto& token : tokens) {
|
for (const auto& token : tokens) {
|
||||||
string token_upper = phosg::toupper(token);
|
std::string token_upper = phosg::toupper(token);
|
||||||
if (token_upper == "NONE") {
|
if (token_upper == "NONE") {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
} else if (token_upper == "DISABLE_DROP_NOTIFICATION_BROADCAST") {
|
} else if (token_upper == "DISABLE_DROP_NOTIFICATION_BROADCAST") {
|
||||||
ret |= static_cast<uint32_t>(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST);
|
ret |= static_cast<uint32_t>(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid user flag name: " + token_upper);
|
throw std::runtime_error("invalid user flag name: " + token_upper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -363,15 +360,15 @@ ShellCommand c_add_account(
|
|||||||
IS_SHARED_ACCOUNT: Account is a shared serial (disables Access Key and\n\
|
IS_SHARED_ACCOUNT: Account is a shared serial (disables Access Key and\n\
|
||||||
password checks; players will get Guild Cards based on their player\n\
|
password checks; players will get Guild Cards based on their player\n\
|
||||||
names)",
|
names)",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto account = make_shared<Account>();
|
auto account = std::make_shared<Account>();
|
||||||
for (const string& token : phosg::split(args.args, ' ')) {
|
for (const std::string& token : phosg::split(args.args, ' ')) {
|
||||||
if (token.starts_with("id=")) {
|
if (token.starts_with("id=")) {
|
||||||
account->account_id = stoul(token.substr(3), nullptr, 16);
|
account->account_id = std::stoul(token.substr(3), nullptr, 16);
|
||||||
} else if (token.starts_with("ep3-current-meseta=")) {
|
} else if (token.starts_with("ep3-current-meseta=")) {
|
||||||
account->ep3_current_meseta = stoul(token.substr(19), nullptr, 0);
|
account->ep3_current_meseta = std::stoul(token.substr(19), nullptr, 0);
|
||||||
} else if (token.starts_with("ep3-total-meseta=")) {
|
} else if (token.starts_with("ep3-total-meseta=")) {
|
||||||
account->ep3_total_meseta_earned = stoul(token.substr(17), nullptr, 0);
|
account->ep3_total_meseta_earned = std::stoul(token.substr(17), nullptr, 0);
|
||||||
} else if (token == "temporary") {
|
} else if (token == "temporary") {
|
||||||
account->is_temporary = true;
|
account->is_temporary = true;
|
||||||
} else if (token.starts_with("flags=")) {
|
} else if (token.starts_with("flags=")) {
|
||||||
@@ -379,12 +376,12 @@ ShellCommand c_add_account(
|
|||||||
} else if (token.starts_with("user-flags=")) {
|
} else if (token.starts_with("user-flags=")) {
|
||||||
account->user_flags = parse_account_user_flags(token.substr(11));
|
account->user_flags = parse_account_user_flags(token.substr(11));
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid account field: " + token);
|
throw std::invalid_argument("invalid account field: " + token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args.s->account_index->add(account);
|
args.s->account_index->add(account);
|
||||||
account->save();
|
account->save();
|
||||||
co_return deque<string>{format("Account {:08X} added", account->account_id)};
|
co_return std::deque<std::string>{std::format("Account {:08X} added", account->account_id)};
|
||||||
});
|
});
|
||||||
ShellCommand c_update_account(
|
ShellCommand c_update_account(
|
||||||
"update-account", "update-account ACCOUNT-ID PARAMETERS...\n\
|
"update-account", "update-account ACCOUNT-ID PARAMETERS...\n\
|
||||||
@@ -403,10 +400,10 @@ ShellCommand c_update_account(
|
|||||||
temporary: Marks the account as temporary; it is not saved to disk and\n\
|
temporary: Marks the account as temporary; it is not saved to disk and\n\
|
||||||
therefore will be deleted when the server shuts down.\n\
|
therefore will be deleted when the server shuts down.\n\
|
||||||
permanent: If the account was temporary, makes it non-temporary.",
|
permanent: If the account was temporary, makes it non-temporary.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto tokens = phosg::split(args.args, ' ');
|
auto tokens = phosg::split(args.args, ' ');
|
||||||
if (tokens.size() < 2) {
|
if (tokens.size() < 2) {
|
||||||
throw runtime_error("not enough arguments");
|
throw std::runtime_error("not enough arguments");
|
||||||
}
|
}
|
||||||
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
||||||
tokens.erase(tokens.begin());
|
tokens.erase(tokens.begin());
|
||||||
@@ -419,11 +416,11 @@ ShellCommand c_update_account(
|
|||||||
int64_t new_user_flags = -1;
|
int64_t new_user_flags = -1;
|
||||||
uint8_t new_is_temporary = 0xFF;
|
uint8_t new_is_temporary = 0xFF;
|
||||||
int64_t new_ban_duration = -1;
|
int64_t new_ban_duration = -1;
|
||||||
for (const string& token : tokens) {
|
for (const std::string& token : tokens) {
|
||||||
if (token.starts_with("ep3-current-meseta=")) {
|
if (token.starts_with("ep3-current-meseta=")) {
|
||||||
new_ep3_current_meseta = stoul(token.substr(19), nullptr, 0);
|
new_ep3_current_meseta = std::stoul(token.substr(19), nullptr, 0);
|
||||||
} else if (token.starts_with("ep3-total-meseta=")) {
|
} else if (token.starts_with("ep3-total-meseta=")) {
|
||||||
new_ep3_total_meseta = stoul(token.substr(17), nullptr, 0);
|
new_ep3_total_meseta = std::stoul(token.substr(17), nullptr, 0);
|
||||||
} else if (token == "temporary") {
|
} else if (token == "temporary") {
|
||||||
new_is_temporary = 1;
|
new_is_temporary = 1;
|
||||||
} else if (token == "permanent") {
|
} else if (token == "permanent") {
|
||||||
@@ -451,10 +448,10 @@ ShellCommand c_update_account(
|
|||||||
} else if (duration_str.ends_with("y")) {
|
} else if (duration_str.ends_with("y")) {
|
||||||
new_ban_duration = stoull(duration_str.substr(0, duration_str.size() - 1)) * 31536000000000LL;
|
new_ban_duration = stoull(duration_str.substr(0, duration_str.size() - 1)) * 31536000000000LL;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid time unit");
|
throw std::runtime_error("invalid time unit");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("invalid account field: " + token);
|
throw std::invalid_argument("invalid account field: " + token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,18 +479,18 @@ ShellCommand c_update_account(
|
|||||||
args.s->disconnect_all_banned_clients();
|
args.s->disconnect_all_banned_clients();
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return deque<string>{format("Account {:08X} updated", account->account_id)};
|
co_return std::deque<std::string>{std::format("Account {:08X} updated", account->account_id)};
|
||||||
});
|
});
|
||||||
ShellCommand c_delete_account(
|
ShellCommand c_delete_account(
|
||||||
"delete-account", "delete-account ACCOUNT-ID\n\
|
"delete-account", "delete-account ACCOUNT-ID\n\
|
||||||
Delete an account from the server. If a player is online with the deleted\n\
|
Delete an account from the server. If a player is online with the deleted\n\
|
||||||
account, they will not be automatically disconnected.",
|
account, they will not be automatically disconnected.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto account = args.s->account_index->from_account_id(stoul(args.args, nullptr, 16));
|
auto account = args.s->account_index->from_account_id(stoul(args.args, nullptr, 16));
|
||||||
args.s->account_index->remove(account->account_id);
|
args.s->account_index->remove(account->account_id);
|
||||||
account->is_temporary = true;
|
account->is_temporary = true;
|
||||||
account->delete_file();
|
account->delete_file();
|
||||||
co_return deque<string>{"Account deleted"};
|
co_return std::deque<std::string>{"Account deleted"};
|
||||||
});
|
});
|
||||||
|
|
||||||
ShellCommand c_add_license(
|
ShellCommand c_add_license(
|
||||||
@@ -512,57 +509,57 @@ ShellCommand c_add_license(
|
|||||||
add-license 385A92C4 DC 107862F9 d38XTu2p\n\
|
add-license 385A92C4 DC 107862F9 d38XTu2p\n\
|
||||||
add-license 385A92C4 GC 0418572923 282949185033 hunter2\n\
|
add-license 385A92C4 GC 0418572923 282949185033 hunter2\n\
|
||||||
add-license 385A92C4 BB user1 trustno1",
|
add-license 385A92C4 BB user1 trustno1",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto tokens = phosg::split(args.args, ' ');
|
auto tokens = phosg::split(args.args, ' ');
|
||||||
if (tokens.size() < 3) {
|
if (tokens.size() < 3) {
|
||||||
throw runtime_error("not enough arguments");
|
throw std::runtime_error("not enough arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
||||||
|
|
||||||
string type_str = phosg::toupper(tokens[1]);
|
std::string type_str = phosg::toupper(tokens[1]);
|
||||||
if (type_str == "DC-NTE") {
|
if (type_str == "DC-NTE") {
|
||||||
if (tokens.size() != 4) {
|
if (tokens.size() != 4) {
|
||||||
throw runtime_error("incorrect number of parameters");
|
throw std::runtime_error("incorrect number of parameters");
|
||||||
}
|
}
|
||||||
auto license = make_shared<DCNTELicense>();
|
auto license = std::make_shared<DCNTELicense>();
|
||||||
license->serial_number = std::move(tokens[2]);
|
license->serial_number = std::move(tokens[2]);
|
||||||
license->access_key = std::move(tokens[3]);
|
license->access_key = std::move(tokens[3]);
|
||||||
args.s->account_index->add_dc_nte_license(account, license);
|
args.s->account_index->add_dc_nte_license(account, license);
|
||||||
|
|
||||||
} else if (type_str == "DC") {
|
} else if (type_str == "DC") {
|
||||||
if (tokens.size() != 4) {
|
if (tokens.size() != 4) {
|
||||||
throw runtime_error("incorrect number of parameters");
|
throw std::runtime_error("incorrect number of parameters");
|
||||||
}
|
}
|
||||||
auto license = make_shared<V1V2License>();
|
auto license = std::make_shared<V1V2License>();
|
||||||
license->serial_number = stoul(tokens[2], nullptr, 16);
|
license->serial_number = std::stoul(tokens[2], nullptr, 16);
|
||||||
license->access_key = std::move(tokens[3]);
|
license->access_key = std::move(tokens[3]);
|
||||||
args.s->account_index->add_dc_license(account, license);
|
args.s->account_index->add_dc_license(account, license);
|
||||||
|
|
||||||
} else if (type_str == "PC") {
|
} else if (type_str == "PC") {
|
||||||
if (tokens.size() != 4) {
|
if (tokens.size() != 4) {
|
||||||
throw runtime_error("incorrect number of parameters");
|
throw std::runtime_error("incorrect number of parameters");
|
||||||
}
|
}
|
||||||
auto license = make_shared<V1V2License>();
|
auto license = std::make_shared<V1V2License>();
|
||||||
license->serial_number = stoul(tokens[2], nullptr, 16);
|
license->serial_number = std::stoul(tokens[2], nullptr, 16);
|
||||||
license->access_key = std::move(tokens[3]);
|
license->access_key = std::move(tokens[3]);
|
||||||
args.s->account_index->add_pc_license(account, license);
|
args.s->account_index->add_pc_license(account, license);
|
||||||
|
|
||||||
} else if (type_str == "GC") {
|
} else if (type_str == "GC") {
|
||||||
if (tokens.size() != 5) {
|
if (tokens.size() != 5) {
|
||||||
throw runtime_error("incorrect number of parameters");
|
throw std::runtime_error("incorrect number of parameters");
|
||||||
}
|
}
|
||||||
auto license = make_shared<GCLicense>();
|
auto license = std::make_shared<GCLicense>();
|
||||||
license->serial_number = stoul(tokens[2], nullptr, 10);
|
license->serial_number = std::stoul(tokens[2], nullptr, 10);
|
||||||
license->access_key = std::move(tokens[3]);
|
license->access_key = std::move(tokens[3]);
|
||||||
license->password = std::move(tokens[4]);
|
license->password = std::move(tokens[4]);
|
||||||
args.s->account_index->add_gc_license(account, license);
|
args.s->account_index->add_gc_license(account, license);
|
||||||
|
|
||||||
} else if (type_str == "XB") {
|
} else if (type_str == "XB") {
|
||||||
if (tokens.size() != 5) {
|
if (tokens.size() != 5) {
|
||||||
throw runtime_error("incorrect number of parameters");
|
throw std::runtime_error("incorrect number of parameters");
|
||||||
}
|
}
|
||||||
auto license = make_shared<XBLicense>();
|
auto license = std::make_shared<XBLicense>();
|
||||||
license->gamertag = std::move(tokens[2]);
|
license->gamertag = std::move(tokens[2]);
|
||||||
license->user_id = stoull(tokens[3], nullptr, 16);
|
license->user_id = stoull(tokens[3], nullptr, 16);
|
||||||
license->account_id = stoull(tokens[4], nullptr, 16);
|
license->account_id = stoull(tokens[4], nullptr, 16);
|
||||||
@@ -570,19 +567,19 @@ ShellCommand c_add_license(
|
|||||||
|
|
||||||
} else if (type_str == "BB") {
|
} else if (type_str == "BB") {
|
||||||
if (tokens.size() != 4) {
|
if (tokens.size() != 4) {
|
||||||
throw runtime_error("incorrect number of parameters");
|
throw std::runtime_error("incorrect number of parameters");
|
||||||
}
|
}
|
||||||
auto license = make_shared<BBLicense>();
|
auto license = std::make_shared<BBLicense>();
|
||||||
license->username = std::move(tokens[2]);
|
license->username = std::move(tokens[2]);
|
||||||
license->password = std::move(tokens[3]);
|
license->password = std::move(tokens[3]);
|
||||||
args.s->account_index->add_bb_license(account, license);
|
args.s->account_index->add_bb_license(account, license);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid license type");
|
throw std::runtime_error("invalid license type");
|
||||||
}
|
}
|
||||||
|
|
||||||
account->save();
|
account->save();
|
||||||
co_return deque<string>{format("Account {:08X} updated", account->account_id)};
|
co_return std::deque<std::string>{std::format("Account {:08X} updated", account->account_id)};
|
||||||
});
|
});
|
||||||
ShellCommand c_delete_license(
|
ShellCommand c_delete_license(
|
||||||
"delete-license", "delete-license ACCOUNT-ID TYPE PRIMARY-CREDENTIAL\n\
|
"delete-license", "delete-license ACCOUNT-ID TYPE PRIMARY-CREDENTIAL\n\
|
||||||
@@ -601,46 +598,46 @@ ShellCommand c_delete_license(
|
|||||||
delete-license 385A92C4 GC 0418572923\n\
|
delete-license 385A92C4 GC 0418572923\n\
|
||||||
delete-license 385A92C4 XB 7E29A2950019EB20\n\
|
delete-license 385A92C4 XB 7E29A2950019EB20\n\
|
||||||
delete-license 385A92C4 BB user1",
|
delete-license 385A92C4 BB user1",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto tokens = phosg::split(args.args, ' ');
|
auto tokens = phosg::split(args.args, ' ');
|
||||||
if (tokens.size() != 3) {
|
if (tokens.size() != 3) {
|
||||||
throw runtime_error("incorrect argument count");
|
throw std::runtime_error("incorrect argument count");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
||||||
|
|
||||||
string type_str = phosg::toupper(tokens[1]);
|
std::string type_str = phosg::toupper(tokens[1]);
|
||||||
if (type_str == "DC-NTE") {
|
if (type_str == "DC-NTE") {
|
||||||
args.s->account_index->remove_dc_nte_license(account, tokens[2]);
|
args.s->account_index->remove_dc_nte_license(account, tokens[2]);
|
||||||
} else if (type_str == "DC") {
|
} else if (type_str == "DC") {
|
||||||
args.s->account_index->remove_dc_license(account, stoul(tokens[2], nullptr, 16));
|
args.s->account_index->remove_dc_license(account, std::stoul(tokens[2], nullptr, 16));
|
||||||
} else if (type_str == "PC") {
|
} else if (type_str == "PC") {
|
||||||
args.s->account_index->remove_pc_license(account, stoul(tokens[2], nullptr, 16));
|
args.s->account_index->remove_pc_license(account, std::stoul(tokens[2], nullptr, 16));
|
||||||
} else if (type_str == "GC") {
|
} else if (type_str == "GC") {
|
||||||
args.s->account_index->remove_gc_license(account, stoul(tokens[2], nullptr, 0));
|
args.s->account_index->remove_gc_license(account, std::stoul(tokens[2], nullptr, 0));
|
||||||
} else if (type_str == "XB") {
|
} else if (type_str == "XB") {
|
||||||
args.s->account_index->remove_xb_license(account, stoull(tokens[2], nullptr, 16));
|
args.s->account_index->remove_xb_license(account, stoull(tokens[2], nullptr, 16));
|
||||||
} else if (type_str == "BB") {
|
} else if (type_str == "BB") {
|
||||||
args.s->account_index->remove_bb_license(account, tokens[2]);
|
args.s->account_index->remove_bb_license(account, tokens[2]);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid license type");
|
throw std::runtime_error("invalid license type");
|
||||||
}
|
}
|
||||||
|
|
||||||
account->save();
|
account->save();
|
||||||
co_return deque<string>{format("Account {:08X} updated", account->account_id)};
|
co_return std::deque<std::string>{std::format("Account {:08X} updated", account->account_id)};
|
||||||
});
|
});
|
||||||
|
|
||||||
ShellCommand c_lookup(
|
ShellCommand c_lookup(
|
||||||
"lookup", "lookup USER\n\
|
"lookup", "lookup USER\n\
|
||||||
Find the account for a logged-in user.",
|
Find the account for a logged-in user.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto target = args.s->find_client(&args.args);
|
auto target = args.s->find_client(&args.args);
|
||||||
if (target->login) {
|
if (target->login) {
|
||||||
co_return deque<string>{format("Found client {} with account ID {:08X}",
|
co_return std::deque<std::string>{format("Found client {} with account ID {:08X}",
|
||||||
target->channel->name, target->login->account->account_id)};
|
target->channel->name, target->login->account->account_id)};
|
||||||
} else {
|
} else {
|
||||||
// This should be impossible
|
// This should be impossible
|
||||||
throw logic_error("find_client found user who is not logged in");
|
throw std::logic_error("find_client found user who is not logged in");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ShellCommand c_kick(
|
ShellCommand c_kick(
|
||||||
@@ -648,26 +645,26 @@ ShellCommand c_kick(
|
|||||||
Disconnect a user from the server. USER may be an account ID, player name,\n\
|
Disconnect a user from the server. USER may be an account ID, player name,\n\
|
||||||
or client ID (beginning with \"C-\"). This does not ban the user; they are\n\
|
or client ID (beginning with \"C-\"). This does not ban the user; they are\n\
|
||||||
free to reconnect after doing this.",
|
free to reconnect after doing this.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto target = args.s->find_client(&args.args);
|
auto target = args.s->find_client(&args.args);
|
||||||
send_message_box(target, "$C6You have been kicked off the server.");
|
send_message_box(target, "$C6You have been kicked off the server.");
|
||||||
target->channel->disconnect();
|
target->channel->disconnect();
|
||||||
co_return deque<string>{format("Client C-{:X} disconnected from server", target->id)};
|
co_return std::deque<std::string>{std::format("Client C-{:X} disconnected from server", target->id)};
|
||||||
});
|
});
|
||||||
|
|
||||||
ShellCommand c_announce(
|
ShellCommand c_announce(
|
||||||
"announce", "announce MESSAGE\n\
|
"announce", "announce MESSAGE\n\
|
||||||
Send an announcement message to all players.",
|
Send an announcement message to all players.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
send_text_or_scrolling_message(args.s, args.args, args.args);
|
send_text_or_scrolling_message(args.s, args.args, args.args);
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
});
|
});
|
||||||
ShellCommand c_announce_mail(
|
ShellCommand c_announce_mail(
|
||||||
"announce-mail", "announce-mail MESSAGE\n\
|
"announce-mail", "announce-mail MESSAGE\n\
|
||||||
Send an announcement message via Simple Mail to all players.",
|
Send an announcement message via Simple Mail to all players.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
send_simple_mail(args.s, 0, args.s->name, args.args);
|
send_simple_mail(args.s, 0, args.s->name, args.args);
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
ShellCommand c_create_tournament(
|
ShellCommand c_create_tournament(
|
||||||
@@ -698,11 +695,11 @@ ShellCommand c_create_tournament(
|
|||||||
dialogue=ON/OFF: Enable/disable dialogue\n\
|
dialogue=ON/OFF: Enable/disable dialogue\n\
|
||||||
dice-exchange=ATK/DEF/NONE: Set dice exchange mode\n\
|
dice-exchange=ATK/DEF/NONE: Set dice exchange mode\n\
|
||||||
dice-boost=ON/OFF: Enable/disable dice boost",
|
dice-boost=ON/OFF: Enable/disable dice boost",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
string name = get_quoted_string(args.args);
|
std::string name = get_quoted_string(args.args);
|
||||||
string map_name = get_quoted_string(args.args);
|
std::string map_name = get_quoted_string(args.args);
|
||||||
auto map = args.s->ep3_map_index->map_for_name(map_name);
|
auto map = args.s->ep3_map_index->map_for_name(map_name);
|
||||||
uint32_t num_teams = stoul(get_quoted_string(args.args), nullptr, 0);
|
uint32_t num_teams = std::stoul(get_quoted_string(args.args), nullptr, 0);
|
||||||
Episode3::Rules rules;
|
Episode3::Rules rules;
|
||||||
rules.set_defaults();
|
rules.set_defaults();
|
||||||
uint8_t flags = Episode3::Tournament::Flag::HAS_COM_TEAMS;
|
uint8_t flags = Episode3::Tournament::Flag::HAS_COM_TEAMS;
|
||||||
@@ -719,24 +716,24 @@ ShellCommand c_create_tournament(
|
|||||||
} else if (token == "resize") {
|
} else if (token == "resize") {
|
||||||
flags |= Episode3::Tournament::Flag::RESIZE_ON_START;
|
flags |= Episode3::Tournament::Flag::RESIZE_ON_START;
|
||||||
} else if (token.starts_with("dice=")) {
|
} else if (token.starts_with("dice=")) {
|
||||||
auto parse_range_c = +[](const string& s) -> uint8_t {
|
auto parse_range_c = +[](const std::string& s) -> uint8_t {
|
||||||
auto tokens = phosg::split(s, '-');
|
auto tokens = phosg::split(s, '-');
|
||||||
if (tokens.size() != 2) {
|
if (tokens.size() != 2) {
|
||||||
throw runtime_error("dice spec must be of the form MIN-MAX");
|
throw std::runtime_error("dice spec must be of the form MIN-MAX");
|
||||||
}
|
}
|
||||||
return (stoul(tokens[0]) << 4) | (stoul(tokens[1]) & 0x0F);
|
return (stoul(tokens[0]) << 4) | (stoul(tokens[1]) & 0x0F);
|
||||||
};
|
};
|
||||||
auto parse_range_p = +[](const string& s) -> pair<uint8_t, uint8_t> {
|
auto parse_range_p = +[](const std::string& s) -> std::pair<uint8_t, uint8_t> {
|
||||||
auto tokens = phosg::split(s, '-');
|
auto tokens = phosg::split(s, '-');
|
||||||
if (tokens.size() != 2) {
|
if (tokens.size() != 2) {
|
||||||
throw runtime_error("dice spec must be of the form MIN-MAX");
|
throw std::runtime_error("dice spec must be of the form MIN-MAX");
|
||||||
}
|
}
|
||||||
return make_pair(stoul(tokens[0]), stoul(tokens[1]));
|
return std::make_pair(stoul(tokens[0]), std::stoul(tokens[1]));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto subtokens = phosg::split(token.substr(5), ':');
|
auto subtokens = phosg::split(token.substr(5), ':');
|
||||||
if (subtokens.size() < 1) {
|
if (subtokens.size() < 1) {
|
||||||
throw runtime_error("no dice ranges specified in dice= option");
|
throw std::runtime_error("no dice ranges specified in dice= option");
|
||||||
}
|
}
|
||||||
auto atk_range = parse_range_p(subtokens[0]);
|
auto atk_range = parse_range_p(subtokens[0]);
|
||||||
rules.min_dice_value = atk_range.first;
|
rules.min_dice_value = atk_range.first;
|
||||||
@@ -750,7 +747,7 @@ ShellCommand c_create_tournament(
|
|||||||
} else if (subtokens.size() == 4) {
|
} else if (subtokens.size() == 4) {
|
||||||
rules.def_dice_value_range_2v1 = parse_range_c(subtokens[3]);
|
rules.def_dice_value_range_2v1 = parse_range_c(subtokens[3]);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("too many range specs given");
|
throw std::runtime_error("too many range specs given");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rules.atk_dice_value_range_2v1 = 0;
|
rules.atk_dice_value_range_2v1 = 0;
|
||||||
@@ -762,18 +759,18 @@ ShellCommand c_create_tournament(
|
|||||||
rules.def_dice_value_range_2v1 = 0;
|
rules.def_dice_value_range_2v1 = 0;
|
||||||
}
|
}
|
||||||
} else if (token.starts_with("overall-time-limit=")) {
|
} else if (token.starts_with("overall-time-limit=")) {
|
||||||
uint32_t limit = stoul(token.substr(19));
|
uint32_t limit = std::stoul(token.substr(19));
|
||||||
if (limit > 600) {
|
if (limit > 600) {
|
||||||
throw runtime_error("overall-time-limit must be 600 or fewer minutes");
|
throw std::runtime_error("overall-time-limit must be 600 or fewer minutes");
|
||||||
}
|
}
|
||||||
if (limit % 5) {
|
if (limit % 5) {
|
||||||
throw runtime_error("overall-time-limit must be a multiple of 5 minutes");
|
throw std::runtime_error("overall-time-limit must be a multiple of 5 minutes");
|
||||||
}
|
}
|
||||||
rules.overall_time_limit = limit;
|
rules.overall_time_limit = limit;
|
||||||
} else if (token.starts_with("phase-time-limit=")) {
|
} else if (token.starts_with("phase-time-limit=")) {
|
||||||
rules.phase_time_limit = stoul(token.substr(17));
|
rules.phase_time_limit = std::stoul(token.substr(17));
|
||||||
} else if (token.starts_with("hp=")) {
|
} else if (token.starts_with("hp=")) {
|
||||||
rules.char_hp = stoul(token.substr(3));
|
rules.char_hp = std::stoul(token.substr(3));
|
||||||
} else if (token == "allowed-cards=all") {
|
} else if (token == "allowed-cards=all") {
|
||||||
rules.allowed_cards = Episode3::AllowedCards::ALL;
|
rules.allowed_cards = Episode3::AllowedCards::ALL;
|
||||||
} else if (token == "allowed-cards=n") {
|
} else if (token == "allowed-cards=n") {
|
||||||
@@ -815,11 +812,11 @@ ShellCommand c_create_tournament(
|
|||||||
} else if (token == "dice-exchange=none") {
|
} else if (token == "dice-exchange=none") {
|
||||||
rules.dice_exchange_mode = Episode3::DiceExchangeMode::NONE;
|
rules.dice_exchange_mode = Episode3::DiceExchangeMode::NONE;
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid rules option: " + token);
|
throw std::runtime_error("invalid rules option: " + token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deque<string> ret;
|
std::deque<std::string> ret;
|
||||||
if (rules.check_and_reset_invalid_fields()) {
|
if (rules.check_and_reset_invalid_fields()) {
|
||||||
ret.emplace_back("Warning: Some rules were invalid and reset to defaults");
|
ret.emplace_back("Warning: Some rules were invalid and reset to defaults");
|
||||||
}
|
}
|
||||||
@@ -832,19 +829,19 @@ ShellCommand c_delete_tournament(
|
|||||||
"delete-tournament", "delete-tournament TOURNAMENT-NAME\n\
|
"delete-tournament", "delete-tournament TOURNAMENT-NAME\n\
|
||||||
Delete a tournament. Quotes are required around the tournament name unless\n\
|
Delete a tournament. Quotes are required around the tournament name unless\n\
|
||||||
the name contains no spaces.",
|
the name contains no spaces.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
string name = get_quoted_string(args.args);
|
std::string name = get_quoted_string(args.args);
|
||||||
if (args.s->ep3_tournament_index->delete_tournament(name)) {
|
if (args.s->ep3_tournament_index->delete_tournament(name)) {
|
||||||
co_return deque<string>{"Deleted tournament"};
|
co_return std::deque<std::string>{"Deleted tournament"};
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("tournament does not exist");
|
throw std::runtime_error("tournament does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ShellCommand c_list_tournaments(
|
ShellCommand c_list_tournaments(
|
||||||
"list-tournaments", "list-tournaments\n\
|
"list-tournaments", "list-tournaments\n\
|
||||||
List the names and numbers of all existing tournaments.",
|
List the names and numbers of all existing tournaments.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
deque<string> ret;
|
std::deque<std::string> ret;
|
||||||
for (const auto& it : args.s->ep3_tournament_index->all_tournaments()) {
|
for (const auto& it : args.s->ep3_tournament_index->all_tournaments()) {
|
||||||
ret.emplace_back(" " + it.second->get_name());
|
ret.emplace_back(" " + it.second->get_name());
|
||||||
}
|
}
|
||||||
@@ -854,30 +851,30 @@ ShellCommand c_start_tournament(
|
|||||||
"start-tournament", "start-tournament TOURNAMENT-NAME\n\
|
"start-tournament", "start-tournament TOURNAMENT-NAME\n\
|
||||||
End registration for a tournament and allow matches to begin. Quotes are\n\
|
End registration for a tournament and allow matches to begin. Quotes are\n\
|
||||||
required around the tournament name unless the name contains no spaces.",
|
required around the tournament name unless the name contains no spaces.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
string name = get_quoted_string(args.args);
|
std::string name = get_quoted_string(args.args);
|
||||||
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
|
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
|
||||||
if (tourn) {
|
if (tourn) {
|
||||||
tourn->start();
|
tourn->start();
|
||||||
args.s->ep3_tournament_index->save();
|
args.s->ep3_tournament_index->save();
|
||||||
tourn->send_all_state_updates();
|
tourn->send_all_state_updates();
|
||||||
send_ep3_text_message_fmt(args.s, "$C7The tournament\n$C6{}$C7\nhas begun", tourn->get_name());
|
send_ep3_text_message_fmt(args.s, "$C7The tournament\n$C6{}$C7\nhas begun", tourn->get_name());
|
||||||
co_return deque<string>{"Tournament started"};
|
co_return std::deque<std::string>{"Tournament started"};
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("tournament does not exist");
|
throw std::runtime_error("tournament does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ShellCommand c_describe_tournament(
|
ShellCommand c_describe_tournament(
|
||||||
"describe-tournament", "describe-tournament TOURNAMENT-NAME\n\
|
"describe-tournament", "describe-tournament TOURNAMENT-NAME\n\
|
||||||
Show the current state of a tournament. Quotes are required around the\n\
|
Show the current state of a tournament. Quotes are required around the\n\
|
||||||
tournament name unless the name contains no spaces.",
|
tournament name unless the name contains no spaces.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
string name = get_quoted_string(args.args);
|
std::string name = get_quoted_string(args.args);
|
||||||
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
|
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
|
||||||
if (tourn) {
|
if (tourn) {
|
||||||
co_return deque<string>{tourn->bracket_str()};
|
co_return std::deque<std::string>{tourn->bracket_str()};
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("tournament does not exist");
|
throw std::runtime_error("tournament does not exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -891,16 +888,16 @@ ShellCommand c_cc(
|
|||||||
via this command are exempt from permission checks, so commands that\n\
|
via this command are exempt from permission checks, so commands that\n\
|
||||||
require cheat mode or debug mode are always available via cc even if the\n\
|
require cheat mode or debug mode are always available via cc even if the\n\
|
||||||
player cannot normamlly use them.",
|
player cannot normamlly use them.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto c = args.get_client();
|
auto c = args.get_client();
|
||||||
co_await on_chat_command(c, args.args, false);
|
co_await on_chat_command(c, args.args, false);
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
asio::awaitable<deque<string>> f_sc_ss(ShellCommand::Args& args) {
|
asio::awaitable<std::deque<std::string>> f_sc_ss(ShellCommand::Args& args) {
|
||||||
string data = phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
std::string data = phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||||
if (data.size() == 0) {
|
if (data.size() == 0) {
|
||||||
throw invalid_argument("no data given");
|
throw std::invalid_argument("no data given");
|
||||||
}
|
}
|
||||||
data.resize((data.size() + 3) & (~3));
|
data.resize((data.size() + 3) & (~3));
|
||||||
|
|
||||||
@@ -915,7 +912,7 @@ asio::awaitable<deque<string>> f_sc_ss(ShellCommand::Args& args) {
|
|||||||
send_command_with_header(c->channel, data.data(), data.size());
|
send_command_with_header(c->channel, data.data(), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
ShellCommand c_sc("sc", "sc DATA\n\
|
ShellCommand c_sc("sc", "sc DATA\n\
|
||||||
@@ -929,10 +926,10 @@ ShellCommand c_show_slots(
|
|||||||
"show-slots", "show-slots\n\
|
"show-slots", "show-slots\n\
|
||||||
Show the player names, Guild Card numbers, and client IDs of all players in\n\
|
Show the player names, Guild Card numbers, and client IDs of all players in\n\
|
||||||
the current lobby or game.",
|
the current lobby or game.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto c = args.get_proxy_client();
|
auto c = args.get_proxy_client();
|
||||||
|
|
||||||
deque<string> ret;
|
std::deque<std::string> ret;
|
||||||
for (size_t z = 0; z < c->proxy_session->lobby_players.size(); z++) {
|
for (size_t z = 0; z < c->proxy_session->lobby_players.size(); z++) {
|
||||||
const auto& player = c->proxy_session->lobby_players[z];
|
const auto& player = c->proxy_session->lobby_players[z];
|
||||||
if (player.guild_card_number) {
|
if (player.guild_card_number) {
|
||||||
@@ -942,13 +939,13 @@ ShellCommand c_show_slots(
|
|||||||
name_for_char_class(player.char_class),
|
name_for_char_class(player.char_class),
|
||||||
name_for_section_id(player.section_id)));
|
name_for_section_id(player.section_id)));
|
||||||
} else {
|
} else {
|
||||||
ret.emplace_back(format(" {}: (no player)", z));
|
ret.emplace_back(std::format(" {}: (no player)", z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
co_return ret;
|
co_return ret;
|
||||||
});
|
});
|
||||||
|
|
||||||
asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
asio::awaitable<std::deque<std::string>> fn_chat(ShellCommand::Args& args) {
|
||||||
auto c = args.get_client();
|
auto c = args.get_client();
|
||||||
bool is_dchat = (args.command == "dchat");
|
bool is_dchat = (args.command == "dchat");
|
||||||
|
|
||||||
@@ -956,7 +953,7 @@ asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
|||||||
if (!is_dchat && uses_utf16(c->version())) {
|
if (!is_dchat && uses_utf16(c->version())) {
|
||||||
send_chat_message_from_client(c->proxy_session->server_channel, args.args, 0);
|
send_chat_message_from_client(c->proxy_session->server_channel, args.args, 0);
|
||||||
} else {
|
} else {
|
||||||
string data(8, '\0');
|
std::string data(8, '\0');
|
||||||
data.push_back('\x09');
|
data.push_back('\x09');
|
||||||
data.push_back('E');
|
data.push_back('E');
|
||||||
if (is_dchat) {
|
if (is_dchat) {
|
||||||
@@ -969,16 +966,19 @@ asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
|||||||
c->proxy_session->server_channel->send(0x06, 0x00, data);
|
c->proxy_session->server_channel->send(0x06, 0x00, data);
|
||||||
}
|
}
|
||||||
} else if (c->login) {
|
} else if (c->login) {
|
||||||
string text = is_dchat ? phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES) : args.args;
|
std::string text = is_dchat
|
||||||
|
? phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES)
|
||||||
|
: args.args;
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
for (auto& lc : l->clients) {
|
for (auto& lc : l->clients) {
|
||||||
if (lc) {
|
if (lc) {
|
||||||
send_chat_message(lc, c->login->account->account_id, c->character_file()->disp.name.decode(c->language()), text, 0);
|
send_chat_message(
|
||||||
|
lc, c->login->account->account_id, c->character_file()->disp.visual.name.decode(c->language()), text, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
}
|
}
|
||||||
ShellCommand c_c("c", "c TEXT", fn_chat);
|
ShellCommand c_c("c", "c TEXT", fn_chat);
|
||||||
ShellCommand c_chat("chat", "chat TEXT\n\
|
ShellCommand c_chat("chat", "chat TEXT\n\
|
||||||
@@ -988,13 +988,13 @@ ShellCommand c_dchat("dchat", "dchat DATA\n\
|
|||||||
Send a chat message to the server with arbitrary data in it.",
|
Send a chat message to the server with arbitrary data in it.",
|
||||||
fn_chat);
|
fn_chat);
|
||||||
|
|
||||||
asio::awaitable<deque<string>> fn_wchat(ShellCommand::Args& args) {
|
asio::awaitable<std::deque<std::string>> fn_wchat(ShellCommand::Args& args) {
|
||||||
auto c = args.get_client();
|
auto c = args.get_client();
|
||||||
if (!is_ep3(c->version())) {
|
if (!is_ep3(c->version())) {
|
||||||
throw runtime_error("wchat can only be used on Episode 3");
|
throw std::runtime_error("wchat can only be used on Episode 3");
|
||||||
}
|
}
|
||||||
if (c->proxy_session) {
|
if (c->proxy_session) {
|
||||||
string data(8, '\0');
|
std::string data(8, '\0');
|
||||||
data.push_back('\x40'); // private_flags: visible to all
|
data.push_back('\x40'); // private_flags: visible to all
|
||||||
data.push_back('\x09');
|
data.push_back('\x09');
|
||||||
data.push_back('E');
|
data.push_back('E');
|
||||||
@@ -1007,11 +1007,15 @@ asio::awaitable<deque<string>> fn_wchat(ShellCommand::Args& args) {
|
|||||||
for (auto& lc : l->clients) {
|
for (auto& lc : l->clients) {
|
||||||
if (lc) {
|
if (lc) {
|
||||||
send_chat_message(
|
send_chat_message(
|
||||||
lc, c->login->account->account_id, c->character_file()->disp.name.decode(c->language()), args.args, 0x40);
|
lc,
|
||||||
|
c->login->account->account_id,
|
||||||
|
c->character_file()->disp.visual.name.decode(c->language()),
|
||||||
|
args.args,
|
||||||
|
0x40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
}
|
}
|
||||||
ShellCommand c_wc("wc", "wc TEXT", fn_wchat);
|
ShellCommand c_wc("wc", "wc TEXT", fn_wchat);
|
||||||
ShellCommand c_wchat("wchat", "wchat TEXT\n\
|
ShellCommand c_wchat("wchat", "wchat TEXT\n\
|
||||||
@@ -1021,20 +1025,20 @@ ShellCommand c_wchat("wchat", "wchat TEXT\n\
|
|||||||
ShellCommand c_marker(
|
ShellCommand c_marker(
|
||||||
"marker", "marker COLOR-ID\n\
|
"marker", "marker COLOR-ID\n\
|
||||||
Change your lobby marker color.",
|
Change your lobby marker color.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto c = args.get_proxy_client();
|
auto c = args.get_proxy_client();
|
||||||
c->proxy_session->server_channel->send(0x89, stoul(args.args));
|
c->proxy_session->server_channel->send(0x89, std::stoul(args.args));
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
asio::awaitable<deque<string>> fn_warp(ShellCommand::Args& args) {
|
asio::awaitable<std::deque<std::string>> fn_warp(ShellCommand::Args& args) {
|
||||||
auto c = args.get_proxy_client();
|
auto c = args.get_proxy_client();
|
||||||
uint8_t floor = stoul(args.args);
|
uint8_t floor = std::stoul(args.args);
|
||||||
send_warp(c->channel, c->lobby_client_id, floor, true);
|
send_warp(c->channel, c->lobby_client_id, floor, true);
|
||||||
if (args.command == "warpall") {
|
if (args.command == "warpall") {
|
||||||
send_warp(c->proxy_session->server_channel, c->lobby_client_id, floor, false);
|
send_warp(c->proxy_session->server_channel, c->lobby_client_id, floor, false);
|
||||||
}
|
}
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
}
|
}
|
||||||
ShellCommand c_warp("warp", "warp FLOOR-ID", fn_warp);
|
ShellCommand c_warp("warp", "warp FLOOR-ID", fn_warp);
|
||||||
ShellCommand c_warpme("warpme", "warpme FLOOR-ID\n\
|
ShellCommand c_warpme("warpme", "warpme FLOOR-ID\n\
|
||||||
@@ -1044,10 +1048,10 @@ ShellCommand c_warpall("warpall", "warpall FLOOR-ID\n\
|
|||||||
Send everyone to a specific floor.",
|
Send everyone to a specific floor.",
|
||||||
fn_warp);
|
fn_warp);
|
||||||
|
|
||||||
asio::awaitable<deque<string>> fn_info_board(ShellCommand::Args& args) {
|
asio::awaitable<std::deque<std::string>> fn_info_board(ShellCommand::Args& args) {
|
||||||
auto c = args.get_proxy_client();
|
auto c = args.get_proxy_client();
|
||||||
|
|
||||||
string data;
|
std::string data;
|
||||||
if (args.command == "info-board-data") {
|
if (args.command == "info-board-data") {
|
||||||
data += phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
data += phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||||
} else {
|
} else {
|
||||||
@@ -1057,7 +1061,7 @@ asio::awaitable<deque<string>> fn_info_board(ShellCommand::Args& args) {
|
|||||||
data.resize((data.size() + 3) & (~3));
|
data.resize((data.size() + 3) & (~3));
|
||||||
|
|
||||||
c->proxy_session->server_channel->send(0xD9, 0x00, data);
|
c->proxy_session->server_channel->send(0xD9, 0x00, data);
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
}
|
}
|
||||||
ShellCommand c_info_board("info-board", "info-board TEXT\n\
|
ShellCommand c_info_board("info-board", "info-board TEXT\n\
|
||||||
Set your info board contents. This will affect the current session only,\n\
|
Set your info board contents. This will affect the current session only,\n\
|
||||||
@@ -1072,17 +1076,17 @@ ShellCommand c_info_board_data("info-board-data", "info-board-data DATA\n\
|
|||||||
ShellCommand c_create_item(
|
ShellCommand c_create_item(
|
||||||
"create-item", "create-item DATA\n\
|
"create-item", "create-item DATA\n\
|
||||||
Create an item as if the client had run the $item command.",
|
Create an item as if the client had run the $item command.",
|
||||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||||
auto c = args.get_proxy_client();
|
auto c = args.get_proxy_client();
|
||||||
|
|
||||||
if (c->version() == Version::BB_V4) {
|
if (c->version() == Version::BB_V4) {
|
||||||
throw runtime_error("proxy session is BB");
|
throw std::runtime_error("proxy session is BB");
|
||||||
}
|
}
|
||||||
if (!c->proxy_session->is_in_game) {
|
if (!c->proxy_session->is_in_game) {
|
||||||
throw runtime_error("proxy session is not in a game");
|
throw std::runtime_error("proxy session is not in a game");
|
||||||
}
|
}
|
||||||
if (c->lobby_client_id != c->proxy_session->leader_client_id) {
|
if (c->lobby_client_id != c->proxy_session->leader_client_id) {
|
||||||
throw runtime_error("proxy session is not game leader");
|
throw std::runtime_error("proxy session is not game leader");
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemData item = args.s->parse_item_description(c->version(), args.args);
|
ItemData item = args.s->parse_item_description(c->version(), args.args);
|
||||||
@@ -1091,7 +1095,7 @@ ShellCommand c_create_item(
|
|||||||
send_drop_stacked_item_to_channel(args.s, c->channel, item, c->floor, c->pos);
|
send_drop_stacked_item_to_channel(args.s, c->channel, item, c->floor, c->pos);
|
||||||
send_drop_stacked_item_to_channel(args.s, c->proxy_session->server_channel, item, c->floor, c->pos);
|
send_drop_stacked_item_to_channel(args.s, c->proxy_session->server_channel, item, c->floor, c->pos);
|
||||||
|
|
||||||
string name = args.s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
std::string name = args.s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
||||||
send_text_message(c->channel, "$C7Item created:\n" + name);
|
send_text_message(c->channel, "$C7Item created:\n" + name);
|
||||||
co_return deque<string>{};
|
co_return std::deque<std::string>{};
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user